# 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 (afterR, or afterT/U/Wcompletes).
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) vsA/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 50traces 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 -- search
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,HLalways.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
Gwhile halted: restores all registers and resumes at the captured PC.G addrwhile halted: loads the captured SP, jumps toaddr, reports the previous PC. Registers other than PC/SP are not restored.G addrwhile running: diverts the CPU toaddrwithout 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. |