ELEMANIA
Z80 - SDCC Cross Compiler 
SDCC (Small Device C Compiler)

SDCC (Small Device C Compiler) è un cross-compilatore in linguaggio C per i microprocessori a 8 bit. Esso consente di generare file in linguaggio assembly a partire da file sorgenti in linguaggio C.

E' possibile scaricare i file di installazione e i sorgenti per tutti i principali sistemi operativi dalla pagina ufficiale del progetto. Sul sito è anche disponibile tutta la documentazione per l'utilizzo del programma.

Una volta installato SDCC per eseguirlo in ambiente Windows basta aprire un prompt dei comandi in una cartella e digitare dal prompt il comando:

sdcc -mz80 nomefile.c

dove l'opzione --mz80 serve per specificare che si vuole produrre codice assembly per il µP Z80 e nomefile.c è il nome di un file sorgente contenente un programma in linguaggio C (nella medesima cartella).

Al termine dell'esecuzione del programma il compilatore produce diversi file, fra i quali uno di nome nomefile.asm contenente la traduzione in assembly del file sorgente in C.

Siccome SDCC utilizza diversi algoritmi di ottimizzazione per rendere più efficiente il codice, può essere utile in alcuni casi disabilitare le ottimizzazioni, in modo da rendere più semplice la lettura del codice prodotto.

Per ulteriori dettagli sull'uso di SDCC rimandiamo al manuale in lingua inglese.

Un semplice esempio

Iniziamo da qualcosa di veramente semplice. Si consideri il seguente programma in linguaggio C:

unsigned char test;

void main(void)
{
test=0;
}

La variabile test è di tipo unsigned char, cioè occupa solo un byte (8 bit) senza segno in memoria. Abbiamo scelto il tipo unsigned char perché è il più facile da gestire per lo Z80, in quanto il bus dati del µP è a 8 bit. Usando variabili int o float (che sono comunque utilizzabili con SDCC) il codice assembly generato sarebbe più complicato da comprendere.

Dopo aver scritto e salvato il precedente programma in un file con estensione .c (es. prova.c) possiamo compilarlo con

sdcc -mz80 prova.c

Il file prova.asm generato da SDCC è piuttosto lungo, ma il codice assembly vero e proprio si trova alla fine:

_main:

;prova.c:3: test=0;

    ld hl,#_test + 0
    ld (hl), #0x00
    ret

La figura seguente mostra il significato delle diverse istruzioni:

Osserviamo anzitutto l'etichetta _main: che segnala l'inizio del programma (il simbolo di underscore _ iniziale serve per evitare possibili sovrapposizioni fra i nomi delle etichette generate automaticamente dall'assemblatore e quelle scelte a piacere dal programmatore). Notiamo anche che il main viene tradotto per mezzodi un sottoprogramma (si osservi l'istruzione ret alla fine) che dovrà essere chiamato per mezzo di un'istruzione:

call _main

Per comprendere il codice precedente occorre tenere presente il fatto che il file .asm deve ancora essere elaborato dal linker per produrre un modulo eseguibile sul  µP 80. In altre parole, il codice contenuto nel file .asm è ancora incompleto, in quanto gli indirizzi in memoria non sono specificati, ma vengono invece indicati con etichette mnemoniche. Per esempio nel nostro caso #_test+0 dovrà essere sostituito dall'indirizzo in RAM dove si troverà la variabile test.

Gli indirizzi delle variabili sono scelti automaticamente dal linker, anche se è possibile specificare sulla linea di comando di SDCC in quale area di memoria dovranno essere memorizzati i dati. In alternativa è possibile specificare dove si vuole memorizzare una certa variabile all'interno del sorgente in C nel seguente modo:

unsigned char __at 0x8000 test;

void main(void)
{
test=0;
}

dove 0x8000 rappresenta l'indirizzo esadecimale scelto in cui memorizzare la variabile. Come è possibile osservare il codice assembly prodotto non cambia, salvo il fatto che nella prima parte del file prova.asm troviamo la seguente dichiarazione:

;--------------------------------------------------------
; ram data
;--------------------------------------------------------
.area _DATA
_test = 0x8000
;--------------------------------------------------------

L'interpretazione è abbastanza chiara: il valore dell'etichetta _test viene fissato a 0x8000.

Inserire codice assembly all'interno di un sorgente C

In molti casi può risultare conveniente inserire codice assembly all'interno di un sorgente C. Tale codice non viene compilato, ma viene semplicemente inserito tale e quale all'interno del file asm. Consideriamo l'esempio seguente:

unsigned char __at 0x8000 test;
void azzera(void);

void main(void) {
azzera();
}

void azzera(void)
{
__asm
    ld a, #0x00
    ld (#0x8000),a
__endasm;
}

Nel programma precedente tutta la parte di codice compresa fra __asm e __endasm; non viene compilata. Il codice assembly risultante dalla compilazione è il seguente:

;prova.c:4: void main(void) {
; ---------------------------------
; Function main
; ---------------------------------
_main_start::
_main:
;prova.c:5: azzera();
    jp _azzera
_main_end::

;prova.c:8: void azzera(void)
; ---------------------------------
; Function azzera
; ---------------------------------
_azzera_start::
_azzera:
;prova.c:13: __endasm;
    ld a, #0x00
    ld (#0x8000),a
    ret
_azzera_end::

Osserviamo che la chiamata alla subroutine viene tradotta "erroneamente" con

jp _azzera

invece di

call _azzera

Questa traduzione è opera dell'ottimizzatore, cioè il modulo di SDCC che si occupa di massimizzare l'efficienza del codice tradotto (risistemando, all'occorrenza, anche le istruzioni e la loro sequenza). Ci sono varie opzioni di SDCC che consentono di disabilitare i diversi componenti dell'ottimizzatore. Per esempio usando --no-peep nel seguente modo

sdcc -mz80 --no-peep prova.c

la chiamata alla subroutine viene tradotta con CALL invece che con JP.

Osserviamo che, quando si scrive codice misto in C e in assembly, all'interno dell'assembly non si possono usare i nomi e le etichette del C e viceversa. In altre parole sarebbe stato sbagliato scrivere:

void azzera(void)
{
__asm
    ld a, #0x00
    ld (test),a
__endasm;
}

mentre invece è corretta la seguente notazione (che usa un'etichetta dell'assembly):

void azzera(void)
{
__asm
    ld a, #0x00
    ld (#_test),a
__endasm;
}
Porte di input e output

La gestione delle porte di I/O dello Z80 avviene attraverso la dichiarazione di pseudovariabili a 8 bit, nel seguente modo;

__sfr __at 0x01 Porta1;

dove 0x01 è l'indirizzo della porta e Porta1 è il nome scelto arbitrariamente per la pseudovariabile.

Dopo la dichiarazione la pseudovariabile può essere usata all'interno del programma in C come una normale variabile a 8 bit, cioè scrivendo in essa dei valori (se la porta corrispondente è di uscita) oppure leggendo i valori contenuti in essa (nel caso di porte di ingresso).

Per esempio supponendo che la porta all'indirizzo 0x01 sia una porta di uscita, l'esecuzione della seguente assegnazione in C:

Porta1 = 5;

verrebbe tradotta in assembly nel seguente modo:

      ld a, #0x05
      out (#0x01),a

 

precedente - successiva

Sito realizzato in base al template offerto da

http://www.graphixmania.it