Hallo, als blutiger VHDL-Anfänger möchte ich einen SPI Slave in ein Xilinx XC9532 programmieren. Es scheint (anhand von Debug-LEDs) als 'Empfänger' für von einem AVR generierte Daten zu funktionieren. Die Datenbytes, die über MISO aus dem CPLD rauskommen, sind aber nur 'meistens' korrekt, oft ist ein Bit gekippt. Könnt ihr mir sagen, ob die State Machine wie ich es gemacht habe, grundsätzlich funktioniert? (Ist mein erster Versuch) Und ob die Implementation der SPI-Schnittstelle so gehen kann? Einen Knackpunkt sehe ich darin, dass ich als Kriterium dafür, dass ein Byte ganz empfangen wurde, die steigende Flake des SS Signals heranziehe. Wenn ich mit einem Zähler die Taktflanken zähle kostet das wohl 2 FF mehr. Danke für alle Kritik und Verbesserungsvorschläge - für Letztere besonders ;) Als CLK verwende ich eine 48MHz Oszillator. Damit möchte ich erreichen, dass die FSM schön syncron arbeitet. Ist mir das gelungen? library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity SPI is Port ( Reset : in std_logic; CLK : in std_logic; SS : in std_logic; SCK : in std_logic; MOSI : in std_logic; MISO : out std_logic; LEDs : out std_logic_vector (7 downto 0)); end SPI; architecture Behavioral of SPI is type Zustaende is (SPI_Idle, SPI_waitSckH, SPI_waitSckL, SPI_Entscheide_Fertig, SPI_fertig); signal Zustand : Zustaende; signal SPI_empfangen : std_logic; signal SPI_Reg : std_logic_vector (7 downto 0); begin process (CLK, Reset) is begin if Reset = '1' then SPI_Reg <= (others => '0'); MISO <= '0'; Zustand <= SPI_Idle; elsif rising_edge (CLK) then case Zustand is when SPI_Idle => if SS = '0' then MISO <= SPI_Reg (7); Zustand <= SPI_waitSckH; end if; when SPI_waitSckH => if SCK = '1' then SPI_Reg <= SPI_Reg (6 downto 0) & MOSI; Zustand <= SPI_waitSckL; end if; when SPI_waitSckL => if SCK = '0' then MISO <= SPI_Reg (7); Zustand <= SPI_Entscheide_Fertig; end if; when SPI_Entscheide_Fertig => if SCK = '1' then Zustand <= SPI_waitSckH; elsif SS = '1' then Zustand <= SPI_fertig; end if; when SPI_fertig => if SPI_empfangen = '1' then SPI_empfangen <= '0'; Zustand <= SPI_Idle; else SPI_empfangen <= '1'; end if; end case; end if; end process; process (Zustand) begin if Zustand = SPI_fertig then LEDS <= SPI_Reg; end if; end process; end Behavioral;
@ Erwin Endres (erwinendres) >Könnt ihr mir sagen, ob die State Machine wie ich es gemacht habe, >grundsätzlich funktioniert? (Ist mein erster Versuch) Sieht komisch aus. >Byte ganz empfangen wurde, die steigende Flake des SS Signals >heranziehe. Das machen viele ICs. > Wenn ich mit einem Zähler die Taktflanken zähle kostet das >wohl 2 FF mehr. So what. >Als CLK verwende ich eine 48MHz Oszillator. Damit möchte ich erreichen, >dass die FSM schön syncron arbeitet. Ist mir das gelungen? Nein. Das ist auch gerade in diesem Fall weniger sinnvoll. Nutze SCK zum takten, denn die Daten sind synchron zu SCK. Ja, man kann auch mit Überabtastung arbeiten, aber das ist nicht immer sinnvoll. Dann eher die Datenübergabe nach dem Ende des SPI-Zugriffst mit der steigenden Flanke von SS synchronisieren. Etwa so.
1 | library IEEE; |
2 | use IEEE.STD_LOGIC_1164.ALL; |
3 | use IEEE.STD_LOGIC_ARITH.ALL; |
4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; |
5 | |
6 | |
7 | entity SPI is |
8 | Port ( Reset : in std_logic; |
9 | CLK : in std_logic; |
10 | SS : in std_logic; |
11 | SCK : in std_logic; |
12 | MOSI : in std_logic; |
13 | MISO : out std_logic; |
14 | |
15 | LEDs : out std_logic_vector (7 downto 0)); |
16 | end SPI; |
17 | |
18 | architecture Behavioral of SPI is |
19 | |
20 | signal SPI_Reg : std_logic_vector (7 downto 0); |
21 | signal SS_SMP: std_logic_vector (1 downto 0); |
22 | signal sync : std_logic; |
23 | |
24 | -- SPI Daten einlesen, synchron zu SCK
|
25 | |
26 | begin
|
27 | process (sck) is |
28 | begin
|
29 | if rising_edge (SCK) then -- oder falling_edge, je nach SPI Modus |
30 | if SS='0' then |
31 | SPI_Reg <= SPI_reg(6 downto 0) & MOSI; |
32 | end if; |
33 | end if; |
34 | end process; |
35 | |
36 | -- Daten synchronieren
|
37 | |
38 | begin
|
39 | process (clk) is |
40 | begin
|
41 | if rising_edge (CLK) then |
42 | ss_smp <= ss_smp(0) & SS; --SS abtasten und verzögern |
43 | if ss_smp="01" then -- steigende Flanke von SS |
44 | sync<='1'; |
45 | else
|
46 | sync<='0'; |
47 | end if; |
48 | end if; |
49 | end process; |
50 | |
51 | -- Daten verwenden, synchronisiert mit SS
|
52 | |
53 | process (clk) |
54 | begin
|
55 | if rising_edge(CLK) then |
56 | if sync='1' then |
57 | LEDS <= SPI_Reg; |
58 | end if; |
59 | end if; |
60 | end process; |
61 | |
62 | end Behavioral; |
MfG Falk
Hallo Falk, danke für deine Hilfe!! Was mir jetzt aber noch fehlt, ist das Rausschieben des SPI-Registers auf der MISO-Leitung. Ich würde (später) gern das SPI-Register von einem anderen Prozess mit einem Byte Laden und dieses Byte dann vom AVR auslesen. Also, Kommandobyte vom AVR an CPLD schicken, der soll irgendwas tun und dann ein Ergebnis dem AVR zurückgeben. Grüße erwin
@ Erwin Endres (erwinendres) >Ich würde (später) gern das SPI-Register von einem anderen Prozess mit >einem Byte Laden und dieses Byte dann vom AVR auslesen. Das ist etwas kniffliger, aber machbar. Etwa so.
1 | library IEEE; |
2 | use IEEE.STD_LOGIC_1164.ALL; |
3 | use IEEE.STD_LOGIC_ARITH.ALL; |
4 | use IEEE.STD_LOGIC_UNSIGNED.ALL; |
5 | |
6 | |
7 | entity SPI is |
8 | Port ( Reset : in std_logic; |
9 | CLK : in std_logic; |
10 | SS : in std_logic; |
11 | SCK : in std_logic; |
12 | MOSI : in std_logic; |
13 | MISO : out std_logic; |
14 | LEDs : out std_logic_vector (7 downto 0); |
15 | data : in std_logic_vector (7 downto 0); |
16 | data_avail: in std_logic -- darf nur einen Takt lang sein, wenn neue Daten anliegen |
17 | );
|
18 | end SPI; |
19 | |
20 | architecture Behavioral of SPI is |
21 | |
22 | signal SPI_Reg : std_logic_vector (7 downto 0); |
23 | signal SS_SMP: std_logic_vector (1 downto 0); |
24 | signal sync : std_logic; |
25 | |
26 | -- SPI Daten einlesen, synchron zu SCK
|
27 | |
28 | begin
|
29 | process (sck) is |
30 | begin
|
31 | if rising_edge (SCK) then -- oder falling_edge, je nach SPI Modus |
32 | if SS='0' then |
33 | SPI_Reg <= SPI_reg(6 downto 0) & MOSI; |
34 | end if; |
35 | end if; |
36 | end process; |
37 | |
38 | -- Daten synchronieren
|
39 | |
40 | begin
|
41 | process (clk) is |
42 | begin
|
43 | if rising_edge (CLK) then |
44 | ss_smp <= ss_smp(0) & SS; --SS abtasten und verzögern |
45 | if ss_smp="01" then -- steigende Flanke von SS |
46 | sync<='1'; |
47 | else
|
48 | sync<='0'; |
49 | end if; |
50 | end if; |
51 | end process; |
52 | |
53 | -- Daten verwenden, synchronisiert mit SS
|
54 | |
55 | process (clk) |
56 | begin
|
57 | if rising_edge(CLK) then |
58 | if sync='1' then |
59 | LEDS <= SPI_Reg; |
60 | end if; |
61 | end if; |
62 | end process; |
63 | |
64 | -- SPI Daten ausgeben, synchron zu SCK
|
65 | |
66 | begin
|
67 | process (sck) is |
68 | begin
|
69 | if rising_edge (SCK) then -- oder falling_edge, je nach SPI Modus |
70 | if SS='0' then |
71 | if new_data='1' then |
72 | SPI_out <= data; -- neue Daten laden |
73 | new_data_ack <='1'; |
74 | else
|
75 | SPI_out <= SPI_out(6 downto 0) & '0'; |
76 | new_data_ack <='0'; |
77 | end if; |
78 | end if; |
79 | end if; |
80 | end process; |
81 | |
82 | -- MISO Ausgang muss Tristate können, SPI-Bus
|
83 | |
84 | MISO <= SPI_out(7) when SS='0' else 'Z'; |
85 | |
86 | -- hier wird clever synchronisert, in Richtung SPI
|
87 | |
88 | process(clk, new_data_ack) |
89 | begin
|
90 | if new_data_ack='1' then |
91 | new_data <= '0'; |
92 | elseif rising_edge(clk) then |
93 | if data_avail='1' then |
94 | new_data <='1'; |
95 | end if; |
96 | end if; |
97 | end process; |
98 | |
99 | end Behavioral; |
@ Entwickler (Gast) >Falk, darf ich dich fragen, wie lange du die FPGA's schon programmierst? >Berufserfahrung in Jahren? Hmmm, wenn es um die reine FPGA-Programmierung geht, so ca. 3 Jahre. MFG Falk
Hallo Falk, herzlichen Dank für deinen umfangreichen Code! Wie schon gesagt, ich fange gerade an mich mit VHDL zu befassen. Ich versuche noch, dein Programm zu verstehen... Dazu eine Frage:
1 | > process (clk) is |
2 | > begin |
3 | > if rising_edge (CLK) then |
4 | > ss_smp <= ss_smp(0) & SS; --SS abtasten und verzögern |
5 | > if ss_smp="01" then -- steigende Flanke von SS |
6 | > sync<='1'; |
7 | > else |
8 | > sync<='0'; |
9 | > end if; |
10 | > end if; |
11 | > end process; |
Verstehe ich es richtig, dass dieser process ein High-Signal der Länge genau einer CLK-Periode dann erzeugt, wenn SS auf H geht? Bezieht sich dein Kommentar 'SS abtasten und _verzögern_' darauf, dass das Signal ss_mp erst nach dem Beenden des prozesses aktualisiert wird? Zum Verständnis: Brauche ich zwingend das Schieberegister oder ginge es eigentlich auch so: - Warum nicht ;)
1 | process (clk) is |
2 | begin
|
3 | if rising_edge (CLK) then |
4 | if SS = '1' and ss_last = '0' then |
5 | ss_last <= '1'; |
6 | sync <= '1'; |
7 | elsif ss = '1' and ss_last = '1' then |
8 | sync <= '0'; |
9 | else
|
10 | ss_last <= '0'; |
11 | end if; |
12 | end process; |
Danke, erwin
@ Erwin Endres (erwinendres) >genau einer CLK-Periode dann erzeugt, wenn SS auf H geht? Ja. >Bezieht sich dein Kommentar 'SS abtasten und _verzögern_' darauf, dass >das Signal ss_mp erst nach dem Beenden des prozesses aktualisiert wird? Das ist immer so in VHDL. Das Verzögern bezieht sich darauf, dass das SS Signal mit einem 2 Bit Schieberegister um zweit Takte verzögert wird, eben um dieses synchrone Flankenabtastung zu erzielen. >Zum Verständnis: Brauche ich zwingend das Schieberegister Ja. > oder ginge es eigentlich auch so Nein. >: - Warum nicht ;) Aus zwei Gründen. SS ist asynchron zu deinem CPLD-Takt. Das ist gefährlich. Asynchrone Signale müssen IMMER erst abgetastet werden (mit einem FlipFlop), bevor sie in der Taktdomäne verwendet werden dürfen. Bei hohen Taktfrequenzen von 50 MHz++ nimmt man sogar zwei FlipFlops in Reihe dazu, Stichwort Metastabilität. Zweitens ist deine Beschreibung ziemlich aufwändig, wenn gleich formal richtig. MFG Falk
>möchte ich einen SPI Slave in ein Xilinx XC9532 programmieren.
SPI ist eigentlich nichts anderes als ein Schieberegister.
Wenn es also z.B. nur darum geht, irgendwelche IO zu erweitern, dann ist
der Schieberegisteransatz nicht so falsch. Dazu brauchst du keinen extra
Takt, da reicht der SCLK schon aus (das ist das Schöne daran).
Ich habe das so wie im Anhang schon ein paarmal in XC95xx-CPLDs gemacht.
Die Lösung ist skalierbar und bietet Eingänge und Ausgänge.
Hallo Lothar, danke für deine Beispiele! Es geht mir nicht so sehr um I/O, mein erstes 'CPLD-Projekt' war eine schnelle (im Vergleich zum AVR) 10Bit PWM für einen LiPO-Lader die per einfachem Schieberegister geladen wird und nun tatsächlich funktioniert. Mit der Frage nach der SPI-Schnittstelle möchte ich vor allem dazulernen - ohne konkretes Projekt ist das bei mir aber wenig effizient. Ich möchte also einen kleinen Logikanalysator bauen, der per SPI mit einem Controller verbandelt ist. Dazu brauche ich eine State Machine, deshalb habe ich versucht erstmal die doch etwas überschaubarere SPI-Schnittstelle als FSM zu bauen. Daran muss ich wohl noch Arbeiten, Falk fand sie komisch. ;) Grüße erwin
@Lothar So, jetzt habe ich mal versucht deinen Code nachzuvollziehen. Was mir auffällt: die weiter oben geforderte Abtastung und Verzögerung von SS mittels FF fehlt. Darfst du das hier weil keine andere (schnelle) Taktdomäne vorhanden ist?
>Darfst du das hier weil keine andere Taktdomäne vorhanden ist? Richtig, es gibt in meinem Beispiel 2 Taktdomänen: SCLK als Schiebetakt und SS als Übernahmetakt Weil ich aber aus der Spezifikation des SPI-Protokolls herauslesen kann, dass diese beiden Takte immer in definierten zeitlichen Abständen kommen werden (es kann also keine Metastabilität usw. geben), kann ich hier das Einsynchronisieren (mit Überabtastung) sparen. Meine Lösung ist wie gesagt auf die sehr begrenzten FF-Resourcen von CPLD ausgelegt. Und mir war wichtig, ohne zusätzlichen Takt auszukommen, weil bei IO i.A. mit Optokopplern oder Magnetkopplern (IOSLoop, ADUMxxx...) eine Potentialgrenze überschritten wird. Und ich auf der "Leistungsseite" möglichst wenig Logik will. Auf einem FPGA würde ich eher den Ansatz von Falk wählen: Datenschieben mit SCLK und Datenübernahme mit dem auf den FPGA-Takt einsynchronisierten SS Das ist dann aber keine >Verzögerung sondern ein Einsynchronisieren über 2 FFs. Das brauchst du dann, wenn du eine Taktdomäne (SPI), die asynchron zum CPLD/FPGA-Quarz läuft, verarbeiten willst. Die Stichworte dazu sind Metastabilität, Setup&Hold-Zeit.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.