Scopo di questa esercitazione è il progetto e la realizzazione
completa di un'architettura di processore attraverso strumenti di emulazione
circuitale. L'architettura in questione dovrà supportare un insieme
minimale di istruzioni assembler, utilizzando le quali dovrà essere
implementato un algoritmo di ordinamento dei valori interi di un vettore dati
in memoria.
Tale programma di ordinamento (algoritmo BubbleSort) dovrà essere ideato
e memorizzato dai componenti del gruppo, e risulterà essere il test
funzionale principale dell'architettura realizzata.
Il tool utilizzato per l'emulazione circuitale è RETRO, per il quale
trovate tutte le informazioni (originali) su
http://www.ee.uwa.edu.au/~braunl/retro/
e per il quale potete scaricare la versione completa (e patch di correzione per la funzione SAVE) alla URL
http://www.cs.unibo.it/~bononi/ARCH2001/
Retro è stato realizzato da B. "Toy" Chansavat e Thomas Braunl, ed è distribuito sotto GNU public license.
La progettazione dell'architettura del processore (di seguito
CPU) deve essere mirata all'implementazione di un insieme di istruzioni del
linguaggio assembler previsto per la CPU stessa. L'insieme di tali istruzioni
è ovviamente ridotto e non significativo in termini di utilizzo pratico,
in quanto la finalità del progetto è unicamente didattica, e
in quanto esistono limiti pratici alla complessità di realizzazione
dell'architettura finale.
Per la realizzazione dell'architettura della CPU ci si avvale della libreria
di componenti messi a disposizione da RETRO.
Tra questi componenti architetturali, esistono componenti di basso livello
(es. porte logiche, collegamenti, bus) ed esistono componenti più complessi
(es. multiplexer, decoder, flip-flop, registri, ROM, RAM, sommatori...) che
possono essere utilizzati senza limiti (se non quelli fisici legati alla dimensione
della "basetta" virtuale di RETRO).
Il requisito fondamentale riguardo all'esigenza di avere una progettazione
completa dell'architettura richiede che ogni componente complesso utilizzato
almeno una volta nell'architettura realizzata debba essere progettato e realizzato
in emulazione, usando una "basetta" separata di RETRO. Di fatto,
quindi, ogni componente registro, multiplexer, sommatore, ecc. deve essere
progettato e realizzato (in forma prototipale) almeno una volta a partire
dalla sintesi circuitale basata sulle sole porte logiche AND, OR, NOT. A tale
scopo, dovranno essere descritte le fasi di progetto e sintesi, mostrando
i dettagli dei metodi di sintesi utilizzati (mappe di Karnaugh, metodo Quine-MkCluskey,
ecc.). La dimostrazione di funzionamento dei singoli componenti complessi
dovrà essere possibile, prevista e supportata dall'implementazione
attraverso RETRO (ad esempio predisponendo opportunamente led e display per
semplici funzioni di test).
Nella realizzazione finale della CPU non si utilizzeranno di fatto i componenti
progettati (in quanto non importabili come macro-oggetti), ma si utilizzeranno
i componenti della libreria standard di RETRO.
Gli unici componenti complessi che non devono essere realizzati malgrado risultino
utilizzati sono: clock, generatori di impulsi, RAM, ROM, e tutti i dispositivi
di visualizzazione (display, led, ecc).
Tutta la fase di progetto e sintesi dei costituenti della CPU deve essere opportunamente documentata in una relazione finale alla quale saranno allegati i file di RETRO per il test di funzionamento. La relazione dovrà essere consegnata in formato elettronico (.pdf, .ps o .html) entro le date che saranno definite in seguito.
L'architettura della CPU è a 8 bit e deve necessariamente prevedere i seguenti elementi costituenti l'architettura stessa:
ALU: unità aritmetico-logica per supportare l'insieme delle operazioni sui dati richieste dall'architettura
ACC: registro accumulatore a 8 bit, utilizzato per memorizzare i dati/risultati della ALU o i dati da/verso la memoria
CodeRegister: registro per il mantenimento e gestione del codice operativo dell'istruzione in esecuzione
AddressRegister: registro di indirizzamento a 8 bit, utilizzato per mantenere l'indirizzo di memoria dell'eventuale operando di un'istruzione assembler della CPU.
PC: (program counter) registro a 8 bit che punta alla prossima istruzione assembler da eseguire durante l'esecuzione di un programma memorizzato in RAM.
RAM: memoria costituita da 256 locazioni da un byte, indirizzate da 00hex a FFhex, sulle quali potranno essere memorizzati programmi e dati
Clock di sistema: segnale che governa l'evoluzione dell'architettura.
Generatore di Impulsi (pattern generator): meccanismo di astrazione del concetto di microprogramma, che determina la temporizzazione delle fasi di esecuzione delle microistruzioni, e delle componenti architetturali coinvolte, secondo il classico schema di un'architettura CISC. Su questo oggetto saranno definite in seguito le assunzioni che permettono di semplificare l'architettura stessa.
Rete di controllo: agisce sulla base dei segnali ottenuti dal generatore di impulsi, e attraverso la codifica del codice operativo della istruzione in esecuzione, per determinare la effettiva sequenza dei segnali di controllo che governano l'evoluzione della elaborazione della CPU (dipendente dal tipo di istruzione in esecuzione).
Per maggiore chiarezza, verranno forniti esempi di architetture simili che possano risultare di ausilio nella comprensione e progettazione dell'architettura richiesta (es. scaricate da qui cpu2.toy e CPU2.mem, poi analizzate cpu2.toy con RETRO).
Si definisce ora l'insieme delle istruzioni assembler che devono
essere supportate dall'architettura realizzata.
Tutte le operazioni aritmetiche si intendono realizzate su valori binari interi
a 8 bit, espressi in complemento a due.
Fanno eccezione i valori degli indirizzi di RAM che si assumono espressi su
8 bit in binario naturale.
Elenco delle istruzioni assembler da implementare per la CPU:
ACC := ACC ; questa istruzione impegna l'architettura senza causare nessuna modifica allo stato del sistema
ACC := mem(Address) ; il registro ACC viene caricato con il valore (Byte) contenuto alla locazione di RAM di indirizzo Address. Si noti che il tipo di indirizzamento è immediato, e non sono possibili indirizzamenti indiretti.
ACC := ACC - mem(Address) ; il registro ACC viene ad assumere il valore ottenuto dal valore attuale al quale si sottrae il valore contenuto in RAM all'indirizzo Address (indirizzo immediato). Non devono essere gestiti i casi di overflow (negativo).
ACC := ACC + mem(Address) ; il registro ACC viene ad assumere il valore ottenuto dal valore attuale al quale si somma il valore contenuto in RAM all'indirizzo Address (indirizzo immediato). Non devono essere gestiti i casi di overflow (positivo).
ACC := ODD ; questa istruzione causa l'incremento di un'unità del valore in ACC nel caso il valore iniziale fosse pari
ACC := EVEN; questa istruzione causa il decremento di un'unità del valore in ACC nel caso il valore iniziale fosse dispari
mem(Address) := ACC ; questa operazione scrive in RAM alla locazione Address (indirizzo immediato) il valore contenuto nel registro ACC.
if (ACC >= 0) then PC :=PC + Offset ; questa istruzione causa l'aggiornamento condizionale (se ACC >= 0) del program counter (PC), eventualmente sommando il valore immediato Offset (8 bit in complemento a due).
Il ciclo generico di esecuzione di un'istruzione assembler per l'architettura in esame risulta genericamente definito attraverso le fasi di:
fetch del codice operativo dell'istruzione da eseguire
aggiornamento del PC che punta all'istruzione successiva
caricamento dell'eventuale indirizzo dell'operando dell'istruzione sull'Address Register
fetch dell'eventuale operando dell'istruzione
esecuzione dell'operazione da parte della ALU
store del risultato sul registro ACC
Si noti come la sequenza di queste fasi possa essere considerata uno (statico)
schema del microprogramma per ognuna delle istruzioni richieste. Questa assunzione
semplifica la fase di progetto e realizzazione, in quanto permette di definire
un pattern statico di temporizzazione dell'architettura nelle sue micro-costituenti.
Ognuna delle fasi descritte deve essere realizzata attraverso la definizione
di un insieme statico di pattern di temporizzazione, utilizzando il pattern
generator, i cui segnali possono essere dinamicamente mascherati attraverso
la rete di controllo (che risulta molto semplice) basata sull'interpretazione
del codice operativo della istruzione in esecuzione. Si rimanda all'analisi
della rete di esempio (cpu2.toy) per maggiori dettagli.
Per quello che riguarda il progetto di alto livello dell'architettura, la relazione dovrà documentare le scelte, i criteri, e gli accorgimenti usati per ottenere un'architettura che soddisfi al meglio i requisiti minimi definiti. In particolare si ponga attenzione alle diverse modalità di implementazione delle istruzioni richieste, selezionando quelle che si ritengono migliori o meno complesse.
In questa fase del progetto, i costituenti del gruppo dovranno
scrivere il codice assembler di un programma per l'ordinamento di un vettore
di 5 elementi memorizzato in RAM, utilizzando unicamente le istruzioni implementate
per l'architettura realizzata.
N.B. data l'impossibilità di avere indirizzamento non immediato, e volendo
evitare l'esecuzione di programmi auto-modificanti (cioè dati e codice
vanno separati in RAM), si richiede di scrivere un programma rilocato staticamente
che effettui l'ordinamento Bubble Slort del vettore di 5 elementi, secondo lo
schema di algoritmo che segue (i dettagli sono lasciati alla fantasia dei costituenti
dei gruppi, e lo schema presentato non è in alcun modo vincolante).
Si assuma di avere i 5 elementi (espressi su 8 bit in complemento a due e positivi)
memorizzati su 5 locazioni (consecutive per esigenze pratiche di visualizzazione).
1) inizializzazione flag_di_swap=falso
2) confronto coppia elementi 1 e 2 ed eventuale swap (con eventuale attivazione
del flag_di_swap)
3) confronto coppia elementi 2 e 3 ed eventuale swap (con eventuale attivazione
del flag_di_swap)
4) confronto coppia elementi 3 e 4 ed eventuale swap (con eventuale attivazione
del flag_di_swap)
5) confronto coppia elementi 4 e 5 ed eventuale swap (con eventuale attivazione
del flag_di_swap)
6) verifica: se è avvenuto almeno uno swap ai passi 2..5 allora ripeti
da 1)
7) Halt dinamico (ovvero ciclo di esecuzione infinita)
Una volta realizzato l'algoritmo, questo dovrà essere "assemblato"
dagli autori ottenendo il linguaggio macchina equivalente (a questo livello
entrano in gioco le scelte soggettive di definizione delle istruzioni, ecc.).
Il linguaggio macchina ottenuto dovrà essere memorizzato sulla parte
iniziale della RAM (assumendo una rilocazione statica), e quindi eseguito. I
valori del vettore da ordinare, ed eventuali dati temporanei, dovranno essere
allocati staticamente nella porzione finale della RAM (verso l'indirizzo FF).
Per eventuali domande, errori riscontrati o segnalazioni in genere si prega di fare uso dello spazio discussione unibo.cs.arch sul quale mi impegno a rispondere (quasi) giornalmente.