Graphical Integrated Debugging and Emulation Suite for 6502 CPU in C
Hello everyone, in this blog post I will be explaining how and why I wrote c6502, an integrated debugging and emulation suite that I developed for the MOS 6502.
A little bit of tech history
The MOS 6502 is one of the most influential microprocessors in computing history. Introduced in 1975 by MOS Technology, the 6502 was designed to be a low-cost, high-performance alternative to the more expensive processors of the time, such as Intel’s 8080 and Motorola’s 6800.
The 6502 found its way into iconic devices like the Apple I/II, the Commodore PET, and the Atari 2600, fueling the growth of personal computing and gaming. Its simple yet powerful instruction set made it an excellent learning tool for budding engineers and programmers.
Why?
When I started tinkering with 6502 assembly, I quickly realized there wasn’t a single tool that combined all the features I needed. There were great emulators, solid debuggers, and even some decompilers — but they were all separate. Switching between programs disrupted the workflow. So, I created one :)
The goal
My biggest goal with c6502 was to actually run assembly and binaries written for the MOS 6502. I used the cc65 toolchain (ca65
assembler and ld65
linker) to build binaries, then fed them into c6502.
A real 6502 code and binary that runs assembled on c6502:
Another goal was to monitor memory and registers in real time — also supported.
Features overview
- Emulation: Accurately emulates the MOS 6502 CPU and its instruction set, handling memory and flags.
- Debugging: Breakpoints, single-stepping, and real-time CPU state inspection.
- Real-Time Memory and Register Visualization: Watch registers and memory mutate while code runs.
- Decompiler: Converts 6502 binaries back to readable assembly to aid reverse engineering.
The emulation process
In c6502, the emulation process closely mimics the behavior of the 6502:
1. Resetting the CPU
In c_reset()
the registers (A, X, Y, PC, SP) and flags are initialized. Acc, X, Y → 0; PC → 0x0000; flags cleared.
2. Fetching the instruction
uint8_t opcode = memory->mem[cpu->reg.pc];
printf("PC: 0x%04x, Opcode: 0x%02x\n", cpu->reg.pc, opcode);
3. Decoding the instruction
for (size_t i = 0; i < set_size; i++) {
if (opcode == instruction_set[i].opcode) {
instruction = &instruction_set[i];
break;
}
}
4. Addressing modes
Addressing mode determines how to obtain operands (immediate/absolute/zero page/etc.). Example absolute:
uint16_t address = ((mem->mem[cpu->reg.pc + 2] << 8) | mem->mem[cpu->reg.pc + 1]);
5. Executing instructions
Example LDA handler:
void lda_handler(c_cpu_t *cpu, m_memory_t *mem, uint16_t address)
{
cpu->reg.acc = mem->mem[address];
if (cpu->reg.acc == 0) { SET_FLAG(cpu->reg, FLAG_ZERO); }
else { CLEAR_FLAG(cpu->reg, FLAG_ZERO); }
if (cpu->reg.acc & 0x80) { SET_FLAG(cpu->reg, FLAG_NEGATIVE); }
else { CLEAR_FLAG(cpu->reg, FLAG_NEGATIVE); }
}
6. Putting all together
Reset → fetch → decode → address → execute, looped—mirroring the original hardware while exposing a transparent, real-time debugging view.
The c6502 User Interface
The GUI keeps everything in one place for emulation, debugging, and reverse engineering.
- Virtual interface — Main display area for visual output.
- Disassembler — Live disassembly that follows execution and highlights the current instruction.
- Register status — Real-time PC, SP, A, X, Y and flags view.
- Memory status — Inspect memory values and how instructions affect them in real time.
Overall Design Philosophy
All critical info at a glance without switching tools — ideal for learning, debugging, and analysis.
Conclusion
Developing c6502 merged my love for retrocomputing with low-level programming and tool building. The result is a comprehensive suite that integrates emulation, debugging, and decompilation into a single, user-friendly tool. If you’re interested in trying it, grab it on GitHub.