Sharp MZ Series CP/M v2.23 - Developer’s Guide
Overview
Two distinct CP/M implementations exist in this project:
- RFS CP/M — runs on the MZ-80A with the RFS/RomDisk upgrade board. The CBIOS resides in banked ROM (1 main + 4 paged banks) to maximise the Transient Program Area (TPA) available to applications.
- TZFS CP/M — runs on any Sharp MZ machine equipped with a tranZPUter board. The CBIOS uses the tranZPUter's memory mode switching to provide a full 64KB RAM space and extended CBIOS logic in a separate 48KB bank. Five build variants target different machine and display configurations.
CP/M Architecture
CCP — Console Command Processor
DIR, REN, ERA, TYPE, SAVE, USER) or loads and runs a .COM transient program from disk. The CCP occupies approximately 2KB and is loaded at CBASE (0xA000 for RFS, 0xDA00 for TZFS).
The CCP is standard Digital Research code (reconstructed from memory image by Clark A. Calkins, February 1981) and should not normally be modified. Transient programs that need the CCP's memory space may overwrite it; on program exit, a warm boot reloads the CCP from disk or ROM.
BDOS — Basic Disk Operating System
CALL 5. The BDOS handles file operations (open, close, read, write, search, delete, rename), console I/O, and disk management.
The BDOS is also standard Digital Research code and is not modified in this implementation. It begins at
CBASE + 0x0806.
CBIOS — Custom Basic Input Output System
CBASE + 0x1600.
This project's CBIOS is a ground-up implementation for the Sharp MZ hardware. It goes well beyond the minimum CP/M requirements, providing interrupt-driven keyboard input, an ANSI terminal emulator, real-time clock, SD card and floppy disk controllers, and ROM disk support.
Memory Maps
RFS Memory Map
| Address Range | Contents |
|---|---|
| 0x0000 - 0x00FF | CP/M page zero: warm boot vector, IOBYTE, drive/user, BDOS entry |
| 0x0100 - 0x9FFF | TPA (Transient Program Area) — ~40KB available for applications |
| 0xA000 - 0xA805 | CCP (Console Command Processor) |
| 0xA806 - 0xB5FF | BDOS (Basic Disk Operating System) |
| 0xB600 - 0xBFFF | CBIOS stub + Disk Parameter Headers + directory buffer + drive allocation vectors |
| 0xC000 - 0xCFFF | CBIOS main (4KB MROM Bank 2) — jump table, init, ROM disk, interrupts |
| 0xD000 - 0xD7FF | Video RAM (character display) |
| 0xD800 - 0xDFFF | Attribute RAM (colour/attributes) |
| 0xE000 - 0xE7FF | Memory-mapped I/O (keyboard, PIO, CTC, sound) |
| 0xE800 - 0xEFFF | Paged User ROM — 4 banks for CBIOS extensions: |
| Bank 8: Audio, RTC, Keyboard | |
| Bank 9: Screen I/O, ANSI Terminal Parser | |
| Bank 10: SD Card Controller | |
| Bank 11: Floppy Disk Controller | |
| 0xF000 - 0xFFFF | FDC ROM area (active during disk DMA operations) |
TZFS Memory Map
- Mode 16 — All 64KB is RAM; all Sharp MZ hardware is paged out. CP/M applications run in this mode.
- Mode 17 — 48KB RAM bank accessible for extended CBIOS logic, with Sharp hardware visible for I/O operations.
| Address Range | Mode 16 (CP/M) | Mode 17 (CBIOS Extended) |
|---|---|---|
| 0x0000 - 0x00FF | CP/M page zero | CBIOS extended code (cbiosII.asm) |
| 0x0100 - 0xD9FF | TPA — ~55KB available for applications | CBIOS extended code + variables |
| 0xDA00 - 0xE205 | CCP | Sharp hardware visible |
| 0xE206 - 0xEFFF | BDOS | Sharp hardware visible |
| 0xF000 - 0xF7FF | CBIOS code (2KB) | CBIOS code (same) |
| 0xF800 - 0xFFFF | CBIOS variables, buffers, stack (2KB) | CBIOS variables (same) |
CBIOS Jump Table
| Offset | Label | Function | Description |
|---|---|---|---|
| +0 | BOOT | Cold Boot | One-time hardware initialisation, then jump to CCP |
| +3 | WBOOT | Warm Boot | Reload CCP+BDOS, reset hardware state, jump to CCP |
| +6 | CONST | Console Status | Returns A=0xFF if key available, A=0x00 if not |
| +9 | CONIN | Console Input | Wait for and return character in A (with ANSI processing) |
| +12 | CONOUT | Console Output | Output character in C to display (via ANSI terminal emulator) |
| +15 | LIST | Printer Output | Output character in C to list device |
| +18 | PUNCH | Punch Output | Output character in C to punch device (unused) |
| +21 | READER | Reader Input | Read character from reader device (unused) |
| +24 | HOME | Seek Home | Move selected disk head to track 0 |
| +27 | SELDSK | Select Disk | Select disk in C; returns HL=DPH address or HL=0 on error |
| +30 | SETTRK | Set Track | Set track number in BC for next read/write |
| +33 | SETSEC | Set Sector | Set sector number in BC for next read/write |
| +36 | SETDMA | Set DMA Address | Set memory address in BC for disk data transfer |
| +39 | READ | Read Sector | Read 128-byte CP/M sector; returns A=0 on success, A=1 on error |
| +42 | WRITE | Write Sector | Write 128-byte CP/M sector; returns A=0 on success, A=1 on error |
| +45 | LISTST | List Device Status | Returns A=0xFF if list device ready, A=0x00 if not |
| +48 | SECTRN | Sector Translate | Translate logical sector BC via table DE; returns HL=physical sector |
| +51 | — | (RFS: unused / TZFS: DEBUG) | RFS: NOP NOP RET. TZFS: Debug hook |
| +54 | BANKTOBANK | Inter-bank Call (RFS only) | Call function in another ROM bank with bank switching |
Bank Switching — RFS
To call a function in a different bank, the CBIOS writes the bank number to the
BNKSELUSER register (0xEFFE) and then jumps to the function address. The BANKTOBANK_ routine (at jump table offset +54) provides a generic inter-bank call mechanism:
Input: A[7:4] = Target bank number
A[3:0] = Calling bank number
HL = Address of function to call
AF = On stack, passed to called function
Output: All registers preserved from called function
Memory Mode Switching — TZFS
The extended CBIOS code in
cbiosII.asm resides in the Mode 17 RAM bank starting at address 0x0000. This provides nearly 48KB of space for CBIOS logic including the ANSI terminal emulator, keyboard driver, disk controllers, and variable storage.
Disk Subsystem
Disk Parameter Blocks
Each DPB contains 16 bytes with the following fields:
| Field | Size | Description |
|---|---|---|
| SPT | 2 bytes | Sectors Per Track — total 128-byte logical sectors per track |
| BSH | 1 byte | Block Shift Factor — log2(block_size/128). BSH=3 for 1KB, 4 for 2KB, 6 for 8KB |
| BLM | 1 byte | Block Mask — (2^BSH)-1. BLM=7 for 1KB blocks, 15 for 2KB, 63 for 8KB |
| EXM | 1 byte | Extent Mask — determines how many extents per directory entry |
| DSM | 2 bytes | Disk Size Maximum — total number of allocation blocks minus 1 |
| DRM | 2 bytes | Directory Maximum — total number of directory entries minus 1 |
| AL0 | 1 byte | Allocation Bitmap byte 0 — marks blocks reserved for directory |
| AL1 | 1 byte | Allocation Bitmap byte 1 |
| CKS | 2 bytes | Check Size — (DRM+1)/4 for removable media, 0 for fixed disks |
| OFF | 2 bytes | Track Offset — number of reserved (system) tracks |
Supported Disk Formats
The following disk formats are supported, each with its corresponding DPB values:
| Parameter | MZ80A 320K | ROM RFS 240K | 1.44MB 3.5” | 720K 3.5” | 16MB SD Card |
|---|---|---|---|---|---|
| SPT | 64 | 128 | 144 | 72 | 128 |
| BSH | 4 | 3 | 4 | 4 | 6 |
| BLM | 15 | 7 | 15 | 15 | 63 |
| EXM | 1 | 0 | 0 | 0 | 3 |
| DSM | 155 | 240 | 719 | 359 | 2047 |
| DRM | 63 | 31 | 127 | 127 | 511 |
| AL0 | 128 | 128 | 192 | 192 | 192 |
| AL1 | 0 | 0 | 0 | 0 | 0 |
| CKS | 16 | 8 | 32 | 32 | 0 |
| OFF | 1 | 0 | 0 | 0 | 0 |
| Capacity | 320KB | 240KB | 1.44MB | 720KB | 16MB |
| Geometry | 40c/2h/16s/256B | 15c/1h/128s/128B | 80c/2h/36s/512B | 80c/2h/36s/256B | 1024c/1h/32s/512B |
| Block Size | 2KB | 1KB | 2KB | 2KB | 8KB |
Dynamic Drive Allocation
- ROM Drives — Read-only drives stored in Flash ROM (0, 1, or 2 drives)
- Floppy Disk — If FDC hardware is detected (up to 2 drives)
- SD Card — If SPI/SD hardware is detected (multiple 16MB drives)
MAXDISKS constant (7 by default) and the RAM allocated for Disk Parameter Headers and allocation vectors, governed by the CSVALVEND - CSVALVMEM range in cpm_definitions.asm. Increasing the allocated memory allows more SD card drives to be visible.
Adding a New Disk Format
Step 1: Design the DPB
Calculate the DPB values for your disk format:
1. Determine the physical geometry:
- Cylinders (C), Heads (H), Sectors per Track (S), Sector Size (bytes)
- Total capacity = C x H x S x SectorSize
2. Choose a block size (must be 1KB, 2KB, 4KB, 8KB, or 16KB):
- 1KB blocks: BSH=3, BLM=7 (good for small disks < 256KB)
- 2KB blocks: BSH=4, BLM=15 (good for medium disks 256KB-1MB)
- 4KB blocks: BSH=5, BLM=31 (good for 1MB-8MB)
- 8KB blocks: BSH=6, BLM=63 (good for 8MB-16MB)
3. Calculate SPT = (SectorsPerTrack x Heads x PhysicalSectorSize) / 128
4. Calculate DSM = (TotalCapacity - ReservedTracks * TrackSize) / BlockSize - 1
5. Choose DRM (directory entries): typically 64 for small, 128 for medium, 512 for large
6. Calculate EXM based on DSM and block size:
- DSM < 256: EXM = (BlockSize / 1024) - 1
- DSM >= 256: EXM = (BlockSize / 2048) - 1
7. Set AL0/AL1 to reserve directory blocks (typically 128,0 or 192,0)
8. Set CKS = (DRM+1)/4 for removable media, 0 for fixed
9. Set OFF = number of reserved system tracks
Step 2: Add the DPB to the CBIOS Source
cbios.asm for RFS or cbiosII.asm for TZFS), locate the existing DPB definitions and add your new DPB. Each DPB is defined as a sequence of DW and DB directives:
; Example: Custom 4MB disk format
DPB_CUSTOM: DW 128 ; SPT - sectors per track
DB 5 ; BSH - block shift (4KB blocks)
DB 31 ; BLM - block mask
DB 1 ; EXM - extent mask
DW 1023 ; DSM - total blocks - 1
DW 255 ; DRM - directory entries - 1
DB 240 ; AL0 - allocation bitmap 0
DB 0 ; AL1 - allocation bitmap 1
DW 64 ; CKS - check size
DW 0 ; OFF - reserved tracks
Step 3: Add a cpmtools diskdef
diskdefs file so that cpmtools can create and manipulate disk images in the new format:
diskdef MY-CUSTOM-4MB
seclen 512
tracks 256
sectrk 32
blocksize 4096
maxdir 256
skew 0
boottrk 0
os 2.2
end
Step 4: Add Drive Detection (if new controller)
- Add controller initialisation code to the CBIOS BOOT routine
- Implement the read/write sector functions
- Add detection logic to the dynamic drive allocation in the BOOT sequence
- For RFS: add a new ROM bank if space requires it
- For TZFS: add the code to cbiosII.asm in the Mode 17 bank
Step 5: Build and Test
# Build the CP/M CBIOS with the new format
cd <project_root>
tools/assemble_cpm.sh
# Create a blank disk image in the new format
mkfs.cpm -f MY-CUSTOM-4MB blank_custom.img
# Add files to the new disk image
cpmcp -f MY-CUSTOM-4MB blank_custom.img local_file.com 0:FILE.COM
# Verify the disk contents
cpmls -f MY-CUSTOM-4MB blank_custom.img
Console I/O
Keyboard Processing
Detected key presses are placed into a ring buffer (
KEYBUF, 16 bytes for RFS, 64 bytes for TZFS). The CONIN routine reads from this buffer, waiting if empty. Special key combinations are detected:
- SHIFT — modifies key codes for upper case and symbols
- CTRL — generates control characters (0x01–0x1A)
- GRAPH — generates graphics character codes
- SHIFT+GRAPH+BREAK — warm reboot to Sharp Monitor ROM
ANSI Terminal Emulation
Supported ANSI escape sequences include:
| Sequence | Function |
|---|---|
| ESC [nA | Cursor Up n rows |
| ESC [nB | Cursor Down n rows |
| ESC [nC | Cursor Forward n columns |
| ESC [nD | Cursor Back n columns |
| ESC [r;cH | Set Cursor Position (row;column) |
| ESC [2J | Clear Screen |
| ESC [K | Clear to End of Line |
| ESC [nm | Set Graphics Mode (bold, underline, inverse, etc.) |
| ESC [6n | Report Cursor Position |
| ESC [?25h | Show Cursor |
| ESC [?25l | Hide Cursor |
Storage Controllers
SD Card — SPI Interface
The SD card appears as a set of 16MB logical disks. Each disk occupies a contiguous region of the SD card, with the disk number translated to an LBA (Logical Block Address) offset. The CBIOS read/write routines use the standard CP/M deblocking algorithm to translate 128-byte CP/M sectors into 512-byte SD card sectors.
Floppy Disk Controller — MB8866
| Port | Register |
|---|---|
| 0xD8 | Command/Status Register |
| 0xD9 | Track Register |
| 0xDA | Sector Register |
| 0xDB | Data Register |
| 0xDC | Drive Select + Motor Control (bit 7 = motor on, bits 2:0 = drive) |
| 0xDD | Side Select (bit 0 = side) |
The motor is controlled via a countdown timer in the interrupt handler. After the last disk access, the motor runs for a configurable period (default 100 timer ticks = 10 seconds) before being automatically shut off.
ROM Disk
ROM disks are useful for providing a permanent set of CP/M utilities (assembler, debugger, editors) that are always available regardless of the SD card or floppy disk state.
Build System
GLASS Z80 Assembler
java -jar glass.jar input.asm output.obj output.sym -I include_dir/
.obj file is the binary output (ready for ROM programming or concatenation). The .sym file contains the symbol table for debugging.
Build Scripts
build.sh. The build order matters because later scripts depend on artifacts from earlier ones.
RFS Build Order:
| Step | Script | Produces |
|---|---|---|
| 1 | (cpmtools compile) | cpmcp, cpmls, mkfs.cpm, fsck.cpm (from cpmtools/ source) |
| 2 | assemble_rfs.sh | rfs.rom, rfs_mrom.rom (RFS kernel) |
| 3 | assemble_cpm.sh | cbios.rom, cbios_bank1-4.rom, cpm22.rom (CP/M CBIOS) |
| 4 | assemble_roms.sh | Monitor ROMs, MS BASIC variants, IPL, utilities |
| 5 | make_roms.sh | MROM_256.bin, USER_ROM.256.bin (Flash ROM images) |
| 6 | make_cpmdisks.sh | CP/M disk images: 1.44MB floppy + 16MB SD card + ROM drives |
| 7 | make_sdcard.sh | Complete SD card image with RFS drives + CP/M disks |
TZFS Build Order:
| Step | Script | Produces |
|---|---|---|
| 1 | (cpmtools compile) | cpmcp, cpmls, mkfs.cpm, fsck.cpm (from cpmtools/ source) |
| 2 | assemble_tzfs.sh | tzfs.rom (TZFS kernel) |
| 3 | assemble_cpm.sh | 5 variant binaries: cpm223*.bin + cpm223*.mzf |
| 4 | assemble_roms.sh | Monitor ROMs, MS BASIC variants, utilities |
| 5 | make_cpmdisks.sh | CP/M disk images: 1.44MB floppy + 16MB SD card |
TZFS Build Variants
BUILD_VERSION value to cpm_buildversion.asm:
| Version | File Extension | Machine | Display | Description |
|---|---|---|---|---|
| 0 | mz700_80c | MZ-700 | 80 col | MZ-700 with Video Module, 80-column display |
| 1 | mz80a_80c | MZ-80A | 80 col | MZ-80A with Video Module or 40/80 Colour Board |
| 2 | mz80a_40c | MZ-80A | 40 col | MZ-80A standard 40-column display |
| 3 | mz700_40c | MZ-700 | 40 col | MZ-700 standard 40-column display |
| 4 | mz1500_40c | MZ-1500 | 40 col | MZ-1500 standard 40-column display |
assemble_cpm.sh script loops through all five variants, generating a separate cpm223_*.bin and cpm223_*.mzf for each. The MZF files include machine-specific headers from the hdr/ directory and can be loaded directly via TZFS.
Source File Reference
RFS CP/M Source Files
| File | Location | Size | Description |
|---|---|---|---|
| cbios.asm | asm/ | 192KB | Main CBIOS: jump table, init, ROM disk, interrupts |
| cbios_bank1.asm | asm/ | 21KB | Audio, RTC, Keyboard (User ROM Bank 8) |
| cbios_bank2.asm | asm/ | 58KB | Screen I/O, ANSI Terminal (User ROM Bank 9) |
| cbios_bank3.asm | asm/ | 45KB | SD Card Controller (User ROM Bank 10) |
| cbios_bank4.asm | asm/ | 34KB | Floppy Disk Controller (User ROM Bank 11) |
| cpm22.asm | asm/ | 111KB | CP/M v2.23 CCP + BDOS kernel |
| cpm_definitions.asm | asm/include/ | 40KB | Hardware addresses, memory layout, constants |
| cpm_buildversion.asm | asm/include/ | <1KB | Build version selector (auto-generated) |
| macros.asm | asm/include/ | 9KB | Z80 assembler macros |
TZFS CP/M Source Files
| File | Location | Size | Description |
|---|---|---|---|
| cbios.asm | asm/ | 66KB | CBIOS hooks: jump table, timer ISR, keyboard scan |
| cbiosII.asm | asm/ | 160KB | Extended CBIOS: variables, disk I/O, screen, ANSI |
| cpm22.asm | asm/ | 154KB | CP/M v2.23 CCP + BDOS kernel |
| cpm_definitions.asm | asm/include/ | 40KB | Hardware addresses, memory modes, constants |
| cpm_buildversion.asm | asm/include/ | <1KB | Build version selector (auto-generated per variant) |
| macros.asm | asm/include/ | 9KB | Z80 assembler macros |
Key Include File Definitions
cpm_definitions.asm file contains all configurable constants. The most important ones for customisation are:
| Constant | RFS Value | TZFS Value | Description |
|---|---|---|---|
| CBASE | 0xA000 | 0xDA00 | Start of CCP (determines TPA size) |
| CBIOSSTART | 0xC000 | 0xF000 | Start of CBIOS code |
| MAXDISKS | 7 | 7 | Maximum number of CP/M drives |
| KEYBUFSIZE | 16 | 64 | Keyboard ring buffer size (must be power of 2) |
| COLW | 40 or 80 | 40 or 80 | Display width (set by BUILD_80C flag) |
| ROW | 25 | 25 | Display height |
| TMRTICKINTV | 5 | 5 | Timer tick interval (5 x 0.01ms = 100ms) |
| MTROFFMSECS | 100 | 100 | FDC motor-off timer (100 ticks = 10 seconds) |
| HSTSIZ | 512 | 512 | Host disk physical sector size |
| HSTSPT | 32 | 32 | Host disk sectors per track |
CI/CD Pipeline
Pipeline Stages:
| Stage | Action |
|---|---|
| Prepare Build Image | Build Docker image with Java JRE, GCC, make, perl, python3, samdisk |
| Checkout RFS | Clone MZ80A_RFS repository from Gitea |
| Checkout TZFS | Clone TZFS repository from Gitea |
| Determine Version | Auto-increment version from last Gitea release tag |
| Build RFS CP/M | Compile cpmtools, assemble CBIOS + CCP/BDOS, build ROMs and disk images |
| Build TZFS CP/M | Compile cpmtools, assemble 5 CBIOS variants, build disk images |
| Package Releases | Create tar.gz archives: CPM-RFS, CPM-TZFS, CPM-Complete |
| Create Gitea Releases | Tag and release on both MZ80A_RFS and TZFS repositories |
Note: Use samdisk v3.8.8 specifically — version 4 has a known bug that places a random sector at the start of the disk.
Credits
- CP/M v2.2 — Digital Research Inc., reconstructed by Clark A. Calkins
- ANSI Terminal Parser — based on Ewen McNeill’s Amstrad CPC EwenTerm, substantially reworked
- CP/M deblocking algorithm — portions from the Z80-MBC2 project by SuperFabius
- cpmtools — by Michael Haardt (lipro-cpm4l fork)
- samdisk — by Simon Owen
- GLASS Z80 Assembler — by Grauw
Licenses
This design, hardware and software, is licensed under the GNU Public Licence v3.
The Gnu Public License v3
The source and binary files in this project marked as GPL v3 are free software: you can redistribute it and-or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
The source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.