Z80 Assembler

 ☗ 
Ho scritto questo assemblatore sia come esercizio per l'apprendimento di Python, sia per riprendere in mano alcuni vecchi progetti elettronici (e poter ricompilare i sorgenti assembly scritti nei primi anni 90). Lo sviluppo con Python è stato estremamente veloce e semplice rispetto alla precedente implementazione Pascal/Delphi, sia perché non c'è bisogno di occuparsi di puntatori e allocazioni/deallocazioni di memoria, sia perchè le strutture dati dinamiche di Python hanno permesso una migliore astrazione sui dati di lavoro (dizionari, liste, iterazioni automatiche, funzioni di appartenenza ecc). Il programma è stato successivamente portato in Python 3 con funzionalità e codice migliorati, e successivamente reso indipendente sia dalla versione di Python (>=2.6) che dalla piattaforma (Windows e Linux).

UTILIZZO ASSEMBLATORE

Si può realizzare un assemblaggio semplicemente chiamando la funzione assemble presente nel modulo Z80asm.py:

from z80asm import assemble
risultato = assemble(nome_sorgente)


pyzasm.py

Il modulo pyzasm.py è una comoda interfaccia che permette sia l'avvio di un'apposita GUI (se non si specifica un nome di file sorgente), sia l'assemblaggio da riga di comando (se si specifica il nome del file sorgente):
python pyzasm.py nomefile.asm
In entrambi i casi (GUI o riga di comando) il nome del file oggetto viene per default impostato uguale a quello del sorgente, ma con estensione .bin






pyzasm29.zip (Versione 2.9 per Python >=2.6 e 3.x, Windows e Ubuntu/Linux)



FORMATO ASSEMBLY

Può essere elaborato un numero virtualmente illimitato di righe di codice sorgente assembly, tuttavia il codice oggetto prodotto non può eccedere 64 kiB, che rappresentano il massimo spazio di memoria indirizzabile dallo Z80.

Il testo sorgente può essere scritto indifferentemente in maiuscolo o minuscolo utilizzando un qualsiasi editor che produca file in formato testo ASCII, gli eventuali caratteri di tabulazione vengono automaticamente convertiti in spazi, i caratteri non ascii vengono convertiti in '?'.

Il formato del programma segue il consueto schema del linguaggio assembly Z80:
label       opcode       operando       ;commento
label       direttiva    argomenti      ;commento
A differenza di assembler sintatticamente più restrittivi, ogni elemento può iniziare a partire da qualsiasi colonna.

Label

Una label (etichetta) può iniziare con una lettera o con il simbolo di sottolineatura '_' e proseguire con lettere, cifre e simboli di sottolineatura. Facoltativamente può terminare con il simbolo ':' o iniziare con il simbolo '.'

I nomi delle label sono riconosciuti tali in base al contesto. Benchè l'assemblatore lo permetta, per evitare interpretazioni ambigue è consigliabile dare alle label nomi diversi rispetto a direttive di compilazione, opcode, e nomi di registri.
FOO              ; <---- FOO e` una label
LD    EQU  12    ; <---- LD e` una label, ambigua con opcode LD
XOR:             ; <---- XOR e` una label, ambigua con opcode XOR
LD    A,B        ; <---- LD e` un opcode
NOP              ; <---- NOP e` un opcode
.EQU  EQU  15    ; <---- EQU e` una label, ambigua con direttiva EQU
END              ; <---- END e` una direttiva
NOTA: Una label si può trovare da sola su una riga, ma non va mai scritta sulla riga dove è presente una direttiva INCLUDE in quanto verrebbe ignorata.



Commenti

Il punto e virgola indica l'inizio di un commento per il programmatore, tutto ciò che si trova dopo questo simbolo viene ignorato.

NOTA: Un ';' può comunque essere scritto all'interno di una stringa o di una costante carattere, in questi casi viene considerato come un carattere e non come l'inizio di un commento.

Costanti numeriche

Nel testo sorgente valori numerici possono essere scritti in decimale, esadecimale, binario e carattere ASCII. Le costanti decimali negative vengono codificate in complemento a due. I modi diversi per scrivere le costanti binarie ed esadecimali servono per accettare testi sorgenti di diverse "epoche", in passato era comune scrivere una costante esadecimale con il simbolo $. Si possono usare indifferentemente lettere maiuscole o minuscole.
1200       ; Costante decimale
36D        ; Costante decimale

$0AF4      ; Costante esadecimale
&0AF4      ; Costante esadecimale
#0AF4      ; Costante esadecimale
0FAH       ; Costante esadecimale (deve iniziare con una cifra)
0XFA       ; Costante esadecimale

10010B     ; Costante binaria
%10010     ; Costante binaria
0B1001     ; Costante binaria

'Z'        ; Valore ASCII del carattere Z
' '        ; Valore ASCII del carattere spazio
'\''       ; Valore ASCII del carattere '
"'"        ; Valore ASCII del carattere '
'\\'       ; Valore ASCII del carattere \
Davanti ad ogni costante può essere scritto il più o il meno unario.


Stringhe

Le stringhe di caratteri vanno sempre racchiuse tra apici o virgolette
'stringa'              ;caratteri: stringa
"altra stringa"        ;caratteri: altra stringa
In una stringa si può liberamente scrivere il delimitatore che non è stato usato per racchiuderla.
'un "certo" qualcosa'    ;caratteri: un "certo" qualcosa
"un po' di piu`"         ;caratteri: un po' di piu`
In una stringa si possono scrivere le seguenti sequenze di escape che vengono considerate un singolo carattere:
\\  →  \
\'  →  '
\"  →  "
\0  →  byte 0
\t  →  byte 9   (TAB)
\r  →  byte 13  (CR)
\n  →  byte 10  (LF)
Con le sequenze di escape è possibile scrivere anche il carattere corrispondente al delimitatore della stringa:
"un \"certo\" qualcosa"   ;caratteri: un "certo" qualcosa
'un po\' di piu`'         ;caratteri: un po' di piu`
Siccome il simbolo \ si usa per indicare l'inizio di una sequenza di escape, la sequenza di escape \\ serve per indicare il simbolo stesso:
'\\'              ;carattere: \
"\\\"\\"          ;caratteri: \"\
Ogni stringa deve essere lunga almeno un carattere, in assembly non hanno alcun senso le stringhe nulle.


Espressioni

Dove il formato di un'istruzione o di una direttiva di compilazione lo consente, è possibile scrivere un' espressione composta da costanti e label usando gli operatori + - * / % che viene calcolata al momento dell'assemblaggio. Gli operatori matematici / e % indicano rispettivamente la divisione intera e il modulo (resto della divisione).
LABEL1 + LABEL2 - $3F
INIZIO * (4 + 100011B)
In un'espressione si può usare il simbolo '$' che indica il valore del program counter all'inizio della riga/istruzione in esame:
JP     $   ; salta a sè stessa, loop infinito
JR     $   ; salta a sè stessa, loop infinito
DJNZ   $   ; salta a sè stessa finché B non diventa zero
Nel solo caso dei salti relativi (istruzioni 'JR' e 'DJNZ') viene calcolata la differenza tra il valore dell'espressione e il program counter attuale meno 2. Il meno 2 è dovuto al fatto che le istruzioni di salto relativo puntano già all'indirizzo dell'istruzione successiva due byte piu avanti.

Per questo motivo l'espressione $ darà luogo ad un valore di salto relativo pari a -2. Siccome il range di salto a 8 bit è compreso tra -128 e 127, è possibile specificare un offset da $ compreso tra -126 e 129.
JR     $ - 126   ; salta 126 byte indietro dall'indirizzo della JR
JR     $ + 129   ; salta 129 byte più avanti dall'indirizzo della JR


Sintassi

Per quanto riguarda la sintassi delle 696 istruzioni ci si può riferire al file z80tab.txt che le contiene tutte in ordine alfabetico assieme alla relativa codifica esadecimale. Sono inoltre riconosciute 102 istruzioni aggiuntive "non documentate", che non fanno parte del set standard ufficiale, ma di fatto funzionano, e permettono di usare i registri IX e IY anche come singoli registri a 8 bit chiamati IXH IXL IYH IYL.

I simboli ## e #### indicano i punti in cui vanno scritte delle costanti o i valori delle espressioni che vengono calcolate dall'assemblatore. I valori a 16 bit vengono memorizzati in ordine little endian (il byte meno significativo all'indirizzo di memoria più basso).
LD A,##     ;al posto di ## va scritto un valore tra 0 e 255
LD HL,#### ;al posto di #### va scritto un valore tra 0 e 65535






DIRETTIVE DI COMPILAZIONE

Possono essere scritte con o senza i simboli '.' o '#' davanti.


label EQU espressione

Questa direttiva assegna a una label il valore di "espressione".
LABEL1	EQU    45000
LABEL2 EQU $FFFF
LABEL3 EQU LABEL1 + 80

DEFINE ricerca sostituto

Nel testo sorgente i caratteri corrispondenti a 'ricerca' vengono sostituiti con il contenuto di 'sostituto'.
Ad esempio è possibile usare un nome al posto del numero del bit nelle istruzioni BIT SET e RES.
DEFINE  TXBIT  7
BIT     TXBIT,(HL)

DB espressione o stringa, espressione o stringa, ...

La direttiva DB (alias DEFB, DM, DEFM, BYTE, TEXT, ASC) consente di specificare una sequenza di byte che verrà inserita nel codice sorgente nel punto in cui è scritta, ogni elemento è un singolo byte il cui valore può essere compreso tra 0 e 255.
DB  12, 44, -8, 01001B, $FA, VALORE+12, 'F', 1110B
La direttiva DB consente di specificare anche delle stringhe di caratteri, i cui codici ascii verranno inseriti nel codice oggetto nel punto in cui è scritta.
DB   "stringa", 0
DB   'nuova stringa', 0
DB   'nuova stringa\0'
DB   "stringa con degli \"escape\"", 13, 10, 0
DB   "un po'piu` avanti\n\0"
DB   'e` "quasi" riuscito\r\n\0'
NOTA: Il carattere ';' all'interno di una stringa viene considerato parte della stringa stessa e non l'inizio di un commento.

DW espressione, espressione, espressione, ...

La direttiva DW (alias DEFW, WORD)consente di specificare una sequenza di words che verrà inserita nel codice sorgente nel punto in cui è scritta, ogni elemento è una coppia di byte il cui valore può essere compreso tra 0 e 65535 di cui viene memorizzato prima il byte basso e poi quello alto (little endian).
DW  4000, LABEL1+LABEL3, 0FF0CH, 'P', 0
NOTA: In una DW non si possono inserire stringhe ma solo singoli caratteri.

DS bytes, initializer

La direttiva DS (alias DEFS, BLOCK) consente di riservare un blocco di byte, eventualmente inizializzati con il valore specificato da 'initializer'.
TEMP:  DS  128
TEMP2: DS  1024, 207



INCLUDE "nomefile"

Questa direttiva consente di includere nel testo sorgente un altro file sorgente nel punto in cui è scritta, si può arrivare fino a 7 livelli di inclusione.

NOTA: i file da includere devono trovarsi nella stessa directory del file principale.




ORG indirizzo

Questa direttiva è facoltativa, indica l’indirizzo iniziale a cui verranno caricate le istruzioni in memoria, se viene omessa l'indirizzo parte da 0.

Può essere indicata più di una org all'interno dello stesso programma, ma in questo caso si deve usare il file .hex che contiene sempre l'indicazione degli indirizzi di caricamento, mentre il file .bin risulta inutilizzabile.


END

Facoltativa, se presente tutto ciò che la segue (anche nei file inclusi con INCLUDE) viene ignorato.



ESEMPIO DI SINTASSI

                 ORG    30000
   DESTINAZIONE  EQU    61000             ; Valorizza DESTINAZIONE
   DESTINAZ_2    EQU    DESTINAZIONE + 50 ; Valorizza DESTINAZ_2

TRASMETTI:       EQU    50000             ; Indirizzo subr.trasmissione
                 LD     HL,TABELLA_1      ; HL=indirizzo di TABELLA_1
                 LD     BC,30             ; BC=nr.byte da copiare                 
                 LD     DE,DESTINAZ_2     ; DE=destinazione
                 LDIR                     ; Copia blocco di 30 byte
                 LD     HL,MESSAGGIO      ; HL=indirizzo di MESSAGGIO
                 _CICLO
                 LD     A,(HL)            ; Legge carattere
                 CP     0
                 RET    Z                 ; Se zero binario fine
                 CALL   TRASMETTI         ; Chiama subroutine
                 INC    HL                ; Incrementa indice
                 JP     _CICLO            ; Prossimo carattere

                 ORG    40000
CRLF:            DB     "\r\n"
TABELLA_1:       DB     ';', 67, 0x80, 90, "'", %1010, 200, 0b100
                 DB     1, 111B, 22, 56, 180, &FF, #AF, 0, 0, 0, 219
                 DB     "zx\n\0", 0FFH, 0, '\\',  12,  -99, 245, 177
MESSAGGIO        DEFM   'Hello World!', 0


STRUTTURA MODULI ASSEMBLATORE

pyZasm.py  (interfaccia grafica)
  |
  +-- tkinter
  +-- tkfiledialog
  |
  +-- z80asm.py (assemblatore)
         |
         |
         +-- z80dict.py (dizionario codifica istruzioni)



Vecchie versioni buggate non mantenute:

pyfZ80.zip
(Versione 1.0 per Python 2.5 - 2008)

pyfZ80_3k.zip
(Versione 2.0 per Python 3.x - 2011)