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
libraryIEEE;
2
useIEEE.STD_LOGIC_1164.ALL;
3
useIEEE.STD_LOGIC_ARITH.ALL;
4
useIEEE.STD_LOGIC_UNSIGNED.ALL;
5
6
7
entitySPIis
8
Port(Reset:instd_logic;
9
CLK:instd_logic;
10
SS:instd_logic;
11
SCK:instd_logic;
12
MOSI:instd_logic;
13
MISO:outstd_logic;
14
15
LEDs:outstd_logic_vector(7downto0));
16
endSPI;
17
18
architectureBehavioralofSPIis
19
20
signalSPI_Reg:std_logic_vector(7downto0);
21
signalSS_SMP:std_logic_vector(1downto0);
22
signalsync:std_logic;
23
24
-- SPI Daten einlesen, synchron zu SCK
25
26
begin
27
process(sck)is
28
begin
29
ifrising_edge(SCK)then-- oder falling_edge, je nach SPI Modus
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
libraryIEEE;
2
useIEEE.STD_LOGIC_1164.ALL;
3
useIEEE.STD_LOGIC_ARITH.ALL;
4
useIEEE.STD_LOGIC_UNSIGNED.ALL;
5
6
7
entitySPIis
8
Port(Reset:instd_logic;
9
CLK:instd_logic;
10
SS:instd_logic;
11
SCK:instd_logic;
12
MOSI:instd_logic;
13
MISO:outstd_logic;
14
LEDs:outstd_logic_vector(7downto0);
15
data:instd_logic_vector(7downto0);
16
data_avail:instd_logic-- darf nur einen Takt lang sein, wenn neue Daten anliegen
17
);
18
endSPI;
19
20
architectureBehavioralofSPIis
21
22
signalSPI_Reg:std_logic_vector(7downto0);
23
signalSS_SMP:std_logic_vector(1downto0);
24
signalsync:std_logic;
25
26
-- SPI Daten einlesen, synchron zu SCK
27
28
begin
29
process(sck)is
30
begin
31
ifrising_edge(SCK)then-- oder falling_edge, je nach SPI Modus
32
ifSS='0'then
33
SPI_Reg<=SPI_reg(6downto0)&MOSI;
34
endif;
35
endif;
36
endprocess;
37
38
-- Daten synchronieren
39
40
begin
41
process(clk)is
42
begin
43
ifrising_edge(CLK)then
44
ss_smp<=ss_smp(0)&SS;--SS abtasten und verzögern
45
ifss_smp="01"then-- steigende Flanke von SS
46
sync<='1';
47
else
48
sync<='0';
49
endif;
50
endif;
51
endprocess;
52
53
-- Daten verwenden, synchronisiert mit SS
54
55
process(clk)
56
begin
57
ifrising_edge(CLK)then
58
ifsync='1'then
59
LEDS<=SPI_Reg;
60
endif;
61
endif;
62
endprocess;
63
64
-- SPI Daten ausgeben, synchron zu SCK
65
66
begin
67
process(sck)is
68
begin
69
ifrising_edge(SCK)then-- oder falling_edge, je nach SPI Modus
70
ifSS='0'then
71
ifnew_data='1'then
72
SPI_out<=data;-- neue Daten laden
73
new_data_ack<='1';
74
else
75
SPI_out<=SPI_out(6downto0)&'0';
76
new_data_ack<='0';
77
endif;
78
endif;
79
endif;
80
endprocess;
81
82
-- MISO Ausgang muss Tristate können, SPI-Bus
83
84
MISO<=SPI_out(7)whenSS='0'else'Z';
85
86
-- hier wird clever synchronisert, in Richtung SPI
@ 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
>ifrising_edge(CLK)then
4
>ss_smp<=ss_smp(0)&SS;--SS abtasten und verzögern
5
>ifss_smp="01"then-- steigende Flanke von SS
6
>sync<='1';
7
>else
8
>sync<='0';
9
>endif;
10
>endif;
11
>endprocess;
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 ;)
@ 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.