from tests.helpers import (
NEGATIVE_FLAG,
ZERO_FLAG,
load_program,
make_cpu_with_rom,
write_reset_vector,
)
CARRY_FLAG = 1 << 0
DECIMAL_FLAG = 1 << 3
OVERFLOW_FLAG = 1 << 6
STACK_BASE = 0x0100
def prepare_cpu(program, start_addr=0x8000):
cpu, bus, rom = make_cpu_with_rom()
write_reset_vector(rom, start_addr)
load_program(rom, start_addr, program)
cpu.reset()
return cpu, bus, rom
def run_steps(cpu, count: int):
for _ in range(count):
cpu.step()
def test_CPU_VERIFICATION_stack_roundtrip_program():
"""
Final boss objective:
Execute a real stack roundtrip program.
Program:
LDA #$42
PHA
LDA #$00
PLA
NOP
Expected:
A returns to $42 after PLA.
Stack pointer returns to reset value $FD.
Zero and Negative reflect $42.
"""
cpu, bus, rom = prepare_cpu([
0xA9, 0x42,
0x48,
0xA9, 0x00,
0x68,
0xEA,
])
run_steps(cpu, 5)
assert cpu.a == 0x42
assert cpu.s == 0xFD
assert cpu.flags.get_zero_flag() is False
assert cpu.flags.get_negative_flag() is False
assert cpu.pc == 0x8007
def test_CPU_VERIFICATION_subroutine_program():
"""
Final boss objective:
Execute JSR/RTS as a real program flow.
Program:
$8000: JSR $8007
$8003: LDA #$02
$8005: NOP
$8006: NOP
$8007: LDA #$01
$8009: RTS
Expected:
Subroutine first sets A to $01.
RTS returns to $8003.
Main code then sets A to $02.
"""
cpu, bus, rom = prepare_cpu([
0x20, 0x07, 0x80,
0xA9, 0x02,
0xEA,
0xEA,
0xA9, 0x01,
0x60,
])
run_steps(cpu, 4)
assert cpu.a == 0x02
assert cpu.s == 0xFD
assert cpu.pc == 0x8005
def test_CPU_VERIFICATION_branch_program_skips_instruction():
"""
Final boss objective:
Execute a real conditional branch.
Program:
LDA #$00 ; sets Zero
BEQ +2 ; skip LDA #$01
LDA #$01 ; must be skipped
LDA #$03 ; branch target
Expected:
A = $03, proving the branch target was used.
"""
cpu, bus, rom = prepare_cpu([
0xA9, 0x00,
0xF0, 0x02,
0xA9, 0x01,
0xA9, 0x03,
])
run_steps(cpu, 3)
assert cpu.a == 0x03
assert cpu.flags.get_zero_flag() is False
assert cpu.pc == 0x8008
def test_CPU_VERIFICATION_loop_program_with_dex_and_bne():
"""
Final boss objective:
Execute a tiny loop using DEX and BNE.
Program:
LDX #$03
DEX
BNE -3
STX $10
Expected:
X counts down to $00.
BNE stops branching when Zero is set by DEX.
STX stores $00 into RAM address $0010.
"""
cpu, bus, rom = prepare_cpu([
0xA2, 0x03,
0xCA,
0xD0, 0xFD,
0x86, 0x10,
])
run_steps(cpu, 8)
assert cpu.x == 0x00
assert cpu.flags.get_zero_flag() is True
assert bus.read(0x0010) == 0x00
assert cpu.pc == 0x8007
def test_CPU_VERIFICATION_flag_control_program():
"""
Final boss objective:
Execute several flag-control opcodes together.
Program:
SEC
SED
CLV
CLC
CLD
SEI
CLI
Expected final flags:
Carry clear
Decimal clear
Overflow clear
Interrupt Disable clear
"""
cpu, bus, rom = prepare_cpu([
0x38,
0xF8,
0xB8,
0x18,
0xD8,
0x78,
0x58,
])
cpu.p = OVERFLOW_FLAG
run_steps(cpu, 7)
assert (cpu.p & CARRY_FLAG) == 0
assert (cpu.p & DECIMAL_FLAG) == 0
assert (cpu.p & OVERFLOW_FLAG) == 0
assert cpu.flags.get_interrupt_disable_flag() is False
assert cpu.pc == 0x8007
def test_CPU_VERIFICATION_transfers_and_stack_pointer_program():
"""
Final boss objective:
Execute register transfers involving X and S.
Program:
LDX #$80
TXS
LDX #$00
TSX
Expected:
TXS copies $80 into S.
TSX copies S back into X.
TSX sets Negative because X becomes $80.
"""
cpu, bus, rom = prepare_cpu([
0xA2, 0x80,
0x9A,
0xA2, 0x00,
0xBA,
])
run_steps(cpu, 4)
assert cpu.s == 0x80
assert cpu.x == 0x80
assert (cpu.p & NEGATIVE_FLAG) != 0
assert (cpu.p & ZERO_FLAG) == 0
def test_CPU_VERIFICATION_OPCODE_GAUNTLET_program():
"""
Final boss objective:
Execute a larger real program that combines many official opcode families.
This is not a replacement for the focused per-instruction tests. Instead, it
is an integration confidence test: many instructions must cooperate through
real CPU.step() sequencing, real PC movement, real stack behavior, real
status flags, and real RAM writes.
Program overview:
- load/store: LDA, LDX, LDY, STA, STX, STY
- arithmetic: ADC, SBC
- memory math: INC, DEC
- logic: AND, ORA, EOR, BIT
- compare/branch: CMP, BEQ
- stack: PHA, PLA, PHP, PLP
- shifts/rotates: ASL, LSR, ROL, ROR accumulator forms
- transfers: TAX, TXA, TAY, TYA, TXS, TSX
- increments/decrements: INX, DEX, INY, DEY
- flag controls: CLC, SEC, CLD, SED, CLV, CLI, SEI
- NOP
Why this test matters:
A CPU can pass isolated instruction tests but still fail when instructions
interact. This test catches integration bugs such as:
- PC off-by-one errors
- stale flags influencing ADC/SBC or branches
- stack pointer push/pull mistakes
- transfer instructions updating or not updating flags incorrectly
- RAM writes going to the wrong address
"""
cpu, bus, rom = prepare_cpu([
0xA9, 0x10,
0x85, 0x10,
0xA2, 0x03,
0xA0, 0x05,
0xE6, 0x10,
0xC6, 0x10,
0xA5, 0x10,
0x69, 0x05,
0x38,
0xE9, 0x05,
0x29, 0x0F,
0x09, 0x80,
0x49, 0xFF,
0xC9, 0x7F,
0xF0, 0x02,
0xA9, 0x00,
0x24, 0x10,
0x48,
0xA9, 0x00,
0x68,
0x0A,
0x4A,
0x2A,
0x6A,
0xAA,
0xE8,
0xCA,
0x8A,
0xA8,
0xC8,
0x88,
0x98,
0x9A,
0xBA,
0x08,
0x28,
0x18,
0x38,
0xD8,
0xF8,
0xB8,
0x58,
0x78,
0xEA,
0x86, 0x11,
0x84, 0x12,
0x85, 0x13,
])
run_steps(cpu, 46)
assert cpu.a == 0x7F
assert cpu.x == 0x7F
assert cpu.y == 0x7F
assert cpu.s == 0x7F
assert bus.read(0x0010) == 0x10
assert bus.read(0x0011) == 0x7F
assert bus.read(0x0012) == 0x7F
assert bus.read(0x0013) == 0x7F
assert cpu.flags.get_carry_flag() is True
assert cpu.flags.get_decimal_flag() is True
assert cpu.flags.get_interrupt_disable_flag() is True
assert cpu.flags.get_overflow_flag() is False
assert cpu.flags.get_zero_flag() is False
assert cpu.flags.get_negative_flag() is False
assert cpu.pc == 0x8043