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:
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:
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:
Odpowiedzi:
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:
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!)
źródło
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.źródło
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/ ...