retroshield-z80-workbench
A Rust framework for generating Z80 machine code ROMs, designed for RetroShield projects and other Z80-based systems.
Build Z80 programs in Rust using a fluent API instead of writing raw assembly or hex bytes.
Features
- Instruction Helpers: 80+ Z80 instructions as Rust methods (
ld_a(),call(),jp_z(), etc.) - Label System: Define labels and forward-reference them; fixups resolved automatically
- Standard Library: Pre-built routines for serial I/O, VT100 terminal control, and math
- Zero Dependencies: Pure Rust, no external assembler needed
- Multiple Output Formats: Binary (
.bin) and Intel HEX (.hex)
Installation
Add to your Cargo.toml:
[]
= "0.1"
Quick Start
Here's a complete "Hello World" program that prints to a serial terminal:
use *;
API Overview
Core Methods
let mut rom = new;
// Emit raw bytes
rom.emit;
rom.emit_byte;
rom.emit_word; // Little-endian
rom.emit_string; // Null-terminated
// Labels and fixups
rom.label;
rom.jp; // Forward reference OK
rom.resolve_fixups; // Call once at the end
// Output
rom.write_bin?;
rom.write_hex?;
Instruction Helpers
Instead of remembering opcodes, use named methods:
// 8-bit loads
rom.ld_a; // LD A, 0x42
rom.ld_b; // LD B, 10
rom.ld_a_b; // LD A, B
rom.ld_a_hl_ind; // LD A, (HL)
rom.ld_hl_ind_a; // LD (HL), A
rom.ld_a_addr; // LD A, (0x3000)
rom.ld_addr_a; // LD (0x3000), A
// 16-bit loads
rom.ld_hl; // LD HL, 0x2000
rom.ld_de; // LD DE, 0x1000
rom.ld_bc; // LD BC, 100
rom.ld_sp; // LD SP, 0x3FFF
rom.ld_hl_label; // LD HL, data (with fixup)
// Stack
rom.push_af;
rom.push_hl;
rom.pop_de;
rom.pop_bc;
// Arithmetic
rom.add_a; // ADD A, 5
rom.sub_a; // SUB 1
rom.inc_a;
rom.dec_b;
rom.inc_hl;
rom.dec_de;
rom.add_hl_de; // ADD HL, DE
rom.add_hl_bc; // ADD HL, BC
// Logic
rom.and_a; // AND 0x0F
rom.or_a; // OR 0x80
rom.xor_a; // XOR A (clear A)
rom.cp; // CP 0x0D
rom.cpl; // CPL (complement A)
// Jumps
rom.jp; // JP label
rom.jp_z; // JP Z, label
rom.jp_nz; // JP NZ, label
rom.jp_c; // JP C, label
rom.jp_nc; // JP NC, label
rom.jr; // JR label (relative)
rom.djnz; // DJNZ label
// Calls and returns
rom.call; // CALL subroutine
rom.ret; // RET
rom.ret_z; // RET Z
rom.ret_nz; // RET NZ
// I/O
rom.in_a; // IN A, (0x80)
rom.out_a; // OUT (0x81), A
// Misc
rom.nop;
rom.halt;
rom.di; // Disable interrupts
rom.ei; // Enable interrupts
rom.ex_de_hl; // EX DE, HL
Standard Library
The framework includes pre-built routines for common tasks:
// Include all standard library routines
rom.include_stdlib;
// Or include selectively:
rom.emit_io_routines; // getchar, putchar, print_string, newline
rom.emit_terminal_routines; // clear_screen, cursor_pos, cursor_home, etc.
rom.emit_math_routines; // print_byte_dec, div16, negate_hl
I/O Routines (MC6850 ACIA at ports 0x80/0x81):
getchar- Read character into A (blocking)putchar- Write character from Aprint_string- Print null-terminated string at HLnewline- Print CR+LF
Terminal Routines (VT100/ANSI):
clear_screen- Clear screen and home cursorcursor_home- Move cursor to top-leftcursor_pos- Move cursor to row B, column Cclear_to_eol- Clear from cursor to end of linecursor_hide/cursor_show- Toggle cursor visibility
Math Routines:
print_byte_dec- Print A as decimal numberdiv16- 16-bit division: HL / DE → HL quotient, DE remaindernegate_hl- Two's complement negate HL
Complete Example: Number Counter
A program that counts from 0 to 255 on the terminal:
use *;
Memory Map
Default configuration for RetroShield Z80:
| Address | Size | Description |
|---|---|---|
| 0x0000-0x1FFF | 8KB | ROM |
| 0x2000-0x3FFF | 8KB | RAM |
| 0x80 | - | MC6850 Status Register |
| 0x81 | - | MC6850 Data Register |
Custom I/O Ports
Configure different I/O ports for the serial routines:
use MC6850Config;
let config = MC6850Config ;
rom.emit_io_routines_with_config;
Tips
-
Always call
resolve_fixups()after emitting all code and before writing output. -
Place data after code to avoid executing data as instructions.
-
Standard library goes last - include it after your main code so execution doesn't fall into library routines.
-
Use
unique_label()for generated code to avoid label collisions:let loop_label = rom.unique_label; rom.label; // ... loop body ... rom.jp;
License
BSD 3-Clause License. See LICENSE for details.
Contributing
Contributions welcome! Please open an issue or pull request on GitHub.
See Also
- RetroShield - Arduino shields for retro CPUs
- Z80 Instruction Set - Reference documentation