Ziemlich einfacher VHDL-SPI-Bus, der in der Simulation funktioniert, aber nicht auf FPGA (Lattice MACHOX3LF-6900C FPGA und Lattice Diamond-Software)

Ich bin neu in der VHDL- und FPGA-Programmierung, und obwohl ich eine ganze Reihe von Problemen kenne, die zwischen Simulation und Synthese auftreten können, hat mich dieses spezielle Problem ratlos gemacht.

Mein Design ist ziemlich einfach:

  • Der SPI-Bus nimmt ein Datenbyte vom darüber liegenden Modell entgegen, das er dann bei steigenden Taktflanken herausschiebt.

  • Beim Zurücksetzen werden alle Signale auf Anfangswerte gesetzt. Ich triggere diesen Eingang, indem ich einen Draht zwischen dem Reset-Pin und Masse an meinem FPGA anschließe.

  • Die SPI-Schnittstelle tritt dann in eine "Warte"-Phase ein, in der sie auf ein Signal von dem obigen Modell wartet, das immer hoch ist, daher verlässt sie diese Wartephase innerhalb eines Taktzyklus.

  • Er tritt dann in eine "Schreib"-Stufe ein, wo er bei der ersten ansteigenden Flanke des Takteingangs das nächste Bit von seinem Dateneingang zu seinem Datenausgang schreibt und den Taktausgang niedrig schreibt. Bei der folgenden Taktflanke schreibt es seinen Taktausgang hoch, verschiebt die Daten zu dem Gerät, auf das es warten würde, das mit den Ausgangspins des FPGA verbunden ist, und erhöht den Zähler, der verfolgt, welches Eingangsbit des Eingangsbytes es setzt nächsten Eingangstaktzyklus.

  • Dieses Muster wiederholt sich, bis der Ausgangstakt hoch gesetzt wird, verschiebt das letzte Eingangsbit heraus und inkrementiert den Eingangsbit-Auswahlzähler auf 8. Bei der nächsten ansteigenden Flanke des Eingangstakts setzt das Gerät alle Variablen zurück und schaltet in den "Warten"-Modus um. anstatt zu versuchen, das Eingangsbit Nr. 8 des Eingangsbytes auszugeben, das nicht existiert, da das Eingangsbyte-Array ein bit_vector mit Indizes mit der Bezeichnung 0 bis 7 ist.

  • Da diese Version sowohl das Eingangsbyte als auch das Eingangstriggerbit konstant hält, wiederholt das Gerät seine Ausgabe endlos.

Dies alles funktioniert wie erwartet in der Simulation und fast wie erwartet in der Realität, mit Ausnahme der endgültigen Verschiebung aus den Daten. Wenn ich das Programm auf das FPGA hochlade, scheint es den Schritt des Hochschreibens des Taktausgangs auf das 8. Bit zu überspringen und springt stattdessen direkt zur Rücksetzphase und setzt den folgenden Eingangstaktzyklus in die "Wartephase" zurück. Es stellt die Datenausgabe gut ein, aber anstatt die Taktausgabe zum letzten Mal vor dem Zurücksetzen hoch zu setzen, springt es einfach zum Zurücksetzen.

Mein Glaube ist, dass dies mit der Tatsache zu tun hat, dass es den Eingangsbitauswahlzähler auf 8 erhöht, während es gleichzeitig den Ausgangstakt hoch schreibt. Anstatt bis zum nächsten Eingangstaktzyklus zu warten, um zu erkennen, dass dieser Wert jetzt 8 ist und zurückgesetzt werden sollte, sieht es aus irgendeinem Grund sofort die Änderung auf 8 und entscheidet sich für das Zurücksetzen, da der Zähler jetzt auf 8 steht.

Eine letzte Anmerkung, in meinem Code gibt es ein "Statusbit" in der SPI-Schnittstellenarchitektur, das von der obigen Architektur in diesem Design nicht verwendet wird, aber in zukünftigen Designs wird es eine notwendige Sache sein, es aufzunehmen. Ich würde diesen Debugging-Code vereinfachen, indem ich ihn entferne, aber wenn er aus allen Implementierungsebenen entfernt wird, tritt der oben aufgeführte Fehler nicht auf, und das FPGA verhält sich genauso perfekt wie die Simulation. Ich habe keine Ahnung, warum das so ist.

Hier ist mein Code:

Die unterste Ebene im Design, die SPI-Schnittstelle:

entity SPI is 
    port (data_out, clk_out, status_out : out bit; data_in : in bit_vector(0 to 7); CLOCK, begin_in, RESET: in bit);
end entity SPI;

architecture SPIArch of SPI is 
    type SPIState is (waiting, writing);
    signal current_state : SPIState := waiting; 

    subtype byteCount is integer range 0 to 8;  
    signal current_bit : integer := 0;  

    signal data_set : bit := '0';  

begin  
    shiftOut : process (CLOCK, RESET)   
    begin 
        if (RESET = '0') then
            current_bit <= 0;  
            data_set <= '0'; 
            clk_out <= '0';  
            data_out  <= '0'; 
            status_out <= '1';
            current_state <= waiting;   
        elsif (CLOCK = '1' and CLOCK'event) then  
            case current_state is
                when waiting =>
                    if (begin_in = '1') then
                        status_out <= '0';
                        current_state <= writing;
                    end if;
                when writing =>
                    if (current_bit /= 8) then
                        if (data_set = '1') then 
                            clk_out <= '1';
                            data_set <= '0';
                            current_bit <= current_bit + 1;
                        end if;
                        if (data_set = '0') then
                            data_out <= data_in(current_bit);
                            clk_out <= '0';
                            data_set <= '1'; 
                        end if; 
                    else 
                        data_out <= '0';
                        clk_out <= '0'; 
                        current_bit <= 0;  
                        status_out <= '1';
                        data_set <= '0';
                        current_state <= waiting; 
                    end if;
            end case; 
        end if;
    end process shiftOut;
end architecture SPIArch;

Als nächstes die Ebene über der SPI-Schnittstelle, dem "Treiber", um die SPI-Schnittstelle intern mit dem Testwert zu versorgen (beachten Sie, dass der große Taktteiler verwendet wurde, damit ich mithilfe von LEDs sehen konnte, was an den Ausgängen meines FPGA vor sich ging):

entity SPIDrive is
    port (data_out, clk_out, status_out : out bit; CLOCK, RESET: in bit);
end entity SPIDrive;

architecture SPIDriveArch of SPIDrive is 

    signal SPI_clk : bit;
    signal SPI_counter : integer;
    signal SPI_data : bit_vector(0 to 7);
    signal SPI_begin : bit;

begin
    testDevice : entity work.SPI(SPIArch)
        port map (data_out, clk_out, status_out, SPI_data, SPI_clk, SPI_begin, RESET);

    outputTest : process (CLOCK, RESET) 
    begin
        if (RESET = '0') then
            SPI_clk <= '0';
            SPI_counter <= 0;
            SPI_data <= B"10110011";
            SPI_begin <= '1';
        elsif (CLOCK = '1' and CLOCK'event) then
            SPI_counter <= SPI_counter + 1;
            if (SPI_counter = 5000000) then
                SPI_counter <= 0;
                SPI_clk <= not SPI_clk;
            end if;
        end if;
    end process outputTest;
end architecture SPIDriveArch;

Die letzte Ebene, die nur als Prüfstand für die Simulation verwendet wird und lediglich die Taktquelle und den Reset erzeugt, die normalerweise von einem On-Board-Oszillator und einem Eingangspin kommen:

entity testbench1 is
end entity testbench1;

architecture testbench1Arch of testbench1 is 
    signal data_out, clk_out, status_out, CLOCK, RESET: bit;
begin
    troll : entity work.SPIDrive(SPIDriveArch)
        port map (data_out, clk_out, status_out, CLOCK, RESET);

    process 
    begin
        RESET <= '0';
        CLOCK <= '0';
        wait for 1 ns;
        RESET <= '1';
        while (true) loop
            wait for 1 ns;
            CLOCK <= '1';
            wait for 1 ns;
            CLOCK <= '0';
        end loop;
    end process;
end architecture testbench1Arch;

Hier ist die Ausgabe meiner Simulation (HINWEIS, dass ich den Taktteiler im „SPI-Treiber“-Code auf 10 statt 5 Millionen geändert habe, um die Anzeige in der Simulation zu erleichtern, und dass ich einen Reset 1 ns in der Simulation auslöse (wie in mein Testbench-Code), obwohl es im Bild schwer zu sehen ist):Geben Sie hier die Bildbeschreibung ein

Um Verwirrung darüber zu vermeiden, was mein Fehler tatsächlich war, ist hier eine (schlecht) bearbeitete Version des Simulationsbildes, die im Wesentlichen zeigt, was ich im wirklichen Leben von meinem FPGA sehe:Geben Sie hier die Bildbeschreibung ein

Eine letzte, wahrscheinlich unnötige Ergänzung, ich bekomme eine große Anzahl scheinbar ignorierbarer Warnungen während der Synthese (zum Beispiel eine große Anzahl von Latches bei meiner Dateneingabe, was sinnvoll ist, da ich den Dateneingabewert nie ändere), aber ich bekomme auch drei mit der Angabe " keine Designbeschränkungen" in meinen Dateien. Ich nehme an, dass dies für eine Art Simulation und nicht die Ursache meiner Probleme sind, aber sie schienen möglicherweise bemerkenswert, also erwähne ich sie.

Wie im Titel erwähnt, verwende ich Lattice Diamond für die Programmierung und Simulation (obwohl die Simulation Active-HDL öffnet) und das Lattice MACHXOLF-6900C FPGA

„Keine Designeinschränkungen“ ist eine wichtige Warnung. Es sagt Ihnen, dass Sie Dinge wie die Taktrate, mit der das endgültige Design ausgeführt werden soll, nicht angegeben haben. Die Tools sollten Ihnen sagen können, wie schnell Sie Ihre Uhr laufen lassen können, bevor Sie anfangen, Fehler zu bekommen. Wie lässt sich diese Geschwindigkeit mit der Geschwindigkeit vergleichen, mit der Ihre Uhr in der realen Welt läuft?
Ich arbeite mit 12 MHz, ich hatte die Zeitbeschränkungen ignoriert, weil ich früher im Design bei 100 MHz bestanden hatte. Ich habe die gleiche Analyse wie zuvor wiederholt und obwohl ich die Simulation mit CLOCK bei 100 MHz wie zuvor bestanden habe, wollte die Simulation aus irgendeinem Grund auch SPI_clk simulieren, was nicht einmal bei 1 kHz (ja Khz) funktionieren würde. Ich dachte, das könnte daran liegen, dass ich einen großen Taktteiler hatte, aber das Verringern auf 10 zeigte keine Änderung. Ich bin mir ziemlich sicher, dass ich die Analyse einfach falsch mache, aber es könnte der richtige Weg sein, also werde ich es weiter versuchen.
Nach mehr Recherche habe ich diesen Thread ( edaboard.com/thread283723.html ) online gefunden , ich habe viele dieser Threads "Arbeiten in der Simulation, aber nicht in der Praxis" durchforstet, aber dieser ist anders, weil ihr Taktteiler notiert ist als mögliches Problem, und ich habe auch einen Taktteiler in meinem Code. Sie sagen, dass sie durch den Taktaktivierungscode anstelle der Taktteilung ersetzt werden sollen, also werde ich das versuchen und sehen, ob es hilft.

Antworten (1)

Beantwortung meiner eigenen Frage hier, wie sich herausstellt, sollten Sie KEINE Taktteiler in VHDL verwenden. Ich hatte fälschlicherweise angenommen, dass dies in Ordnung ist, solange Sie jede Uhr als Uhr behandeln, aber wie sich herausstellt, gibt es einen einzigen hardwarespezifischen Weg, den die Uhr nimmt, und eine zweite Uhr, die von einem Teiler stammt, hat einfach nicht die gleiche kleine -Verzögerungseigenschaften der benutzerdefinierten Taktlinie.

Ich habe das alles aus diesem Forumsbeitrag ( edaboard.com/thread283723.html ), in dem sie empfehlen, Taktteiler durch Taktgeber zu ersetzen. Anstatt also Ihre gesamte Logik durch eine Taktflanke auslösen zu lassen, haben Sie eine Taktflanke, die einen Blick auf eine Bedingung wirft, die prüft, ob "Taktfreigabe" hoch ist, und wenn sie hoch ist, führt sie den Code aus. Solange Sie die Taktfreigabe nur für einen Taktzyklus hoch halten, verhält es sich gleich. Sie drehen ihn im Grunde alle 10 Millionen Zyklen hoch und beim 10000001. niedrig, wenn Sie zum Beispiel eine Uhr wollen, die durch 10 Millionen geteilt wird.

Sie wollen vermeiden, hinzugehen

if(clk = '1' and clock'event and clock_enable) 

da ich mir ziemlich sicher bin, dass Sie logische Operationen mit Ihrer Uhr vermeiden sollten (ich bin neu in VHDL, also habe ich das selbst noch nie erlebt, lesen Sie es einfach in anderen Forenbeiträgen). Stattdessen gehst du

if (clk = '1' and clock'event) then
    if (clock_enable = '1') then
    ...
Darüber hinaus besteht die moderne (nach 1993) Methode zum Erkennen von Taktflanken darin, die Funktionen rising_edge(clk)und zu verwenden.falling_edge(clk)
Tatsächlich waren steigende_Flanke und fallende Flanke im Paket std_logic_1164 verfügbar, definiert in IEEE Std 1164-1993, genehmigt 6 Monate vor IEEE Std 1076-1993. Es gibt Vorteile, der vorherige Signalwert muss einen binären Wert bedeuten, ebenso wie der aktuelle Zustand. (eine Null ist '0' oder 'L', eine Eins '1' oder 'H'). Dies bedeutet, dass Sie keine Standardwerte für Ihre Taktsignale angeben müssen, da Sie sonst vor dem Zurücksetzen viele arithmetische Operatorwarnungen aus dem Paket numeric_std erhalten können.
Sie können Taktteiler verwenden, um eine neue Uhr zu erzeugen, die in einen Block eintritt, solange alles in diesem Block synchron zu dieser (geteilten) Uhr ist. Aber Sie müssen Ihren Synthesizer über diese neue Clock informieren, damit er die Timing-Einschränkungen kennt, die er erfüllen muss. Und ja, im Allgemeinen ist die Verwendung von Taktaktivierungen der richtige Weg.