Z80
Assemblatore Z80
Un assemblatore personale per CPU Z80
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
molto più veloce e semplice rispetto alla precedente versione in Pascal/Delphi. In
Python non c'è bisogno di occuparsi esplicitamente di puntatori e
allocazioni/deallocazioni della memoria dinamica. Le strutture dati di Python, liste, dizionari,
iterazioni automatiche, funzioni di appartenenza ecc... hanno permesso una migliore astrazione.
Il programma è stato successivamente portato in Python3 con funzionalità e codice migliorati.
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)
- 'nome_sorgente' è una stringa, 'risultato' è una tupla di tre oggetti, il primo è un booleano True o False a seconda che l'assemblaggio sia andato a buon fine o meno.
- In caso di errore il secondo oggetto è una stringa con la riga del sorgente dove si è verificato un errore e il terzo una stringa con il frammento di codice specifico e descrizione dell'errore.
- In caso di assemblaggio ok il secondo oggetto è una stringa che indica la lunghezza in byte del codice oggetto prodotto.
- Ad ogni assemblaggio vengono prodotti i seguenti file:
- Un file binario .bin da caricare in memoria così com'è (è consentita una sola direttiva ORG).
- Un file di testo .hex con la codifica delle istruzioni e il corrispondente indirizzo di caricamento (permette direttive ORG multiple).
- Un file di testo .lst crossreference con la tavola delle label.
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
DOWNLOAD
Versione 0.3 per Python3 (>3.1)
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.
L'assemblatore è case insensitive, il testo sorgente quindi può essere scritto indifferentemente in maiuscolo o minuscolo, utilizzando un qualsiasi editor che produca file in formato testo ASCII. I caratteri con codice inferiore allo spazio vengono
convertiti in spazi, mentre i caratteri con codice superiore al 126 vengono convertiti in punti di domanda.
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, le label possono essere scritte a partire da qualsiasi colonna,
e gli opcode possono essere scritti anche nella prima 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 i simboli '.' o '#'
I nomi delle label sono riconosciuti in modo euristico in base a quanto si può dedurre dal contesto. Nonostante 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
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 diversi modi 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
-20 ; 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
b1001 ; 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 \
$ ; Valore del program counter attuale
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
\r → byte 13 (CR)
\n → byte 10 (LF)
Con le sequenze di escape è quindi possibile scrivere anche il carattere corrispondente all'attuale 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 le stringhe nulle
non hanno senso.
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.
LABEL1 + LABEL2 - $3F
INIZIO * (4 + 100011B)
1 << BIT_INGRESSO
- Tutte le espressioni vengono precalcolate al momento dell'assemblaggio,
nel codice macchina eseguibile non esistono espressioni.
- Le espressioni vengono calcolate da sinistra verso destra senza alcuna priorità,
per definire una qualsiasi priorità si devono usare le parentesi tonde.
- Se il valore di una costante o di un' espressione supera i limiti ammessi
dall'istruzione a cui si riferisce (3, 8 o 16 bit), l' assemblatore
lo segnala con un messaggio di errore.
In un'espressione si può usare il simbolo '$', che identifica l'indirizzo
dell'istruzione attuale:
JP $ ; salta a se stessa, loop infinito
JR $ ; salta a se stessa, loop infinito
DJNZ $ ; salta a se stessa finche' B non diventa zero
CALL $ ; errore, stack overflow!
Sintassi
Per quanto riguarda la sintassi delle 696 istruzioni standard ci si può riferire alla
pagina codici operativi,
che le contiene tutte in ordine alfabetico,
assieme alla relativa codifica esadecimale e al tempo di esecuzione in cicli di clock.
Sono inoltre riconosciute 102 istruzioni aggiuntive “non documentate”,
che non fanno parte del set standard ufficiale, ma
di fatto funzionano, e in molte operazioni permettono di usare
i registri IX e IY come coppie di 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
C'è un solo caso speciale, le seguenti istruzioni prevedono la presenza di
due valori a 8 bit:
LD (IX+##),##
LD (IY+##),##
I registri IX e IY si possono scrivere con o senza offset.
L'offset deve essere compreso tra -128 e +127, valori superiori a +127
(fino al massimo) vengono considerati negativi in complemento a due.
LD A,(IX+30)
LD (IY-20),B
LD (IY),128
LD A,(IX+LOFFS+1)
Direttive di compilazione
Possono essere facoltativamente precedute dai simboli
. o
#
label EQU espressione
La direttiva EQU (alias DL) assegna ad una label il valore
di “espressione”. L'espressione
viene calcolata al momento dell'assemblaggio.
LABEL1 EQU 45000
LABEL2 .EQU $FFFF
LABEL3 #EQU LABEL1 + 80
DEFINE (alias DEF) 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)
O ancora è possibile creare combinazioni più complesse:
DEFINE BIT_ACCESO 5
DEFINE FLAG_ACCESO BIT_ACCESO,(IX)
DEFINE BASE 56
DEFINE FASE_RX (IY+10)
SET FLAG_ACCESO
LD FASE_RX,BASE
Attenzione: le define vanno comunque usate
con molta attenzione, in
quanto effettuano una sostituzione “stupida” direttamente sulle
righe di testo sorgente. Una define troppo generica o
breve può risultare accidentalmente sostituibile anche in qualsiasi
altra riga che contiene la parte da cercare come sottostringa, si
possono cioè verificare delle sostituzioni non volute.
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 o stringa, espressione o stringa, ...
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 fisico reale 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 anche l'indicazione degli indirizzi di caricamento, mentre il file .bin (immagine binaria della memoria) risulterebbe inutilizzabile.
END
Facoltativa, se presente tutto ciò che la segue viene ignorato. Si usa per
aggiungere annotazioni alla fine del codice che verranno ignorate durante l'assemblaggio.
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)