
SINCLAIR’S 8K ROM for the T/S1000 is generally easy to poach upon in machine code. There is almost always a handle — an address you can call — to get the better part of most operating-system functions done for you. Even the floating-point operations are easily accessed. All you need is a good labelling disassembler and/or an annotated listing for the Sinclair ROM, and of course a little machine or assembly programming savvy.
The T/S2000 will be a much more complex machine, with 24K of ROM on board, memory bank switching, and several new BASIC functions. The system includes a function dispatcher that can be called by machine programs to access most BASIC and Input/Output functions, thus removing the burden of knowing your ROM in order to program in Z80 code. Presumably, going through the function dispatcher will give the machine programmer some assurances that future changes in the ROM will not destroy his programs, as the switch to the new ZX81 ROM did back in 1981. Good programming practice will dictate using the function dispatcher for nearly all system calls.
Nevertheless, as a machine programmer, I still want to know my way around a computer’s ROM before I decide whether to fall in love with it, much in the way a hardware hacker wants a schematic. I want an instant disassembler that I can key into a new T/S2000 without the usual wait for the assembly language tools and books to appear. The cure for that is a BASIC program that can be keyed into a brand new 2000 on the first day to churn out an assembly listing of that new ROM on the T/S2040 printer. The result was DISA-Z (listing 1).
BASIC is painfully slow for the job, but to get a printed listing speed hardly matters; the program can run while its user sleeps. Except for the difference in character codes, T/S1000 BASIC is essentially a subset of the 2000 version, so DISA-Z could be written and debugged on the 1000 and then keyed (not loaded) into the 2000, with a reasonable probability of running the first time. Writing a compatible program meant generally avoiding the CHR$ function so that the differences between the 2000’s ASCII and the 1000’s character code would not cause chaos in the program. PEEKs and POKEs of system variables also have to be avoided, because it appears that the 2000 will start its 6K display file just where the system variables of the 1000 begin.
Program Structure
The structure of DISA-Z is shown schematically in Figure 1. Since the data are stored in listed strings, you can run the program. The first and only entry you need to make is the decimal starting address of the code you want to disassemble. DISA-Z then builds a single string (H$) consisting of the address in hexadecimal (lines 350-410), the bytes of code for one instruction (lines 3630-3660), and the Z80 mnemonic for that instruction (lines 970-3490). The string can then be sent either to the screen, your printer or both, and DISA-Z loops back to build another H$ for the next instruction. You terminate the program with the BREAK key.
There are two main tasks in the building of H$. The first, after the address is translated to hex, is to determine the length in bytes of the instruction. One way to do this is to enter a string that contains a number from one to four for each of the possible 256 codes, and then to slice the string to get the instruction length. I tried both that method and the logical sort in lines 420-960. The look-up method, which is faster in machine code, turned out to be about 10 percent slower, so I decided to keep the logical sort, which is a bit longer, but much less subject to typing errors on entry.
0292 DDE1 POP IX
0294 FD4E28 LD C,(IY+28)
0297 FDCB3B7E BIT 7,(IY+3B)
029B 280C JR Z 02A9
029D 79 LD A,C
029E ED44 NEG
02A0 3C INC A
02A1 08 EX AF,AF"
02A2 D3FE OUT FE,A
02A4 E1 POP HL
02A5 D1 POP DE
02A6 C1 POP BC
02A7 F1 POP AF
02A8 C9 RET
02A9 3EFC LD A,FC
02AB 0601 LD B,01
02AD CDB502 CALL 02B5
02B0 2B DEC HL
02B1 E3 EX(SP),HL
02B2 E3 EX(SP),HL
02B3 DDE9 JP(IX)
02B5 ED4F LD R,A
02B7 3EDD LD A,DD
02B9 FB EI
02BA E9 JP(HL)
02BB 21FFFF LD HL,FFFF
02BE 01FEFE LD BC,FEFE
02C1 ED78 IN A,(C)
02C3 F601 OR 01
02C5 F6E0 OR E0
02C7 57 LD D,A
02C8 2F CPL
02C9 FE01 CP 01
02CB 9F SBC A,A
02CC B0 OR B
02CD A5 AND L
02CE 6F LD L,A
02CF 7C LD A,H
02D0 A2 AND D
02D1 67 LD H,A
02D2 CB00 RLC B
02D4 ED78 IN A,(C)
02D6 38ED JR C 02C5
02D8 1F RRA
02D9 CB14 RL H
02DB 17 RLA
02DC 17 RLA
02DD 17 RLA
02DE 9F SBC A,A
02DF E618 AND 18
02E1 C61F ADD A,1F
02E3 322840 LD (4028),A
02E6 C9 RET
The sort for instruction length is simplified by some regularities in the Z80 instruction set. For example, all codes between 63 and 192 are for instructions that are one byte long. It is the Z80 instruction prefixes, particularly the ED prefix, which complicates things, because they only have meaning with certain codes.
A disassembler has to be able to deal with meaningless sequences of code as well as to decode the meaningful ones, so that it doesn’t crash in a patch of garbage or data. I handled the question of meaningless EDs by assigning an instruction length of one to them, whereas the meaningful codes are all either two or four bytes long. DISA-Z then lists “DATA” for the meaningless instructions. I also sorted out any multiple successive occurences of FD and DD prefixes, mainly because they could run H$ to any length and mess up the output format to screen or printer.
When a single FD or DD prefix occurs in front of a code to which it does not apply, the prefix normally has no effect on the instruction, so the prefix is printed but the code is translated as it would be without the prefix. As far as I know, this is the way the Z80 operates, with the probable exception of the fabled “undocumented” prefixed instruction.
I’ve tested most of the undocumented instructions on my single-stepper and discovered that FD or DD will change an eight-bit register operation on either the H or L registers to the low (L) or high (H) eight bits of IX or IY. I have omitted these because they appear to me to be vastly impractical, given the penalty of double code length and especially because of the limitations on dealing with the operating system (ROM) and the IX and IY registers in the T/S1000. It is also unlikely that disassembly of the T/S2000 ROM would require anything so risqué. Nevertheless, it would not be difficult to add a few extra mnemonics for codes such as LD HY,A (load the contents of A into the high eight bits of IY) or LD B,LX (load the contents of the low eight bits of IX into B), whose codes would be FD67 and DD45.
10 REM *********************** DISA Z
20 REM *********************** A Z80 DISASSEMBLER FOR TS COMPUTERS
30 REM ***********************
40 REM *********************** COPYRIGHT,1983 RAY KINGSLEY
50 REM *********************** RUN AND ENTER START ADDRESS IN DECIMAL
60 REM ***********************
70 REM ***********************
90 REM DATA STRINGS
100 LET O$="ADDADCSUBSBCANDXOROR CP "
110 LET D$="BCDEHLSPAFIXIY"
120 LET B$="BITRESSET"
130 LET C$="RSLRC AL"
140 LET R$="BCDEHLXA"
150 LET E$="NZZ NCC POPEP M "
160 LET F$="RLCARRCARLA RRA DAACPL SCF CCF "
170 LET G$="LDCPINOT"
180 REM FOR TS2000 SET TS2=1
190 LET TS2=0
200 LET NCOD=28+20*TS2
220 GOTO 3500
230 REM OCTAL DIGITS
240 FOR K=0 TO 3
250 IF X0>=K*64 AND X0<(K+1)*64 THEN LET X=X0-K*64
260 NEXT K
270 LET C=INT (X/8)
280 LET T=X-C*8
290 RETURN
295 REM BYTE HEXER
300 LET X=PEEK J
310 LET Y=INT (X/16)
320 LET Z=X-Y*16
330 LET H$=H$+CHR$ (NCOD+Y+7*(Y>9)*TS2)+CHR$ (NCOD+Z+7*(Z>9)*TS2)
340 RETURN
350 REM ADDRESS HEXER
360 LET X=INT (J/256)
370 GOSUB 310
380 LET X=J-256*X
390 GOSUB 310
400 LET H$=H$+" "
410 RETURN
420 REM GET INSTRUCTION LENGTH
430 LET L=0
440 IF X<>221 AND X<>253 THEN GOTO 540
450 IF L THEN RETURN
460 GOSUB 480
470 GOTO 440
480 LET L=1
490 LET J=J+1
500 GOSUB 300
510 LET J=J-1
520 LET H$=H$( TO LEN H$-2)
530 RETURN
535 REM I-LENGTH FOR PREFIXES
540 IF L THEN LET L=L+((X>51) AND (X<55) OR X=203)
550 IF X=203 THEN GOTO 940
560 IF X<>237 THEN GOTO 710
570 GOSUB 480
580 IF X<64 OR X>188 THEN RETURN
590 IF X<160 AND X>123 THEN RETURN
600 IF X<124 THEN GOTO 630
610 IF X-INT (X/8)*8<5 THEN GOTO 950
620 RETURN
630 IF X=78 OR X=102 OR X=110 OR X=112 OR X=113 OR X=118 OR X=119 THEN RETURN
640 LET W=X-67
650 IF NOT (W-INT (W/8)*8) THEN GOTO 920
660 LET W=X-76
670 IF W>=0 AND NOT (W-INT (W/8)*8) THEN RETURN
680 LET W=X-85
690 IF W>=0 AND NOT (W-INT (W/8)*8) THEN RETURN
700 GOTO 950
705 REM WITHOUT PREFIXES
710 IF X<64 OR X>191 THEN GOTO 740
720 IF L THEN LET L=L+(X-8*INT (X/8)=6)
730 GOTO 950
740 IF X>191 THEN GOTO 830
750 IF Z=1 THEN GOTO 920
760 LET W=X-34
770 IF W>=0 AND NOT (W-INT (W/8)*8) THEN GOTO 920
780 LET W=X-6
790 IF W>=0 AND NOT (W-INT (W/8)*8) THEN GOTO 940
800 LET W=X-16
810 IF W>=0 AND NOT (W-INT (W/8)*8) THEN GOTO 940
820 GOTO 950
830 IF X=195 OR X=205 THEN GOTO 920
840 IF X=211 OR X=219 THEN GOTO 940
850 LET W=X-194
860 IF NOT (W-INT (W/8)*8) THEN GOTO 920
870 LET W=W-2
880 IF NOT (W-INT (W/8)*8) THEN GOTO 920
890 LET W=W-2
900 IF NOT (W-INT (W/8)*8) THEN GOTO 940
910 GOTO 950
920 IF L=2 THEN GOTO 940
930 LET L=L+1
940 LET L=L+1
950 LET L=L+1
960 RETURN
970 REM MNEMONICS
980 LET H$=H$+" "
990 IF LEN H$<14 THEN GOTO 980
1000 IF X0=118 THEN GOTO 1290
1010 GOSUB 230
1020 IF X0<64 OR X0>191 THEN GOTO 1400
1030 IF X0>127 THEN GOTO 1310
1035 REM EIGHT-BIT REG LDS.
1040 LET P$=R$(C+1)
1050 LET Q$=R$(T+1)
1060 IF P$="X" OR Q$="X" THEN GOSUB 1170
1070 LET H$=H$+"LD "+P$+","+Q$
1080 RETURN
1085 REM ADD POINTER REGS TO H$
1090 IF NOT DD THEN GOTO 1120
1100 LET H$=H$+"IX"
1110 RETURN
1120 IF NOT FD THEN GOTO 1150
1130 LET H$=H$+"IY"
1140 RETURN
1150 LET H$=H$+"HL"
1160 RETURN
1165 REM CHECK FOR FD OR DD
1170 IF FD OR DD THEN GOTO 1210
1180 IF P$="X" THEN LET P$="(HL)"
1190 IF Q$="X" THEN LET Q$="(HL)"
1200 RETURN
1205 REM ADD INDEX REGS TO H$
1210 IF FD THEN LET W$="(IY+"
1220 IF DD THEN LET W$="(IX+"
1230 IF P$="X" THEN LET P$=W$+H$(10 TO 11)+")"
1240 IF Q$="X" THEN LET Q$=W$+H$(10 TO 11)+")"
1250 RETURN
1260 GOSUB 1170
1270 LET H$=H$+P$+","+R$(1+T)
1280 RETURN
1290 LET H$=H$+"HLT"
1300 RETURN
1310 REM ARITH/LOGIC OPS
1320 LET I=C*3+1
1330 LET Q$=R$(T+1)
1340 GOSUB 1170
1350 LET I$=" "
1360 IF I<7 OR I=10 THEN LET I$=" A,"
1370 IF I>18 THEN LET I$=""
1380 LET H$=H$+O$(I TO I+2)+I$+Q$
1390 RETURN
1395 REM 1ST AND 4TH GROUPS
1400 IF X0=203 THEN GOTO 3170
1410 IF X0=253 OR X0=221 THEN GOTO 3420
1420 IF X0>191 THEN GOTO 1570
1425 REM 1ST GROUP
1430 IF T=1 THEN GOTO 2020
1440 IF T=3 THEN GOTO 2240
1450 IF T=4 OR T=5 THEN GOTO 2300
1460 IF T=2 THEN GOTO 2370
1470 IF NOT T THEN GOTO 2080
1480 IF T<>6 THEN GOTO 1540
1490 LET Q$=R$(C+1)
1500 GOSUB 1170
1510 LET W=4*(FD OR DD)
1520 LET H$=H$+"LD "+Q$+","+H$(8+W TO 9+W)
1530 RETURN
1540 LET W=4*C+1
1550 LET H$=H$+F$(W TO W+3)
1560 RETURN
1570 REM DIRECT ARITHMETIC
1580 IF T<>6 THEN GOTO 1660
1590 LET I=3*C+1
1600 LET I$=" "
1610 IF I<7 OR I=10 THEN LET I$=" A,"
1620 IF I>18 THEN LET I$=""
1630 LET H$=H$+O$(I TO I+2)+I$
1640 LET H$=H$+H$(8 TO 9)
1650 RETURN
1660 REM 4TH GROUP
1670 IF X0=237 THEN GOTO 2700
1680 IF T=1 AND NOT (C-INT (C/2)*2) THEN GOTO 1920
1690 IF T=7 THEN GOTO 1990
1700 IF T=5 AND NOT (C-INT (C/2)*2) THEN GOTO 1940
1710 LET FL=0
1720 IF X0=205 OR T=4 THEN GOSUB 1830
1730 IF X0=195 OR T=2 THEN GOSUB 1800
1740 IF X0=201 OR NOT T THEN GOSUB 1870
1750 IF NOT T OR T=2 OR T=4 THEN GOSUB 1890
1760 IF T=3 AND NOT FL THEN GOTO 2510
1770 IF T=1 AND NOT FL THEN GOTO 2610
1780 IF FL THEN GOSUB 1850
1790 RETURN
1795 REM MAIN CONDITIONALS
1800 LET H$=H$+"JP "
1810 LET FL=1
1820 RETURN
1830 LET H$=H$+"CALL "
1840 GOTO 1810
1850 LET H$=H$+H$(10 TO 11)+H$(8 TO 9)
1860 RETURN
1870 LET H$=H$+"RET "
1880 GOTO 1810
1890 LET H$=H$+E$(2*C+1 TO 2*(C+1))
1900 IF NOT (C=1 OR C=3 OR C>5) THEN LET H$=H$+" "
1910 RETURN
1915 REM GROUP 4:C EVEN,T=1 OR 5
1920 LET H$=H$+"POP "
1930 GOTO 1950
1940 LET H$=H$+"PUSH "
1950 IF C=6 THEN LET C=8
1960 IF C=4 THEN LET C=C+6*(FD OR DD)
1970 LET H$=H$+D$(1+C TO 2+C)
1980 RETURN
1985 REM GROUP 4:T=7
1990 LET W=INT (C/2)
2000 LET H$=H$+"RST "+STR$ (W)+STR$ (8*(C-2*W))
2010 RETURN
2020 REM GROUP 1:T=1
2030 IF C-2*INT (C/2) THEN GOTO 2060
2040 LET H$=H$+"LD "+D$(C+1 TO C+2)+","
2050 GOTO 1850
2060 LET H$=H$+"ADD HL,"+D$(C TO C+1)
2070 RETURN
2080 REM GROUP 1:T=0 (JRS)
2090 IF C=0 THEN LET H$=H$+"NOP"
2100 IF C=1 THEN LET H$=H$+"EX AF,AF"""
2110 IF C=2 THEN LET H$=H$+"DJNZ"
2120 IF C>1 THEN GOTO 2140
2130 RETURN
2140 IF C>2 THEN LET H$=H$+"JR "
2150 LET W=C*2-7
2160 IF C>3 THEN LET H$=H$+E$(W TO W+1)
2170 IF NOT (C-2*INT (C/2)) THEN LET H$=H$+" "
2180 LET W=(PEEK (J-1) AND (PEEK (J-1)<128))+((PEEK (J-1)-256) AND (PEEK (J-1)>127))
2190 LET J0=J
2200 LET J=J+W
2210 GOSUB 350
2220 LET J=J0
2230 RETURN
2240 REM GROUP 1:T=3
2250 LET Q$="INC "
2260 IF C-2*INT (C/2) THEN LET Q$="DEC "
2270 LET W=2*INT (C/2)+1
2280 LET H$=H$+Q$+D$(W TO W+1)
2290 RETURN
2300 REM GROUP 1:T=4 OR 5
2310 LET Q$=R$(C+1)
2320 GOSUB 1170
2330 LET P$="INC "
2340 IF T-2*INT (T/2) THEN LET P$="DEC "
2350 LET H$=H$+P$+Q$
2360 RETURN
2370 REM GROUP 1:T=2
2380 LET H$=H$+"LD "
2390 IF NOT (C-2*INT (C/2)) THEN GOTO 2460
2400 IF C=5 THEN GOSUB 1090
2410 IF C=5 THEN GOTO 2430
2420 LET H$=H$+"A"
2430 IF C>3 THEN LET H$=H$+",("+H$(10 TO 11)+H$(8 TO 9)+")"
2440 IF C<4 THEN LET H$=H$+",("+D$(C TO C+1)+")"
2450 RETURN
2460 IF C<4 THEN LET H$=H$+"("+D$(C+1 TO C+2)+"),"
2470 IF C>3 THEN LET H$=H$+"("+H$(10 TO 11)+H$(8 TO 9)+"),"
2480 IF C=4 THEN GOTO 1090
2490 LET H$=H$+"A"
2500 RETURN
2510 REM GROUP 4:T=3
2530 IF C=2 THEN LET H$=H$+"OUT "+H$(8 TO 9)+",A"
2540 IF C=3 THEN LET H$=H$+"IN A,"+H$(8 TO 9)
2550 IF C=4 THEN LET H$=H$+"EX(SP),HL"
2560 IF C=5 THEN LET H$=H$+"EX DE,HL"
2580 IF C=6 THEN LET H$=H$+"DI"
2590 IF C=7 THEN LET H$=H$+"EI"
2600 RETURN
2610 REM GROUP 4:T=1
2620 IF C=3 THEN LET H$=H$+"EXX"
2630 IF C=5 THEN LET H$=H$+"JP("
2640 IF C=7 THEN LET H$=H$+"LD SP,"
2650 IF C<5 THEN RETURN
2660 GOSUB 1090
2670 IF C=7 THEN RETURN
2680 LET H$=H$+")"
2690 RETURN
2700 REM ED PREFIXES TO 3610
2710 IF L=1 THEN GOTO 3460
2720 LET X0=PEEK (J-L+1)
2730 GOSUB 230
2740 IF X0<160 OR X0>187 THEN GOTO 2850
2750 IF T>3 THEN RETURN
2760 IF T=3 AND C<6 THEN LET Q$="OUT"
2770 IF T=3 AND C<6 THEN GOTO 2800
2780 LET W=2*T+1
2790 LET Q$=G$(W TO W+1)
2800 IF (C-2*INT (C/2)) THEN LET Q$=Q$+"D"
2810 IF NOT (C-2*INT (C/2)) THEN LET Q$=Q$+"I"
2820 IF C>5 THEN LET Q$=Q$+"R"
2830 LET H$=H$+Q$
2840 RETURN
2850 IF X0<64 OR X0>123 THEN RETURN
2860 IF T>1 THEN GOTO 2910
2870 LET Q$=R$(C+1)
2880 IF T THEN LET H$=H$+"OUT(C),"+Q$
2890 IF NOT T THEN LET H$=H$+"IN "+Q$+",(C)"
2900 RETURN
2910 LET W=INT (C/2)
2920 IF T<>2 THEN GOTO 2960
2930 IF C-2*W THEN LET H$=H$+"ADC HL,"+D$(2*W+1 TO 2*W+2)
2940 IF NOT (C-2*W) THEN LET H$=H$+"SBC HL,"+D$(2*W+1 TO 2*W+2)
2950 RETURN
2960 IF T<>3 THEN GOTO 3020
2970 IF C-2*W THEN GOTO 3000
2980 LET H$=H$+"LD("+H$(12 TO 13)+H$(10 TO 11)+"),"+D$(2*W+1 TO 2*W+2)
2990 RETURN
3000 LET H$=H$+"LD "+D$(2*W+1 TO 2*W+2)+",("+H$(12 TO 13)+H$(10 TO 11)+")"
3010 RETURN
3020 IF T<>7 THEN GOTO 3120
3030 IF C>3 THEN GOTO 3090
3040 IF C=2*W THEN LET Q$="I"
3050 IF C<>2*W THEN LET Q$="R"
3060 IF C>1 THEN LET H$=H$+"LD A,"+Q$
3070 IF C<2 THEN LET H$=H$+"LD "+Q$+",A"
3080 RETURN
3090 IF C=4 THEN LET H$=H$+"RRD"
3100 IF C=5 THEN LET H$=H$+"RLD"
3110 RETURN
3120 IF T=6 AND C<4 AND C<>1 THEN LET H$=H$+"IM "+STR$ ((C>0)*(C-1))
3130 IF X0=68 THEN LET H$=H$+"NEG"
3140 IF X0=69 THEN LET H$=H$+"RETN"
3150 IF X0=77 THEN LET H$=H$+"RETI"
3160 RETURN
3170 REM BIT OPS
3180 LET CB=1
3190 LET X0=PEEK (J-1)
3200 LET W=INT (X0/64)
3210 LET X0=X0-W*64
3220 IF W THEN GOTO 3350
3230 LET B1=1+INT (X0/32)
3240 LET B2=INT (X0/16)
3250 LET X0=X0-B2*16
3260 LET B3=INT (X0/8)+3
3270 LET B2=B2+5
3280 LET X0=1+X0-(B3-3)*8
3290 LET Q$=R$(X0)
3300 GOSUB 1170
3310 LET H$=H$+C$(B1)+C$(B3)+C$(B2)
3320 IF B2<>6 THEN LET H$=H$+" "
3330 LET H$=H$+Q$
3340 RETURN
3350 LET W=3*W-2
3360 LET D=INT (X0/8)
3370 LET X0=X0-D*8+1
3380 LET Q$=R$(X0)
3390 GOSUB 1170
3400 LET H$=H$+B$(W TO W+2)+" "+STR$ (D)+","+Q$
3410 RETURN
3420 REM FD,DD PREFIXES
3430 LET DD=X0=221
3440 LET FD=X0=253
3450 IF L>1 THEN GOTO 3480
3460 LET H$=H$+"DATA"
3470 RETURN
3480 LET X0=PEEK (J-L+1)
3490 GOTO 1000
3500 REM MAIN LOOP
3510 INPUT J
3520 LET H$=""
3530 LET P$=H$
3540 LET Q$=H$
3550 LET DD=0
3560 LET FD=DD
3570 LET CB=DD
3580 GOSUB 350
3590 GOSUB 300
3600 GOSUB 420
3610 LET H$=H$( TO LEN H$-2)
3620 LET X0=PEEK J
3630 FOR K=1 TO L
3640 GOSUB 300
3650 LET J=J+1
3660 NEXT K
3670 GOSUB 970
3680 LPRINT H$
3700 GOTO 3520
3710 SAVE "DISA z"
3720 LIST
Listing 1. The DISA Z program.

Disassembling Z80 machine code is remarkably more simple than you might think from looking at a listing of all those mnemonics, which often appear as jumbled as the contents of a programmer’s desktop. The least helpful listing is the alphabetical one that often accompanies assemblers. The listing in your T/S1000 manual is one of the better ones in that it makes the command structure versus numeric code about half clear. All those eight-bit register LDs from code 64 (40 hex) and the ADDs through CPs that follow show a logical pattern that lends itself easily to programming a dissassembler. It’s the first and last blocks of 64 instructions that look like trouble.
It was when I first saw an octal listing of Z80 instructions that the full structure of the mnemonics set became apparent. With hexadecimal and binary (to say nothing of decimal) already on your plate, you may welcome another number system like a second mother-in-law, but if you’ll bear with it briefly, you’ll see how DISA-Z sorts to crank out the mnemonics that go with machine code, without even requiring you to amputate your thumbs in order to count or calculate. The Z80 mnemonics are organized according to bit fields that correspond to the digits of the code in octal, but the digits are not always used in sequence.
Figure 2 shows how a binary byte is split up for expression in hexadecimal and octal. The first four bits evaluate as one hex digit and the last four as the other. It takes three binary bits to make one octal digit, so the highest octal digit is left one bit short and takes values from 0 to 3 rather than the full range from 0 to 7. Octal values from 000 to 377 scan the byte values from 0 to 255.

The high digit, from zero to three, distinguishes the four groups of mnemonics with codes from 0 to 63, 64 to 127, 128 to 191, and 192 to 255. The second group is the eight-bit register loads, and the third includes the arithmetic and logical operations between eight-bit registers. These are easy to disassemble because each of the eight registers and eight operations corresponds to one of the eight octal digits.
To make sense of the first and last blocks of instructions, the numerical sequence doesn’t help, but if you regroup the instructions according to the lowest digit, so that all instructions ending in zero are put together, all those ending in 1 together, and so on, then all of the 3X0 instructions are conditional forms of the REI instructions, all the 3X2s are conditional jumps, and all the 3X4s are conditional calls. The eight conditions themselves (in E$) are a function of the second octal digit, the X.
Other groups of instruction types vary with whether the X is even or odd, often with a regular group alternating with an irregular one, but just about all the order in the system can be extracted by looking at the codes in octal form. DISA-Z first sorts on the high octal digit of the code it is reading and then derives the second and the low digit as the variables C and T (lines 230-290), on which the details of the mnemonic sort are based.
When a group is irregular, like the 0X7 group, then the value of X is simply used to slice the mnemonic out of a data string like F$=”RLCARRCARLA RRA DAA CPL SCF CCF” (line 160), taking four characters at a time.
The CB (203 decimal) prefix redefines almost all of the code values in a way that is easy to sort for the mnemonics BII, RES and SET. (Consult again the appendix on the character set in your T/S instruction book.) The block of the first 64 codes contains eight more that are not normally defined in the Zilog canon; these are CB 30 through CB 37. In fact all of these instructions “work” in the sense that they rotate the proper register values left and move a 1 into the vacated right bit, effectively replacing the value X in the register with the value 2X+1. Since I decided to build this group of mnemonics from the string RSLRC-AL (one of the first pair of letters, one of the second pair, and one of the last four), it was easier to christen this group of mnemonics SLL (shift left logical) and to go ahead and disassemble it rather than to pretend that the instruction didn’t exist and wouldn’t affect the code. You’ll be able to fill in these extra mnemonics in the gap in your T/S instruction book, starting with SLL B.
The FD and DD prefixes affect only those instructions that involve (HL) or HL, ignoring the undocumented instructions mentioned earlier (and which affect any mnemonics involving H or L). DISA-Z sets a variable flag called DD or FD for these prefixes and then postpones any action until the (HL) pointer or HL pair are processed. If the undocumented instructions were disassembled too, the H and L registers would have to be processed separately to check for the DD and FD flags.
The register-pair names are contained in the D$ (line 110) and the eight-bit-register names are contained in the R$ (line 140). Notice that the register names begin with the B and C registers, and that the A and AF-pair are the last in their sets. This order corresponds to the way the registers are referred to by the second or third octal digit of the instruction byte. Zero refers to the B register or the BC pair, and a 7 to the A register or the AF (or SP) pair.
Using DISA-Z
The variable TS2 in line 190 must be set to one for the T/S2068 and to zero for the 1000 or 1500. TS2 affects the single use of the CHR$ function in line 330 to adjust for ASCII or “Clive” code.
If you want DISA-Z to print to your screen, then change line 3680 to PRINT H$ and add a line 3690 to control the scroll. On the T/S2068, the line should be POKE 23692,255 (but check your instruction book to make sure that this is still the SCROLL COUNT variable). On the T/S1000, the line should be IF PEEK 16442=2 THEN SCROLL, and a line 210 SLOW is necessary to see the screen.
Your BASIC ROM on the 2000 will begin at address 0 just as it does on the T/S1000, but it will run for 16K (to 3FFF hex) rather than 8K. Both ROMs include a number of data files, which are not Z80 code, but which DISA-Z will merrily disassemble as if they were. Until the map of the ROM is known, it is you the reader who will have to recognize these patches of Z80 nonsense and to work them out as address files, ASCII files or system data. On the T/S1000, the instructions RST 08 and RST 28 are also followed by one (for 08) or more (to the first 34 hex) bytes of data; DISA-Z also misses these and can be occasionally tricked by such bytes into misreading an instruction or two before it locks back onto the correct sequence. I would have corrected for this feature if I could have been sure that the 2000 would make the same use of the RSTs as the 1000. When you find out you can modify the code at line 2000 to allow for the data bytes.
Not having yet seen a live T/S2068, I am not certain how to get at the remaining 8K of ROM, known as SYSTEM ROM, for disassembly. SYSTEM ROM will be bank-switched into the 0-8K memory block, but it is unlikely that you will be able to run a BASIC program like DISA-Z after switching it in. Those of you who can handle machine code might try a short LDIR routine to copy SYSTEM ROM to a higher block of RAM that can be active along with the full BASIC interpreter. You can then change lines 360 and 2200 to subtract the offset from the address so that your disassembly is printed as if it began at address 0.
You might also want to search your instruction book for the RAM address of the friendly function dispatcher and to disassemble that. We all look forward to reading about your discoveries on these pages.
Media
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.