* * * Z80 SIMULATION v3.00 (Freeware) * * * | |
Linguaggio ASSEMBLER |
3.1 IL LINGUAGGIO DI Z80 SIMULATION
Per scrivere programmi che possano essere compilati ed eseguiti da Z80 Simulation, è necessario conoscere la sintassi e la semantica di ciascuna istruzione. Tali istruzioni sono identiche a quelle utilizzate dal microprocessore Z80 o al più possono differire da queste solo per alcuni particolari che comunque sono evidenziati nei prossimi paragrafi. Proprio in virtù di questa similitudine, spesso nel resto del testo, si utilizzerà il termine "microprocessore" al posto del termine "esecutore di Z80 Simulation".
Prima di descrivere una per una le istruzioni, vediamo quali sono gli oggetti su cui operano (registri, flag, memoria, stack, e porte di Input/Output) e il formato generale di una linea di programma.
3.2 I REGISTRI
I registri servono per memorizzare in modo temporaneo i dati da elaborare dal microprocessore. Questi si presentano perciò, come fossero delle locazioni di memoria di 8 o 16 bit a seconda che il registro sia semplice o doppio.
I registri semplici di Z80 Simulation sono:
A, B, C, D, E, H, L, F, I, R
mentre quelli doppi sono:
AF, BC, DE, HL, IX, IY, SP, PC.
Si noti comunque, che i registri doppi AF, BC, DE e HL non sono altro che i registri A, F, B, C, D, E, H, L combinati a due a due. Ciò vuol dire che un'operazione del tipo:
LD HL,#12FE ;carica #12FE in HL
equivale a due istruzioni del tipo:
- LD H,#12 ;carica #12 in H
- LD L,#FE ;carica #FE in L
Inoltre questi ultimi registri, sono presenti in due blocchi: quello formato dai registri AF, BC, DE e HL, e quello degli omologhi che indicheremo con AF', BC', DE' e HL'. Si passa da un blocco all'altro mediante le istruzioni:
- EXX
- EX AF,AF'
Con ciò si mette in evidenza il fatto che i registri normali non possono essere utilizzati contemporaneamente ai rispettivi omologhi e che quindi Z80 Simulation, nella sintassi delle istruzioni non distingue tra normali e omologhi. Sarebbe infatti scorretta l'istruzione:
LD HL',#12FE
Diamo adesso una breve descrizione dei ruoli dei registri:
3.3 I FLAG
Vediamo adesso il significato di ciascun bit del registro F:
Il bit 0 (C). E' il flag del carry che in genere è settato a 1 quando si verifica un riporto sull'ultimo bit. Inoltre è alterato dalle operazioni di shift e dalle operazioni logiche.
Il bit 1 (N). E' il flag N che viene settato quando viene effettuata una sottrazione o un decremento. Non può essere testato mediante i salti condizionati, ma è sfruttato dalla operazione DAA.
Il bit 2 (P/V). E' il flag di parità o di overflow. Quando lavora come flag di parità (in genere nelle operazioni logiche e negli shift) viene posto a uno quando il numero dei bit posti a 1 nel risultato è pari. Se invece lavora come flag di overflow (in genere nelle operazioni aritmetiche) viene settato a 1 quando il segno del risultato è errato. Supponiamo ad esempio di voler sommare -1 a -128. Le istruzioni sono:
- LD A,255 ; -1=255 in complemento a 2
- ADD A,128 ; -128=128 in complemento a 2
Il risultato corretto sarebbe -129 ma invece nel registro A troviamo il valore 127. In questo caso il flag P/V sarà posto a 1.
Il bit 3. Questo bit non è un flag e non ha alcun significato. Il suo valore è casuale.
Il bit 4 (H). E' il flag dell'half-carry e indica il riporto che avviene al bit 3 dell'accumulatore. Non può essere testato con le istruzioni di salto condizionato, ma è sfruttato dall'operazione DAA.
Il bit 5. Questo bit non è un flag e non ha alcun significato. Il suo valore è casuale.
Il bit 6 (Z). E' il flag dello zero: viene posto a 1 nel momento in cui il risultato di un'operazione è 0.
Il bit 7 (S). E' il flag del segno. Quando viene posto a 1 significa che il risultato dell'operazione è negativo (in altre parole il bit più significativo del risultato è uguale a 1).
3.4 LA MEMORIA
Il microprocessore per poter memorizzare i dati dell'elaborazione, qualora i registri non siano più sufficienti, utilizza la memoria. Z80 Simulation ne ha una di 16 Kbyte, i cui indirizzi sono contigui, ma variabili dall'utente mediante il comando dell'ambiente integrato [Options]/[Memoria]/[Posiziona] il cui funzionamento è stato già descritto.
Per accedere ad un byte di memoria ci sono più modi di indirizzamento:
Indirizzamento diretto. In questo modo di indirizzamento, viene specificato direttamente, mediante una costante, l'indirizzo della locazione a cui si vuole accedere. Se si vuole ad esempio caricare il contenuto del registro A nella locazione di indirizzo #2FCB si scrive:
LD (#2FCB),A
Indirizzamento indiretto. In questo caso l'indirizzo della locazione a cui si vuole accedere, viene specificato mediante un doppio registro (registro puntatore). L'equivalente dell'esempio precedente è:
Indirizzamento indicizzato. E' una tecnica di indirizzamento molto simile alla precedente: l'indirizzo è ottenuto come somma (algebrica) tra uno dei registri IX o IY ed una costante a 8 bit che chiameremo spiazzamento. Si noti comunque, che la costante viene rappresentata in complemento a 2 e che quindi lo spiazzamento deve essere compreso tra -128 e 127. Le seguenti istruzioni caricano il valore di A nel byte di indirizzo pari a quello contenuto in IX diminuito di uno:
LD (IX+255),A o anche LD (IX-1),A
mentre queste altre caricano il valore #A5 nella locazione di indirizzo 300:
Per concludere il discorso sulla memoria, si noti che un qualsiasi tipo di accesso ad una locazione non presente, implica l'arresto del programma con la visualizzazione di un messaggio d'errore.
3.5 LO STACK
Una delle difficoltà della programmazione dei microprocessori, consiste nella gestione dello spazio della memoria. Bisogna infatti tener conto degli indirizzi in cui si collocano i dati. Con lo stack è invece tutto più semplice. Questo è una struttura LIFO (comunque residente in memoria), in cui i dati vengono immessi (istruzione PUSH) e poi prelevati (istruzione POP) in ordine inverso da quello con cui sono stati inseriti, senza dover tenere conto dell'indirizzo di memoria in cui saranno destinati.
Lo stack gioca un ruolo importante, anche nella gestione delle chiamate dei sottoprogrammi. Infatti: quando si esegue una chiamata a sottoprogramma (istruzione CALL), il microprocessore salva il contenuto del registro PC nello stack; quando bisogna ritornare all'istruzione successiva alla chiamata (istruzione RET), il microprocessore carica nel PC il dato prelevato dalla cima dello stack. La cima dello stack viene puntata dal registro SP. Tenendo conto che lo stack cresce da indirizzi alti a indirizzi bassi, possiamo dire che: ad ogni deposito nello stack, corrisponde il decremento di SP; ad ogni prelievo corrisponde l'incremento di SP.
All'avviamento di Z80 Simulation, la cima dello stack viene automaticamente posta alla fine della memoria, ma può essere riposizionata all'indirizzo nnnn mediante l'istruzione LD SP,nnnn.
3.6 LE PORTE DI INPUT/OUTPUT
Nel microprocessore Z80, le porte di INPUT/OUTPUT sono 256 con indirizzi che vanno da 0 a 255. Tramite queste, il microprocessore comunica con i dispositivi periferici, ed in particolare scrive su di un dispositivo con un'istruzione di output, e legge da esso con un'istruzione di input.
Tuttavia, Z80 Simulation non può comunicare con l'esterno. In esso le istruzioni di input provocano (qualora il debugger lo permetta), la visualizzazione nella finestra di INPUT/OUTPUT del messaggio:
INP (porta #pp): 00
L'utente può quindi digitare un valore che sarà considerato come input dalla porta pp. Le istruzioni di output causano invece la visualizzazione del messaggio:
OUT (porta #pp): nn
dove nn è il valore scritto sulla porta pp.
3.7 IL FORMATO DELLE COSTANTI
Abbiamo avuto già modo di vedere, specialmente negli esempi, che le costanti numeriche possono essere scritte in 2 formati:
Formato decimale. Si specifica il numero con cifre decimali. Per esempio:
Formato esadecimale. Si specifica il numero con cifre esadecimali precedute dal simbolo #. Per esempio:
Si possono anche definire delle costanti simboliche tramite la pseudo-istruzione EQU il cui funzionamento è descritto più avanti.
3.8 I COMMENTI
Spesso, all'interno del programma, si ha la necessità di inserire dei commenti. In Z80 Simulation questi iniziano con il simbolo ";" e possono occupare un'intera linea o condividerla con un'istruzione. Sono esempi di commento i seguenti:
; SALVATAGGIO NELLO STACK
PUSH HL ;SALVA HL
PUSH AF ;SALVA AF
; FINE SALVATAGGIO
LD IX,0 ;AZZERAMENTO DI IX
CALL STAMPA ;CHIAMA STAMPA
3.9 LE LABEL
Si è già osservato, che mentre nel microprocessore Z80 il registro PC contiene l'indirizzo di memoria della prossima istruzione da eseguire, in Z80 Simulation, questo registro contiene il numero della prossima riga di programma da eseguire. Si deduce perciò, che l'operando delle istruzioni di salto non è un indirizzo, ma un numero di riga: se per esempio si vuole saltare alla linea 315, basta scrivere:
JP 315
Tuttavia, questo metodo è abbastanza scomodo, poiché non solo è necessario tener conto della posizione in cui si collocano le istruzioni, ma, anche un semplice inserimento di una riga, può comportare la modifica di molte istruzioni di salto.
Per evitare questi problemi, il compilatore di Z80 Simulation gestisce le cosiddette label. Queste, altro non sono, che delle costanti, che assumono il valore del numero della linea in cui vengono specificate. Una label deve essere composta da lettere e cifre, non può superare 6 caratteri di lunghezza e non deve iniziare con una cifra.
Vediamo adesso l'uso delle label nel seguente programma che inizializza la memoria dall'indirizzo #0000 all'indirizzo #00FF, con il valore 32:
;INIZIALIZZA MEMORIA (0000-00FF)
;CON IL VALORE 32
ORG #B000
VALUE EQU 32 ; VALUE ¬ 32
LD HL,0 ; HL ¬ 0
LOOP LD (HL),VALUE ; (HL) ¬ value
INC L ; incrementa L e quindi HL
JP NZ,LOOP ; se L<>0 torna a LOOP
HALT
Si noti infine, che le label non possono essere usate in luogo delle costanti nelle istruzioni che non siano di salto, ovvero sarebbe scorretta l'istruzione
LD A,LOOP
dove LOOP sia una label.
3.10 LE CONDIZIONI
Il microprocessore può eseguire dei salti condizionati basandosi sullo stato dei flag. Vediamo adesso quali sono le condizioni:
Ecco alcuni esempi di salto condizionato:
3.11 I SALTI ASSOLUTI E RELATIVI
Si è detto in precedenza, che quando il microprocessore esegue un'istruzione di salto, in realtà altro non fa se non caricare l'indirizzo di salto nel registro PC.
Questo modo di saltare lo diciamo assoluto, per distinguerlo da quello relativo, in cui l'operando dell'istruzione di salto, non è l'indirizzo a cui saltare, bensì un valore compreso fra -128 e 127, che verrà sommato al valore del PC. In particolare poi, in Z80 Simulation, dove non si parla di indirizzi, ma di numeri di linea, un'istruzione del tipo JR 20, fa saltare l'esecutore 20 linee più avanti della linea successiva a quella che contiene la JR. Un'istruzione del tipo JR 253 fa tornare indietro l'esecutore di 3 linee (-3 in complemento a 2 è 253) rispetto alla linea successiva a quella contenente la JR. Si noti comunque, che se l'operando di un'istruzione di salto relativo, è una label, questo non vuol dire che l'esecutore salterà avanti o indietro per un numero di linee pari al valore della label, bensì salterà (come per il salto assoluto) sulla linea in cui è stata definita la label.
3.12 LE PSEUDO-ISTRUZIONI
Le pseudo-istruzioni sono delle istruzioni che non producono codice eseguibile, ma hanno particolari funzioni durante la compilazione del programma. Z80 Simulation ne ha soltanto due, vediamole di seguito:
- HEX80 EQU #80 ; HEX80 ¬ #80
- BUFFER EQU #FDE1 ; BUFFER ¬ #FDE1
Anche le costanti simboliche, come le label, devono essere composte da lettere e cifre, non possono superare 6 caratteri di lunghezza e non devono iniziare con una cifra.
3.13 LE ISTRUZIONI
Nei prossimi paragrafi sono descritte tutte le istruzioni del microprocessore Z80. Il titolo di ciascun paragrafo è l'istruzione stessa, mentre all'interno di questo viene descritta la sintassi, lo scopo, l'influenza sui flag. Spesso sono presenti alcune note sul comportamento dell'istruzione e sulle eventuali differenze tra microprocessore vero e proprio e Z80 Simulation.
3.14 I SIMBOLI UTILIZZATI
Nel descrivere le varie istruzioni utilizzeremo i seguenti simboli:
"REG8" (registri a 8 bit). Con questo simbolo indicheremo i registri a 8 bit.
"REG16" (registri a 16 bit). Con questo simbolo indicheremo i registri a 16 bit.
"PMEM" (puntatore alla memoria). Con questo simbolo indicheremo i puntatori alla memoria del tipo HL, IX+DD e IY+DD, ove "DD" sia un valore compreso tra -128 e 127.
"VAL8" (costante a 8 bit). Con questo simbolo indichiamo una costante a 8 bit.
"VAL16" (costante a 16 bit). Con questo simbolo indichiamo una costante a 16 bit.
"·" (flag non modificato). Il simbolo "· " indica, che il flag sotto cui è posto, non viene modificato dalla istruzione.
"» " (flag modificato). Il simbolo "» " indica, che il flag sotto cui è posto, viene modificato dallistruzione.
"X" (flag modificato in modo casuale). Il simbolo "X" indica, che il flag sotto cui è posto, viene modificato dallistruzione, ma in modo casuale.
"nb" (numero di bit). Con "nb" indicheremo un bit all'interno di un byte. Il bit meno significativo viene indicato con 0.
(condizione). Il Simbolo CC indica le condizioni NZ, Z, NC, C, PO, PE, P, M.