VHDL: moduł odbiorczy losowo kończy się niepowodzeniem podczas zliczania bitów

9

tło

To jest osobisty projekt; dotyczy podłączania FPGA do N64, wartości bajtów, które odbiera FPGA są następnie przesyłane przez UART do mojego komputera. W rzeczywistości działa całkiem dobrze! Niestety w przypadkowych przypadkach urządzenie ulegnie awarii, a następnie wyzdrowieje. Podczas debugowania udało mi się znaleźć problem, ale jestem zaskoczony, jak to naprawić, ponieważ jestem dość niekompetentny w VHDL.

Bawiam się VHDL od kilku dni i być może nie będę w stanie rozwiązać tego.

Problem

Mam oscyloskop mierzący sygnał N64 do FPGA, a drugi kanał łączy się z wyjściem FPGA. Mam też cyfrowe piny rejestrujące wartość licznika.

Zasadniczo N64 wysyła 9 bitów danych, w tym bit STOP. Licznik zlicza odebrane bity danych, a kiedy osiągnę 9 bitów, FPGA rozpoczyna transmisję przez UART.

Oto prawidłowe zachowanie: wprowadź opis zdjęcia tutaj

FPGA to niebieski przebieg, a pomarańczowy przebieg to wejście N64. Na czas odbierania moje FPGA „echo” sygnał wejścia do celów debugowania. Gdy FPGA policzy do 9, zaczyna transmitować dane przez UART. Zauważ, że cyfrowe styki liczą do 9, a wyjście FPGA spada NISKIE zaraz po zakończeniu N64.

Oto przykład awarii:

wprowadź opis zdjęcia tutaj

Zauważ, że licznik pomija bity 2 i 7! FPGA osiąga koniec, czekając na następny bit startowy z N64, ale nic. Tak więc FPGA kończy się i odzyskuje.

Jest to VHDL dla modułu odbioru N64. Zawiera licznik: 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;

Jakieś pomysły? Wskazówki dotyczące debugowania? Wskazówki na temat kodowania automatów skończonych?

W międzyczasie będę się z tym bawił (w końcu to zrobię)! Pomóż mi Stack Exchange, jesteś moją jedyną nadzieją!

Edytować

Kolejne odkrycie w moim debugowaniu, stany przeskoczą z waitForStart z powrotem do waitForStop. Dałem każdemu stanowi wartość z waitForStart równą „5” i waitForStop równą „4”. Zobacz zdjęcie poniżej: wprowadź opis zdjęcia tutaj

Nick Williams
źródło
1
W pierwszym bloku przypadku jest wiersz „s_bitCount <= X” 0 ”;„ Czy to X literówka?
travisbartley
@ trav1s Nie, że „X” oznacza liczbę szesnastkową. Zatem X „0” jest w rzeczywistości „0000” w systemie binarnym.
Nick Williams
1
Mam kilka błędów podczas uruchamiania kodu przez linijkę. Sygnały N64RXD i tdre nie powinny być używane na liście czułości procesu sekwencyjnego, wiersz 36.
travisbartley
1
@ trav1s Dzięki za wskaźnik usunąłem te parametry; masz rację, te nie są konieczne. Niestety nadal mam problem. W tym zakresie dodałem sygnały, aby wykryć, w jakim jestem stanie. Z jakiegoś powodu FPGA przeskakuje z „waitForStart” z powrotem do „waitForStop” bez żadnego stanu pośredniego! Dlatego się nie liczy, ponieważ FPGA nie osiąga stanu, w którym liczy bit. Problemem wydaje się „przeskakiwanie”.
Nick Williams
1
Ale przejście „waitForStart” -> „waitForStop” jest nieprawidłowe. Nie ma sposobu, aby wykonać ten skok w jednym cyklu. Sprawdź bardzo dokładnie, aby upewnić się, że nie ma pomiędzy nimi krótkiego stanu. W przeciwnym razie musi wystąpić usterka sprzętowa / synchronizacji.
travisbartley

Odpowiedzi:

9

Nie widzę synchronizatora na linii danych rx.

Wszystkie wejścia asynchroniczne muszą być zsynchronizowane z zegarem próbkowania. Istnieje kilka powodów: metastabilność i routing. Są to różne problemy, ale są ze sobą powiązane.

Rozpłynięcie się sygnałów w strukturze FPGA zajmuje trochę czasu. Sieć zegara w układzie FPGA została zaprojektowana w celu zrekompensowania opóźnień „podróży”, dzięki czemu wszystkie klapki w układzie FPGA widzą zegar dokładnie w tym samym momencie. Normalna sieć routingu nie ma tego i zamiast tego opiera się na zasadzie, że wszystkie sygnały muszą być stabilne przez pewien czas przed zmianą zegara i pozostać stabilne przez pewien czas po zmianie zegara. Te małe odcinki czasu są znane jako czasy przygotowania i wstrzymania dla danego przerzutnika. Element miejsca i trasy zestawu narzędzi ma bardzo dobre zrozumienie opóźnień routingu dla określonego urządzenia i przyjmuje podstawowe założenie, że sygnał nie narusza czasów ustawiania i utrzymywania przerzutników w układzie FPGA.

Kiedy masz sygnały, które nie są zsynchronizowane z zegarem próbkowania, możesz skończyć w sytuacji, gdy jeden przerzutnik widzi „starą” wartość sygnału, ponieważ nowa wartość nie miała czasu na propagację. Teraz znajdujesz się w niepożądanej sytuacji, w której logika patrząc na ten sam sygnał widzi dwie różne wartości. Może to powodować nieprawidłowe działanie, awarie maszyn stanu i wszelkiego rodzaju trudne do zdiagnozowania spustoszenie.

Innym powodem synchronizacji wszystkich sygnałów wejściowych jest metastabilność. Na ten temat napisane są tomy, ale w skrócie, cyfrowy układ logiczny jest na najbardziej podstawowym poziomie obwodem analogowym. Kiedy linia zegara rośnie, przechwytywany jest stan linii wejściowej, a jeśli sygnał wejściowy nie jest w tym czasie stabilnym wysokim lub niskim poziomem, nieznana wartość pośrednia może zostać przechwycona przez przerzutnik próbkujący.

Jak wiecie, FPGA to cyfrowe bestie i nie reagują dobrze na sygnał, który nie jest ani wysoki ani niski. Co gorsza, jeśli ta nieokreślona wartość przebije się przez próbny przerzutnik do FPGA, może powodować różnego rodzaju dziwności, ponieważ większe części logiki widzą teraz nieokreśloną wartość i starają się ją zrozumieć.

Rozwiązaniem jest zsynchronizowanie sygnału. Na najbardziej podstawowym poziomie oznacza to, że używasz łańcucha klap do przechwytywania danych wejściowych. Każdy metastabilny poziom, który mógł zostać przechwycony przez pierwszy flip flop i udało mu się go wydostać, ma kolejną szansę na rozwiązanie, zanim trafi on w twoją złożoną logikę. Dwa przerzutniki są zwykle więcej niż wystarczające do synchronizacji wejść.

Podstawowy synchronizator wygląda następująco:

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;

Podłącz fizyczny pin linii danych rx kontrolera N64 do wejścia async_in synchronizatora i podłącz sygnał sync_out do wejścia rxd UART.

Niezsynchronizowane sygnały mogą powodować dziwne problemy. Upewnij się, że każde wejście podłączone do elementu FPGA, który nie jest zsynchronizowany z zegarem procesu odczytującego sygnał, jest zsynchronizowane. Obejmuje to przyciski, sygnały UART „rx” i „cts” ... wszystko, co nie jest zsynchronizowane z zegarem używanym przez FPGA do próbkowania sygnału.

(Na marginesie: wiele lat temu napisałem stronę na www.mixdown.ca/n64dev . Właśnie zdałem sobie sprawę, że zepsułem link, kiedy ostatnio aktualizowałem witrynę i naprawię go rano, kiedy wrócę do komputera. Nie miałem pojęcia, że ​​tylu ludzi korzysta z tej strony!)

akohlsmith
źródło
Dzięki za świetną i kompleksową odpowiedź! Spróbuję to i uczynię moją maszynę bardziej niezawodną.
Nick Williams,
2
W rzeczywistości ma bardzo mało wspólnego z metastabilnością (choć to również stanowi problem), a wszystko ma związek z różnymi opóźnieniami ścieżki od asynchronicznego wejścia do różnych FF, które przechowują bity zmiennej stanu.
Dave Tweed
Masz rację, @DaveTweed; Mam tendencję do zlepiania tych dwóch razem i to jest błędne myślenie.
akohlsmith,
Zredagowałem swoją odpowiedź, aby uwzględnić komentarze @ DaveTweed.
akohlsmith,
1
@akohlsmith Amazing! Dodałem synchronizator i to było rozwiązanie. To także niesamowity zbieg okoliczności, że napisałeś stronę miksowania; Znalazłem wiele zasobów w protokole N64, które odwoływały się do tego artykułu i byłem rozczarowany, że łącze zostało zerwane. Dzięki za naprawę.
Nick Williams
6

Problem polega na tym, że używasz niezsynchronizowanych sygnałów do podejmowania decyzji w maszynie stanów. Powinieneś podawać wszystkie te sygnały zewnętrzne przez synchronizatory podwójnego FF przed użyciem ich w maszynie stanu.

Jest to subtelny problem z automatami stanów, który może powstać przy każdej zmianie stanu, która wymaga zmiany dwóch lub więcej bitów w zmiennej stanu. Jeśli użyjesz niezsynchronizowanego wejścia, jeden z bitów może się zmienić, a drugi nie. Spowoduje to przejście do innego stanu niż zamierzony i może być lub nie być stanem prawnym.

To ostatnie stwierdzenie jest powodem, dla którego powinieneś zawsze mieć domyślną wielkość liter (w języku VHDL when others => ...) w swojej instrukcji stanu maszyny stanów, która przenosi cię z dowolnego nielegalnego stanu do legalnego.

Dave Tweed
źródło
Tak, to był wniosek, który chciałem wyodrębnić, ale nie chciałem do niego przeskakiwać przed uzyskaniem wystarczającej ilości informacji ...
travisbartley
1
Cholera, pobiłeś mnie do tego. Obwiniam to za pisanie tego wszystkiego na tablecie. :-)
akohlsmith
@akohlsmith, bycie najszybszym działem na wschodzie nie jest jedyną rzeczą, która ma znaczenie w odpowiedzi. Twoja odpowiedź jest przydatna i oczywiście nie oszukuje, odkąd napisałeś tak szybko po tym.
travisbartley
Kiedyś myślałem, że when others =>to pomaga, ale okazuje się, że nie dostaniesz tego, co twierdzisz (w żadnym używanym przeze mnie syntezatorze), chyba że dodasz atrybuty, aby upewnić się, że syntezator rozumie, że potrzebujesz „bezpiecznej” maszyny stanów. Normalne zachowanie polega na optymalizacji do reprezentacji typu „one-hot” i nie zapewnia logiki odzyskiwania. Zobacz na przykład xilinx.com/support/answers/40093.html i synopsys.com/Company/Publications/SynopsysInsight/Pages/ ...
Martin Thompson,
Łał! To świetna wskazówka i działało jak urok.
Nick Williams