Forum: FPGA, VHDL & Co. SPI-Slave für FPGA sendet keine Daten


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Daniel K. (daniel_k80)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe einen einfachen SPI-Slave für mein FPGA entworfen und möchte 
diesen von einem Mikrocontroller auslesen, bzw. beschreiben lassen.
entity SPI_Slave is
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;

            Busy    : out STD_LOGIC;
            Rx      : out STD_LOGIC_VECTOR(7 downto 0);
            Tx      : in STD_LOGIC_VECTOR(7 downto 0);

            CPOL    : in STD_LOGIC;
            CPHA    : in STD_LOGIC;

            -- SPI connections
            SCLK    : in STD_LOGIC;
            MISO    : out STD_LOGIC;
            CS      : in STD_LOGIC;
            MOSI    : in STD_LOGIC
            );
end SPI_Slave;

architecture SPI_Slave_Arch of SPI_Slave is

    signal Rx_Data          : STD_LOGIC_VECTOR(7 downto 0)           := (others => '0');
    signal Tx_Data          : STD_LOGIC_VECTOR(7 downto 0)           := (others => '0');
    signal SCLK_ShiftReg    : STD_LOGIC_VECTOR(1 downto 0)           := (others => '0');
    signal CS_ShiftReg      : STD_LOGIC_VECTOR(1 downto 0)           := (others => '0');

begin

    process
    begin
        wait until rising_edge(Clock);

        SCLK_ShiftReg  <= SCLK_ShiftReg(0) & SCLK;
        CS_ShiftReg    <= CS_ShiftReg(0) & CS;

        -- Save the transmit data on a falling edge of CS
        if(CS_ShiftReg = "10") then
            Tx_Data <= Tx;
            Busy <= '1';
        elsif(CS_ShiftReg = "01") then
            Busy <= '0';
        end if;

        -- Shift out the data and receive new data as long as CS is pulled low
        if(CS_ShiftReg(0) = '0') then
            if(SCLK_ShiftReg = "01") then
                Rx_Data <= Rx_Data(6 downto 0) & MOSI;
                Tx_Data <= Tx_Data(6 downto 0) & '0';
            end if;
        end if;
    end process;

    Rx <= Rx_Data;
    MISO <= Tx_Data(7);
end SPI_Slave_Arch;
entity Top is
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;
            SCLK    : in STD_LOGIC;
            CS      : in STD_LOGIC;
            MOSI    : in STD_LOGIC;
            MISO    : out STD_LOGIC;
            DataOut : out STD_LOGIC_VECTOR(4 downto 0)
            );
end Top;

architecture Top_Arch of Top is

    type State_t is (State_Reset, State_WaitBusy, State_WaitForTransmit);

    signal CurrentState     : State_t                               := State_Reset;

    signal Busy             : STD_LOGIC                             := '0';
    
    signal Rx               : STD_LOGIC_VECTOR(7 downto 0)          := (others => '0');
    signal Tx               : STD_LOGIC_VECTOR(7 downto 0)          := (others => '0');

    component SPI_Slave is
        Port (  Clock   : in STD_LOGIC;
                nReset  : in STD_LOGIC;
                Busy    : out STD_LOGIC;
                Rx      : out STD_LOGIC_VECTOR(7 downto 0);
                Tx      : in STD_LOGIC_VECTOR(7 downto 0);
                CPOL    : in STD_LOGIC;
                CPHA    : in STD_LOGIC;
                SCLK    : in STD_LOGIC;
                MISO    : out STD_LOGIC;
                CS      : in STD_LOGIC;
                MOSI    : in STD_LOGIC
                );
    end component;

begin

    Slave : SPI_Slave port map (Clock => Clock,
                                nReset => nReset,
                                Busy => Busy,
                                Rx => Rx,
                                Tx => Tx,
                                CPOL => '0',
                                CPHA => '0',
                                SCLK => SCLK,
                                MISO => MISO,
                                MOSI => MOSI,
                                CS => CS
                                );

    process
        variable Counter    : UNSIGNED(7 downto 0)      := (others => '0');
    begin
        wait until rising_edge(Clock);

        case CurrentState is
            when State_Reset =>
                CurrentState <= State_WaitBusy;

            when State_WaitBusy =>
                if(Busy = '0') then
                    CurrentState <= State_WaitForTransmit;
                else
                    CurrentState <= State_WaitBusy;
                end if;

            when State_WaitForTransmit =>
                if(Busy = '1') then
                    Counter := Counter + 1;
                    CurrentState <= State_WaitBusy;
                else
                    CurrentState <= State_WaitForTransmit;
                end if;

            when others =>

        end case;
    
        Tx <= STD_LOGIC_VECTOR(Counter);
 
        if(nReset = '0') then
            CurrentState <= State_Reset;        
        end if;
    end process;

    DataOut(4) <= Busy;
    DataOut(3 downto 0) <= Rx(3 downto 0);

end Top_Arch;

Daten vom Mikrocontroller zum Slave senden funktioniert und auch das 
Senden vom Slave zum Mikrocontroller funktioniert in der Simulation, 
nicht aber in der Hardware. Aus irgendeinem Grund wird der Wert für 
`Counter` nicht erhöht. Wenn ich die Variable auf einen festen Wert 
setze, dann wird der Wert vom Mikrocontroller korrekt ausgelesen. Mit 
einem LA konnte ich zudem sehen, dass das `Busy´`-Signal korrekt 
geschaltet wird, was bedeutet das der Zustandsautomat funktioniert.

Wo könnte das Problem liegen?

Danke und Gruß

von Vancouver (Gast)


Bewertung
1 lesenswert
nicht lesenswert
Stimmt die Reset-Polarität in deinem FPGA? Du verwendest den Reset ja 
tatsächlich nur an einer einzigen Stelle im Design, und wenn der falsch 
gepolt ist, läuft die nur currentState-FSM nicht los und es wird immer 
der Initialwert des Counters ausgegeben.
Btw, warum hast du für Counter eine Variable verwendet? Man sollte auf 
Variablen verzichten, wenn es möglich ist, und hier ist es möglich. 
Falsch ist die Variable allerdings nicht an dieser Stelle.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Vancouver schrieb:
> Du verwendest den Reset ja tatsächlich
... ganz ohne ihn vorher einzusynchronisieren. Da kann es gut mal 
passieren, dass das Design am Anfang seltsame Dinge macht, weil es zum 
Start nicht sauber aus dem State_Reset herauskommt.
Siehe dazu das da:
http://www.lothar-miller.de/s9y/archives/41-Einsynchronisieren-von-asynchronen-Signalen.html

Zudem ist nach kurzem Nachdenken der Zustand State_Reset im Prinzip 
völlig unnötig. Er fügt nach dem Deaktivieren des Reset eh' nur einen 
einzigen Takt Latency ein, weil bedingungslos vom State_Reset nach 
State_WaitBusy gesprungen wird.

von Daniel K. (daniel_k80)


Bewertung
0 lesenswert
nicht lesenswert
Vancouver schrieb:
> Stimmt die Reset-Polarität in deinem FPGA? Du verwendest den Reset ja
> tatsächlich nur an einer einzigen Stelle im Design, und wenn der falsch
> gepolt ist, läuft die nur currentState-FSM nicht los und es wird immer
> der Initialwert des Counters ausgegeben.
> Btw, warum hast du für Counter eine Variable verwendet? Man sollte auf
> Variablen verzichten, wenn es möglich ist, und hier ist es möglich.
> Falsch ist die Variable allerdings nicht an dieser Stelle.

Es war tatsächlich die Reset-Polarität... :)

Jetzt funktioniert es!

Dennoch werde ich den Reset auch ordentlich einsynchronisieren.

Btw. zum Thema Reset...
Das hier hat der Mikrocontroller nach der Konfiguration des FPGAs 
empfangen:
0
1
1
1
1
1
2
3

Anscheinend hat das FPGA (obwohl der Reset aktiv war) einmal den Zähler 
erhöht. Dieses Verhalten ist sogar reproduzierbar. Das 
Einsynchronisieren scheint das Problem zu beheben.

: Bearbeitet durch User
von Vancouver (Gast)


Bewertung
2 lesenswert
nicht lesenswert
Dolle Wurst... Ich gebe anderen schlaue Ratschläge und suche dann in 
meinem eigenen Design stundenlang einen Fehler, der durch einen falsch 
gepolten Reset verursacht wurde. An die Nase fass 

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Daniel K. schrieb:
> Dieses Verhalten ist sogar reproduzierbar.
Dann ist es aber kein Verhalten, das auf ein nicht einsynchronisiertes 
Signal hinweist, denn so ein asynchrones Signal zeichnet sich durch 
völlige Zufälligkeit aus.

> (obwohl der Reset aktiv war)
Sicher? Dauernd?
Was passiert, wenn du dem Register Busy einen inaktiven Defaultwert 
gibst? Und zwar dort, wo das eigentliche Busy Register sitzt:
entity SPI_Slave is
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;

            Busy    : out STD_LOGIC := '0';
            :
Es bringt nämlich nichts, diesen Defaultwert im "Top"-Level zu vergeben, 
wo er sofort vom Pegel des Submoduls "SPI_Slave" überschrieben wird:
    signal Busy  : STD_LOGIC                    := '0';  -- Initwerte wirkungslos, denn .....
    signal Rx    : STD_LOGIC_VECTOR(7 downto 0) := (others => '0');
:
    component SPI_Slave is
        Port (  :
                Busy    : out STD_LOGIC;
                Rx      : out STD_LOGIC_VECTOR(7 downto 0);
                :
                );
    end component;

begin
    Slave : SPI_Slave port map (Clock => Clock,
                                nReset => nReset,
                                Busy => Busy,    -- ..... Pegel vom Submodul 
                                Rx => Rx,        -- überschreibt Initwert!
                                :
> Das Einsynchronisieren scheint das Problem zu beheben.
Das Einsynchronisieren sorgt nämlich auch für definierte Defaultwerte.

BTW:
  Rx <= Rx_Data;
Wenn du das erst bei der steigenden Flanke des SSn (so heißt 
eigentlich der CSn beim SPI Bus) machst, dann zappeln die Ausgänge 
nicht so herum bei der Übertragung:
   :
        elsif(CS_ShiftReg = "01") then
            Rx <= Rx_Data;
            Busy <= '0';
        end if;

BTW2:
Und im Grunde ist das Register Busy das selbe wie CS_ShiftReg(1) plus 
ein Takt Latency. Du könntest also auch einfach so schreiben:
    process
    begin
        wait until rising_edge(Clock);

        SCLK_ShiftReg  <= SCLK_ShiftReg(0) & SCLK;
        CS_ShiftReg    <= CS_ShiftReg(0) & CS;
        Busy           <= CS_ShiftReg(1); -- einfach mit 1 Takt Latency durchreichen
        :

BTW3: musst du bei inaktivem SSn/CSn nicht den MISO-Ausgang hochohmig 
schalten? Übliche SPI-Slaves machen das, damit mehr als 1 Slave an den 
Bus angeschlossen werden kann.

BTW4:
> ich habe einen einfachen SPI-Slave für mein FPGA entworfen
Nur aus Interesse: findet sich dieser Coding-Style schon irgendwo in der 
Literatur?

BTW5:
Sieh dir auch mal den 
Beitrag "Re: Erfahrung mit SPI Slave und Spartan 6 FPGA?" an... ;-)

: Bearbeitet durch Moderator
von Daniel K. (daniel_k80)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Lothar,

danke für deine ganzen Anmerkungen. Schauen wir mal ob ich sie 
beantwortet bekomme :)
Zu BTW 5: Für die Implementierung (ist bisher nur der erste Schuss, da 
ich diese noch ausbauen möchte - u. a. soll da auch noch ein Master mit 
rein) habe ich mich bei verschiedenen Quellen umgeschaut um zu schauen 
wie man so etwas machen kann, bzw. wie der SPI im Detail funktioniert.
Die Quellen sind

- 
https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html
- https://www.digikey.com/eewiki/pages/viewpage.action?pageId=7569477
- https://www.digikey.com/eewiki/pages/viewpage.action?pageId=4096096
- http://www.lothar-miller.de/s9y/categories/26-SPI-Slave
- Beitrag "Re: Erfahrung mit SPI Slave und Spartan 6 FPGA?"

Die Digikey-Beispiele fand ich irgendwie wahnsinnig komplex, ganz im 
Gegensatz zu deinem Beispiel. Was mich an deinem Beispiel "gestört" hat, 
war die fehlende Parametrierung des Modus und der fehlende Sendekanal 
vom Slave, aber für eine erste Idee nicht schlecht, da einfach und 
gepaart mit dem Thread aus dem Forum doch durchaus interessant, was so 
die Funktionsweise des SPI angeht.
Das Beispiel habe ich dann als Grundlage genommen um etwas rumzuspielen, 
bzw. den Standard aus dem TI-Link umzusetzen (sprich die Flanken fürs 
Samplen und Senden - nicht im Code oben abgebildet, da neu).

Zu BTW 4: Was genau meinst du damit? Ist das subtile Kritik oder 
subtiles Lob :D? Da stehe ich etwas auf dem Schlauch.

Zu BTW 3: Da hast du natürlich recht. Das fehlt in dem Beispiel und das 
hatte ich bisher auch noch nicht auf dem Schirm.

Zu BTW 2: Auch da hast du recht. Ich habe das Signal erst einmal rein 
genommen, weil ich noch nicht weiß ob noch eine Busy-Kondition dazu 
kommen könnte. Aktuell ist es natürlich redundant.

Zu BTW 1: Guter Hinweis. Das übernehme ich mal so. Bzgl. der SS / CS 
Konvention weiß ich leider nicht so viel. Mal lese ich CS, mal lese ich 
SS, mal was komplett anderes. :(

Zum Rest: Mmh okay. Dann war es vielleicht doch nicht die Ursache oder 
ich hatte da noch einen Knoten im Reset (der Reset liegt im FPGA-Board 
auf einem Schalter). Auf jeden Fall funktioniert es nun wunderbar und 
auch die Konfiguration von CPHA und CPOL ist nun drin.

von Lothar M. (lkmiller) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Daniel K. schrieb:
> Zu BTW 4: Was genau meinst du damit? Ist das subtile Kritik oder
> subtiles Lob :D? Da stehe ich etwas auf dem Schlauch.
Nein, nur aus Interesse gefragt. Könnte ja sein, dass nach einem 
Jahrzehnt mal einer vom asynchronen, nicht eingetakteten Reset 
weggekommen ist... ;-)

> Zu BTW 1: Guter Hinweis. Das übernehme ich mal so. Bzgl. der SS / CS
> Konvention weiß ich leider nicht so viel. Mal lese ich CS, mal lese ich
> SS, mal was komplett anderes. :(
Für SPI ist Slave Select SS# richtig. Allerdings kann man mit einem 
SPI-Interface (weil es ja nur ein Schieberegister ist) auch MicroWire 
o.ä. Bausteine ansteuern. Dort nennt sich diese Steuerleitung dann CS 
oder komplett anders.

Daniel K. schrieb:
> Die Digikey-Beispiele fand ich irgendwie wahnsinnig komplex
Ich hab mir das mal angeschaut, helle Hölle, sowas Krudes hab ich schon 
lange nicht mehr gesehen:
  PROCESS(ss_n, clk, st_load_en, tx_load_en, rx_req)
  BEGIN
  
    --write address register ('0' for receive, '1' for status)
    IF(bit_cnt(1) = '1' AND falling_edge(clk)) THEN
      wr_add <= mosi;
    END IF;

    --read address register ('0' for transmit, '1' for status)
    IF(bit_cnt(2) = '1' AND falling_edge(clk)) THEN
      rd_add <= mosi;
    END IF;
    
    --trdy register
    IF((ss_n = '1' AND st_load_en = '1' AND st_load_trdy = '0') OR reset_n = '0') THEN  
      trdy <= '0';   --cleared by user logic or reset
    ELSIF(ss_n = '1' AND ((st_load_en = '1' AND st_load_trdy = '1') OR tx_load_en = '1')) THEN
      trdy <= '1';   --set when tx buffer written or set by user logic
    ELSIF(falling_edge(clk)) THEN
:
:

    --receive registers
    --write to the receive register from master
    IF(reset_n = '0') THEN
      rx_buf <= (OTHERS => '0');
    ELSE
      FOR i IN 0 TO d_width-1 LOOP          
        IF(wr_add = '0' AND bit_cnt(i+9) = '1' AND falling_edge(clk)) THEN
          rx_buf(d_width-1-i) <= mosi;
        END IF;
      END LOOP;
    END IF;
 
Da hat (leicht erkennbar an den unnötigen Klammern und der kuriosen 
Schleife) ganz offensichtlich ein Softwareprogrammierer VHDL 
programmiert, ohne einen Funken Verständnis davon zu haben, was daraus 
synthetisiert wird... :-o

So einen kombiniert kombinatorisch synchronen Prozess mit 
unvollständigen Sensitivlisten, mit eigenartigen Clock-Enables und 
kombinatorischen Resets (Stichwort Glitches) bekommt in der Realität 
kein Mensch niemals zuverlässig zum Laufen.

Das könnte bestenfalls als Prüfungsaufgabe durchgehen: finden Sie die 10 
Fehler!

von Daniel K. (daniel_k80)


Bewertung
0 lesenswert
nicht lesenswert
Lothar M. schrieb:
> Da hat (leicht erkennbar an den unnötigen Klammern und der kuriosen
> Schleife) ganz offensichtlich ein Softwareprogrammierer VHDL
> programmiert, ohne einen Funken Verständnis davon zu haben, was daraus
> synthetisiert wird... :-o
>
> So einen kombiniert kombinatorisch synchronen Prozess mit
> unvollständigen Sensitivlisten, mit eigenartigen Clock-Enables und
> kombinatorischen Resets (Stichwort Glitches) bekommt in der Realität
> kein Mensch niemals zuverlässig zum Laufen.
>
> Das könnte bestenfalls als Prüfungsaufgabe durchgehen: finden Sie die 10
> Fehler!

Zum Glück bin ich mit meiner Meinung nicht alleine :D.

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.

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