LO SCAFFALE DEGLI HOBBY

Appunti On Line

AVVERTENZE da leggere attentamente


Z80

Assemblatore Z80


img

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)

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


img

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
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.

img



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)



All'indice principale | Al vecchio sito