Hex Code Memory Loader

This file is part of and Timex Sinclair Public Domain Library Tape 1005. Download the collection to get this file.
Date: 198x
Type: Program
Platform(s): TS 1000

This program is a hexadecimal machine-code editor and assembler aid for entering Z80 opcodes directly into reserved RAM above RAMTOP. It reads RAMTOP from system variables at addresses 16388–16389, then lets the user type hex byte pairs interactively, displaying them in a grid of 10 columns with a moveable cursor. Commands dispatched via a lookup array F(20) support operations including GO (execute via USR), LOAD (POKE bytes into RAM), SAVE, DELETE, cursor movement (positive and negative), relative jump calculation with asterisk-placeholder adjustment, a DOWNLOAD routine that prepends a self-contained tape-loader header sequence, and a full screen PRINT redisplay. The relative-jump subroutine at line 1300 correctly handles the two’s-complement wrapping for backward branches and accounts for placeholder asterisks in the hex string that represent not-yet-resolved bytes. RAMTOP is manipulated at line 9060 by poking address 16389 to reserve blocks of 256 bytes, after which the program re-saves and restarts itself.


Program Analysis

Program Structure

The program is divided into clearly delineated functional blocks, each occupying a line-number range and labelled with inverse-video REM comments:

Line rangePurpose
10–50Memory reservation check / entry gate
60–141Initialisation: read RAMTOP, build command dispatch table F()
200–360Main input loop: strip spaces, classify input, dispatch or append hex
400–470Cursor print / erase subroutines
500–520Command dispatcher
600–660Append hex bytes to display grid
700–750GO – execute code at RAMTOP via USR
800–890LOAD – POKE hex string into RAM
900–970Move cursor forward
1000–1070Move cursor backward
1100–1195Redisplay entire hex buffer
1300–1390Relative-jump calculation
1400–1460SAVE program
1600–1640DELETE bytes at cursor
2000–2090Asterisk-placeholder adjustment for relative jumps
3000–3130DOWNLOAD – prepend tape-loader stub and POKE all bytes
9000–9110Reserve memory by poking RAMTOP, re-save and restart

Command Dispatch Mechanism

Commands are single characters whose ASCII codes (minus 43) index into the array F(20), populated at lines 131–141. The dispatcher at line 510 is simply:

GOSUB F(CODE I$(1)-43)

The trigger condition at line 300 is CODE I$(1)>=44 — i.e. any character with ASCII code above 43 (comma) that is not a hex digit handled earlier. The mapping is:

CharacterASCIIIndex (ASCII−43)GOSUB targetFunction
,441700GO (execute)
3516 (but 3=hex digit, unreachable)800LOAD
.463(unassigned)
+43(below threshold)
F(6)=800Triggered by ASCII 43+6=49 = ‘1’ — potential conflict with hex digit ‘1’

In practice the hex digits 0–9 and A–F have ASCII codes 48–70. The check at line 300 (CODE I$(1)>=44) means single-character hex digits like ‘,’ (44) would be misrouted. The program sidesteps most conflicts because hex input is always exactly two characters, making LEN I$>1 true and causing the two-character hex path to be taken. However the dispatch table assignments at lines 132–141 use indices 6–18, corresponding to ASCII codes 49–61, which overlap with hex digit characters — this is a latent ambiguity for single-character commands that happen to be hex digits.

RAMTOP and Memory Reservation

The ZX81/TS1000 system variable RAMTOP is a 16-bit little-endian value stored at addresses 16388–16389. Line 60 reads it as:

LET RT=PEEK 16388+256*PEEK 16389

When memory has not yet been reserved, the program branches to line 9000, which pokes address 16389 with 128-NB (where NB is the number of 256-byte blocks). This effectively lowers RAMTOP by NB×256 bytes, carving out space above BASIC for machine code. The program then re-saves itself and calls RUN to restart cleanly with the new RAMTOP in effect.

Hex Buffer and Display

All entered bytes are kept in the string H$ as pairs of ASCII hex characters. Placeholder ** pairs are inserted at the current cursor position CI and later replaced during POKE operations. The display grid at lines 610–650 and 1160–1190 prints pairs separated by spaces in rows of 10, with a line break (two extra spaces) after every 10th byte, creating a 10-column layout. The cursor position is displayed as an inverse > character, computed by:

PRINT AT 2+INT(CI/10), 3*(CI-10*INT(CI/10)); "%>"

This maps CI to a row/column on screen without needing a 2D array.

LOAD Subroutine and POKE Loop

The LOAD routine (lines 800–890) prepends a short Z80 stub to H$:

"3E1EED47FD210040C9"

This is: LD A,1EH / LD I,A / LD IX,4000H / RET — a standard setup sequence. The POKE loop at lines 830–890 steps through H$ two characters at a time, skipping * placeholders, and converts each hex pair to a byte value using the formula:

16*(CODE H$(I)-28) + CODE H$(I+1)-28

This relies on the ZX81 character set where ‘0’ = code 28, ‘A’ = code 38, so CODE digit - 28 gives the numeric value 0–15 for hex digits 0–9 and A–F.

Relative Jump Calculation

The subroutine at lines 1300–1390 calculates the signed byte displacement for a Z80 relative jump (e.g. JR, DJNZ). The user supplies a target cursor index JCI; the displacement is JS = JCI - CI - 1. Before computing, subroutine 2000 counts * placeholder characters between the current position and the target and adjusts JS accordingly, since placeholders occupy space in H$ but not in the final machine code. If the displacement exceeds the range −128 to +127, an error message is displayed. For negative displacements, two’s-complement wrapping is applied: JS = JS + 256. The byte is then encoded back into two hex-digit characters using the same code-offset arithmetic.

DOWNLOAD Routine

Lines 3000–3130 implement a self-contained tape-loader download. It prepends a longer Z80 loader stub to H$:

"00002A04404E23462B118240C5011D0009C1EDB03E1EED47FD210040C9"

This stub reads the byte count from the first two bytes at RAMTOP (little-endian), then performs a block load (LDIR) before the standard setup sequence. The total byte count (hex buffer length minus asterisks, plus 9 bytes for the stub) is computed at lines 3020–3050 and POKEd as a 16-bit little-endian value at RAMTOP and RAMTOP+1.

Notable Techniques

  • Space-stripping loop (lines 220–260) walks I$ index-by-index and removes all spaces using string slicing, avoiding any built-in trim function.
  • The F() dispatch array avoids a long chain of IF/GOTO statements for command routing.
  • Cursor display uses integer division (INT(CI/10)) rather than a modulo operator, since ZX81 BASIC has no MOD.
  • Hex-to-byte conversion exploits the contiguous ZX81 character codes for digits and letters, requiring no lookup table.
  • The ** placeholder convention allows multi-byte instructions to be entered incrementally, with operand bytes filled in later.

Bugs and Anomalies

  • Line 3060 contains a typo: "LENGHT" instead of "LENGTH".
  • The delay loop at lines 9040–9050 (FOR T=1 TO 20: NEXT T) is extremely short and unlikely to provide a meaningful pause on any hardware speed.
  • The command dispatch index calculation (CODE I$(1)-43) overlaps with ASCII codes for hex digit characters when commands are single characters, creating a potential ambiguity for single-character inputs that are also valid hex digits (e.g. ‘1’, ‘2’, ‘3’).
  • The SAVE at line 9100 saves to filename "1024%5" (where %5 is an inverse ‘5’), which encodes an auto-run flag in a non-standard way relative to the BASIC line-number convention.
  • Line 1044’s cursor-move-negative code does not call GOSUB 1100 to refresh the display after moving, whereas the positive-move routine at line 960 does call GOSUB 400 — both do call 400, so display is consistent; this is not a bug.

Content

Appears On

Assembled by Tim Ward from many sources. Contains programs 10211 – 10251.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

  10 PRINT "HAVE YOU RESERVED MEMORY?"
  20 INPUT A$
  30 IF A$="" THEN GOTO 50
  40 IF A$(1)="N" THEN GOTO 9000
  50 CLS 
  60 LET RT=PEEK 16388+256*PEEK 16389
  70 PRINT "RAMTOP= ";RT
  80 PRINT "HEX CODE:"
 100 LET CI=0
 110 LET H$=""
 120 GOSUB 400
 130 DIM F(20)
 131 LET F(1)=700
 132 LET F(6)=800
 133 LET F(7)=900
 134 LET F(8)=1000
 135 LET F(10)=1100
 136 LET F(11)=1200
 137 LET F(12)=1300
 138 LET F(13)=1400
 139 LET F(14)=1500
 140 LET F(18)=1600
 141 LET F(17)=3000
 200 INPUT I$
 210 LET A=0
 220 LET A=A+1
 230 IF A>LEN I$ THEN GOTO 300
 240 IF I$(A)<>" " THEN GOTO 220
 250 LET I$=I$( TO A-1)+I$(A+1 TO )
 260 GOTO 230
 300 IF CODE I$(1)>=44 THEN GOTO 500
 310 LET I$=I$+"**"
 320 LET H$=H$( TO 2*CI)+I$+H$(2*CI+1 TO )
 330 GOSUB 450
 340 GOSUB 600
 350 GOSUB 400
 360 GOTO 200
 400 REM %P%R%I%N%T% %C%U%R%S%O%R
 410 PRINT AT 2+INT (CI/10),3*(CI-10*INT (CI/10));"%>";
 420 RETURN 
 450 REM %U%N%P%R%I%N%T% %C%U%R%S%O%R
 460 PRINT AT 2+INT (CI/10),3*(CI-10*INT (CI/10));" ";
 470 RETURN 
 500 REM %R%O%U%T%I%N%E%S
 510 GOSUB F(CODE I$(1)-43)
 520 GOTO 200
 600 REM %P%R%I%N%T% %A%P%P%E%N%D%E%D% %T%E%X%T
 610 FOR J=1 TO LEN I$/2
 620 PRINT I$(2*J-1 TO 2*J)+" ";
 630 LET CI=CI+1
 640 IF CI=10*INT (CI/10) THEN PRINT "  ";
 650 NEXT J
 660 RETURN 
 700 REM %G%O
 710 CLS 
 720 PRINT "NUMBER OF DATA BYTES?"
 730 INPUT D
 740 CLS 
 750 LET Y=USR (RT+D)
 760 RETURN 
 800 REM %L%O%A%D
 810 LET H$=H$+"3E1EED47FD210040C9"
 820 LET J=RT-1
 830 LET I=-1
 840 LET J=J+1
 850 LET I=I+2
 860 IF I>LEN I$ THEN RETURN 
 870 IF H$(I)="*" THEN GOTO 850
 880 POKE J,16*(CODE H$(I)-28)+CODE H$(I+1)-28
 890 GOTO 840
 900 REM %M%O%V%E% %C%U%R%S%O%R% %P%O%S%I%T%I%V%E%L%Y
 910 GOSUB 450
 920 IF LEN I$=1 THEN LET CM=1
 930 IF LEN I$>1 THEN LET CM=VAL I$(2 TO )
 940 LET CI=CI+CM
 950 IF CI>LEN H$/2 THEN LET CI=LEN H$/2
 960 GOSUB 400
 970 RETURN 
 1000 REM %M%O%V%E% %C%U%R%S%O%R% %N%E%G%A%T%I%V%E%L%Y
 1010 GOSUB 450
 1020 IF LEN I$=1 THEN LET CM=1
 1030 IF LEN I$>1 THEN LET CM=VAL I$(2 TO )
 1040 LET CI=CI-CM
 1050 IF CI<0 THEN LET CI=0
 1060 GOSUB 400
 1070 RETURN 
 1100 REM %P%R%I%N%T
 1110 CLS 
 1120 PRINT "RAMTOP= ";RT
 1130 PRINT "HEX CODE:"
 1140 PRINT " ";
 1150 LET CI=0
 1160 FOR J=1 TO LEN H$/2
 1170 PRINT H$(2*J-1 TO 2*J)+" ";
 1185 LET CI=CI+1
 1187 IF CI=10*INT (CI/10) THEN PRINT "  ";
 1190 NEXT J
 1192 GOSUB 400
 1195 RETURN 
 1300 REM %R%E%L%A%T%I%V%E% %J%U%M%P%S
 1310 LET JCI=VAL I$(2 TO )
 1320 LET JS=JCI-CI-1
 1325 GOSUB 2000
 1330 IF JS>=-128 AND JS<=127 THEN GOTO 1357
 1335 CLS 
 1340 PRINT AT 10,6;"INVALID JUMP SIZE"
 1345 FOR Q=1 TO 20
 1346 NEXT Q
 1350 GOSUB 1100
 1355 RETURN 
 1357 IF JS<0 THEN LET JS=JS+256
 1360 LET X1=INT (JS/16)
 1365 LET X0=JS-16*X1
 1367 LET X$=CHR$ (X1+28)+CHR$ (X0+28)
 1370 LET H$=H$( TO 2*CI)+X$+H$(2*CI+1 TO )
 1375 LET CI=CI+1
 1380 GOSUB 1100
 1390 RETURN 
 1400 REM %S%A%V%E
 1410 PRINT "%S%A%V%E.INPUT PROGRAM NAME.                                        (DEFAULT NAME ""H"")"
 1420 INPUT N$
 1430 IF N$="" THEN LET N$="H"
 1440 SAVE N$
 1450 CLS 
 1460 GOTO 10
 1600 REM %D%E%L%E%T%E
 1610 IF LEN I$=1 THEN LET K=1
 1620 IF LEN I$>1 THEN LET K=VAL I$(2 TO )
 1630 LET H$=H$( TO 2*CI)+H$(2*CI+2*K+1 TO )
 1640 RETURN 
 2000 REM %A%S%T%E%R%I%S%K% %A%D%J%U%S%T
 2010 IF JS<0 THEN LET W$=H$(2*JCI+1 TO 2*CI)
 2020 IF JS>=0 THEN LET W$=H$(2*CI+1 TO 2*JCI)
 2030 LET SC=0
 2040 FOR T=1 TO LEN W$
 2050 IF W$(T)="*" THEN LET SC=SC+1
 2060 NEXT T
 2070 IF JS<0 THEN LET JS=JS+SC/2
 2080 IF JS>0 THEN LET JS=JS-SC/2
 2090 RETURN 
 3000 REM %D%O%W%N%L%O%A%D
 3010 LET LH=0
 3020 FOR I=1 TO LEN H$
 3030 IF H$(I)<>"*" THEN LET LH=LH+1
 3040 NEXT I
 3050 LET LH=LH/2+9
 3060 PRINT AT 21,0;"LENGHT ";LH;" BYTES."
 3070 LET H$="00002A04404E23462B118240C5011D0009C1EDB03E1EED47FD210040C9"+H$
 3080 GOSUB 800
 3090 LET LS=INT (LH/256)
 3100 LET LJ=LH-256*LS
 3110 POKE RT,LJ
 3120 POKE RT+1,LS
 3130 RETURN 
 9000 REM %R%E%S%E%R%V%E% %M%E%M%O%R%Y
 9010 PRINT "HOW MANY 256-BYTE BLOCKS?"
 9020 INPUT NB
 9030 PRINT "AFTER LIST PRESS RUN"
 9040 FOR T=1 TO 20
 9050 NEXT T
 9060 POKE 16389,128-NB
 9070 PRINT USR 1040
 9080 STOP 
 9090 CLEAR 
 9100 SAVE "1024%5"
 9110 RUN 

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

People

No people associated with this content.

Scroll to Top