ich implementiere in meinem FPGA (Digilent CMOD7 mit einem Artix7 35) eine Hardware, die ich als ein SPI Slave ansteuern will. Dazu benutze ich dieses Modul https://github.com/nandland/spi-slave/blob/master/Verilog/source/SPI_Slave.v
Als Master benutze ich ein Nucleo Board mit einem STM32L552. Mein Problem ist, das ich zwischen FPGA und MCU keine zuverlässige Verbindung aufbauen kann. Ständig passieren Übertragungsfehler (nach ~100kB an Daten). Bei Debug Versuchen haben ich mir die Clock von dem SPI Slave im always Block ausgeben lassen, mit diesem Testcode:
1
always @(posedge i_SPI_Clk or posedge i_SPI_CS_n)
2
begin
3
if (i_SPI_CS_n)
4
begin
5
o_SPI_DBG <= 0;
6
end
7
else
8
begin
9
o_SPI_DBG <= ~o_SPI_DBG;
10
end
11
end
und dabei festgestellt, das manchmal auch zur fallenden Flanke von i_SPI_Clk getacktet wird. Gemessen habe ich alles über einen Logic Analyzer. Das ist nicht immer der Auslöser von Übertragungsfehlern, aber sehr oft.
Dabei ist auch kurrios: SPI Frequenzen von unter 1MHz scheinen problemlos zu funktionieren, dadrüber bekomme ich ziemlich sicher Übertragungsfehler. Auch Kurios: Mit einem RPi Pico funktioniert es problemlos, auch bei Frequenzen um die 30MHz.
Die SPI Verbindung ist über Jumper Wires auf einem Breadboard ausgeführt. Kabellängen sind ca 20cm.
Der i_SPI_Clk benutzt einen clock eingang im FPGA.
Ich muss ehrlich sagen, ich bin gerade ziemlich Ratlos was hier schief läuft und hoffe, hier ein paar Ideen zu bekommen, was ich ausprobieren könnte. Leider habe ich kein Oszi um mir die Clockleitung genau anzuschauen. Ich kann sie nur mit 50MHz mit dem Logic Analyzer absamplen. Das reicht sicherlich um eine Idee von dem Signal zu bekommen, aber reicht wohl bei weitem nicht für eine Analyse.
Ein weiterer Versuch war ein Serienwiderstand in die Clockleitung rein zu setzen. Das hat ein wenig geholfen, habe werte von 22-39Ohm ausprobiert, aber der große Schuss wars jetzt nicht. Bei den Versuchen habe ich den Widerstand direkt ins Nucleoboard gesteckt und bin dann über ein Jumperwire zum FPGA.
So, nun bitte ich um Hilfe, was könnte hier schief laufen? Mache ich hier was grundsätzlich falsch? Wie sieht es z.B. aus, das der SPI Slave das Clock Signal vom SPI als Clock benutzt? Wäre es besser den SPI direkt mit dem FPGA Clock zu samplen?
deine Infos sind noch ein bisschen dürftig für eine Vermutung, aber ich versuche es mal.
Wie hoch ist denn dein Clock für das FPGA? Und wie hoch ist dein angestrebter SPI clock?
Welchen Mode hast du bei der SPI gewählt? Da gibt es ja vier verschiedene, je nachdem wie und wo geshiftet und gesampled werden soll.
Und ist das auch der gleiche Mode wie bei deinem SPI Master?
Ohne Oszi wird es schwer. Dein Loigc Analyser kann nur digitale Werte anzeigen, du müsstest dir aber die Signalqualität anschauen. Erreichst du die richtigen Pegel für high und low?
Da du schreibst, dass nach etwa 100kB Fehler auftreten könnte es auch gar nichts mit deinen Signalen zu tun haben, sondern damit, wie die Daten weiter verarbeitet werden. Vielleicht gibt es ja da ein Problem?
Als erstes waeren die CPHA/CPOL Einstellungen interessant, klingt eher nach Abtastungsproblem als eingestreutes Rauschen bei langsamer Slew Rate (was ungewollte Flanken provozieren koennte), da es ja offenbar bei langsamen Frequenzen tut und der Widerstand das Verhalten noch verbessert. Grundsaetzlich wuerde ich mal den externen, im Verhaeltnis langsamen Clock abtasten und eine Entprell-Option im SPI-Core vorsehen, damit hat man auch die Slew-Rate-Probleme vom Tisch und die Moeglichkeiten, Glitches auf lange Zeit zu detektieren.
vielen Dank für die Antworten. Das Design läuft mit 100MHz. SPI soll so schnell wie möglich betrieben. Aktuell wird er im Mode 0 und mit ~14MHz betrieben, hoffe aber das ich mich noch auf 25MHz steigern kann. Je schneller, um so besser.
Mit ein paar weiteren Experimenten konnte ich das Problem weiter eingrenzen. Meine ursprüngliche Annahme mit dem Clock ist falsch, ganz offensichtlich ist es die der Chip Select der das Problem verursacht. Dabei folgender Test Code:
1
always @(posedge i_SPI_Clk or posedge i_SPI_CS_n)
2
begin
3
if (i_SPI_CS_n)
4
begin
5
debug <= 0;
6
end
7
else
8
begin
9
debug <= 1;
10
end
11
end
Ein Serien Widerstand in der Chip Select Leitung erzeugt einen deutlich besseres Ergebniss, und auch das mit dem Logic Analyzer gemessene Rauschen ist deutlich geringer, trotzdem passiert es ab und zu, das während dem SPI Transfer der CS gezogen wird und dadurch der Slave nicht mehr im Sync mit dem Master ist, wie in dem glitch Bild zu sehen.
ganz offensichtlich ist es die der Chip Select der das Problem
verursacht.
Der ist doch eh dauerhaft auf Low-Pegel (warum eigentlich?).
Das Gezappel, das du da noch im mV Bereich siehst, ist Übersprechen vom gesamten fliegenden Aufbau zusammen mit den Übergangswiderständen auf dem Steckbrett.
Sieh dir auch mal an, wie so eine übliche Übertragung per SPI abläuft. Da geht vor dem Telegramm der SS auf low und bleibt dort stabil bis zum Ende der Übertragung. Und dann geht er wieder auf high, damit sich der Slave wieder mal auf den Master synchronisieren kann, falls er mal ausser Takt kommt. Solange der SS low ist werden die Daten vom MOSI mit dem SCLK eingetaktet.
Wie sieht es z.B. aus, das der SPI Slave das Clock Signal vom SPI als
Clock benutzt?
Geht schon. Du musst dann nur einen Mechanismus zum Queren der Taktdomäne von SCLK zum FPGA-Takt ausdenken.
Wäre es besser den SPI direkt mit dem FPGA Clock zu samplen?
Das geht laut Nyquist nur, wenn du einen FPGA-Takt >> SCLK hast. Und wenn du da tatsächlich bis auf 100 MHz hoch willst (bist du sicher, dass du das brauchst?), dann mustt du mit 500MHz samplen.
Die SPI Verbindung ist über Jumper Wires auf einem Breadboard ausgeführt.
Nur interessehalber: du hast schon auch die Masse vom µC mit der Masse vom FPGA verbunden?
Sieh dir auch mal an, wie so eine übliche Übertragung per SPI abläuft.
Da geht vor dem Telegramm der SS auf low und bleibt dort stabil bis
zum Ende der Übertragung. Und dann geht er wieder auf high, damit sich
der Slave wieder mal auf den Master synchronisieren kann, falls er mal
ausser Takt kommt. Solange der SS low ist werden die Daten vom MOSI mit
dem SCLK eingetaktet.
So ähnlich läuft es auch ab. Aktuell wird die CSN Leitung auf low gezogen, sobald das System startet. Danach bleibt sie auf low, da es nur einen Slave gibt und die Daten gestreamed werden. Natürlich könnte ich nach jeder Transaktion die CSN Leitung wieder auf high ziehen und vor der neuen die CSN Leitung wieder auf low. Die Datenübertragung würde aber dadurch nicht stabiler werden, da eben zwischen den Bits dieses Event auftreten kann. Warum auch immer, und genau das versuche ich irgendwie rauszufinden. Oft tritt das Event auch einfach zwischen den Bytes auf und da ist es nicht weiter schlimm.
Es ist nur so, ich würde erwarten, das wenn ich den Pin am FPGA auf low ziehe (egal ob durch einen GPIO, Pulldown oder direkt mit GND), das an der CSN Leitung genau garnichts passiert. Der Reset vom FPGA wird auch durch einen anderen GPIO getriggert. Ich will genauso wenig das der FPGA zufällig resetted wird.
Wie sieht es z.B. aus, das der SPI Slave das Clock Signal vom SPI als
Clock benutzt?
Geht schon. Du musst dann nur einen Mechanismus zum Queren der
Taktdomäne von SCLK zum FPGA-Takt ausdenken.
Das wird in dem SPI_Slave erledigt.
Wäre es besser den SPI direkt mit dem FPGA Clock zu samplen?
Das geht laut Nyquist nur, wenn du einen FPGA-Takt >> SCLK hast. Und
wenn du da tatsächlich bis auf 100 MHz hoch willst (bist du sicher, dass
du das brauchst?), dann mustt du mit 500MHz samplen.
Nein, der FPGA takt läuft mit 100MHz, beim SPI würde ich so hoch gehen wollen, wie es geht, aber 25MHz würden mir für den SPI erstmal reichen :)
Die SPI Verbindung ist über Jumper Wires auf einem Breadboard ausgeführt.
Nur interessehalber: du hast schon auch die Masse vom µC mit der Masse
vom FPGA verbunden?
Natürlich, alle Massen sind miteinander verbunden. Das FPGA-Board wird durch die 5V vom Nucleo Board versorgt.
Aktuell wird die CSN Leitung auf low gezogen, sobald das System startet.
Danach bleibt sie auf low
Das ist ein Designfehler. Ein einziger Störimpuls und du bist den Rest des Tages um 1 Bit versetzt.
SPI funktioniert nur in der Theorie ohne den SS#.
Ich will genauso wenig das der FPGA zufällig resetted wird.
Du hast den Reset aber schon so ausgeführt und einsynchronisiert, dass nicht bereits ein kleiner ESD-Spike ausreicht, um das Design (evtl. sogar nur partiell) zurückzusetzen?
Das ist ein Designfehler. Ein einziger Störimpuls und du bist den Rest
des Tages um 1 Bit versetzt.
SPI funktioniert nur in der Theorie ohne den SS#.
Mir gehts hier vorallem rauszufinden und zu verstehen, warum überhaupt so viele Störimpulse auf der SS# Leitung vorhanden sind, und warum ich es nur mit einem Serienwiderstand in der SS# Leitung zum laufen bekomme. Das kann doch nicht sein ... da muss doch was grundsätzliches faul sein.
Du hast den Reset aber schon so ausgeführt und einsynchronisiert, dass
nicht bereits ein kleiner ESD-Spike ausreicht, um das Design (evtl.
sogar nur partiell) zurückzusetzen?
Der Reset wird über das Xilinx Processor System Reset Module einsynchronisiert. Das Teil wartet auch bis das OK vom Clock Wizard kommt.
warum überhaupt so viele Störimpulse auf der SS# Leitung vorhanden sind
Mal abgesehen davon, dass die paar mV sicher noch nichts ausmachen, ist die Ursache ganz einfach: es sind Einkopplungen von steilflankigen Schaltvorgängen (die kurzen, einmal auftretenden Peaks) und eine undefinierte Signal- bzw. Masseführung (die an den "Bits" erkennbare Potentialverschiebung).
Mach es einfach mal so: klemm die Masseklemme des Oszis direkt(!) an die Masse des µC. Also nicht irgendwo in die Nähe des µC, sondern an einen kurzen Draht, den du an die Masse direkt am µC angelötet hast.
Und miss dann mit der Tastkopfspitze direkt an der Masse am FPGA.
Man würde jetzt wegen Masse=Masse erwarten, dass da eine durchgehende schnurgerade Linie auf der 0V Linie des Oszis zu sehen ist.
Was siehst du stattdessen?
Das, was du da siehst, ist ein Spannungsabfall auf der Masseleitung am Leitungswiderstand incl. aller Übergangswiderstände im Steckbrett. Dein Ziel muss nun sein, dieses Gezappel möglichst "schurgerade" auf 0V herunter zu bekommen.
oder an der Leiterplatte, oder den FPGA-Constraints. Das Kuriose ist, dass die Störungen auf der Leitung ja auch dann sichtbar sind, wenn sich beim SPI nichts tut, wenn man das Diagram anschaut.
ich weiss nicht, warum jeder reflexartig immer versucht, sich etwas runterzuladen. Ich habe noch nie irgendwas von diesem Plattformen gebrauchen können, weil der Code dort nicht passt. Oft ist er funktionell nicht zur Aufgabe passend und nicht selten auch noch technisch schlecht.
Das zu vereinen kostet am Ende mehr Aufwand, als die 10 Zeilen selber hinzuschreiben, wie man es braucht.
Wenn ich z.B. diese Art der Verknüpfung sehe:
"always @(posedge i_SPI_Clk or posedge i_SPI_CS_n)"
und dann die Xilinx System Reset Strat angesprochen wird, dann sehe ich schon wieder X Probleme.
Ein SPI-Slave-IF muss gefahren werden, wie ein Chip, d.h. mit dessen Takt, dessen Select und dessen Daten. Entweder komplett aysnchron mit angepassten timings und späterem CRC des Registers, oder eben komplett virtualisiert mit Synchronisation der Signale in die/der Zieldomain.
Darüber muss man sich Gedanken machen und dann die Lösung selektieren.
Was sagt denn eigentlich die Simulation?
Wenn ich z.B. diese Art der Verknüpfung sehe:
"always @(posedge i_SPI_Clk or posedge i_SPI_CS_n)"
und dann die Xilinx System Reset Strat angesprochen wird, dann sehe ich
schon wieder X Probleme.
Ein SPI-Slave-IF muss gefahren werden, wie ein Chip, d.h. mit dessen
Takt, dessen Select und dessen Daten. Entweder komplett aysnchron mit
angepassten timings und späterem CRC des Registers, oder eben komplett
virtualisiert mit Synchronisation der Signale in die/der Zieldomain.
oder eben komplett
virtualisiert mit Synchronisation der Signale in die/der Zieldomain.
In meinen Designs synchronisiere ich immer über /CS.
Die fallende Flanke übernimmt die Daten in das Senderegister und die steigende Flanke übergibt die empfangenen Daten ins restliche Design.
/CS muß natürlich nach allen Regeln der Kunst einsynchronisiert werden (2xFF).
/CS muß natürlich ... einsynchronisiert werden (2xFF).
Das ist hier nicht nötig, denn hier ist der SPI-Slave eine eigene Taktdomäne mit dem i_SPI_Clk als Takt. Man muss dann eben sehr aufpassen mit der Übergabe in die
Das ist hier über einen Fifo gelöst, allerdings geht auch so manches Signal asynchron über diese Taktgrenze.
Sagen wir mal so: weil der lokale SPI-Takt noch kombinatorisch invertiert wird, ist es recht spannend, was denn der Synthesizer da überhaupt draus macht und ob der Takt es auf ein Taktnetz schafft oder ob er über "normales" Routing händisch lokal verdrahtet wird.
Und die Implementierung des SPI-Senders als Multiplexer macht die Sache nicht effizienter:
weil da regelmäßig Beschreibungen mit asynchronen Resets und synchronen Resets gemischt werden, was zu teilweise absonderlichen Schaltungen und problematischen Verhalten führt.
In meinen Designs synchronisiere ich immer über /CS.
Ja, das wäre die zweite von mir beschriebene Möglichkeit.
/CS muß natürlich nach allen Regeln der Kunst einsynchronisiert werden
(2xFF).
Läuft dann aber alles stromfressend auf dem sehr hohen Systemtakt und hat zumindest kleine Latenzen. Bei sehr niedrigem Systemtakt in einem BAT-System wiederum kann es ein timing Problem geben.
Und dann gibt es spezielle Designs, die keinen kontinuierlichen Systemtakt mit PLL haben dürfen, weil das FPGA patient sein muss. Dann geht das nur oldstyle asynchron direkt in Kombinatorik.
weil der lokale SPI-Takt noch kombinatorisch
invertiert wird, ist es recht spannend, was denn der Synthesizer da
überhaupt draus macht
Solche Geschichten fange ich durch meine Design STRAT ab: Es gibt ein physisches Design und ein darin innliegendes Logik design. In letzterem gibt es kein invertierten Takte oder low aktive Geschichten. Das wird alles brav außen abgefangen. Wenn da negierte Takte / Takte vorkommen, muss man immer entscheiden, ob die wirklich negiert sind, wegen low activer Ports am Chip, oder ob sie verschoben sind und wie man das intern korrigiert. Bei einem kontinuierlich laufenden Takt würde man das z.B. auch an einer PLL abfangen (können).
Negierte Takte gibt es im FGPA nur an den Stellen, wo die Zellen das physisch erfordern, z.B. an DDR-Takt-Treibern und DDR-IO-Zellen, die wirklich 2 Takteingänge haben und auch das steckt bei mir immer im äußeren Toplevel.