Il primo passo per lo sviluppo di un'entità VHDL è sempre uno schema di principio. Va bene fatto a mano sulla carta del panino, con FidoCadJ o con quello che si preferisce. Io uso il vecchissimo tgif perché ho una libreria di simboli molto comoda.
Sullo schema marchiamo anche tutti i segnali, con la loro direzione, la loro "larghezza" (in caso di vettori o bus) e soprattutto il loro nome.
A proposito di nomi: a distanza di anni io continuo ad usare la convenzione che mi è stata inculcata durante lo studio e trovo che sia molto utile: i nomi dei segnali finiscono con una x minuscola, seguita da una o piú lettere maiuscole il cuo significato è il seguente:
- S: segnale (controllo)
- D: data
- I: input
- O: output
- P: present
- N: next
- C: clock
- R: reset
- B: active-low
Può piacere, può non piacere. L'importante è che si diano nomi significativi ai segnali.
Lo schema per la nostra UART potrebbe essere per esempio questo:

- schema_uart.png (12.08 KiB) Osservato 878 volte
Lo schema è molto top-level. Non ci sono dettagli di logica o di implementazione.
Però si vede un registro di larghezza 8 che viene usato per bufferare i dati da trasmettere (TxShiftReg) e un altro registro (anch'esso di larghezza 8) che viene usato come shift-register (ingresso seriale e uscita parallela) per i dati ricevuti (RxShiftReg).
Tutto il controllo avviene nella non meglio specificata macchina a stati finiti (FSM).
Ci sono tre segnali di controllo:
- RxBusyxSO: come vedi subito dal nome, questo segnale esce dall'entità e segnala al resto del sistema, che i dati all'interfaccia RxDataxDO non sono validi. Questo segnale va alto quando è stato rilevato uno start bit in ingresso. Torna basso quando viene ricevuto lo stop bit, segno che il byte è completo e valido.
- TxBusyxSO: stesso discorso, indica che l'uart sta trasmettendo dati in uscita.
- TxStartxSI: indica all'UART che i bit in ingresso a TxDataxDI sono validi e possono essere campionati per poi essere trasmessi. Ovviamente questo segnale va tenuto basso quando TxBusyxSO è alto.
Visto che abbiamo degli elementi sequenziali (i flip-flop che formano i registri, sia quelli disegnati esplicitamente, sia quelli necessari a memorizzare lo stato della FSM) ci serviranno anche un clock e un reset. Le porte corrispondenti le vedi sulla sinistra.
A questo punto possiamo già buttar giú la struttura dell'entità:
- Codice: Seleziona tutto
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
entity uart is
generic (CLKFREQ : integer := 5000000;
BAUD : integer := 57600);
port (RxxSI : in std_logic;
TxxSO : out std_logic;
RxDataxDO : out std_logic_vector (7 downto 0);
TxDataxDI : in std_logic_vector (7 downto 0);
RxbusyxSO : out std_logic;
TxbusyxSO : out std_logic;
TxstartxSI : in std_logic;
ClkxC : in std_logic;
RstxRBI : in std_logic);
end uart;
architecture Behavioral of uart is
type state is (idle, startbit, data, stopbit);
signal RXstatexSP, RXstatexSN, TXstatexSP, TXstatexSN : state;
signal RXshiftregxSN, RXshiftregxSP : std_logic_vector (7 downto 0);
signal RXbitcountxSN, RXbitcountxSP : integer range 0 to 8;
signal RXclkcountxSN, RXclkcountxSP : integer range 0 to (CLKFREQ/BAUD)-1;
signal TXshiftregxSN, TXshiftregxSP : std_logic_vector (7 downto 0);
signal TXbitcountxSN, TXbitcountxSP : integer range 0 to 8;
signal TXclkcountxSN, TXclkcountxSP : integer range 0 to (CLKFREQ/BAUD)-1;
begin
-- implentare la UART qui
end Behavioral;
Ho inserito due parametri: la frequenza del clock in ingresso (qui 5 MHz) e la baudrate desiderata.
Le porte rispecchiano quanto appena discusso.
Per la FSM ho definito degli stati, che corrispondono alle tre fasi della trasmissione via UART (start, data, stop) oltre allo stato idle che è quello occupato quando non c'è una trasmissione in corso.
Questo datatype lo assegno a quattro segnali. In realtà sono due coppie. Vedremo piú avanti che questo è l'unico metodo sicuro al 100% per instanziare registri. Si usano due segnali, con lo stesso nome (qui si vede l'utilità di una norma di nomenclatura stretta), uno finisce però in P, l'altro in N. Il primo è il
present state (quello in uscita dal flip-flop o registro), mentre N sta per
next state (è quello in uscita dalla logica combinatoria e in ingresso al flip-flop).
Inoltre ci sono altri sei elementi di memoria così definiti. Tre per TX e tre per RX.
Il primo della lista è il buffer disegnato nello schema qui sopra.
Il secondo tiene conto nel bit all'interno del messaggio ricevuto.
Il terzo è un counter usato per dividere il clock in ingresso e shiftare i dati al momento giusto.
A questo punto conosciamo esattamente il numero di flip-flop che il compiler dovrà instanziare. Se l'output del compiler non corrisponde a quello che ci aspettiamo di leggere c'è un problema. È uno dei primi check che si fanno. Il vantaggio è che se ci si attiene alle regole che ti racconto dopo, il check sarà sempre positivo
C'è una differenza rispetto a quello che avevi scritto tu. Tu per esempio usi statement del tipo:
- Codice: Seleziona tutto
signal ready: std_logic:= '1';
Questo tipo di inizializzazione viene gestita bene dai simulatori come ModelSim, ma il risultato quando si sintetizza (e quindi si va in hardware) è incerto. Meglio evitare e implementare un concetto di reset solido e sano (una delle cose che intendo quando dico di scrivere per il sintetizzatore, non per il compiler.
Vado a preparare cena. Continuiamo piú tardi.
Se non fosse chiaro quanto ho bofonchiato fino ad adesso, batti un colpo.
Saluti Boiler