Fehler in meiner SPI-Implementierung (VHDL)

Ich bin neu in der VHDL/FPGA-Programmierung und habe ein seltsames Verhalten in meiner SPI-Slave-Implementierung festgestellt. Was ich getan habe:

  1. SPI-Master: Ich verwende einen Arduino (ATMega328p MCU) als SPI-Master. Zum Debuggen sendet er einmal pro Sekunde einen 8-Bit-Zählerwert an den SPI-Slave des FPGAs. Es zieht SS herunter, sendet 1 Byte (8 Bit, MSB zuerst, bei 2 MHz SPI-Clock-Rate), zieht SS wieder hoch, erhöht den Zähler und wartet eine Sekunde.
  2. Ich habe mir die Signale mit meinem DSO angesehen, die sehen gut aus (bis auf das Überschwingen). Es ist in der Lage, den Wert zu entschlüsseln und beschwert sich nicht über Protokollprobleme. Mein DSO hat nur 2 Eingänge (CH1 = SCK, CH2 = MOSI im Bild unten). Ich habe auch überprüft, ob SS richtig nach unten / oben gezogen wird, was der Fall ist. Ich habe langsame digitalWrite(SS_pin, LOW);und digitalWrite(SS_pin, LOW);Befehle verwendet, daher gibt es eine lange Lücke zwischen der ersten steigenden Flanke von SCK und der fallenden Flanke von SS_pin; und eine lange Lücke nach der letzten fallenden SCK-Flanke des Bytes, bevor der SS_pin wieder hochgezogen wird.
  3. In meinem VHDL-Code verwende ich einige FIFOs als Synchronisierer, da die SPI-Logik zu einer 100-MHz-Taktdomäne gehört (50-MHz-On-Board-Oszillator => 2x Takt-Multiplikator durch eine PLL-IP).

Das Problem:

Zu Testzwecken wollte ich die 3 LEDs (LOW = an) auf dem FPGA-Board verwenden, um die 3 LSB's der empfangenen 8-Bit anzuzeigen. Da Daten MSB zuerst gesendet werden, sollten dies die letzten 3 empfangenen Bits sein (bei Index=0 beginnend: Bits 5,6 und 7).

Ich führe meinen folgenden Code aus, der auf bit_index = 5/6/7 prüft und die Ausgänge den LEDs-Pins zuweist (negiert, da LOW = Licht eingeschaltet ist). Dies führte jedoch dazu, dass die letzte LED ( leds(0)) statisch war (da ich sie nicht zurücksetze, ist ihr Wert zufällig; also sehe ich etwas wie: 00? => 01? => 10? => 11? => 00?) . Es scheint, dass der bit_index nie 7 erreicht.

Wenn ich es ändere (wie unten gezeigt), um nach bit_index = 4/5/6 zu suchen, funktioniert es wie erwartet (000 => 001 => 010 => 011 => 100 => 101 => 110 => 111 => 000 . ..).

Also, was ist hier das Problem? Ich vermute, es hat mit der Verwendung des Typs Integer für den bit_index zu tun variables. Ich weiß, dass signalssie während des gesamten Taktzyklus beim Lesen auf ihrem aktuellen Wert bleiben, selbst wenn ihnen ein anderer Wert zugewiesen wurde, und ihren Wert im nächsten Zyklus aktualisieren. Soweit ich weiß, variablesverhalten sie sich jedoch irgendwie wie Variablen in Nicht-HDL-Programmiersprachen und ändern ihren Wert sofort. Wie ich herausgefunden habe: Aus irgendeinem magischen Grund sind sie ohne Beschwerden des Compilers synthetisierbar und funktionieren nicht nur für Simulationen ... Also habe ich es verwendet. Jetzt frage ich mich, ob dies normalerweise funktioniert oder zu einigen schwerwiegenden Fehlern führt, selbst das VHDL-Compiler/Synthesizer-Tool beschwert sich nicht darüber.

Bitte schau dir den Code an und lass mich wissen, was das Problem ist und vor allem: WARUM???

DSO-Screenshot:

DSO-Screenshot - SPI, 8-Bit-Wert

VHDL-Code:

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_arith.all;

   library work;
   use work.MyPackage.all;

   entity SPI is
          port(
                 clk  : in  std_logic;
                 SCK  : in  std_logic;
                 SS   : in  std_logic;
                 MOSI : in  std_logic;
                 MISO : out std_logic;
                 -- LEDS for debugging
                 leds : out std_logic_vector(2 downto 0) := "111";
                 -- DATA to transfer
                 data : in  SAMPLE_ARRAY_T
          );
   end entity;

   architecture RTL of SPI is

          -- synchronizer FIFOs
          signal SS_FIFO   : std_logic_vector(2 downto 0);
          signal SCK_FIFO  : std_logic_vector(2 downto 0);
          signal MOSI_FIFO : std_logic_vector(1 downto 0);

          -- output shift register
          -- holding remaining bits of current 16-bit word to send
          signal output_ShiftReg : std_logic_vector(15 downto 0);

          -- current bit / word to be sent at rising_edge of SCK
          shared variable bit_index  : integer range 0 to 15 := 0;
          shared variable word_index : integer range 0 to 7  := 0;

   begin
          process(clk) is
          begin
                 if rising_edge(clk) then
                        -- shift in new data into
                        -- synchronization FIFOs
                        SS_FIFO   <= SS_FIFO(1 downto 0) & SS;
                        SCK_FIFO  <= SCK_FIFO(1 downto 0) & SCK;
                        MOSI_FIFO <= MOSI_FIFO(0) & MOSI;

                        -- on falling_edge(SS): reset offsets
                        -- load first data word to output_ShiftReg
                        if SS_FIFO(2 downto 1) = "10" then
                               bit_index       := 0;
                               word_index      := 0;
                               output_ShiftReg <= data(word_index);
                        end if;

                        -- if SS = LOW and rising_edge of SCK
                        -- transfer data ...
                        if SS_FIFO(1) = '0' and SCK_FIFO(2 downto 1) = "01" then

                               -- display 3 LSB bits of incoming 8 bit data
                               -- via debug LEDs (data is sent MSB first)
                               case bit_index is
                                      when 4 =>           -- WHY?? this should be 5...
                                             leds(2) <= not MOSI_FIFO(1);
                                      when 5 =>           -- WHY?? this should be 6...
                                             leds(1) <= not MOSI_FIFO(1);
                                      when 6 =>           -- WHY?? this should be 7...
                                             leds(0) <= not MOSI_FIFO(1);
                                      when others =>
                               end case;

                               -- return input data
                               MISO <= output_ShiftReg(15);

                               -- increment bit/sample index
                               bit_index := bit_index + 1;
                               if bit_index = 0 then
                                      -- A bit_index overflow happened:
                                      -- load next data word into sample
                                      word_index      := word_index + 1;
                                      output_ShiftReg <= data(word_index);
                               else
                                      -- shift 1 bit out of output_ShiftReg
                                      output_ShiftReg <= output_ShiftReg(14 downto 0) & '0';
                               end if;
                        end if;
                 end if;
          end process;
   end architecture;

SPI-Master (MCU, Arduino / C++):

   #include <SPI.h>

   int SS_pin = 10;

   SPISettings settingsA(2000000, MSBFIRST, SPI_MODE0);

   void setup() {
     Serial.begin(115200);
     // initialize SPI:
     pinMode(SS_pin, OUTPUT);
     digitalWrite(SS_pin, HIGH);
     SPI.begin();
   }

   uint8_t counter = 0;

   void loop() {
     uint8_t retval;
     digitalWrite(SS_pin, LOW);
     SPI.beginTransaction(settingsA);
     retval = SPI.transfer(counter);
     SPI.endTransaction();
     digitalWrite(SS_pin, HIGH);
     Serial.print("SEND: ");
     Serial.print(counter);
     Serial.print(", GOT: ");
     Serial.println(retval);
     ++counter;
     delay(1000);
   }

Zusätzliche Information:

  • FPGA: Altera Cyclone II (EP2C5T144E, Mini-FPGA-Board)
  • Quartus II WebEdition, 64-Bit, 13.0.1sp1 (Build 232)

EDIT #1:

Wie vorgeschlagen habe ich dafür einen Prüfstand geschrieben und über ModelSIM simuliert. Hier ist der Code und die Simulationsergebnisse. Beachten Sie, dass ich die Werte 5, 6 und 7 zum Vergleichen von bit_index verwendet habe (nicht die Workaround-Werte wie oben, die ich für mein FPGA benötige):

            case bit_index is
                when 5 =>           -- using 5, as I should here 
                    leds(2) <= not MOSI_FIFO(1);
                when 6 =>           -- using 6, as I should here 
                    leds(1) <= not MOSI_FIFO(1);
                when 7 =>           -- using 7, as I should here
                    leds(0) <= not MOSI_FIFO(1);
                when others =>
            end case;

SPITest.vhd (Prüfstandscode):

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.numeric_std.all;

   library work;
   use work.MyPackage.all;

   entity SPITest is
   end entity;

   architecture SIM of SPITest is

          component SPI is
                 port(
                        clk  : in  std_logic;
                        SCK  : in  std_logic;
                        SS   : in  std_logic;
                        MOSI : in  std_logic;
                        MISO : out std_logic;
                        -- LEDS for debugging
                        leds : out std_logic_vector(2 downto 0) := "111";
                        -- DATA to transfer
                        data : in  SAMPLE_ARRAY_T
                 );
          end component;

          signal spi_CLK  : std_logic := '0';
          signal spi_SS   : std_logic := '1';
          signal spi_MOSI : std_logic := '1';
          signal spi_MISO : std_logic := '0';

          signal mydata : SAMPLE_ARRAY_T := (others => (others => '0'));

          signal leds : std_logic_vector(2 downto 0) := "111";

          signal clk : std_logic := '0';

          signal counter_slv : std_logic_vector(7 downto 0) := (others => '0');

          constant period : time := 10 ns;

          function to_string(a : std_logic_vector) return string is
                 variable b    : string(1 to a'length) := (others => NUL);
                 variable stri : integer               := 1;
          begin
                 for i in a'range loop
                        b(stri) := std_logic'image(a((i)))(2);
                        stri    := stri + 1;
                 end loop;
                 return b;
          end function;

   begin
          dut : spi port map(clk => clk, SCK => spi_CLK, SS => spi_SS, MISO => spi_MISO, MOSI => spi_MOSI, leds => leds, data => mydata);

          clk <= not clk after period / 2;

          process is
          begin
                 -- initial state
                 spi_CLK  <= '0';
                 spi_SS   <= '1';
                 spi_MOSI <= '1';
                 report "INIT STATE.";

                 wait for 100 ns;

                 -- send 8 times '1'
                 for counter in 0 to 255 loop

                        report "PULLING spi_SS to LOW...";
                        spi_SS <= '0';

                        -- 2000 ns should be enough
                        wait for 2000 ns;

                        counter_slv <= std_logic_vector(to_unsigned(counter, counter_slv'length));
                        for bit in 7 downto 0 loop
                               spi_MOSI <= counter_slv(bit);
                               wait for 250 ns;
                               spi_CLK  <= '1';
                               wait for 250 ns;
                               spi_CLK  <= '0';
                        end loop;

                        wait for 2000 ns;
                        report "PULLING spi_SS back to HIGH...";
                        spi_SS <= '1';

                        wait for 2000 ns;
                 end loop;

                 report "LEDs are: " & to_string(leds);
                 wait;

          end process;

   end architecture;

Falls Sie es selbst versuchen möchten. Sie benötigen außerdem MyPackage.vhl (das SAMPLE_ARRAY_T definiert):

   library ieee;
   use ieee.std_logic_1164.all;

   package MyPackage is

          type SAMPLE_ARRAY_T is array (0 to 7) of std_logic_vector(15 downto 0);

          component SPI is
                 port(
                        clk  : in  std_logic;
                        SCK  : in  std_logic;
                        SS   : in  std_logic;
                        MOSI : in  std_logic;
                        MISO : out std_logic;
                        -- LEDS for debugging
                        leds : out std_logic_vector(2 downto 0) := "111";
                        -- DATA to transfer
                        data : in  SAMPLE_ARRAY_T
                 );
          end component;

   end package;

Ergebnis:

HINWEIS: Die Simulation liefert korrekte Ergebnisse. Die Werte von "LEDs" werden invertiert (wie beabsichtigt, da die LEDs mit VCC verbunden sind und ein gezogenes Low-Signal die LED einschaltet).

Ergebnisse der Simulation

Bearbeiten #2:

Da ich den VHDL-Code für die Simulation geändert habe, habe ich das Verhalten der FPGA-Hardware erneut validiert. Es hat sich nicht geändert: Simulation funktioniert. Synthetisierte Version schlägt fehl. Es scheint also ein Problem zu sein, wie das VHDL synthetisiert wird.

Ich habe den Code für die Ausgabe (MISO) entfernt und nur den Code beibehalten, der die LED-Ausgabe steuert (da dies viel kompakter ist) und Quartus II einen Schaltplan davon erstellen lassen. So sieht es aus:

Geben Sie hier die Bildbeschreibung ein

Scheint, dass der Synthesizer/Optimierer einige verrückte Sachen produziert hat. Besonders die bit_index-Zählung sieht für mich sehr verwirrend aus. Der SS_FIFO-Ausgang steuert beide Multiplexer mit der Bezeichnung "MUX21", die bit_index zurücksetzen. Die SCK_FIFO-Ausgabe wird mit "01" (bei "Equal0") verglichen und mit "nicht SS_FIFO" kombiniert. Dies steuert die Schreibfreigabe für das LED-Register und stellt sicher, dass das Register nicht geschrieben wird, wenn entweder SS HIGH ist oder keine ansteigende Flanke an SCK erkannt wird. Der MOSI_FIFO-Ausgang wird negiert und in die Dateneingänge der Multiplexer "MUX0", "MUX1" und "MUX2" eingespeist. Kombiniert mit dem Datenfeedback von leds[2..0]~reg0 erzeugen sie den neuen Eingang für das Register leds[2..0]~reg0. Aufgrund ihrer Auswahleingänge kann eines der leds[]-Bits durch den MOSI_FIFO-Ausgang ersetzt werden, während die anderen so ausgewählt werden, dass sie unverändert bleiben. "Add0" wird verwendet, um "bit_index" um eins zu inkrementieren. Wenn der "MUX21"-Teil (bit_index~[7..4]) verlassen wird, kann zwischen dem aktuellen Wert der bit_index-Kette (nach dem zweiten Mutex) und diesem um eins erhöhten Wert gewählt werden. Letzteres wird bei einer detektierten ansteigenden Flanke von SCK gewählt. Der rechte "MUX21"-Teil (bit_index~[3..0]) dient zum Zurücksetzen des bit_index auf 0, wenn SS hochgezogen wird. Ich kann die Eingangsverdrahtung von "MUX0", "MUX1" und "MUX2" nicht überprüfen, aber der Rest scheint mir korrekt zu sein. Das bit_index-Register wird auf 0 zurückgesetzt, solange SS HIGH ist. Wenn SS auf LOW gezogen wird, bleibt es immer noch 0, weil der linke Multiplexer sich dafür entscheidet, den aktuellen Wert zu halten, bis eine ansteigende Flanke an "SCK" erkannt wird. In diesem Fall gibt das bit_index-Register immer noch 0 aus und sollte dazu führen, dass die richtigen Datenleitungen der Multiplexer "MUX0", "MUX1" und "MUX2" in das Leds-Register eingespeist werden. Schließlich wird nach einem Taktzyklus des FPGA-Taktes "clk" der Wert bit_index um eins erhöht. Daher wird bit_index vor dem Inkrement verwendet, um die Multiplexereingänge auszuwählen, und wird bei jeder erkannten ansteigenden Flanke von SCK inkrementiert. Das scheint alles völlig richtig zu sein. nach einem Taktzyklus des FPGA-Taktes „clk“ wird der Wert bit_index um eins erhöht. Daher wird bit_index vor dem Inkrement verwendet, um die Multiplexereingänge auszuwählen, und wird bei jeder erkannten ansteigenden Flanke von SCK inkrementiert. Das scheint alles völlig richtig zu sein. nach einem Taktzyklus des FPGA-Taktes „clk“ wird der Wert bit_index um eins erhöht. Daher wird bit_index vor dem Inkrement verwendet, um die Multiplexereingänge auszuwählen, und wird bei jeder erkannten ansteigenden Flanke von SCK inkrementiert. Das scheint alles völlig richtig zu sein.

Hier ist ein kurzes Video, was passiert. Wie Sie sehen können, leuchtet die erste LED (die das LSB anzeigen sollte) weiter. Während die mittlere LED das LSB anzeigt und die andere LED den Status des zweiten Bits anzeigt. Die Pinbelegung ist nicht das Problem (da durch die oben erwähnte Änderung des VHDL-Codes alle LEDs wie erwartet funktionieren).

Irgendwelche Ideen?

Sie sollten eine Testbench schreiben, die die korrekten Signale von Ihrem Prozessor repliziert, und sehen, wie die VHDL darauf reagiert. Verwenden Sie einen Simulator, mit dem Sie interne Signale beobachten können.
@ElliotAlderson: Ich habe jetzt eine Simulation gemacht. Ich habe jedoch nur eine Lücke von 2 us zwischen SPI-Transaktionen simuliert (anstelle der 1s im realen Setup). Die Simulationsergebnisse verhalten sich so, wie ich es mir von der Hardware wünsche (beim Vergleich von bit_index mit 5, 6 und 7 und beim Kopieren von MOSI auf die LED-Ausgänge). Da ich leichte Änderungen an der VHDL-Quelle vorgenommen habe (einige andere Komponenten entfernt), muss ich überprüfen, ob sich die Hardware mit dieser reduzierten Version immer noch anders verhält. Werde das machen sobald ich die Hardware zur Hand habe und euch das Ergebnis mitteilen.
Simulationscode / -ergebnisse zur obigen Frage hinzugefügt.
Habe das Verhalten der Hardware überprüft. Leider ist es immer noch anders als die RTL-Simulation. Siehe Edit #2 in meinem Beitrag. Der vom "RTL-Viewer" generierte Schaltplan wurde hinzugefügt (Werkzeug > Netzlisten-Viewer). Auch ist eine Ansicht verlinkt, die das falsche Verhalten des FPGA zeigt.

Antworten (1)

Nur um zu beginnen, die Tatsache, dass die Simulation funktioniert und der synthetisierte Code nicht, bedeutet nicht unbedingt, dass der Synthesizer etwas falsch gemacht hat. Denn normalerweise tut er/sie genau das, was er/sie tun soll, nämlich IHREN Code synthetisieren. Es gibt viele Aspekte in der realen Welt, die in den meisten Simulationen vernachlässigt werden

  • asynchrone eingehende Signale
  • externe Signale, die in Ihrer Simulation nicht so aussehen, wie Sie es erwarten
  • fehlende Eingangs-FFs

Aber in deinem Fall hast du wahrscheinlich recht. Ich neige dazu, wann immer möglich auf Variablen zu verzichten, und meistens ist es das auch, weil sie gefährlich sind. Um ehrlich zu sein, wusste ich nicht einmal, dass so etwas shared variablein VHDL existiert. Laut dieser Quelle http://vhdlguru.blogspot.com/2010/03/variables-and-shared-variables.html sind sie für Simulationen verwendbar, aber nicht synthetisierbar. Also würde ich damit beginnen, die Variablen durch Signale zu ersetzen (die erforderlichen Codeanpassungen vornehmen) und erneut testen.

Und ich würde vorschlagen, ein Reset-Signal hinzuzufügen, um immer in einem definierten Zustand zu starten.

Scheint vernünftig. Ich werde Variablen durch Signale ersetzen und erneut testen. Wenn es immer noch nicht funktioniert, kann dies an einigen Metastabilitätsproblemen liegen (asynchrone eingehende Signale, wie Sie sagten). In diesem Fall werde ich versuchen, den Simulationscode als separaten SPI-Master mit unterschiedlichen Pins, aber derselben Taktdomäne zu synthetisieren. Verbinden Sie es außerhalb des FPGA (damit der Optimierer nichts davon weiß). Daher sollte der SPI-Master synchron sein, wenn die Kabel kurz genug und gleich lang sind (und vielleicht verlangsame ich die Taktrate auf einige kHz).
Übrigens: Shared variablessind im Bereich der Architektur gültig (und können von mehreren Prozessen gemeinsam genutzt werden ) wie interne Signale, während übliche Variablen auf den Bereich des Prozesses / der Funktion / des Verfahrens beschränkt sind. Ich vermute jedoch, dass sie im Code für die Hardware gefährlich sind, wie Sie sagen.
Zur Verdeutlichung: Laut dem Link in der Antwort variableist a synthetisierbar, während shared variableses angeblich nicht synthetisierbar ist. shared variablesEs scheint also gefährlich zu sein, nur zu verwenden .
Natürlich sind Variablen synthetisierbar, aber sie brauchen immer noch besondere Sorgfalt, ich sage nur, benutze Signale wann immer möglich.
@SDwarfs Freigegebene Variablen, die einen gewöhnlichen Typ (jeden Typ außer einem geschützten Typ) verwenden, waren eine temporäre Funktion, die in 1076 (VHDL)-93 eingeführt wurde und von 1076-2002 veraltet und entfernt wurde. Es ist überraschend zu sehen, dass sie immer noch von Tools unterstützt werden.