Z80 Disassembler

Date: 198x
Type: Program
Platform(s): TS 2068

This program implements a complete Z80 CPU disassembler, accepting a start and end address plus an optional offset, then decoding memory contents into human-readable Z80 assembly mnemonics. It handles all major Z80 instruction prefixes including CB (bit operations), DD (IX-indexed), ED (extended), and FD (IY-indexed), with proper multi-byte instruction decoding. Output can be displayed in either hexadecimal or decimal address format, and a printer output option triggers screen-copy pagination via COPY. The disassembler works by extracting the binary bit-field pattern of each opcode (bits broken into groups for x, y, and z fields) and matching them against a large DATA table of mnemonic templates starting at line 3000, using variable substitution characters within the template strings to fill in register names, immediate values, and displacement operands.


Program Analysis

Program Structure

The program is organized into a main loop and a collection of subroutines, with the instruction database encoded in DATA statements. The high-level flow is:

  1. Initialization and user prompts (line 420): start address, end address, hex/decimal display mode, printer option, and address offset.
  2. Main decode loop (lines 440–810): fetch one byte at a time, handle prefix bytes, extract opcode bit-fields, look up the mnemonic template, perform variable substitution, and print.
  3. Output/pagination subroutine (lines 820–880): handles COPY for printer output and page-break counting.
  4. Subroutines for byte-to-hex conversion (lines 120–180), 16-bit address formatting (lines 200–220), displacement decoding (lines 240–340), and V-type operand substitution (lines 360–400).
  5. DATA tables (lines 3000–3345): mnemonic templates indexed by prefix and opcode bit-fields.

Opcode Bit-Field Extraction

Lines 600–620 implement a software bit-unpacker. The opcode byte is converted into an 8-character binary string stored in D$ (LSB first, stored at index 1). Four derived values are then computed:

  • A(1): bits 7–6 (the x field, value 0–3), used as the primary table index.
  • A(2): bits 2–0 (the z field, value 0–7), the secondary index.
  • A(3): bits 5–3 (the y field, value 0–7), used for register/condition selection.
  • A(4): bits 4–3 (the p sub-field, value 0–3), used for register-pair selection.

These map directly to the standard Z80 opcode decoding matrix (x, y, z, p, q fields).

DATA Table Indexing

The mnemonic templates are addressed by the formula RESTORE 3000 + (100*CL + 10*A(1) + A(2)) at line 660, where CL encodes the active prefix (0 = unprefixed, 1 = CB, 2 = ED). This creates a sparse table: line 3000 covers unprefixed x=0,z=0; line 3100 covers CB-prefix x=1,z=0; line 3200 covers ED-prefix x=2,z=0; and so on. Each DATA line holds one or more "pattern","mnemonic" pairs. The pattern string is three characters matching against A$(1) (y-field digit), A$(2) (bit 3), and A$(3) (p sub-field digit), with "-" acting as a wildcard.

Mnemonic Template Variable Substitution

Lines 740–800 scan the matched mnemonic template character by character, replacing placeholder characters with actual operand text. The substitution codes are:

CharacterMeaningData line
!$Register/condition codes indexed by A(3)3333–3336
%Register indexed by A(2)3337
&Register pair, two variants indexed by A(4) and prefix3338–3339
'Push/pop register pair (AF variant)3339
*(HL)/(IX+d)/(IY+d) depending on IN prefix3342
-HL/IX/IY depending on IN prefix3345
.Inline immediate byte (calls subroutine at 360)
/Index displacement (calls subroutine at 240)
..16-bit immediate address via GO SUB 240 path
#Condition code (part of template literal)

The substitution loop at lines 740–800 replaces each placeholder in D$ in-place, expanding the string as needed, then continues scanning from the same position to handle chained substitutions.

Prefix Handling

Lines 550–560 detect prefix bytes before dispatching through the decode matrix. CL records CB (value 1) or ED (value 2) prefix state, while IN records DD (value 1) or FD (value 2) for IX/IY indexing. When both IN>0 and CL>0 (i.e., DDCB or FDCB prefix pair), line 580 pre-fetches the displacement operand before reading the actual opcode, which is correct Z80 behavior for the DDCB/FDCB instruction group where the displacement precedes the final opcode byte.

Hex Conversion Subroutine

The subroutine at line 180 converts a byte value in X into a two-character hex string in X$. It uses arithmetic on the AND operator’s precedence behavior: (48 + digit AND digit <= 9) + (55 + digit AND digit > 9) selects either the ASCII digit (48+) or the uppercase letter (55+, since 55+10=65=’A’). This avoids any IF/THEN branching.

Displacement and Relative Jump Decoding

Line 270 converts the raw displacement byte into a signed integer using the expression (X AND X<128) + (X-256 AND X>127), correctly handling two’s complement sign extension from 8 to full integer. Lines 280–340 compute the absolute target address for JR/DJNZ instructions by adding the displacement to the current program counter (M+1+X+MX), and display both the raw displacement and the resolved address.

Output and Pagination

The output subroutine (lines 820–880) implements a simple three-screen page counter in PC. When printer output is selected, after every three screen-fulls it emits eight blank lines via LPRINT to advance the paper. When printer output is not selected, it prompts the user whether to invoke COPY before clearing the screen and continuing.

Notable Techniques

  • POKE 23658,8 at line 70 sets the system FLAGS2 byte to enable lowercase input, useful for the y/n prompts.
  • DIM A$(PI) at line 100 exploits the fact that INT(PI)=3 to allocate a 3-character string array without writing the literal 3.
  • The sparse DATA table layout (line numbers as lookup keys via RESTORE) avoids any explicit search loop over instruction encodings.
  • The binary string representation in D$ uses STR$ (X+.5 - INT X) to extract the fractional part of successive halvings—a compact way to read individual bits without bitwise operators.
  • The MX offset variable allows the disassembler to display relocated addresses, useful when disassembling code that has been copied to a different memory location for inspection.

Bugs and Anomalies

  • Line 250 references variable Z$ before it is ever assigned in a fresh run; on the first DDCB/FDCB instruction encountered, Z$ may be uninitialized. In practice this is only reached after line 580 has already set Z$, so it functions correctly in the normal decode path.
  • The prompt text at line 420 spells “HEXADECIMAL” as “HEXADECIMAL” in the source but the DATA string reads "<H>EXADECIMAL"—the leading H is part of the angle-bracket option, not a typo in the word itself.
  • Line 3036 encodes arithmetic/logic instructions as "$.", which combines a register-group substitution ($ → one of ADD A, ADC A, SUB, etc.) with an immediate byte fetch (.). The SUB template in DATA line 3336 includes a spurious comma ("SUB,") that would produce output like SUB,42 rather than the standard SUB 42.
  • Instructions with undefined or illegal opcodes will fall through the DATA search loop without finding a match, potentially causing a BREAK or reading garbage from adjacent DATA lines.

Image Gallery

Source Code

   70 POKE 23658,8
   80 PAPER 0:INK 7:BORDER 0:CLS 
  100 LET O=0:LET X=O:LET T=O:DIM A$(PI):DIM A(4):LET M=O:DIM X$(2):GO TO 420
  120 REM CONVERSION
  140 LET M=M+1
  160 LET X= PEEK M
  180 LET X$(1)= CHR$ ((48+ INT (X/16) AND INT (X/16) <=9)+(55+ INT (X/16) AND INT (X/16)>9)):LET X=X- INT (X/16)*16:LET X$(2)= CHR$ ((48+X AND X <=9)+(55+X AND X>9)):RETURN 
  200 LET N=M
  220 LET X= INT (N/256):GO SUB 180:LET M$=X$:LET X=N-256* INT (N/256):GO SUB 180:LET M$=M$+X$:RETURN 
  240 REM DISPLACEMENT
  250 IF IN <>0 AND CL=1 THEN LET M$=Z$:RETURN 
  260 GO SUB 120:PRINT ;X$;:LET X= PEEK M
  270 LET X=(X AND X<128)+(X-256 AND X>127):LET M$=(X$ AND H$="H")+(STR$ X AND H$="D"):RETURN 
  280 GO SUB 260:LET N=M+1+X+MX
  300 IF H$="D" THEN LET M$= STR$ X+" ("+ STR$ N+")"
  320 IF H$="H" THEN LET Z$=X$:GO SUB 220:LET M$=Z$+" ("+M$+")"
  340 LET D$=D$+M$:PRINT TAB 15;D$:GO TO 440
  360 REM V CHANGE
  370 IF O+1> LEN D$ THEN GO TO 400
  380 IF D$(O+1)="." THEN GO SUB 120:PRINT ;X$;:LET M$=X$:GO SUB 120:PRINT ;X$;:LET M$=(X$+M$ AND H$="H")+(STR$ (256* PEEK M+ PEEK (M-1)) AND H$="D"):LET D$=D$( TO O-1)+D$(O+1 TO ):RETURN 
  400 GO SUB 120:PRINT ;X$;:LET M$=(X$ AND H$="H")+(STR$ PEEK M AND H$="D"):RETURN 
  420 PRINT AT 8,8; FLASH 1;"Z80 DISASSEMBLER":INPUT "INPUT START ADDRESS#";M:LET M=M-1:INPUT "INPUT END ADDRESS#";MM:PRINT AT 20,9;"DO YOU WANT":INPUT "<H>EXADECIMAL  OR <D>ECIMAL ADDRESS OUTPUT ";H$:INPUT "PRINTER OUTPUT (y/n) ";p$:CLS :INPUT AT 10,8;"OFFSET #";MX:LET PC=3
  440 LET CL=0:LET IN=CL:IF M+1>MM THEN GO SUB 820:GO TO 420
  460 IF PEEK 23689=2 THEN GO SUB 820
  480 LET M=M+1:LET N=M+MX:IF H$(1)="D" OR H$(1)="d" THEN LET M$= STR$ N:LET O=5- LEN M$:PRINT TAB O;M$;" ";
  500 IF H$(1)="H" THEN GO SUB 220:PRINT M$;" "; 
  520 GO SUB 160
  530 PRINT ;X$;
  540 LET X= PEEK M:IF CL>0 THEN GO TO 580
  550 IF X=203 OR X=237 THEN LET CL=(1 AND X=203)+(2 AND X=237):GO SUB 140:GO TO 530
  560 IF X=221 OR X=253 THEN LET IN=(1 AND X=221)+(2 AND X=253):GO SUB 140:GO TO 530
  570 IF X=118 THEN PRINT TAB 15;"HALT":GO TO 480
  580 IF IN>0 AND CL>0 THEN GO SUB 270:LET Z$=M$:GO SUB 140:PRINT ;X$;:LET X= PEEK M
  590 REM BINARY
  600 LET D$="00000000":FOR O=1 TO 8:LET X=(X/2):LET D$(O)= STR$ (X+.5- INT X):NEXT O
  620 LET A(1)= VAL D$(8)*2+ VAL D$(7):LET A(2)= VAL D$(3)*4+ VAL D$(2)*2+ VAL D$(1):LET A(3)= VAL D$(6)*4+ VAL D$(5)*2+ VAL D$(4):LET A(4)= VAL D$(6)*2+ VAL D$(5):LET A$(1)= STR$ A(3):LET A$(2)=D$(4):LET A$(3)= STR$ A(4)
  640 REM SEARCH
  660 RESTORE 3000+(100*CL+10*A(1)+A(2))
  670 READ D$:FOR X=1 TO 3:IF D$(X)="-" THEN GO TO 700
  680 IF A$(X) <>D$(X) THEN READ D$:GO TO 670
  700 NEXT X:READ D$
  720 REM VAR CHANGE
  730 IF D$( TO 2)="JR" OR D$( TO 2)="DJ" THEN GO TO 280
  740 FOR O=1 TO 14
  750 IF O> LEN D$ THEN GO TO 810
  760 LET B$=D$(O):IF B$ >="!" AND B$ <="'" THEN RESTORE 3300+ CODE B$:LET T=(A(3) AND B$<"%")+(A(2) AND B$="%")+(A(4) AND B$>"%"):FOR N=0 TO T:READ M$:NEXT N:LET D$=D$( TO O-1)+M$+D$(O+1 TO ):LET B$=D$(O)
  770 IF B$="*" OR B$="-" THEN RESTORE 3300+ CODE B$:FOR N=0 TO IN:READ M$:NEXT N:LET D$=D$( TO O-1)+M$+D$(O+1 TO ):LET B$=D$(O)
  780 IF B$="." OR B$="/" THEN GO SUB (360 AND B$=".")+(240 AND B$="/"):LET D$=D$( TO O-1)+M$+D$(O+1 TO )
  800 NEXT O
  810 PRINT TAB 15;D$:GO TO 440
  820 REM OUTPUT
  840 IF P$="N" OR P$="n" THEN BEEP .1,20:INPUT "COPY(y/n)?";M$:IF M$="N" OR M$="n" THEN CLS :RETURN 
  850 COPY :CLS 
  860 LET PC=PC-1:IF PC=0 THEN LET PC=3:FOR X=1 TO 8:LPRINT :NEXT X
  880 RETURN 
 3000 DATA "0--","NOP","1--","EX AF,AF","2--","DJNZ ","3--","JR ","4--","JR NZ,","5--","JR Z,","6--","JR NC,","7--","JR C,"
 3001 DATA "-0-","LD &,..","-1-","ADD -,&"
 3002 DATA "0--","LD (BC),A","1--","LD A,(BC)","2--","LD (DE),A","3--","LD A,(DE)","4--","LD (..),-","5--","LD -,(..)","6--","LD (..),A","7--","LD A,(..)"
 3003 DATA "-0-","INC &","-1-","DEC &"
 3004 DATA "---","INC """
 3005 DATA "---","DEC """
 3006 DATA "---","LD "",."
 3007 DATA "0--","RLCA","1--","RRCA","2--","RLA","3--","RRA","4--","DAA","5--","CPL","6--","SCF","7--","CCF"
 3017 DATA "---","LD "",%"
 3027 DATA "---","$%"
 3030 DATA "---","RET #"
 3031 DATA "-0-","POP '","-10","RET","-11","EXX","-12","JP (-)","-13","LD SP,-"
 3032 DATA "---","JP #,.."
 3033 DATA "0--","JP ..","2--","OUT (.),A","3--","IN A,(.)","4--","EX (SP),-","5--","EX DE,HL","6--","DI","7--","EI"
 3034 DATA "---","CALL #,.."
 3035 DATA "-0-","PUSH '","-1-","CALL .."
 3036 DATA "---","$."
 3037 DATA "0--","RST 00H (0)","1--","RST 08H (8)","2--","RST 10H (16)","3--","RST 18H (24)","4--","RST 20H (32)","5--","RST 28H (40)","6--","RST 30H (48)","7--","RST 38H (56)"
 3107 DATA "0--","RLC %","1--","RRC %","2--","RL %","3--","RR %","4--","SLA %","5--","SRA %","7--","SRL %"
 3117 DATA "---","BIT !,%"
 3127 DATA "---","RES !,%"
 3137 DATA "---","SET !,%"
 3210 DATA "---","IN "",(C)"
 3211 DATA "---","OUT (C),"""
 3212 DATA "-0-","SBC HL,&","-1-","ADC HL,&"
 3213 DATA "-0-","LD (..),&","-1-","LD &,(..)"
 3214 DATA "---","NEG"
 3215 DATA "-0-","RETN","-1-","RETI"
 3216 DATA "0--","IM 0","2--","IM 1","3--","IM 2"
 3217 DATA "0--","LD I,A","1--","LD R,A","2--","LD A,I","3--","LD A,R","4--","RRD","5--","RLD"
 3220 DATA "4--","LDI","5--","LDD","6--","LDIR","7--","LDDR"
 3221 DATA "4--","CPI","5--","CPD","6--","CPIR","7--","CPDR"
 3222 DATA "4--","INI","5--","IND","6--","INIR","7--","INDR"
 3223 DATA "4--","OUTI","5--","OUTD","6--","OTIR","7--","OTDR"
 3333 DATA "0","1","2","3","4","5","6","7"
 3334 DATA "B","C","D","E","H","L","*","A"
 3335 DATA "NZ","Z","NC","C","PO","PE","P","M"
 3336 DATA "ADD A,","ADC A,","SUB,","SBC A,","AND ","XOR ","OR ","CP "
 3337 DATA "B","C","D","E","H","L","*","A"
 3338 DATA "BC","DE","-","SP"
 3339 DATA "BC","DE","-","AF"
 3342 DATA "(HL)","(IX+/)","(IY+/)"
 3345 DATA "HL","IX","IY"
 4000 SAVE "DISASSY" LINE 1

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.