This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
The Final Test: Complete the ALU (SUB, SBC) for the Checksum
Summary
The emulator has passed alldeadlockssync, but the screen is still blank because the VRAM remains empty. The diagnostic indicates that the CPU is failing the cartridge header checksum verification because it is missing subtraction instructions (SUB, SBC). As a result, the boot software goes into a deliberate infinite loop, preventing the game from starting.
This Step completes the CPU ALU by implementing and correcting the instructionsSUB A, randSBC A, r(Subtract with Carry), allowing the CPU to correctly calculate the cartridge checksum and pass the boot sequence. With the ALU complete, the emulator will finally be able to pass the cartridge integrity check and hand over control to the main game.
x = 0; for (i = 0x0134; i<= 0x014C; i++) { x = x - rom[i] - 1; }, which depends fundamentally on the instructionsSUBandSBC. If these instructions are not implemented or have bugs, the checksum will be incorrect and the system will deliberately crash.Hardware Concept: The Cartridge Header Checksum and Arithmetic
The header of the ROM, at address0x014D, contains an 8-bit checksum. The boot software calculates its own checksum to validate the integrity of the ROM. The formula is:
x = 0;
for (i = 0x0134; i<= 0x014C; i++) {
x = x - rom[i] - 1;
}
This repeated operation of subtraction and decrement depends fundamentally on the instructionsSUB(subtraction) andSBC(subtraction with carry/borrow). If any of these instructions fail or are not implemented, the checksum will be incorrect and the system will crash.
Why is it critical?The boot code (either the BIOS or the game itself) performs this check as a security measure. If the calculated checksum does not match the one stored in0x014D, the system deliberately enters an infinite loop to freeze the system. Does not copy the graphics. The game does not start. It just stops safely.
Fountain:Pan Docs - Cartridge Header, Checksum Calculation.
Implementation
1. Correctness of SBC Implementation
Although the functionsalu_subandalu_sbcwere already implemented, a subtle bug was detected in the calculation of the C flag (Carry/Borrow) inalu_sbc. The original implementation useda_old< (value + carry), which can cause overflow ifvalue + carry > 255.
Fixed to safely use 16-bit result:result > 0xFFindicates that there was underflow (the result is negative in 16-bit signed arithmetic), which is the correct condition to activate the C flag in a subtraction.
void CPU::alu_sbc(uint8_t value) {
// SBC: Subtract with Carry - A = A - value - C
uint8_t a_old = regs_->a;
uint8_t carry = regs_->get_flag_c() ? 1 : 0;
uint16_t result = static_cast<uint16_t>(a_old) -
static_cast<uint16_t>(value) -
static_cast<uint16_t>(carry);
regs_->a = static_cast<uint8_t>(result);
// Calculate flags
regs_->set_flag_z(regs_->a == 0);
regs_->set_flag_n(true);
// H: half-borrow (bit 4 -> 3) including carry
uint8_t a_low = a_old & 0x0F;
uint8_t value_low = value & 0x0F;
bool half_borrow = (a_low< (value_low + carry));
regs_->set_flag_h(half_borrow);
// C: borrow complete (underflow)
// Use the 16-bit result to safely detect underflow
regs_->set_flag_c(result > 0xFF);
}
2. Verification of SUB and SBC Opcodes
It was verified that all the opcodes ofSUB(0x90-0x97) andSBC(0x98-0x9F) are correctly implemented on the CPU switch:
0x90-0x97:SUB A, r(where r can be B, C, D, E, H, L, (HL), A)0x98-0x9F:SBC A, r(where r can be B, C, D, E, H, L, (HL), A)
All opcodes are correctly mapped and call the corresponding helper functions.
3. Specific Tests for SUB and SBC
Three new tests were added intests/test_core_cpu_alu.pyto specifically validate the instructionsSUBandSBCwith records:
- test_sub_a_b:Verify that
SUB Bcorrectly calculates the subtraction and activates the Z flag when the result is 0. - test_sbc_a_b_with_borrow:Verify that
SBC A,Bworks correctly when the C (borrow) flag is activated. - test_sbc_a_b_with_full_borrow:Verify that
SBC A,Bcorrectly detects the complete borrow (underflow) and activates the C flag.
Affected Files
src/core/cpp/CPU.cpp- Correction of the calculation of the C flag inalu_sbctests/test_core_cpu_alu.py- Added 3 new tests for SUB and SBC with records
Tests and Verification
The ALU tests were run to validate that all subtraction instructions work correctly:
$ python -m pytest tests/test_core_cpu_alu.py -v
============================= test session starts =============================
platform win32 -- Python 3.13.5, pytest-9.0.2, pluggy-1.6.0
collected 10 items
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_add_immediate_basic PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_sub_immediate_zero_flag PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_add_half_carry PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_xor_a_optimization PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_inc_a PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_dec_a PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_add_full_carry PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_sub_a_b PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_sbc_a_b_with_borrow PASSED
tests/test_core_cpu_alu.py::TestCoreCPUALU::test_sbc_a_b_with_full_borrow PASSED
============================= 10 passed in 0.07s =============================
Compiled C++ module validation:All tests pass, confirming that the instructionsSUBandSBCThey are correctly implemented and calculate the flags accurately.
SUB specific test:
def test_sub_a_b(self, cpu, mmu):
"""Check SUB B."""
cpu.registers.a = 0x3E
cpu.registers.b = 0x3E
mmu.write(0x0100, 0x90) # SUB B
cpu.registers.pc = 0x0100
cpu.step()
assert cpu.registers.a == 0x00
assert cpu.registers.flag_z is True
assert cpu.registers.flag_n is True
Sources consulted
- Bread Docs:CPU Instruction Set - SUB, SBC
- Bread Docs:Cartridge Header - Checksum Calculation
- GBEDG:Opcodes Table - SUB, SBC
Educational Integrity
What I Understand Now
- The Cartridge Checksum:It is a critical security measure that validates the integrity of the ROM before launching the game. If the calculated checksum does not match the stored one, the system deliberately crashes.
- The Importance of SUB and SBC:These instructions are not just basic arithmetic operations; They are fundamental for the calculation of the checksum. Without them, the emulator cannot pass the integrity check.
- The Flag C in Subtractions:In subtraction, the C flag indicates "borrow". It is activated when there is underflow (the result is negative in signed arithmetic). The safe way to calculate it is using the 16-bit result:
result > 0xFF. - The Boot Sequence:The boot code performs multiple checks before handing over control to the game. The checksum is the last software barrier before the game can start.
What remains to be confirmed
- Verification with Real ROM:Although the unit tests pass, it remains to be verified that the emulator can correctly calculate the checksum of a real ROM and pass the boot verification.
- BIOS behavior:If the emulator uses a real BIOS, the checksum is calculated during BIOS execution. If you don't use BIOS, the game itself can calculate its own checksum.
Hypotheses and Assumptions
Main Hypothesis:With the ALU complete (SUB and SBC properly implemented), the emulator should be able to calculate the cartridge checksum correctly and pass the boot check. This should allow the game to eventually copy the graphics to VRAM and trigger background rendering.
Assumption:We assume that the bootstrap code uses the standard checksum formula documented in Pan Docs. If a game uses a different formula, it might require additional research.
Next Steps
- [ ] Run the emulator with a real ROM (ex:
tetris.gb) and verify that you can calculate the checksum correctly - [ ] Verify that the game passes the boot check and copies the graphics to VRAM
- [ ] If the screen is still blank, investigate other possible causes (e.g. missing instructions, bugs in other parts of the CPU)
- [ ] Document the final result: Is the Nintendo logo visible or are there more barriers to overcome?