Sinclair ZX81 | helloWorld Assembly (Parte 2/2)

di | Marzo 18, 2026

Riprendo nuovamente la mia versione dell’helloWorld, alla luce delle nuove conoscenze, e partiamo sibito dal sorgente che ho leggermente modificato. In evidenza le righe modificate :

$ cat helloCliveRom/helloCliveRom.asm
;/*
;.--------------------------------------------------------------------------.
;|                                                                          |
;|                                                                          |
;|     PROGRAM : HelloClive                                                 |
;|                                                                          |
;|     AUTHOR  : jack0e                                                     |
;|                                                                          |
;|     REMARK  ; My first software for Sinclair ZX81                        |
;|               Written in Assembly Language                               |
;|                                                                          |
;|     DATE    : 09/03/2026                                                 |
;|                                                                          |
;|     COMPILE : tasm -80 -b -s helloClive.asm helloClive.p                 |
;|                                                                          |
;|                                                                          |
;'--------------------------------------------------------------------------'

;               ============================================================
;               name   : zx81defs.asm
;               remark : defines to make us feel more at home
;               ------------------------------------------------------------
;               #include "zx81defs.asm"
;               ============================================================


;               ============================================================
;               name   : zx81rom.asm
;               remark : EQUs for ZX81 ROM routines
;               ------------------------------------------------------------
;               #include "zx81rom.asm"
;               ============================================================

;               ============================================================
;               name    : charcodes.asm
;               remarks : ZX81 Character code / How to survive whith ASCII
;               ------------------------------------------------------------
;
;               Sinclair Code.
;
;               Char  Code    |   Char  Code    |   Char  Code    |   Char  Code    |
;               ----- -----   |   ----- -----   |   ----- -----   |   ----- -----   |
;               blank $00     |   0     $1C     |   A     $26     |   CR    $76     |
;               "     $0B     |   1     $1D     |   B     $27     |   NULL  $FF     |
;               funt  $0C     |   2     $1E     |   C     $28     |
;               $     $0D     |   3     $1F     |   D     $29     |
;               :     $0E     |   4     $20     |   E     $2A     |
;               ?     $0F     |   5     $21     |   F     $2B     |
;               (     $10     |   6     $22     |   G     $2C     |
;               )     $11     |   7     $23     |   H     $2D     |
;               >     $12     |   8     $24     |   I     $2E     |
;               <     $13     |   9     $25     |   J     $2F     |
;               =     $14     |                 |   K     $30     |
;               +     $15                       |   L     $31     |
;               -     $16                       |   M     $32     |
;               *     $17                       |   N     $33     |
;               /     $18                       |   O     $34     |
;               ;     $19                       |   P     $35     |
;               ,     $1A                       |   Q     $36     |
;               .     $1B                       |   R     $37     |
;                                               |   S     $38
;                                               |   T     $39
;                                               |   U     $3A
;                                               |   V     $3B
;                                               |   W     $3C
;                                               |   X     $3D
;                                               |   Y     $3E
;                                               |   Z     $3F
;
;               ------------------------------------------------------------
;               #include "charcodes.asm"
;               ============================================================


;               =============================================================
;               Name   ; zx81sys.asm
;               Remark : Definition System variables
;               -------------------------------------------------------------
;               #include "zx81sys.asm"
;               =============================================================
                .ORG  16393                      ; Origin of a ZX81 file is always 16393

;               /*
;               .-----------------------------------------------------------.
;               |           SYSTEM VARIABLE of the Sinclair ZX81            |
;               |             memory byte from 16384 to 16508               |
;               '-----------------------------------------------------------' */
VERSN:          .BYTE 0
E_PPC:          .WORD 2
D_FILE:         .WORD Display
DF_CC:          .WORD Display+1                  ; First character of display
VARS:           .WORD Variables
DEST:           .WORD 0
E_LINE:         .WORD BasicEnd
CH_ADD:         .WORD BasicEnd+4                 ; Simulate SAVE "X"
X_PTR:          .WORD 0
STKBOT:         .WORD BasicEnd+5
STKEND:         .WORD BasicEnd+5                 ; Empty stack
BREG:           .BYTE 0
MEM:            .WORD MEMBOT
UNUSED1:        .BYTE 0
DF_SZ:          .BYTE 2
S_TOP:          .WORD $0002                      ; Top program line number
LAST_K:         .WORD $fdbf
DEBOUN:         .BYTE 15
MARGIN:         .BYTE 55
NXTLIN:         .WORD Line2                      ; Next line address
OLDPPC:         .WORD 0
FLAGX:          .BYTE 0
STRLEN:         .WORD 0
T_ADDR:         .WORD $0c8d
SEED:           .WORD 0
FRAMES:         .WORD $f5a3
COORDS:         .WORD 0
PR_CC:          .BYTE $bc
S_POSN:         .WORD $1821
CDFLAG:         .BYTE $40
PRBUFF:         .BYTE 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,$76  ; 32 Spaces + Newline
MEMBOT:         .BYTE 0,0,0,0,0,0,0,0,0,0,$84,$20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0      ; 30 zeros
UNUNSED2:       .WORD 0


;.===========================================================.
;|                       MAIN Function                       |
;'==========================================================='
;main()
;{
;               =============================================
;               name   : line1.asm
;               remark : Standard REM statement will
;                        contain our 'hex' code.
;                        line1 | This is the REM statement.
;                        example statement :
;                        00 00 39 00 EA
;                                    XX
;                              X===X  ^
;                        X===X   ^    '---- REM
;                          ^     '--------- Lenght Code
;                          '--------------- 00 00
;
;               ---------------------------------------------
;               #include "line1.asm
;               =============================================
Line1:          .BYTE $00,$00               ; Line 1
                .WORD Line1End-Line1Text    ; Line 1 length
Line1Text:      .BYTE $ea                   ; REM


;               +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;               The UserCode starts here and gets added to the end of the REM
;               +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

                ld   de, helloClive
                call print

                ld   de, author
                call print

;               /*
;               .-----------------.
;               | Return to BASIC |
;               '-----------------' */
                ret
;}


;.===========================================================.
;|                      The my function                      |
;'==========================================================='

;/*
;.-----------------------------------------------------------.
;| name   : print                                            |
;| remark : display a string, using PRINT Routine (RST 10)   |
;| input  ; register de : string                             |
;'-----------------------------------------------------------' */
print
                ld a,(de)              ; Load in Accumulator (A) the address of string
                cp $ff                 ; Compare character for verify the endString ($ff=\0|null)
                jp z,printEnd          ; If the character is null (endString), goto end function
                rst 10                 ; PRINT
                inc de                 ; inc de (so string pointer)
                jr print               ; repeat while
printEnd
                ret


;               ================================================================
;               name   : vars.asm
;               remark : my variable
;               ----------------------------------------------------------------
;               #include "vars.asm"
;               ================================================================
;                     H   E   L   L   O   _   C   L   I   V   E   \0
helloClive      .BYTE $2D,$2A,$31,$31,$34,$00,$28,$31,$2E,$3B,$2A,$FF
;                     \CR J   A   C   K   0   E   \0
author          .BYTE $76,$2F,$26,$28,$30,$1C,$2A,$FF


;               ============================================================
;               code ends
;               ============================================================
;               name   : line2.asm
;               remark : end the REM line and put in the RAND USR line
;                        to call our 'hex code'
;               ------------------------------------------------------------
;               #include "line2.asm"
;               ------------------------------------------------------------

;               /*
;               .---------------------------------------.
;               | This is the end of line 1 - the REM   |
;               '---------------------------------------' */
                .BYTE $76                        ; Newline

;               /*
;               .---------------------------------------.
;               | Followed by the the RAND USR line.    |
;               '---------------------------------------' */
Line1End



Line2           .BYTE $00,$0a
                .WORD Line2End-Line2Text
Line2Text       .BYTE $F9,$D4                    ; RAND USR
                .BYTE $1D,$22,$21,$1D,$20        ; 16514
                .BYTE $7E                        ; Number
                .BYTE $8F,$01,$04,$00,$00        ; Numeric encoding
                .BYTE $76                        ; Newline
Line2End


;               ============================================================
;               ZX81 Screen Definition
;               ============================================================
;               name   : screen.asm
;               remark : Display file definition (24x32+CR) - lowres
;               ------------------------------------------------------------
;               #include "screen.asm"
;               ------------------------------------------------------------
Display         .BYTE $76
                .BYTE $76 ; Line 0
                .BYTE $76 ; Line 1
                .BYTE $76 ; Line 2
                .BYTE $76 ; Line 3
                .BYTE $76 ; Line 4
                .BYTE $76 ; Line 5
                .BYTE $76 ; Line 6
                .BYTE $76 ; Line 7
                .BYTE $76 ; Line 8
                .BYTE $76 ; Line 9
                .BYTE $76 ; Line 10
                .BYTE $76 ; Line 11
                .BYTE $76 ; Line 12
                .BYTE $76 ; Line 13
                .BYTE $76 ; Line 14
                .BYTE $76 ; Line 15
                .BYTE $76 ; Line 16
                .BYTE $76 ; Line 17
                .BYTE $76 ; Line 18
                .BYTE $76 ; Line 19
                .BYTE $76 ; Line 20
                .BYTE $76 ; Line 21
                .BYTE $76 ; Line 22
                .BYTE $76 ; Line 23



;               ============================================================
;               ZX81 Screen Definition
;               ============================================================
;               name   : endbasic.asm
;               remark : Close out the basic program structure
;                        BASIC Variable Area
;               ------------------------------------------------------------
;               #include "endbasic.asm"
;               ------------------------------------------------------------
Variables:
VariablesEnd:   .BYTE $80
BasicEnd:

#END

Riuso nella routine PRINT (integrata nel Sinclair Basic)

In pratica, è stata sostituita la funzione printf (che scriveva direttamente nell’area di memoria video) con la funzione print che effettua una chiamata alla routine PRINT. Riutilizza quindi un codice integrato nel Sinclair Basic, già presente nella ROM 8k del Sincair ZX81. Più o meno il funzionamento della routine è il medesimo, ossia : [1] punta l’accumulatore alla stringa da stampare (ossia al valore del registro DE a 16bit; [2] verifica che stia puntando al fine stringa (ff), nel caso esce in caso contrario prosegue. [3] esegue la PRINT (rst 10), che stampa ciò che è presente nell’accumulatore [4] Incrementa di puntatore alla stringa contenuto come detto nell’accumulator e prosegue col ciclo di lettura.

;/*
;.-----------------------------------------------------------.
;| name   : print                                            |
;| remark : display a string, using PRINT Routine (RST 10)   |
;| input  ; register de : string                             |
;'-----------------------------------------------------------' */
print
                ld a,(de)        ; Load in Accumulator (A) the address of string
                cp $ff           ; Compare character the endString ($ff=\0|null)
                jp z,printEnd    ; If the character is null (endString), go out
                rst 10           ; PRINT
                inc de           ; inc de (so string pointer)
                jr print         ; repeat while
printEnd
                ret

La nuova funzione print necessita di ricevere il puntatore alla stringa da stampare, tramite valorizzazione del registro DE. Anche l’invocazone risulta qundi leggermente modificata (righe 178-186).


                ld   de, helloClive
                call print

                ld   de, author
                call print

Chiaramente il riuso di routine presenti nella ROM 8k del Sinclair ZX81 crea inevitabilmente una diendenza con tale macchina, ed il codice risulta meno portabile della precedente versione dell’helloWorld, in cui la scrittura in memoria video avviene direttamente senza alcuna intermediazione.

Ancora hacking, e sempre sulla memoria video… (display)

Ho modificato anche la variabile Display, dopo aver ricevuto l’ultima mail di Paul sull’argomento. Non h capita del tutto, ma mi ha messo una pulce nelle orecchie. Mi chiedevo : “…ma come è possible che l’eseguibile di helloWorld occupi oltre 800 byte. Come hanno fatto a sviluppare un’intero giorco degli scacchi in solo kb di ram”. (cerca 1kchess.p sulla rete). La risposta è stata : “la maggior parte dello spazio è occato attualmente dalla memoria video…”.

Ho capito che si stava parlando della variabile display. O_o Cavolo ! Sto quindi focalizzando l’attenzione li. Vediamo come precedentemente era definita tale variabile (o allocazione di memoria, come la si vuol chiamare!

Questa di seguito è la precedente definizione di display. Sulla rete molti nerd dell’assembler zx81, includono tale struttura nell’header screen.asm”. Io no, preferisco in questa fase di apprendimento avere tutto a portata di mano in un unico file, e senza fronzoli unitili.

E’ facile cpire che tale struttura definisce l’intera area-video dello zx81 (24 righe x 32 colonne = 768 caratteri/byte), allocati inutimente nel caso in cui non se ne abbia bisogno di riepire tutte le righe con degli spazi (lo spazio in ASCII è 32, ma in Sinclair Code è 0). Ogni fine riga viene terminata poi con un CR, ossia un carattere per andare a nuova riga. (Sinclair Code $76)

;               ============================================================
;               ZX81 Screen Definition
;               ============================================================
;               name   : screen.asm
;               remark : Display file definition (24x32+CR) - lowres
;               ------------------------------------------------------------
;               #include "screen.asm"
;               ------------------------------------------------------------
Display         .BYTE $76
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 0
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 1
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 2
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 3
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 4
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 5
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 6
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 7
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 8
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 9
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 10
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 11
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 12
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 13
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 14
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 15
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 16
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 17
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 18
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 19
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 20
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 21
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 22
                .BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$76 ; Line 23

Nel mi caso non serve a nulla, inizializzare tutta l’area video con degli spazi, posso tranquillamente risparmiare parecchi byte (768 – 25 = 743), quindi ho lasciato su ogni riga che definisce la struttura display esclusivamente il carattere di Carriage Return ($76), ottentendo il nuovo blocco alle righe 253-267

;               ============================================================
;               ZX81 Screen Definition
;               ============================================================
;               name   : screen.asm
;               remark : Display file definition (24x32+CR) - lowres
;               ------------------------------------------------------------
;               #include "screen.asm"
;               ------------------------------------------------------------
Display         .BYTE $76
                .BYTE $76 ; Line 0
                .BYTE $76 ; Line 1
                .BYTE $76 ; Line 2
                .BYTE $76 ; Line 3
                .BYTE $76 ; Line 4
                .BYTE $76 ; Line 5
                .BYTE $76 ; Line 6
                .BYTE $76 ; Line 7
                .BYTE $76 ; Line 8
                .BYTE $76 ; Line 9
                .BYTE $76 ; Line 10
                .BYTE $76 ; Line 11
                .BYTE $76 ; Line 12
                .BYTE $76 ; Line 13
                .BYTE $76 ; Line 14
                .BYTE $76 ; Line 15
                .BYTE $76 ; Line 16
                .BYTE $76 ; Line 17
                .BYTE $76 ; Line 18
                .BYTE $76 ; Line 19
                .BYTE $76 ; Line 20
                .BYTE $76 ; Line 21
                .BYTE $76 ; Line 22
                .BYTE $76 ; Line 23

Confrontanto le dimensioni due eseguibili, è palese la diffenza (989byte la precedente versione “helloCliveHack”, 209byte l’attuale versione), che è di 780byte (di cui 743byte son per il restringimento dell’area video, ed i rimanenti 37 byte per le precedenti modifiche). CAVOLO! Non male 😛


jack0e@harlock MINGW64 ~/hack/zx81Dev/tasm
$ ls -la helloCliveRom/helloCliveRom.p
-rw-r--r-- 1 37502307 1049089 209 Mar 18 16:08 helloCliveRom/helloCliveRom.p

jack0e@harlock MINGW64 ~/hack/zx81Dev/tasm
$ ls -la helloClive/helloCliveHack.p
-rw-r--r-- 1 37502307 1049089 989 Mar 18 10:28 helloClive/helloCliveHack.p

Chi la usa la struttura display ? Facile : D_FILE e DF_CC


$ cat helloCliveRom/helloCliveRom.asm | grep Display
D_FILE:         .WORD Display
DF_CC:          .WORD Display+1                  ; First character of display

Per il momento mi fermo qui, anzi no! Non prima di aver eseguito un’analisi rapida del nuovo eseguibile di soli 209byte, di cui :

  • [1] La variabile diplay occupa 26 byte
  • [2] Le variabili di sistema occupano 118 byte
0000: 2D 2A 31 31 34 28 31 2E 3B 2A 37 34 B2 00 02 00 : HELLOCLIVEROM . 
000F: C1 40 C2 40 DA 40 00 00 DB 40 DF 40 00 00 E0 40 : ......  ....  ..
001F: E0 40 00 5D 40 00 02 02 00 BF FD 0F 37 AF 40 00 : .. .. .. Z.?RJ. 
002F: 00 00 00 00 8D 0C 00 00 A3 F5 00 00 BC 21 18 40 :     $£  7.  W5/.
003F: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :                 
004F: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :                 
005F: 76 00 00 00 00 00 00 00 00 00 00 84 20 00 00 00 : .          .4   
006F: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :                 
007F: 00 00 00 2E 00 EA 11 9A 40 CD 8F 40 11 A6 40 CD :    I .),..?.)A..
008F: 8F 40 C9 1A FE FF CA 99 40 D7 13 18 F6 C9 2D 2A : ?..,...;..</..HE
009F: 31 31 34 00 28 31 2E 3B 2A FF 76 2F 26 28 30 1C : LLO CLIVE..JACK0
00AF: 2A FF 76 00 0A 0E 00 F9 D4 1D 22 21 1D 20 7E 8F : E.. .: ..16514.?
00BF: 01 04 00 00 76 76 76 76 76 76 76 76 76 76 76 76 : ..  ............
00CF: 76 76 76 76 76 76 76 76 76 76 76 76 76 76 80    : ...............

Ogni nodo torna al pettine, prima o poi […] Dovrò affrontare prima o poi l’argomento Variabili di Sistema.

Link utili

https://www.retroisle.com/sinclair/zx81/Technical/Firmware/ZX81Assembly.pdf