Ich bin neu in der VHDL/FPGA-Programmierung und habe ein seltsames Verhalten in meiner SPI-Slave-Implementierung festgestellt. Was ich getan habe:
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.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 signals
sie 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ß, variables
verhalten 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???
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;
#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:
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).
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:
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?
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
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 variable
in 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.
Shared variables
sind 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.variable
ist a synthetisierbar, während shared variables
es angeblich nicht synthetisierbar ist. shared variables
Es scheint also gefährlich zu sein, nur
zu verwenden .
Elliot Alderson
SDwarfs
SDwarfs
SDwarfs