Il numero dei registri interni general purpose dello Z80 è piuttosto limitato e il programmatore si trova spesso nella necessità di liberare un registro per poterlo riusare per altri scopi, senza però perderne il contenuto.
Per fare ciò possiamo usare le istruzioni di LD che consentono di salvare e ripristinare l'accumulatore in memoria RAM. Per esempio:
In modo simile è possibile salvare e successivamente ripristinare gli altri registri a coppie, nel seguente modo:
Si noti che il valore di A occupa una sola locazione di memoria (indirizzo 8000h), mentre le successive coppie di registri ne occupano 2. Solo per il registro accumulatore A è disponibile una istruzione LD in memoria separata; tutti gli altri registri devono essere caricati a coppie.
Un modo per rendere un po' più amichevole il codice precedenti è usare la direttiva EQU che permette di associare gli indirizzi numerici con nomi mnemonici (creando una specie di pseudo variabile in assembly) nel modo seguente:
vA EQU 8000h vBC EQU 8001h vDE EQU 8003h vHL EQU 8005h LD (vA),A ; salva A LD (vBC), BC ; salva BC LD (vDE), DE ; salva DE LD (vHL), HL ; salva HL
.... ; resto del programma LD A,(vA) ; ripristina A LD BC, (vBC) ; ripristina BC LD DE, (vDE) ; ripristina DE LD HL, (vHL) ; ripristina HL
In molti casi sarebbe più comodo non doversi occupare esplicitamente di gestire la memoria RAM e di conoscere gli indirizzi esatti dove vengono salvati i dati (registri). Questo è possibile se il µP dispone di un meccanismo automatico che faccia le nostre veci nel decidere gli opportuni indirizzi di memoria, mentre noi ci occupiamo soltanto dei dati da trasferire.
Lo stack (tradotto talvolta in italiano con pila o catasta) è appunto un'area di memoria privilegiata, dove il microprocessore può salvare momentaneamente e poi recuperare dati di varia natura. Precisiamo subito che quest'area è speciale solo per l'uso che ne facciamo: dal punto di vista hardware non ha niente di strano, anzi, si tratta semplicemente di un'area della normale memoria RAM
All'interno dello Z80 è presente un registro speciale, detto stack pointer (SP, puntatore allo stack), il cui scopo è appunto quello di gestire lo stack. Si presti attenzione a non confondere i termini: lo stack pointer (e la sua logica di gestione) si trovano all'interno del µP, mentre lo stack vero e proprio risiede nella RAM collegata al microprocessore.
La prima operazione che è necessario compiere per poter utilizzare lo stack è inizializzare lo stack pointer con l'indirizzo base dello stack. In pratica il programmatore decide in quale area della RAM desidera memorizzare lo stack e le sue dimensioni e inizializza lo SP con un'istruzione del tipo:
Questa istruzione viene generalmente eseguita all'inizio di ogni programma in assembly. La figura seguente mostra il significato di stack e di stack pointer:
Si osservi che lo SP all'inizio punta al fondo dell'area di stack, cioè all'indirizzo più alto nello stack. Le operazioni push e pop eseguite sullo stack fanno crescere l'area di stack "all'indietro" (backwards), cioè, all'aumentare dei dati salvati sullo stack, lo stack pointer viene progressivamente decrementato.
Per queste ragioni di solito l'area riservata allo stack occupa gli indirizzi più alti in RAM, a partire dall'ultimo indirizzo (in modo da riservare il maggior spazio possibile alla crescita dello stack). Anzi, dato che, come vedremo meglio fra poco, la prima locazione dello stack non viene utilizzata, spesso lo stack pointer viene inizializzato col primo indirizzo in memoria 0000h, in modo tale che, al primo decremento, esso punti all'ultima locazione libera (FFFFh).
Come suggerito dal nome stesso, lo stack viene gestito dal µP come una pila o catasta di oggetti (esempio una pila di fogli posati su una scrivania). Sull'area di memoria stack è possibile effettuare solo due operazioni e precisamente:
Le operazione di push e pop gestiscono lo stack secondo un criterio detto First In First Out (FIFO), ovvero: il primo dato a uscire è l'ultimo entrato (quello posizionato in cima allo stack).
Le operazioni push e pop corrispondono a due istruzioni omonime dello Z80:
Come si può osservare tutte le istruzioni precedenti operano su dati a 16 bit che vengono trasferiti da/verso lo stack suddivisi in due byte, alto e basso, occupando pertanto due locazioni di memoria consecutive. Consideriamo l'esempio che segue:
Al termine dell'esecuzione di questa sequenza di istruzioni i due registri BC e HL si sono scambiati il contenuto: BC contiene 0302h e HL contiene 0001h.
La figura seguente mostra la situazione dello stack e dei registri interni dopo l'esecuzione delle istruzioni PUSH BC e PUSH HL:
Osserviamo in particolare che:
Vediamo ora l'effetto dell'esecuzione delle due istruzioni POP BC e POP HL:
Si osservi che alla fine lo SP contiene lo stesso valore che conteneva inizialmente (FFF0). Tuttavia è bene sapere che lo Z80 non effettua alcun controllo di correttezza sulle operazioni compiute sullo stack e che pertanto è compito del programmatore accertarsi del fatto che il numero di POP sia uguale al numero di PUSH precedentemente eseguite.
Osserviamo inoltre che i valori non vengono fisicamente cancellati dalla memoria RAM dopo essere stati prelevati con un'istruzione POP. Tuttavia essi, trovandosi al di sotto della locazione puntata da SP, risultano in pratica inaccessibili con ulteriori operazioni di POP sullo stack.
Al termine della nostra discussione sul funzionamento dello stack, vediamo ora un primo semplice esempio di come l'area di stack potrebbe essere utilizzata per salvare temporaneamente il contenuto di tutti i registri interni dello Z80 e per ripristinarli in seguito. Si consideri il seguente spezzone di programma:
Evidentemente il programmatore aveva bisogno di usare tutti i registri interni alla CPU, in un qualche compito "locale", ma senza perderne il contenuto. Dopo l'esecuzione di tutte le POP, la situazione precedente viene completamente ripristinata. Osserviamo che:
a) il programmatore non ha dovuto indirizzare esplicitamente le locazioni di memoria usate;
b) un certo numero di PUSH deve essere sempre seguito da un numero identico di POP (altrimenti lo
stack di riempie senza svuotarsi o viceversa);
c) la sequenza di recupero deve essere rovesciata rispetto a quella di salvataggio.
La coppia PUSH/POP è ideale quando è necessario usare un registro senza rischiare di perderne il contenuto; può succedere infatti di avere tutti i registri caricati con valori importanti e, non avendone altri (dato il loro limitato numero...) di non sapere come fare. In questi casi è possibile salvare i registri sullo stack (con l'istruzione PUSH), riusarli liberamente e quindi ripristinare i valori iniziali (con la POP).
Un altra applicazione frequente delle istruzioni PUSH e POP è lo scambio di valori fra i registri, come abbiamo già visto prima.
In realtà il salvataggio e il ripristino dei registri interni dello Z80 può essere effettuato in modo più rapida ed efficiente usando le istruzioni EX e EXX. La seconda scambia il contenuto delle copie di registri BC, DE e HL con le corrispondenti copie "ombra" (shadow registers) BC', DE' e HL'. Restano esclusi da questo salvataggio l'accumulatore A e il registro dei flag F, per salvare i quali occorre usare l'istruzione EX. Ecco un esempio:
Sito realizzato in base al
template offerto da
http://www.graphixmania.it