mikrocontroller.net

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


Autor: Erwin Endres (erwinendres)
Datum:

Bewertung
1 lesenswert
nicht 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;

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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.

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

signal SPI_Reg : std_logic_vector (7 downto 0);
signal SS_SMP: std_logic_vector (1 downto 0);
signal sync : std_logic;

-- SPI Daten einlesen, synchron zu SCK

begin
process (sck) is
begin
  if rising_edge (SCK) then            -- oder falling_edge, je nach SPI Modus
    if SS='0' then
      SPI_Reg <= SPI_reg(6 downto 0) & MOSI;
    end if;
  end if;
end process;

-- Daten synchronieren

begin
process (clk) is
begin
  if rising_edge (CLK) then
    ss_smp <= ss_smp(0) & SS;    --SS abtasten und verzögern
    if ss_smp="01" then          -- steigende Flanke von SS
      sync<='1';
    else 
      sync<='0';
    end if;
  end if;
end process;

-- Daten verwenden, synchronisiert mit SS

process (clk)
begin
  if rising_edge(CLK) then
    if sync='1' then
      LEDS <= SPI_Reg;
    end if;
  end if;
end process;

end Behavioral;

MfG
Falk

Autor: Erwin Endres (erwinendres)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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.
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);
           data : in std_logic_vector (7 downto 0);
           data_avail: in std_logic            -- darf nur einen Takt lang sein, wenn neue Daten anliegen
           );
end SPI;

architecture Behavioral of SPI is

signal SPI_Reg : std_logic_vector (7 downto 0);
signal SS_SMP: std_logic_vector (1 downto 0);
signal sync : std_logic;

-- SPI Daten einlesen, synchron zu SCK

begin
process (sck) is
begin
  if rising_edge (SCK) then            -- oder falling_edge, je nach SPI Modus
    if SS='0' then
      SPI_Reg <= SPI_reg(6 downto 0) & MOSI;
    end if;
  end if;
end process;

-- Daten synchronieren

begin
process (clk) is
begin
  if rising_edge (CLK) then
    ss_smp <= ss_smp(0) & SS;    --SS abtasten und verzögern
    if ss_smp="01" then          -- steigende Flanke von SS
      sync<='1';
    else 
      sync<='0';
    end if;
  end if;
end process;

-- Daten verwenden, synchronisiert mit SS

process (clk)
begin
  if rising_edge(CLK) then
    if sync='1' then
      LEDS <= SPI_Reg;
    end if;
  end if;
end process;

-- SPI Daten ausgeben, synchron zu SCK

begin
process (sck) is
begin
  if rising_edge (SCK) then            -- oder falling_edge, je nach SPI Modus
    if SS='0' then
      if new_data='1' then
        SPI_out <= data;               -- neue Daten laden
        new_data_ack <='1';
      else
        SPI_out <= SPI_out(6 downto 0) & '0';
        new_data_ack <='0';
      end if;
    end if;
  end if;
end process;

-- MISO Ausgang muss Tristate können, SPI-Bus

MISO <= SPI_out(7) when SS='0' else 'Z';

-- hier wird clever synchronisert, in Richtung SPI

process(clk, new_data_ack)
begin
  if new_data_ack='1' then
    new_data <= '0';
  elseif rising_edge(clk) then
    if data_avail='1' then
      new_data <='1';
    end if;
  end if;
end process;

end Behavioral;

Autor: Entwickler (Gast)
Datum:

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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Erwin Endres (erwinendres)
Datum:

Bewertung
0 lesenswert
nicht 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:
> process (clk) is
> begin
>   if rising_edge (CLK) then
>     ss_smp <= ss_smp(0) & SS;    --SS abtasten und verzögern
>     if ss_smp="01" then          -- steigende Flanke von SS
>       sync<='1';
>     else
>       sync<='0';
>     end if;
>   end if;
> 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 ;)
process (clk) is
begin
   if rising_edge (CLK) then
      if SS = '1' and ss_last = '0' then
         ss_last <= '1';
         sync <= '1';
      elsif ss = '1' and ss_last = '1' then    
         sync <= '0';    
      else
         ss_last <= '0';
      end if;
end process;

Danke, erwin

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Und hier noch die Testbench ;-)

Autor: Erwin Endres (erwinendres)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Erwin Endres (erwinendres)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.