Skip to content

EN Debug

drew.po28@gmail.com edited this page Jun 27, 2026 · 3 revisions

Debug

Menu path: Debug · Boards: all · Requires: SD card (only for "Save dump to file" and the on-SD log)

Overview

Two different things live under the word "debug" in pico-spec, and it helps to keep them apart:

  1. The Debug menu (OSD, runtime) — a built-in Z80 debugger you reach from the menu (or hotkeys) while the emulator is running: a single-step source/memory/register view, breakpoints (PC, port and memory), Jump to / Poke / NMI, a memory dump to screen or SD file, and a runtime toggle for the on-SD log. This is the user-facing part and is documented below.

  2. Developer logging (build-time / source-level) — Debug::log to the console UART, Debug::log2SD to debug.log on SD, and a set of per-subsystem *_TRACE verbose logs that are CMake compile options (off in shipped builds). See Developer logging.

The Debug menu is for inspecting guest (ZX-Spectrum) code at runtime. The trace options are for developers debugging the emulator itself and are not in any menu.

Menu structure

Debug
├── Debug dialog      (Alt+F5)   open the full-screen Z80 debugger
├── BreakPoint        (Alt+F7)   add a breakpoint (pick type → address)
├── BP List                      list/edit breakpoints, jump the debugger to one
├── Jump to                      set PC to an address (no execution)
├── Input Poke        (Alt+F9)   write a byte to memory (address/value, optional bank)
├── Trigger NMI       (Alt+F10)  raise a Z80 NMI
└── Debug Log ▸                  Write debug.log  [Yes/No]  — runtime on-SD log toggle

(Default hotkeys shown — all of these are reconfigurable in Options → Host keys.)

BP List and Jump to no longer have dedicated hotkeys — both actions are still available from the Debug menu. The freed Alt+F8 now defaults to toggle on-screen LED indicators (a new, reconfigurable hotkey).

Debug menu items

Item Hotkey What it does
Debug dialog Alt+F5 Opens the full-screen debugger (osdDebug). Disassembly + register panel + memory panel, single-step, breakpoints, search, screen-preview. Keys below
BreakPoint Alt+F7 Pick a type (PC address / Port read / Port write / Mem write / Mem read) then enter an address. Up to 20 breakpoints, saved to NVS
BP List — List active breakpoints (type + address), delete with Del; Enter opens the debugger at that address
Jump to — Set the Z80 PC to a typed address. Changes where execution resumes; does not step
Input Poke Alt+F9 Write one byte: enter Address + Value (and optionally a RAM bank 0–7). Useful for cheats/patches
Trigger NMI Alt+F10 Raise a non-maskable interrupt on the Z80 (Z80::triggerNMI). With DivMMC active this is the esxDOS automap NMI; ZX-Byte shows its NMI menu
Debug Log — Submenu Write debug.log [Yes/No] — turns the on-SD developer log on/off at runtime (see below). Persisted to NVS (debug_log)

Several of these actions are also bound to hotkeys, so you can open the debugger, add a breakpoint, poke or NMI without going through the menu. BP List and Jump to are menu-only (their old Alt+F7 / Alt+F8 hotkeys were removed).

Breakpoint types

Type Code Fires when…
PC address PC the program counter reaches the address
Port read PR the guest reads from the I/O port
Port write PW the guest writes to the I/O port
Mem write MW the guest writes to the memory address
Mem read MR the guest reads from the memory address

When a breakpoint hits, the current frame ends and the debugger view opens. The running-machine stats line also shows BP(s):N while breakpoints are set.

The full-screen debugger

The debugger (Debug dialog) has three sections you cycle with Tab: Code (disassembly around PC), Memory (a hex/ASCII dump panel) and Regs. Press F1 inside it for the built-in help. Keys:

Key Action
Space Step one CPU instruction
Alt+Space Step over a CALL (sets a temporary breakpoint at the return address)
Enter Go to an address (view; in a panel, inline hex edit)
Tab Switch section: Code / Memory / Regs
F5 Toggle a PC breakpoint at the cursor line
F7 Add a breakpoint (choose type)
Alt+F7 Breakpoint list
F8 Set PC to the address at the cursor
F2 Show the guest screen (memory dump preview overlay)
Alt+F2 Save dump to file → dump.log on SD (range picker first)
Alt+F1 Search memory for a hex byte sequence
F3 Search next
Alt+T Toggle the memory panel between HEX and ASCII
Alt+F9 Show the guest screen full-screen
F11 / F12 Load / Save snapshot
+ / − / 0 Shift the screen preview up / down / default
PageUp/Down, arrows Scroll code / move the memory cursor
Esc Exit the debugger

Memory dump to SD (dump.log)

Alt+F2 in the debugger asks for an address range, then writes a full state dump to /.config/pico-spec/dump.log (created fresh each time, CREATE_ALWAYS). The dump contains, in order:

Section Contents
Header Dump range #xxxx – #xxxx
Machine Arch + RomSet
ROM/paging state romInUse, romLatch, bankLatch, videoLatch, pagingLock, page0ram, newSRAM, divmmc
TR-DOS on/off + TR-DOS BIOS version
Registers AF BC DE HL (+ alternates AF' BC' DE' HL'), IX IY SP PC, I R IM IFF1 IFF2 Halted
Flags S Z H P N C decoded from F
Stack top 8 words (SP+00 … SP+0E)
CPU T-states + statesInFrame
TR-DOS / WD1793 TR-DOS state, FDD write-protect A–D, and the FDC: cmd status track sector data drive side dsr led retry state stepState control, plus per-disk tracks / sides / wp / filename
Memory dump hex + ASCII over the chosen range

This is the efficient way to capture a complete runtime snapshot for offline analysis, versus single-stepping by hand.

Developer logging

This section is build-time / source-level — none of it is in a menu except the runtime debug.log toggle above.

Debug::log() — console UART (always on)

Debug::log(fmt, …) prints to the console UART (or printf where there's no debug UART). It is always compiled in and always fires — no flag. It is best-effort and lossy under flood: it only writes while the UART TX FIFO has room and drops the rest rather than blocking (a logging stall in a hot path — e.g. the ZiFi net pump or a tape loader — would otherwise freeze the main loop or break loader timing).

⚠️ Never log inside a tight loader / hot loop. Even one write per IN A,(0xFE) adds thousands of T-states and breaks turbo tape loaders.

Debug::log2SD() — debug.log on SD (runtime-gated)

Debug::log2SD(fmt, …) appends timestamped lines to /.config/pico-spec/debug.log on the SD card. It is gated by a runtime flag, Debug::log_enabled, controlled by the Debug → Debug Log → Write debug.log [Yes/No] menu toggle (persisted to NVS as debug_log). When the flag is off the call collapses to a single branch and writes nothing. The log is capped at 200 KB and wraps (rewritten from the start) when full; each session starts with a --- BOOT (POWER-ON | WATCHDOG) --- header.

Note: this is a runtime toggle, not the old DEBUG=1 compile define. Turn it on from the menu, reproduce the issue, then read debug.log off the SD card.

Per-subsystem *_TRACE options (CMake, off by default)

Verbose per-access traces live in the source but are compiled out unless you build with the matching CMake option. They are defined in one place (CMakeLists.txt) as option(... OFF) → -DXXX_TRACE=1/0; sources just use #if XXX_TRACE … #endif. When off they are true no-ops (no code, no RAM). Enable with e.g. cmake -B build -DFDD_PORT_TRACE=ON.

Option Subsystem traced
IDE_PORT_TRACE PROFI IDE/HDD port access (Ports.cpp, IDE.cpp)
FDD_PORT_TRACE WD1793 (ВГ93) FDD command/port trace
RTC_PORT_TRACE MC146818 (Mr Gluk) RTC ..F7 port access
PROFI_PORT_TRACE 0x7FFD / 0xDFFD paging-port writes
SND_PORT_TRACE per-port I/O histogram (sound-DAC port hunting)
PERF_TRACE per-60-frame CPU / HDMI / FPS performance log
GS_PERF_TRACE General Sound per-second perf counters
GS_DEBUG_TRACE General Sound port-IO trace ring + auto-dumps (~50 KB SRAM)
ZIFI_TRACE ZiFi (ESP-01S NIC) port / UART traffic
ZIFI_NET_VERBOSE per-packet ZiFi net-client trace (floods logs)

⚠️ These flood the console UART and can break timing-sensitive paths (WiFi scan, FTP/transfers, tape loaders). Keep them OFF unless actively debugging that one subsystem, and never ship a build with them on.

Debug Probe UART (*_DBG_UART, CMake)

Separate per-board CMake switches (MURM1_DBG_UART, PICO_PC_DBG_UART, PICO_DV_DBG_UART, ZERO_DBG_UART, ZERO2_DBG_UART, all OFF by default) route the console UART out to a header for a debug probe / serial monitor. Enabling one takes GPIO pins from other peripherals (e.g. moves the keyboard and disables NESPAD on MURM1) — see Boards & pinout.

Files on SD

Path Purpose
/.config/pico-spec/debug.log Debug::log2SD output (≤ 200 KB, wraps). On when Debug Log is Yes
/.config/pico-spec/dump.log One state+memory dump per Alt+F2 in the debugger (overwritten each time)

How to test

Action What it checks Expected result
Set a PC breakpoint, run guest code past it Breakpoint engine Frame ends, debugger opens at the address; stats show BP(s):1
In the debugger, Alt+F2 with SD present Dump-to-file dump.log created with registers/stack/WD1793/memory; "Dump saved" notice
Debug Log → Yes, reproduce, read SD log2SD runtime gate debug.log grows with timestamped lines + a --- BOOT … --- header
Input Poke an address, resume Poke The byte changes in memory (and in the debugger Memory panel)

See Test images.

Links

Clone this wiki locally