223. Validation cpu executes tiny ines rom
VALIDATION TEST: CPU executes a tiny generated iNES ROM.
Lesson 223 of 356 · tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.py
This is not a student implementation step.
Prerequisites under validation
- this file: make_tiny_ines_rom and
test_cpu_executes_tiny_generated_ines_rom_from_reset_vector
- emulator/cartridge/ines.py: PRG_ROM_BANK_SIZE, CHR_ROM_BANK_SIZE,
parse_ines_header, and parse_ines_rom
- emulator/cartridge/cartridge.py: Cartridge.from_ines_bytes
- emulator/cartridge/mapper_factory.py: create_mapper
- emulator/cartridge/mapper000.py: Mapper000.read_prg
- emulator/bus/cpu_bus.py: CpuBus.__post_init__ and CpuBus.read
- emulator/cpu/cpu.py: CPU.reset, CPU.fetch_byte, and CPU.step
- emulator/cpu/opcodes.py: OPCODE_TABLE entry $A9
- emulator/cpu/instructions.py: ldaValidation sequence
1. make_tiny_ines_rom builds a 16-byte iNES header declaring mapper 0, one
16KB PRG bank, one 8KB CHR bank, and no trainer.
2. It writes A9 42 (LDA #$42) and EA (NOP) at PRG offsets $0000-$0002,
corresponding to CPU $8000-$8002.
3. It writes reset-vector bytes 00 80 at PRG offsets $3FFC-$3FFD. Mapper000's
16KB mirror makes CPU reads $FFFC-$FFFD observe those bytes.
4. Cartridge.from_ines_bytes parses and slices the generated bytes.
5. CpuBus(cartridge=cartridge) creates Mapper000 through create_mapper.
6. CPU.reset reads the vector through CpuBus and Mapper000, producing
PC=$8000.
7. One CPU.step fetches $A9 through the same path, dispatches lda, fetches
operand $42, and leaves A=$42 and PC=$8002. The trailing NOP is fixture
data and is not executed by this test.Run exactly this validation with
pytest -q tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.pyThe full boundary under validation is
iNES bytes
-> Cartridge.from_ines_bytes(data)
-> CpuBus(cartridge=cartridge)
-> create_mapper(cartridge)
-> Mapper000
-> CPU.reset()
-> CPU.step()Why this step exists
Validation exists because unit tests can prove each part in isolation while bugs still appear at component boundaries. This test adds no production implementation; it proves that lessons 212-222 work together so the CPU can fetch and execute an instruction from cartridge-backed PRG ROM using the reset vector.
The ROM is generated inside the test. It is intentionally tiny and does not use PPU, APU, controllers, interrupts, or any feature outside the current emulator scope.
Tiny program placed at CPU address $8000:
A9 42 LDA #$42
EA NOPReset vector
CPU $FFFC = $00
CPU $FFFD = $80So after reset
PC = $8000Important Mapper000 detail
For a 16KB NROM cartridge, CPU $FFFC-$FFFD maps to PRG ROM offsets $3FFC-$3FFD because $C000-$FFFF mirrors the 16KB PRG bank.
Invariants
The iNES payload is complete and in-memory; mapper number and bank sizes survive parsing; reset-vector reads and opcode/operand fetches all use cartridge-backed PRG routing; reset selects $8000; and one instruction consumes exactly two bytes. A common misconception is to place the vector at offset $7FFC as with a 32KB/FakeROM layout, or to claim the NOP was validated after only one step.
Out of scope
Do not add production code for this validation. PPU construction/register routing (the following Chapter 3 transition), APU, controllers, interrupts, timing, PRG writes, CHR/PPU reads, external ROM files, and later console/PPU integration are intentionally not exercised or implied here.
Run this lesson
uv run pytest tests/chapter_02_rom_loading/test_223_VALIDATION_cpu_executes_tiny_ines_rom.py -v