Meine Verilog-Implementierung des seriellen Empfängers verhält sich nicht wie erwartet

Ich habe meine eigene Implementierung eines seriellen Empfängers codiert. Es funktioniert für eingehende Daten mit einer Baudrate von 115200.

Hier ist mein Code:

module my_serial_receiver(
    input clk,
    input reset_n,
    input Rx,
    output reg [7:0] received_byte,
    output reg byte_ready
);

parameter IDLE = 4'd0, BIT_0 = 4'd1, BIT_1 = 4'd2,
                BIT_2 = 4'd3, BIT_3 = 4'd4, BIT_4 = 4'd5, BIT_5 = 4'd6, 
                BIT_6 = 4'd7, BIT_7 = 4'd8, BYTE_READY = 4'd9;

reg [3:0] state = 0;
reg [7:0] baud_rate_clock = 0;

always @(posedge clk) begin
  if (baud_rate_clock == 8'd216) begin
    baud_rate_clock <= 0;
  end
  else begin
    baud_rate_clock <= baud_rate_clock + 8'd1;
  end
end

assign baud_tick = ~|baud_rate_clock;

always @(posedge baud_tick or negedge reset_n) begin
    if (~reset_n) begin
        state <= 0;
        byte_ready <= 0;
    end
    case(state)
        IDLE: begin
             byte_ready <= 0;
             if (Rx == 0) state <= BIT_0;
        end
        BIT_0: begin
           received_byte[0] <= Rx;
           state <= BIT_1;
        end
        BIT_1: begin
             received_byte[1] <= Rx;
             state <= BIT_2;
        end
        BIT_2: begin
             received_byte[2] <= Rx;
             state <= BIT_3;
        end
        BIT_3: begin
             received_byte[3] <= Rx;
             state <= BIT_4;
        end
        BIT_4: begin
             received_byte[4] <= Rx;
             state <= BIT_5;
        end
        BIT_5: begin
             received_byte[5] <= Rx;
             state <= BIT_6;
        end
        BIT_6: begin
             received_byte[6] <= Rx;
             state <= BIT_7;
        end
        BIT_7: begin
             received_byte[7] <= Rx;
             state <= BYTE_READY;
        end
        BYTE_READY: begin
             byte_ready <= 1;
             state <= IDLE;
        end
        default: state <= IDLE;
    endcase
end      


endmodule

Hier meine Simulationsergebnisse :

Simulationsergebnisse

Also im Grunde reagiert die Simulation genau so, wie ich es will.

Ich habe 0x55, 0x74 und 0x40 gesendet, und dann wird byte_ready behauptet, also weiß ich, dass das richtig ist.

Darüber hinaus wird mein baud_tick-Signal 115200 Mal pro Sekunde richtig bestätigt.

Insgesamt scheint meine Simulation perfekt zu sein.

Wenn ich das FPGA tatsächlich programmiere und die Rx-Leitung anschließe, passiert nichts (wie in, keine LEDs blinken). Ich habe es so codiert, dass die 8 LEDs die Werte der 8 Bits des Bytes annehmen. Ich habe diese Methode zuvor mit der Implementierung einer anderen Person getestet und sie hat perfekt zum Testen des Moduls funktioniert.

Ich habe sogar den SignalTap-Logikanalysator verwendet und bestätigt, dass die eingehenden Rx-Daten korrekt sind und die Baudrate mit einem Fehler von + oder - 0,5% korrekt ist.

Ich habe keine Ahnung, was ich von hier aus tun soll. Irgendwelche Vorschläge?

Es wäre hilfreich, wenn Sie zumindest einen Teil des Rx-Codes posten würden. Sie sagen, signaltap zeigt, dass die Rx-Daten korrekt sind, aber "nichts" passiert. Was bedeutet das? Was zeigen die LEDs an, die Werte der empfangenen Bytes oder eine laufende Zählung der empfangenen Bytes?
@Ciano Danke, ich habe die Frage bearbeitet. Mir wurde klar, dass der Code in ein Bildlauffeld eingefügt wird, also ist das gut.
Wenn das oberste Modul es instanziiert, könnte es helfen, wenn Sie es posten. Übrigens hat der Code ein Problem, nämlich dass er die Bits in der Mitte nicht abtastet, aber das hat nichts damit zu tun, dass die LEDs überhaupt nicht blinken.

Antworten (2)

FPGA-Code funktioniert in der Verhaltenssimulation, aber nicht in der Hardware

Dies passiert manchmal. Vielleicht stimmt also etwas mit der Hardware nicht, und vielleicht bildet die Simulation nicht genau ab, was in der Realität passiert. Muss beides prüfen.

Ich gehe davon aus, dass Sie ein Student sind, da es kostenlosen vorgefertigten Code gibt, der bereits serielle UARTs implementiert. Meine Antwort lautet also, wie Sie diese Art von Problem debuggen können.

1. Ist die Synthese wirklich gelungen?

Gibt es beim Erstellen des Projekts Diagnose- oder Warnmeldungen? Ist der synthetisierte Code sinnvoll?

Untersuchen Sie den Post-Übersetzungs-/Post-Synthese-Code, der von den verschiedenen Synthese-Tools generiert wird.

Wie geschrieben, ist der von Ihnen gepostete Code verhaltensorientiert ( always @posedge), nicht strukturell (Instanzen und Drähte). Das macht die Simulation sehr einfach, aber das Synthesetool muss versuchen zu erkennen, wonach Sie fragen, indem es es mit Vorlagen abgleicht. Wenn Ihre alwaysAnweisung nicht mit einer der Vorlagen übereinstimmt, weiß das Synthesetool nicht, wie es sie implementieren soll. Das sollte irgendwo eine Diagnosemeldung erzeugen.

Die erste Übersetzungsphase erzeugt einen RTL-Code (Register Transfer Logic), der eine generische kombinatorische Logik plus Flip-Flops ist. In diesem Beispiel sollten Sie einige Flip-Flops für den Baudratenzähler und die Zustandsmaschine sowie einige kombinatorische Logik zur Bestimmung des nächsten Zustands erwarten. Spätere Synthesephasen finden heraus, wie das FPGA konfiguriert wird, um diesen RTL-Code zu implementieren. Diese Mapping- und Place-and-Route-Phasen wählen aus, welcher Slice (Xilinx) oder konfigurierbare Logikblock (Altera) welches Flip-Flop sein wird, welcher Pin für welche Eingangs-/Ausgangsports der obersten Ebene verwendet werden soll und wie alles miteinander verbunden werden soll.

Nach der Übersetzung/Mapping/Place-and-Route können Sie zurückgehen und die Post-Mapping- oder Post-Place-and-Route-Simulation ausführen. Dies simuliert den RTL-Code, den das Synthesetool generiert hat, nicht den Verhaltenscode, mit dem Sie begonnen haben. Diese Simulation weiß, wo sich jedes Flip-Flop befindet und wie viele Pikosekunden Verzögerung jede Route hat, sodass Sie eine realistischere Simulation der Zeitflanken sehen können. (Da Ihr Takt nur 25 MHz beträgt, sollte es genügend Zeitspielraum geben.)

Simulation und Synthese sind sehr unterschiedlich. Ein alwaysBlock, der in der Simulation gut funktioniert, kann nach der Synthese unerwartet weggelassen werden. (Ich habe einmal die Hälfte eines ziemlich komplexen Systems aufgrund einer Warnung "unvollständige Initialisierung ignoriert" in einem Modul auf niedriger Ebene verloren, was dazu führte, dass Xilinx ISE 14.7 ein ganzes Modul durch eine konstante 0 ersetzte. Und alle Module auf höherer Ebene das hing von diesem Signal ab, wurde auch zu nichts optimiert, da ihre Ausgänge konstant wurden.Da es keine interne Logik mehr gab, um meinen SPI-Ausgang zu treiben, wurde der Ausgangspin nicht geroutet.Jetzt habe ich die Gewohnheit entwickelt, dies zu überprüfen die RTL nach der Synthese, um sicherzustellen, dass keine Teile fehlen.)

Ich habe einen C-Programmierhintergrund, daher muss ich mich beim Schreiben von Verilog ständig selbst herausfordern: Welche Hardware soll es synthetisieren, und dann sicherstellen, dass ich sie so beschreibe, dass das Synthesetool es verstehen kann.

2. Was macht die Hardware eigentlich?

Da das FPGA nichts tut, was Sie direkt beobachten können, ist es schwierig zu diagnostizieren, ob das FPGA eingeschaltet, mit Ihrem Code konfiguriert und Ihre Zustandsmaschine ausgeführt wird. Ändern Sie Ihre oberste Ebene, um einige Diagnoseausgaben hervorzuheben:

  • Bringen Sie state[3:0]zu vier der LEDs

  • Bringen Sie baud_tickdas Signal zu einer LED

  • Fügen Sie Ihrem Toplevel einen "Herzschlag"-LED-Blinker hinzu (schalten Sie einfach ein D-Flip-Flop mit der halben Clk-Rate um), um zu bestätigen, dass die FPGA-Firmware konfiguriert ist. Diese Diagnose ist es wert, zumindest vorerst auf eine der LEDs zu verzichten.

  • Bringen Sie received_byte[0](das erste und niedrigstwertige Bit) zu einer LED

  • Bringen Sie received_byte[7](das letzte und höchstwertige Bit) zu einer LED

Dies bedeutet, dass Sie für diese Diagnosesignale die acht LEDs auf Ihrer Platine anstelle des received_byte[7:0]parallelen Datenausgangs verwenden. Aber diese Signale auf niedrigerer Ebene geben Ihnen nützlichere Informationen, um zu sehen, was in Ihrem Modul vor sich geht. Sie können es wieder ändern, nachdem Sie die Fehler behoben haben.

Ich nehme an, Ihr FPGA-Board hat einige entprellte Schiebeschalter. Jetzt ist es an der Zeit, sie an die Arbeit zu bringen:

  • Verwenden Sie zum Fahren einen entprellten Schiebeschalterreset

  • Verwenden Sie zum Fahren einen entprellten SchiebeschalterRx

  • Verwenden Sie zum Fahren einen entprellten Schiebeschalterclk

Ändern Sie vorübergehend Ihren Baudratenparameter, sodass Sie alle 7 Taktzyklen baud_tick erhalten, sodass Sie manuell durch alle Zustände schalten können, ohne die Zeit aus den Augen zu verlieren.

Gehen Sie auf echter Hardware langsam durch die Zustandsmaschine.

  • Überprüfen Sie, ob die Heartbeat-LED blinkt (abwechselnd 1/2 Taktfrequenz)

  • Überprüfen Sie, ob Sie baud_tickeinen Impuls mit der erwarteten Rate erhalten ( TIPP : Ich denke, hier finden Sie das Problem. Was ist, wenn der Anfangswert baud_rate_clockzufällig ist?)

  • Überprüfen Sie, ob die Zustandsmaschine außerhalb des Reset-Zustands in den richtigen Zustand wechselt.

  • Überprüfen Sie, ob Ihre Zustandsmaschine im Leerlauf bleibt, wenn sie Rxim Leerlauf ist.

  • Überprüfen Sie, ob Ihre Zustandsmaschine das Startbit erkennt.

  • Überprüfen Sie, ob Ihre Zustandsmaschine die Zustände korrekt durchläuft und in den Ruhezustand zurückkehrt.

  • Überprüfen Sie, ob die Werte received_byte[0]und Ihren Erwartungen entsprechen.received_byte[7]

Ein Oszilloskop ist eine echte Rettungsleine, wenn Sie eine zur Verfügung haben. Auf diese Weise können Sie sehen, wie die Hardware mit voller Geschwindigkeit läuft, ohne mit entprellten Schaltern durchschalten zu müssen. Für ein komplizierteres Design wäre dies ein wesentliches Werkzeug.

3. Warum stimmt die Simulation nicht mit der Realität überein?

Das vollständige Testen eines Designs ist ein wirklich schwieriges Problem. Vor allem, wenn es sich um Ihren eigenen Code handelt. In diesem Beispiel hat Ihr Code die Eingänge clk, reset und Rx. Du fährst schon clk.

  • Was passiert, wenn Rx beim Verlassen des Zurücksetzens im "falschen" Zustand ist? Wird sich die Zustandsmaschine erholen?

  • Was ist, wenn der Sender eine andere Baudrate verwendet (+5 %, -5 %, +10 %, -10 %, x2, x0,5)

  • Was ist, wenn die Zustandsmaschine in einem zufälligen Zustand startet, wird sie sich erholen oder bleibt sie hängen?

  • Was ist, wenn der Rx-Frame den falschen Stoppbitwert hat?

  • Was ist, wenn der Rx markiert ist, wenn Sie Platz erwarten?

  • Was ist, wenn der Rx invertiert ist? (Dies ist ein sehr häufiger Fehler in RS232-UART-Systemen, da der Übersetzer von Logikpegel zu RS232-Pegel normalerweise invertiert.)

Du hast die Idee. Alle Testbedingungen zu finden, ist genauso schwierig wie jede andere Art von Designarbeit. Es wird immer Lücken in der Abdeckung geben. Planen Sie also, zurückzugehen und weitere Tests hinzuzufügen, basierend auf dem, was Sie bei der Überprüfung der Hardware finden.

Eines der schwierigsten Dinge sowohl beim Programmieren als auch beim HDL-Design ist das Testen Ihrer eigenen Designs. Wenn ich eine Testbench für meinen eigenen Code schreibe, mache ich mir immer Sorgen, dass ich nicht genug mögliche Testfälle abgedeckt habe. Und jeder Testfall, der mir einfällt, habe ich wahrscheinlich in meinem Code behandelt – es sind die Tests, an die ich nicht gedacht habe, die Lücken in der Testabdeckung verursachen. Was ist beispielsweise, wenn das Eingangssignal nicht mit dem zu testenden Gerät synchronisiert ist? Das ist bei echter Hardware sehr üblich, kann aber in einer Verhaltenssimulation schwierig einzurichten sein.

Ich hoffe, das reicht, um Sie auf den Weg zu bringen. Viel Glück!

+1 für das gründliche Lesen der Syntheseprotokolle, höchstwahrscheinlich liegt der Grund dort.
@MarkU Vielen Dank für einen so ausführlichen Bericht. Ich werde mich während meiner FPGA-Arbeit auf jeden Fall häufig auf diesen Beitrag beziehen. Ich habe versucht, Sie mit den LEDs zu debuggen, und es zeigte sich, dass nur baud_tick und die clk-LED leuchteten. Scheinbar passiert nichts mehr. Nachdem ich darüber nachgedacht hatte, stellte ich fest, dass ich meinen UART auf synchrone Weise entworfen hatte, was dem ganzen Punkt eines universellen asynchronen Empfängers / Senders ziemlich entgegengesetzt ist. Ich werde versuchen, es asynchron neu zu gestalten und sehen, was passiert.

Ihr Code ist nicht synthetisierbar, da ein elseBefore vorhanden sein muss case. Sie hätten es in der Simulation erwischt, indem Sie den reset_nStift getestet hätten.

  • Erforderliche Änderungen sind fett gedruckt
  • Empfohlene Verbesserungen sind fett-kursiv
always @(posedge baud_tick or negedge reset_n) begin
    if (~reset_n) begin
        state <= IDLE; // not a bug, but state should reset to IDLE, not 0
        byte_ready <= 1'b0; // not a bug, but size and radix should be defined
        // it is recommend not to place a non-async reset in a block with async reset
        received_byte <= 8'h0; // reset signal or move to another always block
    end
    else begin // <== synthesis bug here, the "else begin" was missing <==
      case(state)
        // ... your state code ...
      endcase
    end// end to match begin 
end

Dieser fehlende elseFehler sollte in Ihrem Syntheseprotokoll/Bericht enthalten sein.

Ich habe Ihre Änderung hinzugefügt, aber sie hatte keine Auswirkung.
@thejohnny, irgendetwas im Syntheseprotokoll?
Entschuldigung, ich bin ziemlich neu in Quartus. Wo finde ich dieses Protokoll?
@thejohnny, ich habe keinen Zugriff auf Quartus. Sie müssen das Benutzerhandbuch und die Tutorials überprüfen.