An assembler-based executor for 6502 and compatible processors, with an interactive monitor, symbol table support, a Dear ImGui-based graphical debugger, and an MCP server for LLM integration. Assembly is handled by the embedded KickAssembler (65CE02/45GS02 fork), enabling full macro support and rich diagnostic output.
For a full walkthrough of all features, see doc/tutorial.md. For detailed information on the graphical debugger, see README-gui.md.
Help with this development by contributing and buy me coffee at: https://kodecoffee.com/i/ctalkobt
- Features
- Graphical Debugger (GUI)
- Building
- Quick Start
- Command-Line Options
- Assembler Syntax
- Interactive Monitor
- Symbol Tables
- MCP Server
- Project Scaffolding (Environments)
- File Structure
- Known Limitations
| Flag | Processor | Notes |
|---|---|---|
6502 |
NMOS 6502 | Standard documented opcodes |
6502-undoc |
NMOS 6502 | Includes undocumented/illegal opcodes |
65c02 |
CMOS 65C02 | WDC extensions (BIT imm, STZ, TRB, TSB, …) |
65ce02 |
CSG 65CE02 | Adds Z register, B register, 16-bit branches, word ops |
45gs02 |
MEGA65 45GS02 | Full 32-bit quad instructions, MAP translation, 28-bit flat addressing |
- Quad Instructions: 32-bit operations (LDQ, STQ, ADDQ, etc.) via the
$42 $42prefix. - Flat Addressing: Access up to 256MB of memory using 28-bit physical addresses.
- MAP Translation: 8KB virtual memory blocks can be mapped to any 20-bit physical offset.
- Extended Registers: 8-bit Z and B registers, and a 16-bit Stack Pointer (SP).
- New Addressing Modes: Stack-relative indirect indexed
(d,SP),Yand flat indirect[d]or[d],Z.
Assembly is performed by KickAssembler (tools/KickAss65CE02.jar) — a 65CE02/45GS02-extended fork of the standard KickAssembler v5.24. Java 8+ is required.
Key syntax conventions:
- Comments:
//single-line;/* */block. - Origin:
* = $0200 - CPU selection:
.cpu _45gs02,.cpu _65ce02,.cpu _65c02,.cpu _6502— the simulator reads this directive and auto-selects the matching processor; no-pflag is needed. - Mnemonics: lowercase (
lda,sta,jsr, …) - Pseudo-ops:
.byte,.word,.text,.align,.fill,.import; KickAssembler macros and functions are supported. - Simulator pseudo-ops: Embedded in
//comments so files assemble cleanly without the simulator; see Simulator Pseudo-ops below.
When the simulator loads a .asm file with no corresponding .prg, it auto-assembles via KickAssembler and caches the result.
- History / Time Machine: Step backwards and forwards through execution history. The simulator records register states and memory deltas.
- Up to 16 simultaneous breakpoints: Supports complex conditions (e.g.,
PC == $1234 && A == $00 && .Z == 1). - Execution trace: Detailed logs of every instruction, address, register state, and cycle count.
- Interactive monitor: Full-featured CLI for inspecting/modifying registers and memory.
- ROM TRAP intercept: Simulate Kernal/ROM calls (e.g., CHROUT) without loading real ROM files.
- Speed throttle: Configurable run-speed limiter;
1.0= C64 PAL clock (~985 kHz),0= unlimited.
- Software rasteriser: All display modes rendered — Standard Char, Multicolour Char, Extended Colour (ECM), Standard Bitmap, Multicolour Bitmap.
- Sprite rendering: All 8 hardware sprites with X/Y expansion, multicolour, X-MSB, and correct priority.
- CLI commands:
vic2.info,vic2.regs,vic2.sprites,vic2.savescreen,vic2.savebitmap. - MCP tools:
vic2_info,vic2_regs,vic2_sprites,vic2_savescreen,vic2_savebitmap.
- Sparse 28-bit Space: Supports up to 256MB of physical memory, allocated on demand in 4KB pages.
- MAP Translation: Full implementation of the C65/MEGA65 MAP register logic for virtual-to-physical address mapping.
- Write Logging: Tracks memory writes for history undo/redo and GUI highlighting.
The simulator includes a template-based project scaffolding system to instantly bootstrap development environments.
| Command | Description |
|---|---|
env list |
List all available project templates (found in templates/). |
env create <id> <name> [dir] [VAR=VAL...] |
Materialize a new project from a template. Supports custom variable overrides. |
Templates are defined as JSON files in the templates/ directory. They define the directory structure, boilerplate assembly code, Makefiles, and default variables.
Built-in templates include:
6502-minimal: Bare-bones 6502 setup.c64-standard: C64 setup with BASIC stub andKernal loop.mega65-basic: Standard 45GS02 structure with Makefile.mega65-quad: Focused on 32-bit quad operations and extended registers.
The simulator includes a comprehensive IDE-style debugger built with Dear ImGui. It provides a real-time, multi-pane view of the processor state and allows for interactive development.
- Register Display: Live view of all CPU registers (A, X, Y, Z, B, SP, PC) and status flags with diff highlighting.
- Disassembly View: Real-time disassembly with breakpoint toggles, cycle counts, and symbol integration.
- Memory View: Hex + ASCII dump with "follow PC/SP" modes and highlighting of last-written bytes.
- CLI Console: A full-featured interactive terminal mirroring the CLI monitor mode with command history.
- Source Viewer: View the original assembly source code alongside the disassembly.
- Profiler / Heatmap: Visualise execution frequency and cycle consumption across the entire 64KB memory map.
- Watch List: Monitor specific memory addresses and their values in real-time.
- Instruction Reference: Built-in searchable database of all opcodes, addressing modes, and cycle counts for the active processor.
- Symbol Browser: Search and inspect all loaded labels, variables, and constants.
- Stack Inspector: Human-readable view of the hardware stack and annotated frames.
- Trace Log: Rolling ring-buffer of recently executed instructions and CPU states.
- VIC-II Viewer: Three panes — Screen (live 384×272 rendered frame with all modes and sprites), Sprites (per-sprite thumbnail grid), and Registers (decoded D011–D01A, video addresses, colour swatches).
- Speed Throttle: Run menu slider limits execution to a configurable multiple of the C64 PAL clock; setting persists across sessions via ImGui INI.
| Key | Action |
|---|---|
| F5 | Run |
| F6 | Pause |
| Esc | Pause (if running) |
| F7 | Step into (1 instruction) |
| F8 | Step over (executes the JSR body; single-steps otherwise) |
| F9 | Toggle breakpoint at current PC |
| Shift+F7 | Step back 1 instruction in history |
| Shift+F8 | Step forward in history |
| Ctrl+R | Reset CPU |
| Ctrl+L | Focus the filename bar / open load dialog |
| Ctrl+G | Go to address (opens a hex-entry popup, scrolls disassembly) |
| Ctrl+F | Focus search in the active pane (Source / Symbol Browser) |
| Ctrl+Shift+F | Focus disassembly address bar |
` (backtick) |
Focus the CLI console input |
make # build sim6502 (CLI)
make gui # build sim6502-gui (Graphical Debugger)
make test # build and run test suite
make clean # remove object files and binaries- CLI: G++ (C++17 or later), GNU Make.
- Assembler: Java 8+ (for
tools/KickAss65CE02.jar). - GUI: G++ (C++17 or later),
libsdl2-dev,libgl-dev,pkg-config.- Note: Dear ImGui is automatically fetched from GitHub on first
make gui.
- Note: Dear ImGui is automatically fetched from GitHub on first
- MCP Server: Node.js 16+ (
cd src/mcp && npm installbefore first use).
# Assemble and run a file
./sim6502 examples/hello.asm
# Choose a processor variant explicitly
./sim6502 -p 45gs02 examples/45gs02_test.asm
# Processor auto-detected from .cpu directive — no -p needed
./sim6502 examples/45gs02_z_register.asm
# Launch the Graphical Debugger
./sim6502-gui
# Interactive monitor with a source file
./sim6502 -I examples/hello.asm
# Interactive monitor with no source file (blank memory)
./sim6502 -I
# Interactive monitor with a preset symbol table, no file
./sim6502 -I --preset c64
# Enable execution trace
./sim6502 --trace examples/hello.asm
./sim6502 --trace trace.log examples/hello.asm
# Set a breakpoint and view memory on exit
./sim6502 -b 0x0210 -m 0x0200:0x0220 examples/hello.asm
# Load C64 symbols and display them
./sim6502 --preset c64 --show-symbols examples/hello.asmPROCESSOR SELECTION
-p, --processor <CPU> 6502 | 6502-undoc | 65c02 | 65ce02 | 45gs02
-l, --list List all available processor types
EXECUTION
-a, --address <ADDR> Override start address (hex $xxxx, or a label name)
DEBUGGING
-I, --interactive Enter interactive monitor (no source file required)
-b, --break <ADDR> Set a breakpoint (hex address, e.g. 0x1000 or $1000; can also take an optional condition)
-t, --trace [FILE] Enable execution trace; optional output file
MEMORY
-m, --mem <START:END> Hex-dump memory range on exit (e.g. 0x0200:0x0300)
SYMBOL TABLES
--symbols <FILE> Load a custom symbol table file
--preset <ARCH> Load a built-in preset: c64 | c128 | mega65 | x16
--show-symbols Print the loaded symbol table on exit
OTHER
-J, --json Emit all monitor output as JSON (used by the MCP server)
-h, --help Show help and exit
Programs run from address $0200 by default unless overridden with -a.
Execution stops when the program executes RTS with an empty stack (returning to the simulator), at STP, a breakpoint, or after 100 000 cycles. BRK now executes fully — pushing state and jumping through the IRQ vector at $FFFE/$FFFF.
Source files use KickAssembler syntax (65CE02/45GS02 fork). Mnemonics must be lowercase.
.cpu _45gs02 // select processor (at top of file)
* = $0200 // set program counter originAccepted CPU identifiers: _6502, _65c02, _65ce02, _45gs02.
lda #$42 // single-line comment
/* block
comment */SCREEN = $0400 // named constant
COLOUR = $D800loop: // global label
dex
bne looplda #$FF // hex
lda #%11111111 // binary
lda #'A' // character (ASCII 65)
lda #255 // decimalSet the program counter to an absolute address.
* = $C000Emit one or more bytes. Accepts all literal formats and label names (emits the low byte of the label address).
.byte $48, $65, $6C, $6C, $6F // "Hello"Emit 16-bit little-endian words. Useful for vectors and jump tables.
vectors:
.word reset_handler, irq_handlerEmit raw string bytes with no null terminator.
message: .text "Hello, World!\n"Advance the PC to the next multiple of n, padding with zero bytes.
.align $100 // page-alignSimulator pseudo-ops are embedded as // comments so that the .asm file can be assembled directly by KickAssembler without errors. When the simulator loads the file it preprocesses these lines into KickAssembler .print statements that emit the resolved address to stdout, which the simulator captures to register the pseudo-op at the correct run-time address.
When execution reaches this address, the simulator displays state for the named device.
sta $D401 // write note frequency hi byte
//.inspect "SID #1" // show SID state at the next instruction
lda #20
jsr wait_framesWhen a JSR targets this address, the simulator logs the call and simulates an immediate RTS (useful for intercepting Kernal/ROM calls without loading ROM).
CHROUT = $FFD2
//.trap "CHROUT"Override or explicitly declare the target processor variant through the metadata pipeline. Useful in .sym_add companion files or when you want the simulator to select the correct CPU independent of the -p flag.
//.cpu "45gs02"Accepted values: 45gs02, 65ce02, 65c02, 6502.
Note: For .asm files, use the native KickAssembler .cpu _45gs02 directive at the top of the file — the simulator detects it automatically and the preprocessor additionally injects a SIM_CPU: marker so the type flows through the metadata pipeline. The //.cpu comment form is primarily useful in .sym_add companion files alongside pre-assembled .prg/.bin binaries.
Set the target machine profile (controls which I/O devices are mapped into the address space).
//.machine "mega65"Accepted values: mega65, x16, c64, c128, raw6502.
For programs loaded directly as .prg or .bin (built by any assembler), a .sym_add file with the same base name can carry supplemental annotations. The simulator loads it automatically alongside .sym and .list.
A .sym_add file may contain any of the following formats:
; KickAssembler symbol format
.label sid_freq_hi=$0222
; Simulator pseudo-op markers (same format emitted by KickAssembler stdout)
SIM_INSPECT:0222:SID #1
SIM_TRAP:FFD2:CHROUT
; Processor / machine type metadata
SIM_CPU:45gs02
SIM_MACHINE:mega65
SIM_CPU: and SIM_MACHINE: cause the simulator to switch to the named processor variant and machine profile when the file is loaded — identical to the effect of //.cpu and //.machine pseudo-ops in .asm source.
Produce a .sym_add from a KickAssembler build by redirecting stdout:
java -jar tools/KickAss65CE02.jar prog.asm -symbolfile -o prog.prg > prog.sym_addThis allows any external toolchain to annotate .prg/.bin files with .inspect, .trap, and processor metadata without requiring re-assembly through the simulator.
Enter the monitor with -I. Pressing Enter on a blank line executes a single step.
In addition to monitor commands, you can execute individual assembler statements directly by prefixing them with a dot (.).
This is useful for modifying state or testing snippets without advancing the Program Counter of the program you are debugging. The simulator assembles the instruction in the background, executes it, and then restores the original PC.
> .lda #$FF
Executed: lda #$FF (Registers updated, PC preserved)
REGS A=FF X=00 Y=00 S=01FF P=22 PC=0200 Cycles=12
> .tax
Executed: tax (Registers updated, PC preserved)
REGS A=FF X=FF Y=00 S=01FF P=22 PC=0200 Cycles=14Registers are automatically displayed after each manual execution.
| Command | Description |
|---|---|
step [n] |
Execute n instructions (default 1) |
next |
Step over subroutine calls |
finish |
Run until current subroutine returns |
stepback / sb |
Step backward 1 instruction in history |
stepfwd / sf |
Step forward (re-execute) in history |
run |
Run until top-level RTS, STP, or a breakpoint |
trace [on|off|file <path>] |
Enable/disable execution trace, or redirect to a file |
break <addr> [cond] |
Set a breakpoint with an optional condition (e.g., A == $00 && .Z == 1) |
clear <addr> |
Remove a breakpoint |
list |
List all active breakpoints |
regs |
Show all registers |
mem <addr> [len] |
Hex dump memory |
write <addr> <val> |
Write one byte to memory |
bload "file" [addr] |
Load binary or .prg (C64 format) file |
bsave "file" <start> <end> |
Save memory range to binary or .prg file |
disasm [addr [count]] |
Disassemble memory |
asm [addr] |
Enter inline assembler at addr (blank line exits) |
jump <addr> |
Set the Program Counter |
set <reg> <val> |
Set a register (A X Y Z B S P PC) |
flag <flag> <val> |
Set a status flag (N V B D I Z C) |
processor <type> |
Switch processor variant (6502, 65c02, 65ce02, 45gs02) |
processors |
List all supported processor types |
info <mnemonic> |
Show addressing modes and cycle counts for one opcode |
symbols |
List the loaded symbol table |
validate <addr> [REG=val…] : [REG=val…] |
Call subroutine at addr with given input registers and verify expected output registers |
snapshot |
Record the current memory state as a baseline |
diff |
Show all bytes that changed since the last snapshot |
list_patterns |
List all built-in assembly snippet templates |
get_pattern <name> |
Print the full source for a snippet (e.g. mul8_mega65, memcopy) |
speed [scale] |
Get or set run speed. 1.0 = C64 PAL (~985 kHz). 0 = unlimited |
reset |
Reset CPU |
help |
Show command summary |
quit / exit |
Exit the simulator |
The interactive monitor includes commands for inspecting and capturing the VIC-II video chip state.
| Command | Description |
|---|---|
vic2.info |
Print VIC-II state summary: mode string, D011/D016/D018, bank, screen/charset/bitmap addresses, border and background colours |
vic2.regs |
Full register dump — D011/D016/D018 with every decoded bit, D012 raster line (9-bit), D019 interrupt status (IRQ/RST/MBC/MMC/LP), D01A interrupt enable, bank and all video addresses, all five colour registers (D020 border, D021–D024 BG0–BG3) with colour names |
vic2.sprites |
Print all 8 sprite states: X/Y, colour, MCM/XE/YE/BG flags, data address |
vic2.savescreen [file] |
Render full 384×272 PAL frame (border + display + sprites) to a PPM file (default: vic2screen.ppm) |
vic2.savebitmap [file] |
Render the 320×200 active display area (no border; sprites included and clipped to active area) to a PPM file — mode-aware: adapts to Standard Char, Multicolour Char, ECM, Standard Bitmap, or Multicolour Bitmap (default: vic2bitmap.ppm) |
Breakpoints can be made conditional using standard assembler expression syntax. Supported operators (in order of precedence):
- Parentheses:
( ) - Unary:
~(bitwise NOT) - Shifts:
<<, >> - Bitwise:
&(AND),^(XOR),|(OR) - Comparisons:
==, !=, <, >, <=, >= - Logical:
&&(AND),||(OR)
Example: break $C000 (A & $40) != 0 && .Z == 1
Load with --preset <name>: c64, c128, mega65, x16.
When any program is loaded the simulator looks for side-files sharing the same base name:
| Extension | Contents | Purpose |
|---|---|---|
.sym |
KickAssembler symbol file | Labels and constants |
.list |
ACME-format listing | Source-to-address map |
.sym_add |
Supplemental annotations | Additional symbols, SIM_INSPECT:/SIM_TRAP: markers, and SIM_CPU:/SIM_MACHINE: processor metadata |
.sym_add is useful for .prg/.bin files produced outside the simulator — place it next to the binary and the simulator picks it up automatically on load, applying all processor and machine-type metadata it contains.
TRAPs simulate Kernal/ROM routines without requiring the actual ROM to be loaded. When a JSR to a TRAP address is encountered:
- Register state is logged.
- The routine is skipped (return address popped).
- CPU cycles are updated (standard 6 cycle overhead).
src/mcp/index.js is a Node.js MCP server that exposes the simulator to LLMs, allowing AI assistants to write, debug, and execute 6502 assembly code directly.
| Tool | Description |
|---|---|
load_program |
Assemble and load 6502 source code |
step_instruction |
Execute N instructions |
run_program |
Run until breakpoint / top-level RTS / STP |
trace_run |
Execute N instructions and return a compact per-instruction log (address, disassembly, register state after each step) |
read_registers |
Return current CPU register state |
read_memory |
Read a range of memory bytes |
write_memory |
Write a byte to memory |
reset_cpu |
Reset CPU registers |
set_processor |
Switch processor variant |
list_processors |
List supported processor types |
get_opcode_info |
Fetch addressing modes and cycles for a mnemonic |
set_breakpoint |
Set a breakpoint at an address |
clear_breakpoint |
Remove a breakpoint |
list_breakpoints |
List all active breakpoints |
assemble |
Inline-assemble code into memory |
disassemble |
Disassemble memory at an address |
step_back |
Step back one instruction using execution history |
step_forward |
Step forward in history |
validate_routine |
Run an array of register test-vectors against a subroutine; returns pass/fail per test |
snapshot |
Capture a memory baseline; subsequent execution tracks all writes |
diff_snapshot |
Show every address that changed since the last snapshot, with before/after values and writer PC |
list_patterns |
List all built-in assembly snippet templates grouped by category and processor |
get_pattern |
Return a fully documented, parameterised snippet by name (e.g. mul8_mega65, memcopy) |
speed |
Get or set run-speed throttle (1.0 = C64) |
vic2_info |
Print VIC-II state summary (mode, key addresses, colours) |
vic2_regs |
Full register dump: D011–D01A decoded, raster line, interrupt flags, bank, all video addresses, all colour registers |
vic2_sprites |
Print all 8 sprite states |
vic2_savescreen |
Render full 384×272 PAL frame to PPM |
vic2_savebitmap |
Render 320×200 active display area to PPM (mode-aware, no border; sprites included) |
list_env_templates |
Discover available project environment templates |
create_project |
Bootstrap a new project directory structure from a template |
The engine is split into focused static libraries that are linked into a single libsim6502.a:
src/lib6502-core/: CPU engine and instruction sets.cpu_engine.cpp/h: Dispatch loop and CPU lifecycle.cpu_6502.cpp/h: Register model and addressing-mode helpers.cpu_state.h,cpu_types.h,cpu_observer.h,machine.h,cycles.h,dispatch.h.opcodes/: Per-variant opcode handlers (6502.cpp,65c02.cpp,65ce02.cpp,45gs02.cpp,6502_undoc.cpp).
src/lib6502-mem/: Memory subsystem.memory.cpp/h: 64KB + sparse 28-bit far memory, MAP translation, write logging.io_handler.h,io_registry.h: I/O device registration and dispatch.interrupts.cpp/h: IRQ/NMI handling.
src/lib6502-devices/: Hardware device emulation.audio.cpp/h: SID audio output.device/: VIC-II, SID I/O, CIA, MEGA65 I/O device implementations.
src/lib6502-toolchain/: Source-level tools.metadata.cpp/h:.prg/.bin/.asmloading, KickAssembler auto-assembly, simulator pseudo-op preprocessing (//.inspect,//.trap,//.cpu,//.machine), native.cpu/.processordirective injection,.sym_addcompanion file loading,detect_asm_cpu_type().symbols.cpp/h: Symbol table (labels, constants,SYM_INSPECT,SYM_TRAP,SYM_PROCESSOR).list_parser.cpp/h: Source-map and ACME-list parsing.disassembler.cpp/h: Disassembly for all processor variants.patterns.cpp/h: Built-in assembly snippet library.project_manager.cpp/h: Template-based project scaffolding.
src/lib6502-debug/: Debugging infrastructure.debug_context.cpp/h: Execution history, step-back/forward, snapshot/diff.breakpoints.h,condition.cpp/h: Breakpoint management and expression evaluator.trace.h,debug_types.h: Trace ring buffer and shared type definitions.
src/sim_api.cpp/src/sim_api.h: Public C API consumed by the CLI and GUI.src/cli/: Command-line interface and interactive monitor.main.cpp: CLI entry point.commands/: Individual command classes (Command Pattern).
src/gui/: Dear ImGui-based graphical debugger.src/mcp/: MCP server for LLM integration (index.js).tests/: Regression test suite (KickAssembler.asmfiles; run withmake test).tools/:run_tests.py,test_patterns.py,KickAss65CE02.jar, and helpers.examples/: Sample assembly programs (KickAssembler syntax, all assemble directly).symbols/: Pre-built symbol tables (C64, C128, MEGA65, X16).
- Assembler: No macro support yet. Complex expressions in operands are not supported beyond single values and symbol references.
- Cycle Counts: While provided, counts may not be 100% cycle-accurate for all addressing modes and page-crossing penalties in all variants.
- Decimal Mode: BCD flag behavior matches correct arithmetic output but does not currently emulate NMOS-specific undefined N/V/Z flag quirks.
- CLI processor auto-detection for
.prg/.bin: The CLI creates the CPU before loading, soSIM_CPU:in a.sym_addcompanion file does not take effect at the CLI level — use-p <variant>explicitly. The GUI and MCP paths (viasim_api) apply companion-file processor metadata correctly.
Proprietary — see LICENSE. Will move to open source at a future date.
Last Updated: 2026-03-14