
Realizzazione hardware/VHDL
L’edge detector CPU (Central Processing Unit) è un processore la cui funzione è quella di estrarre i contorni di un’immagine presente in una memoria esterna al dispositivo e memorizzare la nuova immagine rappresentante i contorni nella memoria stessa.

figura 1 – Segnali di ingresso e di uscita della CPU e relativo collegamento con la RAM esterna
Come si può vedere dalla figura 1, la CPU possiede un segnale di reset, un segnale di interrupt mediante il quale viene avviata la procedura di estrazione dei contorni e un segnale di clock per la sincronizzazione delle azioni. Le immagini che il dispositivo può trattare hanno una dimensione di 512x512 pixel, con una codifica di colore di 8 bit per pixel (scala di grigio). La memoria esterna è organizzata per righe e quindi ciascuna riga rappresenta un pixel (8 bit). L’immagine sorgente e quella di destinazione sono memorizzate consecutivamente e, poiché ogni immagine richiede 512*512=262144 celle di memoria a 8 bit, la memoria dovrà avere 512*512*2=524288 celle di memoria e quindi un BUS di indirizzamento a 19 bit, come illustrato in figura 2.

figura 2 – Organizzazione della memoria RAM esterna
Il BUS di comunicazione dati con la memoria è ad 8 bit poiché ogni pixel è codificato con 8 bit. Inoltre è presente il segnale r/w che consente di leggere un byte presente ad un certo indirizzo quando è a livello logico basso, di scriverlo in caso contrario.
La scansione dell’immagine sorgente avviene per righe per cui vengono letti due pixel adiacenti controllando se la differenza di luminosità supera una soglia prefissata, distinguendo due casi:
Per implementare l’algoritmo di estrazione contorni utilizziamo un set di 8 istruzioni (ISA – Instruction Set Architecture) da 16 bit suddivise in quattro classi:
|
Operazioni di ALU |
|
|
ADD |
r r r |
|
SUB |
r r r |
|
Operazioni di branch |
|
|
BRGT |
r r r |
|
RETURN |
- |
|
Operazioni memoria |
|
|
LOADB |
r r |
|
STORB |
r # |
|
Operazioni di trasferimento |
|
|
MOVE |
r # |
|
SHIFTL |
r r # |
Il primo registro a sinistra per tutte le istruzioni, tranne per la BRGT, rappresenta il registro di destinazione dell’operazione eseguita. La codifica delle istruzioni viene riportata nella figura 3.
L’istruzione di salto condizionato (BRGT – Branch If Greather Than) viene gestita caricando prima dell’istruzione di salto, il nuovo indirizzo del Program Counter (PC)nel registro 14 del register file attraverso l’istruzione MOVE. Se il salto viene preso, viene caricato il Program Counter con il valore presente nel registro 14 del register file, altrimenti viene incrementato di 1 e si continua l’esecuzione sequenziale del codice. Quindi il blocco di 4 bit contrassegnato da “PC reg value” fa riferimento al valore binario 1110 che rappresenta il registro 14 del register file.

figura 3 – Instruction set architecture dell’edge detector CPU

figura 4 – Gestione dell’istruzione di salto condizionato
Il programma risiede in una memoria ROM interna alla CPU con un BUS di indizzamento e dati ad 8 bit per cui ogni istruzione occupa due locazioni di memoria. Ogni istruzione viene letta nel seguente modo:

figura 5 – Caricamento del registro istruzione a 16 bit
Nella figura 6 è riportato il listato assembler del programma che il processore deve eseguire.

figura 6 – Codice assembler del programma di estrazione contorni
A questo punto possiamo passare alla specifica RTL (Register Transfer Level) attraverso un diagramma ASM, riportato nella figura 7.
Come si può vedere, la CPU inizialmente rimane in attesa di una interrupt: se int_req è zero non viene eseguita nessuna operazione, ma non appena questo diventa uno, ha inizio l’esecuzione della sequenza di operazioni. La prima operazione riguarda il caricamento del registro rom_mar (ROM Memory Address Register) che contiene l’indirizzo della locazione della ROM da cui leggere ovvero la parte bassa dell’istruzione (8 bit meno significativi). Successivamente viene trasferito il byte letto in registro ausiliario (a 8 bit) chiamato temp e nello stesso tempo viene incrementato di 1 l’indirizzo della ROM puntanto così al byte successivo. A questo punto vengono letti gli 8 bit più significativi dell’istruzione e vengono concatenati nel registro IR con gli 8 bit meno significati presenti nel registro temp ottenendo così l’istruzione completa a 16 bit all’interno del registro istruzione IR. Il passo successivo consiste nel riconoscere la classe dell’istruzione letta attraverso un controllo sui bit 15 e 14 del registro istruzione abilitando i relativi segnali di controllo.

figura 7 – Diagramma ASM dell’edge detector CPU
In relazione ai segnali di abilitazione vengono eseguite le varie operazioni, i cui digrammi ASM sono riportati qui di seguito. Se viene asserito il segnale alu_en si tratta di una operazione di ALU per cui, attraverso l’unità di decodifica dell’istruzione (ID – Instruction decode) vengono decodificati gli indirizzi degli operandi. A questo punto viene eseguito un controllo sul bit 13 del registro istruzione al fine di distinguere le istruzioni all’interno della classe corrispondente: se il valore è 0 viene eseguita un’addizione, se il valore è 1 viene eseguita una sottrazione.

figura 8 – Diagramma ASM relativo alle istruzioni ADD e SUB
Se l’istruzione riguarda un’operazione di trasferimento[1], anche qui si effettua un controllo sul bit 13 del registro istruzione: se vale 0 viene eseguita l’istruzione MOVE e in particolare viene decodificato il registro di destinazione r_des, il valore immediato a 9 bit identificato da imm_op, dopodichè questo viene trasferito nel registro di destinazione; se vale 1 viene eseguita l’istruzione SHIFTL. Viene decodificato l’indirizzo di destinazione r_des, quello sorgente r_sor e il numero di shift da eseguire sul valore presente nel registro r_sor (nel nostro caso 9) identificato dal blocco a 5 bit “shift number”. A questo punto viene “shiftato” il valore del registro r_sor e il risultato viene trasferito nel registro r_des.

figura 9 – Diagramma ASM relativo alle istruzioni SHIFTL e MOVE
Se l’istruzione è un’operazione di salto e, se il bit 13 del registro istruzione è 1, si esegue l’istruzione RETURN caricando il valore zero nel PC al fine di puntare alla prima istruzione da eseguire. Quindi in queste condizioni si riporta la CPU ad attendere un nuovo impulso sul segnale di interrupt al fine di avviare nuovamente l’esecuzione del programma. Se il tredicesimo bit del registro istruzione vale 0 viene eseguita l’istruzione di salto condizionato BRGT. In particolare, vengono prima decodificati gli indirizzi r_sor1, r_sor2 e vengono confrontati tra loro: se r_sor1>r_sor2 viene caricato nel PC l’indirizzo dell’etichetta alla quale saltare, mentre nel caso contrario viene continuata l’esecuzione sequenziale del codice.

figura 10 – Diagramma ASM relativo alle istruzioni RETURN e BRGT
Infine, se viene asserito il segnale mem_en l’istruzione è un’operazione di memoria. Se il bit 13 del registro istruzione vale 0 viene eseguita l’istruzione LOADB che carica un byte dalla memoria esterna. In particolare vengono decodificati gli indirizzi del registro che contiene l’indirizzo della locazione di memoria alla quale puntare e del registro di destinazione che contiene il valore letto. A questo punto viene eseguita l’operazione vera e propria scrivendo il byte ricevuto dalla memoria nel registro facente riferimento all’indirizzo precedentemente decodificato. Se il tredicesimo bit del registro istruzione vale 0, viene eseguita l’istruzione STORB mediante la quale viene scritto un byte in memoria. Quindi viene decodificato l’indirizzo che contiene l’indirizzo della locazione di memoria nella quale scrivere e il valore dell’operatore immediato, dopodichè vengono abilitati i segnali relativi alla scrittura scrivendo il valore dell’operatore immediato (8 bit) nella locazione puntata dal relativo indirizzo precedentemente decodificato.

figura 11 – Diagramma ASM relativo alle istruzioni LOADB e STORB
Per quanto riguarda la struttura interna della CPU, questa è costituita da un’unità di fetch delle istruzioni, un’unità di decodifica per l’indirizzamento degli operandi, una ALU a 19 bit, un’unità di esecuzione, una ROM interna di 256 locazioni da 8 bit ciascuna e un banco di 16 registri a 19 bit. Nella figura 12 è visibile il data path dell’edge detector CPU dove si possono notare dei BUS interni a 19 bit per le operazioni di ALU, per il register file e BUS a 8 bit per la lettura e/o scrittura dei dati in memoria.

figura 12 – Data path dell’edge detector CPU
In definitiva il funzionamento della CPU, e quindi i vari passi da seguire per l’esecuzione delle istruzioni, sono i seguenti:
1.
1.1. Caricamento del PC nel registro rom_mar per puntare al byte basso dell’istruzione da eseguire, abilitazione del segnale di lettura della ROM e caricamento degli operandi nell’ALU (fronte di salita del clock).
1.2. Lettura del byte basso dell’istruzione dalla memoria ROM interna ed esecuzione dell’operazione di somma tra il registro rom_mar e il registro cablato “1” (fronte di discesa del clock).
2.
2.1. Scrittura del byte basso nel registro istruzione, scrittura del risultato dell’operazione di ALU precedente nel registro rom_mar per puntare al byte alto dell’istruzione da eseguire, bypass del risultato dell’ALU sull’operando 1 e abilitazione del segnale di lettura della ROM (fronte di salita del clock).
2.2. Lettura del byte alto dell’istruzione dalla memoria ROM interna ed esecuzione della somma tra il risultato dell’operazione di ALU precedente (rom_mar + 1) e il registro cablato “1” (fronte di discesa del clock).
3.
3.1. Caricamento del PC con il risultato dell’ALU (rom_mar+2 per puntare all’istruzione successiva), scrittura del byte alto nel registro istruzione e disabilitazione del segnale di lettura della ROM (fronte di salita del clock).
3.2. Nessuna operazione (fronte di discesa del clock).
4.
4.1. Controllo dei bit 13, 14 e 15 del registro istruzione (IR) per la discriminazione dell’istruzione da eseguire e decodifica degli indirizzi degli operandi (fronte di salita del clock).
4.2. Nessuna operazione (fronte di discesa del clock).
5. Esecuzione dell’istruzione:
5.1. Se è l’istruzione MOVE:
5.1.1. viene caricato il valore dell’operando immediato nel registro di destinazione (fronte di salita del clock).
5.1.2. nessuna operazione (fronte di discesa del clock)
5.2. Se è l’istruzione SHIFTL:
5.2.1. viene shiftato di n posizioni il registro sorgente e viene scritto il risultato nel registro di destinazione (fronte di salita del clock).
5.2.2. nessuna operazione (fronte di discesa del clock)
5.3. Se è un’operazione di ALU:
5.3.1. vengono caricati gli operandi (fronte di salita del clock)
5.3.2. esecuzione dell’operazione di ALU (fronte di discesa del clock)
5.3.3. scrittura del risultato nel registro di destinazione (fronte di salita del clock) – l’esecuzione di questa operazione avviene in parallelo al fetch dell’istruzione successiva.
5.4. Se è l’istruzione STORB:
5.4.1. viene scritto l’indirizzo della cella di memoria nella quale scrivere sul BUS di indirizzamento e il dato da memorizzare sul BUS dati (fronte di salita del clock)
5.4.2. nessuna operazione (fronte di discesa del clock)
5.4.3. scrittura del dato presente sul BUS dati nella cella di memoria indirizzata dal BUS di indirizzamento (fronte di salita del clock) – l’esecuzione di questa operazione avviene in parallelo al fetch dell’istruzione successiva.
5.5. Se è l’istruzione LOADB:
5.5.1. viene scritto l’indirizzo della cella di memoria dalla quale leggere il dato sul BUS di indirizzamento (fronte di salita del clock)
5.5.2. nessuna operazione (fronte di discesa del clock)
5.5.3. scrittura del dato letto dalla RAM presente sul BUS dati nel registro di destinazione (fronte di salita del clock) - l’esecuzione di questa operazione avviene in parallelo al fetch dell’istruzione successiva.
5.6. Se è l’istruzione RETURN:
5.6.1. viene caricato il valore 0 nel PC e viene riportata la CPU nella condizione iniziale, cioè in attesa di un segnale di interrupt (fronte di salita del clock)
5.6.2. nessuna operazione (fronte di discesa del clock)
5.7. Se è l’istruzione BRGT:
5.7.1. valutazione dell’operazione di confronto (fronte di salita del clock)
5.7.2. nessuna operazione (fronte di discesa del clock – 1° ciclo)
5.7.2.1. se la condizione è vera carica l’indirizzo presente nel registro 14 nel PC (fronte di salita del clock)
5.7.2.2. se la condizione è falsa continua l’esecuzione sequenziale (fronte di salita del clock)
5.7.3. nessuna operazione (fronte di discesa del clock – 2° ciclo).
E’ bene precisare che la CPU in questione lavora su entrambi i fronti di clock. Infatti, mentre l’unità di decodifica, gli accessi nella RAM esterna, gli accessi ai registri avvengono sul fronte di salita del clock, l’ALU e l’unità di fetch delle istruzioni avvengono sul fronte di discesa del clock. Questo per ridurre la latenza di esecuzione delle istruzioni al fine di creare una sorta di pipeline interna (in particolare per ridurre la latenza delle istruzioni di memoria e delle istruzioni di salto). Attraverso l’attivazione di alcuni segnali interni, la CPU è in grado di eseguire il fetch dell’istruzione successiva e allo stesso tempo portare a termine l’esecuzione dell’istruzione precedentemente avviata senza perdere ulteriori cicli di clock. Il codice VHDL è diviso in due file dove nel primo è possibile notare la presenza di una funzione di shift, un processo di controllo, un processo operativo e un processo per l’assegnazione dello stato prossimo; nel secondo file sono definite le unità di memoria ROM e RAM, l’ALU e la funzione per la gestione del BUS dati bi-direzionale a 8 bit della RAM esterna.
Per quanto riguarda la sintesi, il sistema viene mappato su una FPGA or2c12aM84 prodotta da Lattice semiconductor. Di seguito viene riportato il log della sintesi mediante Leonardo Spectrum.
|
Dispositivi utilizzati per il chip or2c12aM84 |
|||
|
Risorse |
Utilizzate |
Disponibili |
Utilizzo |
|
IOs |
31 |
64 |
48.44% |
|
LUTs |
1250 |
1300 |
96.15% |
|
PFUs |
313 |
325 |
96.31% |
|
Flipflops |
441 |
1300 |
33.92% |
Per ulteriori approfondimenti e particolari sulla struttura hardware è possibile visionare i commenti presenti nel codice VHDL.

figura 13 – Realizzazione circuitale su FPGA dell’ALU
Nella figura 13 è riportato lo schema dell’ALU relativo alla sintesi mediante Leonardo spectrum, mentre nella figura 14 vengono riportati gli schemi dell’Edge detector, delle unità di memoria e dell’ALU.



[1] Si tenga presente che l’istruzione SHIFTL è in realtà un’operazione di ALU. Per poter suddividere le istruzioni in quattro classi e quindi per semplicità, consideriamo quest’ultima come un’operazione di trasferimento.