
In früheren Artikeln haben wir uns bereits mit dem
Avalon-MM- Bus getroffen, bei dem MM für Memory Mapped steht, dh auf Memory projiziert. Dieser Reifen ist sehr vielseitig. Es können mehrere Master- (Master) und mehrere Slave-Geräte daran angeschlossen werden. Wir haben bereits zwei führende Geräte gleichzeitig verbunden (Instruction Master und Data Master), da der NIOS II-Prozessor über eine Harvard-Architektur verfügt, sodass die Befehls- und Datenbusse unterschiedlich sind. Viele Autoren verbinden sie jedoch mit demselben gemeinsamen Gerät, um die Softwareentwicklung von außen zu vereinfachen zum Bus.
Wenn ein Block auf dem Bus über DMA-Funktionen (Direct Memory Access) verfügt, enthält er auch einen Master für den Bus.
Tatsächlich beruht die Hauptunannehmlichkeit dieses Reifens auf dieser Tatsache (viele führende, viele Anhänger). Als wir unseren Slave entworfen haben, mussten wir die Adresse dekodieren. Als ich meinen Anführer machte, gab es viel mehr Aufhebens um die Schiedsgerichtsbarkeit. Ein roter Faden durch die gesamte Artikelserie ist jedoch die Behauptung, dass die Entwicklung unter Redd ein Hilfsteil des Projekts ist und nicht zu viel Arbeit erfordern sollte. Und wenn wir uns von der Routine befreien können, müssen wir uns davon befreien.

Alle Artikel des Zyklus:
- Entwicklung der einfachsten „Firmware“ für in Redd installierte FPGAs und Debugging am Beispiel des Speichertests
- Entwicklung der einfachsten „Firmware“ für in Redd installierte FPGAs. Teil 2. Programmcode
- Entwicklung eines eigenen Kerns zur Einbettung in ein auf FPGA basierendes Prozessorsystem
- Entwicklung von Programmen für den Zentralprozessor Redd am Beispiel des Zugriffs auf das FPGA
Das uns bereits bekannte Dokument mit den
Avalon-Schnittstellenspezifikationen (im Allgemeinen gebe ich keine direkten Links an, da sie sich immer ändern, sodass das gesamte Netzwerk mit Artikeln mit toten Links übersät ist. Es ist einfacher, die aktuelle Position zu finden, indem der Name in die Suchmaschine eingegeben wird) berichtet, dass zusätzlich zum
Avalon-MM- Bus Es gibt auch einen
Avalon-ST- Bus, bei dem ST für Stream steht, dh Streaming. Tatsache ist, dass die übertragenen Daten sehr oft eine Stream-Struktur haben. Ja, sogar der klassische Sektor der Festplatte. Es hat eine feste Größe. Es sollte von Anfang bis Ende weitergegeben werden. Selbst wenn wir es im adressierbaren Bereich betrachten, werden die Adressen linear zunehmen. Und wenn Sie den FIFO-Block zum Speichern verwenden, sind die darin enthaltenen Adressen für uns vollständig verborgen. Sie sind es, aber mit ihnen zu arbeiten ist nicht unser Anliegen.
Gleiches gilt für viele andere Streaming-Daten: Sie gehen immer von Anfang bis Ende und werden nacheinander in Repositorys abgelegt. Genau diese Streaming-Protokolle werden zum Übertragen solcher Daten verwendet. Neben dem Fehlen einer expliziten Adressierung ist der
Avalon-ST- Bus insofern interessant, als er immer zwei Geräte verbindet: eine Quelle und einen Empfänger. Es gibt immer zwei davon. Ein Gerät ist immer die Quelle, das zweite ist immer der Empfänger. Daher betreffen Probleme bei der Schlichtung dieses Busses nicht. So sehen typische Gerätepaare aus, die an diesen Bus angeschlossen sind:

Und hier sind die typischen Signale dieses Busses:

Darüber hinaus sind die
Fehlerzeilen optional, sie übertragen von uns zugewiesene binäre Fehlercodes und wir können sagen, dass es keine Fehlercodes gibt. Und die Kanalnummernleitungen werden, wie wir oben gesehen haben, nur benötigt, wenn das Demultiplexen weiter durchgeführt wird. Wenn nicht, wird die Kanalnummer nicht benötigt. Wir werden vorerst darauf verzichten. Es bleiben drei Zeilen übrig: Daten, ein Bereitschaftssignal und ein Datenbestätigungssignal (Strobe). Nun, ein weiteres Taktsignal, da der Bus synchron ist.
Aus der Dokumentation folgt auch, dass drei weitere Signale möglich sind, die dem Bus die Übertragungseigenschaften klar definierter Pakete hinzufügen:

Im Allgemeinen ist der Reifen sehr interessant, und heute werden wir damit experimentieren. Wie wir bereits wissen, ist das FPGA über die im
FT245-SYNC- Modus betriebene
FT2232H- Brücke mit dem USB-Bus des Redd-Komplexes verbunden. Genau genommen handelt es sich bei den Daten, die über diese Schnittstelle übertragen werden, um Streaming-Daten. Heute lernen wir, wie wir diese Daten auf Basis von NIOS II auf unser Prozessorsystem übertragen können. Es ist schade, dass das
FT245-SYNC- Protokoll, obwohl es gestreamt wird, nicht vollständig mit dem
Avalon-ST- Bus
übereinstimmt . Um Chipbeine zu sparen, verfügt es über einen bidirektionalen Datenbus, und der
Avalon-ST- Bus ist unidirektional. Wir müssen also einen Block erstellen, der enge, aber nicht übereinstimmende Protokolle koordiniert.
Das
FT245-SYNC- Protokoll haben wir bereits in einem
der vorherigen Artikel kennengelernt . Ich
möchte Sie daran erinnern, dass die Beschreibung im Dokument
AN_130 FT2232H enthalten ist, das in einem synchronen FT245-FIFO-Modus verwendet wird . Hier ist ein typisches Zeitdiagramm für die Übertragung von einer Brücke zu einem FPGA

Im Allgemeinen bin ich als Programmierer sehr daran interessiert, dass das übertragene Paket deutlich Anfang und Ende markiert hätte. Um die Logik des UDP-Protokolls logischer zu gestalten, müssen Sie dem Stream spezielle Benchmark-Daten hinzufügen, die für meine Programmierung, Bemühungen und Prozessorzyklen aufgewendet werden, wenn die Übertragung im TCP-Stil erfolgt. Es sieht aus wie bei der RXF-Leitung kann uns dabei helfen. Wir überprüfen ... Wir füllen die im
vorherigen Artikel angegebene „Firmware“ im FPGA aus, um die Leistung zu messen, und schließen die Oszilloskopsonde an die RXF-Leitung an. Als Testprogramm für den Redd-Zentralprozessor verwenden wir die Basis, die auch zur Messung der Leistung verwendet wird. Statt große Datenmengen zu senden, senden wir einen monolithischen Block mit 0 x 400 Byte.
uint8_t temp [maxBlockSize]; memset (temp,0,sizeof (temp)); uint32_t dwWritten; FT_Write(ftHandle0, temp, 0x400, &dwWritten);
Wir erhalten das folgende Bild auf der RXF-Linie:

Es ist klar, dass die Mikroschaltung 0x200 Byte Puffer empfängt (das ist, wie viel es in einem USB2.0 HS-Paket enthalten kann), und diese dann an den Kanal sendet. Im Allgemeinen ist dies seltsam, da in der Dokumentation angegeben ist, dass in jeder Richtung zwei Puffer verwendet werden. Während der Übertragung sollte der zweite Puffer Zeit zum Auffüllen gehabt haben. Leider. Das Ende der Füllung ist eindeutig spät. Dies zeigt tatsächlich, warum die Leistung die theoretischen 52 Megabyte pro Sekunde nicht erreicht: Ein großer Prozentsatz der Zeit (wenn auch nicht 50%) wird einfach nicht übertragen.
Aber auf die eine oder andere Weise, und wir haben herausgefunden, dass es möglich ist, den Beginn eines Pakets an einer negativen RXF-Flanke nur zu erkennen, wenn die Paketgröße 0x200 Bytes nicht überschreitet. Wenn wir nur Befehle mit einer kleinen Datenmenge an das Gerät senden, ist dies durchaus erreichbar. Wenn wir jedoch große Datenströme senden, müssen wir einen kontinuierlichen Kanal verwenden, der in seiner Logik UART (oder beispielsweise dem TCP-Kanal) ähnelt und die Paketgrenzen rein programmatisch hervorhebt.
Im Allgemeinen verwenden wir zur Vereinfachung der Darstellung die Streaming-Version als Grundlage. Wir werden heute keine Pakete berücksichtigen. Nun, welche Version des
Avalon-ST- Busses wir als Grundlage nehmen, ist klar. Wir beginnen unseren Block zu entwerfen. Wie oben erwähnt, müssen wir nicht nur eine Brücke, sondern auch einen Switch herstellen, da der
FT245FIFO- Bus bidirektional und der
Avalon-ST- Bus unidirektional ist. Das heißt, es müssen zwei
Avalon-ST- Busse gleichzeitig hergestellt werden: Ausgang und Eingang.

Wir beginnen langsam, einen Automaten zu entwickeln, der die Logik implementiert, die wir brauchen. Natürlich wird diese Logik im Artikel maximal vereinfacht. Beginnen wir mit der Übertragung von Daten vom FPGA auf den PC, da dieser Vorgang etwas einfacher ist (Sie müssen den Status der OE-Leitung, über den wir im
letzten Artikel gesprochen haben, nicht ändern). Das heißt, wir implementieren den Sink-Port.
Von der Seite des
Avalon-ST- Busses aus habe ich die folgende Betriebsart gewählt (es gibt sehr viele davon im Dokument, aber diese ist der Schnittstelle zum
FT245-SYNC am nächsten):

Lassen Sie mich Sie an die Richtung der Signale erinnern:

Das heißt, wir warten nur auf die Bestätigung im Bus (
gültig ), klicken auf die Daten und versehen diese Tatsache mit der
Bereitschaftsleitung .
Von der Seite
FT245_FIFO sieht
das Protokoll folgendermaßen aus:

Es stellt sich heraus, dass wir auf das TXE-Signal warten und die Daten mit dem WR # -Signal versehen müssen (die Polarität ist für beide Signale invers).
TXE # ist in seiner Funktionalität sehr ähnlich zu
Ready , und WR # ist
gültig . Die Details sind etwas anders, aber die Logik ist ähnlich.
Es stellt sich heraus, dass wir einen einzelnen Zustand zu PC herausgreifen können, in dem die einfachsten Kommutierungen einiger Zeilen durchgeführt werden. Die Bedingung für den Eintritt in diesen Zustand ist die Bereitschaft beider Parteien zur Übertragung, dh (TXE # == 0) UND (gültig == 1). Sobald ein Teil der Bereitschaft verschwunden ist, kehren wir in den Leerlauf zurück.
Der Übergangsgraph des Automaten ist noch einfach:

Und die Schalttabelle ist wie folgt (wo die Namen der Signale mehrdeutig sind, werden ihnen Indizes hinzugefügt, wo die Namen eindeutig sind - es gibt keine Indizes):
Weiter zu einer etwas komplexeren Übertragung von Source zu FT245_FIFO. Wie wir
im vorherigen Artikel gesehen haben , besteht die Komplikation darin, die Richtung mit dem OE # -Signal zu wechseln:

Für den
Avalon_ST- Bus
ist alles wie zuvor, daher werden die Bilder nicht ein zweites Mal angezeigt, aber jetzt befinden wir uns an der Quellposition.
Hier entspricht die RXF # -Leitung der
gültigen Leitung und die RD # -Leitung der
Bereitschaftsleitung . Nun gut, fügen Sie der Maschine ein paar Zustände hinzu:

und die folgende Logik für in diesem Zustand aktive Signale:
Es ist klar, dass das Schema nicht das idealste war. Es gibt verschiedene Nuancen, die mit Pufferüber- oder -unterläufen verbunden sind. Es sollte jedoch keinen Datenverlust geben, aber für die Optimalität müssen Sie irgendwo anfangen!
Wir beginnen, die entwickelte Theorie auf SystemVerilog-Code zu übertragen. Es stimmt, wir können nicht alle Funktionen von SystemVerilog verwenden. Es gab einen Fall, in dem ich einen
großen Artikel schrieb, in dem ich die praktische Synthetisierbarkeit der schönen Merkmale dieser Sprache in einer realen Entwicklungsumgebung testete. Hier bitten wir nur um die Verwendung von Schnittstellen, da das System zwei Instanzen vom
Typ Avalon-ST haben wird . Ach und ah. Hier ist der Testcode:
interface AvalonST #(parameter width=8)(input clk); logic [width-1:0] data; logic ready; logic valid; modport source (input clk, ready, output data,valid); modport sink (input clk, data, valid, output ready); endinterface module FT245toAvalonST ( AvalonST.source source, AvalonST.sink sink ); //assign source.ready = sink.valid; assign sink.ready = source.valid; endmodule
Es ist im Hauptcompiler perfekt synthetisiert (eine kommentierte Zeile beim Löschen eines Kommentars führt zu einem Fehler, um sicherzustellen, dass der Synthesizer alles richtig interpretiert). Wenn Sie jedoch die Schaltfläche
Synthesedateien analysieren für eine Komponente für diesen Code
aktivieren , wird ein Fehler
generiert, dass der Typ
AvalonST unbekannt ist. Das heißt, die Analyse dort erfolgt nicht auf SystemVerilog, sondern auf reinem Verilog. Wie schade.

Darüber hinaus wird die Sprache korrekt bestimmt, nur der Analysator versteht die Schnittstellen zwischen den Ports nicht.

Im Allgemeinen müssen Sie die hässliche alte Syntax verwenden.
Mit dieser Syntax erhalten wir die folgende Modulschnittstelle: module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data );
Unhöflich, Vintage, aber was können Sie tun.
Wir realisieren den Übergangsgraphen des Automaten ohne Schnickschnack: // enum {idle, toPC, dropOE, fromPC} state = idle; // always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end
Die Steuerung der Ausgänge bedarf jedoch einiger Erläuterungen.
Ein Teil der Installationen erfolgt „auf der Stirn“: // // , , // - . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; // , // assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end
Beispielsweise sollte für einen bidirektionalen Datenbus eine typische Lösung angewendet werden. Wie wir uns erinnern, wird es im Schnittstellenteil wie folgt deklariert:
inout [7:0] ft245_data,
und das Lesen davon kann auf die übliche Weise erfolgen. In unserem Fall wickeln wir einfach alle Daten in die Daten des ausgehenden
Avalon-ST- Busses ein:
// assign source_data = ft245_data;
Aber im Allgemeinen können Sie immer aus dem Bus lesen und nach Belieben. Sie sollten jedoch mit dem Multiplexer darauf schreiben. Wenn wir Daten in den Bus schreiben, sollten diese Daten von jedem anderen vorbereiteten Bus stammen. In der Regel wird eine Variable vom Typ
reg (oder eine neue
Logik ) in einem Modul aufgelöst. In unserem Fall existiert ein solcher Bus bereits. Dies ist der
sink_data- Bus. In anderen Fällen wird der Zustand Z ausgegeben. Wenn Sie mit der Schaltung vertraut sind, kennen Sie einen typischen Ausgabepuffer. Entweder werden Eingabedaten übersprungen oder es wird in den Z-Zustand versetzt. In unserem Code sieht dieser Multiplexer folgendermaßen aus:
// inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz;
Und noch ein Signal ft245_siwu. Wir verwenden es nie, also ziehen Sie es gemäß der Dokumentation zu FT2232H zur Einheit:
// FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1;
Eigentlich ist das alles.
Das ganze Modul sieht so aus: module FT245toAvalonST ( input clk, input reset, inout [7:0] ft245_data, input logic ft245_rxf, input logic ft245_txe, output logic ft245_rd, output logic ft245_wr, output logic ft245_oe, output logic ft245_siwu, input logic source_ready, output logic source_valid, output logic[7:0] source_data, output logic sink_ready, input logic sink_valid, input logic[7:0] sink_data ); // enum {idle, toPC, dropOE, fromPC} state = idle; // always_ff @(posedge clk,posedge reset) begin if (reset == 1) begin state <= idle; end else begin case (state) idle: begin if ((ft245_txe == 0) && (sink_valid == 1)) state <= toPC; else if ((ft245_rxf == 0)&&(source_ready == 1)) state <= dropOE; end toPC: begin if (!((ft245_txe == 0) && (sink_valid == 1))) state <= idle; end dropOE: begin state <= fromPC; end fromPC: begin if (!((ft245_rxf == 0)&&(source_ready == 1))) state <= idle; end endcase end end // // , , // - . always_comb begin ft245_oe <= 1; ft245_rd <= 1; ft245_wr <= 1; source_valid <= 0; sink_ready <= 0; // , // assign- case (state) idle: begin end toPC: begin ft245_wr <= !(sink_valid); sink_ready <= !(ft245_txe); end dropOE: begin ft245_oe <= 0; end fromPC: begin ft245_oe <= 0; ft245_rd <= !(source_ready); source_valid <= !(ft245_rxf); end endcase end // - c , ... // FTDI : // Tie this pin to VCCIO if not used. assign ft245_siwu = 1; // inout- assign ft245_data = (state == toPC) ? sink_data : 8'hzz; // assign source_data = ft245_data; endmodule
Wie man das Modul in die Liste der zur Verwendung im Prozessorsystem verfügbaren Module aufnimmt, haben wir in einem der
vorherigen Artikel ausführlich untersucht, daher zeige ich nur das Ergebnis in der Abbildung. Ich erinnere mich, dass ich, um dies zu erreichen, zwei
AVALON-ST- Busse hinzufügen musste, einen
Conduit- Bus, Signale von einem falsch definierten
AVALON-MM- Bus
abrufen musste. Wenn in diesem Bus kein einziges Signal mehr vorhanden ist, löschen Sie es einfach. Unterwegs zeigt die Abbildung die Einstellungen, die ich für die
AVALON-ST- Busse ausgewählt habe (8 Bit pro Symbol, keine Fehler, der maximale Kanal ist Null, die Latenz ist Null).

Mit der Entwicklung eines Moduls zum Andocken von Reifen - fertig. Aber leider, ah. Sich zu entwickeln ist nur der Beginn der Arbeit. Die Implementierung ist viel schwieriger. Wie an der Position des Scrollers auf dem Bildschirm zu sehen ist, ist das Ende des Artikels noch weit entfernt. Wir beginnen also mit der Erstellung eines einfachen Projekts, bei dem die
Busverbindung FT245-SYNC mit den Bussen
AVALON-ST verwendet wird . Es ist das einfachste. Ein seriöses Projekt passt nicht in den Rahmen eines einzelnen Artikels von angemessener Größe. Ich werde jetzt eine Vereinfachung nach der anderen vereinfachen, damit die Aufmerksamkeit der Leser für den Rest des Textes ausreicht, damit sie nicht aufhören, ein Wort zu lesen. Die erste Vereinfachung besteht darin, dass 60-MHz-
Takte für den
FT245_SYNC vom
FT2232H- Chip
selbst erzeugt werden . Ich könnte dem System zwei Taktleitungen hinzufügen, aber sobald jeder es sieht, werden wir solche Spinnweben von Drähten haben, dass meine Mutter nicht trauert. Wenn ich immer noch auf verschiedene Taktleitungen achte, werden wir alle verwirrt sein. Daher kündige ich einfach an, dass unser Prozessorsystem heute vom
FT2232H- Chip und nicht von einem normalen Generator
getaktet wird .
Warum kannst du das nicht immer tun? Ganz einfach: Solange sich der
FT2232H nicht im 245_SYNC-Modus befindet, liegen diese Impulse nicht am Ausgang vor. Das heißt, Sie müssen zuerst das Programm für den Zentralprozessor ausführen und erst dann alles in das FPGA laden. Wenn wir ein System für einen externen Kunden erstellen würden, würde eine solche Lösung viele Probleme verursachen. Ich weiß aus Erfahrung, dass sie uns regelmäßig anrufen und sagen, dass nichts funktioniert, wir würden an Bars erinnern, aber das würde für eine Weile helfen. Aber wir machen eine interne Sache und werden sie nur unter Laborbedingungen verwenden. Das heißt, im Rahmen dieser Aufgabe ist dies zulässig.
Dies bringt jedoch neue Herausforderungen mit sich. Wir haben eine Frequenz von 60 MHz, und der SDRAM-Taktblock, den wir derzeit verwenden, ist eng an eine Frequenz von 50 MHz gebunden. Ja, ich habe geprüft, 60 können eingereicht werden, aber tun wir so, als würden wir versuchen, nicht über die zulässigen Modi hinauszugehen. In den folgenden Artikeln werde ich versuchen zu zeigen, wie dieser harte Block ersetzt werden kann, aber heute sagen wir nur, dass wir ihn vom SDRAM-Prozessorsystem ausschließen, da unsere Takteinheit vom SDRAM nicht mit der verwendeten Frequenz arbeiten kann. Das Programm und seine Daten befinden sich vollständig im internen Speicher des FPGA. Es wurde experimentell festgestellt, dass FPGAs in der heutigen Konfiguration maximal 28 Kilobyte RAM für dieses Unternehmen belegen können. Es stellt sich heraus, dass Sie Volumen und nicht mehrere Zweierpotenzen nehmen können ...
Zusätzlich verwenden wir die Standarduhr und die Reset-Einheit. Es wird etwas anders zurückgesetzt als das, das wir für SDRAM verwendet haben. Um den Artikel nicht zu komplizieren, werde ich die Tatsache ausnutzen, dass das in der Entwicklung befindliche System immer unter der Kontrolle eines Debuggers arbeitet, sodass ich zum Debuggen einen Reset vom JTAG-Subsystem starten werde.
Insgesamt erhalten wir eine solche Skizze des Basisprozessorsystems (die schwierigste Reset-Linie ist momentan hervorgehoben, die blaue Markierung befindet sich auf der Signalquelle):

wo die Frequenz für den Takt- und Rücksetzblock eingestellt wurde:

und für RAM - die Lautstärke:

Heute müssen wir den Text im Terminal anzeigen. Deshalb werden wir dem System einen so interessanten Block hinzufügen:

Mit diesem Block können wir Funktionen aufrufen, die printf ähneln. Zusätzlich zum AVALON_MM-Bus muss auch der Interrupt-Anforderungsausgang angeschlossen werden.

Damit ist die Beschaffung für das Prozessorsystem abgeschlossen. Es ist Zeit, unsere Einheit einzubetten. Wohin wird er die Daten senden? Unter den uns zur Verfügung stehenden Blöcken befindet sich ein sehr interessanter FIFO-Speicher mit zwei Ports. Sein Reiz liegt in der Tatsache, dass ein Port für den
AVALON-ST- Modus konfiguriert und mit unserem Gerät verbunden werden kann und der zweite für den
AVALON_MM- Modus und die Verwendung mit dem NIOS II-Prozessor. Dieser wundervolle Block befindet sich hier:

Wir haben zwei
Avalon-ST-Busse (einen zum Lesen, den anderen zum Schreiben), daher benötigen wir auch zwei FIFO-Blöcke. Jetzt werde ich eines davon sehr detailliert durchgehen, wir wickeln ein paar Kilometer Web (und eine Reihe von Textbildschirmen mit Bildern) und ungefähr in der Sekunde sagen wir, dass "es analog gemacht werden kann", was nur auf Unterschiede hinweist. Daher fügen wir dem System vorerst nur einen Block hinzu und sehen uns seine Einstellungen an. Es gibt viele Einstellungen. Man könnte einfach die erforderlichen Werte angeben, so dass jeder auf den Artikel als Referenz verweist, aber plötzlich gerät jemand in eine Situation, die konfiguriert werden muss, aber es gibt keinen Zugriff auf das Netzwerk (und damit auf den Artikel). Daher werde ich Einstellungen iterativ hinzufügen. Zuerst offensichtlich, dann - wie es das System verlangt - immer wieder durch den Dialog laufen. So wird jeder den Prozess spüren und ihn jederzeit wiederholen können. Also. Standardmäßig erhielten wir die folgenden Einstellungen:

Ich werde jetzt FIFO machen, das Daten von
Avalon-ST sammelt und auf
Avalon-MM hochlädt. Es stellt sich heraus, dass die erste Bearbeitung folgendermaßen aussehen wird:

Ich habe diese interessante Warnung erhalten:

Es stellt sich heraus, dass, wenn mindestens einer der Ports auf den Speicher projiziert wird, die
Avalon-ST -Busbreite streng 32 Bit betragen muss. Und wir haben einen 8-Bit-Bus. Wie Sie sich auf die Bittiefen einigen können, werde ich Ihnen etwas niedriger sagen, aber im Moment machen wir hier einen 32-Bit-Bus mit einem 8-Bit-Zeichen. Deaktivieren Sie den Batch-Modus, wie im theoretischen Teil festgelegt.

Als nächstes kommt die Kapazität. Angenommen, ich stelle 256 Wörter in die Warteschlange (d. H. 1024 Bytes):

Nun der Status. Anfangs habe ich dem keine Bedeutung beigemessen, und ich habe das Programm fest eingefroren. Jetzt weiß ich, dass der Status benötigt wird. Da wir programmgesteuert mit dem Ausgabeport arbeiten, fügen wir den Status dafür hinzu.

und den Fehler abfangen:

Na dann. Doppelte Taktung hinzufügen. Verbinden Sie einfach beide Eingänge mit derselben Taktleitung, da wir einen haben.
Uhhhh Insgesamt haben wir:

Es ist jedoch noch zu früh, um dieses Unternehmen mit dem gemeinsamen System zu verbinden. Wie wir herausgefunden haben, verlässt der 8-Bit-
Avalon-ST- Bus den von uns entwickelten Block, und dies sollte den 32-Bit-Block einschließen. Wie geht es uns Block umbauen? Nein! Alles wurde vor uns getan. Folgendes wird uns helfen:

Fügen Sie es dem System hinzu. Da es sich um eine reine Schönheitsschicht handelt, platzieren wir sie außerdem mit dem entsprechenden Pfeil zwischen unserem Block und dem FIFO:

Wir nehmen folgende Einstellungen vor: Am Eingang haben wir einen 8-Bit-Bus, am 32-Bit-Ausgang. Paketsignale werden nicht verwendet, es werden
fertige und
gültige Signale verwendet.

Es ist Zeit, ein Netz zu weben. Zuerst lege ich die Streaming-Linien (in der Abbildung sind beide hervorgehoben, die Markierungen befinden sich auf den Datenempfängern):

Das heißt, das Signal von der Quelle unseres Blocks geht zum Eingang des Adapters. Und vom Adapterausgang zum FIFO-Eingang. Wie gesagt, alle Verbindungen im Streaming-Protokoll werden Punkt-zu-Punkt-Verbindungen hergestellt.
Nun hängen wir die Reset-Leitungen und Taktleitungen auf und verbinden alles mit dem Systembus und den Interrupts ...

Nun ... Und jetzt fügen wir nach dem gleichen Prinzip FIFO hinzu, um Daten an den
FT245SYNC auszugeben . Nur dort gelangen die Daten von
Avalon-MM in 32-Bit-Form in den FIFO. Sie durchlaufen einen 32-in-8-Adapter und gelangen dann zum SINK-Eingang unseres Blocks, der nicht im Stromkreis angeschlossen ist ... Wir erhalten das folgende Fragment des endgültigen Stromkreises (der Speicher dort stellte sich mit einem einzigen Takt heraus):

Weitere Formalitäten, die wir bereits in den in früheren Artikeln beschriebenen Experimenten gut ausgearbeitet haben (
größtenteils - in diesem ). Wir weisen dem Prozessor Vektoren zu. Für das System nennen wir die automatische Zuweisung von Interrupt-Nummern und Adressen. Wir speichern das System ... Jeder erinnert sich, dass der Name des gespeicherten Systems mit dem Namen des Projekts übereinstimmen muss, damit sich das System auf der obersten Ebene der Hierarchie befindet? Fügen Sie das System zum Projekt hinzu, erstellen Sie einen groben Entwurf des Projekts und weisen Sie die Beine zu. Persönlich habe ich betrogen: Ich habe die Zuweisungen aus der * .qsf-Datei des Entwurfsprojekts in die aktuelle Abschlussdatei kopiert (und Sie können mein Projekt übernehmen und die entsprechenden * .qsf-Zeilen in Ihre kopieren, aber Sie können einfach alle Beine über die GUI zuweisen). Ich achte besonders darauf, dass das clk-Signal wie in früheren Projekten mit Abschnitt 23 und nicht mit Abschnitt 25 verbunden ist. Ich erinnere Sie daran, dass wir hier von der FT2232-Ausgabe ticken.

Großartig! Die Hardware ist bereit. Wir gehen zur Software über. Mit was fangen wir an? Heute ist diese Frage nicht wert. Wenn wir mit einem Programm beginnen, das auf dem NIOS II-Prozessor ausgeführt wird, funktioniert für uns nichts. Zuerst müssen wir den FT2232 in den 245-SYNC-Modus versetzen, nur dann empfängt unser Prozessorsystem Taktimpulse. Daher beginnen wir mit dem Code für den Zentralprozessor.
Wir bekommen so etwas: #include <cstdio> #include <sys/time.h> #include <unistd.h> #include "ftd2xx.h" FT_HANDLE OpenFT2232H() { FT_HANDLE ftHandle0; static FT_DEVICE ftDevice; // int nDevice = 0; while (true) { // if (FT_Open(nDevice, &ftHandle0) != FT_OK) { printf("No FT2232 found\n"); // , return 0; } // ? if (FT_GetDeviceInfo(ftHandle0, &ftDevice, NULL, NULL, NULL, NULL) == FT_OK) { // , if (ftDevice == FT_DEVICE_2232H) { // , AN130 FT_SetBitMode(ftHandle0, 0xff, 0x00); usleep(1000000); //Sync FIFO mode FT_SetBitMode(ftHandle0, 0xff, 0x40); FT_SetLatencyTimer(ftHandle0, 2); FT_SetUSBParameters(ftHandle0, 0x10000, 0x10000); return ftHandle0; } } // FT_Close(ftHandle0); // nDevice += 1; } printf("No FT2232 found\n"); } int main() { FT_HANDLE ftHandle0 = OpenFT2232H(); if (ftHandle0 == 0) { printf("Cannot open device\n"); return -1; } int item; bool bWork = true; while (bWork) { printf("1 - Send 16 bytes\n"); printf("2 - Send 256 bytes\n"); printf("3 - Receive loop\n"); printf("0 - Exit\n"); scanf("%d", &item); switch (item) { case 0: bWork = false; break; case 1: { static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f }; DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 2: { unsigned char data[0x100]; for (size_t i = 0; i < sizeof(data); i++) { data[i] = (unsigned char)i; } DWORD dwWritten; FT_Write(ftHandle0, (void*)data, sizeof(data), &dwWritten); } break; case 3: { DWORD dwRxBytes; DWORD dwRead; DWORD buf[0x100]; while (true) { FT_GetQueueStatus(ftHandle0, &dwRxBytes); if (dwRxBytes != 0) { printf("Received %d bytes (%d DWORDs)\n", dwRxBytes, dwRxBytes / sizeof(buf[0])); if (dwRxBytes > sizeof(buf)) { dwRxBytes = sizeof(buf); } FT_Read(ftHandle0, buf, dwRxBytes, &dwRead); for (DWORD i = 0; i < dwRxBytes / sizeof(buf[0]);i++) { printf("0x%X, ",buf[i]); } printf("\n"); } } } break; } } // , FT_Close(ftHandle0); return 0; }
Die Funktion OpenFT2232H () ist uns aus dem letzten Artikel bekannt . Sie öffnet das FT2232-Gerät und versetzt es in den von uns benötigten Modus. Unmittelbar nach dem erfolgreichen Start des Programms erhalten wir Taktimpulse und damit die Möglichkeit, das Programm für NIOS II zu debuggen. Nun, die Funktionalität der Hauptfunktion ist so einfach wie ein Hocker. Senden Sie einige Daten (1), senden Sie viele Daten (2), empfangen Sie Daten (3). Bitte beachten Sie, dass alle Daten in Blöcken gesendet werden, die ein Vielfaches von vier Bytes sind. Dies ist alles, weil wir einen 8-in-32-Adapter haben. Am Ende sollten die Daten in doppelten Worten stehen. Ansonsten ist alles klar.Wenn Sie ein Programm für NIOS II entwickeln, sollten Sie zuerst BSP konfigurieren. Ich erinnere Sie daran, dass ich das Programm selbst gemäß der Hello World Small-Vorlage erstelle. Die geänderten Felder im BSP sind in der folgenden Abbildung rot markiert (wie das Programm aus der Vorlage erstellt und wie das BSP korrigiert wird, wurde in einem der vorherigen Artikel ausführlich erläutert ). Ich möchte Sie daran erinnern, dass ich die Wurzel des Baums, dh das Element Einstellungen, auswähle, sodass alle Einstellungen sofort rechts sichtbar sind.
Als nächstes generiere BSP und ändere aufgrund meiner Gewohnheit den Dateinamen hello_world_small.c in hello_world_small.cpp. Danach bereinige ich das Projekt, damit bei dieser Umbenennung keine Fehler auftreten.Ich werde die Arbeit ganz oberflächlich überprüfen (ein echter Tester würde sicherlich die Übertragung großer Datenmengen, die die FIFO-Größe überschreiten, gründlich testen, aber der Zweck des Artikels besteht darin, die Grundprinzipien aufzuzeigen und nicht sicherzustellen, dass sie aufgrund ihrer wahnsinnigen Größe nicht mehr gelesen werden können). Und ich werde die Grundprinzipien in zwei Schritten zeigen. Der erste Schritt besteht darin, die Datenübertragung vom Zentralprozessor zu NIOS II zu überprüfen. Dafür habe ich folgenden Code entwickelt: extern "C" { #include "sys/alt_stdio.h" #include <system.h> #include <altera_avalon_fifo_util.h> } #include <stdint.h> int main() { while (1) { int level = IORD_ALTERA_AVALON_FIFO_LEVEL(FIFO_0_OUT_CSR_BASE); if (level != 0) { alt_printf("0x%x words received:\n",level); for (int i=0;i<level;i++) { alt_printf("0x%x,",IORD_ALTERA_AVALON_FIFO_DATA (FIFO_0_OUT_BASE)); } alt_printf("\n"); } } /* Event loop never exits. */ while (1); return 0; }
Dieses Programm wartet darauf, dass Daten im FIFO angezeigt werden. Wenn sie dort erschienen sind, werden sie angezeigt.Zum Test kommen. Zuerst werde ich so tun, als hätte ich vergessen, mit dem Timing zu beginnen. Daher lade ich nach dem Einschalten von Redd die FPGA-Firmware und versuche dann, ein Debugging-Programm für NIOS II auszuführen. Ich bekomme diese Nachricht:
Wenn Sie dasselbe haben, bedeutet dies, dass Sie wirklich vergessen haben, das Prozessorsystem zu takten. Aber jetzt wissen Sie, wie Sie es schnell identifizieren können. Und um dies zu vermeiden, ist es notwendig und ausreichend, das Programm auszuführen, das wir für den Zentralprozessor geschrieben haben. Sobald die FT2232-Brücke gestartet und initialisiert wird, werden Taktimpulse an unseren Prozessor gesendet, und es ist möglich, den Vorgang des Startens des Debuggens zu wiederholen. Darüber hinaus kann das Programm für den Zentralprozessor zu diesem Zeitpunkt abgeschlossen sein. Taktimpulse gehen nirgendwo hin: Die Brücke ist bereits für den FT245-SYNC- Modus konfiguriert .Drücken Sie im Programm für den Zentralprozessor 1. Je nach Situation wird im Terminal entweder Folgendes angezeigt :0x2 empfangene Wörter:
0x3020100,0x7060504,
0x2 empfangene Wörter:
0xb0a0908,0xf0e0d0c,entweder:0x3 Wörter die Received:
0x3020100,0x7060504,0xb0a0908,
0x1 Wörter die Received:
0xf0e0d0c,Im Prinzip kann es 1, dann 3 Doppelwörter geben, aber das ist mir nicht eingefallen. Es hängt alles davon ab, wie viele Bytes Zeit haben, um auf dem Bus zu laufen, bevor die erste Anzeige beginnt. Und wenn es gestartet wurde, haben am Ende alle anderen Bytes Zeit zum Ausführen, da die Datenübertragung über JTAG kein schneller Prozess ist. Wenn der Bus Bursts verwendet, kann das Programm die Daten erst nach Empfang des Pakets sehen. In einigen Fällen ist es gut (es gibt noch kein Paket, warum sollte ich es sehen? Besonders wenn das Paket vorübergehend ist), in einigen Fällen ist es schlecht (FIFO ist eine Black Box, für die endgültige Verarbeitung sollten die Daten in den adressierbaren RAM kopiert werden, und dies geschieht besser parallel zum Empfang von Daten )Die übertragenen Daten werden in Doppelwörtern in Little Endian-Notation platziert. Ich möchte Sie daran erinnern, dass das folgende Array übergeben wird: static const unsigned char data[0x10] = { 0x00,0x01,0x02,0x03, 0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f };
Alles ist richtig.
Wenn Sie im Programm Punkt 2 für den Zentralprozessor auswählen, wird eine Meldung angezeigt (zur besseren Lesbarkeit werden die Zeilen bei der Vorbereitung des Artikels formatiert): 0x3 words received: 0x3020100,0x7060504,0xb0a0908, 0x3d words received: 0xf0e0d0c, 0x13121110,0x17161514,0x1b1a1918,0x1f1e1d1c, 0x23222120,0x27262524,0x2b2a2928,0x2f2e2d2c, 0x33323130,0x37363534,0x3b3a3938,0x3f3e3d3c, 0x43424140,0x47464544,0x4b4a4948,0x4f4e4d4c, 0x53525150,0x57565554,0x5b5a5958,0x5f5e5d5c, 0x63626160,0x67666564,0x6b6a6968,0x6f6e6d6c, 0x73727170,0x77767574,0x7b7a7978,0x7f7e7d7c, 0x83828180,0x87868584,0x8b8a8988,0x8f8e8d8c, 0x93929190,0x97969594,0x9b9a9998,0x9f9e9d9c, 0xa3a2a1a0,0xa7a6a5a4,0xabaaa9a8,0xafaeadac, 0xb3b2b1b0,0xb7b6b5b4,0xbbbab9b8,0xbfbebdbc, 0xc3c2c1c0,0xc7c6c5c4,0xcbcac9c8,0xcfcecdcc, 0xd3d2d1d0,0xd7d6d5d4,0xdbdad9d8,0xdfdedddc, 0xe3e2e1e0,0xe7e6e5e4,0xebeae9e8,0xefeeedec, 0xf3f2f1f0,0xf7f6f5f4,0xfbfaf9f8,0xfffefdfc,
Alles ist auch wahr. Wir prüfen den Rückwärtsgang. Wir ersetzen das Programm für NIOS II durch Folgendes: /* - 2 */ uint32_t buf[] = {0x11223344,0x55667788,0x99aabbcc,0xddeeff00}; for (uint32_t i=0;i<sizeof(buf)/sizeof(buf[0]);i++) { IOWR_ALTERA_AVALON_FIFO_DATA (FIFO_1_IN_BASE,buf[i]); }
Wir wählen Punkt 3 im Programm für den Zentralprozessor aus und führen diese Version des Programms für NIOS II aus. Wirerhalten
: 16 Bytes (4 DWORDs) empfangen 0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00,Beide Kanäle arbeiten grob. Und wir werden es irgendwie ein anderes Mal überprüfen.Fazit
Dieser Artikel beschreibt die Grundlagen des Avalon-ST- Bus-Streaming-Protokolls . Durch dieses Protokoll wird die Verbindung des Redd-Zentralprozessors mit dem im FPGA implementierten Prozessorsystem organisiert. Die Leser erhielten eine Vorstellung von der einfachsten Interaktionsmethode zwischen Zentral- und Hilfsprozessoren. Download gestartet in Entwicklungsprojekten kann hier .Die Kenntnisse über Streaming-Protokolle und deren Verwendung sind jedoch sehr grundlegend. In den folgenden Artikeln wird gezeigt, wie mit diesen Protokollen Daten im dynamischen RAM auf der Redd-Karte effizient gespeichert werden können.