ELEMANIA
Z80 - Linguaggio assembly
Linguaggio assembly e programma assemblatore

In generale il linguaggio assembly è un linguaggio di programmazione a basso livello (per contrapposizione con i cosiddetti linguaggi ad alto livello, come il C, il Pascal etc.) nel quale ad ogni istruzione corrisponde uno e un solo codice in linguaggio macchina. In pratica l'assembly fornisce degli equivalenti letterali che evitano al programmatore la fatica di memorizzare i codici esadecimali delle corrispondenti istruzioni.

Ogni microprocessore presenta il proprio set di istruzioni e di conseguenza ha il proprio linguaggio assembly. In realtà il linguaggio assembly non dipende solo dal µP ma anche dal particolare assemblatore (assembler) usato per effettuare la traduzione da assembly a linguaggio macchina. L'uso di un programma assemblatore rende in effetti molto pratico la scrittura di programmi in assembly, in quantio la traduzione in linguaggio macchina viene effettuata automaticamente dall'assemblatore (in caso contrario sarebbe lo stesso programmatore a dover tradurre a mano le istruzioni da assembly a linguaggio macchina e in tal caso gran parte dei vantaggi dell'uso del linguaggio assembly verrebbero meno).

Occorre però osservare che, a differenza di quanto accade per i linguaggi ad alto livello (es. il C), le regole e la sintassi del linguaggio asssembly non sono strettamente standardizzate e possono variare, anche per lo stesso µP, da un assemblatore all'altro. Nel seguito ci si atterrà alle regole sintattiche più comunemente utilizzate, avvertendo però il lettore che si potranno incontrare anche grosse differenze in casi particolari.

Struttura generale di un programma in assembly Z80

In generale un programma scritto in assembly Z80 sarà costiutito da un file di testo, cioè da un file realizzato con un semplice editor di testo (es. il Blocco Note di Windows). L'estensione del file è arbitraria poiché varia da un assemblatore all'altro (alcuni usano l'estensione .asm ma non si tratta di uno standard).

Le istruzioni del programma devono essere scritte spostandosi verso destra di una tabulazione, cioè di 8 caratteri (anche un singolo blank va bene, ma la tabulazione fornisce un codice più ordinato). In pratica la prima colonna deve rimanere vuota, come nell'esempio qui sotto:

            LD A,0
            LD B,7
            ADD A,B

La prima colonna dev'essere lasciata libera perché, come vedremo meglio fra poco, può essere usata per le etichette.

In generale ogni riga del programma corrisponde a una singola istruzione. Le istruzioni possono essere scritte in maiuscolo o minuscolo a seconda dell'assemblatore (in generale è indifferente, l'assembly non è case-sensitive, cioè non distingue maiuscole da minuscole).

Eventuali commenti vengono indicati da un punto e virgola ; come nell'esempio qui sotto:

            LD A,0 ; carica zero in A
            LD B,7 ; carica sette in B
            ADD A,B ; somma A e B

Alcuni programmatori hanno l'abitudine di separare anche i commenti per mezzo di una tabulazione, in modo da suddividere il programma ordinatamente in colonne, così:

            LD A,0        ; carica zero in A
            LD B,7        ; carica sette in B
            ADD A,B      ; somma A e B

L'idea è quella di riservare la prima colonna alle etichette, la seconda alle istruzioni e la terza ai commenti. E' importante osservare che molti assemblatori richiedono che anche eventuali righe vuote vengano commentate. Pertanto per inserire correttamente righe di separazione all'interno del programma bisogna scrivere così:

            LD A,0    ; carica zero in A
            LD B,7    ; carica sette in B
;
            ADD A,B  ; somma A e B

 

Costanti numeriche

Occorre, come premessa, tenere presente che le convenzioni di uso delle costanti numeriche possono variare da un programma assemblatore all'altro (cioè non esiste uno standard universale).

In generale le costanti numeriche, se non diversamente indicato, sono intese come valori decimali (in base 10):

LD A, 473

Alcune volte i valori vengono specificati come decimali aggiungendo una lettera d finale:

LD A,473d

Per indicare un valore esadecimale, di solito si aggiunge in fondo una lettera h, come ad esempio in

LD A,0F5h

Come mostra l'esempio qui sopra, quando il numero esadecimale inizia con una cifra letterale (es. A, B, C, D, E o F), si premette uno zero davanti al numero, per evitare che il valore venga interpretato come una costante letterale.

In altri casi i valori esadecimali vengono indicati con

LD A, $F5

oppure con

LD A, 0xF5

In alcuni assemblatori che consentono di assegnare etichette a locazioni di memoria (vedi oltre la spiegazione della direttiva EQU), per evitare la confusione fra i numeri in esadecimale e i nomi delle variabili si usa premettere uno 0 a tutti i valori esadecimali (es. 0FFh).

Se si vuole specificare un valore binario, le alternative solitamente utilizzate sono:

LD A, @11000000
 LD A, 1011b

Un'attenzione particolare dev'essere prestata ai valori negativi. Essi sono rappresentati con la notazione in complemento a due, ma questo riguarda semplicemente la traduzione effettuata dall'assemblatore. Infatti all'interno del microprocessore qualunque valore viene trattato come un numero binario, indipendentemente dalla sua rappresentazione. Per essere più chiari, le seguenti istruzioni di load caricano in A lo stesso identico valore:

            LD A,-1
            LD A, 255
            LD A, 0FFh

 

Etichette (label)

Le etichette (label) sono nomi mnemonici assegnati a una riga di programma e sono molto utili nelle istruzioni di salto (jump) per realizzare cicli e selezioni. Si consideri per esempio il seguente programma:

            LD D,0FFh
inizio: 
            DEC D
            JP NZ,inizio

In questo caso inizio è il nome dell'etichetta (scelto a piacere). La prima riga del programma è contrassegnata dall'etichetta (seguita obbligatoriamente dai due punti :). L'istruzione

JP NZ,inizio

salta all'etichetta inizio se il risultato dell'ultima operazione compiuta è diverso da zero (NZ, Not Zero). In pratica il programma dell'esempio realizza un ciclo, in cui D viene inizializzato col valore FFh = 15 e quindi viene decrementato finché non arriva a zero.

In pratica l'etichetta consente al programmatore in assembly di risparmiare la fatica di tenere a mente la posizione in memoria di ogni singola istruzione. Sarà l'assemblatore, traducendo l'istruzione JP, a occuparsi di sostituire l'etichetta con l'indirizzo dell'istruzione DEC D.

Direttive dell'assemblatore

Una direttiva non è una istruzione, ma un comando, inserito nel codice sorgente, rivolto all’assemblatore. L’assemblatore utilizzerà tale indicazione per tradurre in modo appropriato il codice sorgente. Nel codice macchina tradotto, perciò, non troveremo nessuna istruzione in corrispondenza delle direttive; per questo, sono chiamate anche pseudo-istruzioni.

Le direttive, ancor più delle istruzioni stesse, variano molto da un assemblatore a un altro. Tuttavia quelle elencate nel seguito sono le più comuni e quelle più frequentemente presenti.

Direttiva EQU

La direttiva EQU consente di assegnare un etichetta a un valore numerico costante. Si consideri l'esempio seguente:

num        EQU 8000h     ; la costante 'num' equivale all'indirizzo 8000h
             LD A,(num)     ; carico in A il contenuto dell'indirizzo "num"

Si noti che num è usato come un'etichetta, ma non è seguito dai due punti. In pratica in questo esempio num è un'identificativo mnemonico che può essere usato come una costante al posto dell'indirizzo 8000h in tutte le istruzioni. Il vantaggio, oltre a rendere più semplice la lettura del programma, è quello di facilitare eventuali modifiche (per esempio se lo stesso valore numerico viene usato più volte nel programma, assegnandogli un nome unico, basta modificare solo la direttiva EQU per modificare automaticamente tutte le istruzioni che usano tale indirizzo).

Possiamo pensare alla direttiva

num        EQU 8000h     ; la costante 'num' equivale all'indirizzo 8000h
             LD A,(num)     ; carico in A il contenuto dell'indirizzo "num"

come a una sorta di dichiarazione di variabile, in qualche modo simile a quanto avremmo in un linguaggio ad alto livello (es. il C) scrivendo:

int num;

Si tratta tuttavia solo di una comoda astrazione, che ci permette di non dover ricordare a memoria gli indirizzi effettivi in RAM. Tuttavia l'assemblatore tratta il nome num (nel nostro esempio) solo come un alias al posto di un valore numerico. Infatti, come si è già detto, per poter leggere o scrivere qualcosa a tale indirizzo occorre ricordarsi le parentesi tonde (che specificano appunto che si tratta di un indirizzo):

num        EQU 8000h     ; la costante 'num' equivale all'indirizzo 8000h
             LD A,(num)     ; carico in A il contenuto dell'indirizzo "num"

Per comodità di lettura, di solito il programmatore definisce le "variabili" dichiarandone i rispettivi simboli all’inizio del programma. Quando allochiamo una variabile in assembly, oltre che al suo indirizzo, dovremo fare attenzione anche che non si sovrapponga con altre. Ricordiamoci, infatti che in assembly il nome della variabile non costituisce un puro riferimento astratto, ma corrisponde effettivamente al suo indirizzo di memoria. Facciamo attenzione anche a non commettere il superficiale errore di confondere indirizzo e contenuto: è differente pensare al nome della variabile, che identifica dove questa è reperibile (l'indirizzo), ed al suo contenuto, rappresentato dai bit della/delle locazioni di memoria ad essa assegnate. Nel caso di variabili composte da più byte, salvo casi particolari, il nome si riferisce all'indirizzo del primo byte.

Si noti che, a differenza di quanto accade con la maggior parte dei linguaggi di programmazione ad alto livello, in assembly le variabili non hanno un tipo (int, double, etc.), cioè non sono tipizzate. Si tratta infatti di semplici locazioni di memoria. L'interpretazione del loro contenuto (come intero, carattere, etc.) è demandata completamente al programmatore.

 

Direttiva ORG

Ogni istruzione Z80, tradotta in linguaggio macchina, richiede da uno a quattro byte di memoria, e nell'insieme tutto il programma occupa una certa area di memoria. In assembly, il programmatore deve precisare l'indirizzo iniziale del programma stesso: questo si ottiene utilizzando un altro tipo di direttiva, nel nostro caso la ORG (il nome deriva da origine). La direttiva ORG ordina all'assemblatore di tradurre il codice che viene dopo, allocandolo a partire dall'indirizzo precisato. Per esempio:

        ORG O000h      ;assembla a partire dall'indirizzo di RESET
        JP 0100h         ;salto alla prima locazione del programma
;
        ORG 0100h      ; assembla a partire dall'indirizzo 0100h
        INC HL           ; incrementa HL
        DEC B            ; decrementa B

La prima ORG obbliga il traduttore a piazzare l'istruzione JP 0100h all'indirizzo 0000h. Questo è l'indirizzo della prima istruzione che lo Z80 carica dalla memoria dopo un RESET hardware. Con la prima ORG abbiamo quindi collegato il RESET hardware del µP all'esecuzione del nostro programma, che sarà lanciato automaticamente all'attivazione dell'ingresso hardware di RESET.

L'istruzione JP 0100h, quando sarà eseguita dal processore, causerà un "salto" (jump) alla istruzione presente all'indirizzo 0100h. La seconda ORG, invece, ordina all'assemblatore di piazzare le istruzioni che seguono a partire dall'indirizzo 0100h (questo è dunque l'indirizzo dell'istruzione INC HL nel nostro esempio).

Ci si potrebbe chiedere perché non abbiamo allocato il programma direttamente all’indirizzo 0000h, visto che il processore parte da lì, invece che ricorrere ad un salto e farlo cominciare alla 0100h. Il motivo è che le locazioni che seguono la 0000h sono riservate. Ad esempio, le locazioni a partire dalla 0038h sono riservate alla gestione delle interruzioni (che vedremo più avanti); ma già all’indirizzo 0008h troviamo una locazione riservata (alle istruzione di restart). E’ tradizione fare “cifra tonda“ e scavalcare le prime 256 locazioni, saltando all’indirizzo 0100h, che si trova oltre tutte le locazioni riservate.

La direttiva ORG e la direttiva EQU possono essere usate insieme, come nel seguente esempio:

RESET   EQU 000h
START   EQU 010h
;
        ORG RESET       ;assembla a partire dall'indirizzo di RESET
        JP START         ;salto alla prima locazione del programma
;
        ORG START        ; assembla a partire dall'indirizzo 0100h
        INC HL             ; incrementa HL
        DEC B              ; decrementa B

Direttive DS, DB e DW

Il programmatore spesso ha la necessità di predisporre alcune locazioni nella ROM contenenti valori costanti. Queste costanti possono essere dei numeri, dei caratteri ASCII, o delle intere tabelle di numeri e caratteri. La direttiva DS consente di riservare uno spazio in memoria pari al numero di byte indicato:

            ORG 0100h       ; assembla a partire dall'indirizzo 0100h
tabella:  DS 64             ;  spazio di 64 byte riservato a partire dall'indirizzo 0100h

Le direttive DB e DW permettono invece di riservare un byte o una word (due byte) nella memoria ROM e di assegnare loro un valore. DB è l’abbreviazione di “Define Byte”, e definisce una o più costanti della dimensione di un byte ciascuna. DW è l’abbreviazione di “Define Word”, e definisce una o più costanti della dimensione di una word a 16 bit ciascuna, spezzate in memoria in coppie di byte (nella sequenza byte Low, byte High).

Si consideri l'esempio seguente:

             ORG 0100h          ; carica le seguenti a partire dall'indirizzo 0100h
BYTES:    DB 5,7               ; inserisce 2 byte in memoria con valori:
                                     ; 05h (all'indirizzo 0100h) e 07h (in 0101h)
WORDS:   DW 0FF05h,8      ; inserisce 2 costanti a 16 bit:
                                     ; 05h (all'indirizzo 0102h) e FFh (in 0103h)
                                     ; 08h (all'indirizzo 0104h) e 00h (in 0105h)

Si noti che i due byte alto e basso delle due costanti sono invertiti in memoria (vedremo in seguito come questa sia una caratteristica del trattamento delle costanti a 16 bit da parte dello Z80).

Osserviamo che le direttive DB e DW sono generalmente abbinate a etichette (i nomi scelti, BYTES e WORDS, sono del tutto liberi e arbitrari), che consentono al programma di accedere rapidamente alle aree di memoria contenenti le costanti. Si noti che queste direttive funzionano solo con indirizzi di memoria ROM e dunque sono adatte solo per dichiarare costanti predefinite (e non variabili).

 

precedente - successiva

Sito realizzato in base al template offerto da

http://www.graphixmania.it