Master the fundamental concepts of emulation through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksThe Nintendo Entertainment System runs a Ricoh 2A03, a 6502 derivative at 1.79 MHz (NTSC). Emulating it means accurate fetch-decode-execute, memory maps, and cycle counting for the PPU and APU.
6502 features:
Memory map slices:
For example, opcode \`0xA9\` (LDA immediate) loads the next byte into A and sets Z/N flags.
Cycle accuracy matters: many PPU tricks depend on the CPU running a known number of cycles per instruction.
Read the memory map through a bus function so mapper chips can redirect PRG-ROM windows later. Hard-coding `0x8000` reads works for NROM only; MMC1 and UxROM need bank switching hooks.
This exercise asks you to implement a 6502 CPU core with correct addressing modes for a starter opcode set. You will fetch from the NES memory map and update flags the way test ROMs expect.
You will use the same mental model here when reading production interpreter source later in the track. Sketch one concrete input on paper, predict the outcome, then confirm with code. That discipline catches logic errors early and makes debugging far faster when you extend the implementation in follow-on tasks.
Implement the NES CPU, a MOS 6502 without decimal mode: registers A, X, Y, SP and P, a 64 KiB address space, the stack at $0100-$01FF, the official instruction set with all addressing modes, exact flag behaviour, and cycle counting, including page-crossing penalties. Print a nestest-style trace, the way emulator authors validate their CPUs.
Whitespace-separated tokens. ; starts a comment.
XX: a program byte (2 hex digits), loaded from the origin upward.org ADDR: the load and start address (hex, before the first byte; default 0600).mem ADDR XX XX ...: preloads memory (to the end of the line).dump ADDR N: prints N (1..16) bytes after the run.trace off.cycles N: stops before an instruction once the count ≥ N (1..10000000, default 100000).run: runs, then resets everything to defaults. The end of input runs any pending program.The power-on state is A = X = Y = 0, P = $24, SP = $FD, and the cycle count starts at 7 (the reset sequence).
Every official opcode and mode is used. Semantics:
A + M + C. SBC is ADC with ~M. V is set when the operands have the same sign and the result's sign differs.C = reg ≥ M, and N/Z come from reg - M. BIT: Z = (A & M) == 0, with N and V copied from M bits 7 and 6.P | $30. PLP loads (pulled & ~$10) | $20. JSR pushes the address of its own last byte, and RTS adds 1.($zp,X) and ($zp),Y pointers. JMP ($xxFF) reads its high byte from $xx00, the 6502 bug.| group | IMM | ZP | ZP,X/Y | ABS | ABS,X/Y | (ZP,X) | (ZP),Y |
|---|---|---|---|---|---|---|---|
| LDA LDX LDY ORA AND EOR ADC SBC CMP | 2 | 3 | 4 | 4 | 4+ | 6 | 5+ |
| STA STX STY | 3 | 4 | 4 | 5 | 6 | 6 | |
| ASL LSR ROL ROR INC DEC | A: 2 | 5 | 6 | 6 | 7 | ||
| CPX CPY / BIT | 2 / - | 3 | 4 |
+ adds 1 when the indexed address is on a different page from the base.cLoading…
%04X %-8s %-13s A:.. X:.. Y:.. P:.. SP:.. CYC:n), including the BRK.#$05, $10, $10,X, $1234,Y, ($1234), ($10,X), ($10),Y and A. Branches show the target address.NV-BDIZC, uppercase when set.illegal opcode $XX at $XXXX after … and halted: cycle limit N reached.bad byte: X, no program, dump: ADDR 1..16, cycles: 1..10000000, org must come before the program.Input:
cLoading…
Output:
cLoading…
Hidden tests cover signed overflow, SBC borrow, compare and BIT flags, a page-crossing abs,X read, the JMP ($10FF) bug, same-page and cross-page taken branches, ($zp),Y with a page cross, zero-page index wraparound, read-modify-write on memory, illegal opcodes and the cycle limit.