tranZPUter Filing System — Developer's Guide

TZFS Developer's Guide

This guide is a detailed walkthrough of the TZFS (tranZPUter Filing System) 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 TZMM bank-switching architecture, and shows how to add new commands, modify existing modules, and port TZFS to new hardware platforms.
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 TZFS 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 TZFS:
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 MMCFG mode register, K64F service port, 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 TZFS for accessing fields within fixed-format data structures such as the K64F service block.

GLASS Assembler Syntax
TZFS 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: MMCFG EQU 060H — the assembler replaces every occurrence of MMCFG with 0x60.
  • 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_FUSIONX = 1 ... ENDIF — the enclosed instructions are only assembled when the condition is true. This is how TZFS builds platform-specific firmware variants from one source tree.

Source Tree

Path Contents
asm/ All Z80 assembly source files
asm/tzfs.asm Bank 0: primary entry point, cold-start initialisation, command dispatcher, jump tables
asm/tzfs_bank2.asm Bank 1 (TZMM_TZFS2): messages, help screen, print routines, Sharp↔ASCII conversion
asm/tzfs_bank3.asm Bank 2 (TZMM_TZFS3): memory utilities, I/O port R/W, tape compensation, CPU/emulation commands
asm/tzfs_bank4.asm Bank 3 (TZMM_TZFS4): full Z80 assembler and disassembler (occupies 52 KB of user RAM)
asm/include/ Shared definitions and configuration files
asm/include/tzfs_definitions.asm All configuration constants and I/O port definitions
asm/include/tzfs_variables.asm Z80 variable declarations
asm/include/tzfs_mondef.asm Monitor-specific definitions
asm/include/tzfs_svcstruct.asm K64F service command and structure definitions
asm/include/tzfs_utilities.asm Inline utility macros
asm/include/macros.asm Common macros shared across modules
asm/monitor_SA1510.asm SA-1510 monitor firmware for the MZ-80A
asm/monitor_80c_SA1510.asm SA-1510 patched for 80-column mode
asm/monitor_1Z-013A.asm MZ-700 1Z-013A monitor firmware
asm/monitor_80c_1Z-013A.asm 1Z-013A patched for 80-column mode
asm/MZ80B_IPL.asm MZ-80B IPL (Initial Program Loader)
build.sh Top-level build script
tools/ Build tools including glass.jar (GLASS Z80 assembler)
config/ CP/M disk format definitions
releases/ Pre-built release binaries

Configuration: tzfs_definitions.asm

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

Build Target Flags
BUILD_FUSIONX EQU 0    ; 1 = running on tranZPUter FusionX hardware
BUILD_PICOZ80 EQU 1    ; 1 = running on picoZ80 board
ENADEBUG      EQU 0    ; 1 = enable additional diagnostic output during operation
The combination of BUILD_FUSIONX and BUILD_PICOZ80 controls which command set is assembled. When both are 1 (FusionX + picoZ80 combined build), the MZ hardware emulation commands (MZ80K, MZ700, etc.), the CPU switching commands (T80, Z80, ZPU), and the video control commands (VBORDER, VMODE, VGA) are excluded, because those commands are not meaningful on that combined target.

Address Constants
UROMADDR    EQU 0E800H   ; Base address of the TZFS entry point (User ROM window)
TZVARMEM    EQU 0EC80H   ; TZFS variable block (WARMSTART flag, model, tape comp. value)
TZSVCMEM    EQU 0ED80H   ; K64F service communication block (shared memory API)
BANKRAMADDR EQU 02000H   ; Base address for bank4 assembler/disassembler tables
The most important address distinction in TZFS: UROMADDR (0xE800) is the fixed entry point that the native monitor calls, while TZVARMEM (0xEC80) and TZSVCMEM (0xED80) live in the tranZPUter SRAM area above the monitor RAM, always visible regardless of which TZMM mode is active.

TZMM Mode Constants
MMCFG       EQU 060H     ; I/O port address of the memory management configuration register

TZMM_TZFS   EQU 022H     ; Mode 0x22: bank 0 — main TZFS code at 0xE800-0xFFFF
TZMM_TZFS2  EQU 023H     ; Mode 0x23: bank 1 — tzfs_bank2 mapped at 0xF000-0xFFFF
TZMM_TZFS3  EQU 024H     ; Mode 0x24: bank 2 — tzfs_bank3 mapped at 0xF000-0xFFFF
TZMM_TZFS4  EQU 025H     ; Mode 0x25: bank 3 — tzfs_bank4 mapped over full user range
A single OUT (MMCFG), A instruction selects which 64 KB SRAM block is visible in the Z80 address space. This is the entire bank-switching mechanism — there is no coded latch, no unlock sequence, and no multi-step protocol as in some other designs.

Character and Control Definitions
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
These named constants appear throughout the source wherever the code needs to output control characters. Using named constants rather than raw hex values keeps the source readable and makes it straightforward to locate all uses of, for example, the carriage return character with a simple search.

Bank Switching in Detail

Bank switching is the core architectural mechanism of TZFS. Understanding it is essential before modifying any source file. The TZFS approach is architecturally different from RFS — larger, simpler, and more powerful.

Why Banking is Needed
The Sharp MZ's native monitor ROM occupies 0x0000–0xDFFF. The User ROM window at 0xE800–0xEFFF gives TZFS only 2 KB of permanently visible address space — far too small for a full filing system, interactive assembler, disassembler, memory editor, hardware emulation controller, and K64F service API. Banking allows TZFS to present different code modules in the same address range on demand, multiplying the effective code space well beyond the 2 KB physical window.

The TZMM Switching Mechanism
The tranZPUter hardware contains 512 KB of SRAM organised as eight 64 KB blocks. A hardware register — the Memory Management Configuration register at I/O port 0x60 — selects which SRAM block (or blocks) are visible in the Z80 address space and at which addresses. Writing a TZMM mode value to this port takes effect immediately on the next memory cycle.
TZFS uses four TZMM modes. Their effect on the address space is:
TZMM Mode Value 0xE800–0xEFFF 0xF000–0xFFFF Used by
TZMM_TZFS 0x22 TZFS core (bank 0) TZFS core (bank 0) Normal operation; command dispatch
TZMM_TZFS2 0x23 TZFS core (bank 0) tzfs_bank2 (bank 1) Help, messages, print routines
TZMM_TZFS3 0x24 TZFS core (bank 0) tzfs_bank3 (bank 2) Memory utils, I/O, emulation
TZMM_TZFS4 0x25 tzfs_bank4 (bank 3) Assembler/disassembler (full user RAM)
A critical point: in modes TZMM_TZFS, TZMM_TZFS2, and TZMM_TZFS3, the 0xE800–0xEFFF window always maps to the same TZFS core code (bank 0). Only the 0xF000–0xFFFF window switches. This means the bank-switching stubs in tzfs.asm are always reachable regardless of which mode is active — there is no risk of the switching code disappearing when a bank switch occurs.
TZMM_TZFS4 is the exception: it maps bank 3 over the entire user RAM area including 0x1200–0xCFFF, which is used for the assembler/disassembler's large opcode and instruction tables. While TZMM_TZFS4 is active, normal user program RAM is not accessible. The dispatcher restores TZMM_TZFS before returning to the monitor.

Comparison with RFS Bank Switching
RFS switches pages within a single 512 KB Flash ROM using a 2 KB window — each bank is exactly 2 KB. The RomDisk v2+ board adds a coded latch that requires a multi-step unlock sequence before the bank number can be written. This protects against accidental switches but adds code overhead to every cross-bank call.
TZFS switches entire 64 KB SRAM blocks using a single I/O port write. There is no coded latch, no unlock sequence, and no minimum bank size constraint. A TZFS bank can be 4 KB (tzfs_bank2, tzfs_bank3) or 52 KB (tzfs_bank4). The trade-off is that TZFS requires the tranZPUter hardware's memory management unit — it cannot run on a plain RomDisk card.

Command Table Format (tzfs.asm)
The monitor command dispatcher in tzfs.asm uses a compact command table with the same format as RFS. Each entry describes one command:
; One TZFS 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: 0=TZMM_TZFS, 1=TZMM_TZFS2, 2=TZMM_TZFS3, 3=TZMM_TZFS4.
;   Bits 2:0   Length of the command string in bytes.
;
; Example — the DASM command (lives in bank 3 / TZMM_TZFS4, 4-char string):
CMDTABLE:
    DB  000H | 000H | 018H | 004H    ; FLAGS: not-end, not-exact, bank 3 (0x18), length 4
    DB  "DASM"                        ; Command string
    DW  ?DASM                         ; Handler — the inter-bank stub for DASM_MAIN
The dispatcher reads the monitor input line, walks the table, and for each entry:
  1. Compares the input against the command string.
  2. If matched, extracts the bank number (TZMM mode index) and handler address from the table entry.
  3. Performs the TZMM mode switch by writing the appropriate value to port 0x60.
  4. Calls the handler with any remaining input (parameters) available in the monitor input buffer.
  5. Restores TZMM_TZFS on return and passes control back to the monitor.

Jump Tables

TZFS uses two distinct jump table mechanisms. Both are fixed-address tables of JP instructions whose addresses never change regardless of which TZMM mode is active.

External Jump Table at 0xE880 (TZFSJMPTABLE)
The native monitor firmware (SA-1510 for the MZ-80A, 1Z-013A for the MZ-700, and the MZ-80B IPL) need to call TZFS services — specifically the tape/SD I/O routines that replace the hardware CMT interface. These monitor ROMs were not written to know TZFS's internal structure. Instead, TZFS publishes a set of fixed-address entry points starting at 0xE880 (UROMADDR + 0x80). Each entry is a single JP instruction:
CMT_RDINF  EQU UROMADDR+80H  ; 0xE880 — Read tape/SD file header
CMT_RDDATA EQU UROMADDR+83H  ; 0xE883 — Read tape/SD file data body
CMT_WRINF  EQU UROMADDR+86H  ; 0xE886 — Write tape/SD file header
CMT_WRDATA EQU UROMADDR+89H  ; 0xE889 — Write tape/SD file data body
CMT_VERIFY EQU UROMADDR+8CH  ; 0xE88C — Verify tape data against memory
CMT_DIR    EQU UROMADDR+8FH  ; 0xE88F — SD card directory listing
CMT_CD     EQU UROMADDR+92H  ; 0xE892 — SD card change directory
SET_FREQ   EQU UROMADDR+95H  ; 0xE895 — Set CPU operating frequency
Each JP at these addresses dispatches to the real TZFS implementation, which may itself perform a TZMM bank switch to reach the appropriate handler. Monitor firmware that previously called its internal tape routines now calls these fixed addresses instead. This is how TZFS transparently intercepts all tape operations and redirects them to the SD card.

Inter-Bank Function Stubs (? prefix)
Every function that needs to be called from a bank other than the one it lives in has a corresponding ?-prefixed stub in the jump table section of tzfs.asm. For example, PRINTMSG lives in tzfs_bank2 (TZMM_TZFS2), but the command dispatcher in bank 0 needs to call it. The stub ?PRINTMSG in bank 0:
  1. Preserves the current TZMM mode (reads and saves the active mode value).
  2. Writes TZMM_TZFS2 to port 0x60, mapping tzfs_bank2 into 0xF000–0xFFFF.
  3. Calls the actual PRINTMSG routine at its address in tzfs_bank2.
  4. Restores the saved TZMM mode by writing it back to port 0x60.
  5. Returns to the original caller.
The caller simply writes:
CALL ?PRINTMSG
and the bank switch is handled transparently. The ? prefix is a naming convention — not special assembler syntax — that visually identifies every cross-bank call in the source.
Because the stubs live in bank 0 at fixed addresses in the 0xE800–0xEFFF window, they are reachable from any TZMM mode — even TZMM_TZFS4, which replaces most of user RAM. This is why the E800–EFFF window is never switched: it must always contain the stubs and jump tables.

Module Walkthroughs

tzfs.asm — Command Dispatcher (Bank 0, TZMM_TZFS)
Role: The entry point for all TZFS functionality. When the native monitor (SA-1510, 1Z-013A, etc.) encounters a command it does not recognise, it passes control to the User ROM entry point at 0xE800. This module is always bank 0, always mapped, and always reachable.
Cold start vs warm start: At 0xE800, TZFS first checks the WARMSTART flag in TZVARMEM (0xEC80). If the flag is clear (power-on or hard reset), TZFS performs a full cold-start sequence:
  1. Writes TZMM_TZFS to port 0x60 to ensure the correct SRAM block is active.
  2. Calls the K64F service to load the appropriate monitor firmware (LOAD40ABIOS or LOAD700BIOS40) into tranZPUter SRAM at 0x0000.
  3. Clears the TZFS variable block at TZVARMEM.
  4. Sets the WARMSTART flag so subsequent entries skip initialisation.
  5. Cold starts the loaded monitor firmware.
On all subsequent entries (warm starts), TZFS jumps directly to the command dispatcher.
Key sections:
  • External jump table stubs (0xE880–0xE8xx): The CMT_RDINF, CMT_RDDATA, CMT_WRINF, CMT_WRDATA, CMT_VERIFY, CMT_DIR, CMT_CD, and SET_FREQ JP instructions called by monitor firmware.
  • Inter-bank stub table (TZFSJMPTABLE): All ?-prefixed stubs — one per cross-bank function. New functions added to tzfs_bank2/3/4 must have their stub added here.
  • Command table (CMDTABLE): The list of all TZFS commands with their bank index and handler address. Platform-specific commands are conditionally assembled.
  • Main dispatcher loop: Reads the monitor's input buffer, walks CMDTABLE, performs the TZMM mode switch, and calls the handler. If no command matches, returns to the native monitor to print its error response.
  • Variable storage: The WARMSTART flag, current machine model code, and tape compensation value are stored in the TZVARMEM block. The K64F service shared memory lives at TZSVCMEM (0xED80).

tzfs_bank2.asm — Messages and Help (Bank 1, TZMM_TZFS2)
Role: All user-facing text output: character set conversion, string printing, filename display, and the help screen. Assembled with ORG 0xF000 — it occupies 0xF000–0xFFFF when TZMM_TZFS2 is active.
Key functions:
  • PRINTASCII: Converts Sharp MZ character codes to ASCII before output. Codes in the range 0x00–0x7F are looked up in the ATBL conversion table; codes ≥ 0x80 and the carriage return pass through unchanged. This is necessary because the Sharp MZ series uses a non-standard character encoding where many printable characters have different code points from ASCII.
  • PRINTMSG: Prints a string with embedded escape codes that allow formatted multi-value output: code 0xFF pushes a value from the stack, 0xFE/0xFD/0xFC print 1/2/3 values respectively from the stack as hex, and 0xFB prints the BC register pair. This eliminates the need for bespoke print routines for every message that includes a variable value.
  • PRTFN: Prints a Sharp MZF filename — a fixed 17-byte field stored in Sharp character encoding. Calls PRINTASCII for each character.
  • PRTSTR: Prints a null-terminated string directly to the screen.
  • HELPSCR: The complete help screen text stored as CR-terminated strings, one per line. Conditionally assembled: lines describing hardware emulation, CPU switching, and video control commands are excluded when both BUILD_FUSIONX and BUILD_PICOZ80 are 1.

tzfs_bank3.asm — Utilities (Bank 2, TZMM_TZFS3)
Role: The main utilities bank — memory editing, hex dump, block copy, fill, I/O port access, tape compensation, hardware emulation control, CPU switching, and video mode control. Assembled with ORG 0xF000 — it occupies 0xF000–0xFFFF when TZMM_TZFS3 is active.
Key functions:
  • SKIPCOMMA: Scans the monitor input buffer for a comma delimiter. Returns with the Z flag set if no comma is found, with HL pointing past the comma if one is found. Used by all commands that accept multiple parameters separated by commas.
  • CHECKMODEL: Maps machine model character codes — K (MZ-80K), C (MZ-80C), 1 (MZ-1200), A (MZ-80A), 7 (MZ-700), 8 (MZ-800), B (MZ-80B), 2 (MZ-2000) — to binary model numbers 0–7. Used by the hardware emulation commands.
  • MCORX (?MCORX): The interactive memory editor (M command). Presents each byte at the current address, shows the address and current value in hex, and accepts a new hex byte or Enter to leave unchanged. Advances through memory byte by byte. Ctrl+C exits.
  • DUMP* (?DUMPX): The hex dump (D command). Reads the start and end addresses from the input, then for each 16-byte line prints the 4-digit hex address, 16 hex byte values, and 16 ASCII character equivalents (dot for non-printable). Calls PRINTASCII for character display.
  • COPYM (?COPYM): Block memory copy (CP command). Accepts source address, destination address, and byte count. Performs a safe overlapping-aware copy.
  • FILL (?FILL): Fill a memory range with a constant byte (FILL command). Accepts start address, end address, and fill value.
  • TAPECOMP (?TAPECOMP): Tape delay compensation (TC command). Reads or writes the compensation value stored in TZVARMEM. Increasing this value lengthens the bit-timing windows to compensate for mechanical wear on vintage tape decks with slow or uneven motor speed.
  • READIO (?READIO): Read an I/O port (RIO command). Accepts a port address, performs IN A, (C), and prints the result as hex.
  • WRITEIO (?WRITEIO): Write an I/O port (WIO command). Accepts a port address and byte value, performs OUT (C), A.
  • Hardware emulation handlers (?SETMZ80K etc.): Each handler sets the appropriate K64F service command (e.g. TZSVC_CMD_EMU_SETMZ80K) and triggers the service request. Conditionally assembled — excluded when BUILD_FUSIONX + BUILD_PICOZ80 = 1.
  • CPU switch handlers (?SETZ80, ?SETT80, ?SETZPUEVO): Ask the K64F to load any required firmware then signal the FPGA to activate the requested CPU. T80 is a soft-core Z80 implemented in the FPGA; ZPU Evolution is also an FPGA soft CPU. Conditionally assembled.
  • Video control handlers (?SETVMODE, ?SETVGAMODE, ?SETVBORDER): Call K64F video control services. Conditionally assembled.

tzfs_bank4.asm — Assembler / Disassembler (Bank 3, TZMM_TZFS4)
Role: A full interactive Z80 assembler and disassembler. This bank is architecturally unique: it does not fit in the 4 KB 0xF000–0xFFFF window. Instead, TZMM_TZFS4 maps bank 3 over the entire user RAM area (0x1200–0xCFFF) as well as 0xF000–0xFFFF. The assembler/disassembler opcode and instruction tables require this 52 KB space; no user program RAM is available while this bank is active.
Assembled with ORG 0x2000 (BANKRAMADDR), the code physically occupies two regions: 0x1200–0xCFFF for the large lookup tables, and 0xF000–0xFFFF for the executable code itself.
Key functions:
  • DASM_MAIN (?DASM): Full Z80 disassembler (DASM command). Reads start and end addresses from the input buffer. For each instruction, displays the hex address, the raw machine code bytes, and the decoded mnemonic with operands. Handles all Z80 prefix bytes (0xCB, 0xDD, 0xED, 0xFD) and all extended instruction groups, including the full IX/IY displacement addressing modes. The opcode decoding tables are derived from the TASM Z80 assembler reference.
  • ASM_MAIN (?ASM): Full interactive Z80 assembler (ASM command). Prompts the user at each successive address. The user types a Z80 mnemonic with operands (e.g. LD HL, 1234H), the assembler parses the mnemonic and operand fields, looks up the encoding in the instruction tables, assembles the machine code bytes, writes them to the target RAM address, and advances to the next address. Pressing Enter on a blank line exits.
  • Local workspace: COUNT_C, ADDR_LO/HI, ASM_ADDR (8 bytes), ASM_BUF (16 bytes), and various parameter and value buffers occupy fixed addresses within the bank's address space. These are separate from the TZVARMEM variable block and exist only while TZMM_TZFS4 is active.

K64F Service Calls

The tranZPUter hardware contains a Kinetis K64F ARM microcontroller that manages SD card access and monitor firmware loading. The Z80 communicates with the K64F through a shared memory block at TZSVCMEM (0xED80) and a single I/O port (SVCREQ, 0x68). The service API is defined in tzfs_svcstruct.asm. For CPU switching and hardware emulation, the K64F's role is limited to loading the required firmware or ROM before the FPGA activates the mode — the T80, ZPU Evolution, and all Sharp MZ hardware emulation are implemented entirely in the FPGA, not in the K64F.
The call sequence from Z80 code is:
; Step 1 — Set the command and any parameters in the service block.
LD   A, TZSVC_CMD_LOADFILE
LD   (TZSVCCMD), A              ; Write the command byte

; Step 2 — Signal request to the K64F.
LD   A, TZSVC_STATUS_REQUEST
LD   (TZSVCRESULT), A           ; Set result field to "request pending"
OUT  (SVCREQ), A                ; Trigger K64F interrupt — the K64F polls this port

; Step 3 — Poll the result field until the K64F completes the operation.
WAIT:
    LD   A, (TZSVCRESULT)
    CP   TZSVC_STATUS_REQUEST   ; Still pending?
    JR   Z, WAIT                ; Yes — keep waiting

; Step 4 — Check result. TZSVC_STATUS_OK (0x00) = success, non-zero = error.
LD   A, (TZSVCRESULT)
OR   A
JR   NZ, ERROR
The K64F responds within milliseconds for SD card operations (file load) and somewhat faster for firmware loading operations that precede a CPU or emulation switch. The polling loop is safe with interrupts enabled — the Z80 is not doing useful work during the wait, and the K64F's interrupt is edge-triggered on the port write, not level-sensitive.
Key service commands (from tzfs_svcstruct.asm):
TZSVC_CMD_LOADFILE      ; Load a named file from SD card into Z80 RAM
TZSVC_CMD_SAVEFILE      ; Save Z80 RAM region to a named file on SD card
TZSVC_CMD_DIR           ; Read directory listing from SD card
TZSVC_CMD_SD_RDPAGE     ; Read raw SD sector
TZSVC_CMD_SD_WRPAGE     ; Write raw SD sector
TZSVC_CMD_EMU_SETMZ80K  ; Switch hardware emulation to MZ-80K mode
TZSVC_CMD_EMU_SETMZ80A  ; Switch hardware emulation to MZ-80A mode
TZSVC_CMD_EMU_SETMZ700  ; Switch hardware emulation to MZ-700 mode
TZSVC_CMD_EMU_SETMZ800  ; Switch hardware emulation to MZ-800 mode
TZSVC_CMD_EMU_SETMZ80B  ; Switch hardware emulation to MZ-80B mode
TZSVC_CMD_CPU_SETZ80    ; Set active CPU to hardware Z80
TZSVC_CMD_CPU_SETT80    ; Load firmware then signal FPGA to activate T80 (FPGA soft-core Z80)
TZSVC_CMD_CPU_SETZPUEVO ; Load firmware then signal FPGA to activate ZPU Evolution (FPGA soft CPU)
TZSVC_CMD_SET_VIDMODE   ; Set video output mode
TZSVC_CMD_SET_VGAMODE   ; Set VGA output mode
TZSVC_CMD_SET_VBORDER   ; Set video border colour
Parameters and return values are passed through additional fields in the TZSVCMEM block. The layout of this block is defined by the TZSVCSTRUCT structure in tzfs_svcstruct.asm. For file operations, the filename is written as a null-terminated ASCII string at the TZSVCFNM offset before issuing the command.

Adding a New Monitor Command

To add a new command — for example, PROBE that reads a range of I/O ports and prints their values — follow these steps:
  1. Write the handler in the appropriate bank file. For a utility command, add a labeled routine in tzfs_bank3.asm:
PROBE:
    ; HL points to parameters in the monitor input buffer on entry.
    CALL  SKIPCOMMA           ; Parse start port
    ...                       ; implementation
    RET
  1. Add a ?-prefix stub in tzfs.asm if the handler needs to be called from bank 0 or from any other bank. In the TZFSJMPTABLE section, add:
?PROBE:
    PUSH  AF
    LD    A, TZMM_TZFS3
    OUT   (MMCFG), A          ; Switch to bank3
    CALL  PROBE               ; Call the handler
    LD    A, TZMM_TZFS
    OUT   (MMCFG), A          ; Restore bank0
    POP   AF
    RET
  1. Add an entry to CMDTABLE in tzfs.asm. Bank 2 corresponds to TZMM_TZFS3 (bits 5:3 = 010 = 0x10), length 5:
; FLAGS: not-end (bit7=0), not-exact (bit6=0), bank 2 = 0x10, length 5 = 0x05
    DB  000H | 000H | 010H | 005H
    DB  "PROBE"
    DW  ?PROBE
  1. Add help text to HELPSCR in tzfs_bank2.asm. Add a CR-terminated string describing the new command. If the command is platform-specific, wrap it with the appropriate conditional assembly guard (IF BUILD_FUSIONX = 1 ... ENDIF).
  2. Run ./build.sh. The assembler will report any size overflows — if tzfs_bank3 now exceeds 0xF000–0xFFFF, remove or compress other code in that bank or move less-used routines to tzfs_bank2.

Adding a New Hardware Platform

To port TZFS to a new hardware target (for example, a new tranZPUter board revision with different I/O port assignments or additional memory management modes):
  1. Add a build flag in tzfs_definitions.asm:
BUILD_NEWBOARD EQU 0    ; 1 = build for the new target board
  1. Add I/O port and TZMM mode constants as needed. If the new board uses a different port address for MMCFG or defines additional TZMM modes, add them in the definitions file under a conditional block:
IF BUILD_NEWBOARD = 1
MMCFG        EQU 070H         ; New board uses port 0x70 for memory management
TZMM_TZFS5   EQU 026H         ; Additional bank mode for new board
ENDIF
  1. Add conditional assembly blocks throughout the source where the new board's behaviour differs — I/O port sequences for bank switching, K64F service command numbers, or monitor firmware identifiers. Follow the existing pattern of IF BUILD_FUSIONX = 1 ... ENDIF blocks.
  2. Implement the K64F service API on the new platform. The new board's co-processor must respond to the SVCREQ port write (or an equivalent interrupt mechanism), read the command and parameters from TZSVCMEM, execute the service, and write the result back. The Z80-side service call sequence requires no changes — only the ARM-side firmware changes.
  3. Stub out unsupported service commands initially. Have the K64F return TZSVC_STATUS_OK immediately for commands the new hardware does not yet implement. This lets the Z80 side compile and run while firmware support is developed incrementally.
  4. Update build.sh to add a build target for the new board, setting BUILD_NEWBOARD=1 and any other required flags on the GLASS assembler command line.

Debugging Tips

Enable debug output: Set ENADEBUG EQU 1 in tzfs_definitions.asm before building. This includes additional diagnostic messages at strategic points — particularly during cold-start initialisation, K64F service calls, and TZMM mode switches.
Probe I/O ports directly with RIO/WIO: The RIO port command reads any I/O port and prints its value as hex. WIO port,value writes a byte to any port. These commands are invaluable for verifying that the MMCFG register (port 0x60) and the K64F service port (0x68) are responding as expected, without writing test code.
Inspect the TZFS variable block: Type D EC80 to hex-dump the TZVARMEM area. The WARMSTART flag is the first byte — a non-zero value confirms TZFS has completed cold-start initialisation. The tape compensation value and current model code follow immediately.
Inspect the K64F service block: Type D ED80 to hex-dump the TZSVCMEM area. The command byte and result byte are at the start of this block. After a failed service call, the result byte will be non-zero and can be cross-referenced against the TZSVC_STATUS_* constants in tzfs_svcstruct.asm.
Inspect the TZFS entry code and jump tables: Type D E800 to dump the first 320 bytes of the TZFS core — this shows the external jump table at 0xE880 (verify the JP instructions are intact) and the beginning of the inter-bank stub table.
Verify assembled code with DASM: After using the ASM command to enter a routine interactively, use DASM addr,endaddr immediately to disassemble the freshly written bytes. Encoding errors in operand types (immediate vs indirect, 8-bit vs 16-bit) are immediately visible in the disassembly output.
Tape read failures: If tape load operations fail on vintage hardware, use the TC (tape compensation) command to increase the timing window. Start by increasing the value in steps of 10 and attempting each load until successful, then note the working value. Values that are too large cause false bit detections; too small causes missed bits on slow or worn tape mechanisms.
Bank switching verification: If a cross-bank call crashes or produces unexpected output, check that the ?-prefixed stub correctly saves and restores the TZMM mode. A common mistake is returning from the stub with the wrong mode active, causing the next memory access to read from the wrong SRAM block.

Reference Sites

Resource Link
TZFS project page /sharpmz-upgrades-tzfs/
TZFS User Manual /sharpmz-upgrades-tzfs-usermanual/
TZFS Technical Guide /sharpmz-upgrades-tzfs-technicalguide/
tranZPUter project page /transzputer/
RFS Developer’s Guide /sharpmz-upgrades-rfs-developersguide/
GLASS Z80 Assembler Bundled in tools/glass.jar
Zilog Z80 CPU User Manual Standard datasheet — bus timing, instruction set, register reference
Sharp MZ-80A Service Manual Hardware reference — memory map, I/O port assignments, monitor ROM entry points
Sharp MZ-700 Technical Manual Hardware reference for MZ-700 monitor ROM and memory layout