
In part 1 we covered the history of Prestel and Prism, and in part 2 we cracked open the VTX5000 to examine the hardware. Now how did they squeeze a viewdata terminal app into 8KB...
The ROM Map
The 8K ROM at $0000-$1FFF is packed tight:
| Address Range | Size | Contents |
|---|---|---|
$0000-$0026 | 39 bytes | Jump table (API entry points) |
$0027-$0038 | 18 bytes | RST $38 maskable interrupt handler |
$003A-$1100 | 4,295 bytes | Embedded Spectrum BASIC program |
$1101-$1B1C | 2,588 bytes | Z80 machine code |
$1B1D-$1B1F | 3 bytes | Padding |
$1B20-$1CFF | 480 bytes | 5×8 pixel character font (96 chars) |
$1D00-$1F82 | 643 bytes | Startup splash screen (Viewdata frame) |
$1F83-$1FCB | 73 bytes | Configuration data and defaults |
$1FCC-$1FFF | 52 bytes | ROM paging trampolines |
A whopping 4K of the ROM is a BASIC program we'll get to in a minute and half a KB goes on that SAA5050-like 8x5 font and another half-KB on a Micronet 800 splash screen.
That leaves just 3KB and the most interesting part is 2.5KB of optimized Z80 handling the performance-critical modem I/O, Viewdata rendering, keyboard scanning, ring buffers etc.
Memory Paging & Trampolines
Before we get to that though we'll look very quickly at the glue code to manage the switching between ROMs.
The Spectrum's own ROM sits at $0000-$3FFF and unlike say, the BBC Micro, there isn't a region for third parties to jump in and out of. So, the VTX5000 takes over that address space by way of the Spectrum expansion port ROMCS line. ROM Chip Select, as it is known, disables the internal ROM when active, allowing an external device to respond to reads in that address range instead. As mentioned in part 2 the Intel 8251 UART otherwise-unused RTS line is used to control this paging:
LD A,$15 ; Command: RTS=0 → VTX ROM paged in
OUT ($FF),A
LD A,$35 ; Command: RTS=1 → Spectrum ROM restored
OUT ($FF),A
The problem with paging is that you don't want code changing underneath where you're currently executing. If you have multiple banks like a 128K machine not a problem just page out a different bank but when you're dealing with a single bank such as between the Speccy and VTX ROMs that occupy the same space you need a different solution.
The VTX ROM solves this with trampoline routines - small routines that live outside the being-paged bank so they're safe. During cold start, the init code copies 128 bytes from the end of the ROM ($1FCC-$1FFF) to the top of RAM ($FFCC-$FFFF on a 48K Spectrum, or $7FCC-$7FFF on a 16K). These trampolines handle all the actual ROM switching:
; Trampoline at $FFED (copied from $1FED):
; Called by RST $38 handler to page in Spectrum ROM for interrupts
TRAMP_INT_SPECROM:
LD A,$35 ; Page in Spectrum ROM
OUT ($FF),A
EX (SP),HL ; Bus-cycle delay to allow bus to settle
EX (SP),HL
DEFB $FF ; RST $38 → runs Spectrum's interrupt handler
There are four trampolines in total:
| Trampoline | Purpose |
|---|---|
TRAMP_INT_SPECROM ($FFED) | RST $38 → page in Spectrum ROM, run its interrupt handler |
TRAMP_INT_RETURN ($FFF4) | Return from interrupt → page VTX ROM back in |
TRAMP_CALL_SPECROM ($FFCC) | Call a routine in the Spectrum ROM (for PRINT, etc.) |
TRAMP_RETURN_SPECROM ($FFDA) | Return to Spectrum ROM (used to launch BASIC) |
TRAMP_START_BASIC ($FFE3) | Page in VTX ROM to setup BASIC terminal code |
Interrupt Handling
That's not the end of our paging trouble though. The Z80's maskable interrupt fires 50 times per second from the Spectrum's ULA. When the VTX ROM is paged in, the interrupt vector at RST $38 ($0038) lands in our code, not the Spectrum's ROM handling which we still need it to do for frame counting, keyboard scanning etc.
The VTX5000 version of the handler is short:
RST38_HANDLER:
PUSH AF
BIT 7,(IY+$7B) ; Test RAM size flag: 0=16K, 1=48K
JP Z,$7FED ; 16K: trampoline at top of 16K RAM
JP $FFED ; 48K: trampoline at top of 48K RAM
It pushes AF (), checks whether we're on a 16K or 48K machine, then jumps to the appropriate trampoline. The trampoline pages in the Spectrum ROM, the Z80 immediately executes RST $38 in that ROM (which does the keyboard scan, updates FRAMES, etc.), and then the return trampoline pages us back. It adds about a 200 t-state overhead 50 times a second but given we're just while the VTX5000 is in control.
Cold Start
The power-on cold start routine at $1101 has to get RAM, ROM, and I/O all set up in the right order:
COLD_START:
DI ; Disable interrupts during setup
LD IY,$5C3A ; IY -> Spectrum system variables (ERR_NR)
LD A,$3F
LD I,A ; Set I=$3F for IM2 interrupt vector table
LD HL,$5C00 ; Start of system variables area
LD DE,$5C01
LD BC,$A3FF ; Clear $A3FF bytes ($5C00 to $FFFF)
LD (HL),$00
LDIR ; Zero all RAM from $5C00 upward
; --- RAM size test ---
LD A,$55 ; Test pattern 1
LD (HL),A ; Write to top of RAM
CP (HL) ; Read back and compare
JR NZ,.ram_16k ; If mismatch, RAM is only 16K
CPL ; Test pattern 2 ($AA)
LD (HL),A
CP (HL)
JR Z,.ram_ok ; If match, full 48K RAM confirmed
.ram_16k:
RES 7,H ; Limit HL to 16K range ($7FFF max)
.ram_ok:
; --- Copy ROM paging trampolines to top of RAM ---
; The trampolines at $1F80-$1FFF handle interrupt routing and ROM paging.
; They must reside in RAM since they switch between VTX and Spectrum ROMs.
LD DE,$1FFF ; Destination: top of ROM address space
LD BC,$0080 ; 128 bytes to copy
EX DE,HL ; HL=dest(top of RAM), DE=$1FFF
LDDR ; Copy $1F80-$1FFF to top of RAM (going down)
; --- Copy character font + splash screen + config to RAM ---
LD BC,$FEC0 ; Offset adjustment
EX DE,HL
ADD HL,BC ; Adjust destination pointer
EX DE,HL
LD BC,$0280 ; 640 bytes (font + splash + config)
LDDR ; Copy $1B00-$1D7F area to RAM below trampolines
; --- Set RAMTOP for BASIC ---
LD HL,$FCA9 ; Offset to calculate BASIC RAMTOP
ADD HL,DE ; HL = safe top for BASIC (below our data)
LD ($5CB4),HL ; Store as P_RAMT (physical RAM top for BASIC)
LD SP,$6000 ; Temporary stack in mid-RAM
; --- Initialise hardware and copy BASIC program ---
CALL GET_IX ; Set IX to device state block
CALL HW_INIT ; Initialise 8251 USART and ports
; --- Copy boot code to RAM and execute ---
; A small loader is copied to $6200 and executed from there. It pages in
; the Spectrum ROM to copy the PRINT/channel routines, then pages back.
LD HL,$1156 ; Source: embedded copy routine in this ROM
LD DE,$6200 ; Destination: safe RAM area
LD BC,$0026 ; 38 bytes of copy code
LDIR ; Copy the loader to RAM
JP $6200 ; Execute from RAM (we can't page ROM while running from it)
That boot loader needs to page in the Spectrum ROM to copy some of its output routines (the PRINT channel handler at $1203), but we can't do that while executing from the VTX ROM - the trampoline problem again. So a 38-byte stub gets copied to $6200 in RAM, safely above the ROM address range, and runs from there. It pages in the Spectrum ROM, copies 174 bytes of output routines, patches a jump instruction, then pages the VTX ROM back in.
BASIC
As the ROM map shows, over half the ROM is a Spectrum BASIC program. The machine code handles the time-critical stuff - modem I/O, screen rendering, keyboard - and the BASIC program drives everything else via PEEK, POKE, and USR calls.
The glue between the two is a device state block near the top of RAM ($FF80 on 48K, $7F80 on 16K), always pointed to by the IX register. Cursor position, display attributes, modem config, parser state, timeout counters, ring buffer pointers - it's all in there. Think of it as a big struct passed by pointer through every call.
The BASIC program handles the user interface - menus, login, file operations, the mailbox messaging system:
- Log on to Prestel/Micronet (automatically or manually)
- Enter the Micronet terminal for browsing
- Save and load Viewdata frames to cassette tape
- Download telesoftware
- Compose and send mailbox messages
- Print frames
- Drop to the BASIC prompt
The full VTX5000 BASIC program is tightly written - single-character variable names, no comments beyond the copyright REM on line 10, data-driven menus. The structure is typical BASIC of the era (computed GO TOs, RESTORE/READ for menu data) so I won't dwell on it. What's more interesting is what the program actually does.
Logging On
On first run the user's 10-digit Prestel ID is all question marks. If it starts with ? the code prompts for a new one, validates it's exactly 10 characters, and pokes it byte-by-byte into the device state block where the machine code can find it:
1450 LET I$=q$: FOR i=o TO 9: POKE (id+i), CODE q$(i+l): NEXT i
Then it displays "Phone Computer" in flashing text - your cue to dial the Prestel number (the VTX has no dialer), wait for the modem tone, and press a key. Logging off sends *90## which was the Prestel logout sequence, confirms with a "Log OFF ?" prompt that only accepts y, then flips the modem flags to disconnect and returns to the menu.
The Mailbox Editor
The mailbox system (lines 7000-7900) is a surprisingly complete message editor crammed into a few hundred bytes of BASIC. You type lines at an INPUT prompt, each one getting packed into a string buffer b$ with a 557-byte limit. The first two bytes of the buffer store the total message length as a 16-bit value:
7240 LET i=i-3: LET b$(l)= CHR$ (i-256* INT (i/256)): LET b$(2)= CHR$ INT (i/256)
That's manually encoding a little-endian 16-bit length into the first two characters of a string - the Spectrum doesn't have any integer packing, so you get to do it yourself with CHR$ and INT. You can save messages to tape, load them back, preview them on screen, and send them over the Prestel connection. Lines ending in # are treated as Prestel "send" characters.
Telesoftware: Self-Replacing Code
The telesoftware downloader (lines 6000-6320) is the wildest part. It moves CLEAR to reclaim RAM below the program, adjusts the Spectrum's PROG pointer directly via POKE, then calls USR to receive the data over the modem. When the download finishes the BASIC program has effectively replaced itself in memory with whatever was received, and runs it.
6105 POKE fmod,123- CODE s$: POKE mf,dl: LET ix=ix-999:
POKE 23732,ix-256* INT (ix/256): POKE 23733, INT (ix/256):
LET X= USR str
That's directly writing to the Spectrum's PROG system variable (addresses 23732/23733) to redirect where BASIC thinks its program lives. After the machine code receives the telesoftware, control returns to BASIC... but it's no longer the same program. The Prestel service would send complete BASIC programs this way - games, utilities, even other terminal software - and they'd just appear and run.
Acorn's Teletext adapter did similar things for downloading BBC BASIC programs over Ceefax and I have memories of hanging out of our school IT class window antenna in-hand trying to get uncorrupted blocks...
In part 4 we'll dig into the machine code portion.
0 responses