Forum: FPGA, VHDL & Co. SPI im CPLD mit State Machine - geht das so?


von Erwin E. (erwinendres)


Lesenswert?

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;

von Falk B. (falk)


Lesenswert?

@ 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

von Erwin E. (erwinendres)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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;

von Entwickler (Gast)


Lesenswert?

Falk, darf ich dich fragen, wie lange du die FPGA's schon programmierst? 
Berufserfahrung in Jahren?

von Falk B. (falk)


Lesenswert?

@ 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

von Erwin E. (erwinendres)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

>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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

Und hier noch die Testbench ;-)

von Erwin E. (erwinendres)


Lesenswert?

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

von Erwin E. (erwinendres)


Lesenswert?

@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?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

>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
Noch kein Account? Hier anmelden.