L’intento di questo articolo non é di fornire una guida al VHDL, ma un workflow dallo schematico al bitstream attraverso l’ISE Design Suite di Xilinx rispondendo alle domande di chi compra un nuovo FPGA e non sa da dove iniziare.
Registratevi al sito Xilinx e createvi un account e scaricate Xilinx ISE Design Suite per Windows o Linux, nel mio caso sto utilizzando la versione 14.7. Decomprimete il file scaricato e installate la suite scegliendo il Webpack che ha la licenza per studenti.
Step 1 – Creare un nuovo progetto
a) Aprite Xilinx ISE Design Suite e create un nuovo progetto.
b) File -> New project.
c) Nominate il progetto ad esempio ledBlink e scegliete il percorso d’installazione.
d) I dati inerenti al vostro FPGA gli trovate anche direttamente sulla board, una volta compilato il Project Settings cliccate Next.
Nel mio caso sto utilzzando una FPGA Board Nexys4 Artix-7 acquistata presso il vendor italiano Mirifica.
Step 2 – Utilizzo dello schematico
a) Facciamo Right click su xc7a100t-1csg324 e selezioniamo New Source.
b) Infine selezioniamo “Schematic” nominandolo ad esempio “sch_ledBlink” e clickiamo “Next” e “Finish”.
c) Realizzando per primo lo schematico questo verrà impostato in automatico come Top Module.
Diventando così la nostra work-area dove inseguito andremo ad utilizzare il “simbolo” del blink-led che realizzeremo nel prossimo step a partire dal codice VHDL.
Step 3 – VHDL Module
a) Facciamo Right click su “sch_ledBlink″ e selezioniamo “New Source”.
b) Infine selezioniamo “VHDL Module” nominandolo ledBlink e clickiamo “Next” e “Finish”
VHDL e linguaggi di programmazione
A volte l’approccio di chi ha esperienza con un linguaggio di alto livello (c# , Java etc) è di considerare il VHDL come un qualsiasi altro linguaggio, scordandosi però che non si sta “programmando” ma “descrivendo” hardware. In particolare, vi sono due caratteristiche che differenziano il VHDL rispetto ai linguaggi di programmazione convenzionali:
- lo svolgimento parallelo di più operazioni.
- la gestione dei tempi di propagazione dei segnali all’interno dei circuiti digitali
Questi due punti sono interessanti se pensate all’implementazione di reti neurali artificiali (ANN) su FPGA considerando che l’architettura di Von Neumann su cui si basano i computer è seriale invece una delle peculiarità delle ANN era il loro essere parallele e distribuite. Qui trovate l’implementazione di un perceptron su FPGA utilizzando Matlab e System Generator.
Entity e architecture
Una descrizione di un componente VHDL richiede una entity declaration (riga 6-12) e un’architecture body (riga 14-34), la prima inerente all’interfaccia (interface) del componente quindi alle porte di input e output. La seconda invece definisce la funzione dei componenti. Inoltre possono esistere diverse possibili architetture per la stessa entity.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity LED_Blink is
port (
CLK_100MHz: in std_logic;
LED: out std_logic
);
end LED_Blink;
architecture Behavioral of LED_Blink is
signal Counter: std_logic_vector(26 downto 0);
signal CLK_1Hz: std_logic;
begin
Prescaler: process(CLK_100MHz)
begin
if rising_edge(CLK_100MHz) then
if Counter < "10111110101111000010000000" then -- 50 mln
Counter <= Counter + 1;
else
CLK_1Hz <= not CLK_1Hz;
Counter <= (others => '0');
end if;
end if;
end process Prescaler;
LED <= CLK_1Hz;
end Behavioral;
How it works
Perché il nostro led lampeggi abbiamo bisogno di dividere la frequenza di clock del FPGA attraverso un contatore binario, ma di quanti bits? Non dobbiamo contare fino a , ma fino a in modo che il nostro led si accenda e si spenga.
Rappresentando il numero intero di in codifica binaria:
notiamo così che l’array standard logic avrà bisogno di un minimo di 27 bits.
signal Counter: std_logic_vector(26 downto 0);
Il contatore viene incrementato sul fronte di salita del clock fino a quando non è minore del valore ed infine resettato.
Library
Come nei linguaggi di programmazione possiamo utilizzare delle librerie e dei packages che aggiungono funzionalità al VHDL standard. I package e le librerie sono moduli che possono contenere porzioni di codice isolate dal resto della descrizione con lo scopo di rendere il codice riutilizzabile. Le entity, le architecture, i package, i package body e la configuration prendono il nome di design units. Un modello praticamente è fatto di design units al cui interno si costruisce la descrizione dettagliata del sistema. Ad esempio la LIBRARY IEEE contiene i seguenti packages:
- std_logic_1164 che definisce la std_logic e relative funzioni
- std_logic_arith funzioni aritmetiche
- std_logic_signed funzioni aritmetiche su numeri con segno
- std_logic_unsigned per quelli senza segno
I package contengono quindi le definizioni di tutti i tipi di variabili, degli operatori aritmetici e relazionali nonché alcune funzioni di importanza rilevante, come quelle di conversione (in interi, in std_logic e std_logic_vector, in interi con o senza segno, in bit e bit_vector).
Entity declaration
Il design entity inizia con la dichiarazione dell’entity definendone innanzitutto il nome che però non deve contenere spazi bianchi e caratteri speciali o iniziare con numeri. Se doveste usare un editor esterno , ad esempio SublimeText, il nome della entity e del file dovranno coincidere quando importate il file .vhd in un progetto su ISE Design. Con l’istruzione Port dichiariamo le porte di input e output dell’interfaccia.
L’entitity definisce quindi le porte con cui il nostro sistema comunica con il mondo esterno.
entity nome_entity is
Port ( nome_porta : direzione e tipo_segnale;
nome_porta : direzione e tipo_segnale);
end nome_entity;
Notiamo come dopo la dichiarazione dell’ultimo segnale non va messo il punto e virgola (;), ma nell’ultima parantesi rotonda chiusa a indicare il fatto che è l’ultima dichiarazione dell’entity.
Spesso la entity viene paragonata ad una scatola nera (black box) visto che ne vediamo solo gli ingressi e le uscite , ma non il funzionamento interno. Più semplicemente immaginiamoci un simbolo di uno schematico e chiediamoci quali saranno gl’ingressi e le uscite del nostro sistema?
Nel nostro caso vogliamo far lampeggiare un Led, quindi avremmo come input il clock (riga 10) e come output il led (riga 11).
Architecture body
Dopo aver definito l’interfaccia di una design entity, si passa alla descrizione di un’architettura cioè al funzionamento interno del sistema .
L’architettura è composta da un header e da un body che inizia con la keyword begin ed è la parte dove si inseriscono le istruzioni descrittive ed è un’area concorrente cioè dove le istruzioni, a prescindere dall’ordine di scrittura, possono essere eseguite parallelamente. Esiste però anche anche un’area sequenziale , dichiarata dal costrutto process, al cui interno le istruzioni vengono eseguite nell’ordine di scrittura.
architecture nome_architettura of nome_entity is
-- eventuale dichiarazione
begin
-- istruzione concorrente
end nome_architettura;
Process
Il process body, nonostante sia un insieme di istruzioni sequenziali (sequential staments), viene considerato come un’unica operazione concorrente ed è alla base delle descrizioni comportamentali. Solo alle istruzioni sequenziali sono concesse le assegnazioni <= in quanto l’assegnazione di un signale viene considerata sequenziale all’interno di un processo. All’interno di un processo è possibile definire dei tipi, delle costanti, delle variabili che sono locali al processo e quindi invisibili ad altri processi o statements concorrenti dell’architettura.
Un processo è composto da tre parti:
– sensitivity list ovvero la lista dei segnali in grado di attivare il processo in caso di un evento nei segnali di ingresso.
– dichiarazione dei tipi, di sottotipi, delle costanti che hanno solo visibilità locale.
– process body che rappresenta il comportamento del process.
nome_process: process (sensitivity list)
dichiarazione
begin
process body
end process nome_process;
Prima abbiamo importato la libreria standard IEEE 1164 che ci permette di utilizzare le due funzioni: rising_edge() e falling_edge() che assumono valore true quando il segnale utilizzato come argomento delle funzioni effettua un fronte di salita o di discesa.
Step 4 – Create Schematic Symbol
Nella barra laterale di ISE Design Suite selezioniamo il codice VHDL da convertire in Simbolo e in seguito a selezionare per aggiungerlo al nostro schematico. Per sicurezza converebbe anche fare prima un Check Syntax.
In basso alla barra laterale di sinistra abbiamo diverse tab come Design, Files, Libraries, noi andremo a clicckare su Symbols dove selezioneremo dalla path del nostro progetto il simbolo appena realizzato.
In categories ritroveremo la path d’installazione del nostro progetto selezionandola su Symbols apparirà il simbolo appena creato.
Ora dobbiamo solo clickare su ledBlink e trascianare il simbolo sopra l’area di lavoro. Dovreste ottenere un risultato analogo al mio:
Come best practice mia personale quando inserisco I/O Marker scelgo di dare nomi in minuscolo per gli input e in maiuscolo per gli Output. Così facendo è più facile distinguerli scrivendo l’user contraits file UCF.
Step 5 – Implementation Contraits File
a) Facciamo Right click su xc7a100t-1csg324 e selezioniamo New Source infine “Implementation Constraints File” e nominiamolo ad esempio “pinMapBlink”.
b) Premiamo Next e poi Finish.
In questo file imposteremo il pin-map del FPGA come gl’inputs, gli outputs ed il clock. Ci sono altri modi per farlo come ad esempio l’utilizzo di PlanAhead Design, ma preferisco questo metodo perché con pochi in-outputs è il più veloce.
Ora da Nexys 4 Resource Center dovete scaricare dalla sezione “Design Reosurces” l’user contrait file per avere la location dei pins,ad esempio il led si trova nella location “U3”.
NET "clk" LOC = "E3" |IOSTANDARD = "LVCMOS33";
NET "clk" TNM_NET = clk;
TIMESPEC TS_clk = PERIOD "clk" 20 ns HIGH 50%;
NET "LED" LOC = "U3" |IOSTANDARD = "LVCMOS33";
Step 6 – Sintesi
La sintesi ,detta anche compilazione, è il passaggio da una descrizione ad alto livello (comportamentale) ad una a bassa livello (net-list) ossia a livello di gates anche attraverso l’utilizzo del sintetizzatore di altre aziende non limitando la portabilità del VHDL. Infine l’elaborato della sintesi prende il nome di net-list attraverso un programma di place e routing, è in grado di generare il codice bitstream da inserire nel FPGA.
Step 7 – Upload del codice
Per l’implementazione del bit-code su FPGA vi consiglio il framework di Digilent Adept 2 selezionando il file con estensione .bit che avete generato dopo la sintesi che troverete nella path folder d’installazione del progetto.
Step 8 – Hello world!
Ed ecco il risultato finale con il led che lampeggia nella locazione (U3) che avevamo scelto nel nostro contraits file.
Volendo sapere per quanto tempo il led rimane acceso o spento ci basta calcolare: 26 è il valore dei bits del contatore.
Ora dobbiamo dividere il risultato ottenuto per la frequenza di clock del FPGA cioè 100.000.000 di oscillazioni al secondo
Moltiplichiamo il risultato , avendo così il periodo.
Se controlliamo il led dovremmo vedere che lampeggia una volta ogni 1,3 secondi (spento-acceso o acceso-spento) e 2,7 secondi per un loop completo (da spento-spento o da acceso-acceso). E la frequenza?
Bibliografia
Nexys
[1] Nexys 4, Digilent
[2] Nexys 4 Reference Manual, Digilent
FPGAs
[1] Volnei A. Pedroni,Circuit Design with VHDL, MIT Press Ltd, 2004
[2] Volnei A. Pedroni, Digital electronics and design with VHDL, Morgan Kaufmann, 2008
[3] Volnei A. Pedroni, Circuit Design and Simulation with VHDL, MIT Press, 2010
[4] Gina Smith, FPGAs 101: Everything you need to know to get started,Newnes, 2010