VHDL: il modulo di ricezione fallisce casualmente durante il conteggio dei bit


9

sfondo

Questo è un progetto personale; riguarda il collegamento di un FPGA a un N64, i valori byte che l'FPGA riceve vengono quindi inviati tramite UART al mio computer. In realtà funziona abbastanza bene! Sfortunatamente, in momenti casuali il dispositivo non funzionerà, quindi verrà ripristinato. Attraverso il debug, sono riuscito a trovare il problema, tuttavia sono sconcertato su come risolverlo perché sono abbastanza incompetente con VHDL.

Sto giocando con il VHDL da un paio di giorni e potrei non essere in grado di risolverlo.

Il problema

Ho un oscilloscopio che misura il segnale N64 nell'FPGA e l'altro canale si collega all'uscita dell'FPGA. Ho anche pin digitali che registrano il valore del contatore.

In sostanza, l'N64 invia 9 bit di dati, incluso un bit di STOP. Il contatore conta i bit di dati ricevuti e quando raggiungo i 9 bit, l'FPGA inizia a trasmettere tramite UART.

Ecco il comportamento corretto: inserisci qui la descrizione dell'immagine

L'FPGA è la forma d'onda blu e la forma d'onda arancione è l'ingresso dell'N64. Per tutta la durata della ricezione, il mio FPGA "echo" il segnale dell'ingresso a scopo di debug. Dopo che l'FPGA conta fino a 9, inizia a trasmettere i dati tramite UART. Notare che i pin digitali contano fino a 9 e l'uscita FPGA diventa BASSA immediatamente dopo il completamento dell'N64.

Ecco un esempio di errore:

inserisci qui la descrizione dell'immagine

Si noti che il contatore salta i bit 2 e 7! L'FPGA raggiunge la fine, aspettando il prossimo bit di partenza dall'N64 ma niente. Quindi l'FPGA scade e si ripristina.

Questo è il VHDL per il modulo di ricezione N64. Contiene il contatore: s_bitCount.

library IEEE;
use IEEE.STD_LOGIC_1164.all;   
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity N64RX is
     port(
         N64RXD : in STD_LOGIC;                    --Data input
         clk25 : in STD_LOGIC;
         clr : in STD_LOGIC; 
         tdre : in STD_LOGIC;                      --detects when UART is ready
         transmit : out STD_LOGIC;                 --Signal to UART to transmit  
         sel : out STD_LOGIC; 
         echoSig : out STD_LOGIC;
         bitcount : out STD_LOGIC_VECTOR(3 downto 0);
         data : out STD_LOGIC_VECTOR(3 downto 0)   --The significant nibble
         );
end N64RX;

--}} End of automatically maintained section

architecture N64RX of N64RX is 

type state_type is (start, delay2us, sigSample, waitForStop, waitForStart, timeout, count9bits, sendToUART);

signal state: state_type;
signal s_sel, s_echoSig, s_timeoutDetect : STD_LOGIC;
signal s_baudCount : STD_LOGIC_VECTOR(6 downto 0);  --Counting variable for baud rate in delay
signal s_bitCount : STD_LOGIC_VECTOR(3 downto 0);  --Counting variable for number of bits recieved 
signal s_data : STD_LOGIC_VECTOR(8 downto 0);   --Signal for data

constant delay : STD_LOGIC_VECTOR(6 downto 0) := "0110010";  --Provided 25MHz, 50 cycles is 2us 
constant delayLong : STD_LOGIC_VECTOR(6 downto 0) := "1100100";

begin 

n64RX: process(clk25, N64RXD, clr, tdre)
begin
    if clr = '1' then
        s_timeoutDetect <= '0';
        s_echoSig <= '1';
        s_sel <= '0';
        state <= start;
        s_data <= "000000000";
        transmit <= '0'; 
        s_bitCount <= "0000";
        s_baudCount <= "0000000";  
    elsif (clk25'event and clk25 = '1') then    --on rising edge of clock input
        case state is
            when start =>   
                --s_timeoutDetect <= '0';
                s_sel <= '0';
                transmit <= '0';        --Don't request UART to transfer   
                s_data <= "000000000";
                s_bitCount <= X"0";   
                if N64RXD = '1' then
                    state <= start;
                elsif N64RXD = '0' then     --if Start bit detected
                    state <= delay2us;
                end if;    

            when delay2us =>                 --wait two microseconds to sample
                --s_timeoutDetect <= '0';
                s_sel <= '1';
                s_echoSig <= '0';
                if s_baudCount >= delay then    
                    state <= sigSample;
                else
                    s_baudCount <= s_baudCount + 1;
                    state <= delay2us;
                end if;  

            when sigSample => 
                --s_timeoutDetect <= '1';
                s_echoSig <= N64RXD;
                s_bitCount <= s_bitCount + 1;
                s_baudcount <= "0000000";
                s_data <= s_data(7 downto 0) & N64RXD;      
                state <= waitForStop;   

            when waitForStop => 
                s_echoSig <= N64RXD;
                if N64RXD = '0' then
                    state <= waitForStop;
                elsif N64RXD = '1' then
                    state <= waitForStart;
                end if;   

            when waitForStart => 
                s_echoSig <= '1';
                s_baudCount <= s_baudCount + 1; 
                if N64RXD = '0' then 
                    s_baudCount <= "0000000";
                    state <= delay2us;
                elsif N64RXD = '1' then 
                    if s_baudCount >= delayLong then
                        state <= timeout;
                    elsif s_bitCount >= X"9" then
                        state <= count9bits;
                    else
                        state <= waitForStart;
                    end if;
                end if;     

            when count9bits =>  
                s_sel <= '0';
                if tdre = '0' then
                    state <= count9bits;
                elsif tdre = '1' then
                    state <= sendToUART;
                end if;   

            when sendToUART =>
                transmit <= '1';
                if tdre = '0' then
                    state <= start;
                else
                    state <= sendToUART;
                end if;

            when timeout =>
                --s_timeoutDetect <= '1';
                state <= start;

        end case;   
    end if;
end process n64RX;  
--timeoutDetect <= s_timeoutDetect;
bitcount <= s_bitCount;
echoSig <= s_echoSig;
sel <= s_sel;
data <= s_data(4 downto 1);

end N64RX;

Quindi, qualche idea? Suggerimenti per il debug? Suggerimenti sulla codifica di macchine a stati finiti?

Nel frattempo, continuerò a giocarci (alla fine ce la farò)! Aiutami a Stack Exchange, sei la mia unica speranza!

modificare

Un'ulteriore scoperta nel mio debug, gli stati passeranno da waitForStart a waitForStop. Ho dato a ogni stato un valore con waitForStart uguale a '5' e waitForStop uguale a '4'. Vedi l'immagine qui sotto: inserisci qui la descrizione dell'immagine


1
Nel tuo primo blocco di casi, c'è la riga "s_bitCount <= X" 0 ";" Quella X è un refuso?
Travisbartley,

@ trav1s No, quella "X" indica esadecimale. Quindi X "0" è in realtà "0000" in binario.
Nick Williams,

1
Ho avuto un paio di errori durante l'esecuzione del codice attraverso una linter. I segnali N64RXD e tdre non devono essere utilizzati nell'elenco di sensibilità del processo sequenziale, linea 36.
travisbartley,

1
@ trav1s Grazie per il puntatore, ho rimosso quei parametri; hai ragione, quelli non sono necessari. Purtroppo ho ancora il problema. Con l'ambito, ho aggiunto segnali per rilevare lo stato in cui mi trovo. Per qualche motivo, l'FPGA salta da "waitForStart" a "waitForStop" senza uno stato in mezzo! Questo è il motivo per cui non conta perché l'FPGA non raggiunge lo stato in cui conta il bit. Il "salto indietro" sembra essere il problema.
Nick Williams,

1
Ma la transizione "waitForStart" -> "waitForStop" non è valida. Non c'è modo di fare quel salto in un singolo ciclo. Controlla attentamente per assicurarti che non ci sia uno stato molto breve nel mezzo. Altrimenti, ci deve essere un errore hardware / di temporizzazione.
Travisbartley,

Risposte:


9

Non vedo un sincronizzatore sulla linea dati rx.

Tutti gli ingressi asincroni devono essere sincronizzati con il clock di campionamento. Ci sono un paio di ragioni per questo: metastabilità e routing. Questi sono problemi diversi ma sono correlati.

Ci vuole tempo perché i segnali si propagino attraverso il tessuto FPGA. La rete di clock all'interno dell'FPGA è progettata per compensare questi ritardi di "viaggio" in modo tale che tutti gli infradito all'interno dell'FPGA vedano l'orologio nello stesso preciso momento. La normale rete di routing non ha questo, e invece fa affidamento sulla regola che tutti i segnali devono essere stabili per un po 'di tempo prima che l'orologio cambi e rimanere stabili per un po' di tempo dopo che l'orologio cambia. Questi piccoli frammenti di tempo sono noti come tempi di installazione e di attesa per un dato infradito. Il componente di posizione e percorso della toolchain ha un'ottima comprensione dei ritardi di instradamento per il dispositivo specifico e presuppone di base che un segnale non violi i tempi di configurazione e di attesa dei flip flop nell'FPGA.

Quando si hanno segnali che non sono sincronizzati con il clock di campionamento, si può finire nella situazione in cui un flip flop vede il "vecchio" valore di un segnale poiché il nuovo valore non ha avuto il tempo di propagarsi. Ora ti trovi in ​​una situazione indesiderata in cui la logica che osserva lo stesso segnale vede due valori diversi. Ciò può causare un funzionamento errato, crash delle macchine a stati e tutti i tipi di problemi difficili da diagnosticare.

L'altro motivo per cui è necessario sincronizzare tutti i segnali di input è qualcosa chiamato metastabilità. Ci sono volumi scritti su questo argomento, ma in breve, i circuiti logici digitali sono al loro livello più elementare un circuito analogico. Quando la vostra linea di clock aumenta, lo stato della linea di input viene catturato e se quell'ingresso non è un livello alto o basso stabile in quel momento, un flip flop di campionamento può catturare un valore "intermedio" sconosciuto.

Come sapete, gli FPGA sono bestie digitali e non reagiscono bene a un segnale che non è né alto né basso. Peggio ancora, se quel valore indeterminato si fa strada oltre il flip flop di campionamento e nell'FPGA può causare tutti i tipi di stranezze poiché porzioni più grandi della logica ora vedono un valore indeterminato e cercano di dargli un senso.

La soluzione è sincronizzare il segnale. Al suo livello più elementare questo significa che usi una catena di infradito per catturare l'input. Qualsiasi livello metastabile che potrebbe essere stato catturato dal primo flip flop e che sia riuscito a risolverlo ha un'altra possibilità di essere risolto prima che colpisca la tua complessa logica. Due infradito sono in genere più che sufficienti per sincronizzare gli ingressi.

Un sincronizzatore di base è simile al seguente:

entity sync_2ff is
port (
    async_in : in std_logic;
    clk : in std_logic;
    rst : in std_logic;
    sync_out : out std_logic
);
end;

architecture a of sync_2ff is
begin

signal ff1, ff2: std_logic;

-- It's nice to let the synthesizer know what you're doing. Altera's way of doing it as follows:
ATTRIBUTE altera_attribute : string;
ATTRIBUTE altera_attribute OF ff1 : signal is "-name SYNCHRONIZER_IDENTIFICATION ""FORCED IF ASYNCHRONOUS""";
ATTRIBUTE altera_attribute OF a : architecture is "-name SDC_STATEMENT ""set_false_path -to *|sync_2ff:*|ff1 """;

-- also set the 'preserve' attribute to ff1 and ff2 so the synthesis tool doesn't optimize them away
ATTRIBUTE preserve: boolean;
ATTRIBUTE preserve OF ff1: signal IS true;
ATTRIBUTE preserve OF ff2: signal IS true;

synchronizer: process(clk, rst)
begin
if rst = '1' then
    ff1 <= '0';
    ff2 <= '0';
else if rising_edge(clk) then
    ff1 <= async_in;
    ff2 <= ff1;
    sync_out <= ff2;
end if;
end process synchronizer;
end sync_2ff;

Collega il pin fisico per la linea dati rx del controller N64 all'ingresso async_in del sincronizzatore e collega il segnale sync_out all'ingresso rxd del tuo UART.

I segnali non sincronizzati possono causare strani problemi. Assicurarsi che qualsiasi ingresso collegato a un elemento FPGA non sincronizzato con l'orologio del processo che legge il segnale sia sincronizzato. Questo include pulsanti, segnali UART 'rx' e 'cts' ... tutto ciò che non è sincronizzato con l'orologio che l'FPGA sta usando per campionare il segnale.

(A parte: ho scritto la pagina su www.mixdown.ca/n64dev molti anni fa. Mi sono appena reso conto che ho rotto il link quando ho aggiornato il sito per l'ultima volta e lo riparerò la mattina quando torno al computer. Non avevo idea che così tante persone usassero quella pagina!)


Grazie per l'ottima e completa risposta! Proverò questo e renderò la mia macchina più robusta.
Nick Williams,

2
In realtà ha ben poco a che fare con la metastabilità (anche se anche questa è una preoccupazione), e tutto ciò che ha a che fare con i diversi ritardi del percorso dall'ingresso asincrono ai vari FF che contengono i bit della variabile di stato.
Dave Tweed,

Hai ragione, @DaveTweed; Tendo a raggruppare i due insieme e questo è un pensiero sbagliato.
akohlsmith il

Ho modificato la mia risposta per tenere conto dei commenti di @ DaveTweed.
akohlsmith,

1
@akohlsmith Amazing! Ho aggiunto il sincronizzatore ed era la soluzione. Inoltre, è una coincidenza incredibile che tu abbia scritto la pagina di mixdown; Ho trovato un sacco di risorse sul protocollo N64 che faceva riferimento a quell'articolo e sono rimasto deluso dal collegamento interrotto. Grazie per averlo riparato.
Nick Williams,

6

Il tuo problema è che stai usando segnali non sincronizzati per prendere decisioni nella tua macchina a stati. Dovresti alimentare tutti quei segnali esterni attraverso i sincronizzatori a doppio FF prima di usarli nella macchina a stati.

È un problema sottile con le macchine a stati che può sorgere in qualsiasi transizione di stato che comporta una modifica a due o più bit nella variabile di stato. Se si utilizza un input non sincronizzato, uno dei bit potrebbe cambiare mentre l'altro non riesce a cambiare. Questo ti porta in uno stato diverso da quello previsto e potrebbe essere o meno uno stato legale.

L'ultima affermazione è il motivo per cui dovresti sempre avere anche un caso predefinito (in VHDL when others => ...) nella tua dichiarazione del caso della macchina a stati che ti porta da qualsiasi stato illegale a uno legale.


Sì, questa era la conclusione che stavo per isolare, ma non volevo saltare prima di ottenere abbastanza informazioni ...
travisbartley,

1
Accidenti, mi hai battuto. Lo biasimo nel scrivere tutto questo su un tablet. :-)
akohlsmith il

@akohlsmith, essere la pistola più veloce dell'est non è l'unica cosa che conta nel rispondere. La tua risposta è utile e ovviamente non è stata una fregatura da quando hai pubblicato così presto dopo questo.
travisbartley,

Pensavo che when others =>fosse d'aiuto, ma si scopre che non ti ottiene ciò che rivendichi (sotto qualsiasi sintetizzatore che ho usato) a meno che tu non aggiunga attributi per garantire che il synth capisca che vuoi una macchina a stati "sicura". Il comportamento normale consiste nell'ottimizzare su una rappresentazione one-hot e non fornire la logica di recupero. Vedere xilinx.com/support/answers/40093.html e synopsys.com/Company/Publications/SynopsysInsight/Pages/… per esempio.
Martin Thompson,

Wow! È un ottimo consiglio e ha funzionato come un fascino.
Nick Williams,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.