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


von Daniel K. (daniel_k80)


Lesenswert?

Hallo,

ich habe einen einfachen SPI-Slave für mein FPGA entworfen und möchte 
diesen von einem Mikrocontroller auslesen, bzw. beschreiben lassen.
1
entity SPI_Slave is
2
    Port (  Clock   : in STD_LOGIC;
3
            nReset  : in STD_LOGIC;
4
5
            Busy    : out STD_LOGIC;
6
            Rx      : out STD_LOGIC_VECTOR(7 downto 0);
7
            Tx      : in STD_LOGIC_VECTOR(7 downto 0);
8
9
            CPOL    : in STD_LOGIC;
10
            CPHA    : in STD_LOGIC;
11
12
            -- SPI connections
13
            SCLK    : in STD_LOGIC;
14
            MISO    : out STD_LOGIC;
15
            CS      : in STD_LOGIC;
16
            MOSI    : in STD_LOGIC
17
            );
18
end SPI_Slave;
19
20
architecture SPI_Slave_Arch of SPI_Slave is
21
22
    signal Rx_Data          : STD_LOGIC_VECTOR(7 downto 0)           := (others => '0');
23
    signal Tx_Data          : STD_LOGIC_VECTOR(7 downto 0)           := (others => '0');
24
    signal SCLK_ShiftReg    : STD_LOGIC_VECTOR(1 downto 0)           := (others => '0');
25
    signal CS_ShiftReg      : STD_LOGIC_VECTOR(1 downto 0)           := (others => '0');
26
27
begin
28
29
    process
30
    begin
31
        wait until rising_edge(Clock);
32
33
        SCLK_ShiftReg  <= SCLK_ShiftReg(0) & SCLK;
34
        CS_ShiftReg    <= CS_ShiftReg(0) & CS;
35
36
        -- Save the transmit data on a falling edge of CS
37
        if(CS_ShiftReg = "10") then
38
            Tx_Data <= Tx;
39
            Busy <= '1';
40
        elsif(CS_ShiftReg = "01") then
41
            Busy <= '0';
42
        end if;
43
44
        -- Shift out the data and receive new data as long as CS is pulled low
45
        if(CS_ShiftReg(0) = '0') then
46
            if(SCLK_ShiftReg = "01") then
47
                Rx_Data <= Rx_Data(6 downto 0) & MOSI;
48
                Tx_Data <= Tx_Data(6 downto 0) & '0';
49
            end if;
50
        end if;
51
    end process;
52
53
    Rx <= Rx_Data;
54
    MISO <= Tx_Data(7);
55
end SPI_Slave_Arch;
1
entity Top is
2
    Port (  Clock   : in STD_LOGIC;
3
            nReset  : in STD_LOGIC;
4
            SCLK    : in STD_LOGIC;
5
            CS      : in STD_LOGIC;
6
            MOSI    : in STD_LOGIC;
7
            MISO    : out STD_LOGIC;
8
            DataOut : out STD_LOGIC_VECTOR(4 downto 0)
9
            );
10
end Top;
11
12
architecture Top_Arch of Top is
13
14
    type State_t is (State_Reset, State_WaitBusy, State_WaitForTransmit);
15
16
    signal CurrentState     : State_t                               := State_Reset;
17
18
    signal Busy             : STD_LOGIC                             := '0';
19
    
20
    signal Rx               : STD_LOGIC_VECTOR(7 downto 0)          := (others => '0');
21
    signal Tx               : STD_LOGIC_VECTOR(7 downto 0)          := (others => '0');
22
23
    component SPI_Slave is
24
        Port (  Clock   : in STD_LOGIC;
25
                nReset  : in STD_LOGIC;
26
                Busy    : out STD_LOGIC;
27
                Rx      : out STD_LOGIC_VECTOR(7 downto 0);
28
                Tx      : in STD_LOGIC_VECTOR(7 downto 0);
29
                CPOL    : in STD_LOGIC;
30
                CPHA    : in STD_LOGIC;
31
                SCLK    : in STD_LOGIC;
32
                MISO    : out STD_LOGIC;
33
                CS      : in STD_LOGIC;
34
                MOSI    : in STD_LOGIC
35
                );
36
    end component;
37
38
begin
39
40
    Slave : SPI_Slave port map (Clock => Clock,
41
                                nReset => nReset,
42
                                Busy => Busy,
43
                                Rx => Rx,
44
                                Tx => Tx,
45
                                CPOL => '0',
46
                                CPHA => '0',
47
                                SCLK => SCLK,
48
                                MISO => MISO,
49
                                MOSI => MOSI,
50
                                CS => CS
51
                                );
52
53
    process
54
        variable Counter    : UNSIGNED(7 downto 0)      := (others => '0');
55
    begin
56
        wait until rising_edge(Clock);
57
58
        case CurrentState is
59
            when State_Reset =>
60
                CurrentState <= State_WaitBusy;
61
62
            when State_WaitBusy =>
63
                if(Busy = '0') then
64
                    CurrentState <= State_WaitForTransmit;
65
                else
66
                    CurrentState <= State_WaitBusy;
67
                end if;
68
69
            when State_WaitForTransmit =>
70
                if(Busy = '1') then
71
                    Counter := Counter + 1;
72
                    CurrentState <= State_WaitBusy;
73
                else
74
                    CurrentState <= State_WaitForTransmit;
75
                end if;
76
77
            when others =>
78
79
        end case;
80
    
81
        Tx <= STD_LOGIC_VECTOR(Counter);
82
 
83
        if(nReset = '0') then
84
            CurrentState <= State_Reset;        
85
        end if;
86
    end process;
87
88
    DataOut(4) <= Busy;
89
    DataOut(3 downto 0) <= Rx(3 downto 0);
90
91
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)


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. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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)


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:
1
0
2
1
3
1
4
1
5
1
6
1
7
2
8
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)


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. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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:
1
entity SPI_Slave is
2
    Port (  Clock   : in STD_LOGIC;
3
            nReset  : in STD_LOGIC;
4
5
            Busy    : out STD_LOGIC := '0';
6
            :
Es bringt nämlich nichts, diesen Defaultwert im "Top"-Level zu vergeben, 
wo er sofort vom Pegel des Submoduls "SPI_Slave" überschrieben wird:
1
    signal Busy  : STD_LOGIC                    := '0';  -- Initwerte wirkungslos, denn .....
2
    signal Rx    : STD_LOGIC_VECTOR(7 downto 0) := (others => '0');
3
:
4
    component SPI_Slave is
5
        Port (  :
6
                Busy    : out STD_LOGIC;
7
                Rx      : out STD_LOGIC_VECTOR(7 downto 0);
8
                :
9
                );
10
    end component;
11
12
begin
13
    Slave : SPI_Slave port map (Clock => Clock,
14
                                nReset => nReset,
15
                                Busy => Busy,    -- ..... Pegel vom Submodul 
16
                                Rx => Rx,        -- überschreibt Initwert!
17
                                :
> Das Einsynchronisieren scheint das Problem zu beheben.
Das Einsynchronisieren sorgt nämlich auch für definierte Defaultwerte.

BTW:
1
  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:
1
   :
2
        elsif(CS_ShiftReg = "01") then
3
            Rx <= Rx_Data;
4
            Busy <= '0';
5
        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:
1
    process
2
    begin
3
        wait until rising_edge(Clock);
4
5
        SCLK_ShiftReg  <= SCLK_ShiftReg(0) & SCLK;
6
        CS_ShiftReg    <= CS_ShiftReg(0) & CS;
7
        Busy           <= CS_ShiftReg(1); -- einfach mit 1 Takt Latency durchreichen
8
        :

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)


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. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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:
1
  PROCESS(ss_n, clk, st_load_en, tx_load_en, rx_req)
2
  BEGIN
3
  
4
    --write address register ('0' for receive, '1' for status)
5
    IF(bit_cnt(1) = '1' AND falling_edge(clk)) THEN
6
      wr_add <= mosi;
7
    END IF;
8
9
    --read address register ('0' for transmit, '1' for status)
10
    IF(bit_cnt(2) = '1' AND falling_edge(clk)) THEN
11
      rd_add <= mosi;
12
    END IF;
13
    
14
    --trdy register
15
    IF((ss_n = '1' AND st_load_en = '1' AND st_load_trdy = '0') OR reset_n = '0') THEN  
16
      trdy <= '0';   --cleared by user logic or reset
17
    ELSIF(ss_n = '1' AND ((st_load_en = '1' AND st_load_trdy = '1') OR tx_load_en = '1')) THEN
18
      trdy <= '1';   --set when tx buffer written or set by user logic
19
    ELSIF(falling_edge(clk)) THEN
20
:
21
:
22
23
    --receive registers
24
    --write to the receive register from master
25
    IF(reset_n = '0') THEN
26
      rx_buf <= (OTHERS => '0');
27
    ELSE
28
      FOR i IN 0 TO d_width-1 LOOP          
29
        IF(wr_add = '0' AND bit_cnt(i+9) = '1' AND falling_edge(clk)) THEN
30
          rx_buf(d_width-1-i) <= mosi;
31
        END IF;
32
      END LOOP;
33
    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)


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.

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.