# SDH-100 Monitor

The monitor is an interactive memory and CPU debugger built into the SDH-100 firmware. It runs on the USB MON CDC channel and can:

  • Dump, edit, fill, search, move, and compare physical S-100 RAM via DMA.
  • Do the same on the local shadow RAM that backs ROM/RAM emulation.
  • Disassemble code, and (where built in) assemble new code in place.
  • Capture and display CPU registers (8080 or Z80) using a real interrupt.
  • Run, single-step, step-over, and trace-until-tight-loop the CPU.
  • Watch memory addresses or I/O ports and halt on first access.
  • Show last-seen values on every input and output port.
  • Display the page-by-page memory map, including Dazzler and VIO regions.

This document is a reference. It assumes 8080 / Z80 familiarity, an S-100 vocabulary, and a terminal connected to the USB MON channel.

# Activation and exit

The MON CDC channel carries both firmware log output and the monitor. Logging continues to flow when the monitor is idle.

To enter the monitor, press Enter twice within 500 ms while the monitor is idle. The monitor prints a banner:

=== SDH-100 Monitor ===
Dialect: Z80
Type ? for help, X to exit

The dialect line reports the currently selected display dialect (8080 or Z80). On entry the monitor silently verifies shadow against physical RAM and warns if they have drifted.

The prompt is:

  • > -- the CPU is running freely.
  • [HALT]> -- the CPU has been halted by the monitor (after R, or after T / U / W completes).

Type ? for the in-monitor help screen.

To exit, type X or Q. The monitor releases the bus, disarms any active watch, and prints Monitor exited. Logging traffic continues on the same channel.

# Concepts

# Physical RAM vs shadow RAM

Every 256-byte page of the CPU address space has a page type:

  • R -- RAM emulated by the SDH-100 (writable, served from shadow).
  • O -- ROM emulated by the SDH-100 (read-only, served from shadow).
  • r -- shadow tracking real physical RAM on the S-100 bus.
  • X -- clash: physical RAM exists where the SDH-100 mapped a page.
  • . -- empty.

Physical RAM commands (D, E, F, M, S, C, L, A) arbitrate the S-100 bus and access RAM via DMA. The CPU is paused for the duration.

Shadow RAM commands (SD, SE, SF, SC, SS) read and write the local copy directly. They do not touch the bus, so they are instantaneous and do not pause the CPU.

V verifies shadow against physical. Y overwrites shadow from physical. Both default to all shadow-mapped pages when called with no arguments, or take an address range.

# CPU halt and resume

R halts the CPU by raising an interrupt and running a short capture routine that saves every register and parks the CPU. While halted, the monitor can read the saved registers, single-step, arm watches, and issue IN / OUT cycles. G resumes the CPU, either at the captured PC (G alone) or at a new address (G addr).

This is deliberately not a real HALT instruction. A HALT instruction stops execution but preserves nothing -- the registers are not saved, the CPU has not stored a clean return point, and the only way out is a reset or another interrupt. The monitor's capture approach saves every register so R can display them, lets you modify them through shadow RAM, and restores them on G so the program continues exactly where it left off. The same captured state is what makes single-step, watches, and IN / OUT injection possible.

While the CPU is halted the monitor uses the RST vector page (0008h-003Fh) for its capture routine. CP/M's working storage at 0040h and above is never touched, and the original contents of the RST page are restored on resume.

Execution commands require EI (or NMI mode on Z80)

All execution commands (R, G, I, O, T, U, W) require interrupts to be enabled on the CPU (EI on both 8080 and Z80). Without EI, the capture routine cannot fire and the command will time out.

On a Z80, this restriction is lifted by setting MON.break=NMI in boot.conf: the NMI break-in path is non-maskable and breaks in even on DI'd code.

# Display dialect

The monitor can display 8080 or Z80 dialect. The dialect affects:

  • Disassembly mnemonic style (L).
  • Assembler input syntax (A).
  • Register-display field set: 8080 hides IX, IY, I, R, and the alternate register set.
  • Flag bit names: H / V (Z80) vs A / P (8080).
  • Trace-step output mnemonic and flags.

The dialect is purely cosmetic -- it changes how things are displayed, not how the CPU is captured or stepped. By default the dialect tracks the live CPU type. DI forces 8080, DZ forces Z80. The setting persists for the session.

# Number formats

  • Addresses and byte values are hex, no prefix. Examples: D 100, F 0 FF 00, O FE 80.
  • Step counts and delays in T, U, T+, U+ are decimal -- they are quantities, not addresses. Example: T 16 50 traces 16 steps with 50 ms between them.
  • Output is uppercase hex.

# Command reference

The reference is grouped to match the in-monitor ? help.

# Physical RAM (DMA)

# D -- dump

D
D addr
D addr end

Prints 16-byte hex+ASCII lines from addr & FFF0h through end. The default end is addr + 7Fh (eight lines). Bare D continues from the end of the previous dump; without a prior dump it prints Bad args. Arbitrates the bus.

# E -- enter / edit

E addr

Interactive byte-by-byte edit. The monitor holds the bus for the entire session and prompts:

XXXX: YY-

Type two hex digits and the byte is written to physical RAM and the address advances. Space skips to the next address without writing. . or Enter exits.

# F -- fill

F addr end val

Fills the inclusive range [addr, end] with byte val. All three arguments are required. Prints Filled XXXX-XXXX with VV.

# M -- move / copy

M src end dest

Copies [src, end] to dest. Direction is chosen automatically so overlapping regions are safe.

S addr end b1 [b2 ...]

Scans [addr, end] for the byte sequence b1 b2 ... (up to 16 bytes). Prints each matching address, or Not found.

# C -- compare

C addr end addr2

Compares [addr, end] against the region of equal length starting at addr2. Prints differing bytes with both addresses and values, or Identical.

# L -- disassemble

L
L addr
L addr end

Disassembles up to about 20 instructions per call in the current dialect. With no argument, L continues from the end of the previous listing; if no previous listing and the CPU is halted, it starts at the captured PC; otherwise it prints usage. With addr only, it disassembles from addr for one screen. With addr end, it stops when the next instruction would pass end. The continuation pointer is updated after each call.

# A -- assemble (where built in)

A addr

Available when the firmware is built with assembler support. Holds the bus, prompts:

XXXX:

Accepts one mnemonic per line in the current dialect. Encoded bytes are written to physical RAM and the address advances. Hex is the default number format -- no 0x prefix on operands. A blank line or . exits.

Coverage: full 8080, full Z80 including IX / IY, IXH / IXL, the CB-prefixed bit and shift group, and the ED-prefixed extended instructions.

# Shadow RAM (local)

SD [addr [end]]    -- dump (same forms as D, including continuation)
SE addr            -- enter / edit (same prompt as E)
SF addr end val    -- fill
SC src end dest    -- move / copy
SS addr end b1...  -- search

These mirror their physical-RAM counterparts but operate on the local shadow buffer. They do not arbitrate the bus, so they are instantaneous and the CPU keeps running.

SC vs SM

The move command is SC (Shadow Copy), not SM. SM is the memory-map display.

# Compare and sync

# V -- verify shadow vs physical

V
V addr
V addr end

Reports each differing byte as XXXX: shad=YY phys=ZZ. With no arguments, checks every shadow-mapped page; with addr only, checks a 256-byte region; with addr end, checks the supplied range. Prints Shadow matches physical if no differences are found.

The same check runs silently on every monitor activation.

# Y -- sync shadow from physical

Y
Y addr
Y addr end

Overwrites shadow with the value read from physical RAM, byte by byte. Same range conventions as V. Use this after the CPU has written to RAM behind the SDH-100's back -- e.g. after a long run without monitor halts.

# Memory map

# SM -- show map

SM

Prints a 256-cell grid, one character per 256-byte page, using the page-type letters: R O r X .. Dazzler and VIO memory regions are overlaid as D / d and V / v when those devices are active.

# Execution (requires EI, or MON.break=NMI on Z80)

# R, H -- register dump

R
H

Halts the CPU, captures every register, and displays:

  • PC, SP, AF, BC, DE, HL always.
  • IX, IY, I, R, and the alternate register set when running on a Z80.
  • Flag bits using the current dialect's names.

H is an alias for R. After R the prompt is [HALT]>. Issuing R while already halted re-displays the previously captured registers without re-halting.

# G -- go

G
G addr
  • G while halted: restores all registers and resumes at the captured PC.
  • G addr while halted: loads the captured SP, jumps to addr, reports the previous PC. Registers other than PC/SP are not restored.
  • G addr while running: diverts the CPU to addr without halting first.

After G the prompt returns to >.

# I -- input from port

I pp

Issues a real IN A,(pp) cycle on the bus. Prints Port PP = DD. If the CPU was halted, it stays halted after the operation; if running, it resumes.

# O -- output to port

O pp dd

Issues a real OUT (pp),A with value dd. Prints Port PP <- DD. Same halted / running behaviour as I.

# Trace (after R only)

T, U, T+, and U+ execute one instruction at a time. They require the CPU to be halted (use R first). Counts and delays are decimal. Any keypress during a multi-step run (Z or anything else) requests an abort; the in-flight step completes first, then the monitor prints Trace aborted.

# T -- step into

T
T n
T n d

Step n instructions (default 1). d is the inter-step delay in milliseconds (default 0). n = 0 runs unbounded until aborted. After each step the monitor prints a one-line trace frame:

AF=XXXX[SZ5H3VNC] BC=XXXX DE=XXXX HL=XXXX SP=XXXX PC=XXXX  bytes  mnem

# U -- step over

U
U n
U n d

Like T, but CALL and RST (plus Z80 block ops on Z80) are stepped over: the breakpoint is placed only at the return address, not inside the callee. Other instructions step identically to T.

# T+, U+ -- trace until tight loop

T+
T+ max
T+ max d
U+ max d

Runs up to max steps (default 4096; 0 is unbounded) looking for a tight loop -- the same PC seen 3 times with all step PCs spanning no more than 12 bytes. Reports either:

Tight loop at XXXX (span XXXX-XXXX, N bytes)

or:

No tight loop detected within step cap

The space between the command and the count is optional: T+ 100 50 and T+100 50 are equivalent.

# Watch (after R only)

W arms a one-shot hardware-assisted watch and resumes the CPU. The monitor prints what it is watching, and the prompt switches to indicate the watching state. When the watch fires, the CPU is halted and a trace-style one-liner is printed showing the access and the register state at the hit PC.

The CPU must be halted when arming a watch (use R first).

# W -- arm watch

Memory watch:

W R addr [end]
W W addr [end]
W A addr [end]

The type letter is R for Read, W for Write, or A for Address (read or write -- matches on any direction at the address). addr and end are 16-bit hex addresses; the watch fires on the first matching access to any byte in [addr, end]. If end is omitted, the watch is for the single address addr.

Port watch:

W I pp [end]
W O pp [end]
W P pp [end]

The type letter is I for Input, O for Output, or P for Port (IN or OUT -- matches on any direction on the port). pp and end are 8-bit hex port numbers; the watch fires on the first matching cycle on any port in [pp, end]. If end is omitted, the watch is for the single port pp. The monitor refuses to watch OUT on port A1h because the monitor itself uses that port internally and a watch on it would self-trigger.

# Bare W -- re-arm the previous watch

After a watch fires (or is aborted with Z), a bare W with no arguments re-arms the most recently armed watch with the same type and range. Useful for stepping through a stream of matching accesses without retyping the full command. The monitor remembers the last W across other commands, so an interleaved R / D / etc. between hits doesn't clear it. The recall is cleared only by exiting the monitor (or restarting the firmware); a fresh W with explicit arguments updates the recall to the new spec. If W is issued with no arguments before any watch has been set in the current monitor session, the monitor responds No previous watch and stays halted.

# IN watch capture-PC accuracy at 4 MHz

Z80 4 MHz timing caveat

When an IN watch fires with the Z80 at 2 MHz, the captured PC is the address of the instruction immediately following the IN (i.e. IN+1). At 4 MHz the firmware can't drive NMI* low quickly enough to fall inside the Z80's INT-sampling window for the IN's IORQ cycle, and the capture lands one instruction further on -- the IN+1 instruction has executed and the captured PC is IN+2.

This is harmless when the slipped instruction is a regular opcode -- read backward from the displayed PC and you'll find the IN. But if the slipped instruction is RET or CALL, the captured PC is the return target or the callee entry, with no direct way to recover the original IN's address. If you need an exact post-IN PC capture, run the Z80 at 2 MHz while the watch is armed.

Memory and OUT watches are unaffected.

# Z -- abort an active watch

Z is a single keystroke (not a parsed command). While a watch is active it is the only key that aborts: the watch is disarmed, the CPU is halted, and the monitor reports Watch aborted; PC=XXXX plus the register one-liner. Other input is ignored while watching.

(During a multi-step trace, by contrast, any keypress aborts -- see T / U above.)

# Port caches

The SDH-100 records the last value seen on every input and output port as the CPU executes I/O cycles. The caches are zeroed at power-up and on reset.

# PI -- dump IN cache

PI

Prints a 256-entry table of the last byte read from each input port. -- means the port has not been observed since the cache was last cleared. + marks ports handled by the SDH-100 where the handler and the observed bus access agree. * flags a disagreement -- the SDH-100 served the port but the cache snoop saw a different access direction. A blank marker means the port is on an external bus device. The same legend is printed at the bottom of the output.

# PO -- dump OUT cache

PO

Same as PI but for output ports. + marks ports handled by the SDH-100; blank means the port is on an external bus device.

# PC -- clear caches

PC

Zeroes both the IN and OUT port caches.

# Display dialect

# DI, DZ -- force dialect

DI    -- force 8080 (Intel)
DZ    -- force Z80 (Zilog)

Cosmetic only. Affects L disassembly, A assembly syntax, the register-display field set, the flag-bit names, and the trace-step mnemonic and flags. Without DI / DZ the dialect tracks the live CPU.

# Control

# ? -- help

?

Prints the in-monitor help screen.

# X, Q -- exit

X
Q

Deactivates the monitor, releases the bus, disarms any active watch, and prints Monitor exited.

# State requirements at a glance

Command CPU state required
D, E, F, M, S, C, L, A Any -- the bus is taken via DMA.
SD, SE, SF, SC, SS Any -- shadow only, no DMA.
V, Y Any -- DMA for the read side.
SM, PI, PO, PC Any.
DI, DZ, ?, X, Q Any.
R / H Any -- transitions to halted.
G (no addr) Halted.
G addr Any.
I, O Any -- preserves halted state.
T, U, T+, U+ Halted (use R first).
W ... Halted (use R first).
Z While a watch is active.
Last Updated: 5/2/2026, 5:36:29 PM