Skip to content →

65816 – the cpu

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

SPStack-Pointer8-bit in emulation, 16-bit native
X/YIndex RegistersPage 0 in emulation, any page native. 8-bit when X-Flag is set, 16-bit when X-Flag is clear
AAccumulator8-bit when M-Flag is set, 16-bit when M-Flag is clear
PStatus RegisterHolds all Flags
DDirect Page Register16-Bit wide.
PBRProgram Bank Register8-Bit wide, holds the bank of code execution
DBRData Bank Register8-Bit wide, holds the bank of data read/write

flags

BBreakOnly set for interruptsEmulation
NNegativeSet when result is negativeBoth
VOverflowSet when result overflows the available data widthBoth
MMemory / Accu. size (0 = 16-bit, 1 = 8-bit)Sets the width of Accumulator and memory accessesNative
XX/Y registers size (0 = 16-bit, 1 = 8-bit)Sets the width of the X- and Y-RegistersNative
DDecimalSet when Decimal / BCD mode is enabledBoth
IIRQ DisableSet when Interrupts are disabledBoth
ZZeroSet when result is zeroBoth
CCarryIs set on carry or borrow in additons and subtractionsBoth
EEmulationSet 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 #constImmediate692 / 32+1 if m=0
ADC addrAbsolute6D34+1 if m=0
ADC longAbsolute Long6F45+1 if m=0
ADC dpDirect Page6523+1 if m=0, +1 if DP.l ≠ 0
ADC (dp)Direct Page Indirect7225+1 if m=0, +1 if DP.l ≠ 0
ADC [dp]Direct Page Indirect Long6726+1 if m=0, +1 if DP.l ≠ 0
ADC addr, XAbsolute Indexed, X7D34+1 if m=0, +1 if index crosses page boundary
ADC long, XAbsolute Long Indexed, X7F45+1 if m=0
ADC addr, YAbsolute Indexed, Y7934+1 if m=0, +1 if index crosses page boundary
ADC dp, XDirect Page Indexed, X7524+1 if m=0, +1 if DP.l ≠ 0
ADC (dp, X)Direct Page Indirect, X6126+1 if m=0, +1 if DP.l ≠ 0
ADC (dp), YDP Indirect Indexed, Y7125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
ADC [dp], YDP Indirect Long Indexed, Y7726+1 if m=0, +1 if DP.l ≠ 0
ADC sr, SStack Relative6324+1 if m=0
ADC (sr, S), YSR Indirect Indexed, Y7327+1 if m=0
AND #constImmediate292 / 32+1 if m=0
AND addrAbsolute2D34+1 if m=0
AND longAbsolute Long2F45+1 if m=0
AND dpDirect Page2523+1 if m=0, +1 if DP.l ≠ 0
AND (dp)Direct Page Indirect3225+1 if m=0, +1 if DP.l ≠ 0
AND [dp]Direct Page Indirect Long2726+1 if m=0, +1 if DP.l ≠ 0
AND addr, XAbsolute Indexed, X3D34+1 if m=0, +1 if index crosses page boundary
AND long, XAbsolute Long Indexed, X3F45+1 if m=0
AND addr, YAbsolute Indexed, Y3934+1 if m=0, +1 if index crosses page boundary
AND dp, XDirect Page Indexed, X3524+1 if m=0, +1 if DP.l ≠ 0
AND (dp, X)Direct Page Indirect, X2126+1 if m=0, +1 if DP.l ≠ 0
AND (dp), YDP Indirect Indexed, Y3125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
AND [dp], YDP Indirect Long Indexed, Y3726+1 if m=0, +1 if DP.l ≠ 0
AND sr, SStack Relative2324+1 if m=0
AND (sr, S), YSR Indirect Indexed, Y3327+1 if m=0
ASLAccumulator0A12
ASL addrAbsolute0E36+2 if m=0
ASL dpDirect Page0625+2 if m=0, +1 if DP.l ≠ 0
ASL addr, XAbsolute Indexed, X1E37+2 if m=0, +1 if index crosses page boundary
ASL dp, XDirect Page Indexed, X1626+2 if m=0, +1 if DP.l ≠ 0
BCC nearBranch if Carry Clear9022+1 if branch taken, +1 if e=1
BCS nearBranch if Carry SetB022+1 if branch taken, +1 if e=1
BEQ nearBranch if Equal (z flag=1)F022+1 if branch taken, +1 if e=1
BNE nearBranch if Not Equal (z=0)D022+1 if branch taken, +1 if e=1
BMI nearBranch if Minus3022+1 if branch taken, +1 if e=1
BPL nearBranch if Plus1022+1 if branch taken, +1 if e=1
BVC nearBranch if Overflow Clear5022+1 if branch taken, +1 if e=1
BVS nearBranch if Overflow Set7022+1 if branch taken, +1 if e=1
BRA nearBranch Always8023+1 if e=1
BRL labelBranch Always Long8234
BIT #constImmediate892 / 32+1 if m=0
BIT addrAbsolute2C34+1 if m=0
BIT dpDirect Page2423+1 if m=0, +1 if DP.l ≠ 0
BIT addr, XAbsolute Indexed, X3C34+1 if m=0, +1 if index crosses page boundary
BIT dp, XDirect Page Indexed, X3424+1 if m=0, +1 if DP.l ≠ 0
BRK paramInterrupt0027+1 if e=0
COP paramInterrupt0227+1 if e=0
CLCClear Carry Flag1812
CLIClear Interrupt Disable Flag5812
CLDClear Decimal FlagD812
CLVClear Overflow FlagB812
CMP #constImmediateC92 / 32+1 if m=0
CMP addrAbsoluteCD34+1 if m=0
CMP longAbsolute LongCF45+1 if m=0
CMP dpDirect PageC523+1 if m=0, +1 if DP.l ≠ 0
CMP (dp)Direct Page IndirectD225+1 if m=0, +1 if DP.l ≠ 0
CMP [dp]Direct Page Indirect LongC726+1 if m=0, +1 if DP.l ≠ 0
CMP addr, XAbsolute Indexed, XDD34+1 if m=0, +1 if index crosses page boundary
CMP long, XAbsolute Long Indexed, XDF45+1 if m=0
CMP addr, YAbsolute Indexed, YD934+1 if m=0, +1 if index crosses page boundary
CMP dp, XDirect Page Indexed, XD524+1 if m=0, +1 if DP.l ≠ 0
CMP (dp, X)Direct Page Indirect, XC126+1 if m=0, +1 if DP.l ≠ 0
CMP (dp), YDP Indirect Indexed, YD125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
CMP [dp], YDP Indirect Long Indexed, YD726+1 if m=0, +1 if DP.l ≠ 0
CMP sr, SStack RelativeC324+1 if m=0
CMP (sr, S), YSR Indirect Indexed, YD327+1 if m=0
CPX #constImmediateE02 / 32+1 if x=0
CPX addrAbsoluteEC34+1 if x=0
CPX dpDirect PageE423+1 if x=0, +1 if DP.l ≠ 0
CPY #constImmediateC02 / 32+1 if x=0
CPY addrAbsoluteCC34+1 if x=0
CPY dpDirect PageC423+1 if x=0, +1 if DP.l ≠ 0
DECAccumulator3A12
DEC addrAbsoluteCE36+2 if m=0
DEC dpDirect PageC625+2 if m=0, +1 if DP.l ≠ 0
DEC addr, XAbsolute Indexed, XDE37+2 if m=0, +1 if index crosses page boundary
DEC dp, XDirect Page Indexed, XD626+2 if m=0, +1 if DP.l ≠ 0
DEXImpliedCA12
DEYImplied8812
EOR #constImmediate492 / 32+1 if m=0
EOR addrAbsolute4D34+1 if m=0
EOR longAbsolute Long4F45+1 if m=0
EOR dpDirect Page4523+1 if m=0, +1 if DP.l ≠ 0
EOR (dp)Direct Page Indirect5225+1 if m=0, +1 if DP.l ≠ 0
EOR [dp]Direct Page Indirect Long4726+1 if m=0, +1 if DP.l ≠ 0
EOR addr, XAbsolute Indexed, X5D34+1 if m=0, +1 if index crosses page boundary
EOR long, XAbsolute Long Indexed, X5F45+1 if m=0
EOR addr, YAbsolute Indexed, Y5934+1 if m=0, +1 if index crosses page boundary
EOR dp, XDirect Page Indexed, X5524+1 if m=0, +1 if DP.l ≠ 0
EOR (dp, X)Direct Page Indirect, X4126+1 if m=0, +1 if DP.l ≠ 0
EOR (dp), YDP Indirect Indexed, Y5125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
EOR [dp], YDP Indirect Long Indexed, Y5726+1 if m=0, +1 if DP.l ≠ 0
EOR sr, SStack Relative4324+1 if m=0
EOR (sr, S), YSR Indirect Indexed, Y5327+1 if m=0
INCAccumulator1A12
INC addrAbsoluteEE36+2 if m=0
INC dpDirect PageE625+2 if m=0, +1 if DP.l ≠ 0
INC addr, XAbsolute Indexed, XFE37+2 if m=0, +1 if index crosses page boundary
INC dp, XDirect Page Indexed, XF626+2 if m=0, +1 if DP.l ≠ 0
INXImpliedE812
INYImpliedC812
JMP addrAbsolute4C33
JMP (addr)Absolute Indirect6C35
JMP (addr, X)Absolute Indexed Indirect, X7C36
JMP long
JML longAbsolute Long5C44
JMP [addr]
JML [addr]Absolute Indirect LongDC36
JSR addrAbsolute2036
JSR (addr, X)Absolute Indexed Indirect, XFC38
JSL longAbsolute Long2248
LDA #constImmediateA92 / 32+1 if m=0
LDA addrAbsoluteAD34+1 if m=0
LDA longAbsolute LongAF45+1 if m=0
LDA dpDirect PageA523+1 if m=0, +1 if DP.l ≠ 0
LDA (dp)Direct Page IndirectB225+1 if m=0, +1 if DP.l ≠ 0
LDA [dp]Direct Page Indirect LongA726+1 if m=0, +1 if DP.l ≠ 0
LDA addr, XAbsolute Indexed, XBD34+1 if m=0, +1 if index crosses page boundary
LDA long, XAbsolute Long Indexed, XBF45+1 if m=0
LDA addr, YAbsolute Indexed, YB934+1 if m=0, +1 if index crosses page boundary
LDA dp, XDirect Page Indexed, XB524+1 if m=0, +1 if DP.l ≠ 0
LDA (dp, X)Direct Page Indirect, XA126+1 if m=0, +1 if DP.l ≠ 0
LDA (dp), YDP Indirect Indexed, YB125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
LDA [dp], YDP Indirect Long Indexed, YB726+1 if m=0, +1 if DP.l ≠ 0
LDA sr, SStack RelativeA324+1 if m=0
LDA (sr, S), YSR Indirect Indexed, YB327+1 if m=0
LDX #constImmediateA22 / 32+1 if x=0
LDX addrAbsoluteAE34+1 if x=0
LDX dpDirect PageA623+1 if x=0, +1 if DP.l ≠ 0
LDX addr, YAbsolute Indexed, YBE34+1 if x=0, +1 if index crosses page boundary
LDX dp, YDirect Page Indexed, YB624+1 if x=0, +1 if DP.l ≠ 0
LDY #constImmediateA02 / 32+1 if x=0
LDY addrAbsoluteAC34+1 if x=0
LDY dpDirect PageA423+1 if x=0, +1 if DP.l ≠ 0
LDY addr, XAbsolute Indexed, XBC34+1 if x=0, +1 if index crosses page boundary
LDY dp, XDirect Page Indexed, XB424+1 if x=0, +1 if DP.l ≠ 0
LSRAccumulator4A12
LSR addrAbsolute4E36+1 if m=0
LSR dpDirect Page4625+1 if m=0, +1 if DP.l ≠ 0
LSR addr, XAbsolute Indexed, X5E37+1 if m=0, +1 if index crosses page boundary
LSR dp, XDirect Page Indexed, X5626+1 if m=0, +1 if DP.l ≠ 0
MVN srcBank, destBankBlock Move5437 per byte moved
MVP srcBank, destBankBlock Move4437 per byte moved
NOPImpliedEA12
ORA #constImmediate092 / 32+1 if m=0
ORA addrAbsolute0D34+1 if m=0
ORA longAbsolute Long0F45+1 if m=0
ORA dpDirect Page0523+1 if m=0, +1 if DP.l ≠ 0
ORA (dp)Direct Page Indirect1225+1 if m=0, +1 if DP.l ≠ 0
ORA [dp]Direct Page Indirect Long0726+1 if m=0, +1 if DP.l ≠ 0
ORA addr, XAbsolute Indexed, X1D34+1 if m=0, +1 if index crosses page boundary
ORA long, XAbsolute Long Indexed, X1F45+1 if m=0
ORA addr, YAbsolute Indexed, Y1934+1 if m=0, +1 if index crosses page boundary
ORA dp, XDirect Page Indexed, X1524+1 if m=0, +1 if DP.l ≠ 0
ORA (dp, X)Direct Page Indirect, X0126+1 if m=0, +1 if DP.l ≠ 0
ORA (dp), YDP Indirect Indexed, Y1125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
ORA [dp], YDP Indirect Long Indexed, Y1726+1 if m=0, +1 if DP.l ≠ 0
ORA sr, SStack Relative0324+1 if m=0
ORA (sr, S), YSR Indirect Indexed, Y1327+1 if m=0
PEA addrStack (Absolute)F435
PEI (dp)Stack (DP Indirect)D426+1 if DP.l ≠ 0
PER labelStack (PC Relative Long)6236
PHAPush Accumulator4813+1 if m=0
PHBPush Data Bank8B13
PHDPush Direct Page Register0B14
PHKPush Program Bank Register4B13
PHPPush Processor Status Register0813
PHXPush Index Register XDA13+1 if x=0
PHYPush Index Register Y5A13+1 if x=0
PLAPull Accumulator6814+1 if m=0
PLBPull Data BankAB14
PLDPull Direct Page Register2B15
PLPPull Processor Status Register2814
PLXPull Index Register XFA14+1 if x=0
PLYPull Index Register Y7A14+1 if x=0
REP #constImmediateC223
ROLAccumulator2A12
ROL addrAbsolute2E36+1 if m=0
ROL dpDirect Page2625+1 if m=0, +1 if DP.l ≠ 0
ROL addr, XAbsolute Indexed, X3E37+1 if m=0, +1 if index crosses page boundary
ROL dp, XDirect Page Indexed, X3626+1 if m=0, +1 if DP.l ≠ 0
RORAccumulator6A12
ROR addrAbsolute6E36+1 if m=0
ROR dpDirect Page6625+1 if m=0, +1 if DP.l ≠ 0
ROR addr, XAbsolute Indexed, X7E37+1 if m=0, +1 if index crosses page boundary
ROR dp, XDirect Page Indexed, X7626+1 if m=0, +1 if DP.l ≠ 0
RTIStack (return interrupt)4016+1 if e=0
RTSStack (return)6016
RTLStack (return long)6B16
SBC #constImmediateE92 / 32+1 if m=0
SBC addrAbsoluteED34+1 if m=0
SBC longAbsolute LongEF45+1 if m=0
SBC dpDirect PageE523+1 if m=0, +1 if DP.l ≠ 0
SBC (dp)Direct Page IndirectF225+1 if m=0, +1 if DP.l ≠ 0
SBC [dp]Direct Page Indirect LongE726+1 if m=0, +1 if DP.l ≠ 0
SBC addr, XAbsolute Indexed, XFD34+1 if m=0, +1 if index crosses page boundary
SBC long, XAbsolute Long Indexed, XFF45+1 if m=0
SBC addr, YAbsolute Indexed, YF934+1 if m=0, +1 if index crosses page boundary
SBC dp, XDirect Page Indexed, XF524+1 if m=0, +1 if DP.l ≠ 0
SBC (dp, X)Direct Page Indirect, XE126+1 if m=0, +1 if DP.l ≠ 0
SBC (dp), YDP Indirect Indexed, YF125+1 if m=0, +1 if DP.l ≠ 0, +1 if index crosses page boundary
SBC [dp], YDP Indirect Long Indexed, YF726+1 if m=0, +1 if DP.l ≠ 0
SBC sr, SStack RelativeE324+1 if m=0
SBC (sr, S), YSR Indirect Indexed, YF327+1 if m=0
SECSet Carry Flag3812
SEISet Interrupt Disable Flag7812
SEDSet Decimal FlagF812
SEP #constImmediateE223
STA addrAbsolute8D34+1 if m=0
STA longAbsolute Long8F45+1 if m=0
STA dpDirect Page8523+1 if m=0, +1 if DP.l ≠ 0
STA (dp)Direct Page Indirect9225+1 if m=0, +1 if DP.l ≠ 0
STA [dp]Direct Page Indirect Long8726+1 if m=0, +1 if DP.l ≠ 0
STA addr, XAbsolute Indexed, X9D35+1 if m=0
STA long, XAbsolute Long Indexed, X9F45+1 if m=0
STA addr, YAbsolute Indexed, Y9935+1 if m=0
STA dp, XDirect Page Indexed, X9524+1 if m=0, +1 if DP.l ≠ 0
STA (dp, X)Direct Page Indirect, X8126+1 if m=0, +1 if DP.l ≠ 0
STA (dp), YDP Indirect Indexed, Y9126+1 if m=0, +1 if DP.l ≠ 0
STA [dp], YDP Indirect Long Indexed, Y9726+1 if m=0, +1 if DP.l ≠ 0
STA sr, SStack Relative8324+1 if m=0
STA (sr, S), YSR Indirect Indexed, Y9327+1 if m=0
STPImpliedDB13
STX addrAbsolute8E34+1 if x=0
STX dpDirect Page8623+1 if x=0, +1 if DP.l ≠ 0
STX dp, YDirect Page Indexed, Y9624+1 if x=0, +1 if DP.l ≠ 0
STY addrAbsolute8C34+1 if x=0
STY dpDirect Page8423+1 if x=0, +1 if DP.l ≠ 0
STY dp, XDirect Page Indexed, X9424+1 if x=0, +1 if DP.l ≠ 0
STZ addrAbsolute9C34+1 if m=0
STZ dpDirect Page6423+1 if m=0, +1 if DP.l ≠ 0
STZ addr, XAbsolute Indexed, X9E35+1 if m=0
STZ dp, XDirect Page Indexed, X7424+1 if m=0, +1 if DP.l ≠ 0
TAXTransfer A to XAA12
TAYTransfer A to YA812
TCDTransfer 16 bit A to DP5B12
TCSTransfer 16 bit A to SP1B12
TDCTransfer DP to 16 bit A7B12
TSCTransfer SP to 16 bit A3B12
TSXTransfer SP to XBA12
TXATransfer X to A8A12
TXSTransfer X to SP9A12
TXYTransfer X to Y9B12
TYATransfer Y to A9812
TYXTransfer Y to XBB12
TRB addrAbsolute1C36+2 if m=0
TRB dpDirect Page1425+2 if m=0, +1 if DP.l ≠ 0
TSB addrAbsolute0C36+2 if m=0
TSB dpDirect Page0425+2 if m=0, +1 if DP.l ≠ 0
WAIImpliedCB13additional cycles needed by interrupt handler to restart the processor
WDMImplied4222
XBAImpliedEB13
XCEImpliedFB12

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:

Comments

  1. hitachihex hitachihex

    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

Leave a Reply