VHDL: Das Empfangsmodul schlägt beim Zählen der Bits zufällig fehl

9

Hintergrund

Dies ist ein persönliches Projekt; Beim Anschließen eines FPGA an einen N64 werden die vom FPGA empfangenen Bytewerte dann über UART an meinen Computer gesendet. Es funktioniert tatsächlich ziemlich gut! Zu zufälligen Zeiten fällt das Gerät leider aus und wird dann wiederhergestellt. Durch das Debuggen habe ich es geschafft, das Problem zu finden, aber ich bin ratlos, wie ich es beheben kann, weil ich mit VHDL ziemlich inkompetent bin.

Ich spiele jetzt seit ein paar Tagen mit der VHDL und bin möglicherweise nicht in der Lage, dieses Problem zu lösen.

Das Problem

Ich habe ein Oszilloskop, das das N64-Signal im FPGA misst, und der andere Kanal wird mit dem Ausgang des FPGA verbunden. Ich habe auch digitale Pins, die den Zählerwert aufzeichnen.

Im Wesentlichen sendet der N64 9 Datenbits, einschließlich eines STOP-Bits. Der Zähler zählt die empfangenen Datenbits und wenn ich 9 Bits erreiche, beginnt das FPGA mit der Übertragung über UART.

Hier ist das richtige Verhalten: Geben Sie hier die Bildbeschreibung ein

Das FPGA ist die blaue Wellenform und die orange Wellenform ist der Eingang des N64. Für die Dauer des Empfangs "hallt" mein FPGA das Signal des Eingangs zu Debugging-Zwecken. Nachdem das FPGA bis 9 gezählt hat, beginnt es mit der Übertragung der Daten über UART. Beachten Sie, dass die digitalen Pins bis 9 zählen und der FPGA-Ausgang unmittelbar nach Fertigstellung des N64 auf LOW geht.

Hier ist ein Beispiel für einen Fehler:

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass der Zähler die Bits 2 und 7 überspringt! Das FPGA erreicht das Ende und wartet auf das nächste Startbit vom N64, aber nichts. Das FPGA läuft also ab und erholt sich.

Dies ist die VHDL für das N64-Empfangsmodul. Es enthält den Zähler: 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;

Also irgendwelche Ideen? Debugging-Tipps? Tipps zum Codieren von Finite-State-Maschinen?

In der Zwischenzeit werde ich weiter damit herumspielen (ich werde es irgendwann haben)! Hilf mir, den Austausch zu stapeln, du bist meine einzige Hoffnung!

Bearbeiten

Eine weitere Entdeckung in meinem Debugging ist, dass die Zustände von waitForStart zurück zu waitForStop springen. Ich habe jedem Status einen Wert mit waitForStart gleich '5' und waitForStop gleich '4' gegeben. Siehe das Bild unten: Geben Sie hier die Bildbeschreibung ein

Nick Williams
quelle
1
In Ihrem ersten Fallblock gibt es die Zeile "s_bitCount <= X" 0 ";" Ist das X ein Tippfehler?
Travisbartley
@ trav1s Nein, dieses "X" bedeutet hexadezimal. X "0" ist also tatsächlich "0000" in Binärform.
Nick Williams
1
Ich habe ein paar Fehler beim Ausführen des Codes durch einen Linter. Die Signale N64RXD und tdre sollten nicht in der Sensitivitätsliste des sequentiellen Prozesses, Zeile 36, verwendet werden.
Travisbartley
1
@ trav1s Danke für den Zeiger, ich habe diese Parameter entfernt; Du hast recht, die sind nicht notwendig. Ich habe das Problem leider immer noch. Mit dem Oszilloskop habe ich Signale hinzugefügt, um festzustellen, in welchem ​​Zustand ich mich befinde. Aus irgendeinem Grund springt das FPGA vom "waitForStart" zurück zum "waitForStop" ohne dazwischen liegenden Zustand! Aus diesem Grund zählt es nicht, weil das FPGA nicht den Zustand erreicht, in dem es das Bit zählt. Der "Rücksprung" scheint das Problem zu sein.
Nick Williams
1
Der Übergang "waitForStart" -> "waitForStop" ist jedoch ungültig. Es gibt keine Möglichkeit, diesen Sprung in einem einzigen Zyklus zu machen. Überprüfen Sie sehr genau, ob dazwischen kein sehr kurzer Zustand vorliegt. Andernfalls muss ein Hardware- / Timing-Fehler vorliegen.
Travisbartley

Antworten:

9

Ich sehe keinen Synchronisierer in der Empfangsdatenleitung.

Alle asynchronen Eingänge müssen mit dem Abtasttakt synchronisiert sein. Dafür gibt es mehrere Gründe: Metastabilität und Routing. Dies sind verschiedene Probleme, die jedoch miteinander zusammenhängen.

Es braucht Zeit, bis sich Signale durch die FPGA-Struktur ausbreiten. Das Taktnetzwerk im FPGA ist so ausgelegt, dass diese "Reise" -Verzögerungen ausgeglichen werden, sodass alle Flip-Flops im FPGA die Uhr genau zum gleichen Zeitpunkt sehen. Das normale Routing-Netzwerk hat dies nicht und stützt sich stattdessen auf die Regel, dass alle Signale für eine kurze Zeit stabil sein müssen, bevor sich die Uhr ändert, und für eine kurze Zeit stabil bleiben müssen, nachdem sich die Uhr ändert. Diese kleinen Zeitabschnitte werden als Setup- und Haltezeiten für ein bestimmtes Flip-Flop bezeichnet. Die Orts- und Routenkomponente der Toolchain hat ein sehr gutes Verständnis der Routing-Verzögerungen für das jeweilige Gerät und geht davon aus, dass ein Signal die Setup- und Haltezeiten der Flip-Flops im FPGA nicht verletzt.

Wenn Sie Signale haben, die nicht mit dem Abtasttakt synchronisiert sind, kann dies dazu führen, dass ein Flip-Flop den "alten" Wert eines Signals sieht, da der neue Wert keine Zeit hatte, sich zu verbreiten. Jetzt befinden Sie sich in einer unerwünschten Situation, in der die Logik, die dasselbe Signal betrachtet, zwei unterschiedliche Werte sieht. Dies kann zu Fehlbedienungen, abgestürzten Zustandsmaschinen und allen Arten von schwer zu diagnostizierenden Schäden führen.

Der andere Grund, warum Sie alle Ihre Eingangssignale synchronisieren müssen, ist die sogenannte Metastabilität. Es gibt Bände zu diesem Thema, aber kurz gesagt ist die digitale Logikschaltung auf ihrer grundlegendsten Ebene eine analoge Schaltung. Wenn Ihre Taktleitung ansteigt, wird der Zustand der Eingangsleitung erfasst, und wenn dieser Eingang zu diesem Zeitpunkt keinen stabilen hohen oder niedrigen Pegel aufweist, kann ein unbekannter "Zwischen" -Wert vom Abtast-Flipflop erfasst werden.

Wie Sie wissen, sind FPGAs digitale Biester und reagieren nicht gut auf ein Signal, das weder hoch noch niedrig ist. Schlimmer noch, wenn dieser unbestimmte Wert über das Sampling-Flip-Flop hinaus in das FPGA gelangt, kann er alle Arten von Verrücktheit verursachen, da größere Teile der Logik jetzt einen unbestimmten Wert sehen und versuchen, ihn zu verstehen.

Die Lösung besteht darin, das Signal zu synchronisieren. Im einfachsten Fall bedeutet dies, dass Sie eine Kette von Flip-Flops verwenden, um den Eingang zu erfassen. Jedes metastabile Level, das möglicherweise vom ersten Flip-Flop erfasst und erkannt wurde, erhält eine weitere Chance, gelöst zu werden, bevor es auf Ihre komplexe Logik trifft. Zwei Flip-Flops sind normalerweise mehr als ausreichend, um Eingänge zu synchronisieren.

Ein grundlegender Synchronisierer sieht folgendermaßen aus:

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;

Verbinden Sie den physischen Pin für die Empfangsdatenleitung des N64-Controllers mit dem async_in-Eingang des Synchronisierers und verbinden Sie das sync_out-Signal mit dem rxd-Eingang Ihres UART.

Nicht synchronisierte Signale können seltsame Probleme verursachen. Stellen Sie sicher, dass alle Eingänge, die an ein FPGA-Element angeschlossen sind, das nicht mit der Uhr des Prozesses synchronisiert ist, der das Signal liest, synchronisiert sind. Dies umfasst Drucktasten, UART-Signale 'rx' und 'cts' ... alles, was nicht mit der Uhr synchronisiert ist, mit der das FPGA das Signal abtastet.

( Nebenbei : Ich habe die Seite vor vielen Jahren unter www.mixdown.ca/n64dev geschrieben. Ich habe gerade festgestellt, dass ich den Link bei der letzten Aktualisierung der Website unterbrochen habe und ihn am Morgen reparieren werde, wenn ich wieder an einem Computer bin. Ich hatte keine Ahnung, dass so viele Leute diese Seite benutzt haben!)

Akohlsmith
quelle
Vielen Dank für die tolle und umfassende Antwort! Ich werde es versuchen und meine Maschine robuster machen.
Nick Williams
2
Es hat eigentlich sehr wenig mit Metastabilität zu tun (obwohl dies auch ein Problem ist) und alles mit den unterschiedlichen Pfadverzögerungen von der asynchronen Eingabe zu den verschiedenen FFs, die die Bits der Zustandsvariablen enthalten.
Dave Tweed
Du hast recht, @DaveTweed; Ich neige dazu, die beiden zusammenzufassen, und das ist falsches Denken.
Akohlsmith
Ich habe meine Antwort bearbeitet, um die Kommentare von @ DaveTweed zu berücksichtigen.
Akohlsmith
1
@akohlsmith Erstaunlich! Ich habe den Synchronizer hinzugefügt und es war die Lösung. Es ist auch ein unglaublicher Zufall, dass Sie die Mixdown-Seite geschrieben haben. Ich habe eine Reihe von Ressourcen im N64-Protokoll gefunden, die auf diesen Artikel verweisen, und ich war enttäuscht, dass der Link unterbrochen wurde. Vielen Dank für die Behebung.
Nick Williams
6

Ihr Problem ist, dass Sie nicht synchronisierte Signale verwenden, um Entscheidungen in Ihrer Zustandsmaschine zu treffen. Sie sollten alle diese externen Signale über Doppel-FF-Synchronisierer speisen, bevor Sie sie in der Zustandsmaschine verwenden.

Es ist ein subtiles Problem bei Zustandsautomaten, das bei jedem Zustandsübergang auftreten kann, bei dem zwei oder mehr Bits in der Zustandsvariablen geändert werden. Wenn Sie einen nicht synchronisierten Eingang verwenden, ändert sich möglicherweise eines der Bits, während sich das andere nicht ändert. Dies führt Sie in einen anderen als den beabsichtigten Zustand, und es kann sich um einen legalen Zustand handeln oder nicht.

Diese letzte Aussage ist der Grund, warum Sie when others => ...in Ihrer State-Machine-Case-Anweisung immer einen Standardfall (in VHDL ) haben sollten , der Sie von einem illegalen zu einem legalen Staat führt.

Dave Tweed
quelle
Ja, das war die Schlussfolgerung, die ich isolieren wollte, aber ich wollte nicht
dorthin
1
Verdammt, du hast mich geschlagen. Ich beschuldige es, dass ich das alles auf einem Tablet abgetippt habe. :-)
Akohlsmith
@akohlsmith, die schnellste Waffe im Osten zu sein, ist nicht das einzige, was bei der Beantwortung zählt. Ihre Antwort ist nützlich und hat offensichtlich nicht geschummelt, da Sie so bald nach dieser gepostet haben.
Travisbartley
Früher dachte ich, when others =>das würde helfen, aber es stellt sich heraus, dass Sie nicht das bekommen, was Sie behaupten (unter jedem Synthesizer, den ich verwendet habe), es sei denn, Sie fügen Attribute hinzu, um sicherzustellen, dass der Synthesizer versteht, dass Sie eine "sichere" Zustandsmaschine wollen. Das normale Verhalten besteht darin, auf eine One-Hot-Darstellung zu optimieren und keine Wiederherstellungslogik bereitzustellen. Siehe beispielsweise xilinx.com/support/answers/40093.html und synopsys.com/Company/Publications/SynopsysInsight/Pages/… .
Martin Thompson
Wow! Das ist ein toller Tipp und es hat wie ein Zauber funktioniert.
Nick Williams