[{"data":1,"prerenderedAt":1646},["ShallowReactive",2],{"blogvtx5000-part-3-software-rom":3,"blogMore-Technology":1634,"comments-vtx5000-part-3-software-rom":1645},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"tags":10,"category":13,"excerpt":14,"body":47,"_type":1625,"_id":1626,"_source":1627,"_file":1628,"_stem":1629,"_extension":1630,"url":1631,"wordCount":1632,"minutes":826,"commentCount":1633},"/blog/2026/2026-04-16-vtx5000-part-3-software-rom","2026",false,"en","VTX5000: Part 3 - Software ROM","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...",[11,12],"ZX Spectrum","vintage computing","Technology",{"type":15,"children":16},"root",[17,33],{"type":18,"tag":19,"props":20,"children":21},"element","p",{},[22,25,27,29,31],{"type":23,"value":24},"text","In ",{"type":23,"value":26},"part 1",{"type":23,"value":28}," we covered the history of Prestel and Prism, and in ",{"type":23,"value":30},"part 2",{"type":23,"value":32}," we cracked open the VTX5000 to examine the hardware. Now how did they squeeze a viewdata terminal app into 8KB...",{"type":18,"tag":19,"props":34,"children":35},{},[36,38,45],{"type":23,"value":37},"The 8K ROM at ",{"type":18,"tag":39,"props":40,"children":42},"code",{"className":41},[],[43],{"type":23,"value":44},"$0000-$1FFF",{"type":23,"value":46}," is packed tight:",{"type":15,"children":48,"toc":1618},[49,66,73,83,314,319,324,330,335,356,414,419,454,528,533,650,656,677,682,729,734,740,753,1267,1288,1294,1323,1344,1349,1389,1411,1418,1431,1446,1467,1473,1486,1500,1529,1535,1563,1594,1599,1604,1612],{"type":18,"tag":19,"props":50,"children":51},{},[52,53,59,60,65],{"type":23,"value":24},{"type":18,"tag":54,"props":55,"children":57},"a",{"href":56},"/blog/2026/03/23/prism-vtx5000-part-1/",[58],{"type":23,"value":26},{"type":23,"value":28},{"type":18,"tag":54,"props":61,"children":63},{"href":62},"/blog/2026/03/30/prism-vtx5000-part-2/",[64],{"type":23,"value":30},{"type":23,"value":32},{"type":18,"tag":67,"props":68,"children":70},"h3",{"id":69},"the-rom-map",[71],{"type":23,"value":72},"The ROM Map",{"type":18,"tag":19,"props":74,"children":75},{},[76,77,82],{"type":23,"value":37},{"type":18,"tag":39,"props":78,"children":80},{"className":79},[],[81],{"type":23,"value":44},{"type":23,"value":46},{"type":18,"tag":84,"props":85,"children":86},"table",{},[87,111],{"type":18,"tag":88,"props":89,"children":90},"thead",{},[91],{"type":18,"tag":92,"props":93,"children":94},"tr",{},[95,101,106],{"type":18,"tag":96,"props":97,"children":98},"th",{},[99],{"type":23,"value":100},"Address Range",{"type":18,"tag":96,"props":102,"children":103},{},[104],{"type":23,"value":105},"Size",{"type":18,"tag":96,"props":107,"children":108},{},[109],{"type":23,"value":110},"Contents",{"type":18,"tag":112,"props":113,"children":114},"tbody",{},[115,138,160,182,204,226,248,270,292],{"type":18,"tag":92,"props":116,"children":117},{},[118,128,133],{"type":18,"tag":119,"props":120,"children":121},"td",{},[122],{"type":18,"tag":39,"props":123,"children":125},{"className":124},[],[126],{"type":23,"value":127},"$0000-$0026",{"type":18,"tag":119,"props":129,"children":130},{},[131],{"type":23,"value":132},"39 bytes",{"type":18,"tag":119,"props":134,"children":135},{},[136],{"type":23,"value":137},"Jump table (API entry points)",{"type":18,"tag":92,"props":139,"children":140},{},[141,150,155],{"type":18,"tag":119,"props":142,"children":143},{},[144],{"type":18,"tag":39,"props":145,"children":147},{"className":146},[],[148],{"type":23,"value":149},"$0027-$0038",{"type":18,"tag":119,"props":151,"children":152},{},[153],{"type":23,"value":154},"18 bytes",{"type":18,"tag":119,"props":156,"children":157},{},[158],{"type":23,"value":159},"RST $38 maskable interrupt handler",{"type":18,"tag":92,"props":161,"children":162},{},[163,172,177],{"type":18,"tag":119,"props":164,"children":165},{},[166],{"type":18,"tag":39,"props":167,"children":169},{"className":168},[],[170],{"type":23,"value":171},"$003A-$1100",{"type":18,"tag":119,"props":173,"children":174},{},[175],{"type":23,"value":176},"4,295 bytes",{"type":18,"tag":119,"props":178,"children":179},{},[180],{"type":23,"value":181},"Embedded Spectrum BASIC program",{"type":18,"tag":92,"props":183,"children":184},{},[185,194,199],{"type":18,"tag":119,"props":186,"children":187},{},[188],{"type":18,"tag":39,"props":189,"children":191},{"className":190},[],[192],{"type":23,"value":193},"$1101-$1B1C",{"type":18,"tag":119,"props":195,"children":196},{},[197],{"type":23,"value":198},"2,588 bytes",{"type":18,"tag":119,"props":200,"children":201},{},[202],{"type":23,"value":203},"Z80 machine code",{"type":18,"tag":92,"props":205,"children":206},{},[207,216,221],{"type":18,"tag":119,"props":208,"children":209},{},[210],{"type":18,"tag":39,"props":211,"children":213},{"className":212},[],[214],{"type":23,"value":215},"$1B1D-$1B1F",{"type":18,"tag":119,"props":217,"children":218},{},[219],{"type":23,"value":220},"3 bytes",{"type":18,"tag":119,"props":222,"children":223},{},[224],{"type":23,"value":225},"Padding",{"type":18,"tag":92,"props":227,"children":228},{},[229,238,243],{"type":18,"tag":119,"props":230,"children":231},{},[232],{"type":18,"tag":39,"props":233,"children":235},{"className":234},[],[236],{"type":23,"value":237},"$1B20-$1CFF",{"type":18,"tag":119,"props":239,"children":240},{},[241],{"type":23,"value":242},"480 bytes",{"type":18,"tag":119,"props":244,"children":245},{},[246],{"type":23,"value":247},"5×8 pixel character font (96 chars)",{"type":18,"tag":92,"props":249,"children":250},{},[251,260,265],{"type":18,"tag":119,"props":252,"children":253},{},[254],{"type":18,"tag":39,"props":255,"children":257},{"className":256},[],[258],{"type":23,"value":259},"$1D00-$1F82",{"type":18,"tag":119,"props":261,"children":262},{},[263],{"type":23,"value":264},"643 bytes",{"type":18,"tag":119,"props":266,"children":267},{},[268],{"type":23,"value":269},"Startup splash screen (Viewdata frame)",{"type":18,"tag":92,"props":271,"children":272},{},[273,282,287],{"type":18,"tag":119,"props":274,"children":275},{},[276],{"type":18,"tag":39,"props":277,"children":279},{"className":278},[],[280],{"type":23,"value":281},"$1F83-$1FCB",{"type":18,"tag":119,"props":283,"children":284},{},[285],{"type":23,"value":286},"73 bytes",{"type":18,"tag":119,"props":288,"children":289},{},[290],{"type":23,"value":291},"Configuration data and defaults",{"type":18,"tag":92,"props":293,"children":294},{},[295,304,309],{"type":18,"tag":119,"props":296,"children":297},{},[298],{"type":18,"tag":39,"props":299,"children":301},{"className":300},[],[302],{"type":23,"value":303},"$1FCC-$1FFF",{"type":18,"tag":119,"props":305,"children":306},{},[307],{"type":23,"value":308},"52 bytes",{"type":18,"tag":119,"props":310,"children":311},{},[312],{"type":23,"value":313},"ROM paging trampolines",{"type":18,"tag":19,"props":315,"children":316},{},[317],{"type":23,"value":318},"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.",{"type":18,"tag":19,"props":320,"children":321},{},[322],{"type":23,"value":323},"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.",{"type":18,"tag":67,"props":325,"children":327},{"id":326},"memory-paging-trampolines",[328],{"type":23,"value":329},"Memory Paging & Trampolines",{"type":18,"tag":19,"props":331,"children":332},{},[333],{"type":23,"value":334},"Before we get to that though we'll look very quickly at the glue code to manage the switching between ROMs.",{"type":18,"tag":19,"props":336,"children":337},{},[338,340,346,348,354],{"type":23,"value":339},"The Spectrum's own ROM sits at ",{"type":18,"tag":39,"props":341,"children":343},{"className":342},[],[344],{"type":23,"value":345},"$0000-$3FFF",{"type":23,"value":347}," 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 ",{"type":18,"tag":349,"props":350,"children":351},"strong",{},[352],{"type":23,"value":353},"ROMCS",{"type":23,"value":355},"  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:",{"type":18,"tag":357,"props":358,"children":363},"pre",{"className":359,"code":360,"language":361,"meta":362,"style":362},"language-z80 shiki shiki-themes everforest-light dracula","LD A,$15        ; Command: RTS=0 → VTX ROM paged in\nOUT ($FF),A\n\nLD A,$35        ; Command: RTS=1 → Spectrum ROM restored\nOUT ($FF),A\n","z80","",[364],{"type":18,"tag":39,"props":365,"children":366},{"__ignoreMap":362},[367,378,387,397,406],{"type":18,"tag":368,"props":369,"children":372},"span",{"class":370,"line":371},"line",1,[373],{"type":18,"tag":368,"props":374,"children":375},{},[376],{"type":23,"value":377},"LD A,$15        ; Command: RTS=0 → VTX ROM paged in\n",{"type":18,"tag":368,"props":379,"children":381},{"class":370,"line":380},2,[382],{"type":18,"tag":368,"props":383,"children":384},{},[385],{"type":23,"value":386},"OUT ($FF),A\n",{"type":18,"tag":368,"props":388,"children":390},{"class":370,"line":389},3,[391],{"type":18,"tag":368,"props":392,"children":394},{"emptyLinePlaceholder":393},true,[395],{"type":23,"value":396},"\n",{"type":18,"tag":368,"props":398,"children":400},{"class":370,"line":399},4,[401],{"type":18,"tag":368,"props":402,"children":403},{},[404],{"type":23,"value":405},"LD A,$35        ; Command: RTS=1 → Spectrum ROM restored\n",{"type":18,"tag":368,"props":407,"children":409},{"class":370,"line":408},5,[410],{"type":18,"tag":368,"props":411,"children":412},{},[413],{"type":23,"value":386},{"type":18,"tag":19,"props":415,"children":416},{},[417],{"type":23,"value":418},"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.",{"type":18,"tag":19,"props":420,"children":421},{},[422,424,429,431,436,438,444,446,452],{"type":23,"value":423},"The VTX ROM solves this with ",{"type":18,"tag":349,"props":425,"children":426},{},[427],{"type":23,"value":428},"trampoline routines",{"type":23,"value":430}," - 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 (",{"type":18,"tag":39,"props":432,"children":434},{"className":433},[],[435],{"type":23,"value":303},{"type":23,"value":437},") to the top of RAM (",{"type":18,"tag":39,"props":439,"children":441},{"className":440},[],[442],{"type":23,"value":443},"$FFCC-$FFFF",{"type":23,"value":445}," on a 48K Spectrum, or ",{"type":18,"tag":39,"props":447,"children":449},{"className":448},[],[450],{"type":23,"value":451},"$7FCC-$7FFF",{"type":23,"value":453}," on a 16K). These trampolines handle all the actual ROM switching:",{"type":18,"tag":357,"props":455,"children":457},{"className":359,"code":456,"language":361,"meta":362,"style":362},"; Trampoline at $FFED (copied from $1FED):\n; Called by RST $38 handler to page in Spectrum ROM for interrupts\nTRAMP_INT_SPECROM:\n    LD A,$35            ; Page in Spectrum ROM\n    OUT ($FF),A\n    EX (SP),HL          ; Bus-cycle delay to allow bus to settle\n    EX (SP),HL\n    DEFB $FF            ; RST $38 → runs Spectrum's interrupt handler\n",[458],{"type":18,"tag":39,"props":459,"children":460},{"__ignoreMap":362},[461,469,477,485,493,501,510,519],{"type":18,"tag":368,"props":462,"children":463},{"class":370,"line":371},[464],{"type":18,"tag":368,"props":465,"children":466},{},[467],{"type":23,"value":468},"; Trampoline at $FFED (copied from $1FED):\n",{"type":18,"tag":368,"props":470,"children":471},{"class":370,"line":380},[472],{"type":18,"tag":368,"props":473,"children":474},{},[475],{"type":23,"value":476},"; Called by RST $38 handler to page in Spectrum ROM for interrupts\n",{"type":18,"tag":368,"props":478,"children":479},{"class":370,"line":389},[480],{"type":18,"tag":368,"props":481,"children":482},{},[483],{"type":23,"value":484},"TRAMP_INT_SPECROM:\n",{"type":18,"tag":368,"props":486,"children":487},{"class":370,"line":399},[488],{"type":18,"tag":368,"props":489,"children":490},{},[491],{"type":23,"value":492},"    LD A,$35            ; Page in Spectrum ROM\n",{"type":18,"tag":368,"props":494,"children":495},{"class":370,"line":408},[496],{"type":18,"tag":368,"props":497,"children":498},{},[499],{"type":23,"value":500},"    OUT ($FF),A\n",{"type":18,"tag":368,"props":502,"children":504},{"class":370,"line":503},6,[505],{"type":18,"tag":368,"props":506,"children":507},{},[508],{"type":23,"value":509},"    EX (SP),HL          ; Bus-cycle delay to allow bus to settle\n",{"type":18,"tag":368,"props":511,"children":513},{"class":370,"line":512},7,[514],{"type":18,"tag":368,"props":515,"children":516},{},[517],{"type":23,"value":518},"    EX (SP),HL\n",{"type":18,"tag":368,"props":520,"children":522},{"class":370,"line":521},8,[523],{"type":18,"tag":368,"props":524,"children":525},{},[526],{"type":23,"value":527},"    DEFB $FF            ; RST $38 → runs Spectrum's interrupt handler\n",{"type":18,"tag":19,"props":529,"children":530},{},[531],{"type":23,"value":532},"There are four trampolines in total:",{"type":18,"tag":84,"props":534,"children":535},{},[536,552],{"type":18,"tag":88,"props":537,"children":538},{},[539],{"type":18,"tag":92,"props":540,"children":541},{},[542,547],{"type":18,"tag":96,"props":543,"children":544},{},[545],{"type":23,"value":546},"Trampoline",{"type":18,"tag":96,"props":548,"children":549},{},[550],{"type":23,"value":551},"Purpose",{"type":18,"tag":112,"props":553,"children":554},{},[555,574,593,612,631],{"type":18,"tag":92,"props":556,"children":557},{},[558,569],{"type":18,"tag":119,"props":559,"children":560},{},[561,567],{"type":18,"tag":39,"props":562,"children":564},{"className":563},[],[565],{"type":23,"value":566},"TRAMP_INT_SPECROM",{"type":23,"value":568}," ($FFED)",{"type":18,"tag":119,"props":570,"children":571},{},[572],{"type":23,"value":573},"RST $38 → page in Spectrum ROM, run its interrupt handler",{"type":18,"tag":92,"props":575,"children":576},{},[577,588],{"type":18,"tag":119,"props":578,"children":579},{},[580,586],{"type":18,"tag":39,"props":581,"children":583},{"className":582},[],[584],{"type":23,"value":585},"TRAMP_INT_RETURN",{"type":23,"value":587}," ($FFF4)",{"type":18,"tag":119,"props":589,"children":590},{},[591],{"type":23,"value":592},"Return from interrupt → page VTX ROM back in",{"type":18,"tag":92,"props":594,"children":595},{},[596,607],{"type":18,"tag":119,"props":597,"children":598},{},[599,605],{"type":18,"tag":39,"props":600,"children":602},{"className":601},[],[603],{"type":23,"value":604},"TRAMP_CALL_SPECROM",{"type":23,"value":606}," ($FFCC)",{"type":18,"tag":119,"props":608,"children":609},{},[610],{"type":23,"value":611},"Call a routine in the Spectrum ROM (for PRINT, etc.)",{"type":18,"tag":92,"props":613,"children":614},{},[615,626],{"type":18,"tag":119,"props":616,"children":617},{},[618,624],{"type":18,"tag":39,"props":619,"children":621},{"className":620},[],[622],{"type":23,"value":623},"TRAMP_RETURN_SPECROM",{"type":23,"value":625}," ($FFDA)",{"type":18,"tag":119,"props":627,"children":628},{},[629],{"type":23,"value":630},"Return to Spectrum ROM (used to launch BASIC)",{"type":18,"tag":92,"props":632,"children":633},{},[634,645],{"type":18,"tag":119,"props":635,"children":636},{},[637,643],{"type":18,"tag":39,"props":638,"children":640},{"className":639},[],[641],{"type":23,"value":642},"TRAMP_START_BASIC",{"type":23,"value":644}," ($FFE3)",{"type":18,"tag":119,"props":646,"children":647},{},[648],{"type":23,"value":649},"Page in VTX ROM to setup BASIC terminal code",{"type":18,"tag":67,"props":651,"children":653},{"id":652},"interrupt-handling",[654],{"type":23,"value":655},"Interrupt Handling",{"type":18,"tag":19,"props":657,"children":658},{},[659,661,667,669,675],{"type":23,"value":660},"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 (",{"type":18,"tag":39,"props":662,"children":664},{"className":663},[],[665],{"type":23,"value":666},"$0038",{"type":23,"value":668},") lands in ",{"type":18,"tag":670,"props":671,"children":672},"em",{},[673],{"type":23,"value":674},"our",{"type":23,"value":676}," code, not the Spectrum's ROM handling which we still need it to do for frame counting, keyboard scanning etc.",{"type":18,"tag":19,"props":678,"children":679},{},[680],{"type":23,"value":681},"The VTX5000 version of the handler is short:",{"type":18,"tag":357,"props":683,"children":685},{"className":359,"code":684,"language":361,"meta":362,"style":362},"RST38_HANDLER:\n    PUSH AF\n    BIT 7,(IY+$7B)       ; Test RAM size flag: 0=16K, 1=48K\n    JP Z,$7FED            ; 16K: trampoline at top of 16K RAM\n    JP $FFED              ; 48K: trampoline at top of 48K RAM\n",[686],{"type":18,"tag":39,"props":687,"children":688},{"__ignoreMap":362},[689,697,705,713,721],{"type":18,"tag":368,"props":690,"children":691},{"class":370,"line":371},[692],{"type":18,"tag":368,"props":693,"children":694},{},[695],{"type":23,"value":696},"RST38_HANDLER:\n",{"type":18,"tag":368,"props":698,"children":699},{"class":370,"line":380},[700],{"type":18,"tag":368,"props":701,"children":702},{},[703],{"type":23,"value":704},"    PUSH AF\n",{"type":18,"tag":368,"props":706,"children":707},{"class":370,"line":389},[708],{"type":18,"tag":368,"props":709,"children":710},{},[711],{"type":23,"value":712},"    BIT 7,(IY+$7B)       ; Test RAM size flag: 0=16K, 1=48K\n",{"type":18,"tag":368,"props":714,"children":715},{"class":370,"line":399},[716],{"type":18,"tag":368,"props":717,"children":718},{},[719],{"type":23,"value":720},"    JP Z,$7FED            ; 16K: trampoline at top of 16K RAM\n",{"type":18,"tag":368,"props":722,"children":723},{"class":370,"line":408},[724],{"type":18,"tag":368,"props":725,"children":726},{},[727],{"type":23,"value":728},"    JP $FFED              ; 48K: trampoline at top of 48K RAM\n",{"type":18,"tag":19,"props":730,"children":731},{},[732],{"type":23,"value":733},"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.",{"type":18,"tag":67,"props":735,"children":737},{"id":736},"cold-start",[738],{"type":23,"value":739},"Cold Start",{"type":18,"tag":19,"props":741,"children":742},{},[743,745,751],{"type":23,"value":744},"The power-on cold start routine at ",{"type":18,"tag":39,"props":746,"children":748},{"className":747},[],[749],{"type":23,"value":750},"$1101",{"type":23,"value":752}," has to get RAM, ROM, and I/O all set up in the right order:",{"type":18,"tag":357,"props":754,"children":756},{"className":359,"code":755,"language":361,"meta":362,"style":362},"COLD_START:\n    DI                        ; Disable interrupts during setup\n    LD IY,$5C3A               ; IY -> Spectrum system variables (ERR_NR)\n    LD A,$3F\n    LD I,A                    ; Set I=$3F for IM2 interrupt vector table\n    LD HL,$5C00               ; Start of system variables area\n    LD DE,$5C01\n    LD BC,$A3FF               ; Clear $A3FF bytes ($5C00 to $FFFF)\n    LD (HL),$00\n    LDIR                      ; Zero all RAM from $5C00 upward\n\n; --- RAM size test ---\n    LD A,$55                  ; Test pattern 1\n    LD (HL),A                 ; Write to top of RAM\n    CP (HL)                   ; Read back and compare\n    JR NZ,.ram_16k            ; If mismatch, RAM is only 16K\n    CPL                       ; Test pattern 2 ($AA)\n    LD (HL),A\n    CP (HL)\n    JR Z,.ram_ok              ; If match, full 48K RAM confirmed\n.ram_16k:\n    RES 7,H                  ; Limit HL to 16K range ($7FFF max)\n.ram_ok:\n\n; --- Copy ROM paging trampolines to top of RAM ---\n; The trampolines at $1F80-$1FFF handle interrupt routing and ROM paging.\n; They must reside in RAM since they switch between VTX and Spectrum ROMs.\n    LD DE,$1FFF               ; Destination: top of ROM address space\n    LD BC,$0080               ; 128 bytes to copy\n    EX DE,HL                  ; HL=dest(top of RAM), DE=$1FFF\n    LDDR                      ; Copy $1F80-$1FFF to top of RAM (going down)\n\n; --- Copy character font + splash screen + config to RAM ---\n    LD BC,$FEC0               ; Offset adjustment\n    EX DE,HL\n    ADD HL,BC                 ; Adjust destination pointer\n    EX DE,HL\n    LD BC,$0280               ; 640 bytes (font + splash + config)\n    LDDR                      ; Copy $1B00-$1D7F area to RAM below trampolines\n\n; --- Set RAMTOP for BASIC ---\n    LD HL,$FCA9               ; Offset to calculate BASIC RAMTOP\n    ADD HL,DE                 ; HL = safe top for BASIC (below our data)\n    LD ($5CB4),HL             ; Store as P_RAMT (physical RAM top for BASIC)\n    LD SP,$6000               ; Temporary stack in mid-RAM\n\n; --- Initialise hardware and copy BASIC program ---\n    CALL GET_IX               ; Set IX to device state block\n    CALL HW_INIT              ; Initialise 8251 USART and ports\n\n; --- Copy boot code to RAM and execute ---\n; A small loader is copied to $6200 and executed from there. It pages in\n; the Spectrum ROM to copy the PRINT/channel routines, then pages back.\n    LD HL,$1156               ; Source: embedded copy routine in this ROM\n    LD DE,$6200               ; Destination: safe RAM area\n    LD BC,$0026               ; 38 bytes of copy code\n    LDIR                      ; Copy the loader to RAM\n    JP $6200                  ; Execute from RAM (we can't page ROM while running from it)\n",[757],{"type":18,"tag":39,"props":758,"children":759},{"__ignoreMap":362},[760,768,776,784,792,800,808,816,824,833,842,850,859,868,877,886,895,904,913,922,931,940,949,958,966,975,984,993,1002,1011,1020,1029,1037,1046,1055,1064,1073,1081,1090,1099,1107,1116,1125,1134,1143,1152,1160,1169,1178,1187,1195,1204,1213,1222,1231,1240,1249,1258],{"type":18,"tag":368,"props":761,"children":762},{"class":370,"line":371},[763],{"type":18,"tag":368,"props":764,"children":765},{},[766],{"type":23,"value":767},"COLD_START:\n",{"type":18,"tag":368,"props":769,"children":770},{"class":370,"line":380},[771],{"type":18,"tag":368,"props":772,"children":773},{},[774],{"type":23,"value":775},"    DI                        ; Disable interrupts during setup\n",{"type":18,"tag":368,"props":777,"children":778},{"class":370,"line":389},[779],{"type":18,"tag":368,"props":780,"children":781},{},[782],{"type":23,"value":783},"    LD IY,$5C3A               ; IY -> Spectrum system variables (ERR_NR)\n",{"type":18,"tag":368,"props":785,"children":786},{"class":370,"line":399},[787],{"type":18,"tag":368,"props":788,"children":789},{},[790],{"type":23,"value":791},"    LD A,$3F\n",{"type":18,"tag":368,"props":793,"children":794},{"class":370,"line":408},[795],{"type":18,"tag":368,"props":796,"children":797},{},[798],{"type":23,"value":799},"    LD I,A                    ; Set I=$3F for IM2 interrupt vector table\n",{"type":18,"tag":368,"props":801,"children":802},{"class":370,"line":503},[803],{"type":18,"tag":368,"props":804,"children":805},{},[806],{"type":23,"value":807},"    LD HL,$5C00               ; Start of system variables area\n",{"type":18,"tag":368,"props":809,"children":810},{"class":370,"line":512},[811],{"type":18,"tag":368,"props":812,"children":813},{},[814],{"type":23,"value":815},"    LD DE,$5C01\n",{"type":18,"tag":368,"props":817,"children":818},{"class":370,"line":521},[819],{"type":18,"tag":368,"props":820,"children":821},{},[822],{"type":23,"value":823},"    LD BC,$A3FF               ; Clear $A3FF bytes ($5C00 to $FFFF)\n",{"type":18,"tag":368,"props":825,"children":827},{"class":370,"line":826},9,[828],{"type":18,"tag":368,"props":829,"children":830},{},[831],{"type":23,"value":832},"    LD (HL),$00\n",{"type":18,"tag":368,"props":834,"children":836},{"class":370,"line":835},10,[837],{"type":18,"tag":368,"props":838,"children":839},{},[840],{"type":23,"value":841},"    LDIR                      ; Zero all RAM from $5C00 upward\n",{"type":18,"tag":368,"props":843,"children":845},{"class":370,"line":844},11,[846],{"type":18,"tag":368,"props":847,"children":848},{"emptyLinePlaceholder":393},[849],{"type":23,"value":396},{"type":18,"tag":368,"props":851,"children":853},{"class":370,"line":852},12,[854],{"type":18,"tag":368,"props":855,"children":856},{},[857],{"type":23,"value":858},"; --- RAM size test ---\n",{"type":18,"tag":368,"props":860,"children":862},{"class":370,"line":861},13,[863],{"type":18,"tag":368,"props":864,"children":865},{},[866],{"type":23,"value":867},"    LD A,$55                  ; Test pattern 1\n",{"type":18,"tag":368,"props":869,"children":871},{"class":370,"line":870},14,[872],{"type":18,"tag":368,"props":873,"children":874},{},[875],{"type":23,"value":876},"    LD (HL),A                 ; Write to top of RAM\n",{"type":18,"tag":368,"props":878,"children":880},{"class":370,"line":879},15,[881],{"type":18,"tag":368,"props":882,"children":883},{},[884],{"type":23,"value":885},"    CP (HL)                   ; Read back and compare\n",{"type":18,"tag":368,"props":887,"children":889},{"class":370,"line":888},16,[890],{"type":18,"tag":368,"props":891,"children":892},{},[893],{"type":23,"value":894},"    JR NZ,.ram_16k            ; If mismatch, RAM is only 16K\n",{"type":18,"tag":368,"props":896,"children":898},{"class":370,"line":897},17,[899],{"type":18,"tag":368,"props":900,"children":901},{},[902],{"type":23,"value":903},"    CPL                       ; Test pattern 2 ($AA)\n",{"type":18,"tag":368,"props":905,"children":907},{"class":370,"line":906},18,[908],{"type":18,"tag":368,"props":909,"children":910},{},[911],{"type":23,"value":912},"    LD (HL),A\n",{"type":18,"tag":368,"props":914,"children":916},{"class":370,"line":915},19,[917],{"type":18,"tag":368,"props":918,"children":919},{},[920],{"type":23,"value":921},"    CP (HL)\n",{"type":18,"tag":368,"props":923,"children":925},{"class":370,"line":924},20,[926],{"type":18,"tag":368,"props":927,"children":928},{},[929],{"type":23,"value":930},"    JR Z,.ram_ok              ; If match, full 48K RAM confirmed\n",{"type":18,"tag":368,"props":932,"children":934},{"class":370,"line":933},21,[935],{"type":18,"tag":368,"props":936,"children":937},{},[938],{"type":23,"value":939},".ram_16k:\n",{"type":18,"tag":368,"props":941,"children":943},{"class":370,"line":942},22,[944],{"type":18,"tag":368,"props":945,"children":946},{},[947],{"type":23,"value":948},"    RES 7,H                  ; Limit HL to 16K range ($7FFF max)\n",{"type":18,"tag":368,"props":950,"children":952},{"class":370,"line":951},23,[953],{"type":18,"tag":368,"props":954,"children":955},{},[956],{"type":23,"value":957},".ram_ok:\n",{"type":18,"tag":368,"props":959,"children":961},{"class":370,"line":960},24,[962],{"type":18,"tag":368,"props":963,"children":964},{"emptyLinePlaceholder":393},[965],{"type":23,"value":396},{"type":18,"tag":368,"props":967,"children":969},{"class":370,"line":968},25,[970],{"type":18,"tag":368,"props":971,"children":972},{},[973],{"type":23,"value":974},"; --- Copy ROM paging trampolines to top of RAM ---\n",{"type":18,"tag":368,"props":976,"children":978},{"class":370,"line":977},26,[979],{"type":18,"tag":368,"props":980,"children":981},{},[982],{"type":23,"value":983},"; The trampolines at $1F80-$1FFF handle interrupt routing and ROM paging.\n",{"type":18,"tag":368,"props":985,"children":987},{"class":370,"line":986},27,[988],{"type":18,"tag":368,"props":989,"children":990},{},[991],{"type":23,"value":992},"; They must reside in RAM since they switch between VTX and Spectrum ROMs.\n",{"type":18,"tag":368,"props":994,"children":996},{"class":370,"line":995},28,[997],{"type":18,"tag":368,"props":998,"children":999},{},[1000],{"type":23,"value":1001},"    LD DE,$1FFF               ; Destination: top of ROM address space\n",{"type":18,"tag":368,"props":1003,"children":1005},{"class":370,"line":1004},29,[1006],{"type":18,"tag":368,"props":1007,"children":1008},{},[1009],{"type":23,"value":1010},"    LD BC,$0080               ; 128 bytes to copy\n",{"type":18,"tag":368,"props":1012,"children":1014},{"class":370,"line":1013},30,[1015],{"type":18,"tag":368,"props":1016,"children":1017},{},[1018],{"type":23,"value":1019},"    EX DE,HL                  ; HL=dest(top of RAM), DE=$1FFF\n",{"type":18,"tag":368,"props":1021,"children":1023},{"class":370,"line":1022},31,[1024],{"type":18,"tag":368,"props":1025,"children":1026},{},[1027],{"type":23,"value":1028},"    LDDR                      ; Copy $1F80-$1FFF to top of RAM (going down)\n",{"type":18,"tag":368,"props":1030,"children":1032},{"class":370,"line":1031},32,[1033],{"type":18,"tag":368,"props":1034,"children":1035},{"emptyLinePlaceholder":393},[1036],{"type":23,"value":396},{"type":18,"tag":368,"props":1038,"children":1040},{"class":370,"line":1039},33,[1041],{"type":18,"tag":368,"props":1042,"children":1043},{},[1044],{"type":23,"value":1045},"; --- Copy character font + splash screen + config to RAM ---\n",{"type":18,"tag":368,"props":1047,"children":1049},{"class":370,"line":1048},34,[1050],{"type":18,"tag":368,"props":1051,"children":1052},{},[1053],{"type":23,"value":1054},"    LD BC,$FEC0               ; Offset adjustment\n",{"type":18,"tag":368,"props":1056,"children":1058},{"class":370,"line":1057},35,[1059],{"type":18,"tag":368,"props":1060,"children":1061},{},[1062],{"type":23,"value":1063},"    EX DE,HL\n",{"type":18,"tag":368,"props":1065,"children":1067},{"class":370,"line":1066},36,[1068],{"type":18,"tag":368,"props":1069,"children":1070},{},[1071],{"type":23,"value":1072},"    ADD HL,BC                 ; Adjust destination pointer\n",{"type":18,"tag":368,"props":1074,"children":1076},{"class":370,"line":1075},37,[1077],{"type":18,"tag":368,"props":1078,"children":1079},{},[1080],{"type":23,"value":1063},{"type":18,"tag":368,"props":1082,"children":1084},{"class":370,"line":1083},38,[1085],{"type":18,"tag":368,"props":1086,"children":1087},{},[1088],{"type":23,"value":1089},"    LD BC,$0280               ; 640 bytes (font + splash + config)\n",{"type":18,"tag":368,"props":1091,"children":1093},{"class":370,"line":1092},39,[1094],{"type":18,"tag":368,"props":1095,"children":1096},{},[1097],{"type":23,"value":1098},"    LDDR                      ; Copy $1B00-$1D7F area to RAM below trampolines\n",{"type":18,"tag":368,"props":1100,"children":1102},{"class":370,"line":1101},40,[1103],{"type":18,"tag":368,"props":1104,"children":1105},{"emptyLinePlaceholder":393},[1106],{"type":23,"value":396},{"type":18,"tag":368,"props":1108,"children":1110},{"class":370,"line":1109},41,[1111],{"type":18,"tag":368,"props":1112,"children":1113},{},[1114],{"type":23,"value":1115},"; --- Set RAMTOP for BASIC ---\n",{"type":18,"tag":368,"props":1117,"children":1119},{"class":370,"line":1118},42,[1120],{"type":18,"tag":368,"props":1121,"children":1122},{},[1123],{"type":23,"value":1124},"    LD HL,$FCA9               ; Offset to calculate BASIC RAMTOP\n",{"type":18,"tag":368,"props":1126,"children":1128},{"class":370,"line":1127},43,[1129],{"type":18,"tag":368,"props":1130,"children":1131},{},[1132],{"type":23,"value":1133},"    ADD HL,DE                 ; HL = safe top for BASIC (below our data)\n",{"type":18,"tag":368,"props":1135,"children":1137},{"class":370,"line":1136},44,[1138],{"type":18,"tag":368,"props":1139,"children":1140},{},[1141],{"type":23,"value":1142},"    LD ($5CB4),HL             ; Store as P_RAMT (physical RAM top for BASIC)\n",{"type":18,"tag":368,"props":1144,"children":1146},{"class":370,"line":1145},45,[1147],{"type":18,"tag":368,"props":1148,"children":1149},{},[1150],{"type":23,"value":1151},"    LD SP,$6000               ; Temporary stack in mid-RAM\n",{"type":18,"tag":368,"props":1153,"children":1155},{"class":370,"line":1154},46,[1156],{"type":18,"tag":368,"props":1157,"children":1158},{"emptyLinePlaceholder":393},[1159],{"type":23,"value":396},{"type":18,"tag":368,"props":1161,"children":1163},{"class":370,"line":1162},47,[1164],{"type":18,"tag":368,"props":1165,"children":1166},{},[1167],{"type":23,"value":1168},"; --- Initialise hardware and copy BASIC program ---\n",{"type":18,"tag":368,"props":1170,"children":1172},{"class":370,"line":1171},48,[1173],{"type":18,"tag":368,"props":1174,"children":1175},{},[1176],{"type":23,"value":1177},"    CALL GET_IX               ; Set IX to device state block\n",{"type":18,"tag":368,"props":1179,"children":1181},{"class":370,"line":1180},49,[1182],{"type":18,"tag":368,"props":1183,"children":1184},{},[1185],{"type":23,"value":1186},"    CALL HW_INIT              ; Initialise 8251 USART and ports\n",{"type":18,"tag":368,"props":1188,"children":1190},{"class":370,"line":1189},50,[1191],{"type":18,"tag":368,"props":1192,"children":1193},{"emptyLinePlaceholder":393},[1194],{"type":23,"value":396},{"type":18,"tag":368,"props":1196,"children":1198},{"class":370,"line":1197},51,[1199],{"type":18,"tag":368,"props":1200,"children":1201},{},[1202],{"type":23,"value":1203},"; --- Copy boot code to RAM and execute ---\n",{"type":18,"tag":368,"props":1205,"children":1207},{"class":370,"line":1206},52,[1208],{"type":18,"tag":368,"props":1209,"children":1210},{},[1211],{"type":23,"value":1212},"; A small loader is copied to $6200 and executed from there. It pages in\n",{"type":18,"tag":368,"props":1214,"children":1216},{"class":370,"line":1215},53,[1217],{"type":18,"tag":368,"props":1218,"children":1219},{},[1220],{"type":23,"value":1221},"; the Spectrum ROM to copy the PRINT/channel routines, then pages back.\n",{"type":18,"tag":368,"props":1223,"children":1225},{"class":370,"line":1224},54,[1226],{"type":18,"tag":368,"props":1227,"children":1228},{},[1229],{"type":23,"value":1230},"    LD HL,$1156               ; Source: embedded copy routine in this ROM\n",{"type":18,"tag":368,"props":1232,"children":1234},{"class":370,"line":1233},55,[1235],{"type":18,"tag":368,"props":1236,"children":1237},{},[1238],{"type":23,"value":1239},"    LD DE,$6200               ; Destination: safe RAM area\n",{"type":18,"tag":368,"props":1241,"children":1243},{"class":370,"line":1242},56,[1244],{"type":18,"tag":368,"props":1245,"children":1246},{},[1247],{"type":23,"value":1248},"    LD BC,$0026               ; 38 bytes of copy code\n",{"type":18,"tag":368,"props":1250,"children":1252},{"class":370,"line":1251},57,[1253],{"type":18,"tag":368,"props":1254,"children":1255},{},[1256],{"type":23,"value":1257},"    LDIR                      ; Copy the loader to RAM\n",{"type":18,"tag":368,"props":1259,"children":1261},{"class":370,"line":1260},58,[1262],{"type":18,"tag":368,"props":1263,"children":1264},{},[1265],{"type":23,"value":1266},"    JP $6200                  ; Execute from RAM (we can't page ROM while running from it)\n",{"type":18,"tag":19,"props":1268,"children":1269},{},[1270,1272,1278,1280,1286],{"type":23,"value":1271},"That boot loader needs to page in the Spectrum ROM to copy some of its output routines (the PRINT channel handler at ",{"type":18,"tag":39,"props":1273,"children":1275},{"className":1274},[],[1276],{"type":23,"value":1277},"$1203",{"type":23,"value":1279},"), but we can't do that while executing from the VTX ROM - the trampoline problem again. So a 38-byte stub gets copied to ",{"type":18,"tag":39,"props":1281,"children":1283},{"className":1282},[],[1284],{"type":23,"value":1285},"$6200",{"type":23,"value":1287}," 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.",{"type":18,"tag":67,"props":1289,"children":1291},{"id":1290},"basic",[1292],{"type":23,"value":1293},"BASIC",{"type":18,"tag":19,"props":1295,"children":1296},{},[1297,1299,1305,1307,1313,1315,1321],{"type":23,"value":1298},"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 ",{"type":18,"tag":39,"props":1300,"children":1302},{"className":1301},[],[1303],{"type":23,"value":1304},"PEEK",{"type":23,"value":1306},", ",{"type":18,"tag":39,"props":1308,"children":1310},{"className":1309},[],[1311],{"type":23,"value":1312},"POKE",{"type":23,"value":1314},", and ",{"type":18,"tag":39,"props":1316,"children":1318},{"className":1317},[],[1319],{"type":23,"value":1320},"USR",{"type":23,"value":1322}," calls.",{"type":18,"tag":19,"props":1324,"children":1325},{},[1326,1328,1334,1336,1342],{"type":23,"value":1327},"The glue between the two is a device state block near the top of RAM (",{"type":18,"tag":39,"props":1329,"children":1331},{"className":1330},[],[1332],{"type":23,"value":1333},"$FF80",{"type":23,"value":1335}," on 48K, ",{"type":18,"tag":39,"props":1337,"children":1339},{"className":1338},[],[1340],{"type":23,"value":1341},"$7F80",{"type":23,"value":1343}," 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.",{"type":18,"tag":19,"props":1345,"children":1346},{},[1347],{"type":23,"value":1348},"The BASIC program handles the user interface - menus, login, file operations, the mailbox messaging system:",{"type":18,"tag":1350,"props":1351,"children":1352},"ul",{},[1353,1359,1364,1369,1374,1379,1384],{"type":18,"tag":1354,"props":1355,"children":1356},"li",{},[1357],{"type":23,"value":1358},"Log on to Prestel/Micronet (automatically or manually)",{"type":18,"tag":1354,"props":1360,"children":1361},{},[1362],{"type":23,"value":1363},"Enter the Micronet terminal for browsing",{"type":18,"tag":1354,"props":1365,"children":1366},{},[1367],{"type":23,"value":1368},"Save and load Viewdata frames to cassette tape",{"type":18,"tag":1354,"props":1370,"children":1371},{},[1372],{"type":23,"value":1373},"Download telesoftware",{"type":18,"tag":1354,"props":1375,"children":1376},{},[1377],{"type":23,"value":1378},"Compose and send mailbox messages",{"type":18,"tag":1354,"props":1380,"children":1381},{},[1382],{"type":23,"value":1383},"Print frames",{"type":18,"tag":1354,"props":1385,"children":1386},{},[1387],{"type":23,"value":1388},"Drop to the BASIC prompt",{"type":18,"tag":19,"props":1390,"children":1391},{},[1392,1394,1402,1404,1409],{"type":23,"value":1393},"The ",{"type":18,"tag":54,"props":1395,"children":1399},{"href":1396,"rel":1397},"https://gist.github.com/damieng/fe3640c2a788f6143d1b5f15b291ef42",[1398],"nofollow",[1400],{"type":23,"value":1401},"full VTX5000 BASIC program",{"type":23,"value":1403}," 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 ",{"type":18,"tag":670,"props":1405,"children":1406},{},[1407],{"type":23,"value":1408},"does",{"type":23,"value":1410},".",{"type":18,"tag":1412,"props":1413,"children":1415},"h4",{"id":1414},"logging-on",[1416],{"type":23,"value":1417},"Logging On",{"type":18,"tag":19,"props":1419,"children":1420},{},[1421,1423,1429],{"type":23,"value":1422},"On first run the user's 10-digit Prestel ID is all question marks. If it starts with ",{"type":18,"tag":39,"props":1424,"children":1426},{"className":1425},[],[1427],{"type":23,"value":1428},"?",{"type":23,"value":1430}," 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:",{"type":18,"tag":357,"props":1432,"children":1435},{"className":1433,"code":1434,"language":1290,"meta":362,"style":362},"language-basic shiki shiki-themes everforest-light dracula","1450 LET I$=q$: FOR i=o TO 9: POKE (id+i), CODE q$(i+l): NEXT i\n",[1436],{"type":18,"tag":39,"props":1437,"children":1438},{"__ignoreMap":362},[1439],{"type":18,"tag":368,"props":1440,"children":1441},{"class":370,"line":371},[1442],{"type":18,"tag":368,"props":1443,"children":1444},{},[1445],{"type":23,"value":1434},{"type":18,"tag":19,"props":1447,"children":1448},{},[1449,1451,1457,1459,1465],{"type":23,"value":1450},"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 ",{"type":18,"tag":39,"props":1452,"children":1454},{"className":1453},[],[1455],{"type":23,"value":1456},"*90##",{"type":23,"value":1458}," which was the Prestel logout sequence, confirms with a \"Log OFF ?\" prompt that only accepts ",{"type":18,"tag":39,"props":1460,"children":1462},{"className":1461},[],[1463],{"type":23,"value":1464},"y",{"type":23,"value":1466},", then flips the modem flags to disconnect and returns to the menu.",{"type":18,"tag":1412,"props":1468,"children":1470},{"id":1469},"the-mailbox-editor",[1471],{"type":23,"value":1472},"The Mailbox Editor",{"type":18,"tag":19,"props":1474,"children":1475},{},[1476,1478,1484],{"type":23,"value":1477},"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 ",{"type":18,"tag":39,"props":1479,"children":1481},{"className":1480},[],[1482],{"type":23,"value":1483},"b$",{"type":23,"value":1485}," with a 557-byte limit. The first two bytes of the buffer store the total message length as a 16-bit value:",{"type":18,"tag":357,"props":1487,"children":1489},{"className":1433,"code":1488,"language":1290,"meta":362,"style":362},"7240 LET i=i-3: LET b$(l)= CHR$ (i-256* INT (i/256)): LET b$(2)= CHR$  INT (i/256)\n",[1490],{"type":18,"tag":39,"props":1491,"children":1492},{"__ignoreMap":362},[1493],{"type":18,"tag":368,"props":1494,"children":1495},{"class":370,"line":371},[1496],{"type":18,"tag":368,"props":1497,"children":1498},{},[1499],{"type":23,"value":1488},{"type":18,"tag":19,"props":1501,"children":1502},{},[1503,1505,1511,1513,1519,1521,1527],{"type":23,"value":1504},"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 ",{"type":18,"tag":39,"props":1506,"children":1508},{"className":1507},[],[1509],{"type":23,"value":1510},"CHR$",{"type":23,"value":1512}," and ",{"type":18,"tag":39,"props":1514,"children":1516},{"className":1515},[],[1517],{"type":23,"value":1518},"INT",{"type":23,"value":1520},". You can save messages to tape, load them back, preview them on screen, and send them over the Prestel connection. Lines ending in ",{"type":18,"tag":39,"props":1522,"children":1524},{"className":1523},[],[1525],{"type":23,"value":1526},"#",{"type":23,"value":1528}," are treated as Prestel \"send\" characters.",{"type":18,"tag":1412,"props":1530,"children":1532},{"id":1531},"telesoftware-self-replacing-code",[1533],{"type":23,"value":1534},"Telesoftware: Self-Replacing Code",{"type":18,"tag":19,"props":1536,"children":1537},{},[1538,1540,1546,1548,1554,1556,1561],{"type":23,"value":1539},"The telesoftware downloader (lines 6000-6320) is the wildest part. It moves ",{"type":18,"tag":39,"props":1541,"children":1543},{"className":1542},[],[1544],{"type":23,"value":1545},"CLEAR",{"type":23,"value":1547}," to reclaim RAM below the program, adjusts the Spectrum's ",{"type":18,"tag":39,"props":1549,"children":1551},{"className":1550},[],[1552],{"type":23,"value":1553},"PROG",{"type":23,"value":1555}," pointer directly via POKE, then calls ",{"type":18,"tag":39,"props":1557,"children":1559},{"className":1558},[],[1560],{"type":23,"value":1320},{"type":23,"value":1562}," 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.",{"type":18,"tag":357,"props":1564,"children":1566},{"className":1433,"code":1565,"language":1290,"meta":362,"style":362},"6105 POKE fmod,123- CODE s$: POKE mf,dl: LET ix=ix-999:\n     POKE 23732,ix-256* INT (ix/256): POKE 23733, INT (ix/256):\n     LET X= USR str\n",[1567],{"type":18,"tag":39,"props":1568,"children":1569},{"__ignoreMap":362},[1570,1578,1586],{"type":18,"tag":368,"props":1571,"children":1572},{"class":370,"line":371},[1573],{"type":18,"tag":368,"props":1574,"children":1575},{},[1576],{"type":23,"value":1577},"6105 POKE fmod,123- CODE s$: POKE mf,dl: LET ix=ix-999:\n",{"type":18,"tag":368,"props":1579,"children":1580},{"class":370,"line":380},[1581],{"type":18,"tag":368,"props":1582,"children":1583},{},[1584],{"type":23,"value":1585},"     POKE 23732,ix-256* INT (ix/256): POKE 23733, INT (ix/256):\n",{"type":18,"tag":368,"props":1587,"children":1588},{"class":370,"line":389},[1589],{"type":18,"tag":368,"props":1590,"children":1591},{},[1592],{"type":23,"value":1593},"     LET X= USR str\n",{"type":18,"tag":19,"props":1595,"children":1596},{},[1597],{"type":23,"value":1598},"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.",{"type":18,"tag":19,"props":1600,"children":1601},{},[1602],{"type":23,"value":1603},"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...",{"type":18,"tag":19,"props":1605,"children":1606},{},[1607],{"type":18,"tag":349,"props":1608,"children":1609},{},[1610],{"type":23,"value":1611},"In part 4 we'll dig into the machine code portion.",{"type":18,"tag":1613,"props":1614,"children":1615},"style",{},[1616],{"type":23,"value":1617},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":362,"searchDepth":380,"depth":380,"links":1619},[1620,1621,1622,1623,1624],{"id":69,"depth":389,"text":72},{"id":326,"depth":389,"text":329},{"id":652,"depth":389,"text":655},{"id":736,"depth":389,"text":739},{"id":1290,"depth":389,"text":1293},"markdown","content:blog:2026:2026-04-16-vtx5000-part-3-software-rom.md","content","blog/2026/2026-04-16-vtx5000-part-3-software-rom.md","blog/2026/2026-04-16-vtx5000-part-3-software-rom","md","/blog/2026/04/16/vtx5000-part-3-software-rom/",1917,0,[1635,1638,1641],{"title":1636,"date":1637,"url":62},"VTX5000: Part 2 - Hardware","2026-03-30T23:00:00.000Z",{"title":1639,"date":1640,"url":56},"VTX 5000: Part 1 - Prism, Prestel and Teletext","2026-03-13",{"title":1642,"date":1643,"url":1644},"Extracting files from Tatung Einstein disk images","2023-02-13T10:03:00-08:00","/blog/2023/02/03/tatung-einstein-disk-file-extraction/",[],1776332212951]