This program generates and saves a “double thickness” character set replacement for the system font. It works by POKEing 30 bytes of Z80 machine code into memory at address 30000, then executing it via RANDOMIZE USR 30000 to process the existing character ROM data and produce a double-thickness version of each character glyph at address 30208 (768 bytes covers the 96 printable characters at 8 bytes each). The generated code block can then be relocated to any address by updating the two system variables at 23606–23607, which together form the 16-bit pointer to the user-defined character set. The program finishes by prompting for a tape name, saving the 768-byte code block, verifying it, and issuing NEW to clean up.
Program Analysis
Program Structure
The program is a one-shot utility that follows a strict linear sequence:
- Lines 1–5: Documentation REMs explaining usage and relocation.
- Line 20:
LIST— displays the listing so the user can read the instructions before running. - Line 30–40: Loads 30 bytes of Z80 machine code into addresses 30000–30029 via a
FOR/READ/POKE/NEXTloop. - Line 50: Executes the machine code with
RANDOMIZE USR 30000. - Lines 60–80: Prompts for a tape name and saves the resulting 768-byte block at address 30208.
- Lines 90–110: Rewind prompt,
VERIFY, confirmation beep. - Line 120:
NEWclears the BASIC workspace.
Machine Code Analysis
The 30 DATA bytes disassemble to a Z80 routine that reads the original ROM character data and writes a “double thickness” (bold) version into RAM. The bytes are:
| Address | Bytes | Mnemonic | Notes |
|---|---|---|---|
| 30000 | 33,0,61 | LD HL,15616 | Source: start of ROM character set (address 0x3D00) |
| 30003 | 17,0,118 | LD DE,30208 | Destination: output buffer (0x7600) |
| 30006 | 1,0,3 | LD BC,768 | 768 bytes = 96 chars × 8 bytes |
| 30009 | 237,176 | LDIR | Copy original font to RAM first |
| 30011 | 33,0,118 | LD HL,30208 | Now process the RAM copy in place |
| 30014 | 17,0,3 | LD DE,768 | Counter (reused as loop limit via register pair) |
| 30017 | 126 | LD A,(HL) | Load one font byte |
| 30018 | 79 | LD C,A | Save original |
| 30019 | 203,63 | SRL A | Shift right by 1 |
| 30021 | 177 | OR C | OR with original — each set bit spreads one pixel right |
| 30022 | 119 | LD (HL),A | Write back double-thickness byte |
| 30023 | 35 | INC HL | Advance pointer |
| 30024 | 27 | DEC DE | Decrement counter |
| 30025 | 122 | LD A,D | Test if DE=0 |
| 30026 | 179 | OR E | D OR E = 0 only when DE exhausted |
| 30027 | 200 | RET Z | Return when all 768 bytes processed |
| 30028 | 24,243 | JR -13 | Loop back to 30017 |
The bold effect is achieved by ORing each byte with itself shifted one bit to the right (SRL A then OR C). This causes every lit pixel to also illuminate the pixel immediately to its right, producing a horizontally thickened glyph without any ROM patching.
Memory Layout
15616(0x3D00): ROM character set start — 768 bytes of standard 6×8 font data.30000–30029: Temporary machine code buffer (30 bytes).30208(0x7600): Output buffer for the generated bold font (768 bytes = 30208–30975).- System variables
23606–23607: Low/high bytes of the user-defined character set pointer, updated manually post-load as described in the REM.
Key BASIC Idioms
FOR N=30000 TO 30029: READ A: POKE N,A: NEXT N— the standard single-line DATA loader idiom, keeping the code compact.RANDOMIZE USR 30000— calls machine code without requiring a function return value; any returned value is silently discarded as the seed.INPUT "" LINE N$— usesLINEto accept the full string including spaces without needing quotes, avoiding the default tokenisation ofINPUT.PRINT '(apostrophe in PRINT) — emits a newline before the following string, providing spacing without a separatePRINTstatement.
Notable Techniques
The two-phase approach — first LDIR copying the ROM font into writable RAM, then processing it in-place — is necessary because the ROM is not writable. The loop termination using LD A,D / OR E / RET Z is a classic Z80 idiom for testing a 16-bit counter for zero without consuming a register pair for a dedicated DJNZ-style counter, and avoids the overhead of a CP instruction on a 16-bit value.
The LIST on line 20 is an intentional design choice: since the usage instructions are embedded in the REM on line 5, running the program immediately displays the listing before proceeding, ensuring the user reads the relocation instructions prior to the machine code executing.
Potential Issues
- The machine code and output buffer both reside in the same RAM region (around address 30000). The code occupies 30000–30029 and the output starts at 30208, so there is a 178-byte gap — no overlap occurs.
- No
CLEARis issued in the program itself; the REM instructions tell the end-user to issueCLEAR addr-1before loading the saved code block into a target address, which is essential to prevent BASIC heap corruption. - The right-shift bold algorithm can cause pixel bleed across character boundaries if a glyph has a set bit in column 8 (bit 0 after the shift) — however, the standard ROM font reserves the rightmost column as blank padding, so this is not a practical concern.
Content
Image Gallery
Source Code
1 REM "CHAR SET1"
2 REM 'DOUBLE THICKNESS'
3 REM By Ben Stagnell
4 REM ZX Computing Monthly
5 REM To use in any memory addr CLEAR addr-1, LOAD "code name" CODE addr, play tape then type or include as pgm lines POKE 23606,addr -256*INT (addr/256): POKE 23607,INT (addr/256) Where "addr" is any address you wish to put the code at.
20 LIST
30 FOR N=30000 TO 30029: READ A: POKE N,A: NEXT N
40 DATA 33,0,61,17,0,118,1,0,3,237,176,33,0,118,17,0,3,126,79,203,63,177,119,35,27,122,179,200,24,243
50 RANDOMIZE USR 30000
60 INPUT "NAME TO SAVE >) "; LINE N$
70 CLS : PRINT "SAVING :";N$
80 SAVE N$CODE 30208,768
90 PRINT ''"REWIND TAPE TO VERIFY"
100 VERIFY N$CODE
110 BEEP .1,10: PRINT "SAVE COMPLETE"
120 NEW
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.