Machine Code Monitor

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

This program implements a machine code monitor for examining, entering, saving, and running memory contents. It operates in either decimal or hexadecimal mode (toggled via Mode 5), and uses BASIC subroutines at lines 20–70 to format one- or two-byte values as fixed-width strings in either base. At initialisation, 69 bytes of Z80 machine code are POKEd into the printer buffer area starting at address 23296; these routines handle scroll-up (called via RANDOMIZE USR 23335) and scroll-down (RANDOMIZE USR 23350) operations within the examine display, as well as a character conversion helper. The examine mode (Mode 1) displays 20 memory locations simultaneously, allows cursor movement with keys 6 and 7, and supports in-place byte editing.


Program Analysis

Program Structure

The program is organized into clearly delimited subroutine blocks, each introduced by a REM section. Execution begins at line 10 with a GO TO 9000 that bypasses all subroutines and jumps to the initialization block.

LinesPurpose
20–40Format 1 byte as decimal or hex string in a$
60–70Format 2 bytes (address) as decimal or hex string
110–180Input and validate a number in current base; result in n
200–220Get a 1- or 2-byte value with range check
1000–1210Mode 1: Examine/alter memory
2000–2040Mode 2: Enter machine code bytes sequentially
3000–3060Mode 3: Save a code block to tape with verify
4000–4060Mode 4: Run machine code via PRINT USR n
5000–5040Mode 5: Toggle hex/decimal mode (flag h)
6000–6120Mode 6: Convert between hex and decimal
8000–8060Main menu with READ-driven option list
9000–9230Initialisation: set colours, POKEd machine code, jump to menu

Initialisation and Machine Code Installation

Lines 9010–9030 set up the display environment (blue paper, white ink, border) and disable the cursor via POKE 23658,8. Sixty-nine bytes of Z80 code are then READ from DATA statements at lines 9200–9230 and POKEd into the printer buffer area starting at address 23296.

The three installed routines serve specific roles within the monitor:

  • 23296 — Character/nibble conversion helper (18 bytes); converts a byte value into two printable hex digit characters.
  • 23314 — Scroll the examine display up by one line (21 bytes); called via RANDOMIZE USR 23335 at line 1150 (23335 = 23314 + 21, pointing into the next routine — note the entry point used is actually within the block; 23335 is the address of the scroll-down entry).
  • 23335 — Scroll display down; called via RANDOMIZE USR 23350 at line 1180.

Using the printer buffer (nominally 23296–23366) as scratch RAM for machine code is a common technique, as this region is not used during normal screen operations.

Hex/Decimal Formatting Subroutines

The flag variable h governs all number formatting and input throughout the program. When h=0 (decimal mode), line 20 uses STR$ and string slicing to right-justify a value in a fixed-width field. When h=1 (hex mode), lines 30–40 perform a manual base-16 conversion using repeated INT (a/16) and modulo arithmetic, placing characters into a pre-allocated string a$. The expression CHR$ (b+48+7*(b>9)) is the standard Sinclair idiom for mapping a nibble 0–15 to '0'–'9' or 'A'–'F'.

Number Input and Validation

The input subroutine at lines 110–180 reads a LINE input into b$ and manually parses each character. In decimal mode (lines 140–160), it checks that each digit is in range 0–9, then accumulates n using positional powers of 10: b*10^(LEN b$-x). In hex mode (lines 170–180), it validates against the hex character range and uses powers of 16 instead. The condition on line 170 — b<0 OR b>9 AND b<17 OR b>22 — relies on BASIC operator precedence: AND binds tighter than OR, so this correctly accepts digits 0–9 (codes 0–9) and letters A–F (codes 17–22, i.e. CODE - 48 for ‘A’–’F’).

Examine Memory Mode (Mode 1)

The examine display shows 20 memory locations simultaneously, starting at base address l, with the current cursor position tracked in c (0–19). Navigation uses keys 6 (forward) and 7 (backward), mirroring the conventional Spectrum cursor key assignments. Scrolling is achieved by calling the POKEd machine code rather than redrawing the full screen, keeping display updates fast. The PRINT AT statements use two fixed columns — column 0 for the address label and column 22 for the contents label — with the address and byte value printed at column 9.

Line 1200 is a cursor-clearing helper that uses a multi-argument AT form: PRINT AT c+2,0;TAB 9;AT c+2,22,: — the trailing comma and colon suppress output after clearing those positions.

Menu Dispatch

The menu at lines 8000–8060 uses a compact dispatch technique: after reading a keypress and converting it to a digit a, line 8050 executes GO SUB A*1000, where the multiplication directly computes the target line number (1000–6000) for each mode. The menu text is held in a DATA block at line 8060 and re-READ each time the menu is displayed via RESTORE.

Notable Techniques and Idioms

  • Shared error prompt variable: a$ doubles as both the input prompt and the error message. On invalid input, a$ is set to an error string and the subroutine loops back to line 130, displaying the error as the next prompt.
  • Global string constants: z$ holds "Start Address" and y$ holds "Out of range; try again", set at line 9020. These are used as default prompts throughout to avoid repeating long literals.
  • Mode 4 return value: PRINT USR n at line 4060 executes the machine code and displays whatever integer the Z80 routine returns in the BC register pair. This is a convenient way to invoke code while remaining in BASIC.
  • Hex conversion preserving state: Mode 6 saves and restores the global h flag in a temporary variable t, allowing conversions to be performed independently of the current display mode.
  • Boundary guard in Mode 2: Line 2040 uses LET l=l+1*(l<65535) to increment the address pointer without wrapping past 65535, avoiding an out-of-range POKE.

Potential Bugs and Anomalies

  • Line 60 formats a 2-byte (address) value but falls through to line 40 via GO TO 40, which uses a 2-character string a$=" " initialised at line 30 — however line 70 sets a$=" " (4 characters) before branching to line 40, so the 2-byte path is correct. The 1-byte path (line 30) initialises a 2-character field, which is correct for values 0–255 in hex.
  • The input parser on lines 160 and 180 use LET n=INT n+...: the INT is applied only to the existing value of n, not to the full accumulated result, which may produce rounding artefacts for large values due to floating-point arithmetic. This is a known limitation of pure-BASIC number parsing on this platform.
  • Line 210 calls GO SUB 120 rather than GO SUB 110, meaning the first prompt shown is always a$ as set by the caller rather than the default “Invalid number” message — this is intentional, allowing the caller to supply a custom prompt.

Content

Appears On

One of the largest single-tape collections anywhere, with over 40 programs spanning flight planning, satellite tracking, hydrology, Forth programming, a 17-game mega-pack, and a complete calligraphic font renderer. A snapshot of a thriving Texas user group at its peak.
The foundation of Tony Willing's numbered library series — play Breakout, write documents in a 64-column word processor, morph a triangle into a square with flicker-free animation, or run a complete fig-FORTH system. Fifty programs covering every category.

Related Products

Related Articles

Related Content

Image Gallery

Source Code

    1 REM MONITOR PROGRAM
    2 REM 
   10 GO TO 9000
   11 REM 
   12 REM Format 1 byte
   13 REM 
   20 IF NOT h THEN LET a$="  "+STR$ a: LET a$=a$(LEN a$-2 TO ): RETURN 
   30 LET a$="  "
   40 FOR x=LEN a$ TO 1 STEP -1: LET b=a-INT (a/16)*16: LET a$(x)=CHR$ (b+48+7*(b>9)): LET a=INT (a/16): NEXT x: RETURN 
   50 REM 
   51 REM Format 2 bytes
   52 REM 
   60 IF NOT h THEN LET a$="    "+STR$ a: LET a$=a$(LEN a$-4 TO ): RETURN 
   70 LET a$="    ": GO TO 40
  100 REM 
  101 REM Input n
  102 REM 
  110 LET a$="Invalid number; try again"
  120 REM 
  121 REM Start here
  122 REM 
  130 LET n=0: INPUT (a$+" "); LINE b$
  140 FOR x=1 TO LEN b$: LET b=CODE b$(x)-48: IF h THEN GO TO 170
  150 IF b<0 OR b>9 THEN GO TO 110
  160 LET n=INT n+b*10^(LEN b$-x): NEXT x: RETURN 
  170 IF b<0 OR b>9 AND b<17 OR b>22 THEN GO TO 110
  180 LET b=b-7*(b>9): LET n=INT n+b*16^(LEN b$-x): NEXT x: RETURN 
  200 REM 
  201 REM Get 1
  202 REM 
  210 GO SUB 120: IF n<0 OR n>65535 THEN LET a$=y$: GO TO 210
  220 RETURN 
 1000 REM 
 1001 REM EXAMINE
 1002 REM 
 1010 CLS : PRINT INVERSE 1;"M"; INVERSE 0;"enu,"; INVERSE 1;"N"; INVERSE 0;"ewaddress,"; INVERSE 1;"A"; INVERSE 0;"lter,"; INVERSE 1;"6&7"; INVERSE 0;" scroll": LET a$=z$
 1020 GO SUB 200: IF n>65516 THEN LET a$="Too high; re-enter": GO TO 1020
 1030 LET c=0: LET l=n: PRINT AT 2,0;: FOR y=0 TO 19: LET p=y: GO SUB 1210: NEXT y: GO SUB 1190
 1040 LET a$=INKEY$: IF a$="A" OR a$="a" THEN GO TO 1100
 1050 IF a$="6" THEN GO TO 1130
 1060 IF a$="7" THEN GO TO 1160
 1070 IF a$="M" THEN CLS : RETURN 
 1080 IF a$="N" THEN GO SUB 1200: LET a$=z$: GO TO 1020
 1090 GO TO 1040
 1100 LET a$="New value"
 1110 GO SUB 120: IF n>255 THEN LET a$=y$: GO TO 1110
 1120 POKE l+c,n: LET p=c: GO SUB 1210
 1130 IF c<>19 THEN GO SUB 1200: LET c=c+1: GO SUB 1190: GO TO 1040
 1140 IF l>65515 THEN GO TO 1040
 1150 LET l=l+1: RANDOMIZE USR 23335: GO SUB 1190: LET p=c: GO SUB 1210: GO TO 1040
 1160 IF c<>0 THEN GO SUB 1200: LET c=c-1: GO SUB 1190: GO TO 1040
 1170 IF NOT l THEN GO TO 1040
 1180 LET l=l-1: RANDOMIZE USR 23350: LET p=c: GO SUB 1210: GO TO 1040
 1190 PRINT AT c+2,0;"Address>>";AT c+2,22;"<<Contents": RETURN 
 1200 PRINT AT c+2,0;TAB 9;AT c+2,22,: RETURN 
 1210 LET a=l+p: GO SUB 50: PRINT AT p+2,9;a$;"     ";: LET a=PEEK (l+p): GO SUB 20: PRINT a$: RETURN 
 2000 REM 
 2001 REM ENTER CODE
 2002 REM 
 2010 CLS : PRINT "Mode 2 allows you to enter code";AT 4,2;"ENTER A VALUE OUT OF RANGE TO"'TAB 7;"RETURN TO THE MENU";AT 10,2;"Current address = "
 2020 LET a$=z$: GO SUB 200: LET l=n
 2030 LET a=l: GO SUB 50: PRINT AT 10,20;a$: LET a$="Code": GO SUB 120: IF n<0 OR n>255 THEN RETURN 
 2040 POKE l,n: LET l=l+1*(l<65535): GO TO 2030
 3000 REM 
 3001 REM SAVE CODE
 3002 REM 
 3010 CLS : PRINT "Mode 3 will save code on tape"
 3020 LET a$=z$: GO SUB 200: LET l=n: LET a=n: GO SUB 50: PRINT AT 6,4;z$;" = ";a$
 3030 LET a$="Number of bytes": GO SUB 200: LET s=n: LET a=n: GO SUB 50: PRINT AT 8,2;"Length of block = ";a$
 3040 INPUT "Name of file ";a$: SAVE a$CODE l,s
 3050 PRINT AT 12,3;"REWIND TAPE AND CHECK "'TAB 6;a$;'': VERIFY a$CODE l,s
 3060 INPUT "O.K. --- Press enter for menu";a$: RETURN 
 4000 REM 
 4001 REM RUN CODE
 4002 REM 
 4010 CLS : PRINT "Mode 4 will run machine code"
 4020 LET a$=z$: GO SUB 200: LET a=n: GO SUB 50: PRINT AT 6,3;"The code will be run from";8,12;a$;AT 12,3;"Press :- R to run code";AT 14,13;"N to change address";AT 16,13;"M for the menu"
 4030 LET a$=INKEY$: IF a$="M" THEN RETURN 
 4040 IF a$="N" THEN GO TO 4010
 4050 IF a$<>"R" THEN GO TO 4030
 4060 CLS : PRINT USR n: INPUT "Code has run; press ENTER";a$: RETURN 
 5000 REM 
 5001 REM CHANGE BASE
 5002 REM 
 5010 CLS : PRINT AT 3,2;"The Monitor program now works";AT 5,4;"with numbers to the base": IF h THEN GO TO 5030
 5020 LET h=1: PRINT AT 8,12;"SIXTEEN": GO TO 5040
 5030 LET h=0: PRINT AT 8,14;"TEN":
 5040 INPUT "Press ENTER for the menu";a$: RETURN 
 6000 REM 
 6001 REM CONVERT NUMBER
 6002 REM 
 6010 LET t=h: CLS : PRINT AT 4,6;"TO CONVERT:-";AT 8,5;"Hex to decimal press H";AT 10,5;"Decimal to hex press D";AT 15,7;"For the menu press M"
 6020 LET a$=INKEY$: IF a$="" THEN GO TO 6020
 6030 IF a$="M" THEN LET h=t: RETURN 
 6040 IF a$="D" THEN GO TO 6070
 6050 IF a$="H" THEN GO TO 6100
 6060 GO TO 6020
 6070 LET a$="Decimal": LET h=0
 6080 GO SUB 200
 6090 LET a=n: LET h=1: GO SUB 60: PRINT #1;"Hex = ";a$: GO TO 6020
 6100 LET a$="Hex": LET h=1
 6110 GO SUB 200
 6120 PRINT #1;"Decimal = ";n: GO TO 6020
 8000 REM 
 8001 REM MENU
 8002 REM 
 8010 CLS : PRINT AT 1,6;"MACHINE CODE MONITOR": PLOT 46,158: DRAW 163,0
 8020 PRINT AT 3,6;"Press the number for";AT 4,6;"the desired function"
 8030 RESTORE : FOR x=1 TO 6: READ a$: PRINT AT x*2+6,5;x;" ---- ";a$: NEXT x
 8040 LET a=CODE INKEY$-48: IF a<1 OR a>6 THEN GO TO 8040
 8050 GO SUB A*1000: GO TO 8000
 8060 DATA "Examine Memory","Enter Code","Save Code","Run Code","Change Base","Convert Number"
 9000 REM 
 9001 REM Set up
 9002 REM 
 9010 INK 7: PAPER 1: BORDER 1: POKE 23658,8
 9020 LET h=0: LET z$="Start Address": LET y$="Out of range; try again"
 9030 RESTORE 9200: FOR x=23296 TO 23364: READ a: POKE x,a: NEXT x
 9100 GO TO 8000
 9200 DATA 245,230,24,246,64,103,241,245,230,7,15,15,15,198,9,11,241,201
 9210 DATA 205,0,91,6,8,197,1,13,0,213,229,237,176,225,209,36,20,193,16,241,201
 9220 DATA 62,2,205,0,91,235,60,205,18,91,254,21,32,244,201
 9230 DATA 62,21,205,0,91,235,61,205,18,91,254,2,32,244,201

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

People

No people associated with this content.

Scroll to Top