Last active
August 29, 2015 14:07
-
-
Save gucchan22/20a7d772492eaba830c7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string.h> | |
#include <stdint.h> | |
#include <iostream> | |
#include <fstream> | |
#include "SDL.h" | |
class RAM; | |
class CPU; | |
class PPU; | |
class RAM { | |
VM& vm; | |
uint8_t mem[2048]; // 2KB RAM | |
public: | |
explicit RAM(VM &nes) : vm(nes) {} | |
~RAM() {} | |
void hardwareReset() { | |
// ref: http://wiki.nesdev.com/w/index.php/CPU_power_up_state | |
memset(mem, 0xff, 2048); | |
mem[0x0008] = 0xf7; | |
mem[0x0009] = 0xef; | |
mem[0x000a] = 0xdf; | |
mem[0x000f] = 0xbf; | |
} | |
void softwareReset() {} | |
// addr & 0x7ff = addr mod 2048 | |
uint8_t read(const uint16_t addr) const { return mem[addr & 0x7ff]; } | |
void write(const uint16_t addr, const uint8_t v) { mem[addr & 0x7ff] = v; } | |
}; | |
/* | |
0x0000: RAM | |
0x0800: RAM Mirroring | |
<- 4回ループ (RAMには0x0000 ~ 0x1ffffまで割当てられてミラーリングされている) -> | |
0x2000: I/O Port (for PPU) | |
0x2008: I/O Port (for PPU) Mirroring | |
<- 1024回ループ (RAMには0x2000 ~ 0x3fffまで割当てられてミラーリングされている) -> | |
0x4000: I/O Port (for APU) | |
0x4020: Extend-RAM | |
0x6000: Savedata | |
0x8000~0xffff: Cartridge | |
*/ | |
class VM { | |
IO &io; | |
RAM &ram; | |
CPU &cpu; | |
PPU &ppu; | |
ROMController *mmc; | |
int32_t clock_d; | |
const int32_t cpu_clock_divisor = 12; | |
public: | |
class VirtualMachineAddressException : public std::exception {}; | |
explicit VM(IO& iop, CPU& nes_cpu, RAM& nes_ram, PPU& nes_ppu ROMController *nrom) : | |
io(iop), cpu(nes_cpu), ram(nes_ram), ppu(nes_ppu), mmc(nrom), clock_d(0) {} | |
~VM() {} | |
void addClock(uint32_t op_clock) { clock_d += op_clock; } | |
void run() { | |
cpu.run(); | |
//ppu.run(); (Graphics) | |
//apu.run(); (Audio) | |
} | |
void connectChrToPPU() {} // CHR-ROM -> PPU | |
uint8_t read(uint16_t addr) { | |
switch(addr & 0xe000) { | |
case 0x0000: return ram.read(addr); | |
case 0x2000: return ppu.read(addr); | |
// for Cartridge (0x8000 ~ 0xa000: Low, 0xc000 ~ 0xe000: High) | |
case 0x8000: | |
case 0xa000: | |
case 0xc000: | |
case 0xe000: | |
return mmc->readRom(addr); | |
default: throw VirtualMachineAdressException; | |
} | |
} | |
void write(uint16_t addr, const uint8_t v) { | |
switch(addr & 0xe000) { | |
case 0x0000: ram.write(addr, v); break; | |
case 0x2000: ppu.write(addr, v); break; | |
// 0x8000 ~ 0xe000 -> mmc->writeL/H (Mapper-N) | |
default: throw VirtualMachineAddressException; | |
} | |
} | |
}; | |
/* | |
I/O Port (Joypad, APU Sound etc.,) | |
ref: http://hp.vector.co.jp/authors/VA042397/nes/ioport.html | |
ref: http://wiki.nesdev.com/w/index.php/Input_devices | |
*/ | |
class IO { | |
VM& vm; | |
public: | |
IO(VM& nes) : vm(nes) {} | |
~IO() {} | |
}; | |
/* | |
iNES (*.nes) ROM File format. | |
ref: http://wiki.nesdev.com/w/index.php/INES | |
*/ | |
class INES { | |
const char *rom_name; | |
uint8_t *prg_dat = NULL; | |
uint8_t *chr_dat = NULL; | |
int prg_page, chr_page, prg_rom_size, chr_rom_size, mapper; | |
enum MirrorType { | |
HORIZONTAL, VERTICAL | |
}; | |
MirrorType mirroring; | |
bool is_enable_sram, is_enable_trainer, is_enable_four_screen; | |
bool loaded = false; | |
public: | |
explicit INES(const std::string& fname) : rom_name(fname.c_str()) {} | |
~INES() { | |
delete [] prg_dat; | |
delete [] chr_dat; | |
} | |
bool loadROM() { | |
std::ifstream rom(rom_name, std::ios::binary | std::ios::in); // rb | |
char magic_byte[4]; | |
uint8_t load_size; | |
int tt, tu; | |
rom.read(magic_byte, 4); | |
if(strncmp(magic_byte, "NES\x1A", 4) != 0) { | |
rom.read(reinterpret_cast<char*>(&tt),1); | |
rom.read(reinterpret_cast<char*>(&tu),1); | |
prg_page = tt; | |
chr_page = tu; | |
prg_rom_size = prg_page * 0x4000; | |
chr_rom_size = chr_page * 0x2000; | |
rom.read(reinterpret_cast<char*>(&tt), 1); | |
rom.read(reinterpret_cast<char*>(&tu), 1); | |
mirroring = ((tt & 1) == 0 ? HORIZONTAL : VERTICAL); | |
is_enable_sram = (tt & (1 << 1)) != 0; | |
is_enable_trainer = (tt & (1 << 2)) != 0; | |
is_enable_four_screen = (tt & (1 << 3)) != 0; | |
mapper = (tt >> 4) | (tu & 0xf0); | |
loaded = true; | |
rom.seekg(16, std::ios_base::beg); // skip iNES-header. | |
if(is_enable_trainer || mapper != 0) return false; // trainer if present. header(16byte) -> trainer patch(0 or 512byte) || mapper-0 | |
// load PRG/CHR-ROM | |
prg_dat = new uint8_t[prg_rom_size]; | |
chr_dat = new uint8_t[chr_rom_size]; | |
rom.read(prg_dat, prg_rom_size); | |
rom.read(chr_dat, chr_rom_size); | |
return true; | |
} | |
return false; | |
} | |
void inspect() const { | |
if(loaded) { | |
std::cout << "ROM Information (iNES(*.nes) Format):\n"; | |
std::cout << "\t\tProgram ROM Size\t\t: " << (prg_rom_size / 1024) << "KB(" << prg_rom_size << "Bytes.," << prg_page << "Pages)\n"; | |
std::cout << "\t\tCharacter ROM Size: " << (chr_rom_size / 1024) << "KB(" << chr_rom_size << "Bytes.," << chr_page << "Pages.)\n"; | |
std::cout << "Flags:\n"; | |
std::cout << "\t\tMirroring: " << (mirroring == VERTICAL : "VERTICAL" : "HORIZONTAL") << ".\n"; | |
std::cout << "\t\tSRAM: " << (is_enable_sram == 1 ? "Enabled" : "Disabled") << ".\n"; | |
std::cout << "\t\tFour Screen:" << (is_enable_four_screen == 1 ? "Enabled" : "Disabled") << std::endl; | |
} | |
} | |
uint8_t* getChrRom() { return chr_rom; } | |
uint8_t* getPrgRom() { return prg_rom; } | |
bool isEnableSram() const { return is_enable_sram; } | |
bool isEnableMapper() const { return is_enable_trainer; } | |
int getPrgRomSize() const { return prg_rom_size; } | |
int getChrRomSize() const { return chr_rom_size; } | |
int getPrgPage() const { return prg_page; } | |
int getChrPage() const { return chr_page; } | |
MirrorType getMirrorType() const { return mirroring; } | |
}; | |
/* | |
NES Cartridge | |
for Nintendo Mapper-0 (16KB-ROM, 32KB-ROM) ONLY | |
ref: http://wiki.nesdev.com/w/index.php/INES_Mapper_000 | |
*/ | |
class ROMController { | |
INES *rom; | |
uint16_t mirror_addr; | |
uint8_t chr_mem[0x2000]; // 1024byte | |
bool chr_zflag = true; | |
public: | |
explicit ROMController(INES* ines) : vm(nes), rom(ines) { | |
if(rom->getChrPage() == 0) { | |
memset(chr_mem, 0, 0x2000); | |
chr_zflag = true; | |
} | |
mirror_addr = (rom->getPrgPage() > 1 ? 0x7fff : 0x3fff); | |
} | |
~ROMController() { delete rom; } | |
// addr mod 8192 (mirroring.) | |
void writeChrPatt(uint16_t addr, uint8_t v) { | |
if(chr_zflag) chr_mem[addr & 0x1fff] = v; | |
} | |
uint8_t readChrPatt(uint16_t addr) { return chr_zflag ? chr_mem[addr & 0x1fff] : rom->readChrRom()[addr & 0x1fff]; } | |
uint8_t readRom(uint16_t addr) { return rom->getPrgRom()[addr & mirror_addr]; } | |
}; | |
/* | |
PPU (Picture Processing Unit) | |
1 CPU-CLK = 3 PPU-CLK (cpu_clock * 3 == ppu_clock) | |
PPU Memory: VRAM + OAM. | |
Character: | |
16pixel = 16byte | |
8*8 pixel (Background=Character-pixel*0xff, Sprite=Character-pixel*0xff) -> Pattern-table. | |
1pixel = 2bit (64*n=k(bit), k*1/8=16(byte), n=2(bit). 2^n=4color (color=(C1,C2,C3,Tr.))) | |
Attribute-Table: | |
Palette-Table -> select 4-colors. -> Character. | |
Palette-Table: | |
NES: 64color | |
background=16colors, splite=16(12colors + 4Trs.) | |
Background: | |
SCREEN_SIZE = 256 * 240 | |
*/ | |
class PPU { | |
VM& vm; | |
/* | |
ref: http://wiki.nesdev.com/w/index.php/OAM | |
PPU OAM(Object-Attribute RAM) Memory Map: | |
byte0: pos-y | |
byte1: Character-num | |
byte2: Settings (bit-flag) | |
byte2-(0~1): palette-table (2bit h) | |
byte2-(2~4): *reserved* | |
byte2-5: 0=back, 1=front | |
byte2-6: 1:左右反転 | |
byte2-7: 1:上下反転 | |
byte3: pos-x | |
*/ | |
class OAM { | |
public: | |
OAM() {} | |
~OAM() {} | |
}; | |
/* | |
ref: http://wiki.nesdev.com/w/index.php/PPU_palettes | |
ref: http://wiki.nesdev.com/w/index.php/PPU_memory_map | |
+-------+-------+ | |
+ A + B + | |
+-------+-------+ | |
+ C + D + | |
+-------+-------+ | |
PPU VRAM(Video-RAM) Memory Map: | |
0x0000 ~ 0x0fff: Pattern-table1 -> Cartridge (pixel) | |
0x1000 ~ 0x1fff: Pattern-table2 -> Cartridge (pixel) | |
0x2000 ~ 0x23bf: Name-table A -> | |
0x23c0 ~ 0x23ff: Attribute-table A -> | |
0x2400 ~ 0x27bf: Name-table B -> | |
0x27c0 ~ 0x27ff: Attribute-table B -> | |
0x2800 ~ 0x2bbf: Name-table C -> | |
0x2bc0 ~ 0x2bff: Attribute-table C -> | |
0x2c00 ~ 0x2fbf: Name-table D -> | |
0x2fc0 ~ 0x2fff: Attribute-table D -> | |
0x3000 ~ 0x3eff: Name-table (0x2000 ~ 0x2eff) mirror | |
0x3f00 ~ 0x3f1f: palette-table (0x3f00 ~ 0x3f0f: bg, 0x3f10~0x3f1f: sprite) | |
0x3d20 ~ 0x3fff: palette-table (0x3f00 ~ 0x3f1f) mirror | |
*/ | |
class VRAM { | |
PPU *ppu; | |
uint8_t video_mem[0x3fff + 1]; // 0x0000 ~ 0x3ffff | |
public: | |
VRAM(PPU *nes_ppu) : ppu(nes_ppu) {} | |
~VRAM() {} | |
}; | |
uint8_t readSpriteData(uint16_t addr) const { return /*OAM[addr]*/; } | |
void writeSpriteData(uint16_t addr, uint8_t v) { /* OAM[addr] = v; */ } | |
public: | |
explicit PPU(VM& nes) : vm(nes) {} | |
~PPU() {} | |
// VM -> PPU I/O-Port (0x2000 ~ 0x3ffff) | |
// ref: http://wiki.nesdev.com/w/index.php/PPU_registers | |
uint8_t read(const uint16_t addr) { | |
switch(addr & 0xf) { | |
case 0x02: return getPPURegister(); | |
case 0x04: return getSpriteRegister(); | |
case 0x07: return getVRAMRegister(): | |
} | |
} | |
void write(const uint16_t addr, uint8_t v) { | |
switch(addr & 0xf) { | |
case 0x00: setUpPPURegister(v); break; | |
case 0x01: setUpMaskConfig(v); break; | |
case 0x03: setSpriteDataAddr(v); break; // OAM address | |
case 0x04: setSpriteData(v); break; // OAM data | |
case 0x05: setUpHardwareScroll(v); break; | |
case 0x06: setVRAMDataAddr(v); break; | |
case 0x07: setVRAMData(v); break; | |
} | |
} | |
// Read | |
uint8_t getPPURegister() { | |
vramAddrRegisterWritten = false; | |
scrollRegisterWritten = false; | |
uint8_t ppu_reg = ((VBLANKHasStarted ? 256 : 0) | (sprite0Hit ? 64 : 0) | (spriteOverflow ? 32 : 0)); | |
VBLANKHasStarted = false; | |
return ppu_reg; | |
} | |
uint8_t getSpriteRegister() { return readSpriteData(spliteAddr); } | |
uint8_t getVRAMRegister() {} | |
// Write | |
void setUpPPURegister() {} | |
void setUpMaskConfig() {} | |
void setSpriteDataAddr() {} | |
void setSpriteData() {} | |
void setUpHardwareScroll() {} | |
void setVRAMDataAddr() {} | |
void setVRAMData() {} | |
}; | |
/* | |
6502-Processor(MPU, RP2A03 RICOH) | |
Registers: | |
(8bit): A, X, Y, SP, P | |
(16bit): PC | |
Details of P(Processor Status Register): | |
N .. Nagative flag | |
V .. oVerflow flag | |
R .. Reserved flag (always P[R]=1) | |
B .. Break flag | |
D .. Decimal flag | |
I .. Interrupt flag | |
Z .. Zero flag | |
C .. Carry flag | |
ref: http://pgate1.at-ninja.jp/NES_on_FPGA/nes_cpu.htm | |
ref: http://hp.vector.co.jp/authors/VA042397/nes/6502.html | |
*/ | |
class CPU { | |
VM& vm; | |
uint8_t reg_a, reg_x, reg_y, sp, psr; | |
uint16_t pc = 0x0000; | |
bool nmi_flag = false; | |
bool irq_flag = false; | |
enum PSRFlags { | |
CARRY_FLAG = 1, | |
ZERO_FLAG = 2, | |
INTERRUPT_FLAG = 4, | |
DECIMAL_FLAG = 8, | |
BREAK_FLAG = 16, | |
INIT_FLAG = 32, // P[R]=1のみ立っている初期フラグ(0b00100000) | |
OVERFLOW_FLAG = 64, | |
NEGATIVE_FLAG = 128 | |
}; | |
uint8_t psr_table[0x100]; | |
enum RegType { RX, RY }; | |
// Stack (0x0100 ~ 0x01ff (256byte fixed)) | |
class Stack { | |
CPU* cpu_ptr; | |
const uint16_t base_addr = 0x0100; | |
public: | |
Stack(CPU* cpu) : cpu_ptr(cpu) {} | |
~Stack() {} | |
void push(uint8_t v) { | |
cpu_ptr->write((base_addr | cpu_ptr->sp), v); | |
cpu_ptr->sp--; | |
} | |
uint8_t pop() { | |
cpu_ptr->sp++; | |
return cpu_ptr->read(base_addr | cpu_ptr->sp); | |
} | |
}; | |
Stack stack = this; | |
/* オペコード毎の実行クロック数 | |
uint8_t opcode = this->read(this->pc++); | |
printf("%d¥n",clock_table[opcode]); | |
(ref): http://pgate1.at-ninja.jp/NES_on_FPGA/nes_cpu.htm | |
(ref): http://wiki.nesdev.com/w/index.php/Clock_rate | |
(ref): http://wiki.nesdev.com/w/index.php/CPU | |
*/ | |
const int clock_table[0x100] = { | |
7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, | |
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, | |
6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, | |
6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, | |
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, | |
2, 5, 2, 6, 4, 4, 4, 4, 2, 4, 2, 5, 5, 4, 5, 5, | |
2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, | |
2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, | |
2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7, | |
2, 6, 3, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, | |
2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 6, 7 | |
}; | |
uint8_t read(uint16_t addr) { return vm.read(addr); } | |
void write(uint16_t addr, uint8_t v) { vm.write(addr, v); } | |
void update_psr(const uint8_t reg) { psr = ((psr & 0x7d) | psr_table[reg]); } | |
void load(uint8_t ®, const uint16_t addr) { | |
reg = read(addr); | |
update_psr(reg); | |
} | |
public: | |
explicit CPU(VM& nes) : vm(nes) { | |
for(int i = 0; i <= 0xff; i++) { | |
psr_table[i] = (i == 0 ? 0x02 : i <= 0x7f ? 0x00 : 0x80); | |
} | |
} | |
~CPU() {} | |
// ref: http://wiki.nesdev.com/w/index.php/CPU_power_up_state (At power up) | |
void hardwareReset() { | |
psr = 0x34; // 0b00110100 = IRQ disabled. | |
reg_a = reg_x = reg_y = 0; | |
sp = 0xfd; | |
vm.write(0x0417, 0); | |
vm.write(0x4015, 0); | |
for(uint16_t addr = 0x4000; addr < 0x400f; ++addr) vm.write(addr, 0); | |
pc = ((read(0xfffd) << 8) | read(0xfffc)); | |
} | |
// ref: http://wiki.nesdev.com/w/index.php/CPU_power_up_state (After reset) | |
void softwareReset() { | |
vm.addClock(6); | |
sp -= 3; | |
psr |= INTERRUPT_FLAG; // IRQ disable. | |
vm.write(0x4015, 0); | |
pc = ((read(0xfffd) << 8) | read(0xfffc)); | |
} | |
// Debug functions. | |
void debugPSR() const { | |
printf("Negative Flag(N)=%d\n", (psr & static_cast<int>(NEGATIVE_FLAG)) >> 7); | |
printf("oVerflow Flag(V)=%d\n", (psr & static_cast<int>(OVERFLOW_FLAG)) >> 6); | |
printf("Reserved Flag(R)=1\n"); | |
printf("Break Flag(B)=%d\n", (psr & static_cast<int>(BREAK_FLAG)) >> 4); | |
printf("Decimal Flag(D)=%d\n", (psr & static_cast<int>(DECIMAL_FLAG)) >> 3); | |
printf("InterruptI Flag(I)=%d\n", (psr & static_cast<int>(INTERRUPT_FLAG)) >> 2); | |
printf("Zero Flag(Z)=%d\n", (psr & static_cast<int>(ZERO_FLAG)) >> 1); | |
printf("Carry Flag(C)=%d\n", (psr & static_cast<int>(CARRY_FLAG))); | |
} | |
void debugRegisters() const { | |
std::cout << "PC(Program Counter) =" << std::hex << pc << "\n"; | |
std::cout << "SP(Stack Pointer) =" << std::hex << sp << "\n"; | |
std::cout << "A(Accumlator) =" << std::hex << reg_a << "\n"; | |
std::cout << "X(Index-X) =" << std::hex << reg_x << "\n"; | |
std::cout << "Y(Index-Y) =" << std::hex << reg_y << "\n" << std::endl; | |
} | |
/* Hardware-Interrupt | |
Interrupt-vector | |
0xfffa: NMI (P[I]=1,P[B]=0) | |
0xfffc: RESET (P[I]=1) | |
0xfffe: IRQ/BRK (P[I]=1, P[B]=0(IRQ), P[B]=1(BRK)) | |
*/ | |
void enableNMI() { nmi_flag = true; } | |
void reserveIRQ() { irq_flag = true; } | |
void releaseIRQ() { irq_flag = false; } | |
void onNMI() { | |
vm.addClock(7); | |
psr &= ~BREAK_FLAG; // ~BREAK_FLAG = 0b11110111 (P[B]=0) | |
stack.push(static_cast<uint8_t>((pc >> 8) & 0xff)); // pc(high) ->stack | |
stack.push(static_cast<uint8_t>(pc & 0xff)); // pc(low) -> stack | |
stack.push(psr); | |
psr |= INTERRUPT_FLAG; // P[I]=1 | |
pc = ((read(0xfffb) << 8) | read(0xfffa)); // interrupt-vector | |
} | |
void onIRQ() { | |
if((psr & INTERRUPT_FLAG) == INTERRUPT_FLAG) return; // P[I]=1ならIRQを拒否る | |
vm.addClock(7); | |
psr &= ~BREAK_FLAG; // P[B]=0 | |
stack.push(static_cast<uint8_t>((pc >> 8) & 0xff)); | |
stack.push(static_cast<uint8_t>(pc & 0xff)); | |
stack.push(psr); | |
psr |= INTERRUPT_FLAG; // P[I]=1 | |
pc = ((read(0xfffd) << 8) | read(0xfffc)); | |
} | |
// Adressing-Mode | |
uint16_t immediate() { return pc++; } | |
uint16_t zeroPage() { return static_cast<uint16_t>(vm.read(pc++)); } | |
uint16_t indexedZeroPage(const RegType r) { return static_cast<uint16_t>(vm.read(pc++) + (r == RX ? reg_x : reg_y)); } | |
uint16_t absolute() { // (Op Rl Rh) .. Op Rh*16+Rl | |
uint16_t addr = vm.read(pc++); | |
return addr | (vm.read(pc++) << 8); | |
} | |
uint16_t indexedAbsolute(const RegType r) { | |
uint16_t org = vm.read(pc++); | |
org = org | (vm.read(pc++) << 8); | |
const uint16_t addr = org + (r == RX ? reg_x : reg_y); | |
if(((addr ^ org) & 0x0100) != 0) vm.addClock(1); | |
return addr; | |
} | |
uint16_t absoluteIndirect() { | |
uint16_t src = vm.read(pc++); | |
src = src | (vm.read(pc++) << 8); | |
return vm.read(src) + (vm.read(src & 0xff00) | (((src + 1) & 0x00ff)) << 8); | |
} | |
uint16_t relative() { | |
int8_t tmp = static_cast<int8_t>(vm.read(pc++)); | |
return tmp + pc; | |
} | |
uint16_t indirectX() { | |
uint8_t idx = vm.read(pc++) + reg_x; | |
uint16_t addr = vm.read(idx++); | |
addr = addr | (vm.read(idx) << 8); | |
return addr; | |
} | |
uint16_t indirectY() { | |
uint8_t idx = vm.read(pc++); | |
uint16_t org = vm.read(idx); | |
org = org | (vm.read(pc) << 8); | |
const uint16_t addr = org + reg_y; | |
if(((addr ^ org) & 0x0100) != 0) vm.addClock(1); | |
return addr; | |
} | |
// Operations (ref: http://nesdev.com/opcodes.txt) | |
void opLda(uint16_t addr) { load(reg_a, vm.read(addr)); } | |
void opLdx(uint16_t addr) { load(reg_x, vm.read(addr)); } | |
void opLdy(uint16_t addr) { load(reg_y, vm.read(addr)); } | |
void opSta(uint16_t addr) { vm.write(addr, reg_a); } | |
void opStx(uint16_t addr) { vm.write(addr, reg_x); } | |
void opSty(uint16_t addr) { vm.write(addr, reg_y); } | |
// Add M to A with C (A+M+C->A) (Flags:N Z V C) | |
void opAdc(uint16_t addr) { | |
const uint8_t v = vm.read(addr); | |
const uint8_t res = reg_a + v + (psr & CARRY_FLAG); | |
const uint16_t a = static_cast<uint8_t>(res & 0xff); | |
psr = (psr & ~(OVERFLOW_FLAG | CARRY_FLAG)) | |
| ((((reg_a ^ v) & 0x80) ^ 0x80) & ((reg_a ^ a) & 0x80)) >> 1 | |
| ((res >> 8) & CARRY_FLAG); | |
reg_a = a; | |
update_psr(reg_a); | |
} | |
void opSbc(uint16_t addr) {} | |
// JuMP (JMP) (Flags:None) | |
void opJmp(uint16_t addr) { pc = addr; } | |
// Jump SubRoutine (JSR) (Flags:None) | |
void opJsr(uint16_t addr) { | |
pc--; | |
stack.push(static_cast<uint8_t>((pc >> 8) & 0xff)); | |
stack.push(static_cast<uint8_t>(pc & 0xff)); | |
pc = addr; | |
} | |
void opCli() { psr &= ~INTERRUPT_FLAG; } // CLear Interrupt-flag (CLI) (Flag:I) | |
void opCld() { psr &= ~DECIMAL_FLAG; } // CLear Decimal-flag (CLD) (Flag:D),. BCD-Mode is not implemented on NES. | |
void opClc() { psr &= ~CARRY_FLAG; } // CLear Carry-flag (CLC) (Flag:C) | |
void opClv() { psr &= ~OVERFLOW_FLAG; } // CLear oVerflow-flag (CLV) (Flag:V) | |
void opSec() { psr |= CARRY_FLAG; } // SEt Carry-flag (SEC) (Flag:C) | |
void opSed() { psr |= DECIMAL_FLAG; } // SEt Decimal-flag (SED) (Flag:D),. BCD-Mode is not implemented on NES. | |
void opSei() { psr |= INTERRUPT_FLAG; } // SEt Interrupt-flag (SEI) (Flag:I) | |
void opNop() {} | |
// ReTurn Subroutine (RTS) (Flags:None) | |
void opRts() { | |
pc = stack.pop(); | |
pc = ((stack.pop() << 8) | pc) + 1; | |
} | |
// ReTurn Interrupt (RTI) (Flags:None) | |
void opRti() { | |
psr = stack.pop(); | |
pc = stack.pop(); | |
pc = ((stack.pop() << 8) | pc); | |
} | |
void run() { | |
const uint8_t opcode = vm.read(pc++); | |
switch(opcode) { | |
// lda | |
case 0xa9: opLda(immediate()); break; | |
case 0xa5: opLda(zeroPage()); break; | |
case 0xad: opLda(absolute()); break; | |
case 0xb5: opLda(indexedZeroPage(RX)); break; | |
case 0xbd: opLda(indexedAbsolute(RX)); break; | |
case 0xb9: opLda(indexedAbsolute(RY)); break; | |
case 0xa1: opLda(indirectX()); break; | |
case 0xb1: opLda(indirectY()); break; | |
// ldx | |
case 0xa2: opLdx(immediate()); break; | |
case 0xa6: opLdx(zeroPage()); break; | |
case 0xae: opLdx(absolute()); break; | |
case 0xb6: opLdx(indexedZeroPage(RY)); break; | |
case 0xbe: opLdx(indexedAbsolute(RY)); break; | |
// ldy | |
case 0xa0: opLdy(immediate()); break; | |
case 0xa4: opLdy(zeroPage()); break; | |
case 0xac: opLdy(absolute()); break; | |
case 0xb4: opLdy(indexedZeroPage(RY)); break; | |
case 0xbc: opLdy(indexedAbsolute(RX)); break; | |
// sta | |
case 0x85: opSta(zeroPage()); break; | |
case 0x8d: opSta(absolute()); break; | |
case 0x95: opSta(indexedZeroPage(RX)); break; | |
case 0x9d: opSta(indexedAbsolute(RX)); break; | |
case 0x99: opSta(indexedAbsolute(RY)); break; | |
case 0x81: opSta(indirectX()); break; | |
case 0x91: opSta(indirectY()); break; | |
// stx | |
case 0x86: opStx(zeroPage()); break; | |
case 0x8e: opStx(absolute()); break; | |
case 0x96: opStx(indexedZeroPage(RY)); break; | |
// sty | |
case 0x84: opSty(zeroPage()); break; | |
case 0x8c: opSty(absolute()); break; | |
case 0x94: opSty(indexedZeroPage(RX)); break; | |
case 0x8a: opTxa(); break; // txa | |
case 0x98: opTya(); break; // tya | |
case 0x9a: opTxs(); break; // txs | |
case 0xa8: opTay(); break; // tay | |
case 0xaa: opTax(); break; // tax | |
case 0xba: opTsx(); break; // tsx | |
case 0x08: opPhp(); break; // php | |
case 0x28: opPlp(); break; // plp | |
case 0x48: opPha(); break; // pha | |
case 0x68: opPla(); break; // pla | |
// adc | |
case 0x69: opAdc(immediate()); break; | |
case 0x65: opAdc(zeroPage()); break; | |
case 0x6d: opAdc(absolute()); break; | |
case 0x75: opAdc(indexedZeroPage(RX)); break; | |
case 0x7d: opAdc(indexedAbsolute(RX)); break; | |
case 0x79: opAdc(indexedAbsolute(RY)); break; | |
case 0x61: opAdc(indirectX()); break; | |
case 0x71: opAdc(indirectY()); break; | |
// sbc | |
case 0xe9: opSbc(immediate()); break; | |
case 0xe5: opSbc(zeroPage()); break; | |
case 0xed: opSbc(absolute()); break; | |
case 0xf5: opSbc(indexedZeroPage(RX)); break; | |
case 0xfd: opSbc(indexedAbsolute(RX)); break; | |
case 0xf9: opSbc(indexedAbsolute(RY)); break; | |
case 0xe1: opSbc(indirectX()); break; | |
case 0xf1: opSbc(indirectY()); break; | |
// cpx | |
case 0xe0: opCpx(immediate()); break; | |
case 0xe4: opCpx(zeroPage()); break; | |
case 0xec: opCpx(absolute()); break; | |
// cpy | |
case 0xc0: opCpy(immediate()); break; | |
case 0xc4: opCpy(zeroPage()); break; | |
case 0xcc: opCpy(absolute()); break; | |
// cmp | |
case 0xc9: opCmp(immediate()); break; | |
case 0xc5: opCmp(zeroPage()); break; | |
case 0xcd: opCmp(absolute()); break; | |
case 0xd5: opCmp(indexedZeroPage(RX)); break; | |
case 0xdd: opCmp(indexedAbsolute(RX)); break; | |
case 0xd9: opCmp(indexedAbsolute(RY)); break; | |
case 0xe1: opCmp(indirectX()); break; | |
case 0xf1: opCmp(indirectY()); break; | |
// and | |
case 0x29: opAnd(immediate()); break; | |
case 0x25: opAnd(zeroPage()); break; | |
case 0x2d: opAnd(absolute()); break; | |
case 0x35: opAnd(indexedZeroPage(RX)); break; | |
case 0x3d: opAnd(indexedAbsolute(RX)); break; | |
case 0x39: opAnd(indexedAbsolute(RY)); break; | |
case 0x21: opAnd(indirectX()); break; | |
case 0x31: opAnd(indirectY()); break; | |
// eor | |
case 0x49: opEor(immediate()); break; | |
case 0x45: opEor(zeroPage()); break; | |
case 0x4d: opEor(absolute()); break; | |
case 0x55: opEor(indexedZeroPage(RX)); break; | |
case 0x5d: opEor(indexedAbsolute(RX)); break; | |
case 0x59: opEor(indexedAbsolute(RY)); break; | |
case 0x41: opEor(indirectX()); break; | |
case 0x51: opEor(indirectY()); break; | |
// ora | |
case 0x09: opCmp(immediate()); break; | |
case 0x05: opCmp(zeroPage()); break; | |
case 0x0d: opCmp(absolute()); break; | |
case 0x15: opCmp(indexedZeroPage(RX)); break; | |
case 0x1d: opCmp(indexedAbsolute(RX)); break; | |
case 0x19: opCmp(indexedAbsolute(RY)); break; | |
case 0x01: opCmp(indirectX()); break; | |
case 0x11: opCmp(indirectY()); break; | |
// bit | |
case 0x24: opBit(zeroPage()); break; | |
case 0x2c: opBit(absolute()); break; | |
// asl | |
case 0x0a: opAsl(); break; // none | |
case 0x06: opAsl(zeroPage()); break; | |
case 0x0e: opAsl(absolute()); break; | |
case 0x16: opAsl(indexedZeroPage(RX)); break; | |
case 0x1e: opAsl(indexedAbsolute(RX)); break; | |
// lsr | |
case 0x4a: opLsr(); break; // none | |
case 0x06: opLsr(zeroPage()); break; | |
case 0x0e: opLsr(absolute()); break; | |
case 0x16: opLsr(indexedZeroPage(RX)); break; | |
case 0x5e: opLsr(indexedAbsolute(RX)); break; | |
// rol | |
case 0x2a: opRol(); break; // none | |
case 0x26: opRol(zeroPage()); break; | |
case 0x2e: opRol(absolute()); break; | |
case 0x36: opRol(indexedZeroPage(RX)); break; | |
case 0x3e: opRol(indexedAbsolute(RX)); break; | |
// ror | |
case 0x6a: opRor(); break; // none | |
case 0x66: opRor(zeroPage()); break; | |
case 0x6e: opRor(absolute()); break; | |
case 0x76: opRor(indexedZeroPage(RX)); break; | |
case 0x7e: opRor(indexedAbsolute(RX)); break; | |
case 0xe8: opInx(); break; // inx | |
case 0xc8: opIny(); break; // iny | |
case 0xca: opDex(); break; // dex | |
case 0x88: opDec(); break; // dec | |
// inc | |
case 0xe6: opInc(zeroPage()); break; | |
case 0xee: opInc(absolute()); break; | |
case 0xf6: opInc(indexedZeroPage(RX)); break; | |
case 0xfe: opInc(indexedAbsolute(RX)); break; | |
case 0x18: opClc(); break; // clc | |
case 0x58: opCli(); break; // cli | |
case 0xb8: opClv(); break; // clv | |
case 0xd8: opCld(); break; // cld | |
case 0x38: opSec(); break; // sec | |
case 0x78: opSei(); break; // sei | |
case 0xf8: opSed(); break; // sed | |
case 0xea: opNop(); break; // nop | |
case 0x00: opBrk(); break; // brk | |
case 0x20: opJsr(absolute()); break; // jsr | |
case 0x4c: opJmp(absolute()); break;// jmp | |
case 0x40: opRti(); break; // rti | |
case 0x60: opRts(); break;// rts | |
} | |
vm.addClock(clock_table[opcode]); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment