OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / README.md
67.4 KiB · Markdown · last changed feat: the RRDC control port (-controlport), contract 0.6: GET/POST /perf from the Performance panel's measurements, built by perf_json.c with DAP's x16/perfStats; a guest marker region named in advance; a framed guest's regions carried across the frame boundary and an interrupt after $F1 counted as work (#1) 25 days ago renderedsource blame history raw
1
# X16Emu ADD — the Advanced Disassembler and Debugger for the Commander X16
2
3
<img src="./.gh/logo.png" align="right" width="200" alt="X16Emu ADD logo" />
4
5
[![Build Status]https://github.com/xylothan/x16-emulator/actions/workflows/build.yml/badge.svg]https://github.com/xylothan/x16-emulator/actions/workflows/build.yml
6
[![Release]https://img.shields.io/github/v/release/xylothan/x16-emulator]https://github.com/xylothan/x16-emulator/releases
7
[![Dev build]https://img.shields.io/github/release-date-pre/xylothan/x16-emulator?label=dev%20build&color=orange]https://github.com/xylothan/x16-emulator/releases/tag/dev
8
[![License: BSD-Clause]https://img.shields.io/github/license/xylothan/x16-emulator]./LICENSE
9
[![Contributors]https://img.shields.io/github/contributors/xylothan/x16-emulator.svg]https://github.com/xylothan/x16-emulator/graphs/contributors
10
11
12
**X16Emu ADD** — the **A**dvanced **D**isassembler and **D**ebugger — is a fork of the
13
[official Commander X16 emulator][upstream] built for people who write software for the X16
14
rather than just run it. It is the upstream emulator with a real debugger bolted on: a dockable
15
graphical debug UI, source-level debugging from cc65 `.dbg` files, and a Debug Adapter Protocol
16
server so the machine can be driven from VS Code, Visual Studio, or any other DAP-speaking editor.
17
18
Everything the official emulator does, ADD does too.
19
20
> ### ⚠️ This is experimental software
21
>
22
> The emulator core is upstream and is great, but **the debugger, the DAP server and the
23
> Windows-specific changes are experimental** and probably not great. Expect rough edges, and
24
> expect details to change between releases.  
25
>
26
> That said it has been used successfully to carry several real Commodore 64 to X16 porting
27
> projects through to completion, which is exactly the work it was built for. Anyone debugging
28
> assembly on the X16 today will very likely save time with it.  This software is purpose built
29
> to further personal projects and make my life easier.  If it makes your life easier, great!
30
>
31
> **It is published as-is, and is not formally supported at this time.**
32
33
Why X16Emu ADD
34
---------------
35
36
|  |  |
37
| --- | --- |
38
| **Graphical debugger** | A dockable Dear ImGui debug window with disassembly, CPU, memory, source, call stack, symbols, breakpoints, I/O and file access, VERA/PSG/FM/PCM inspectors, and a performance budget view. |
39
| **Source-level debugging** | Point it at a cc65 `.dbg` file and step through the original `.s`/`.c` source, with labels, equates and live values. `.dbg` files auto-load, including for overlays the program `LOAD`s at runtime. |
40
| **Debug from an editor** | A built-in DAP server means breakpoints, stepping, watches, memory and disassembly in VS Code, Visual Studio, or any DAP-speaking client. |
41
| **Conditional breakpoints** | Break on `A == $05`, `byte[$1234] != 0`, a specific RAM bank, or the Nth hit. |
42
| **Watchpoints** | Break on writes to an address or a whole range, with up to 64 of them. |
43
| **Scriptable input** | Inject keystrokes, typed text and joystick state over DAP, making automated regression runs of a game practical. |
44
| **VERA inspectors** | Live palette, tile, sprite, bitmap and tilemap viewers plus fully decoded VERA registers — decoded per scanline, so raster splits are visible. |
45
| **65C816 / GS aware** | 24-bit bank:address memory browsing, mode-correct register widths, and the X16 virtual registers R0–R15 broken out. |
46
| **Real Windows builds** | Self-contained statically linked `x16emu.exe` for x64, x86 and ARM64. No SDL2, zlib or Visual C++ redistributable to install. |
47
| **The window doesn't freeze** | On Windows the emulator keeps running and painting while its window is dragged or resized. |
48
| **Automated releases** | Every push is built for all platforms; tagging stages a draft release with all the assets attached, ready for a human to publish. |
49
50
[![The X16Emu ADD debugger stopped inside a cc65 assembly loop]./.gh/screenshots/debugger-overview.png]./.gh/screenshots/debugger-overview.png
51
52
*Stopped mid-loop in a cc65 assembly program: your own source on the left with the current line
53
highlighted, 65C816-aware registers and the X16 virtual registers `R0`–`R15` on the right, and a
54
live hex editor underneath. Every panel is dockable, and the machine keeps running in its own
55
window while you inspect it.*
56
57
The debugger is documented in full, panel by panel and with a screenshot of each, under
58
[Advanced Debugging]#advanced-debugging below.
59
60
Relationship to the official emulator
61
-------------------------------------
62
63
ADD exists to add debugging tooling, not to diverge from the X16 platform. It has no relation
64
to upstream development. Don't bug the upstream developers about it.
65
66
**Everything about the Commander X16 itself — the KERNAL, BASIC, VERA, the hardware, the file
67
formats — is documented officially, and those docs apply here unchanged:**
68
69
* [Official X16 documentation][x16docs] — the reference for the machine, its KERNAL and BASIC
70
* [x16-rom][x16rom] — the KERNAL/BASIC ROM sources
71
* [Official emulator][upstream] — upstream, whose release notes cover the emulator core each ADD build is based on
72
* [commanderx16.com][website]
73
74
Use the official docs for the machine. Use this README for what ADD adds on top.
75
76
### Version and release numbering
77
78
Releases are named **`R49.nnn`**:
79
80
* `R49` names the official Commander X16 release line the build follows. It is not a promise of
81
  byte-for-byte equivalence with official R49: the emulator core tracks upstream's ongoing
82
  development after that release, so a build may include upstream fixes made since R49 was cut.
83
* `nnn` counts the ADD builds on top of it, and goes up as ADD's own features land.
84
85
When upstream cuts its next release, ADD releases move to `R50.001` and so on. A release is cut by
86
pushing an `R49.nnn` **tag**, which is what the build workflow keys on; if you also want a branch
87
for a release line, give it a distinct name such as `release/R49.001`, since a branch and a tag
88
sharing one name makes `R49.001` ambiguous to git.
89
90
Each release package ships the `rom.bin` it was built and tested against — use that one. Older ROMs
91
may not work with newer emulators, and vice versa.
92
93
Features
94
--------
95
96
Inherited from the official emulator:
97
98
* CPU: 65C02 and 65C816 instruction sets, selected by command line switch
99
* VERA
100
    - Mostly cycle exact emulation
101
    - Supports almost all features:
102
        - composer
103
        - two layers
104
        - sprites
105
        - VSYNC, raster, sprite IRQ
106
* Sound
107
    - PCM
108
    - PSG
109
    - YM2151
110
    - MIDI via FluidSynth
111
* Real-Time-Clock
112
* NVRAM
113
* System Management Controller
114
* SD card: reading and writing (image file)
115
* VIA
116
    - ROM/RAM banking
117
    - keyboard
118
    - mouse
119
    - gamepads
120
121
Added by ADD:
122
123
* Dear ImGui graphical debugger (`-imgui`)
124
* Debug Adapter Protocol server for editor-based debugging (`-debugport`)
125
* cc65 source-level debugging (`-dbgfile`, `-srcpath`)
126
* Conditional breakpoints, hit counts and memory watchpoints
127
* Statically linked MSVC builds for Windows x64/x86/ARM64
128
* Non-blocking window drag and resize on Windows
129
130
Binaries & Compiling
131
--------------------
132
133
Binary releases for macOS, Windows and Linux are available on the [X16Emu ADD releases page][releases].
134
The official builds, which have upstream's keyboard-driven debugger but none of ADD's tooling, are
135
[over here][upstream-releases].
136
137
### Which Windows download?
138
139
Windows ships in three flavours. Unless you have a reason to pick otherwise, take `x16emu_win64`.
140
141
| Package | What it is |
142
| --- | --- |
143
| `x16emu_win64`, `x16emu_win32`, `x16emu_win-arm64` | A single self-contained `x16emu.exe`, statically linked. Nothing to install — no SDL2, zlib or Visual C++ redistributable. Built without FluidSynth, so it offers no MIDI options at all. |
144
| `…-midi` | A separate build with FluidSynth compiled in, shipped with `libfluidsynth-3.dll` for the MIDI synth (`-midicard` / `-sf2`). Take this if you want MIDI. |
145
| `x16emu_win64-mingw`, `x16emu_win32-mingw` | The older MinGW build, kept as a fallback. Ships MIDI support and the `-trace` option, at the cost of around 25 DLLs alongside the executable. |
146
147
All of them include the debugger and the DAP server.
148
149
### Development builds
150
151
Every check-in to `main` refreshes the [`dev` prerelease][dev-release], so there is always a
152
current build to download without waiting for a tagged release. It is not an official release and
153
carries no promises, but it is built and unit-tested by the same workflow that cuts the real ones.
154
155
The tag and the file names never change, so a link keeps working as the build rolls forward:
156
157
```
158
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_win64-dev.zip
159
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_linux-x86_64-dev.zip
160
https://github.com/xylothan/x16-emulator/releases/download/dev/x16emu_macos_m1-dev.zip
161
```
162
163
Swap in any package name from the table above. Which build you actually have is recorded three
164
ways: in the release title, in `BUILD_VERSION.txt` inside the zip, and on the last page of the
165
bundled `README.pdf`. They all read the same identifier, for example:
166
167
```
168
r49-dev-20260810.003
169
```
170
171
That is the upstream release this fork sits on, the UTC date, and how many commits landed that
172
day — so `.003` is the third check-in of the tenth.
173
174
Two differences worth knowing about the default builds:
175
176
- The non-`-midi` packages are built without FluidSynth, so they do not offer the MIDI options
177
  at all — they will not appear in `-h`, and dropping the DLL next to one will not enable them.
178
- The MSVC builds have no `-trace` option. The generated ROM listing contains a string literal
179
  larger than MSVC's 16 KB limit, so a trace-enabled build cannot compile. Use the `-mingw`
180
  package if you need `-trace`.
181
182
The emulator itself is dependent only on SDL2. However, to run the emulated system you will also need a compatible `rom.bin` ROM image. This will be
183
loaded from the directory containing the emulator binary, or you can use the `-rom .../path/to/rom.bin` option.
184
185
> **WARNING:** Older versions of the ROM might not work in newer versions of the emulator, and vice versa.
186
187
You can build a ROM image yourself using the [build instructions][x16rom-build] in the [x16-rom][x16rom] repo. The `rom.bin` included in the [*latest* release][releases] of the emulator may also work with the HEAD of this repo, but this is not guaranteed.
188
189
### Building from source
190
191
The emulator depends on SDL2. The debugger and DAP server add two more requirements:
192
193
* a **C++17** compiler, because Dear ImGui and the debug UI are written in C++ (Dear ImGui itself is
194
  vendored in `src/extern/imgui/`, so there is nothing to install for it), and
195
* **cJSON**, used by the DAP server — `libcjson-dev` on Debian/Ubuntu, `cjson` on Homebrew,
196
  or supplied by vcpkg on Windows.
197
198
Both the ImGui debugger and the DAP server are always compiled in; `-imgui` and `-debugport`
199
are runtime switches, so there is nothing to enable at build time.
200
201
### macOS Build
202
203
Install SDL2 using `brew install sdl2`, and cJSON using `brew install cjson`.
204
205
### Linux Build
206
207
The SDL2 development package is available from the package repositories of most major Linux distributions:
208
- Red Hat: `yum install SDL2-devel cjson-devel`
209
- Debian: `apt-get install libsdl2-dev libcjson-dev`
210
211
Type `make` to build the source. The output will be `x16emu` in the current directory. Remember you will also need a `rom.bin` as described above.
212
213
### WebAssembly Build
214
215
Steps for compiling WebAssembly/HTML5 can be found [here][webassembly].
216
217
### Windows Build
218
219
`build.ps1` at the repo root wraps all of this. It finds the installed Visual Studio, picks the
220
CMake that can actually drive that generator, locates vcpkg, and selects the matching preset from
221
`CMakePresets.json`:
222
223
```powershell
224
.\build.ps1                      # Debug build of the emulator
225
.\build.ps1 -Config Release      # Release build
226
.\build.ps1 -Tests               # build and run the unit test suite
227
.\build.ps1 -Run                 # build, then launch it
228
.\build.ps1 -DapTest -FetchRom   # download a ROM, boot, run the DAP testbench
229
.\build.ps1 -Dbg                 # build tools/x16dbg, the .NET DAP client
230
```
231
232
Useful extras: `-Target <name>` for a single CMake target, `-Rom <path>` to point at a ROM (or set
233
`X16_ROM`), `-Clean` / `-Reconfigure`, `-Force` to stop an emulator that is holding `x16emu.exe`
234
open, and `-EmuArgs '-prg','A.PRG','-run'` to pass arguments through to the emulator.
235
`Get-Help .\build.ps1 -Full` lists them all.
236
237
Two things it exists to save you from: the CMake on `PATH` is often older than the installed
238
Visual Studio and does not know its generator, which fails with a wall of generator names that
239
never mentions the real problem; and a running emulator holds its own `.exe` open, so the link step
240
dies with a bare `LNK1104` that says nothing about why.
241
242
#### Building by hand
243
244
The Windows releases are built with **MSVC and CMake**, using vcpkg for dependencies and custom
245
triplets in `vcpkg-triplets/` that link everything statically. That is what produces the
246
self-contained `x16emu.exe` in the release packages:
247
248
```
249
cmake -B build -G "Visual Studio 17 2022" -A x64 ^
250
  -DCMAKE_TOOLCHAIN_FILE=%VCPKG_INSTALLATION_ROOT%/scripts/buildsystems/vcpkg.cmake ^
251
  -DVCPKG_OVERLAY_TRIPLETS=vcpkg-triplets ^
252
  -DVCPKG_TARGET_TRIPLET=x64-windows-x16emu ^
253
  -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded ^
254
  -DENABLE_TRACE=OFF
255
cmake --build build --config Release
256
```
257
258
Use `-A Win32` with `x86-windows-x16emu`, or `-A ARM64` with `arm64-windows-x16emu`, for the other
259
architectures. Add `-DENABLE_FLUIDSYNTH=ON -DVCPKG_MANIFEST_FEATURES=midi` for a MIDI build.
260
`ENABLE_TRACE` must stay off under MSVC: the generated ROM listing contains a string literal
261
larger than MSVC's 16 KB limit.
262
263
The output is `build\Release\x16emu.exe` and `build\Release\makecart.exe`. Remember you will also
264
need a `rom.bin` as described above.
265
266
Cross-compiling from macOS/Linux/MSYS2 with mingw-w64 also still works. Install the mingw-w64
267
toolchain and the mingw32 version of SDL, then:
268
```
269
CROSS_COMPILE_WINDOWS=1 MINGW32=/usr/x86_64-w64-mingw32 WIN_SDL2=/usr/x86_64-w64-mingw32 make
270
```
271
Paths to those libraries can be changed to your installation directory if they aren't located there.
272
273
The output will be `x16emu.exe` in the current directory. You will also need `SDL2.dll` from SDL2's binary folder.
274
275
276
Starting
277
--------
278
279
You can start `x16emu`/`x16emu.exe` either by double-clicking it, or from the command line. The
280
latter allows you to specify additional arguments. When starting `x16emu` without arguments, it
281
will pick up the system ROM (`rom.bin`) from the executable's directory.
282
283
The command line options of the official emulator work here too — `-prg`, `-bas`, `-run`, `-scale`,
284
`-sdcard`, `-fsroot`, `-warp`, `-gif`, `-wav`, `-ram`, `-c816` and the rest. They are documented in
285
the [official emulator's README][upstream-readme]. Two caveats, both from the packaging rather than
286
from ADD itself: the MSVC builds have no `-trace`, and the non-`-midi` packages have no MIDI
287
options at all. `x16emu -h` is the authoritative list for the build you actually have.
288
289
The options below are the ones ADD adds on top. They all concern debugging.
290
291
| Option | What it does |
292
|---|---|
293
| `-imgui` | Opens the graphical debugger in its own window, with dockable panels for the CPU, memory, disassembly, source, breakpoints, symbols, the call stack, VERA graphics, the three audio sources, I/O ports and file access, and the performance budget. Additive: independent of, and combinable with, `-debug`. |
294
| `-debugport [<port>]` | Starts the Debug Adapter Protocol server so an IDE can attach. Default port 9009. |
295
| `-bp <address>` | Arms a breakpoint before the first instruction runs, for catching start-up code you could never attach to in time. Can be repeated. Needs a debugger to resume from, so it opens `-debug` unless `-imgui` or `-debugport` is already giving you one. See [Catching early boot with `-bp`]#catching-early-boot-with--bp. |
296
| `-dbgfile <path>` | Loads a cc65 `.dbg` file, so addresses map back to source files and line numbers. |
297
| `-dbgauto` / `-no-dbgauto` | Whether to merge the `.dbg` beside each program the machine loads. Follows the debugger unless forced either way. |
298
| `-srcpath <dir>` | Adds a directory to search for the source files a `.dbg` names. Can be repeated. |
299
300
Upstream's `-debug [<address>]` still works. ADD does not extend it; see the
301
[official README][upstream-readme] for what it does.
302
303
All of these are covered properly under [Advanced Debugging]#advanced-debugging.
304
305
Using the emulated machine
306
--------------------------
307
308
Running the X16 itself — the keyboard layouts, BASIC and the screen editor, SD card images, the
309
host filesystem interface, GIF and WAV recording, the emulator I/O registers at `$9FB0`-`$9FBF`,
310
the CRT cartridge format, the `makecart` tool, and the WebEmulator's URL options — is identical
311
to the official emulator and is documented there, with one addition: ADD uses the otherwise unused
312
register **`$9FBC`** for [performance budget markers]docs/perf-budget.md, which a program can
313
write to say exactly where its frame work begins and ends. It reads back a protocol version, so
314
guest code can detect whether the emulator it is running under understands them.
315
316
* [Official emulator README][upstream-readme] — command line options, recording, I/O registers,
317
  SD card images, HostFS, the CRT format and `makecart`
318
* [Official X16 documentation][x16docs] — the machine itself, its KERNAL and BASIC
319
* [WebAssembly notes][webassembly] — building the web target
320
321
The emulator hotkeys (`Ctrl`+`F` for fullscreen, `Ctrl`+`M` for mouse capture, `Ctrl`+`R` to
322
reset, `Ctrl`+`V` to paste, and so on, or their command-key equivalents on macOS) are upstream's
323
and are unchanged. ADD adds a few of its own, listed under
324
[Keyboard shortcuts]#keyboard-shortcuts.
325
326
327
Advanced Debugging
328
------------------
329
330
This is the part of the emulator ADD exists for. There are two ways to debug, and they are
331
additive — either or both can be turned on:
332
333
| You want | Use | Start with |
334
| --- | --- | --- |
335
| A graphical debugger with panels, source view and VERA inspectors | **ImGui debugger** | `-imgui` |
336
| To debug from VS Code / Visual Studio / a script | **DAP server** | `-debugport` |
337
338
Upstream's own small keyboard-driven debugger (`-debug`) is still present and still works, but
339
ADD does not extend it and it is documented [upstream][upstream-readme]. Everything below
340
describes the ImGui debugger and the DAP server.
341
342
A good default for day-to-day assembly work:
343
344
```
345
x16emu -imgui -prg myprog.prg -run
346
```
347
348
A good default when your source is built with cc65:
349
350
```
351
x16emu -imgui -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src
352
```
353
354
> ⚠️ The ImGui debugger and the DAP server are **experimental and probably broken**. They have
355
> helped complete several real C64→X16 porting projects, but treat the debugger like a goat that
356
> eats your homework: if something looks funny, assume it is doing something wrong until you can
357
> prove otherwise. Expect rough edges and occasional changes to keys, layouts and DAP details
358
> between releases.
359
360
#### Using the debugger and an editor at the same time
361
362
Breakpoints live in one place. Whether a breakpoint came from `-bp` on the command line, F9 in
363
the debugger, the ImGui breakpoints panel, or your editor over DAP, there is a single table
364
deciding where the machine stops, and each of those can hold a breakpoint at the same address
365
without disturbing the others. Disconnecting your editor takes away the breakpoints *it* asked
366
for and leaves yours alone.
367
368
The emulator's own UIs share one view of that table: a breakpoint you set with F9 appears in the
369
ImGui panel, and one you disable there still shows in the Disassembly and Source gutters.
370
371
An editor attached over DAP is the exception, because it keeps its own list:
372
373
* A breakpoint you set with F9 will not appear in your editor's gutter.
374
* A breakpoint you delete with F9 goes away in the emulator, but your editor still believes in
375
  it — and because DAP clients re-send a source file's whole breakpoint list whenever you edit
376
  it, your editor will usually put it back.
377
378
This is a limitation of the protocol rather than an oversight: in DAP the client owns its
379
breakpoint list and is expected to be authoritative about it. If you are driving the emulator
380
from an editor, the least surprising approach is to manage breakpoints from the editor and treat
381
F9 as a local, temporary override.
382
383
### The ImGui debugger
384
385
`-imgui` opens a second, resizable OS window titled **"Commander X16 - ImGui Debugger"**,
386
960×720, placed next to the emulator window. It is available in the native Windows, Linux and
387
macOS builds.
388
389
It does not require `-debug`, and it does not replace the emulator window — the machine keeps
390
running in its own window while you inspect it.
391
392
#### Panels
393
394
Every panel is dockable, closable and reopenable from the **View** menu. Each one is shown in
395
[A tour of the panels]#a-tour-of-the-panels below.
396
397
| Panel | What it gives you |
398
| --- | --- |
399
| [**Disassembly**]#disassembly | Live disassembly around the PC. Hovering an operand shows the effective address and the value there. Right-click for *Run to here* and *Toggle breakpoint*. |
400
| [**CPU**]#cpu-virtual-registers-and-the-stack | Registers with 65C816-aware widths, decoded status flags, and the stack with the most recent push on top. A collapsible **Virtual Regs (R0–R15)** section shows the X16 pseudo-registers at `$02`–`$21`. A collapsible **Watch** section holds your own address watches — bank-qualified, up to 16 bytes each, editable, with hex/decimal/binary tooltips. |
401
| [**Memory**]#memory | A hex editor with three tabs: **CPU** (the CPU map), **Banked** (browse any RAM bank at `$A000`–`$BFFF`) and **VRAM** (VERA's full 17-bit address space). Drag to select a range, search by hex bytes or ASCII with Find Next/Prev, jump to an address, and watch changed bytes flash. Right-click to *Add to watch*, *Add range to watch*, *Copy address*, *Break on write* or *Clear selection*. Edits go through the normal write path, so I/O side effects happen and watchpoints fire. |
402
| [**Source**]#source-level-stepping | Your original `.s`/`.c` source in tabs, with the current line highlighted and centred on each stop. For C, stepping moves a whole statement at a time rather than an instruction. Right-click for *Run to here* and *Toggle breakpoint*. **Open…** pre-loads a file so you can set breakpoints before the PC ever gets there. Hovering a label or number resolves it to an address and its live value. |
403
| [**VERA**]#vera-graphics-debugging | Six tabs: **Registers** (all 32 registers `$9F20`–`$9F3F`, fields decoded), **Palette**, **Tiles**, **Sprites**, **Bitmap** and **Tilemap**. Each view decodes using the registers that actually rendered each scanline, so raster splits show up correctly rather than being flattened to the end-of-frame state. |
404
| [**Breakpoints**]#breakpoints | Every breakpoint, with its condition and hit count. Enable, disable or delete individually. |
405
| [**Symbols**]#symbols | A filterable list of every label from your `.dbg`, with live values. Right-click to *Go to*, *Toggle breakpoint* or *Run to here*. |
406
| [**Call Stack**]#call-stack | A heuristic 65xx stack unwind. Frames are named after the nearest enclosing label, so even code without debug info gets a useful name. Click a frame to jump there in both source and disassembly. |
407
| [**PSG**]#psg--veras-16-voice-sound-generator | VERA PSG voices with live register values, plus scope traces. |
408
| [**YM2151**]#ym2151--the-fm-chip | FM channel state and scope traces. |
409
| [**PCM**]#pcm--the-audio-fifo | VERA PCM state and scope traces. |
410
| [**I/O**]#watching-io-and-file-access | What the machine is doing to its ports. An **Activity** log of register accesses and decoded device events, each row naming and explaining the register it touched; **SD Card** command, block and status state; **Files** — every file the machine has opened, by name, on either file path; **Joysticks** with buttons decoded and lit live; **VIA**, **I2C** (with the SMC and RTC behind it) and **Serial**, each annotated with what the bits are wired to. |
411
| [**Performance**]#performance-budgets | How much of a frame your code actually uses. Work / interrupt / waiting split against a 60, 30 or arbitrary fps budget, a per-frame history graph with the budget drawn across it, rolling min/mean/p50/p95/p99/max with budget overruns, and a per-routine breakdown by address zone or guest marker. |
412
413
While the machine is paused, the audio panels keep drawing their scope traces by projecting from
414
the current register state, so you can see what a voice *would* be doing at the moment you stopped.
415
416
#### Toolbar
417
418
![The debugger toolbar: menus, transport buttons, speed control and status readout]./.gh/screenshots/toolbar.png
419
420
Along the top, after the menus: **Continue**, **Pause**, **Step Into**, **Step Over**,
421
**Step Out** (each greyed out when it does not apply and each showing its shortcut in the
422
tooltip), then the speed control, then a status readout.
423
424
The speed control shows the emulated clock as a real number — `1.75MHz`, `800kHz`, or `warp` —
425
rather than a percentage, coloured blue below native speed, **red above it**, and orange in warp
426
mode. `-` and `+` step the speed, `1x` returns to the machine's own clock, and `Warp` removes the
427
limit entirely.
428
429
The status readout shows `RUNNING`/`PAUSED`, an `IRQ` marker (with nesting depth) while the PC is
430
inside an interrupt handler, the 24-bit PC as `KK:PPPP`, and the elapsed cycle and instruction counts.
431
432
#### Menus
433
434
* **View** — show or hide any panel.
435
* **Layout** — *Reset to Default Layout* reopens everything and rebuilds the default docking arrangement.
436
* **System** — *Reset* (`Ctrl+Shift+F5`, keeps your breakpoints), *Trigger IRQ*, *Trigger NMI*, and *Settings…*.
437
* **Help** — the keyboard shortcut table, so you never need this README for it.
438
439
**Settings…** covers interrupt following, break-on-interrupt, whether a break auto-switches you to
440
the Disassembly or Source panel, memory change highlighting and its duration, holding audio while
441
paused, I/O capture and its per-device gating, and the global interface scale. Settings and your
442
window layout are saved to `imgui.ini` and restored next run.
443
444
![The Settings window, with Appearance, Navigation, Execution, Memory, Audio and Safety sections]./.gh/screenshots/settings.png
445
446
#### Keyboard shortcuts
447
448
Active when the debugger window has focus and you are not typing into a search or goto box.
449
450
| Key | Action |
451
| --- | --- |
452
| `F5` | Continue (while paused) |
453
| `Break` / `Pause` | Pause (while running) |
454
| `F11` | Step into |
455
| `Shift`+`F11` | Step out |
456
| `F10` | Step over |
457
| `Ctrl`+`F10` | Run to cursor |
458
| `F9` | Toggle a breakpoint at the PC |
459
| `Ctrl`+`Shift`+`F5` | Reset the machine, keeping breakpoints |
460
| `Ctrl`+`Mouse wheel` | Zoom the font of the panel under the cursor |
461
462
On macOS these are the physical `Control` and `Shift` keys, not `Command`.
463
464
These work in the emulator window and are handy while debugging graphics:
465
466
| Key | Action |
467
| --- | --- |
468
| `Ctrl`+`F3` | Toggle VERA layer 0 |
469
| `Alt`+`F3` | Toggle VERA layer 1 |
470
| `Shift`+`F3` | Toggle sprites |
471
| `Shift`+`F8` | Toggle KERNAL skip |
472
473
### A tour of the panels
474
475
Every screenshot below comes from one session: a cc65 assembly program built with `ca65 -g`, run as
476
477
```
478
x16emu -imgui -prg demo.prg -run -dbgfile demo.dbg -srcpath src
479
```
480
481
#### Source-level stepping
482
483
![Source panel stopped on a highlighted line inside an assembly loop, with the file open in a tab]./.gh/screenshots/source-stepping.png
484
485
The Source panel shows the file the PC is actually in — comments, labels, original formatting and
486
all — with the current line highlighted and centred on every stop. Files open as tabs and the panel
487
switches between them on its own as execution crosses module boundaries, so a program split across
488
a dozen `.s` files needs no bookkeeping from you.
489
490
`F10` walks the highlight down the loop an instruction at a time while `A` and `X` track along in
491
the CPU panel. **Open…** pre-loads a file the PC has not reached yet, so you can arm a breakpoint
492
before it ever gets there, and hovering a label or a literal resolves it to an address and its
493
live value.
494
495
#### Disassembly
496
497
![Disassembly panel showing raw bytes, mnemonics, resolved effective addresses and symbol names]./.gh/screenshots/disassembly.png
498
499
When the PC lands somewhere the debug info does not cover — KERNAL ROM, a trampoline, someone
500
else's binary — the Disassembly panel takes over. Every operand is resolved to its effective
501
address and annotated with the symbol living there (`=08AD point_at_cell`), so ROM code reads
502
almost as well as your own. Right-click any line for *Run to here* or *Toggle breakpoint*.
503
504
Which panel comes to the front on a break is a preference, not a rule: **Settings…** can switch to
505
Disassembly whenever there is no source and switch back when there is.
506
507
#### Call stack
508
509
![Call Stack panel showing inner, middle and outer frames each resolved to a source file and line]./.gh/screenshots/call-stack.png
510
511
The 65C02 has no frame pointer, so this is a heuristic unwind of the hardware stack — but it is a
512
useful one. Each frame is named after the nearest enclosing label and resolved back to a source
513
file and line, and frames that fall outside your debug info are still labelled with where they are
514
(`[KERNAL/ROM]`, `[stub/KERNAL RAM]`) rather than dropped. Click any frame to send both the Source
515
and Disassembly panels to it.
516
517
#### Symbols
518
519
![Symbols panel listing every label with its address and live byte and word values]./.gh/screenshots/symbols-panel.png
520
521
Every label from the `.dbg`, filterable by name, each with its address and the byte and word
522
currently living there. Double-click to go to a symbol; right-click for *Toggle breakpoint* or
523
*Run to here*.
524
525
#### CPU, virtual registers and the stack
526
527
![CPU panel with registers, the X16 virtual registers R0-R15, decoded status flags and interrupt state]./.gh/screenshots/cpu-panel.png
528
529
Registers are shown at the width the CPU is actually using — the panel is 65C816-aware, so `A`,
530
`X` and `Y` widen and narrow with the `M` and `X` flags instead of always reading as eight bits.
531
Status flags are broken out as individual checkboxes, and every field is editable in place.
532
533
Two things here are specific to the X16. **Virtual Regs (R0–R15)** decodes the sixteen 16-bit
534
pseudo-registers the KERNAL and cc65 keep at `$02`–`$21`, so you read `R3` rather than working out
535
which zero-page pair that was. The **Interrupts** block reports whether you are currently inside a
536
handler and how deep, the VERA `IEN`/`ISR` state, and how many cycles remain until the next VSYNC.
537
538
Below that sit the stack, most recent push first, and a **Watch** section for your own
539
bank-qualified address watches — up to 16 bytes each, editable, with hex/decimal/binary tooltips.
540
541
#### Memory
542
543
![Memory panel hex editor showing a highlighted byte with its decimal, hex and binary preview]./.gh/screenshots/memory-panel.png
544
545
Three tabs: **CPU** for the processor's view, **Banked** for any RAM bank at `$A000`–`$BFFF`, and
546
**VRAM** for VERA's full 17-bit space. Jump to an address, search by hex bytes or ASCII, drag to
547
select a range, and watch bytes flash as they change. The selection is decoded underneath as
548
decimal, hex and binary in the type of your choosing.
549
550
Edits go through the emulator's normal write path rather than poking the array behind its back, so
551
I/O side effects happen and watchpoints fire exactly as they would have if the program had done it.
552
553
#### Breakpoints
554
555
Set one by clicking the gutter in the Source or Disassembly panel, pressing `F9` at the PC, or
556
right-clicking the line:
557
558
![Right-click menu on a source line offering Run to here and Toggle breakpoint]./.gh/screenshots/source-context-menu.png
559
560
##### Conditions and hit counts
561
562
A breakpoint that fires on all sixteen passes of a loop is rarely what you want. Every breakpoint
563
carries an optional condition and ignore count, editable from the Breakpoints panel — no DAP client
564
required:
565
566
![The breakpoint condition editor, with a dropdown offering A, X, Y, SP, P, byte and word operands]./.gh/screenshots/breakpoint-condition-operands.png
567
568
The left operand is a register (`A`, `X`, `Y`, `SP`, `P`) or memory (`byte[addr]`, `word[addr]`);
569
the comparison and the value complete it. The value box is **hex** — it is labelled with a `$` —
570
so `$0A` is decimal ten:
571
572
![The condition set to X == $0A, with an ignore-first-N-hits control and a hit counter]./.gh/screenshots/breakpoint-condition.png
573
574
Run, and the loop stops on exactly the pass you asked for — the eleventh, with the emulator having
575
printed `0` through `10` and `X` still reading `$0A` because the `INX` has not happened yet:
576
577
![Emulator screen showing the counter printed 0 to 10 where the conditional breakpoint stopped]./.gh/screenshots/demo-conditional-break-screen.png
578
579
The panel lists every breakpoint with its bank, hit count and condition, and has both a
580
**Memory writes** section for watchpoints and a *Run to* box:
581
582
![Breakpoints panel listing an execution breakpoint with one hit, plus the Memory writes section]./.gh/screenshots/breakpoints-panel.png
583
584
Conditions and hit counts set from a DAP client appear here too, and vice versa; the DAP syntax is
585
documented under [Remote debugging with DAP]#remote-debugging-with-dap.
586
587
##### Catching early boot with `-bp`
588
589
Every other way of setting a breakpoint needs the machine to already be up and you to already be
590
looking at it. `-bp` is the one that does not: it arms the breakpoint *before the first
591
instruction runs*, which is the only way to stop inside something that has already finished by
592
the time you could click a gutter — a `.prg` that runs from its load address, KERNAL or ROM code
593
during start-up, or anything reached from the reset vector.
594
595
The usual pairing is with the graphical debugger, so the machine is already stopped at the
596
interesting place when the window appears:
597
598
```
599
x16emu -imgui -prg myprog.prg -run -bp C000
600
```
601
602
It composes the same way with the other front ends, and can be repeated:
603
604
```
605
x16emu -imgui -bp C000 -bp 05:A000        # several, one pinned to RAM bank 5
606
x16emu -debugport -bp C000                # stop at boot, attach an editor afterwards
607
x16emu -bp C000                           # no front end named, so -debug opens
608
```
609
610
A breakpoint is no use without something able to resume from it, so `-bp` opens the text
611
debugger when you have not asked for another front end. Name `-imgui` or `-debugport` and it
612
leaves your choice alone. `-wp <address>[,<length>]` works the same way for writes.
613
614
The breakpoint belongs to the command line, not to whichever debugger displays it. It shows up
615
in the Breakpoints panel and to a DAP client, and neither can take it away — a client
616
disconnecting leaves it exactly where you put it. Deleting it is a deliberate act: `F9` on the
617
line, or the bin in the Breakpoints panel.
618
619
#### Watchpoints — break on write
620
621
A breakpoint stops when the PC reaches an address. A watchpoint stops when the *program writes to
622
memory*, which is how you find what is corrupting a variable when you have no idea which code is to
623
blame. Right-click any byte in the Memory panel — or drag-select a range first, for up to 64 active
624
watchpoints:
625
626
![Right-click menu on a memory byte offering Add to watch, Copy address and Break on write]./.gh/screenshots/memory-context-menu.png
627
628
Each watchpoint can carry a value filter, so you stop only when a particular value is written
629
rather than on every store:
630
631
![A break-on-write entry at $32A3 with the filter set to stop only when the written value is $42]./.gh/screenshots/watchpoints.png
632
633
Run, and the eight earlier writes (`01 02 04 08 10 20 40 80`) pass straight through:
634
635
![Emulator screen showing the eight earlier writes printed, none of which stopped execution]./.gh/screenshots/demo-watchpoint-screen.png
636
637
Execution stops on the instruction after the store that actually mattered:
638
639
![Source panel stopped on the line immediately after the store of $42 that triggered the watchpoint]./.gh/screenshots/watchpoint-hit.png
640
641
> **A note on names.** The debugger deliberately avoids the word *watchpoint* in the UI, because
642
> the CPU panel's **Watch** list already means something else — addresses you are keeping an eye
643
> on, which never stop execution. In the UI it is *Break on write* in the Memory panel's
644
> right-click menu and **Memory writes** in the Breakpoints panel.
645
646
#### VERA graphics debugging
647
648
The VERA panel has six tabs. All of them read the chip directly rather than the finished frame, so
649
they are live and correct even while the machine is stopped and the emulator window is showing a
650
stale image — and crucially, each view decodes using the registers that actually rendered each
651
scanline, so **raster splits show up as raster splits** instead of being flattened to whatever the
652
registers happened to hold at end of frame.
653
654
##### Registers
655
656
![VERA Registers tab listing all 32 registers with a decoded summary of video mode and both layers]./.gh/screenshots/vera-registers.png
657
658
All 32 registers at `$9F20`–`$9F3F` in hex and binary, plus a **Decoded** block that spells out
659
what they add up to: the data port address and increment, output mode, which layers and sprites are
660
on, scaling, border colour, and each layer's depth, map size, map base, tile base and tile size.
661
662
##### Palette
663
664
![VERA Palette tab showing all 256 entries as colour swatches]./.gh/screenshots/vera-palette.png
665
666
All 256 entries from `$1FA00`–`$1FBFF` as swatches. Hover one for its index and 12-bit RGB value.
667
668
##### Tiles
669
670
![VERA Tiles tab decoding eight 8x8 4bpp tiles at 8x zoom]./.gh/screenshots/vera-tiles.png
671
672
Decodes VRAM as tiles at whatever base address, depth, size, palette offset and zoom you point it
673
at, so you can confirm your art landed correctly before anything is drawn with it. Hover a tile for
674
its address; click to select.
675
676
##### Tilemap
677
678
![VERA Tilemap tab rendering the 80x60 visible area of a 128x64 layer 0 tile map]./.gh/screenshots/vera-tilemap.png
679
680
Renders a layer's map through that layer's own registers — the same tile base, map base, map size
681
and depth the hardware is using. **Follow raster** decodes each row with the registers that were in
682
force when that row was drawn, which is what makes mid-screen register changes visible.
683
684
Here is the same map on the emulated display a moment later, once layer 0 was switched on and the
685
text layer's opaque background was cleared:
686
687
![The emulator screen showing the tiled layer 0 running behind the text layer]./.gh/screenshots/demo-tilemap-screen.png
688
689
##### Sprites
690
691
![VERA Sprites tab showing eight enabled sprites with image previews and decoded attributes]./.gh/screenshots/vera-sprites.png
692
693
All 128 sprite records at `$1FC00`, fully decoded: a rendered preview of each sprite's image, its
694
data address, colour depth, size, X/Y position, Z-depth, palette offset and flip bits. **Hide
695
disabled** collapses the list to just the sprites that are actually live.
696
697
##### Bitmap
698
699
![VERA Bitmap tab decoding a 320x240 4bpp bitmap straight out of VRAM]./.gh/screenshots/vera-bitmap.png
700
701
Decodes a bitmap-mode layer straight out of VRAM at the width, height and zoom you choose. Because
702
it reads VRAM rather than the display, the image is visible while it is still being written and
703
before the layer has been switched on at all.
704
705
#### Audio debugging
706
707
All three sound sources get a panel, and each shows raw register state, a decode of what that state
708
*means*, and scope traces. While the machine is paused the panels keep drawing by projecting from
709
the current register state, so you can see what a voice *would* be doing at the moment you stopped
710
— the **Hold audio panels while paused** setting controls this.
711
712
##### PSG — VERA's 16-voice sound generator
713
714
![PSG panel showing per-voice waveform, frequency, decoded pitch in Hz, nearest note, pulse width, volume, pan and level]./.gh/screenshots/psg-panel.png
715
716
Every one of the 16 voices, with the raw frequency word decoded to Hz *and* to the nearest note
717
with its cents error, the waveform, pulse width as a duty percentage, the volume both raw and after
718
the hardware's lookup table, pan, and a live level meter. Selecting a voice expands the four
719
registers behind it byte by byte. There are also **Registers** and **Scope** tabs, the latter with
720
a trace per voice plus the summed output.
721
722
##### YM2151 — the FM chip
723
724
![YM2151 panel showing all eight FM channels with key code, decoded pitch, algorithm, feedback, pan and key-on state]./.gh/screenshots/fm-panel.png
725
726
All eight channels with their key code decoded to a note and a frequency, algorithm, feedback,
727
pan, PMS/AMS, per-operator key-on state, envelope phase and output level.
728
729
The **Algorithm** tab is the one worth knowing about: it draws the operator routing for the
730
selected channel from the registers that were actually written, and box brightness follows live
731
envelope attenuation, so you can watch a patch sound rather than reading its bytes.
732
733
![The FM Algorithm tab drawing the four-operator routing diagram for algorithm 7]./.gh/screenshots/fm-algorithm.png
734
735
##### PCM — the audio FIFO
736
737
![PCM panel decoding audio control, playback rate, FIFO fill level, read/write indices and AFLOW IRQ state]./.gh/screenshots/pcm-panel.png
738
739
The audio FIFO is the one sound source where the CPU has to keep up, so this panel is mostly about
740
whether it is: FIFO fill level as a bar and a byte count, how many frames that is in milliseconds,
741
read and write indices, and whether the `AFLOW` interrupt is currently asserted. Format, volume and
742
playback rate are decoded from `AUDIO_CTRL` and `AUDIO_RATE`, and the panel reads the PCM block
743
directly rather than through `$9F3B`, so inspecting it does not itself consume audio.
744
745
#### Watching I/O and file access
746
747
The **I/O** panel covers the ports: everything in `$9F00`–`$9FFF`, plus the devices that hang off
748
them. Its **Activity** tab is a rolling log rather than a state dump, because I/O is a
749
conversation — a command goes out, a status comes back — and a snapshot taken between commands
750
tells you nothing. Each row names the register it touched (`DATA0`, `T1C_L`, `SPI_CTRL`) and
751
explains it on hover, so you are not looking up addresses in a manual while you debug.
752
753
**Capture is off by default, and so is every device.** It is the one debugger feature the running
754
machine pays for — a test and a branch on every I/O access — and the I/O page is the busiest
755
address range in the system. Turn it on, then tick only what you are actually looking for. Each
756
device's checkbox says what it captures, what it tells you that its own tab does not, and roughly
757
how fast it produces events: VERA's data ports alone are thousands of accesses per frame, and I2C
758
and the joysticks are polled around sixty times a second whether or not anything happens. The SD
759
card's data path at `$9F3E`/`$9F3F` is captured separately as **SPI** so it survives VERA being
760
off.
761
762
Every other tab works with capture off — they read live device state, not the log.
763
764
##### Two ways to reach a file, and only one of them has names
765
766
This is the thing worth knowing before you go looking for a filename and cannot find it.
767
768
| | When | What you see |
769
| --- | --- | --- |
770
| **Host filesystem** | the default, no SD image attached | Real filenames. The emulator intercepts the KERNAL's IEEE calls, so it knows the name, the channel, the direction and the byte counts. |
771
| **SD card image** | `-sdcard <image>` | **Blocks only.** The ROM's own FAT driver runs inside the emulated machine and talks to the card over SPI. The emulator sees `CMD17`, an LBA and 512 bytes — never a name. |
772
773
They are mutually exclusive by default: `-sdcard` turns host-filesystem access off unless you also
774
pass `-hostfsdev`. `-fsroot` sets the host directory the machine sees — it does **not** attach a
775
card, so with `-fsroot` alone the SD tabs are legitimately empty and say so.
776
777
The **Files** tab shows both, in separate sub-tabs, so it is always clear which one is in play.
778
779
Its host-filesystem half keeps an always-on **history** of opens, closes, directory listings, DOS
780
commands and the status each produced. That history is the point: a file operation erases itself
781
when it completes, so a list of *currently open* channels is empty almost all the time, and a
782
directory listing or a failed open would otherwise flash past unseen. The history is kept whether
783
or not capture is on, because file operations are rare enough to record for free.
784
785
For the SD image, ADD closes the gap by parsing the filesystem *itself*: **Build index** reads the
786
image's partition table, BPB, FATs and directory tree — including long filenames — and maps every
787
cluster back to a path. Block traffic then resolves to `/GAMES/FOO.PRG + 2048`, and the file list
788
shows how many bytes of each file the machine has actually read or written this session, listing
789
the files it touched first so they stand out from the ones that merely exist.
790
791
The index is a snapshot, not a live view. If the machine writes to a FAT or a directory the index
792
is marked **STALE** and you rebuild it by hand. That is deliberate: quietly serving a filename
793
that has since become wrong would be worse than admitting the index has aged.
794
795
#### Performance budgets
796
797
The 6502 never idles. It burns every cycle of a frame whether your code is working or spinning on
798
a vsync flag, so "cycles per frame" is always 100% and tells you nothing. The **Performance**
799
panel answers the question you actually have — *how much of the frame is my code using, and how
800
much room is left?* — by telling **work** apart from **waiting**.
801
802
Pick a target frame rate and it shows the split against that budget: a stacked bar of work,
803
interrupt time and idle drawn to the width of the budget, so overrunning visibly runs off the end
804
rather than quietly rescaling. Headroom is reported in cycles *and* scanlines, which is the unit
805
most X16 code is written in. On an 8 MHz machine a VGA frame is exactly 134,400 cycles, or 256 per
806
scanline.
807
808
`Target FPS` asks how many vsyncs your frame work may span. The frame period is fixed by VERA, so
809
asking for 30 fps does not make frames longer — it means the work may take two of them and the
810
budget doubles. A target faster than the machine scans is the opposite question ("must this fit in
811
half a frame?") and gets a fractional budget.
812
813
Below that: a per-frame history graph with the budget drawn across it and overruns in red; rolling
814
statistics over 1s / 10s / 30s / 60s / session with min, mean, p50, p95, p99, max and budget
815
overruns; and a **breakdown** attributing cycles to named routines. Zones can be drawn by address
816
— or picked straight off a symbol from your `.dbg`, since nobody knows the extent of
817
`sprite_update` by heart — and a zone can be flagged *idle* to declare "cycles here are waiting".
818
819
Waiting is found automatically: `WAI`, plus a store-free tight loop, which covers polling `$9F27`
820
and polling a flag an IRQ handler sets. It cannot see a wait loop that stores or calls a
821
subroutine — mark those as an idle zone, or bracket the real work with `$9FBC` markers from the
822
guest for exact attribution. [docs/perf-budget.md]docs/perf-budget.md documents the model and
823
its limits in full.
824
825
Everything here is also available over DAP — see
826
[Profiling the guest]#profiling-the-guest-how-much-of-a-frame-is-your-code-using — so CI can
827
watch for regressions without a human reading a panel.
828
829
The panel is open and docked with the other bottom-row views by default, so a normal `-imgui`
830
session profiles from boot — the numbers are only useful if they are already there when you go
831
looking. That costs the running machine an add and a table lookup per instruction; close the tab
832
and it stops.
833
834
### Source-level debugging with cc65
835
836
Build with debug info, and the emulator will show you your own source instead of raw disassembly.
837
838
Build, in assembly:
839
840
```
841
ca65 --debug-info -o myprog.o myprog.s
842
ld65 -C myprog.cfg --dbgfile myprog.dbg -o myprog.prg myprog.o
843
```
844
845
Build, in C:
846
847
```
848
cl65 -t cx16 -g -Wl --dbgfile,myprog.dbg -o myprog.prg myprog.c
849
```
850
851
Run:
852
853
```
854
x16emu -imgui -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src
855
```
856
857
There are two ways a `.dbg` gets loaded:
858
859
* `-dbgfile <file.dbg>` loads one explicitly at start-up.
860
* Whenever the machine loads a program from the host filesystem — `-prg`, a BASIC `LOAD`, or a DOS
861
  shortcut like `/PROG.PRG` — the `.dbg` sitting beside it is merged automatically. The rule is
862
  simply to swap the extension: `myprog.prg` → `myprog.dbg`. Debug info describing the address
863
  range being replaced is dropped first, so an overlay loading over another module takes over its
864
  addresses, and swapping the first one back restores it.
865
866
Runtime loading follows the debugger: it happens whenever the debugger is enabled. `-no-dbgauto`
867
turns it off, and `-dbgauto` forces it on without the debugger. It is worth turning off for
868
software you do not trust, because the file being read is chosen by the emulated program, and a
869
`.dbg` can name source paths anywhere on the host.
870
871
Two limits are worth knowing. Loads that do not use the KERNAL's block-transfer path are not
872
noticed — `VERIFY`, loading into VRAM, and loading into `$9D00`–`$9FFF` all fall back to
873
byte-at-a-time reads. And editing a `.dbg` while the emulator is running has no effect, because
874
each file is merged once; restart the emulator after a rebuild.
875
876
Because a `.dbg` only records source *file names*, the emulator locates the actual `.s`/`.c` files
877
by searching, in order: the path stored in the `.dbg`, the directory of the `.dbg`, each
878
`-srcpath <dir>` you passed (most recent first), the directory of the loaded program, and finally
879
the current directory.
880
881
**C programs show the C.** cc65 compiles `myprog.c` to `myprog.s` and assembles that, and
882
with `-g` the `.dbg` describes the same bytes twice: once against your C statements and once
883
against the generated assembly, instruction by instruction. The debugger prefers the C, so the
884
Source panel follows your `.c` file and breakpoints set on a `.c` line land at the start of that
885
statement. The generated `.s` is left out of the source panel's **Open…** list, since `cl65`
886
deletes it on the way out; a `.s` you wrote yourself is still offered, and so is one you kept.
887
The disassembly is unaffected — it still aligns on the real instruction boundaries.
888
889
Stepping runs a whole C statement per press. F10 steps over a call — `printf(...)` and
890
everything it does is one press — and F11 steps into one, landing on the first line of the
891
function. Both fall back to stepping one instruction at a time when the debug info has no C in
892
it, so assembly projects step exactly as they always did; the Source panel has a **Step by
893
line** toggle if you want the instruction-at-a-time behaviour for C too. The Disassembly panel
894
is unaffected either way.
895
896
What you do *not* get for C yet is data: locals, parameters and watch expressions still have to
897
be read as raw memory, because nothing here decodes cc65's software stack or its type records.
898
Globals work, as they are ordinary labels.
899
900
**Banked code is partly handled.** A `.dbg` does not record which RAM bank a `$A000`–`$BFFF`
901
segment belongs to. When the running machine loads a program, the emulator notes which RAM bank was
902
mapped as the first byte landed, and associates that bank with the segment — but only when the
903
load's start address and size identify exactly one segment. A file containing several segments, or
904
one whose size does not match a segment, leaves those segments bank-unknown. Nothing consumes that
905
association yet; it is recorded for the debugger front-ends still to come.
906
907
### Remote debugging with DAP
908
909
`-debugport [<port>]` starts a [Debug Adapter Protocol]https://microsoft.github.io/debug-adapter-protocol/
910
server over TCP, on port **9009** by default, so an IDE such as VS Code or Visual Studio can
911
attach for source-level debugging. It speaks standard `Content-Length`-framed JSON, so any
912
compliant DAP client works.
913
914
```
915
x16emu -debugport -prg myprog.prg -run -dbgfile myprog.dbg -srcpath ./src
916
x16emu -debugport 4711 ...           # pick your own port
917
x16emu -debugport -imgui ...         # attach an IDE and watch the panels at the same time
918
```
919
920
The emulator runs immediately on launch; it does not auto-pause waiting for a client.
921
922
A minimal VS Code attach configuration looks like this — note that the `type` value depends on
923
which DAP extension you wire it up to, as no editor extension is bundled with the emulator:
924
925
```json
926
{
927
  "version": "0.2.0",
928
  "configurations": [
929
    {
930
      "name": "Attach to X16 Emulator",
931
      "request": "attach",
932
      "type": "x16",
933
      "host": "localhost",
934
      "port": 9009
935
    }
936
  ]
937
}
938
```
939
940
#### What the server supports
941
942
Standard requests: `initialize`, `launch`, `attach`, `configurationDone`, `threads`, `stackTrace`,
943
`scopes`, `variables`, `setVariable`, `setBreakpoints`, `setFunctionBreakpoints`,
944
`setInstructionBreakpoints`, `setDataBreakpoints`, `setExceptionBreakpoints`, `breakpointLocations`,
945
`continue`, `next`, `stepIn`, `stepOut`, `pause`, `goto`, `gotoTargets`, `stepInTargets`,
946
`evaluate`, `readMemory`, `writeMemory`, `disassemble`, `loadedSources`, `source`, `restart`,
947
`disconnect` and `terminate`.
948
949
Custom requests: `x16/registers`, `x16/sendKey`, `x16/type`, `x16/joystick`, `x16/perfStats` and
950
`x16/perfConfig`. The server also emits a custom `x16/perfBudgetOverrun` event. Clients can detect
951
the profiling surface from the `supportsX16PerfStats` capability in the `initialize` response.
952
953
Four scopes are exposed on every stack frame: **Registers** (including the 65C816 extras and the
954
current RAM/ROM bank), **Virtual Registers** (R0–R15), **Zero Page** and **Stack**. Memory reads
955
and writes use 24-bit addresses, so the high byte selects the 65C816 program bank on a GS machine.
956
957
##### Reading and writing VRAM
958
959
VERA's video RAM is not in the CPU map — the CPU reaches it only through the data port, one
960
auto-incrementing byte at a time — so `readMemory` and `writeMemory` take a `vram:` prefix on
961
`memoryReference` to address VERA's 17-bit space directly:
962
963
```
964
readMemory  { "memoryReference": "vram:1B000", "count": 32 }
965
writeMemory { "memoryReference": "vram:1B000", "data": "3q2+7w==" }
966
```
967
968
VRAM addresses default to hex, with `$` and `0x` accepted too; a bare reference is still a CPU
969
address. The echoed `address` keeps the prefix, so paging through VRAM by feeding it back with an
970
`offset` stays in the right space. This is the only way to write VRAM over DAP, and unlike the
971
`vram` console expression it is not capped at 128 bytes and returns binary rather than text.
972
973
A reference that cannot be parsed is now rejected. It used to fall back to address 0, so a typo
974
answered `success: true` with the bytes from the wrong memory and nothing to say so.
975
976
#### Instruction breakpoints and banks
977
978
`setInstructionBreakpoints` takes an `instructionReference` in either form:
979
980
```
981
setInstructionBreakpoints { "breakpoints": [ { "instructionReference": "33:A555" } ] }
982
setInstructionBreakpoints { "breakpoints": [ { "instructionReference": "0x33A555" } ] }
983
```
984
985
`bb:aaaa` names a RAM/ROM window bank explicitly and means exactly what `-bp bb:aaaa` means, hex on
986
both sides. In the packed 24-bit form the high byte is read as the machine could have produced it:
987
the 65C816 program bank on a GS, and the window bank otherwise, since `read6502` forces the program
988
bank to zero elsewhere. A bare 16-bit address names no bank and breaks in whichever one is mapped.
989
990
Naming a bank for an address the window registers do not reach — anything below `$A000`, or a
991
non-zero GS program bank — is refused with `verified: false` and a `message`, as is giving the bank
992
twice. Previously a banked reference was recorded against the *program* bank, which on a non-GS
993
machine is always zero, so the breakpoint matched nothing while the client had been told
994
`verified: true`. An `offset` displaces the address within its bank and no longer carries into it.
995
996
#### Conditional breakpoints
997
998
Put a condition string on a breakpoint in `setBreakpoints`. Terms are joined with `&&`.
999
1000
* **Pin to a bank:** `bank == 2`, `bank 2`, `rombank == 3`. Decimal or `$XX`/`0xXX`.
1001
* **Test a value:** operands `A`, `X`, `Y`, `SP`, `P`, `byte[$ADDR]`, `word[$ADDR]`, or a bare
1002
  `$ADDR` (which means `byte[$ADDR]`). Operators `==`, `!=`, `<`, `<=`, `>`, `>=`.
1003
1004
```
1005
A == $05
1006
byte[$1234] != 0
1007
word[$0002] >= $0100
1008
bank == 0 && byte[$1234] != 0
1009
```
1010
1011
A `hitCondition` of `"5"` stops on the fifth hit.
1012
1013
#### Expressions you can evaluate
1014
1015
These work in a watch or REPL window, in addition to plain registers and addresses:
1016
1017
| Expression | Result |
1018
| --- | --- |
1019
| `$C000`, `$01C000`, `0xC000` | The byte at that CPU address (24-bit; high byte is the 65C816 bank) |
1020
| `A`, `X`, `Y`, `SP`, `PC`, `P` | Register values, sized for the current CPU mode |
1021
| `regs_all` | A one-line summary of every register |
1022
| `vram ADDR [COUNT]` | VERA VRAM bytes, from a 17-bit hex address (capped at 128; for bulk or binary access use `readMemory` with a `vram:` reference) |
1023
| `vera_reg` | All 32 VERA registers |
1024
| `vera_line LINE` | The layer registers that actually rendered that display scanline — the way to catch a raster split |
1025
| `bp_add ADDR`, `bp_remove ADDR`, `bp_list`, `bp_clear` | Manage breakpoints |
1026
| `watch_add ADDR [LEN]`, `watch_remove ADDR`, `watch_list`, `watch_clear` | Manage write watchpoints |
1027
| `reset` | Reset the machine |
1028
1029
cc65 labels and equates from a loaded `.dbg` resolve too.
1030
1031
#### Scripting input for automated tests
1032
1033
Three custom requests let a script drive the machine, which is what makes automated regression
1034
runs of a game practical:
1035
1036
| Command | Arguments | Effect |
1037
| --- | --- | --- |
1038
| `x16/sendKey` | `key` (SDL key name like `"Return"`/`"A"`/`"Left"`, or a numeric scancode), `action` (`"press"` (default), `"down"`, `"up"`), optional `modifiers` (array of key names held around the key) | Injects a key event through the same path as a physical key press. |
1039
| `x16/type` | `text` (string) | Types a string into the KERNAL keyboard buffer, like a clipboard paste. Ideal for BASIC lines and filenames; supports the `\Xnn` hex escape. |
1040
| `x16/joystick` | `index` (port 0–3), `buttons` (array of `up`, `down`, `left`, `right`, `a`, `b`, `x`, `y`, `start`, `select`, `l`, `r`), optional `mask` (raw 16-bit active-low), `enabled` (`false` releases the port back to a physical controller; `true` on its own connects one with nothing held) | Holds or releases a virtual SNES controller. Buttons not listed are released, so one request sets the whole held state. |
1041
1042
`x16/joystick` needs no gamepad and no `-joy1`..`-joy4` flag: driving a port enables it, so a
1043
headless CI machine can play a game that reads the controller. Sending neither `buttons` nor
1044
`mask` reports the port's state without changing it, which is how a test asserts what it just
1045
pressed. Every request answers with the resulting `mask`, the held `buttons`, and whether a
1046
physical controller is bound.
1047
1048
While a port is held its shift register behaves as an attached controller's does, so the KERNAL
1049
reports a joystick present — `JOY(1)` reads `$00` with nothing held rather than `$FF`. Releasing
1050
it with `enabled: false` makes the port read empty again.
1051
1052
There is also `x16/registers`, which returns the full CPU, KERNAL and VERA state as JSON in one call.
1053
1054
#### Profiling the guest: how much of a frame is your code using?
1055
1056
The 6502 never idles — it burns every cycle of a frame whether your code is working or spinning
1057
on a vsync flag — so "cycles per frame" is always 100% and tells you nothing. The emulator
1058
therefore classifies cycles as **work** or **waiting**, and reports the work against a frame
1059
budget. It measures VERA's VRAM bandwidth alongside, because a program can have cycles to spare
1060
and still run out of bus. See [docs/perf-budget.md]docs/perf-budget.md for how the
1061
classification works, what the bandwidth model is derived from, and where each can be fooled.
1062
1063
| Command | Arguments | Effect |
1064
| --- | --- | --- |
1065
| `x16/perfStats` | optional `windows` (array of seconds, or names like `"30s"`/`"session"`), `includeZones` (default true), `includeFrames` (raw per-frame samples, oldest first) | Returns the budget, the last completed frame, rolling statistics — min/mean/p50/p95/p99/max, budget overruns and the worst one — per window, and a `bandwidth` object with VERA's VRAM traffic. Asking arms both if they were off. |
1066
| `x16/perfConfig` | `enabled`, `targetFps`, `idleMode` (`"auto"`/`"markers"`/`"none"`), `historyFrames`, `overrunEvents`, `zones` (array of `{name, start, end, bank, idle}`), `reset` | Configures the above. `zones` replaces the address zones wholesale. |
1067
1068
The **`bandwidth`** object answers the other half of "what is eating my frame?": what VERA's VRAM
1069
bus did, rather than what the CPU did. It reports per-layer fetches and bytes, the CPU's own
1070
traffic through `$9F23`/`$9F24` — the one piece of VERA bandwidth a program controls directly —
1071
and VERA FX amplification, where one `sta` can move four bytes.
1072
1073
Its headline is `peakLineClocks` against `lineClocks` (800), **not** a percentage of VERA's total
1074
bandwidth: a frame uses maybe a fifth of the chip's 100 MB/s, so percent-of-peak would be true and
1075
useless. One scanline is the window that genuinely runs out. Sprites are last on that bus, so
1076
`spriteBusHeadroomClocksAtPeak` is what the sprite renderer was really competing for.
1077
1078
Every clock figure in `bandwidth` is **bus occupancy**, and `clockUnits` says so. That is a
1079
different clock from the one the sprite views report: sprite render time is wall clock and runs
1080
about five times higher, because it ticks while the renderer paints with the bus idle. Convert
1081
before comparing the two — sprite render time has its own view in the VERA panel under
1082
**Multiplex → Render time**.
1083
1084
The emulator also pushes an **`x16/perfBudgetOverrun` event** when the guest misses its budget,
1085
coalesced to at most one per second with a count and the worst frame in that period — so tooling
1086
can spot a regression without polling.
1087
1088
`targetFps` asks *how many vsyncs your frame work may span*. The frame period is fixed by VERA, so
1089
asking for 30 fps does not make frames longer: it means the work may take two of them, and the
1090
budget doubles. A target faster than the machine scans is the opposite question — "must this
1091
routine fit in half a frame?" — and gets a fractional budget. On an 8 MHz machine a VGA frame is
1092
exactly 134,400 cycles, or 256 per scanline.
1093
1094
Profiling costs the running machine an add and a table lookup per instruction. The Performance
1095
panel is open by default, so `-imgui` profiles from boot and closing the tab stops it; without the
1096
panel, nothing is collected until a DAP client asks.
1097
1098
#### The control port: the same numbers to a test harness
1099
1100
`-controlport <port>` serves the [Retro Remote Debug Controller]https://oxygenhub.io/doomsdayone/retro-remote-debug-controller
1101
HTTP contract on `127.0.0.1:<port>`: the surface a pytest harness drives to read memory, step
1102
frames deterministically, take screenshots and inject keys, pads and the pointer. It is the plain
1103
fork's control port with one more level, **contract 0.6**, which adds the profiler:
1104
1105
| Request | Effect |
1106
| --- | --- |
1107
| `GET /perf?windows=1,30,session&frames=n&since=f` | The budget, the last completed frame (total, work, idle, IRQ, main), the statistics windows, the zones with their own windows, raw per-frame records (`since=` the last frame you saw, so none is missed), and `platform`: VERA's `bandwidth` (with `lines=1`, each scanline's clocks) and the sprite renderer's time per line (`sprites`). |
1108
| `POST /perf` | A JSON body: `enabled`, `targetFps`, `idleMode`, `historyFrames`, `reset`, `zones` (address ranges, or `{"name", "marker"}` to name a guest's `$9FBC` region), and `sprites` (sprite render timing, off until asked). |
1109
1110
The fields are `x16/perfStats`'s, from the same builders, so a number means the same thing
1111
whichever way a client asked. `/perf` needs cJSON, as DAP does; without it the port serves 0.5.
1112
1113
```
1114
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy ./x16emu -prg GAME.PRG -run -controlport 8391 &
1115
curl -s -XPOST localhost:8391/perf -d '{"enabled":true,"zones":[{"name":"logic","marker":1}]}'
1116
curl -s 'localhost:8391/perf?windows=1,session&frames=60'
1117
```
1118
1119
Run it with a display driver, dummy or real, not `-headless`: the port's `/step` counts video
1120
frames, which a headless run never completes.
1121
1122
#### x16dbg, the bundled command-line client
1123
1124
`tools/x16dbg` is a small .NET 8 console DAP client, useful for poking at a running emulator
1125
without an IDE and for scripting.
1126
1127
```
1128
dotnet run --project tools/x16dbg                       # interactive
1129
dotnet run --project tools/x16dbg -- mem 1030-1040      # one-shot
1130
dotnet run --project tools/x16dbg -- -host 127.0.0.1 -port 9009
1131
```
1132
1133
Options are `-host <ip>` (default `127.0.0.1`), `-port <port>` (default `9009`), `-i` to force
1134
interactive mode, and `-h` for help. With no command it drops into an interactive prompt.
1135
1136
Commands, in both modes: `step`/`s`, `continue`/`c`, `break`/`b`, `status`/`t`, `reset`,
1137
`regs`/`r`, `setreg <reg> <hexval>`, `mem <addr>-<end>` or `mem <addr> <len>`,
1138
`setmem <addr> <hexdata>`, `vram`/`v` and `setvram` for VERA's video RAM, `joy`/`j` to drive a
1139
controller port, and `bp add|remove|list|clear <addr>`. Addresses are hex.
1140
1141
```
1142
> b
1143
! STOPPED (pause)
1144
  PC=$C1C6  A=$02 (  2)  X=$FF (255)  Y=$0A ( 10)
1145
  SP=$F0  P=$24 [--B-DI--]  RAM Bank=$00  ROM Bank=$00
1146
> bp add 080D
1147
> mem C000-C00F
1148
  C000: A9 00 85 00 A9 01 85 01 A9 00 8D 00 02 A9 00 8D  |................|
1149
> c
1150
```
1151
1152
`vram` reads VERA's 17-bit video RAM, which is not in the CPU map — so `mem 1B000` and
1153
`vram 1B000` are two different memories at the same number — and `setvram` is the only way to
1154
write it. Both echo the bytes back after a write:
1155
1156
```
1157
> vram 1B000 10
1158
  1B000: DF 64 20 64 20 64 20 64 20 64 20 64 E9 64 20 61  |.d d d d d d.d a|
1159
> setvram 1F9C0 CAFEBABE
1160
Wrote 4 bytes to vram:1F9C0
1161
  1F9C0: CA FE BA BE                                      |....            |
1162
```
1163
1164
`joy <port 1-4>` drives a virtual SNES controller with no gamepad attached and no `-joy1`..`-joy4`
1165
flag. The button list is the complete held state, so anything left off it is released:
1166
1167
```
1168
> joy 1 right a
1169
  Joystick 1: right a
1170
    mask=$FF7E  source=virtual
1171
> joy 1 start          (right and a let go)
1172
> joy 1                (query, changes nothing)
1173
> joy 1 none           (connected, nothing held — JOY(1) reads $00)
1174
> joy 1 release        (port empty again — JOY(1) reads $FF)
1175
```
1176
1177
Buttons are `up down left right a b x y start select l r`.
1178
1179
One caveat about one-shot mode: each command is its own debug session, and the emulator resumes
1180
when a session that paused it goes away — deliberately, so a headless machine cannot be left
1181
halted with nobody able to restart it. Commands that only inspect are fine, and memory, VRAM and
1182
joystick state persist either way, but anything that depends on staying stopped (`break`,
1183
`setreg`, stepping) belongs in interactive mode, where one session stays open.
1184
1185
Web Site
1186
--------
1187
1188
Commander X16: [https://commanderx16.com]https://commanderx16.com
1189
1190
X16Emu ADD: [https://github.com/xylothan/x16-emulator]https://github.com/xylothan/x16-emulator
1191
1192
1193
License
1194
-------
1195
1196
Copyright (c) 2019-2023 Michael Steil &lt;[email protected]&gt;, [www.pagetable.com]https://www.pagetable.com/, et al.
1197
All rights reserved. License: 2-clause BSD
1198
1199
ADD's additions are released under the same 2-clause BSD license. Dear ImGui is vendored in
1200
`src/extern/imgui/` under its own MIT license.
1201
1202
1203
Release Notes
1204
-------------
1205
See [RELEASES.md]RELEASES.md for what each ADD build adds. The release notes for the upstream
1206
emulator each build is based on are [published upstream][upstream-releases].
1207
1208
1209
<!-------------------------------------------------------------------->
1210
[dev-release]: https://github.com/xylothan/x16-emulator/releases/tag/dev
1211
[releases]: https://github.com/xylothan/x16-emulator/releases
1212
[upstream]: https://github.com/X16Community/x16-emulator
1213
[upstream-readme]: https://github.com/X16Community/x16-emulator#readme
1214
[upstream-releases]: https://github.com/X16Community/x16-emulator/releases
1215
[webassembly]: https://github.com/xylothan/x16-emulator/blob/main/webassembly/WebAssembly.md
1216
[x16docs]: https://github.com/X16Community/x16-docs
1217
[x16rom-build]: https://github.com/X16Community/x16-rom#releases-and-building
1218
[x16rom]: https://github.com/X16Community/x16-rom
1219
[website]: https://commanderx16.com
1220
1221
<!-- For PDF formatting -->
1222
<div class="page-break"></div>