The Super Nintendo runs on a 65816, a 16-Bit CPU that is capable of falling into an emulation mode, to emulate the 6502 (as is in the NES). This wasn’t really used by Nintendo though. Depening on the emulation mode there are various flags available / not available, as are the lengths of various registers different e.g. the accumulator (16-bit wide native to 8-bit wide emulation). It’s also able to address 16megs of space (by using 256 banks of 64k), plus additional components like CGRAM, VRAM etc. through certain access registers.
registers
SP | Stack-Pointer | 8-bit in emulation, 16-bit native |
X/Y | Index Registers | Page 0 in emulation, any page native. 8-bit when X-Flag is set, 16-bit when X-Flag is clear |
A | Accumulator | 8-bit when M-Flag is set, 16-bit when M-Flag is clear |
P | Status Register | Holds all Flags |
D | Direct Page Register | 16-Bit wide. |
PBR | Program Bank Register | 8-Bit wide, holds the bank of code execution |
DBR | Data Bank Register | 8-Bit wide, holds the bank of data read/write |
flags
B | Break | Only set for interrupts | Emulation |
N | Negative | Set when result is negative | Both |
V | Overflow | Set when result overflows the available data width | Both |
M | Memory / Accu. size (0 = 16-bit, 1 = 8-bit) | Sets the width of Accumulator and memory accesses | Native |
X | X/Y registers size (0 = 16-bit, 1 = 8-bit) | Sets the width of the X- and Y-Registers | Native |
D | Decimal | Set when Decimal / BCD mode is enabled | Both |
I | IRQ Disable | Set when Interrupts are disabled | Both |
Z | Zero | Set when result is zero | Both |
C | Carry | Is set on carry or borrow in additons and subtractions | Both |
E | Emulation | Set when the CPU runs in 6502 emulation mode (this is set on startup!) | / |
vectors
In several set addresses in the ROM are the addresses of numerous vectors stored, that the CPU makes use of. For example, just like with the 6502, the RESET vector is used for the initial boot-up sequence, or of course, a reset.
This is a table of the default values of said vectors, yet they can be set by the cartridge header (look up the appropriate chapter so see how it’s done) , so you shouldn’t rely on this table alone.
COP (native) | 0xFFE4 / 0xFFE5 |
BRK (native) | 0xFFE6 / 0xFFE7 |
ABORT (native) | 0xFFE8 / 0xFFE9 |
NMI (native) | 0xFFEA / 0xFFEB |
IRQ (native) | 0xFFEE / 0xFFEF |
COP (emulation) | 0xFFF4 / 0xFFF5 |
ABORT (emulation) | 0xFFF8 / 0xFFF9 |
NMI (emulation) | 0xFFFA / 0xFFFB |
RESET (emulation) | 0xFFFC / 0xFFFD |
IRQ/BRK (emulation) | 0xFFFE / 0xFFFF |
Don’t forget that we are working with a little Endian system, so when putting the addresses together, the low byte comes first in the vector.
addressing modes
The 65816 again comes with a set of addressing mode, quite a few which are already familiar from the 6502. The logic in my approach was (again) to have switch-case and pass the individual addressing modes to the instructions functions, if necessary.
The variable `pbr` is set whenever we cross a page boundary, as this adds cycles for certain instructions.
immediate (8bit)
u32 ADDR_getImmediate_8() {
regs.PC++;
u8 pb = regs.PB;
return (pb << 16) | regs.PC;
}
immediate (16bit)
u32 ADDR_getImmediate_16() {
regs.PC += 2;
u8 pb = regs.PB;
u32 adr = (pb << 16) | regs.PC - 1;
return adr;
}
absolute
u32 ADDR_getAbsolute() { // verified
regs.PC += 2;
u8 dbr = regs.DBR;
u16 adr = ((BUS_readFromMem((regs.PB << 16) | regs.PC) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC-1));
return (dbr << 16) | adr;
}
absolute long
u32 ADDR_getAbsoluteLong() {
regs.PC += 3;
return ((BUS_readFromMem((regs.PB << 16) | regs.PC) << 16) | (BUS_readFromMem((regs.PB << 16) | regs.PC - 1) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC - 2));
}
absolute indexed X
u32 ADDR_getAbsoluteIndexedX() {
regs.PC += 2;
u8 dbr = regs.DBR;
u16 adr = ((BUS_readFromMem((regs.PB << 16) | regs.PC) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC - 1));
pbr = (adr & 0xff00) != ((adr + regs.getX()) & 0xff00);
adr = adr + regs.getX();
return (dbr << 16) | adr;
}
absolute indexed Y
u32 ADDR_getAbsoluteIndexedY() {
regs.PC += 2;
u8 dbr = regs.DBR;
u16 adr = ((BUS_readFromMem((regs.PB << 16) | regs.PC) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC - 1));
pbr = (adr & 0xff00) != ((adr + regs.getY()) & 0xff00);
adr = adr + regs.getY();
return (dbr << 16) | adr;
}
absolute long indexed x
u32 ADDR_getAbsoluteLongIndexedX() {
regs.PC += 3;
return ((BUS_readFromMem((regs.PB << 16) | regs.PC) << 16) | (BUS_readFromMem((regs.PB << 16) | regs.PC - 1) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC - 2)) + regs.getX();
}
absolute indirect
u32 ADDR_getAbsoluteIndirect() {
regs.PC += 2;
u8 lo = BUS_readFromMem((regs.PB << 16) | regs.PC-1);
u8 hi = BUS_readFromMem((regs.PB << 16) | regs.PC);
u16 adr = (hi << 8) | lo;
u8 i_lo = BUS_readFromMem(adr);
u8 i_hi = BUS_readFromMem(adr + 1);
return (regs.PB << 16) | (i_hi << 8) | i_lo;
}
absolute indirect long
u32 ADDR_getAbsoluteIndirectLong() {
regs.PC += 2;
u8 lo = BUS_readFromMem((regs.PB << 16) | regs.PC - 1);
u8 hi = BUS_readFromMem((regs.PB << 16) | regs.PC);
u16 adr = (hi << 8) | lo;
u8 i_lo = BUS_readFromMem(adr);
u8 i_hi = BUS_readFromMem(adr + 1);
regs.setProgramBankRegister(BUS_readFromMem(adr + 2));
return (regs.PB << 16) | (i_hi << 8) | i_lo;
}
absolute indexed indirect X
u32 ADDR_getAbsoluteIndexedIndirectX() {
regs.PC += 2;
u8 lo = BUS_readFromMem((regs.PB << 16) | regs.PC - 1);
u8 hi = BUS_readFromMem((regs.PB << 16) | regs.PC);
u32 adr = ((regs.PB << 16) | (hi << 8) | lo) + regs.getX();
u8 i_lo = BUS_readFromMem(adr);
u8 i_hi = BUS_readFromMem(adr + 1);
return (regs.PB << 16) | (i_hi << 8) | i_lo;
}
long
u32 ADDR_getLong() {
regs.PC += 3;
return (BUS_readFromMem((regs.PB << 16) | regs.PC) << 16) | (BUS_readFromMem((regs.PB << 16) | regs.PC - 1) << 8) | BUS_readFromMem((regs.PB << 16) | regs.PC - 2);
}
direct page
u32 ADDR_getDirectPage() {
regs.PC++;
return BUS_readFromMem((regs.PB << 16) | regs.PC) + regs.D;
}
direct page indexed X
u32 ADDR_getDirectPageIndexedX() {
regs.PC++;
return BUS_readFromMem((regs.PB << 16) | regs.PC) + regs.D + regs.getX();
}
direct page indexed Y
u32 ADDR_getDirectPageIndexedY() {
regs.PC++;
return BUS_readFromMem((regs.PB << 16) | regs.PC) + regs.D + regs.getY();
}
direct page indirect
u32 ADDR_getDirectPageIndirect() {
regs.PC++;
u8 dbr = regs.DBR;
u8 dp_index = BUS_readFromMem(((regs.PB << 16) | regs.PC) + regs.D);
u16 dp_adr = (BUS_readFromMem(dp_index + 1) << 8) | BUS_readFromMem(dp_index);
return (dbr << 16) | dp_adr;
}
direct page indirect long
u32 ADDR_getDirectPageIndirectLong() {
regs.PC++;
u16 dp_index = BUS_readFromMem(((regs.PB << 16) | regs.PC)) + regs.D;
u32 dp_adr = (BUS_readFromMem(dp_index + 2) << 16) | (BUS_readFromMem(dp_index + 1) << 8) | BUS_readFromMem(dp_index);
return dp_adr;
}
direct page indirect X
u32 ADDR_getDirectPageIndirectX() {
regs.PC++;
u8 dbr = regs.DBR;
u8 dp_index = BUS_readFromMem(((regs.PB << 16) | regs.PC) + regs.D) + regs.getX();
u16 dp_adr = (BUS_readFromMem(dp_index + 1) << 8) | BUS_readFromMem(dp_index);
return (dbr << 16) | dp_adr;
}
direct page indirect indexed Y
u32 ADDR_getDirectPageIndirectIndexedY() {
regs.PC++;
u8 dp_index = BUS_readFromMem(((regs.PB << 16) | regs.PC)) + regs.D;
u32 dp_adr = (regs.DBR << 16) | (BUS_readFromMem(dp_index + 1) << 8) | BUS_readFromMem(dp_index);
pbr = (dp_adr & 0xff00) != ((dp_adr + regs.getY()) & 0xff00);
dp_adr += regs.getY();
return dp_adr;
}
direct page indirect long indexed Y
u32 ADDR_getDirectPageIndirectLongIndexedY() {
regs.PC++;
u8 dp_index = BUS_readFromMem(((regs.PB << 16) | regs.PC)) + regs.D;
u32 dp_adr = (BUS_readFromMem(dp_index + 2) << 16) | (BUS_readFromMem(dp_index + 1) << 8) | BUS_readFromMem(dp_index);
pbr = (dp_adr & 0xff00) != ((dp_adr + regs.getY()) & 0xff00);
dp_adr += regs.getY();
return dp_adr;
}
stack relative
u32 ADDR_getStackRelative() {
regs.PC++;
u8 byte = BUS_readFromMem((regs.PB << 16) | regs.PC);
return regs.getSP() + byte;
}
stack relative indirect indexed Y
u32 ADDR_getStackRelativeIndirectIndexedY() {
regs.PC++;
u8 byte = BUS_readFromMem((regs.PB << 16) | regs.PC);
u8 base = BUS_readFromMem(((regs.DBR << 16) | regs.getSP()) + byte);
return base + regs.getY();
}
instructions
Like I said earlier, the instructions were put in a switch-case, with the addressing modes passed as pointers (if necessary), and additional checks for duration of the instruction.
...
case 0x09: return (regs.P.getAccuMemSize()) ? ORA(ADDR_getImmediate_8, 2 + regs.P.isMReset()) : ORA(ADDR_getImmediate_16, 2 + regs.P.isMReset()); break;
case 0x0a: return ASL_A(2); break;
case 0x0b: return PHD(); break;
case 0x0c: return TSB(ADDR_getAbsolute, 6 + (2 * regs.P.isMReset())); break;
case 0x0d: return ORA(ADDR_getAbsolute, 4 + regs.P.isMReset()); break;
case 0x0e: return ASL(ADDR_getAbsolute, 6 + regs.P.isMReset()); break;
...
Most of the instructions should be pretty clear and already known from other emulators / CPUs. I’ll show a few examples and instructions that aren’t very common.
adc – add with carry
This instruction normally wouldn’t be too much of a hassle to implement, but since we need to consider (a) the Accu/Mem width (M-flag) and (b) decimal mode (D-flag), this becomes a bit more complex than usual.
// Add with carry
u8 ADC(u32(*f)(), u8 cycles) {
if (regs.P.getAccuMemSize()) {
u8 val = BUS_readFromMem(f());
u32 res = 0;
if (regs.P.getDecimal()) {
res = (regs.getAccumulator() & 0xf) + (val & 0x0f) + regs.P.getCarry();
if (res > 0x09) {
res += 0x06;
}
regs.P.setCarry(res > 0x0f);
res = (regs.getAccumulator() & 0xf0) + (val & 0xf0) + (regs.P.getCarry() << 4) + (res & 0x0f);
}
else {
res = (regs.getAccumulator() & 0xff) + val + regs.P.getCarry();
}
regs.P.setOverflow((~(regs.getAccumulator() ^ val) & (regs.getAccumulator() ^ res) & 0x80) == 0x80);
if (regs.P.getDecimal() && res > 0x9f) {
res += 0x60;
}
regs.P.setCarry(res > 0xff);
regs.P.setZero((u8)res == 0);
regs.P.setNegative((res & 0x80) == 0x80);
regs.setAccumulator((u8)(res & 0xff));
}
else {
u32 adr = f();
u8 lo = BUS_readFromMem(adr);
u8 hi = BUS_readFromMem(adr + 1);
u16 val = (hi << 8) | lo;
u32 res = 0;
if (regs.P.getDecimal()) {
res = (regs.getAccumulator() & 0x000f) + (val & 0x000f) + regs.P.getCarry();
if (res > 0x0009) {
res += 0x0006;
}
regs.P.setCarry(res > 0x000f);
res = (regs.getAccumulator() & 0x00f0) + (val & 0x00f0) + (regs.P.getCarry() << 4) + (res & 0x000f);
if (res > 0x009f) {
res += 0x0060;
}
regs.P.setCarry(res > 0x00ff);
res = (regs.getAccumulator() & 0x0f00) + (val & 0x0f00) + (regs.P.getCarry() << 8) + (res & 0x00ff);
if (res > 0x09ff) {
res += 0x0600;
}
regs.P.setCarry(res > 0x0fff);
res = (regs.getAccumulator() & 0xf000) + (val & 0xf000) + (regs.P.getCarry() << 12) + (res & 0x0fff);
}
else {
res = regs.getAccumulator() + val + regs.P.getCarry();
}
regs.P.setOverflow((~(regs.getAccumulator() ^ val) & (regs.getAccumulator() ^ res) & 0x8000) == 0x8000);
if (regs.P.getDecimal() && res > 0x9fff) {
res += 0x6000;
}
regs.P.setCarry(res > 0xffff);
regs.P.setZero((u16)(res) == 0);
regs.P.setNegative((res & 0x8000) == 0x8000);
regs.setAccumulator((u16)res);
}
regs.PC++;
return cycles;
}
cpx – compare X register with memory
Again, not a very unique instruction, but here we need to check the flag (X) that indicates the width of the X/Y-Registers.
// Compare X-Register with memory
u8 CPX(u32(*f)(), u8 cycles) {
if (regs.P.getIndexSize()) {
u32 adr = f();
u8 m = BUS_readFromMem(adr);
u8 val = (u8)regs.getX() - m;
regs.P.setNegative(val >> 7);
regs.P.setZero(val == 0);
regs.P.setCarry(regs.getX() >= m);
}
else {
u32 adr = f();
u8 lo = BUS_readFromMem(adr);
u8 hi = BUS_readFromMem(adr + 1);
u16 m = (hi << 8) | lo;
u16 val = regs.getX() - m;
regs.P.setNegative(val >> 15);
regs.P.setZero(val == 0);
regs.P.setCarry(regs.getX() >= m);
}
regs.PC++;
return cycles;
}
mvn – move next block
This instruction is some kind of copy mechanism, which copies bytes from one destination (X-Register) to another (Y-Register), until the Accumulator reaches 0xffff. This instruction is of course only available in native mode.
// Block move next
u8 MVN() {
u8 dst_bank = BUS_readFromMem(regs.PC + 1);
u8 src_bank = BUS_readFromMem(regs.PC + 2);
regs.setDataBankRegister(dst_bank);
u32 dst = (dst_bank << 16) | regs.getY();
u32 src = (src_bank << 16) | regs.getX();
u8 val = BUS_readFromMem(src);
BUS_writeToMem(val, dst);
regs.setAccumulator((u16)(regs.getAccumulator()-1));
regs.setX((u16)(regs.getX() + 1));
regs.setY((u16)(regs.getY() + 1));
if (regs.getAccumulator() == 0xffff)
regs.PC += 3;
return 7;
}
list of instructions
Syntax | Addressing Mode | Opcode | Bytes | Cycles | Extra |
---|---|---|---|---|---|
ADC #const | Immediate | 69 | 2 / 3 | 2 | +1 if m=0 |
ADC addr | Absolute | 6D | 3 | 4 | +1 if m=0 |
ADC long | Absolute Long | 6F | 4 | 5 | +1 if m=0 |
ADC dp | Direct Page | 65 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC (dp) | Direct Page Indirect | 72 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC [dp] | Direct Page Indirect Long | 67 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC addr, X | Absolute Indexed, X | 7D | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
ADC long, X | Absolute Long Indexed, X | 7F | 4 | 5 | +1 if m=0 |
ADC addr, Y | Absolute Indexed, Y | 79 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
ADC dp, X | Direct Page Indexed, X | 75 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC (dp, X) | Direct Page Indirect, X | 61 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC (dp), Y | DP Indirect Indexed, Y | 71 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
ADC [dp], Y | DP Indirect Long Indexed, Y | 77 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ADC sr, S | Stack Relative | 63 | 2 | 4 | +1 if m=0 |
ADC (sr, S), Y | SR Indirect Indexed, Y | 73 | 2 | 7 | +1 if m=0 |
AND #const | Immediate | 29 | 2 / 3 | 2 | +1 if m=0 |
AND addr | Absolute | 2D | 3 | 4 | +1 if m=0 |
AND long | Absolute Long | 2F | 4 | 5 | +1 if m=0 |
AND dp | Direct Page | 25 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
AND (dp) | Direct Page Indirect | 32 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
AND [dp] | Direct Page Indirect Long | 27 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
AND addr, X | Absolute Indexed, X | 3D | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
AND long, X | Absolute Long Indexed, X | 3F | 4 | 5 | +1 if m=0 |
AND addr, Y | Absolute Indexed, Y | 39 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
AND dp, X | Direct Page Indexed, X | 35 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
AND (dp, X) | Direct Page Indirect, X | 21 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
AND (dp), Y | DP Indirect Indexed, Y | 31 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
AND [dp], Y | DP Indirect Long Indexed, Y | 37 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
AND sr, S | Stack Relative | 23 | 2 | 4 | +1 if m=0 |
AND (sr, S), Y | SR Indirect Indexed, Y | 33 | 2 | 7 | +1 if m=0 |
ASL | Accumulator | 0A | 1 | 2 | |
ASL addr | Absolute | 0E | 3 | 6 | +2 if m=0 |
ASL dp | Direct Page | 06 | 2 | 5 | +2 if m=0, +1 if DP.l ≠ 0 |
ASL addr, X | Absolute Indexed, X | 1E | 3 | 7 | +2 if m=0, +1 if index crosses page boundary |
ASL dp, X | Direct Page Indexed, X | 16 | 2 | 6 | +2 if m=0, +1 if DP.l ≠ 0 |
BCC near | Branch if Carry Clear | 90 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BCS near | Branch if Carry Set | B0 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BEQ near | Branch if Equal (z flag=1) | F0 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BNE near | Branch if Not Equal (z=0) | D0 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BMI near | Branch if Minus | 30 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BPL near | Branch if Plus | 10 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BVC near | Branch if Overflow Clear | 50 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BVS near | Branch if Overflow Set | 70 | 2 | 2 | +1 if branch taken, +1 if e=1 |
BRA near | Branch Always | 80 | 2 | 3 | +1 if e=1 |
BRL label | Branch Always Long | 82 | 3 | 4 | |
BIT #const | Immediate | 89 | 2 / 3 | 2 | +1 if m=0 |
BIT addr | Absolute | 2C | 3 | 4 | +1 if m=0 |
BIT dp | Direct Page | 24 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
BIT addr, X | Absolute Indexed, X | 3C | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
BIT dp, X | Direct Page Indexed, X | 34 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
BRK param | Interrupt | 00 | 2 | 7 | +1 if e=0 |
COP param | Interrupt | 02 | 2 | 7 | +1 if e=0 |
CLC | Clear Carry Flag | 18 | 1 | 2 | |
CLI | Clear Interrupt Disable Flag | 58 | 1 | 2 | |
CLD | Clear Decimal Flag | D8 | 1 | 2 | |
CLV | Clear Overflow Flag | B8 | 1 | 2 | |
CMP #const | Immediate | C9 | 2 / 3 | 2 | +1 if m=0 |
CMP addr | Absolute | CD | 3 | 4 | +1 if m=0 |
CMP long | Absolute Long | CF | 4 | 5 | +1 if m=0 |
CMP dp | Direct Page | C5 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP (dp) | Direct Page Indirect | D2 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP [dp] | Direct Page Indirect Long | C7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP addr, X | Absolute Indexed, X | DD | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
CMP long, X | Absolute Long Indexed, X | DF | 4 | 5 | +1 if m=0 |
CMP addr, Y | Absolute Indexed, Y | D9 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
CMP dp, X | Direct Page Indexed, X | D5 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP (dp, X) | Direct Page Indirect, X | C1 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP (dp), Y | DP Indirect Indexed, Y | D1 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
CMP [dp], Y | DP Indirect Long Indexed, Y | D7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
CMP sr, S | Stack Relative | C3 | 2 | 4 | +1 if m=0 |
CMP (sr, S), Y | SR Indirect Indexed, Y | D3 | 2 | 7 | +1 if m=0 |
CPX #const | Immediate | E0 | 2 / 3 | 2 | +1 if x=0 |
CPX addr | Absolute | EC | 3 | 4 | +1 if x=0 |
CPX dp | Direct Page | E4 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
CPY #const | Immediate | C0 | 2 / 3 | 2 | +1 if x=0 |
CPY addr | Absolute | CC | 3 | 4 | +1 if x=0 |
CPY dp | Direct Page | C4 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
DEC | Accumulator | 3A | 1 | 2 | |
DEC addr | Absolute | CE | 3 | 6 | +2 if m=0 |
DEC dp | Direct Page | C6 | 2 | 5 | +2 if m=0, +1 if DP.l ≠ 0 |
DEC addr, X | Absolute Indexed, X | DE | 3 | 7 | +2 if m=0, +1 if index crosses page boundary |
DEC dp, X | Direct Page Indexed, X | D6 | 2 | 6 | +2 if m=0, +1 if DP.l ≠ 0 |
DEX | Implied | CA | 1 | 2 | |
DEY | Implied | 88 | 1 | 2 | |
EOR #const | Immediate | 49 | 2 / 3 | 2 | +1 if m=0 |
EOR addr | Absolute | 4D | 3 | 4 | +1 if m=0 |
EOR long | Absolute Long | 4F | 4 | 5 | +1 if m=0 |
EOR dp | Direct Page | 45 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR (dp) | Direct Page Indirect | 52 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR [dp] | Direct Page Indirect Long | 47 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR addr, X | Absolute Indexed, X | 5D | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
EOR long, X | Absolute Long Indexed, X | 5F | 4 | 5 | +1 if m=0 |
EOR addr, Y | Absolute Indexed, Y | 59 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
EOR dp, X | Direct Page Indexed, X | 55 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR (dp, X) | Direct Page Indirect, X | 41 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR (dp), Y | DP Indirect Indexed, Y | 51 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
EOR [dp], Y | DP Indirect Long Indexed, Y | 57 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
EOR sr, S | Stack Relative | 43 | 2 | 4 | +1 if m=0 |
EOR (sr, S), Y | SR Indirect Indexed, Y | 53 | 2 | 7 | +1 if m=0 |
INC | Accumulator | 1A | 1 | 2 | |
INC addr | Absolute | EE | 3 | 6 | +2 if m=0 |
INC dp | Direct Page | E6 | 2 | 5 | +2 if m=0, +1 if DP.l ≠ 0 |
INC addr, X | Absolute Indexed, X | FE | 3 | 7 | +2 if m=0, +1 if index crosses page boundary |
INC dp, X | Direct Page Indexed, X | F6 | 2 | 6 | +2 if m=0, +1 if DP.l ≠ 0 |
INX | Implied | E8 | 1 | 2 | |
INY | Implied | C8 | 1 | 2 | |
JMP addr | Absolute | 4C | 3 | 3 | |
JMP (addr) | Absolute Indirect | 6C | 3 | 5 | |
JMP (addr, X) | Absolute Indexed Indirect, X | 7C | 3 | 6 | |
JMP long | |||||
JML long | Absolute Long | 5C | 4 | 4 | |
JMP [addr] | |||||
JML [addr] | Absolute Indirect Long | DC | 3 | 6 | |
JSR addr | Absolute | 20 | 3 | 6 | |
JSR (addr, X) | Absolute Indexed Indirect, X | FC | 3 | 8 | |
JSL long | Absolute Long | 22 | 4 | 8 | |
LDA #const | Immediate | A9 | 2 / 3 | 2 | +1 if m=0 |
LDA addr | Absolute | AD | 3 | 4 | +1 if m=0 |
LDA long | Absolute Long | AF | 4 | 5 | +1 if m=0 |
LDA dp | Direct Page | A5 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA (dp) | Direct Page Indirect | B2 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA [dp] | Direct Page Indirect Long | A7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA addr, X | Absolute Indexed, X | BD | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
LDA long, X | Absolute Long Indexed, X | BF | 4 | 5 | +1 if m=0 |
LDA addr, Y | Absolute Indexed, Y | B9 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
LDA dp, X | Direct Page Indexed, X | B5 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA (dp, X) | Direct Page Indirect, X | A1 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA (dp), Y | DP Indirect Indexed, Y | B1 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
LDA [dp], Y | DP Indirect Long Indexed, Y | B7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
LDA sr, S | Stack Relative | A3 | 2 | 4 | +1 if m=0 |
LDA (sr, S), Y | SR Indirect Indexed, Y | B3 | 2 | 7 | +1 if m=0 |
LDX #const | Immediate | A2 | 2 / 3 | 2 | +1 if x=0 |
LDX addr | Absolute | AE | 3 | 4 | +1 if x=0 |
LDX dp | Direct Page | A6 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
LDX addr, Y | Absolute Indexed, Y | BE | 3 | 4 | +1 if x=0, +1 if index crosses page boundary |
LDX dp, Y | Direct Page Indexed, Y | B6 | 2 | 4 | +1 if x=0, +1 if DP.l ≠ 0 |
LDY #const | Immediate | A0 | 2 / 3 | 2 | +1 if x=0 |
LDY addr | Absolute | AC | 3 | 4 | +1 if x=0 |
LDY dp | Direct Page | A4 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
LDY addr, X | Absolute Indexed, X | BC | 3 | 4 | +1 if x=0, +1 if index crosses page boundary |
LDY dp, X | Direct Page Indexed, X | B4 | 2 | 4 | +1 if x=0, +1 if DP.l ≠ 0 |
LSR | Accumulator | 4A | 1 | 2 | |
LSR addr | Absolute | 4E | 3 | 6 | +1 if m=0 |
LSR dp | Direct Page | 46 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
LSR addr, X | Absolute Indexed, X | 5E | 3 | 7 | +1 if m=0, +1 if index crosses page boundary |
LSR dp, X | Direct Page Indexed, X | 56 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
MVN srcBank, destBank | Block Move | 54 | 3 | 7 per byte moved | |
MVP srcBank, destBank | Block Move | 44 | 3 | 7 per byte moved | |
NOP | Implied | EA | 1 | 2 | |
ORA #const | Immediate | 09 | 2 / 3 | 2 | +1 if m=0 |
ORA addr | Absolute | 0D | 3 | 4 | +1 if m=0 |
ORA long | Absolute Long | 0F | 4 | 5 | +1 if m=0 |
ORA dp | Direct Page | 05 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA (dp) | Direct Page Indirect | 12 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA [dp] | Direct Page Indirect Long | 07 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA addr, X | Absolute Indexed, X | 1D | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
ORA long, X | Absolute Long Indexed, X | 1F | 4 | 5 | +1 if m=0 |
ORA addr, Y | Absolute Indexed, Y | 19 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
ORA dp, X | Direct Page Indexed, X | 15 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA (dp, X) | Direct Page Indirect, X | 01 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA (dp), Y | DP Indirect Indexed, Y | 11 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
ORA [dp], Y | DP Indirect Long Indexed, Y | 17 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ORA sr, S | Stack Relative | 03 | 2 | 4 | +1 if m=0 |
ORA (sr, S), Y | SR Indirect Indexed, Y | 13 | 2 | 7 | +1 if m=0 |
PEA addr | Stack (Absolute) | F4 | 3 | 5 | |
PEI (dp) | Stack (DP Indirect) | D4 | 2 | 6 | +1 if DP.l ≠ 0 |
PER label | Stack (PC Relative Long) | 62 | 3 | 6 | |
PHA | Push Accumulator | 48 | 1 | 3 | +1 if m=0 |
PHB | Push Data Bank | 8B | 1 | 3 | |
PHD | Push Direct Page Register | 0B | 1 | 4 | |
PHK | Push Program Bank Register | 4B | 1 | 3 | |
PHP | Push Processor Status Register | 08 | 1 | 3 | |
PHX | Push Index Register X | DA | 1 | 3 | +1 if x=0 |
PHY | Push Index Register Y | 5A | 1 | 3 | +1 if x=0 |
PLA | Pull Accumulator | 68 | 1 | 4 | +1 if m=0 |
PLB | Pull Data Bank | AB | 1 | 4 | |
PLD | Pull Direct Page Register | 2B | 1 | 5 | |
PLP | Pull Processor Status Register | 28 | 1 | 4 | |
PLX | Pull Index Register X | FA | 1 | 4 | +1 if x=0 |
PLY | Pull Index Register Y | 7A | 1 | 4 | +1 if x=0 |
REP #const | Immediate | C2 | 2 | 3 | |
ROL | Accumulator | 2A | 1 | 2 | |
ROL addr | Absolute | 2E | 3 | 6 | +1 if m=0 |
ROL dp | Direct Page | 26 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
ROL addr, X | Absolute Indexed, X | 3E | 3 | 7 | +1 if m=0, +1 if index crosses page boundary |
ROL dp, X | Direct Page Indexed, X | 36 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
ROR | Accumulator | 6A | 1 | 2 | |
ROR addr | Absolute | 6E | 3 | 6 | +1 if m=0 |
ROR dp | Direct Page | 66 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
ROR addr, X | Absolute Indexed, X | 7E | 3 | 7 | +1 if m=0, +1 if index crosses page boundary |
ROR dp, X | Direct Page Indexed, X | 76 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
RTI | Stack (return interrupt) | 40 | 1 | 6 | +1 if e=0 |
RTS | Stack (return) | 60 | 1 | 6 | |
RTL | Stack (return long) | 6B | 1 | 6 | |
SBC #const | Immediate | E9 | 2 / 3 | 2 | +1 if m=0 |
SBC addr | Absolute | ED | 3 | 4 | +1 if m=0 |
SBC long | Absolute Long | EF | 4 | 5 | +1 if m=0 |
SBC dp | Direct Page | E5 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC (dp) | Direct Page Indirect | F2 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC [dp] | Direct Page Indirect Long | E7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC addr, X | Absolute Indexed, X | FD | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
SBC long, X | Absolute Long Indexed, X | FF | 4 | 5 | +1 if m=0 |
SBC addr, Y | Absolute Indexed, Y | F9 | 3 | 4 | +1 if m=0, +1 if index crosses page boundary |
SBC dp, X | Direct Page Indexed, X | F5 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC (dp, X) | Direct Page Indirect, X | E1 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC (dp), Y | DP Indirect Indexed, Y | F1 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary |
SBC [dp], Y | DP Indirect Long Indexed, Y | F7 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
SBC sr, S | Stack Relative | E3 | 2 | 4 | +1 if m=0 |
SBC (sr, S), Y | SR Indirect Indexed, Y | F3 | 2 | 7 | +1 if m=0 |
SEC | Set Carry Flag | 38 | 1 | 2 | |
SEI | Set Interrupt Disable Flag | 78 | 1 | 2 | |
SED | Set Decimal Flag | F8 | 1 | 2 | |
SEP #const | Immediate | E2 | 2 | 3 | |
STA addr | Absolute | 8D | 3 | 4 | +1 if m=0 |
STA long | Absolute Long | 8F | 4 | 5 | +1 if m=0 |
STA dp | Direct Page | 85 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
STA (dp) | Direct Page Indirect | 92 | 2 | 5 | +1 if m=0, +1 if DP.l ≠ 0 |
STA [dp] | Direct Page Indirect Long | 87 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
STA addr, X | Absolute Indexed, X | 9D | 3 | 5 | +1 if m=0 |
STA long, X | Absolute Long Indexed, X | 9F | 4 | 5 | +1 if m=0 |
STA addr, Y | Absolute Indexed, Y | 99 | 3 | 5 | +1 if m=0 |
STA dp, X | Direct Page Indexed, X | 95 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
STA (dp, X) | Direct Page Indirect, X | 81 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
STA (dp), Y | DP Indirect Indexed, Y | 91 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
STA [dp], Y | DP Indirect Long Indexed, Y | 97 | 2 | 6 | +1 if m=0, +1 if DP.l ≠ 0 |
STA sr, S | Stack Relative | 83 | 2 | 4 | +1 if m=0 |
STA (sr, S), Y | SR Indirect Indexed, Y | 93 | 2 | 7 | +1 if m=0 |
STP | Implied | DB | 1 | 3 | |
STX addr | Absolute | 8E | 3 | 4 | +1 if x=0 |
STX dp | Direct Page | 86 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
STX dp, Y | Direct Page Indexed, Y | 96 | 2 | 4 | +1 if x=0, +1 if DP.l ≠ 0 |
STY addr | Absolute | 8C | 3 | 4 | +1 if x=0 |
STY dp | Direct Page | 84 | 2 | 3 | +1 if x=0, +1 if DP.l ≠ 0 |
STY dp, X | Direct Page Indexed, X | 94 | 2 | 4 | +1 if x=0, +1 if DP.l ≠ 0 |
STZ addr | Absolute | 9C | 3 | 4 | +1 if m=0 |
STZ dp | Direct Page | 64 | 2 | 3 | +1 if m=0, +1 if DP.l ≠ 0 |
STZ addr, X | Absolute Indexed, X | 9E | 3 | 5 | +1 if m=0 |
STZ dp, X | Direct Page Indexed, X | 74 | 2 | 4 | +1 if m=0, +1 if DP.l ≠ 0 |
TAX | Transfer A to X | AA | 1 | 2 | |
TAY | Transfer A to Y | A8 | 1 | 2 | |
TCD | Transfer 16 bit A to DP | 5B | 1 | 2 | |
TCS | Transfer 16 bit A to SP | 1B | 1 | 2 | |
TDC | Transfer DP to 16 bit A | 7B | 1 | 2 | |
TSC | Transfer SP to 16 bit A | 3B | 1 | 2 | |
TSX | Transfer SP to X | BA | 1 | 2 | |
TXA | Transfer X to A | 8A | 1 | 2 | |
TXS | Transfer X to SP | 9A | 1 | 2 | |
TXY | Transfer X to Y | 9B | 1 | 2 | |
TYA | Transfer Y to A | 98 | 1 | 2 | |
TYX | Transfer Y to X | BB | 1 | 2 | |
TRB addr | Absolute | 1C | 3 | 6 | +2 if m=0 |
TRB dp | Direct Page | 14 | 2 | 5 | +2 if m=0, +1 if DP.l ≠ 0 |
TSB addr | Absolute | 0C | 3 | 6 | +2 if m=0 |
TSB dp | Direct Page | 04 | 2 | 5 | +2 if m=0, +1 if DP.l ≠ 0 |
WAI | Implied | CB | 1 | 3 | additional cycles needed by interrupt handler to restart the processor |
WDM | Implied | 42 | 2 | 2 | |
XBA | Implied | EB | 1 | 3 | |
XCE | Implied | FB | 1 | 2 |
testing
In general, for testing, the beautiful test ROMs from krom should be used to verify the CPU is working as intended. They do have PPU output, but can be run without PPU – though you will need to handle reads from 0x4210 (which is a register that shows NMI occurences – will be handled in later chapters). For the moment that I made my traces, and for the sake of deterministic traces, I implemented reads from 0x4210 like this:
case 0x4210:
...
if (NMI == 0x42) {
NMI = 0xc2;
return NMI;
}
if (NMI == 0xc2) {
NMI = 0x42;
return NMI;
}
Other than that, just execute the tests until they hit a trap (a JMP that calls itself, causing the code to loop forever).
kroms tests are available for download here.
They are split up to test several instruction groups, so you can easily conquer them one by one. To be able to load them up, you will atleast have to have rudimentary memory mapping set up, which I will cover in the next page.
With the help of bsnes-plus I also made some tracelogs, that can easily be diff’d with the output of your emulator, to verify everything is working fine:
hey Lilac, the blog has been super helpful so far!
but i’ve noticed here that stack relative seems to be wrong, unless all the docs i’m reading are wrong about the stack wrapping at bank 0
returning SP + operand for example when SP is 0xFF10 and operand is 0xFA would give 0x1000A and the stack pointer should wrap at bank 0 boundary high byte address of data would be 0x0B and low byte would be 0x0A
noted from http://6502.org/tutorials/65c816opcodes.html#5.20