Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

Discussione sul trasmettitore UART

Elettronica lineare e digitale: didattica ed applicazioni

Moderatori: Foto Utentecarloc, Foto UtenteIsidoroKZ, Foto Utenteg.schgor, Foto UtenteDirtyDeeds, Foto UtenteBrunoValente

2
voti

[1] Discussione sul trasmettitore UART

Messaggioda Foto Utentedadduni » 17 set 2018, 11:54

Salve a tutti,
con Foto Utenteboiler abbiamo pensato di aprire una discussione in cui ci si confronta e si approfondisce il mondo del design digitale e in particolare la programmazione VHDL su FPGA. Tutto scaturisce dal mio secondo articolo in cui descrivo come ho realizzato un trasmettitore UART su FPGA.
Giustamente Foto Utenteboiler mi ha fatto notare nei commenti che ci sono "un paio di migliorie da fare" ed è stato molto gentile a definirle "un paio" perché è un progettino fatto alla buona e, nonostante funzioni e faccia quello che deve, è ben lontano dalle buone regole di progettazione, mantenibilità e migliorabilità.
Inizio io stesso ad evidenziare un limite enorme del progetto: è blindato. E' pensato per una ed una sola applicazione e va bene solo per quello. Invece sappiamo bene che si tende a generalizzare il più possibile il codice e a renderlo parametrico in modo da cambiare assetto (ad esempio il numero di bit tramessi, parità, baudrate) in maniera semplice. Questo non è stato fatto in quanto, per semplicità, ho creato una macchina a stati basata su contatore a base 10: uno start, 8dati, uno stop. E già qui c'erano sicuramente modi migliori per farlo inizializzando un segnale di stato che memorizzi in quale parte della trasmissione ci troviamo così da rendere le varie fasi un po' più indipendenti.

Già da me ho effettuato delle "scelte di progetto" volutamente semplici e che puntano al risultato immediato, a questo si sommano sicuramente inesperienza ed errori ed è per questo che non vedo l'ora che boiler mi dica cosa ne pensa!

Davide

PS sicuramente farò anche il blocco ricevitore in un altro piccolo articolo, magari se da qui escono belle idee si può pensare di fare un trasmettitore 2.0 con le migliorie aggiunte!
Avatar utente
Foto Utentedadduni
1.597 1 6 12
Expert EY
Expert EY
 
Messaggi: 971
Iscritto il: 23 mag 2014, 16:26

1
voti

[2] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utenteboiler » 17 set 2018, 12:15

Boiler al momento è molto impegnato con un capo-progetto incompetente che gli rompe gli zebedei, ma stasera o domani prova a scrivere qualcosa.

In sè non è sbagliata l'idea di partire con qualcosa di "blindato", come dici tu. Le funzioni aggiuntive si possono aggiungere un po' alla volta.

Quello che è importante è partire con una struttura solida:
- separare processi sequenziali e combinatorici
- KISS (keep it strictly synchronous)
- scrivere per il sintetizzatore, non per il compiler
- instanziare registri solo ed esclusivamente con dei segmenti di codice "sicuri"
- ...

Saluti, Boiler
Avatar utente
Foto Utenteboiler
14,4k 4 7 13
G.Master EY
G.Master EY
 
Messaggi: 2447
Iscritto il: 9 nov 2011, 12:27

0
voti

[3] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utentedadduni » 17 set 2018, 17:11

Innanzitutto grazie per la risposta e non vedo l'ora di sentire i consigli che vorrai darmi :ok: Aspettando periodi più tranquilli per te ti dico subito che quello in teoria ho capito quello che vuoi dire, e il tuo elenco puntato ha perfettamente senso, all'atto pratico però non capisco cosa e come cambiare ad esempio nel "separare sequenziali e combinatori" e "tenere tutto rigorosamente sincrono".
Davide
Avatar utente
Foto Utentedadduni
1.597 1 6 12
Expert EY
Expert EY
 
Messaggi: 971
Iscritto il: 23 mag 2014, 16:26

2
voti

[4] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utenteboiler » 17 set 2018, 19:34

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
schema_uart.png (12.08 KiB) Osservato 872 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 :ok:

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
Avatar utente
Foto Utenteboiler
14,4k 4 7 13
G.Master EY
G.Master EY
 
Messaggi: 2447
Iscritto il: 9 nov 2011, 12:27

0
voti

[5] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utentedadduni » 17 set 2018, 20:59

Nessun colpo da battere, tutto chiaro fin qui e mi sembra di intuire come "andrà a finire" la faccenda del doppio segnale per presentState e nextState, credo di aver letto questo modo di procedere in qualche libro che però non spiegava il perché.
Mi piace molto questo modo di nomenclare, penso che lo copierò volentieri,
Grazie mille di questa spiegazione quando vorrai continuare sappia che hai uno studente pronto ad apprendere!
Avatar utente
Foto Utentedadduni
1.597 1 6 12
Expert EY
Expert EY
 
Messaggi: 971
Iscritto il: 23 mag 2014, 16:26

1
voti

[6] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utenteboiler » 17 set 2018, 22:05

dadduni ha scritto:Mi piace molto questo modo di nomenclare, penso che lo copierò volentieri

Se usi emacs c'è il VHDL mode, che aggiunge una marea di funzioni, tra le quali il syntax highlighting che fa il parsing di questo tipo di nomenclatura.

In realtà aborro emacs, non è un editor, è un sistema operativo che si spaccia per editor. Ma per il VHDL lo uso perché è veramente ottimo.

Lo puoi scaricare qui:
https://guest.iis.ee.ethz.ch/~zimmi/ema ... -mode.html

vhdl-mode.gif


Boiler
Avatar utente
Foto Utenteboiler
14,4k 4 7 13
G.Master EY
G.Master EY
 
Messaggi: 2447
Iscritto il: 9 nov 2011, 12:27

1
voti

[7] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utenteboiler » 17 set 2018, 22:53

Veniamo al punto separare processi sequenziali e combinatori.

Nel codice di cui sopra manca l'implementazione della funzionalità dell'UART.
La suddivideremo in 4 parti:
- concurrent e conditional assignments
- processo combinatorio per la gestione della trasmissione (una parte della FSM)
- processo combinatorio per la gestione della ricezione (l'altra parte della FSM)
- processo sequenziale per i registri

Concurrent e conditional assignments

Il primo punto si riferisce a quei segnali che vengono semplicemente assegnati ad una porta partendo da segnali presenti all'interno dell'entità, eventualmente applicandovi condizioni o semplici operazioni logiche.

Nel nostro caso abbiamo:
Codice: Seleziona tutto
  TxbusyxSO <= '0' when TXstatexSP = idle else '1';
  RxbusyxSO <= '0' when RXstatexSP = idle else '1';
  RxDataxDO <= RXshiftregxSP;


Le prime due righe generano i segnali di "busy". La condizione è semplice: se la FSM è nello stato idle, allora l'UART non è busy (non c'è una trasmissione in corso e quindi i dati sono da considerarsi validi). In ogni altro caso il segnale in uscita dovrà essere alto.
La terza riga assegna il contenuto dello shift register di ricezione alla porta dati d'uscita.

Processi combinatori (FSM)

Di questi ce ne occupiamo domani.

Processo sequenziale per i registri

Codice: Seleziona tutto
  fsm_mem : process (ClkxC, RstxRBI) is
  begin  -- process fsm_mem
    if RstxRBI = '0' then                   -- asynchronous reset (active low)
      RXstatexSP    <= idle;
      RXclkcountxSP <= 0;
      RXbitcountxSP <= 0;
      RXshiftregxSP <= (others => '0');
      TXstatexSP    <= idle;
      TXclkcountxSP <= 0;
      TXbitcountxSP <= 0;
      TXshiftregxSP <= (others => '0');
      
    elsif ClkxC'event and ClkxC = '1' then  -- rising clock edge
      RXstatexSP    <= RXstatexSN;
      RXclkcountxSP <= RXclkcountxSN;
      RXbitcountxSP <= RXbitcountxSN;
      RXshiftregxSP <= RXshiftregxSN;
      TXstatexSP    <= TXstatexSN;
      TXclkcountxSP <= TXclkcountxSN;
      TXbitcountxSP <= TXbitcountxSN;
      TXshiftregxSP <= TXshiftregxSN;
    end if;
  end process fsm_mem;


Qui è dove bisogna diventare pignoli.
Questa è l'unica (!) sintassi che garantisce un risultato sintetizzabile e che corrisponde a quello che si vuole ottenere.

Si potrebbe discutere sulla condizione d'esecuzione. Per farlo dobbiamo parlare della logica IEEE 1164: non esistono solo 1 e 0. Ci sono anche H e L (che sono l'equivalente "debole", per esempio un pull-up o pull-down), così come X e W (i correspondenti stati sconosciuti) o "-" che indica un don't care.
VHDL interpreta rising_edge solo come una transizione da 0 a 1, mentre con ClkxC'event and ClkxC = '1' rilevi per esempio anche una transizione da L a 1 (da pull-down a strong drive, come potrebbe succedere su un bus). Tutte e due le condizioni hanno la loro ragione d'essere. Se si sa cosa si fa, si può scegliere quella che meglio adempie allo scopo. Se non si è sicuri, ClkxC'event and ClkxC = '1' è preferibile (in particolare in simulazione).

Ma torniamo alla sintassi: quello che veramente è importante è che non ci sia logica combinatoria. Scrivere per esempio
Codice: Seleziona tutto
SegnaleAxDP <= SegnaleBxDN and SegnaleCxDN;
non s'ha da fare!

Anche importante è che nella sensitivity list ci siano solo il clock e il reset.

Nella prima metà del processo, è implementato il reset. È importante che tutti i segnali abbiano un valore di reset ben deifnito. Pensa ad esempio se tu avessi una FSM a 5 stati... il compiler dovrebbe istanziare 3 flip-flop e gli stati sarebbero codificati come segue:
000 = stato a
001 = stato b
010 = stato c
011 = stato d
100 = stato e
101 = stato sconosciuto!
110 = stato sconosciuto!
111 = stato sconosciuto!
Al momento dell'accensione i registri potrebbero benissimo trovarsi nella condizione 110.
Per questo motivo è importante:
a) che ci sia un reset che conduca ad uno stato valido
b) che questo reset venga applicato al power-up
c) (facoltativo) che la FSM riconosca anche gli stati erronei e li riconduca allo stato di default (lo vediamo domani)

Visto che è così importante, ripeto in forma generica la struttura di un processo sequenziale (che genera registri al momento della compilazione):

Codice: Seleziona tutto
  register : process (Clk, Rst)
  begin  -- process register
    if Rst = '0' then               -- asynchronous reset (active low)
      uscita_FF <= '0';
    elsif Clk'event and Clk = '1' then  -- rising clock edge
      if Enable = '1' then
        uscita_FF <= ingresso_FF;
      end if;
    end if;
  end process register;


In particolare, questo snippet di codice genera un flip-flop con enable. Se la funzione non serve, si possono semplicemente lasciar via la linea di if e di end if. Occhio che la sensitivity list resta comunque composta solo da clock e reset. Enable non ci sta.

Con questo abbiamo parlato anche del punto instanziare registri solo ed esclusivamente con dei segmenti di codice "sicuri" .

Un ultimo monito (visto che accenni l'argomento nel tuo articolo): il clock va gestito solo ed esclusivamente dalle primitive messe a disposizione a tale scopo dalla FPGA o dalla libreria fisica. Per esempio fare clock gating usando una porta AND e un segnale di controllo porta inevitabilmente a glitches che rendono il comportamento del sistema imprevedibile.
Modificare la frequenza di clock per ottenere un'altra baudrate è fattibile, ma può portare a problemi (al momento del routing si fa una timing analysis e se le frequenze sono variabili il discorso si complica). Un metodo piú semplice è scegliere una frequenza di clock sufficientemente alta per avere un solido oversampling alla baudrate massima e poi usare un counter per ottenere le baudrate inferiori abilitando il sampling al momento giusto ma mantenendo il clock veloce.

Saluti Boiler
Avatar utente
Foto Utenteboiler
14,4k 4 7 13
G.Master EY
G.Master EY
 
Messaggi: 2447
Iscritto il: 9 nov 2011, 12:27

0
voti

[8] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utentedadduni » 18 set 2018, 9:52

Non smetterò mai di ringraziarti per le spiegazioni, sono perfette!
Avrei delle domande se possibile. Dici che questo è scorretto perché in una assegnazione sequenziale tra present state e next state stiamo mettendo della logica combintoria.
Codice: Seleziona tutto
SegnaleAxDP <= SegnaleBxDN and SegnaleCxDN;

Però nelle macchine a stati è ovvio che per calcolare il next state (ingresso FF) ci sia del combinatorio che riguarda gli ingressi della macchina e il present state (uscita del FF). Che differenza c'è tra:
- fare due processi uno strettamente combinatorio che calcola next state e uno strettamente sequenziale che al fronte di clk fa passare il dato attraverso il FF
- fare il calcolo combinatorio e assegazione sequenziale in un solo processo
Tolto il modo di scrivere alla fine immagino vengano fuori comunque (nell esempio) un FF preceduto da una AND.

Ultima domanda, sempre se posso approfittare, alla fine del tuo intervento prima dici di usare le primitive della FPGA, poi di non usare assolutamente della logica per modificare il clock, e poi alla fine che si può usare un coutner. Io ho sempre usato counter per la divisione del clock ed è quello che ho fatto anche nell'articolo: è una procedura sbagliata?

Davide
Avatar utente
Foto Utentedadduni
1.597 1 6 12
Expert EY
Expert EY
 
Messaggi: 971
Iscritto il: 23 mag 2014, 16:26

1
voti

[9] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utentedadduni » 18 set 2018, 10:24

Vediamo se ci ho capito qualcosa con un esempio più semplice: un contatore da 0 a x.
Ho fatto due processi uno per il registro del contatore con nella sensitivity solo clk e reset (così il reset viene asincrono) e un secondo processo combinatorio con nella sensitivity list tutti i suoi ingressi (mi è stato insegnato che solo così si ha la garanzia di avere un blocco puramente combinatorio, è giusto?).
Il registro non fa altro che resettarsi con RST alto e portare il dato dall' ingresso (NextState) all'uscita (PresentState) al fronte di clock.
Il processo combinatorio legge l'uscita, la confronta cno una costante e decide il prossimo dato da caricare.
a parte la banalità del progetto, ti sembra strutturalmente solido secondo le regole che hai spiegato in precedenza?
Codice: Seleziona tutto
architecture Behavioral of counter is
    signal PresentState: std_logic_vector ( 7 downto 0 );
    signal NextState: std_logic_vector ( 7 downto 0 );
begin

process (CLK, RST) begin
    if( RST='1' ) then
        PresentState <= (others => '0');
    elsif rising_edge( CLK ) then
        PresentState <= NextState;
    end if;
end process;   
   
process (PresentState) begin       
    if PresentState= "00110011" then
        NextState <= (others=> '0');
    else
        NextState <= std_logic_vector(unsigned(PresentState) + 1);
    end if;
end process;
     
DATA_OUT<= PresentState;

end Behavioral;


Viene fuori uno schema che a me sembra molto corretto. Tra l'altro quando scrivevo i contatori prima delle tue spiegazioni venivano molto peggio, il codice era più semplice ma nello schema venivano fuori comparatori e più mux, adesso mi sembra avere drasticmente molto più senso.
Immagine.png

Gentilmente ti chiederei conferma di quanto ho detto per conferma di aver capito quello che intendevi
Avatar utente
Foto Utentedadduni
1.597 1 6 12
Expert EY
Expert EY
 
Messaggi: 971
Iscritto il: 23 mag 2014, 16:26

0
voti

[10] Re: Discussione sul trasmettitore UART

Messaggioda Foto Utenteluxinterior » 18 set 2018, 10:33

Leggo con molto interesse.
Il mio piccolo contributo sul clock. Anche io ci ho messo un attimo a capire il discorso dei clock enable quando ho inziato con VHDL.
La priam idea per dividere un clock è quella di usare un counter e di prendere le uscite intermedie come segnale di clock diviso. Questa procedura ha due problemi:
1.- perdi il sincronsimo dei diversi clock divisi a causa dei ritardi di propagazione.
2.- Nell'FPGA le linee che garantiscono la distribuzione del clock sono limitate
se inizi ad avere 4 o 5 clock diversi diventa un macello nell'hardware non hai linee di clock sufficienti e ti trovi a utilizzare dei segnali di clock ch enon sono sincroni tra loro. (Ognuno commuta con un certo ritardo rispetto agli altri proprio per il modo con cui viene generato).
Per questo per dividere il clock i segnali del counter non si utilizzano come clock ma come clock enable.
Distribusici un clock unico e poi utilizzi i segnali che escono dal counter come enable per dividere questo clock.
Un qualcosa del tipo:

Codice: Seleziona tutto
   
elsif (rising_edge(CLOCK)) then

      if (clk_enab = '1') then



Io non faccio cose a frequenze spaziali e utilzzo un segnale clk_enab centrato sul fronte del clock che mi serve. In questo modo ti interessa poco sapere quando sale o scende il segnale di enable. Sei sicuro che quando arriverà il fronte di clock principale tu sarai più o meno al centro dell'impulso di enable. Per questo puoi usare un counter per generare i segnali di enable, nel tempo andranno un poco a destra o un poco a sinistra rispetto al fronte del clock principale ma tu hai sempre la garanzia di entrare nella sequenza esattamente sul fronte del clock principale che sincronizza tutto il sistema.
Avatar utente
Foto Utenteluxinterior
2.249 2 4 8
Expert EY
Expert EY
 
Messaggi: 1253
Iscritto il: 6 gen 2016, 17:48

Prossimo

Torna a Elettronica generale

Chi c’è in linea

Visitano il forum: Nessuno e 33 ospiti