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.
Iniziamo da qualcosa di veramente semplice. Si consideri il seguente programma in linguaggio C:
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:
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:
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:
L'interpretazione è abbastanza chiara: il valore dell'etichetta _test viene fissato a 0x8000.
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:
Nel programma precedente tutta la parte di codice compresa fra __asm e __endasm; non viene compilata. Il codice assembly risultante dalla compilazione è il seguente:
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:
mentre invece è corretta la seguente notazione (che usa un'etichetta dell'assembly):
La gestione delle porte di I/O dello Z80 avviene attraverso la dichiarazione di pseudovariabili a 8 bit, nel seguente modo;
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:
verrebbe tradotta in assembly nel seguente modo:
precedente - successiva
Sito realizzato in base al
template offerto da
http://www.graphixmania.it