E N D
Partendo dall’analisi del testo del problema da risolvere, è opportuno per prima cosa che il programmatore individui tutte le possibili situazioni che il programma potrà incontrare, soprattutto in riferimento al formato massimo e minimo di risultati di eventuali operazioni aritmetiche o logiche; ciò per individuare che tipi di registri scegliere e quanti di essi dovranno essere utilizzati. Sistema a microprocessore Z80 Analizzate le eventuali situazioni critiche, bisogna individuare quanti e quali registri sono essenziali e che funzione svolgerà ognuno di essi nel contesto della soluzione adottata, in particolare calcolando più precisamente possibile quanti contatori e quanti puntatori occorrono, ove necessari. Un contatore è un registro (o una cella di memoria) che deve contenere il numero (in codice esadecimale) di volte che si verifica un evento o si esegue un ciclo (cioè la ripetizione di un blocco di istruzioni) durante l’esecuzione del programma; può essere a 8 o a 16 bit a secondo del numero massimo che potrebbe contenere; deve contenere un valore iniziale, caricato con apposita istruzione di caricamento, prima di svolgere la sua funzione nel programma. Un puntatore è un registro a 16 bit che deve contenere l’indirizzo della cella di memoria con la quale la CPU chiede di interagire in lettura o scrittura in un determinato momento dell’esecuzione del programma; deve contenere un valore iniziale, caricato con apposita istruzione di caricamento, prima di svolgere la sua funzione nel programma. Istruzioni per la programmazione A questo punto è opportuno realizzare un diagramma di flusso (flow-chart) che faccia da riferimento di massima per la stesura del programma in linguaggio mnemonico. Questa operazione, che può apparire superflua per i primi semplici programmi di esercitazione, si rivelerà un’indispensabile strumento per semplificare problemi più complessi dividendoli in piccoli blocchi in sequenza; pertanto acquisendo dall’inizio l’abitudine alla loro rappresentazione grafica, si avrà una fotografia chiara dell’impostazione della soluzione e una più facile e veloce individuazione di eventuali errori concettuali. Diagramma di flusso e programma (scritto in linguaggio mnemonico), possono essere idealmente divisi in 3 blocchi: inizializzazione, elaborazione, controllo; in programmi più complessi può essere previsto un quarto blocco di visualizzazione e/o un’ulteriore suddivisione di uno o più blocchi.
Di seguito si passerà alla scrittura del programma secondo le regole sintattiche, spazi compresi, indicate nel set d’istruzioni; il set dello Z80 è suddiviso, nella sua versione originale (SGS – ATES) in 11 gruppi di istruzioni, precedute da una tabella che riassume lo stato dei flag per gruppi di istruzioni e la legenda dei simboli generici usati: • caricamento a 8 bit • aritmetiche e logiche a 8 bit • salto • caricamento a 16 bit • aritmetiche a 16 bit • manipolazione di singoli bit • varie e di controllo della CPU • di scambio e multiple • chiamata e ritorno • ingresso e uscita • rotazione e scorrimento Ogni gruppo di istruzioni è intestato, da sinistra a destra, come segue: codice mnemonico | spiegazione simbolica | flag | codice operativo | numero cicli T | note mnemonic | symbolic operation | flags| op. code | n° of T cycles | comments Versioni derivate dall’originale, aggiungono le colonne indicanti il codice operativo in sistema numerico esadecimale in aggiunta al binario, il numero di cicli M (macchina) e il numero di byte dell’istruzione, nonché la versione in italiano di note, legende e intestazioni.
Il codice mnemonico di ogni istruzione presenta la parte operativa e gli eventuali operandi, alcuni dei quali appaiono in minuscolo; tutti i caratteri in minuscolo devono essere sostituiti, nella stesura del programma, dai caratteri maiuscoli corrispondenti, scelti tra quelli previsti nelle apposite tabelle riportate nella colonna delle note. Alcune istruzioni hanno un operando sottinteso, ricavabile dalla spiegazione simbolica. Le 6 colonne dei flag specificano come essi vengono alterati o meno in seguito all’esecuzione dell’istruzione corrispondente. Il codice operativo è la traduzione in codice binario del codice mnemonico (in pratica è visualizzato il risultato dell’assemblaggio); le indicazioni in minuscolo vanno sostituite dai bit corrispondenti agli operandi scelti, ricavabili dalle tabelle relative presenti nella colonna delle note. Il numero dei cicli T fornisce la durata di ogni singola istruzione, calcolabile moltiplicando tale numero per il periodo relativo alla frequenza di lavoro del microprocessore (per lo Z80 f = 1/T = 2,5 MHz ÷ 4 MHz). Ogni programma o sottoprogramma inizia con la pseudoistruzione ORG, seguita da un operando costituito dall’indirizzo di memoria a partire dal quale il programma (o sottoprogramma) dovrà essere caricato in fase di assemblaggio; questo indirizzo (come ogni dato) deve essere espresso in esadecimale e sarà l’indirizzo del primo byte della prima istruzione scritta dopo ORG (esempio: ORG 0000H). Al termine del programma e di tutti i sottoprogrammi scritti, deve comparire la pseudoistruzione END, che indica al programma di assemblaggio che l’ultima istruzione su cui operare è la precedente. 1. Un’istruzione di caricamento serve a memorizzare una sorgente in una destinazione; la sorgente può essere un numero, il contenuto di un registro o di una cella di memoria; la destinazione può essere un registro o una cella di memoria; il caricamento da un registro o da una cella di memoria va interpretato come “copiatura” della sorgente nella destinazione, a seguito della quale il precedente contenuto della destinazione viene cancellato dal nuovo, mentre la sorgente mantiene il dato copiato. La sorgente e la destinazione del caricamento devono avere lo stesso numero di bit. La presenza delle parentesi in un’istruzione di caricamento indica che l’operando è il contenuto della cella di memoria il cui indirizzo si trova nel registro (puntatore) tra parentesi, se non è specificato direttamente l’indirizzo. 2. Le istruzioni aritmetiche e logiche a 8 bit hanno come primo operando SEMPRE il registro A (accumulatore) nel quale viene automaticamente caricato il risultato dell’operazione (come si vede dalla spiegazione simbolica); fanno eccezione le istruzioni di incremento e decremento che possono prescindere dall’accumulatore. Nelle spiegazioni simboliche CY sta per carry, cioè riporto.
3. Un salto cancella l’indirizzo presente nel registro PC (program counter), per far sì che la successiva istruzione da eseguire non sia quella memorizzata nella cella (o celle) successiva, ma nella cella relativa all’indirizzo che lo sostituisce (destinazione del salto). Tale indirizzo è l’unico operando dell’istruzione di salto (JP, acronimo di jump = salto) se è “non condizionato”, mentre è il secondo operando se è “condizionato”; in quest’ultimo caso il primo operando è la condizione che, se verificata (vera), consente di effettuare il salto, altrimenti (falsa) il PC mantiene l’indirizzo che aveva e il salto non avviene; per facilitare il programmatore che non può calcolare preventivamente l’indirizzo di destinazione del salto, questo viene sostituito da un’etichetta (label), cioè una parola lunga da 1 a 6 caratteri, che deve iniziare con una lettera, e ripetuta nella colonna delle label, seguita dai due punti (:), all’altezza dell’istruzione alla quale il programma deve saltare; la label scritta nel campo operando, in fase di assemblaggio sarà automaticamente trasformata nell’indirizzo di destinazione, mentre sparirà dalla colonna delle label; ogni distinta label può comparire una sola volta nella colonna delle label, ma più volte nel campo operando dei vari salti presenti nel programma. 4. Il caricamento a 16 bit che riguarda le istruzioni di PUSH e POP, implicano il preventivo caricamento del registro SP (stack pointer) per definire l’indirizzo iniziale dell’area di stack, dove saranno memorizzati temporaneamente e poi richiamati i contenuti dei registri operandi. 5. Le istruzioni aritmetiche a 16 bit hanno come primo operando obbligato ad eccezione di quelle di incremento e decremento. 6. Le istruzioni di manipolazione dei bit possono testare lo stato di un singolo bit, settarlo (porlo a 1) o resettarlo (porlo a 0); il primo operando è il numero della posizione del bit (dalla più significativa 7, alla meno significativa 0), il secondo operando è il relativo registro o contenuto di una cella “puntata” dal registro indicato tra parentesi. Il test del bit determina la condizione di zero (Z) o non zero (NZ) nei salti condizionati. 7. Le distinte istruzioni varie e di controllo sono spiegate nella 2^ colonna del set con l’aiuto delle note. 8. Le istruzioni di scambio che coinvolgono il registro SP implicano il preventivo caricamento dello stesso per definire l’indirizzo iniziale dell’area di stack dalla quale saranno prelevati i dati da caricare nei registri operandi; le istruzioni multiple necessitano la precedente inizializzazione dei registri coinvolti. 9. Le istruzioni di chiamata (a sottoprogramma) e ritorno implicano il preventivo caricamento del registro SP per definire l’indirizzo iniziale dell’area di stack dove saranno memorizzati temporaneamente e poi richiamati gli indirizzi di ritorno dai sottoprogrammi o dalle interruzioni.
10. Le istruzioni di ingresso e uscita sono dedicate alla tecnica d’interfacciamento I/O isolato, nella quale gli 8 bit presenti nella parte bassa del bus indirizzi costituiscono l’indirizzo del porto d’ingresso o d’uscita specificato al posto dell’operando n (tra parentesi) o preventivamente caricato nel registro C (tra parentesi); per la tecnica Memory Mapped sono utilizzate le istruzioni di caricamento che coinvolgono la memoria, le cui celle sono viste come vere e proprie periferiche. 11. Il significato delle istruzioni di rotazione e scorrimento si ricava facilmente dalle relative spiegazioni simboliche e sono spesso usate per raddoppiare o dimezzare i valori oppure pilotare periferiche d’uscita, come ad esempio luci scorrevoli intermittenti. 12. Terminata la scrittura, il file di editing viene salvato per essere elaborato nelle successive fasi di assemblaggio ed esecuzione. 13. Durante la scrittura o dopo aver collaudato il funzionamento del programma, spesso è opportuno ottimizzarlo, eliminando, aggiungendo o modificando istruzioni allo scopo di velocizzare il programma, evitandone possibili blocchi (loop), risparmiare registri o memoria. 14. A volte è necessario creare artificialmente dei loop di ritardo da inserire all’interno del programma (meglio se richiamati come sottoprogrammi) per consentire al programma stesso di far passare un tempo relativamente lungo, per visualizzare un risultato in uscita, come ad esempio la luce di un semaforo. Un classico metodo per ottenerlo è quello ad anelli nidificati: ......... LD A,05H L1: LD B,02H L2: LD C,04H L3: DEC C JP NZ,L3 DEC B JP NZ,L2 DEC A JP NZ,L1 ......... La durata si calcola con la formula: {7 + [21 + (21 + 14 x n3) x n2] x n1} x T dove n1 = 05H = 5 , n2 = 02H = 2 , n3 = 04H = 4 , 7 è il numero di cicli T della prima istruzione, 14 è la somma dei cicli T delle istruzioni DEC C e JP NZ,L3 che saranno eseguite n1 x n2 x n3 volte, 21 è la somma dei cicli T delle istruzioni LD C,04H , DEC B e JP NZ,L2 che saranno eseguite n2 x n1 volte e delle istruzioni LD B,02H , DEC A e JP NZ,L1 che saranno eseguite n1 volte.