Rom Filing System — Developer's Guide

RFS Developer's Guide

This guide is a detailed walkthrough of the RFS source code and development environment. It explains Z80 assembly language concepts for developers who may not be familiar with the language, walks through every source module, documents the bank-switching architecture, and shows how to add new commands, modify existing modules, and port RFS to new hardware.
For hardware architecture and build system details see the Technical Guide. For user-facing operation see the User Manual.

Introduction to Z80 Assembly for Non-Assembly Programmers

The entire RFS firmware is written in Z80 assembly language — the native instruction language of the Zilog Z80 processor used in the Sharp MZ series. Unlike high-level languages, assembly maps almost directly to the physical hardware: every instruction translates to one or a few bytes that the CPU executes directly.

Registers
The Z80 has no "variables" — instead it has a small set of registers (fast storage locations inside the CPU). The most commonly used ones in RFS:
Register Size Role
A 8-bit Accumulator — the primary register for arithmetic, logic, and I/O operations. Almost every instruction involves A.
B, C 8-bit General purpose. BC together forms a 16-bit pair, commonly used as a loop counter or byte count.
D, E 8-bit General purpose. DE together is a 16-bit pair, commonly used as a source or destination pointer.
H, L 8-bit General purpose. HL together is the main 16-bit memory pointer — most memory read/write instructions use HL.
IX, IY 16-bit Index registers — used for base+offset memory addressing. Slower than HL but convenient for structured data.
SP 16-bit Stack Pointer — points to the top of the call stack. PUSH and POP use SP automatically.
PC 16-bit Program Counter — the address of the current instruction. Incremented automatically; modified by jumps and calls.
F 8-bit Flags register — individual bits set by arithmetic operations: Z (zero), C (carry), S (sign), P/V (parity/overflow).

Key Instructions
  • LD dest, src — Load (copy) data. LD A, B copies B into A. LD A, (HL) reads the byte at the memory address held in HL into A. LD (0x1200), A writes A to memory address 0x1200.
  • CALL addr — Call a subroutine. Pushes the return address (next instruction) onto the stack and jumps to addr. Equivalent to a function call.
  • RET — Return from subroutine. Pops the return address off the stack and jumps to it.
  • JP addr — Unconditional jump to addr. JP Z, addr jumps only if the Zero flag is set (i.e. the last operation produced zero).
  • JR offset — Short relative jump (−128 to +127 bytes). Faster and more compact than JP for nearby branches.
  • DJNZ offset — Decrement B and jump if Not Zero. The canonical Z80 loop instruction: LD B, 10 / LOOP: ... / DJNZ LOOP repeats 10 times.
  • ADD A, n — Add n to A. SUB n subtracts. AND n, OR n, XOR n — bitwise logic on A.
  • IN A, (port) — Read from I/O port into A. OUT (port), A — write A to I/O port. These are how the Z80 communicates with hardware (the WD1773, SPI controller, bank latch, etc.).
  • PUSH rr / POP rr — Save/restore a 16-bit register pair to/from the stack.
  • EI / DI — Enable / Disable interrupts. Code that must not be interrupted (e.g. time-critical tape operations) is wrapped between DI and EI.

Addressing Modes
The Z80 offers several ways to specify where data comes from or goes to:
  • Immediate: LD A, 42 — the value is embedded in the instruction bytes themselves.
  • Register: LD A, B — data comes from or goes to a register.
  • Indirect (via HL): LD A, (HL) — HL contains a memory address; data is read from that address.
  • Extended (direct address): LD A, (0x1200) — the address is a literal 16-bit constant in the instruction.
  • Indexed: LD A, (IX+5) — IX holds a base address; 5 is added to get the effective address. Used in RFS for accessing fields within fixed-format data structures.

GLASS Assembler Syntax
RFS uses the GLASS Z80 assembler. Key syntax features:
  • Comments begin with ; — everything to the right of a semicolon is ignored.
  • Labels are identifiers followed by :. A label at the start of a line names the address of the next instruction.
  • EQU defines a constant: BELL EQU 007H — the assembler replaces every occurrence of BELL with 0x07.
  • DB (Define Byte) inserts raw bytes: DB 0x41, 0x42 emits two bytes. Used for strings and lookup tables.
  • DW (Define Word) inserts 16-bit little-endian values: DW HANDLER emits the address of the HANDLER label.
  • ORG addr sets the assembly origin — subsequent code is assembled as if it lives at addr.
  • INCLUDE "file.asm" textually includes another file at the current position.
  • IF / ENDIF conditional assembly: IF BUILD_SFD700 = 1 ... ENDIF — the enclosed instructions are only assembled when the condition is true. This is how RFS builds four different firmware variants from one source tree.

Source Tree

Path Contents
asm/ All Z80 assembly source files
asm/include/ Shared definitions and configuration files
asm/dis/ Disassembled reference files for SA-5510 and XPATCH
tools/ Build scripts, GLASS assembler, utility binaries
MZF/ MZF format application files, organised by machine type
MZB/ Sector-padded binary applications (generated by build)
roms/ Build output — ROM images and SD card images
releases/ Pre-built release binaries
config/ CP/M disk format definitions (diskdefs)
cpmtools/ cpmtools source (submodule)
src/ Source for supporting tools

Configuration: rfs_definitions.asm

This is the central configuration file, included by every other source file via INCLUDE "rfs_definitions.asm". Every assembly-time option is controlled here. The key sections:

Build Target Flags
HW_SPI_ENA    EQU 1    ; 1 = hardware SPI on RomDisk v2+ PCB
SW_SPI_ENA    EQU 0    ; 1 = software bit-bang SPI (RomDisk v1)
PP_SPI_ENA    EQU 0    ; 1 = SPI via parallel port (RomDisk v1 alternative)
FUSIONX_ENA   EQU 0    ; 1 = running on tranZPUter FusionX
KUMA80_ENA    EQU 0    ; 1 = Kuma 40/80 upgrade present
VIDEOMODULE_ENA EQU 0  ; 1 = 40/80 colour video module present
BUILD_ROMDISK EQU 0    ; 1 = build for RomDisk card
BUILD_SFD700  EQU 0    ; 1 = build for SFD-700 floppy interface
BUILD_PICOZ80 EQU 1    ; 1 = build for picoZ80 board
ENADEBUG      EQU 0    ; 1 = enable debug output during assembly
Exactly one BUILD_* flag must be set to 1 at a time. All conditional assembly blocks throughout the source test these flags to include or exclude platform-specific code.

Address Constants
UROMADDR    EQU 0E800H   ; Base address of the User ROM window
UROMBSTBL   EQU UROMADDR + 020H   ; Bank-switch table entry point (fixed offset)
RFSJMPTABLE EQU UROMADDR + 0B0H   ; RFS jump table start
FDCROMADDR  EQU 0F000H   ; FDC ROM address (SFD-700 MROM location)

; SFD-700 specific bank defaults (only assembled when BUILD_SFD700 = 1):
BNKDEFMROM_MZ80A EQU 0   ; Default MROM bank for MZ-80A (AFI ROM)
BNKDEFMROM_MZ700 EQU 1   ; Default MROM bank for MZ-700 (AFI ROM)
BNKDEFUROM       EQU 2   ; Default UROM bank for RFS (starts at 8KB in Flash)
These constants define where in the Z80 address space each ROM window sits. Code compiled for the User ROM window always assembles with ORG 0xE800; code for the Monitor ROM window assembles at ORG 0x0000.

Character and Control Definitions
Standard ASCII control characters are defined as named constants to make the source self-documenting:
BELL    EQU 007H    ; Terminal bell
CR      EQU 00DH    ; Carriage return
LF      EQU 00AH    ; Line feed
CS      EQU 00CH    ; Clear screen
SPACE   EQU 020H    ; ASCII space
DELETE  EQU 07FH    ; Delete key

Bank Switching in Detail

Bank switching is the heart of the RFS architecture. Understanding it is essential before modifying any source file.

Why Banking is Needed
The Sharp MZ-80A gives User ROM only 2 KB of address space (0xE800–0xEFFF). 2 KB can hold only a few hundred instructions — nowhere near enough for a filing system, assembler, disassembler, tape controller, SD card driver, and CP/M CBIOS. The solution is to physically switch which 2 KB of a 512 KB Flash chip is visible at that address range. By storing 12 different 2 KB programs (banks) in the Flash chip and switching between them on demand, RFS achieves effectively 24 KB of ROM code.

The Bank-Switch Stub
Every bank begins with an identical copy of the bank-switching stub occupying the first 32 bytes of the bank (0xE800–0xE81F). This stub provides:
  • A standard call gateway: Any bank can call any routine in any other bank by calling the stub with the target bank number and target address. The stub writes the bank number to the hardware latch (typically an I/O port write), then calls the requested address. The callee runs in the new bank, and when it returns, the stub switches back to the original bank.
  • A consistent entry point: Because the stub is at a fixed offset (0xE800 + 0x20 for the bank-switch table), code in bank 0 can reliably find the stub in bank 3 even though it has never seen bank 3's internal addresses.
The coded latch on RomDisk v2+ boards adds a protection mechanism: the bank latch register only becomes writable after the CPU reads from the upper 8 bytes of User ROM space (0xEFF8–0xEFFF) a specific number of times in sequence. This prevents accidental bank switches caused by stray code that happens to write to the latch address. The stub handles this unlock sequence before every bank switch.

Command Table Format (rfs.asm)
The monitor command dispatcher in rfs.asm uses a compact command table. Each entry describes one command and is laid out as follows:
; One command table entry:
;
;   DB  FLAGS          ; 1 byte: END|MATCH|BANK[5:3]|SIZE[2:0]
;   DB  "COMMAND"      ; SIZE bytes: the command string (no null terminator)
;   DW  HANDLER_ADDR   ; 2 bytes: address of the handler routine in the named bank
;
; Flags byte:
;   Bit 7 = 1: End of table marker (last entry).
;   Bit 6 = 1: Exact match required (command must be entire input, no trailing chars).
;   Bits 5:3   Bank number where HANDLER_ADDR lives (0–7 for RFS, 8–11 for CBIOS).
;   Bits 2:0   Length of the command string in bytes.
;
; Example — the ASM command (SFD700 build only, lives in bank 7, 3-char string):
CMDTABLE2:
    DB  000H | 000H | 038H | 003H    ; FLAGS: not-end, not-exact, bank 7, length 3
    DB  "ASM"                         ; Command string
    DW  ASM_MAIN                      ; Handler address in bank 7
The dispatcher reads the monitor input line, walks the table, and for each entry:
  1. Compares the input against the command string (case-insensitive on some builds).
  2. If matched, extracts the bank number and handler address from the table entry.
  3. Performs a bank switch to the target bank.
  4. Calls the handler with any remaining input (parameters) available in the monitor input buffer.
Two separate command tables exist: CMDTABLE2 for the SFD-700 build, and CMDTABLE for the RomDisk/picoZ80 build. Both are structured identically but contain different command sets.

Module Walkthroughs

rfs.asm — Command Dispatcher (User ROM Bank 0)
Role: The entry point for all RFS functionality. When the SA-1510 monitor does not recognise a command, it passes control to the User ROM entry point at 0xE800. This is always bank 0.
Key sections:
  • Bank-switch stub (0xE800–0xE81F): The cross-bank call gateway described above. Every bank has an identical copy.
  • Bank-switch table (0xE800 + 0xB0): A jump table that maps bank numbers to their physical Flash ROM addresses. Modified at boot time if the hardware requires non-sequential bank addressing.
  • Command table (CMDTABLE / CMDTABLE2): The list of all RFS commands with their bank and handler address.
  • Main dispatcher loop: Reads the monitor's input buffer, walks the command table, performs the bank switch and calls the handler. If no command matches, returns to the SA-1510 monitor so it can print the "?" error.
  • RFS initialisation: On first entry after reset, RFS detects the hardware platform (from the MODE register on SFD-700, or from flags on RomDisk), initialises the SPI and SD card, and sets the initial drive to 0.
SFD-700 note: When BUILD_SFD700 = 1, rfs.asm assembles with ORG 0xE000 / ALIGN 0xE300 rather than ORG 0xE800, because the SFD-700 maps its User ROM window at 0xE300–0xEFFF (0xE000–0xE2FF is reserved for MZ-700 memory-mapped I/O).

rfs_bank1.asm — Floppy Disk Controller (User ROM Bank 1)
Role: Implements floppy disk boot commands. On the RomDisk / picoZ80 build this bank provides the full F (drive-select boot) and f (direct AFI jump) commands. On the SFD-700 build only FDCK is assembled — the SFD-700 has its own AFI boot ROM at 0xF000, so the F command simply verifies it is present and hands off to it directly; the elaborate drive-select and motor-wait logic is not needed.
Key functions:
  • FLOPPY (RomDisk / picoZ80 only): Prompts for a drive number (if not supplied on the command line), then writes the drive select and motor enable bytes to the floppy controller's I/O port 0xDC. Waits for the spindle motor to reach speed (typically 300–500 ms), reads the boot sector, verifies the bootable-disk signature, and passes control to the boot loader.
  • FDCK (all builds including SFD-700): Reads the byte at 0xF000 to verify the AFI ROM is present and non-zero, then calls 0xF000 directly. On RomDisk / picoZ80 builds this is the f command (lowercase); on the SFD-700 build it is also the F command, because the SFD-700 AFI ROM handles all drive-select logic internally.
  • At the end of the bank, an ALIGN 0xF000 directive ensures the SFD-700 ROM image positions the AFI boot ROM precisely at 0xF000 in the Flash layout.

rfs_bank2.asm — SD Card Controller (User ROM Bank 2)
Role: The complete SD card subsystem — SPI initialisation, SD card command protocol, and the SDCFS directory and file I/O routines. On the SFD-700 build, the SD card controller code is not assembled (the SFD-700 hardware has no SD card interface); the bank slot is present in the ROM image but contains only the bank-switching stub.
Key functions:
  • SPI driver (hardware or software): Conditional assembly selects between hardware SPI (using the RomDisk v2 SPI controller registers) and software bit-bang SPI (toggling individual I/O port bits to clock the SPI bus). The hardware SPI path is substantially faster and used on all current boards (HW_SPI_ENA = 1).
  • SD card initialisation (SDINIT): Implements the SD card initialisation sequence — sends CMD0 (GO_IDLE), CMD8 (SEND_IF_COND), ACMD41 (SD_SEND_OP_COND) to switch the card from SPI mode to the active state. Handles both SD and SDHC/SDXC card types by checking the OCR response.
  • Sector read (SDREAD): Sends CMD17 (READ_SINGLE_BLOCK) with a 32-bit sector address, waits for the data start token (0xFE), then reads 512 bytes into a Z80 RAM buffer. Uses hardware SPI burst mode where available.
  • Sector write (SDWRITE): Sends CMD24 (WRITE_BLOCK), the data start token, 512 bytes of data, and the CRC. Waits for the write response and busy signal to clear.
  • SDCFS directory read (SDDIR): Reads the directory from the first 8 KB of the active drive image and builds a RAM-resident directory cache used by IC, LC, SC, and EC commands.
  • SDCFS file load (SDLOAD): Given a file number from the directory, calculates the sector address of the file's 64 KB block, reads the actual file size bytes, and loads them to the Z80 address specified in the directory entry's LOAD ADDR field.
  • SDCFS file save (SDSAVE): Allocates a new directory slot (or finds an existing entry with the same name to overwrite), sets the START SECTOR, SIZE, LOAD ADDR, and EXEC ADDR fields, then writes the file data to the appropriate 64 KB block.

rfs_bank3.asm — Memory Utilities (User ROM Bank 3)
Role: Implements the D (hex dump), M (memory edit), and CP (memory copy) commands, which are available on all builds. The T2SD (tape to SD) and SD2T (SD to tape) commands are also implemented here but are only assembled for the RomDisk / picoZ80 builds — the SFD-700 build excludes them as there is no SD card.
Hex dump (D): Reads up to 20 lines of 16 bytes each from the target address range. For each line it prints the 4-digit hex address, 16 hex byte values (with a space between every 4 bytes), and the 16 ASCII characters (using a dot for non-printable bytes). The Sharp MZ uses an unusual character encoding — bank 6 provides the Sharp-to-ASCII conversion table used here.
Memory editor (M): Presents each byte in turn, showing the address and current value. The user can type a new hex value (1 or 2 digits) and press Enter to write it, or press Enter alone to leave it unchanged. Pressing Ctrl+C or a specific escape sequence exits.
T2SD and SD2T: These commands perform transparent bidirectional copy between tape (CMT) and the SD card. T2SD calls the CMT load routine from bank 4 to read a tape file into RAM, then calls the SD save routine from bank 2 to write it to the active drive. SD2T calls the SD load routine from bank 2 to put the file into RAM, then calls the CMT save routine from bank 4 to write it to tape. Both directions use the SDCFS directory to maintain filenames, sizes, and addresses.

rfs_bank4.asm — CMT Controller (User ROM Bank 4)
Role: Implements the L/LT, LTNX, S/ST, and V tape (CMT) commands.
The Sharp MZ-80A uses a 1200 baud Kansas City Standard cassette interface. Bytes are encoded as bursts of 1200 Hz (bit 0) or 2400 Hz (bit 1) tone. The tape routines are time-critical — they must read or write each bit within a tight timing window. They use the 8253 timer chip (or CPU cycle-counted loops on platforms without the timer) to measure the incoming tone frequency and generate the outgoing waveform. Interrupts are disabled (DI) throughout tape operations to prevent timing disruption.
The MZF tape format prefixes every program with a 128-byte header containing the file type, filename, data length, load address, and execution address — the same fields stored in the SDCFS directory entry. This is why SD↔tape copy is transparent: the header format is identical.

rfs_bank5.asm — Utility Functions (User ROM Bank 5)
Role: A library of shared routines called by other banks. Because bank-switching is expensive (requires the unlock sequence on v2+ boards), frequently used routines are centralised here to minimise switching overhead.
Key routines:
  • PRTHEX: Prints the A register as two hex digits to the screen.
  • PRTHL: Prints the HL register as four hex digits.
  • PRTSTR: Prints a null-terminated string from (HL) to the screen, handling the Sharp character encoding conversion.
  • INPHEX: Reads a hex number (up to 4 digits) from the keyboard, returning the value in HL.
  • STRCMP: Compares two null-terminated strings.
  • SUBSTR: Extracts a substring, used by the command dispatcher to separate command names from parameters.
  • WAITKEY: Waits for a keypress and returns the key code in A. Used for "press any key to continue" pauses in IC and IR directory listings.

rfs_bank6.asm — Help and Strings (User ROM Bank 6)
Role: Stores the paginated help screen text, all error message strings, and the Sharp MZ character set to ASCII conversion table.
The Sharp MZ series uses a proprietary 8-bit character encoding where many printable characters have different code points than ASCII. The conversion table in this bank maps Sharp character codes to their ASCII equivalents, used when displaying filenames (stored in Sharp encoding) as ASCII text on screen.
The help screen is stored as a sequence of null-terminated strings (one per line). The H command prints them with automatic pagination, calling the WAITKEY routine from bank 5 at each screenful boundary.

rfs_bank7.asm — Assembler / Disassembler (User ROM Bank 7)
Role: Implements the ASM and DASM commands (SFD-700 build), the R DRAM test, and the T timer test.
Interactive assembler (ASM): Presents a line-input prompt at the target address. The user types Z80 mnemonics (e.g. LD A, 42) which are parsed, assembled to machine code bytes, and written directly to the target address in RAM. The address advances by the size of each assembled instruction. This allows assembly of small routines directly on the hardware without an external PC.
Disassembler (DASM): Reads machine code bytes from the target address, decodes each instruction (using a lookup table of Z80 opcode mnemonics), and prints the address, hex bytes, and mnemonic for each instruction. Handles all Z80 prefix bytes (CB, DD, ED, FD) and extended instructions. This is a full Z80 disassembler in 2 KB of ROM — a remarkable feat of compact code.
DRAM test (R): Performs a walking-bit pattern write/verify across the full user RAM space (0x1200–0xCFFF). Reports any failed addresses. Useful for diagnosing faulty RAM chips — a common failure mode in vintage machines.

rfs_mrom.asm — Monitor ROM Utilities (Monitor ROM Bank 3)
Role: Provides the ROM scanning and MZF file load routines that must run from Monitor ROM space rather than User ROM space.
Why a separate Monitor ROM bank? The IR and LR commands need to enumerate MZF files stored in the User ROM Flash chips (User ROM banks beyond 11 hold packed MZF programs). To scan a User ROM bank, the CPLD must switch the User ROM window to point at that bank. But the scanning code itself lives in the User ROM — if it switches the User ROM bank, it instantly replaces itself with a different bank and crashes.
The solution is to place the scanning loop in Monitor ROM bank 3. Monitor ROM bank switching is independent of User ROM bank switching. The MROM scanning routine can freely switch User ROM banks (to enumerate each bank's MZF header list) without disturbing its own execution context.
Key functions:
  • ROMDIR: Scans all User ROM banks above 11, reads each MZF header, and builds a RAM-resident ROM directory used by the IR command.
  • ROMLOAD: Given a file number from the ROM directory, reads the MZF data from the appropriate User ROM bank into the LOAD ADDR specified in the header, then optionally jumps to EXEC ADDR.

CP/M CBIOS Modules
The four CBIOS User ROM banks (8–11) and the CBIOS Monitor ROM bank (2) together implement the complete CP/M 2.2 CBIOS. Each bank provides one subsystem:
cbios.asm (Monitor ROM bank 2): The CBIOS entry point table — all 17 CP/M API vectors (BOOT through SECTRN) are jump addresses within this module. Also holds the disk parameter tables (DPH, DPB structures that tell CP/M the geometry of each disk drive), the cold/warm boot sequences, and the ROM disk controller (reads sectors from User ROM Flash).
cbios_bank1.asm (User ROM bank 8): Audio output (bell tone and melody using the MZ-80A's 8255 PPI and buzzer), real-time clock routines using the 8253 timer, and keyboard input with auto-repeat (key held for > 500 ms repeats at ~10 Hz, matching the behaviour users expect from a modern keyboard).
cbios_bank2.asm (User ROM bank 9): Screen driver — character output at the current cursor position, scrolling, clear screen, cursor positioning. Also the ANSI terminal emulator: a state machine that recognises VT52/VT100 escape sequences (CSI codes) and translates them into the equivalent MZ-80A screen operations. This makes CP/M applications that assume a smart terminal (WordStar, Turbo Pascal, etc.) work correctly without any modification to those applications.
cbios_bank3.asm (User ROM bank 10): SD card disk driver for CP/M. Translates CP/M 128-byte sector read/write requests into SDCFS operations on the CP/M disk images stored after the 256 MB boundary on the SD card. Includes the sector skew table used by SECTRN to improve disk access performance.
cbios_bank4.asm (User ROM bank 11): Floppy disk controller for CP/M. Uses the WD1773 (via SFD-700) or equivalent floppy controller to service CP/M disk read/write requests for physical floppy drives. Data is read un-inverted (unlike the MZ-80A AFI ROM which uses inverted data) because the CBIOS writes its own re-inverted format.

Adding a New Monitor Command

To add a new command FOO that lives in User ROM bank 3 (memory utilities):
  1. Write the handler in rfs_bank3.asm: Add a labeled routine FOO_CMD: that implements the command. Parameters are available in the monitor input buffer (HL points to the first character after the command name). Return with RET when done.
  2. Add an entry to the command table in rfs.asm: In the appropriate CMDTABLE (or CMDTABLE2 for SFD-700), add:
; FLAGS: not-end (bit7=0), not-exact (bit6=0), bank 3 (bits5:3 = 011 = 0x18), length 3 (bits2:0 = 011)
    DB  000H | 000H | 018H | 003H
    DB  "FOO"
    DW  FOO_CMD
  1. Update the help text in rfs_bank6.asm: Add a line describing the new command to the help screen text strings.
  2. Rebuild: Run ./build.sh. The assembler will report any size overflows if bank 3 is now larger than 2 KB — in that case, remove or compress other code in that bank.

Adding a New Hardware Platform

To port RFS to a new hardware target:
  1. Add a build flag in rfs_definitions.asm: BUILD_NEWBOARD EQU 0 (set to 1 when building for the new target).
  2. Add address constants if the new hardware maps ROM windows at different addresses.
  3. Add conditional assembly blocks throughout the source where hardware-specific behaviour differs — I/O port addresses for the bank latch, SPI controller register layout, SD card presence detection, etc. Follow the pattern of existing IF BUILD_ROMDISK = 1 ... ENDIF blocks.
  4. Add a new build script (or extend make_roms.sh) to package the ROM image for the new target's Flash chip layout.
  5. Update the prerequisite check in build.sh if the new target needs additional tools.

Debugging Tips

Enable debug output: Set ENADEBUG EQU 1 in rfs_definitions.asm before building. This includes additional diagnostic output at strategic points in the SD card initialisation and SDCFS routines.
Use the built-in disassembler: On SFD-700 builds, type DASM addr to disassemble assembled code in RAM. This is invaluable for verifying that a newly assembled routine was correctly encoded by the ASM command.
Use the hex dump: D E800 shows the first 320 bytes of the current User ROM bank — useful for verifying that the bank latch is selecting the expected bank.
DRAM test after any RAM modification: Type R to run a comprehensive DRAM test whenever adding new RAM-resident data structures. This catches addressing bugs early before they appear as mysterious crashes.
Bank-switch guard: On RomDisk v2+ boards, never place a DJNZ or any other looping instruction that spans 0xEFF8–0xEFFF. The coded latch interprets reads from that range as the unlock sequence and may switch banks unexpectedly.

Reference Sites

Resource Link
RFS project page /sharpmz-upgrades-rfs/
RFS User Manual /sharpmz-upgrades-rfs-usermanual/
RFS Technical Guide /sharpmz-upgrades-rfs-technicalguide/
RFS Gallery /sharpmz-upgrades-rfs-gallery/
SFD-700 mkII Developer’s Guide /sfd700-developersguide/
picoZ80 Developer’s Guide /picoz80-developersguide/
GLASS Z80 Assembler Bundled in tools/glass.jar
Zilog Z80 CPU User Manual Standard datasheet — bus timing, instruction set, register reference
CP/M 2.2 Alteration Guide Digital Research — CBIOS design reference