Tools / Command-Line Tool
Z-80 Disassembler
A Zilog Z-80 disassembler covering all 256 unprefixed opcodes. CB/DD/ED/FD prefix bytes emit raw-byte placeholders. Reusable on any Z-80 binary — written for Microsoft SoftCard CP/M BIOS analysis but with no SoftCard-specific dependencies.
Tested on: Windows 11 · Python 3.13
A Z-80 disassembler usable on any Z-80 binary — the BIOS of a CP/M system, a TRS-80 or ZX Spectrum dump, embedded firmware from any of the dozens of late-1970s and 1980s machines that shipped with a Z-80. Written specifically for the SoftCard CP/M reverse engineering project but the core has no project-specific dependencies. Lives inside the Orchard repository as nibbler/z80.py; invoked via the nibbler CLI as the z80disasm subcommand.
Coverage
All 256 unprefixed opcodes — the full base instruction set. Decoded into standard mnemonic notation: LD r,r', JP cc,nn, CALL nn, RET, IN A,(n), OUT (n),A, JR e, DJNZ e, plus the full ALU group (ADD, ADC, SUB, SBC, AND, XOR, OR, CP) over 8-bit registers and immediates, the 16-bit register-pair operations (PUSH/POP, INC/DEC, LD rp,nn), the rotation/accumulator operations (RLCA, RRCA, RLA, RRA, DAA, CPL, SCF, CCF), the conditional flags (NZ, Z, NC, C, PO, PE, P, M), and the RST reset vectors.
Relative jumps resolve to absolute targets. A JR NZ,+8 is rendered as JR NZ,$xxxx with the target address computed from (addr + 2 + signed_displacement), so the output reads naturally without manual offset arithmetic.
The four prefix bytes ($CB, $DD, $ED, $FD) are stubs — they emit DB $xx raw-byte markers. This means bit operations, IX/IY-indexed addressing, and the extended instruction set (block instructions, IN/OUT to (C), LDIR, IM, etc.) are not yet decoded; the prefix byte appears as a DB and the operand bytes that follow are decoded as if they were unprefixed instructions, which is wrong but at least doesn’t desynchronize subsequent decoding. Extending coverage to the prefix tables is incremental work; the unprefixed core handles ~90% of typical BIOS code.
Usage
git clone https://github.com/BrentRector/orchard
cd orchard
# Disassemble a binary as Z-80 code:
python -m nibbler z80disasm bios.bin --base 0xFAB8
# Disassemble a specific routine within a larger binary:
python -m nibbler z80disasm bios.bin --base 0xFAB8 --start 0xFB4D --end 0xFB70
The --base argument tells the disassembler the Z-80 address corresponding to byte 0 of the input file. --start and --end (both Z-80 addresses) restrict the output range; useful for examining a single routine within a larger image.
Where it’s been used
The Microsoft SoftCard CP/M 2.23 BIOS at Z-80 $FAB8-$FFFF — disassembling the 15-entry jump table and individual routines like CONOUT to identify the expansion-ROM-window writes (LD HL,$C800) that target Pascal-1.1-class slot cards. See the cpm-videx series.
Roadmap
- CB-prefix decoding — bit operations (
BIT,SET,RES,RLC,RRC,RL,RR,SLA,SRA,SLL,SRL). Roughly 256 sub-opcodes, regular structure. - ED-prefix decoding — extended I/O (
IN r,(C),OUT (C),r), block instructions (LDIR,LDDR,CPIR,CPDR), interrupt mode (IM 0/1/2),RETI/RETN. Roughly 80 sub-opcodes. - DD/FD prefix decoding — IX/IY-indexed addressing. Mostly variants of HL operations with an indexing displacement byte. Tedious but mechanical.
Related tools
- 6502 Disassembler — companion tool for the 6502 side of SoftCard CP/M analysis (the SoftCard’s host machine is an Apple ][, which is 6502-based)
- nibbler — the WOZ-format disk analysis toolkit (the disassemblers happen to share its repository home)