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

Hintergrund

Dies ist ein persönliches Projekt; Es betrifft das Anschließen eines FPGA an einen N64. Die Bytewerte, die das FPGA empfängt, werden dann über UART an meinen Computer gesendet. Funktioniert eigentlich ganz gut! Zu zufälligen Zeiten fällt das Gerät leider aus und wird dann wiederhergestellt. Durch 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 dem VHDL und bin möglicherweise nicht in der Lage, das Problem zu lösen.

Das Problem

Ich habe ein Oszilloskop, das das N64-Signal im FPGA misst, und der andere Kanal ist 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 Bit 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 orangefarbene Wellenform ist der Eingang des N64. Für die Dauer des Empfangs "echos" 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 sofort nach Beendigung 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 zur Codierung von endlichen Zustandsautomaten?

In der Zwischenzeit werde ich weiter damit herumspielen (ich werde es irgendwann haben)! Helfen Sie mir Stack Exchange, Sie sind meine einzige Hoffnung!

Bearbeiten

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

In Ihrem ersten Case-Block gibt es die Zeile "s_bitCount <= X"0";" Ist das X ein Tippfehler?
@trav1s Nein, das "X" bedeutet hexadezimal. X"0" ist also binär "0000".
Okay, ich bin eher ein Verilog-Mensch. Nicht besonders im Zusammenhang mit der Lösung des Problems, aber ich bin neugierig, was Sie mit dem N64 machen? ROM lesen? Einen Emulator bauen?
@trav1s Ich verstehe, mir wurde VHDL beigebracht, also bleibe ich dabei. Ich baue einen BT N64-Controller mit dem FPGA als Dongle-Seite (ich wollte nicht mit einer MCU schlagen). Die BT-Module, die ich mir ansehe, verwenden UART, weshalb ich dieses Protokoll verwende.
Könnten Sie nach Möglichkeit zeigen, was Sie oben haben, aber einschließlich aller E / A und internen Register? Die Anzahl der Signale ist nicht zu hoch und hilft wirklich, den Fehler zu finden.
@trav1s Es ist nicht viel, nur ein UART TX-Modul und einige andere sehr kleine Module. Was die E / A betrifft, sind es nur ein paar Pins für den Eingang und den UART-Ausgang. Ich habe die Ursache bereits auf die N64RX-Komponente eingegrenzt.
Ich verstehe. Es scheint mehr als ein Signal zu geben, das diesen Fehler verursachen könnte, wenn ihre Steuerlogik fehlerhaft ist. Wir wissen, dass s_bitCount ein Problem hat, aber wir wissen nicht, wo in der Steuerlogik es schief geht. Der Hauptverdächtige könnte das Staatsregister sein, aber wenn wir den Code verfolgen könnten, während alle Signale beobachtbar wären, wäre das Problem offensichtlich.
Das Beobachten aller Signale über das Oszilloskop ist möglicherweise unmöglich, daher müssen Sie möglicherweise einen Test in der Simulation erstellen, der den Fehler reproduziert.
@trav1s Ich kann versuchen, es ein paar Mal zu simulieren, aber ich habe das dort produzierte Problem nicht gesehen. Ich werde es versuchen. Danke für den bisherigen Einblick!
Ich habe ein paar Fehler beim Ausführen des Codes durch einen Linter. Die Signale N64RXD und tdre sollten nicht in der Empfindlichkeitsliste des sequentiellen Prozesses, Zeile 36, verwendet werden.
Kannst du die Uhr auf dem Display anzeigen lassen? Ich hatte keine Zeit, Ihren Code zu lesen, aber wenn ich mir anschaue, wie langsam das LSB reagiert, denke ich, dass Sie zu wenig abtasten. Ich habe eine 25-MHz-Taktleitung gesehen – das bedeutet, dass das schnellste Signal, das Sie abtasten können, 12,5 MBit/s beträgt .
@trav1s - Sensitivitätslisten sind in der Synthese falsch. ;)
@trav1s Danke für den Hinweis, ich habe diese Parameter entfernt; du hast recht, die sind nicht nötig. Ich habe das Problem leider immer noch. Mit dem Oszilloskop habe ich Signale hinzugefügt, um zu erkennen, in welchem ​​​​Zustand ich mich befinde. Aus irgendeinem Grund springt das FPGA von „waitForStart“ zurück zu „waitForStop“, ohne dass ein Zustand dazwischen liegt! Aus diesem Grund zählt es nicht, weil das FPGA den Zustand nicht erreicht, in dem es das Bit zählt. Der "Rücksprung" scheint das Problem zu sein.
Werfen Sie tdre auf Ihre Wellenformanzeige und sehen Sie, was es tut. Fragen Sie sich, was die Ausgabe stören kann.
@AaronD.Marasco Hallo Aaron, die N64-Bitlänge beträgt 1 us gegenüber dem 25-MHz-Takt. Ich kann die Abtastung auf 50 MHz erhöhen, aber ich glaube, dass es irgendein Problem mit meiner Zustandsmaschine gibt, ich habe einige Details in meinen vorherigen Kommentar aufgenommen.
Aber der Übergang "waitForStart" -> "waitForStop" ist ungültig. Es gibt keine Möglichkeit, diesen Sprung in einem einzigen Zyklus zu machen. Überprüfen Sie sehr genau, ob es keinen sehr kurzen Zustand dazwischen gibt. Andernfalls muss ein Hardware-/Timing-Fehler vorliegen.
@DavidKoontz Das tdre ist eine Eingabe vom UART TX, um meinem N64RX mitzuteilen, wann es für eine weitere Übertragung bereit ist. Ich habe diesen UART bei 115200 angekurbelt, also hat er viel Headroom. Der N64 gibt 1,6 ms lang kein weiteres Signal aus und ich sende nur ein Byte.
@trav1s Ich war genauso überrascht, ich sehe keinen Übergang zwischen den beiden, also bin ich ein wenig ratlos, wie das passiert ist.
@NickWilliams Ja, aber woher kommt tdre? Ist es impulsgefiltert oder anderweitig von der Uhr abgeleitet, die Ihre Zustandsmaschine betreibt? Wenn es nicht gefiltert ist, könnten Sie unter Rauschen leiden, das zu schnell mit Ihrem Zielfernrohr zu sehen ist. Stellen Sie sich zusätzlich zu einem Designproblem ein Hardwareproblem vor.
@DavidKoontz, wenn tdre aus der Sensitivitätsliste entfernt wurde, jagen Sie einen Ablenkungsmanöver. Dieses Signal ist beim fehlerhaften Zustandsübergang einfach nicht relevant.
@NickWilliams, welche anderen Informationen haben Sie über den Fehler? Können Sie es jedes Mal reproduzieren, oder passiert es zufällig und unvorhersehbar?
@trav1s Der "Rücksprung" für die Staaten tritt jedes Mal auf, wenn ich das Problem sehe. Ich habe in meiner Antwort einen Screenshot gepostet, damit Sie wissen, dass ich nicht verrückt bin! Danke für die bisherige Hilfe.
@travis Signalintegrität. Nichts mit Sensitivitätslisten zu tun. Denken Sie an Setup-Zeitprobleme für den Zustand.

Antworten (2)

Ich sehe keinen Synchronisierer auf der RX-Datenleitung.

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

Signale benötigen Zeit, um sich durch das FPGA-Fabric auszubreiten. Das Taktnetzwerk innerhalb des FPGA ist so ausgelegt, dass es diese "Reise"-Verzögerungen kompensiert, sodass alle Flip-Flops innerhalb des FPGA die Uhr im exakt gleichen Moment sehen. Das normale Routing-Netzwerk hat dies nicht und verlässt 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, nachdem die Uhr geändert wird. Diese kleinen Zeiteinheiten sind als Setup- und Hold-Zeiten für ein bestimmtes Flip-Flop bekannt. Die Place-and-Route-Komponente der Toolchain hat ein sehr gutes Verständnis der Routing-Verzögerungen für das spezifische Gerät und geht von der Grundannahme aus, dass ein Signal die Setup- und Hold-Zeiten der Flip-Flops im FPGA nicht verletzt.

Wenn Sie Signale haben, die nicht mit dem Abtasttakt synchronisiert sind, können Sie in eine Situation geraten, in der ein Flipflop den "alten" Wert eines Signals sieht, da der neue Wert keine Zeit hatte, sich fortzupflanzen. Jetzt befinden Sie sich in der unerwünschten Situation, dass 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, digitale Logikschaltungen sind 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 hat, kann ein unbekannter „Zwischen“-Wert vom Abtast-Flip-Flop erfasst werden.

Wie Sie wissen, sind FPGAs digitale Bestien und reagieren nicht gut auf ein Signal, das weder hoch noch niedrig ist. Schlimmer noch, wenn dieser unbestimmte Wert am Abtast-Flip-Flop vorbei und in das FPGA gelangt, kann er alle möglichen Verrücktheiten verursachen, da größere Teile der Logik jetzt einen unbestimmten Wert sehen und versuchen, einen Sinn daraus zu machen.

Die Lösung besteht darin, das Signal zu synchronisieren. Auf der einfachsten Ebene bedeutet dies, dass Sie eine Kette von Flip-Flops verwenden, um die Eingabe zu erfassen. Jede metastabile Ebene, die möglicherweise vom ersten Flip-Flop erfasst und herausgefunden wurde, erhält eine weitere Chance, aufgelöst zu werden, bevor sie auf Ihre komplexe Logik trifft. Zwei Flip-Flops sind normalerweise mehr als ausreichend, um Eingänge zu synchronisieren.

Ein einfacher 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 rx-Datenleitung des N64-Controllers mit dem async_in-Eingang des Synchronizers 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 jeder Eingang, der mit einem FPGA-Element verbunden ist, das nicht mit der Uhr des Prozesses synchronisiert ist, der das Signal liest, synchronisiert ist. Dazu gehören Drucktasten, UART-'rx'- und 'cts'-Signale ... alles, was nicht mit dem Takt synchronisiert ist, den das FPGA verwendet, um das Signal abzutasten.

(Nebenbei: Ich habe die Seite unter www.mixdown.ca/n64dev vor vielen Jahren geschrieben. Ich habe gerade festgestellt, dass ich den Link unterbrochen habe, als ich die Seite das letzte Mal aktualisiert habe, und werde ihn morgen früh reparieren, wenn ich wieder an einem Computer sitze. Ich hatte keine Ahnung, dass so viele Leute diese Seite benutzen!)

Danke für die tolle und umfassende Antwort! Ich werde das ausprobieren und meine Maschine robuster machen.
Es hat tatsächlich sehr wenig mit Metastabilität zu tun (obwohl das auch ein Problem ist) und alles mit den unterschiedlichen Pfadverzögerungen vom asynchronen Eingang zu den verschiedenen FFs zu tun, die die Bits der Zustandsvariablen enthalten.
Du hast recht, @DaveTweed; Ich neige dazu, beides in einen Topf zu werfen, und das ist falsch gedacht.
Ich habe meine Antwort bearbeitet, um die Kommentare von @ DaveTweed zu berücksichtigen.
@akohlsmith Erstaunlich! Ich habe den Synchronizer hinzugefügt und es war die Lösung. Außerdem ist es ein unglaublicher Zufall, dass du die Mixdown-Seite geschrieben hast; Ich habe eine Reihe von Ressourcen zum N64-Protokoll gefunden, die auf diesen Artikel verwiesen, und ich war enttäuscht, dass der Link nicht funktionierte. Danke, dass du es behoben hast.

Ihr Problem ist, dass Sie unsynchronisierte Signale verwenden, um Entscheidungen in Ihrer Zustandsmaschine zu treffen. Sie sollten alle diese externen Signale durch Doppel-FF-Synchronisierer leiten, bevor Sie sie in der Zustandsmaschine verwenden.

Es ist ein subtiles Problem mit Zustandsmaschinen, das bei jedem Zustandsübergang auftreten kann, der eine Änderung an zwei oder mehr Bits in der Zustandsvariablen beinhaltet. Wenn Sie einen nicht synchronisierten Eingang verwenden, kann sich eines der Bits ändern, während das andere sich nicht ändert. Dies bringt Sie in einen anderen Staat als den beabsichtigten, und es kann sich um einen Rechtsstaat handeln oder auch nicht.

Diese letzte Anweisung ist der Grund, warum Sie auch immer einen Standardfall (in VHDL, when others => ...) in Ihrer Case-Anweisung für die Zustandsmaschine haben sollten, der Sie von einem illegalen Zustand in einen legalen Zustand bringt.

Ja, das war die Schlussfolgerung, die ich isolieren wollte, aber ich wollte nicht zu ihr springen, bevor ich genug Informationen bekommen habe ...
Früher dachte ich, das when others =>sei hilfreich, aber es stellt sich heraus, dass es Ihnen nicht das bringt, 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 zum Beispiel xilinx.com/support/answers/40093.html und synopsys.com/Company/Publications/SynopsysInsight/Pages/… .
Wow! Das ist ein toller Tipp und es hat wie ein Zauber funktioniert.