# SDH-100 boot.conf Configuration Guide
# About the file
The firmware reads /CONF/boot.conf from the microSD card once at
boot.
First-time setup
If the file is missing, boot halts before the bus comes up, the SD card is presented as a USB mass-storage volume, and the board waits there until a valid file is copied and power is cycled.
The format is one directive per line, in the form KEY=VALUE.
- A line whose first character is
#is a comment and is skipped. - A
#after the value also starts an inline comment; everything from#to the end of the line is discarded. - Blank lines are ignored.
- Whitespace around the
=is significant on the key side: a leading space turnsFDC.transportintoFDC.transport, which the firmware will not recognise. Values tolerate trailing spaces in most cases but it is safest to leave none. - If the same key appears twice, the last value wins.
Silent typos and case sensitivity
- Keys are case sensitive. Write
FDC.transport, notfdc.transportorFDC.TRANSPORT. Values are usually case-insensitive but a few are not -- the sections below call out any that are. - Unknown keys are silently ignored. A typo in a directive name does not produce an error -- it produces a missing setting.
Edits take effect on next boot only
Settings are applied during boot. Editing boot.conf while the board
is running has no effect until the next reset.
# Syntax notation
Each directive in the reference below is preceded by a one-line grammar. The notation is:
- Literal tokens (
AUTO,cs8,default,PASS, ...) appear exactly as shown. <name>marks a value you supply (a number, an address, a filename, etc.).A|B|Cmeans choose one of the listed alternatives.[...]marks an optional fragment.[,X...]indicates the fragment may repeat, comma-separated.- Spaces in the grammar are for readability only -- the value itself contains no spaces.
# Annotated showcase boot.conf
The file below exercises every directive the firmware recognises. It is intentionally maximal -- a real installation typically only sets the lines it needs and lets the rest default. Use this as a copy-and-cut starting point.
### boot.conf -- SDH-100 configuration
### --- Boot logging ---------------------------------------------
# BOOT_LOG=QUIET # suppress UART/S-132 console boot log
# BOOT_LOG=TEXT # strip ANSI colour escapes
BOOT_LOG=TEXT # plain-text colour-free boot log
### --- Serial ports ---------------------------------------------
UART0=115200,cs8 # console UART (S-100 SIO Port A path)
UART1=115200,cs8,parenb # SIO Port B path -- enable even parity
### --- Memory map -----------------------------------------------
ROM_LOAD=memon80.hex # one or more Intel-HEX images from /ROMS/
# ROM_LOAD=mpu-a-rom.hex,viofm1.hex
RAM_MAP=AUTO # fill every empty page with emulated RAM
# RAM_MAP=00:7F # alternative: explicit page ranges
# RAM_MAP=00:7F,80:DF
RAM_SET=0x00 # fill emulated RAM with this byte
MEM_TEST=RUN # one walking test pass after init
# MEM_TEST=RUNLOOP # ...or loop forever (development use)
BOOT_JUMP=0xF800 # plant JP F800h at 0000h before release
### --- Disk drives ----------------------------------------------
HARDDISK=blank # /DISKS/blank.hdd as the large disk
FDC.transport=USB # USB or UART (UART claims UART1)
# FDC.uart.drives=A:CPM22.FDC,B:UTILITY.FDC
### --- Printer -------------------------------------------------
LPT.iobase=default # 0xF6
LPT.device=S132 # S132 | FILE | PARALLEL
### --- IMSAI VIO ----------------------------------------------
VIO.iobase=default # 0x04 -- VIO command/data
VIO.membase=default # 0xF000 -- VIO 2 KB video window
### --- SIO controller -----------------------------------------
SIO.iobase=default # 0x00 -- SIO-2 base (Port A and Port B)
# SIO.A.baud=9600 # software-throttle SIO Port A
# SIO.B.baud=9600 # software-throttle SIO Port B
### --- Other I/O port bases ------------------------------------
DZLR.iobase=default # 0x0E -- Cromemco Dazzler
D7AIO.iobase=default # 0x18 -- 8-port digital/analogue I/O
TIMER.iobase=default # 0xA0 -- interval timer / control port
SDH.iobase=default # 0xA1 -- SDH host co-routine port
APU.iobase=default # 0xA2 -- Am9511 APU
FIF.iobase=default # 0xFD -- IMSAI FIF floppy controller
MMU.iobase=default # 0x40 -- IMSAI MMU bank switch
RTC.iobase=default # 0x41 -- DS3231 real-time clock
### --- USB host integration ------------------------------------
USB.disk=AUTOMOUNT # auto-present microSD as MSC volume
USB.tty=PASS # bridge USB CON CDC <-> SIO Port A
# USB.uc1=PASS # bridge USB AUX CDC <-> SIO Port B
USB.debug=TRUE # log USB connect / disconnect events
### --- Monitor ------------------------------------------------
# MON.break=NMI # break via Z80 NMI vector (Z80 only)
### END
The remainder of this document explains every directive in detail.
# Boot logging
# BOOT_LOG
BOOT_LOG=QUIET|TEXT|QUIET,TEXT
Controls how boot-time log output is rendered on the console serial
ports. Accepts the keywords QUIET and TEXT, separately or
combined with a comma or space (BOOT_LOG=QUIET,TEXT). QUIET
suppresses log output on UART0 and on the S-132 console, but leaves
the USB MON CDC channel untouched, so the boot log is still
available there for diagnosis. TEXT strips the ANSI colour-escape
sequences that the firmware normally emits, which matters when the
console is a paper teletype, an early CRT terminal that prints
escape sequences as gibberish, or a logger that captures raw bytes
to a file. The directive only affects the boot phase; once the
firmware finishes initialising and reports SDH-100 Running..., the
boot log stops emitting and the runtime log channel takes over with
its own conventions. If you find yourself wanting clean text in
captured logs, set BOOT_LOG=TEXT permanently -- it has no downside
on a colour-capable terminal beyond the loss of a few highlights.
# Serial ports
# UART0
UART0=<baud>[,cs7|cs8][,cstopb][,parenb[,parodd]]
Configures the S-100 SIO Port A path on UART0 of the RP2350B. The
value is an stty-style list of tokens separated by spaces or commas:
a bare integer is the baud rate; cs8 and cs7 set the data-bit
width; cstopb selects two stop bits; parenb enables even parity;
parodd switches to odd parity (only valid in combination with
parenb). The default if the line is missing or empty is
115200,cs8 -- 115200 baud, 8 data bits, 1 stop bit, no parity. The
SDH-100 always reinitialises UART0 at boot, so commenting the line
out is the same as leaving it at the default; deleting the directive
has no special meaning. UART0 is not configured here when an S-132
console is detected -- the S-132 owns the console TTY in that
case, and its own settings apply. UART0 is also the primary
console for the boot log when BOOT_LOG=QUIET is not in force, so
choose a baud rate your terminal can actually keep up with during
the rapid boot-time output.
# UART1
UART1=<baud>[,cs7|cs8][,cstopb][,parenb[,parodd]]
Configures UART1 of the RP2350B with exactly the same syntax as
UART0. UART1 backs the S-100 SIO Port B path under normal use --
hooked, for example, to a second terminal, a serial printer, or a
modem. Default is again 115200,cs8. The interaction worth knowing
is that FDC.transport=UART claims UART1 exclusively at 2 000 000
baud and reroutes it to the floppy controller protocol; in that
configuration any baud rate or framing you set here is silently
overridden, and SIO Port B emulation stops responding. If you need
SIO Port B for terminal use, leave FDC.transport=USB (the default)
or omit the line. Like UART0, UART1 is reinitialised on every boot
even if you delete the directive, so there is no way to leave the
hardware UART unmanaged.
# Memory and ROM
# ROM_LOAD
ROM_LOAD=<file>[,<file>...]
Names one or more Intel-HEX files in /ROMS/ that the firmware
loads into shadow memory and marks read-only before the CPU is
released. The value is a comma-separated list of filenames -- for
example ROM_LOAD=memon80.hex or
ROM_LOAD=mpu-a-rom.hex,viofm1.hex. Each file is loaded at the
addresses encoded in its hex records, so the load address is part of
the file, not part of the directive. Pages that receive HEX data
become ROM in the live memory map and survive resets initiated by
the bus until the next power cycle. If the directive is absent the
firmware logs a warning and brings the bus up with no emulated ROM,
which is normal when you intend to run on physical S-100 RAM/ROM
boards alone. ROM data placed at the reset vector by ROM_LOAD is
overwritten by BOOT_JUMP if both are set; BOOT_JUMP runs later.
# RAM_MAP
RAM_MAP=AUTO|<start>:<end>[,<start>:<end>...]
Decides which 256-byte pages of the 64 KB CPU address space the
SDH-100 will provide as emulated RAM. The value is either the literal
keyword AUTO or a comma-separated list of START:END page-number
pairs in hexadecimal -- for example RAM_MAP=00:7F,80:DF. Page
numbers are the high byte of the address, so 00:7F covers
addresses 0000h-7FFFh (32 KB) and 00:FF covers the entire 64 KB.
AUTO is the easiest choice and is the right default for most
systems: the firmware first scans the bus for physical memory cards
and any ROM you loaded, then fills every remaining page with emulated
RAM. Manual ranges are useful when you want to leave a hole for a
device that is not yet plugged in, when you intentionally want
unmapped pages to NACK to test software, or when you want to run
with a smaller RAM footprint for diagnostics. Pages outside the
declared ranges return FFh to the CPU and produce no read or write
side-effects. If the directive is absent the firmware logs a warning
and provides no emulated RAM at all -- a configuration only useful
when every page in the system comes from physical S-100 cards.
# RAM_SET
RAM_SET=<byte>
Picks the byte value used to fill emulated RAM at boot. Accepts any
form strtol understands, including 0xAA, 170, or 0252. Only
the low eight bits matter -- RAM_SET=0x1234 is the same as
RAM_SET=0x34. If the line is absent, RAM is filled with
pseudo-random bytes from the firmware's PRNG, which is the right
choice when you want unmapped variables and untouched stack regions
to be detectable by software that checks for known patterns or that
crashes on uninitialised reads. Setting RAM_SET=0x00 makes the
emulated RAM look like cleared dynamic RAM -- familiar for CP/M
loaders and most BASICs. RAM_SET=0xFF mirrors what an unattached
data bus normally pulls up to and is useful when you want freshly
booted RAM to look exactly like physical EPROM holes. The value
applies only to pages declared as emulated RAM (AUTO-filled or
listed explicitly); pages backed by physical S-100 RAM keep whatever
they hold, and ROM pages keep their HEX-loaded content.
# MEM_TEST
MEM_TEST=RUN|RUNLOOP
Runs a walking-pattern test across every shadow-RAM page after the
memory map is built and before the CPU is released. The keyword
RUN performs a single pass; RUNLOOP performs the test in an
infinite loop and forces the SD card to remain visible as a USB MSC
volume so you can pull a log file off the board mid-run. The pattern
sequence is AAh, 55h, FFh, 00h, F0h, 0Fh, 5Ah, A5h, 33h, CCh
applied to every byte; failures are reported in the boot log with
the failing address and the expected and observed bytes. The test
runs while the SDH-100 has been granted the bus, so the CPU is held
off and not yet fetching from any of the pages under test -- a long
test does delay the SDH-100 Running... banner. In daily use the test is unnecessary;
RUN is most useful as a one-shot acceptance check after a hardware
modification or when chasing intermittent crashes that smell like
RAM. RUNLOOP is meant for development, not normal operation. If
the directive is absent or set to anything that does not contain
RUN (note: MEM_TEST=LOOP on its own does not run the test), the
test is skipped.
# BOOT_JUMP
BOOT_JUMP=<addr>
Plants a three-byte unconditional jump (C3 lo hi) at addresses
0000h-0002h immediately before releasing the CPU from HOLD. The
value is a 16-bit address in any base strtol accepts, typically
hex (BOOT_JUMP=0xF800). The firmware writes the bytes both into
shadow RAM and, if the page at zero is also present in physical RAM,
into the physical SRAM via DMA -- so the CPU sees the jump on its
first fetch regardless of whether 0000h is mapped to a SDH-100
page or to a real RAM card. The directive runs after ROM_LOAD and
after MEM_TEST, so it overrides anything the HEX file placed at
the reset vector. The most common application is to land directly
into a monitor or BIOS that loads at a high address (MEMMON80 at
F800h, a CP/M loader at EE00h, etc.) without having to write a
two-byte stub into the ROM image first. Invalid values -- non-numeric
text, addresses above FFFFh, or trailing garbage -- are rejected
with a warning and no jump is injected, which leaves whatever the
HEX file or memory pattern put at the reset vector intact. If the
directive is absent no bytes are written.
# Disk drives
# HARDDISK
HARDDISK=blank|<name>
Mounts a hard-disk image at the large-disk slot of the IMSAI floppy
controller. The value is the basename of a .hdd file in /DISKS/;
the .hdd extension is appended automatically, so
HARDDISK=cpm68k mounts /DISKS/cpm68k.hdd. The reserved value
blank mounts the firmware's standard 4 MB blank image, which is
formatted by the host operating system on first use. The drive
appears at the floppy controller's "I:" slot (large disk index) and
follows the same sector geometry the FIF expects of a hard-disk
image. If the directive is absent no large-disk image is mounted and
software that probes for it sees an empty drive. Unlike floppy
drives, the hard-disk slot is set once at boot -- there is no boot
configuration to swap it at runtime, though host commands can change
it later via the SDH co-routine port.
# FDC.transport
FDC.transport=USB|UART
Selects the channel the IMSAI floppy controller emulator uses to
talk to the host. FDC.transport=USB (the default) routes FDC
traffic over the dedicated USB FDC CDC class device, where it is
served by a USB host application that streams disk images. UART
hands the floppy protocol to UART1 at a fixed 2 000 000 baud, 8N1,
for installations where a USB host is impractical and a wired serial
link is available instead. The value is matched on its first four
characters case-insensitively, so USB, usb, and UART all work.
The most important consequence of UART mode is that SIO Port B
becomes unavailable: UART1 is fully consumed by the floppy protocol,
and any software that drives Port B will see it as silent.
USB.uc1=PASS is also incompatible with FDC.transport=UART for
the same reason. New as of 0.18.0; releases before that always used
USB and accepted no override.
# FDC.uart.drives
FDC.uart.drives=<drive>:<image>[,<drive>:<image>...]
Pre-populates the floppy controller's drive map at boot, but only
when FDC.transport=UART. The value is a comma-separated list of
drive:image pairs, where the drive letter is A through D and
the image is a filename in /DISKS/. For example,
FDC.uart.drives=A:CPM22.FDC,B:UTILITY.FDC mounts those two images
on drives A and B at the first successful FDC handshake. Whitespace
inside an entry is tolerated. Entries that fail to parse, or images
that the FDC layer rejects, are logged and skipped without aborting
the rest of the list. Only the first successful FDC handshake
applies the map; subsequent FDC resets do not re-apply it, and any
runtime drive changes made through host commands persist
independently. The directive is silently ignored when transport is
USB -- a USB host is expected to manage its own image catalogue. If
you switch from UART back to USB transport, leaving the
FDC.uart.drives line in place is harmless.
# Printer
# LPT.iobase
LPT.iobase=default|<port>
Sets the I/O port base for the parallel-printer interface. The value
is default (which selects port F6h) or a hex/decimal port
number. The directive must be present for the printer to be
registered at all -- if you remove the line entirely, the printer
disappears from the I/O map and software targeting it sees no
response. When you change the base, the device occupies a single
port; the strobe and status lines are generated internally and do
not consume additional ports. The directive interacts with
LPT.device: changing the base does not change what the printer
output is doing, only which port gets writes routed to it.
# LPT.device
LPT.device=FILE|PARALLEL|S132
Routes printer output to one of three sinks. Accepts FILE,
PARALLEL, or S132, case-insensitive but with no surrounding
whitespace. FILE -- the default if the directive is absent --
buffers writes and appends them to /lpt.txt on the SD card,
flushing after a second of inactivity, which is the simplest
configuration for capturing CP/M LST: output for later review on a
laptop. PARALLEL drives a physical Centronics-style printer through
the on-board MCP23017 I2C I/O expander; if the chip is not detected
on the I2C bus the firmware logs an error and falls back to FILE
silently, so a stale PARALLEL setting on a board without the I2C
add-on does not stop the system booting. S132 routes printer
writes through the S-132 D7AIO channel, which is useful
when the S-132 is connected to a host that wants to render printer
output on its own screen. Unrecognised values fall back to FILE
with a warning. There is no way to log to multiple sinks at once.
# IMSAI VIO
# VIO.iobase
VIO.iobase=default|<port>
Sets the I/O port base for the VIO video controller's command and
data ports. The value is default (port 04h, occupying 04h-05h)
or a hex/decimal port number. The VIO command interface is two
ports wide regardless of base. Note that this directive is
independent of VIO.membase -- moving the I/O base does not
move the video memory window, and vice versa. A working
configuration requires both bases to be consistent with whatever
software you are running.
# VIO.membase
VIO.membase=default|<addr>
Sets the base address of the VIO's 2 KB video memory window in the
CPU address space. The value is default (F000h, covering
F000h-F7FFh) or a hex address that you have made sure does not
overlap any ROM, RAM, or device window. Moving this is most often
useful when you load a ROM at the same high addresses the VIO
normally occupies and you want to push the video buffer down to
E800h or similar. The window is always exactly 2 KB; you can move
it but you cannot resize it.
# SIO controller
# SIO.iobase
SIO.iobase=default|<port>
Default 00h. Registers the SIO-2 controller, which provides Port A
at base+02h/base+03h (data and command) and Port B at
base+22h/base+23h. The data port at base+00h itself is left
unassigned. Moving this base shifts both ports; software that
hard-codes 02h-style port numbers must be rebuilt or patched.
# SIO.A.baud
SIO.A.baud=<baud>
Imposes a software baud-rate ceiling on SIO Port A. The value is a
decimal baud rate -- e.g. SIO.A.baud=9600 -- and it works by
delaying every character through Port A by the equivalent of
10/baud seconds (10 bits per character, the worst-case framing).
The throttle is purely software: the underlying hardware UART still
runs at whatever UART0 configured, so the terminal on the wire
sees no change. What does change is what the CPU sees -- a Z80 / 8080
program polling the SIO status will observe characters arriving and
the transmit-ready flag clearing at the rate you set, which matters
when you are running CP/M software written for a real 9600-baud
terminal that loses data when characters arrive faster than its
echo buffer can handle. If the directive is absent or set to zero,
no throttle is applied and characters move at full firmware speed.
# SIO.B.baud
SIO.B.baud=<baud>
Same throttle as SIO.A.baud, applied to SIO Port B. Note that this
directive has no effect when FDC.transport=UART -- in that
configuration the SIO Port B emulation is suppressed entirely
because UART1 has been claimed by the floppy controller. If you
configure both, the floppy controller wins and the throttle is
silently ignored until you switch transport back.
# Other I/O port bases
The directives below all set the I/O port base for an emulated
device. Each accepts default (the value listed) or a hex/decimal
port number; each is a one-line entry in the same form as the
others. The directive must be present for the device to be
registered. Deleting an entry does not move the device to a
default port -- it removes the device from the I/O map entirely.
That is intentional and lets you turn devices off cleanly, but it
also means accidentally commenting out a line silently disables the
device. The defaults are listed both in the showcase sample above
and below.
Port-base collisions silently drop the second device
If two devices end up assigned to the same base port, the firmware logs an error and skips registration of the second one; the first device wins. Always verify ranges do not overlap when moving bases.
# DZLR.iobase
DZLR.iobase=default|<port>
Default 0Eh. Registers the Cromemco Dazzler at base+00h and
base+01h. Move this if you have a real Dazzler board on the bus
that you want the SDH-100 to leave alone, or if your software
expects the Dazzler at a non-standard port.
# D7AIO.iobase
D7AIO.iobase=default|<port>
Default 18h. Registers the eight-port digital/analogue I/O block,
spanning base+00h through base+07h. The eight-port footprint is
fixed; the base can be moved but the span cannot be reduced. If the
S-132 is attached, D7AIO traffic at this port is also routed to it
by default.
# TIMER.iobase
TIMER.iobase=default|<port>
Default A0h. Registers the interval-timer / hardware control port
at a single port address. The port carries the periodic interrupt
control bits and is a common target for CP/M time-of-day patches.
# SDH.iobase
SDH.iobase=default|<port>
Default A1h. Registers the SDH-100 host co-routine command port,
which CP/M hosted on the SDH-100 uses to issue management commands
(disk eject, system map query, etc.) into the firmware. Moving this
breaks any pre-built CP/M binary that uses the co-routine; only move
it when you have a specific port conflict and a way to rebuild the
client.
# APU.iobase
APU.iobase=default|<port>
Default A2h. Registers the Am9511 floating-point APU at
base+00h (data) and base+01h (status). Software that uses the
APU expects a fixed port pair, so move this only when something else
on the bus already occupies A2h-A3h.
# FIF.iobase
FIF.iobase=default|<port>
Default FDh. Registers the IMSAI FIF floppy controller's command
port. The FIF emulator interacts with whichever transport
FDC.transport selects -- moving the base does not change which
transport is active, only the port the CPU writes commands to.
# MMU.iobase
MMU.iobase=default|<port>
Default 40h. Registers the IMSAI MMU bank-switch register at a
single port address. The MMU register switches the physical bank in
which the lower 64 KB of address space is served from emulated
shadow RAM, so software that uses banked memory expects this port to
be present.
# RTC.iobase
RTC.iobase=default|<port>
Default 41h. Registers the DS3231 real-time clock interface at a
two-port window: a command register at the base address and a data
register at base+1. Removing the directive removes the RTC from the
I/O map; software polling for it sees no response, which is the
correct behaviour when the on-board RTC is not populated.
# USB host integration
# USB.disk
USB.disk=AUTOMOUNT
Set to AUTOMOUNT to make the SD card visible as a USB mass-storage
volume whenever a host is connected. Without this directive the
firmware leaves the MSC volume ejected once boot is finished, which
is the right behaviour when the SD card is being read concurrently
by the running CP/M system and you want to avoid the host operating
system caching a stale view of it. With AUTOMOUNT, the volume is
re-presented after every soft reset and after the SDH-100 finishes
its boot. Use it when you are actively iterating on boot.conf,
disk images, or HEX files from a laptop and do not need the bus to
be writing to the card concurrently. The directive accepts
AUTOMOUNT only; any other value disables auto-mount.
# USB.tty
USB.tty=PASS
Set to PASS to bridge the USB CON CDC channel to and from SIO
Port A. With this directive, characters typed into the host's CON
terminal (most often a screen or picocom session) appear on Port
A as if they came from a wired terminal, and characters the CPU
writes to Port A are forwarded to the host CON terminal in addition
to the physical UART0 path. This lets you use a USB-only host as the
operating console without a serial cable. The pass-through is
bidirectional; you do not get to choose direction. With no value or
any value other than PASS, the bridge is off and the CON channel
is monitor / log output only.
# USB.uc1
USB.uc1=PASS
Set to PASS to bridge the USB AUX CDC channel to and from SIO
Port B in the same way USB.tty=PASS bridges Port A. Useful when
you want a second host-side terminal for a CP/M session that has a
secondary console -- for example, a debugger on Port B while normal
output runs on Port A. The directive is mutually exclusive with
FDC.transport=UART: the floppy controller and the AUX bridge
cannot share UART1. If both are set, the floppy controller wins and
the AUX bridge becomes inactive.
# USB.debug
USB.debug=TRUE
Set to TRUE to enable boot-log entries for USB connect and
disconnect events on the CON, AUX, MON, and FDC CDC channels. This
is the directive to enable when you suspect a host-side cable or
driver problem is making the SDH-100 misbehave. The extra log
entries are visible on whichever channel you are watching the boot
log on (and on the runtime MON channel), and they carry the channel
name and the new state so you can correlate them with what the host
did. Leave it disabled in normal use -- the log is otherwise quiet
about USB traffic.
# Monitor
# MON.break
MON.break=NMI
Selects how the SDH-100 monitor breaks into a running CPU for the
R, G, I, O, T, U, and W commands. The default behaviour (directive
absent or any other value) is to break in via INT and inject an RST
7 opcode on the INTA cycle, which jumps the CPU to 0x0038 and onto
the firmware's interrupt trampoline. Setting MON.break=NMI
switches the break-in path -- both for direct R, G, I, O commands
and for W watch fires -- to the Z80 NMI input: the firmware drives
the NMI line, the CPU vectors to 0x0066 where a 2-byte JR redirects
into the same trampoline at 0x0008, and the rest of the break-in
proceeds identically. The directive is silently ignored on 8080
systems because the 8080 has no NMI input -- a warning is logged at
boot to flag the misconfiguration. The advantage of the
NMI path is that it is non-maskable: a Z80 program that has
executed DI (or is mid-block-move with EI deferred, or in a
tight ISR) can still be broken into via R/G/I/O or by a watch
fire. The 2-byte JR overlay at 0x0066 sits inside CP/M's default
FCB range (0x005C-0x007F). For direct R/G/I/O it is only live for
the tens of microseconds between bus release and the CPU running
the trampoline -- programs reading the default FCB during that
window are extraordinarily unlikely to hit it. For W watches, the
overlay is live for the entire armed period (which can last
arbitrarily long while the CPU runs waiting for the watch to fire),
and the firmware refreshes it every time it acquires the bus to
sample the CPU; this is the trade-off MON.break=NMI opts into in
exchange for being able to watch DI'd code. Leave the directive
unset unless you have a concrete need to break into DI'd Z80 code.
# Common pitfalls and interactions
A few of the boot.conf directives interact in ways that surprise people the first time. The notes below collect them in one place.
Every .iobase line is non-optional. If you delete SIO.iobase
or LPT.iobase or any of the entries listed in Other I/O port
bases, you do not get the default port -- you get no device at
all. Use KEY=default to take the default; only remove the line
entirely if you actually want that device gone.
FDC.transport=UART disables SIO Port B. Three things lose their
function in this mode: SIO Port B emulation, any software targeting
it, and USB.uc1=PASS. Anything you set on UART1 -- baud, parity,
framing -- is overridden by the 2 000 000-baud, 8N1 configuration
the floppy controller insists on. Switch back to
FDC.transport=USB if Port B is needed.
BOOT_JUMP runs after ROM_LOAD. If your HEX file lays code at
0000h, BOOT_JUMP will overwrite the first three bytes of it with
the jump. That is usually what you want when the HEX is a high
ROM image and 0000h was just collateral; check the layout if it
is not.
RAM_MAP=AUTO interacts with both physical hardware and
ROM_LOAD. The auto-fill happens after the bus is scanned for
physical memory, so any S-100 RAM card you have plugged in claims
its address range first; only pages that are still empty become
emulated RAM. The same applies to ROM pages claimed by ROM_LOAD.
RAM_SET only fills emulated RAM. Pages backed by real RAM
cards keep whatever was already there. ROM pages keep the HEX
contents. If you RAM_SET=0xFF and then dump page 00h and find
non-FF data, the page is on a physical RAM card, not on the
SDH-100.
MEM_TEST=LOOP on its own does nothing. The test only runs if
the value contains the substring RUN. Use RUN for a single pass
or RUNLOOP for a continuous loop; do not use LOOP alone.
Inline comments end at #. Everything from the first # on a
value line is discarded. This is convenient for annotating values
but it means a value cannot contain a literal #. None of the
directives the firmware honours need one, so this is rarely a
limit in practice.
A typo is a missing setting, not an error. The parser does not
warn on unknown keys. If a directive seems to have no effect, check
the spelling and case before assuming a firmware bug -- Fdc.transport
and FDC.Transport both produce silently-ignored entries.