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)
- '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
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)
- 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 nell'ambito dell'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 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)