
Wir müssen den vorletzten Schritt in der praktischen Entwicklung der Arbeit mit UDB machen. Heute werden wir nicht mit dem automatisierten UDB-Editor entwickeln, sondern im halbmanuellen Modus mit dem Datapath Config Tool. Eine sehr gute Hilfe bei der Beherrschung dieses Tools ist AN82156 - PSoC 3, PSoC 4 und PSoC 5LP - Entwerfen von PSoC Creator-Komponenten mit UDB-Datenpfaden. Eigentlich habe ich es selbst studiert.
Vielleicht hat jemand beim Lesen unserer
Übersetzungen der Dokumentation zu UDB versucht, das Wissen von dort in die Praxis umzusetzen, und festgestellt, dass nicht alle in den Veröffentlichungen beschriebenen Funktionen im UDB-Editor verfügbar sind. Dies liegt an der Tatsache, dass die Entwickler einige besonders umständliche Mechanismen nicht in den UDB-Editor eingefügt haben. Die Autoren von AN82156 argumentieren, dass Sie über den UDB-Editor Folgendes nicht tun können:
- parallele Dateneingabe und -ausgabe organisieren;
- dynamisches FIFO-Management organisieren;
- Implementieren der Umkehrung des FIFO-Taktsignals;
- Implementieren Sie die CRC-Funktion.
- Implementieren Sie die PRS-Funktion.
- die Wahl der eingehenden Übertragung implementieren;
- Implementieren Sie eine dynamische eingehende Migration.
Ich selbst möchte hinzufügen, dass ich nicht herausgefunden habe, wie die Permutation von Knabbereien im UDB-Editor implementiert werden kann.
Wenn diese Funktionen im Projekt benötigt werden, müssen Sie Ihren eigenen Verilog-Code erstellen. Ich habe speziell das Wort "erstellen" anstelle von "schreiben" verwendet. Die Kenntnis dieser Programmiersprache reicht auf Leseebene aus. Ich meine, Sie müssen verstehen, welches Design für was benötigt wird. Es ist immer nützlich, von Grund auf neu schreiben zu können, aber diese Fähigkeit ist für das, was in diesem Artikel vorgestellt wird, nicht erforderlich.
Als lösbares Problem habe ich einen halbsynthetischen Fall gewählt. Im Allgemeinen habe ich mich entschlossen, einige Daten an den parallelen Anschluss auszugeben, und insbesondere hat das Text-LCD einen parallelen Anschluss. Ich habe es vor drei Jahren aus dem MZ3D 3D-Drucker gezogen, als ich letzteres auf STM32 transplantiert habe. Daher ist der Fall halbsynthetisch: Heutzutage haben solche Indikatoren normalerweise einen I2C-Eingang und müssen im wirklichen Leben nicht über einen Stapel von Drähten verbunden werden. Moderne LCDs verfügen jedoch auch über parallele Anschlüsse, sodass jeder das Experiment wiederholen kann.
Betrachten Sie das von reprap.org übernommene Schema zum Umschalten der Anzeige (dies war nicht einfach, mein Provider blockiert diese Site sowie eine Reihe anderer technischer Websites und motiviert sie damit, dass sie auf derselben IP-Adresse wie eine blockierte Person leben).

Tolles Layout! Erstens muss ich nicht über das Lesen nachdenken: Daten auf dem LCD können nur geschrieben werden (die R / W-Leitung ist geerdet und am Stecker nicht verfügbar). Zweitens liegen die Daten in einem 4-Bit-Format vor, was bedeutet, dass wir nicht nur die parallele Ausgabe berechnen, sondern auch die Funktion der Nibble-Permutationsfunktion überprüfen können.
Projekterstellung
Starten Sie also PSoC Creator und wählen Sie
Datei-> Neu-> Projekt :

Als nächstes wähle ich mein Steckbrett:

Als nächstes ist das leere Diagramm:

Ich werde das Projekt
LCDTest2 nennen :

Wechseln Sie nun wie zuvor zur Registerkarte
Komponenten :

Nachdem Sie das Projekt ausgewählt haben, drücken Sie die rechte Maustaste und wählen
Sie dann Komponentenelement hinzufügen .

Und hier müssen Sie den
Symbol-Assistenten auswählen. Geben Sie einen Namen ... Nun, sagen wir
LCD4bit .

Ich habe dem Symbol folgende Ports zugewiesen:
clk ist der Takteingang. Ports mit einem LCD-Präfix sind Standard-LCD-Ports.
hungrige Ausgänge, die der DMA-Einheit mitteilen, dass im FIFO freier Speicherplatz vorhanden ist, wurden in einem Artikel über die
Steuerung von RGB-LEDs erörtert. Klicken Sie auf OK, um den Charakter zu erhalten.

Basierend auf diesem Symbol sollte nun eine Verilog-Vorlage generiert werden. Klicken Sie mit der rechten Maustaste in der Nähe des Symbols und wählen
Sie im Kontextmenü die
Option Verilog generieren .

Wir haben die in der folgenden Abbildung gezeigte Vorlage (in Textform macht sie noch keinen Sinn):

Wir haben ein Modul und einige Abschnitte erstellt. Sie haben Datapath jedoch noch nicht erstellt. Um es hinzuzufügen, gehen Sie zum Projektbaum, wählen Sie die Datei
LCD4bit.v aus , drücken Sie die rechte Maustaste und wählen Sie das
Datapath Config Tool im
angezeigten Kontextmenü:

Vor uns öffnet sich ein Fenster, das ich vorerst nur teilweise zeigen werde:

Bitte lieben und bevorzugen, Datapath Editor. Es enthält alle Bits, die in der Übersetzung der proprietären Dokumentation beschrieben wurden. Aber es gibt so viele dieser Teile, dass ich ihn in den frühen Tagen ansah, aber Angst hatte, irgendetwas zu tun. Schau, schau und geh raus. Und erst nach einiger Zeit, als er sich daran gewöhnt hatte, versuchte er etwas zu tun. Deshalb habe ich nur einen Teil des Fensters mitgebracht. Warum alle vorzeitig erschrecken? In der Zwischenzeit müssen wir nur einen Datenpfad erstellen, also wählen wir den Menüpunkt
Bearbeiten-> Neuer Datenpfad :

Welche Option können Sie im angezeigten Dialogfeld auswählen?

Die Frage ist etwas ernster als es scheint. Lassen Sie mich sogar den nächsten Absatz hervorheben, damit niemand erwischt wird (ich habe mich selbst erwischt, und dann habe ich im Netzwerk Fragen von denen gesehen, die ich erhalten habe, und niemand hat sie wirklich beantwortet, und die Antwort ist in
AN82156 , Sie müssen sie nur diagonal lesen, wie es dort steht kurze unauffällige Phrase).
Wenn Sie mit parallelen Daten arbeiten möchten, müssen Sie auf jeden Fall die Option CY_PSOC3_DP wählen. Keine andere Option enthält Ports zum Verbinden paralleler Daten.
Also. Lassen Sie die Instanz LCD_DP heißen:

Klicken Sie auf OK und schließen Sie das
Datapath Config Tool , um das Ergebnis zu speichern. Wir werden später hierher zurückkommen.
Unser Verilog-Code wurde erweitert. Jetzt hat es Datapath. Sein Anfang ist völlig unlesbar. Es ist nicht beängstigend, es wird vom
Datapath Config Tool konfiguriert.

Und wir werden das Ende der Datenpfadbeschreibung regieren. Unsere Seite sieht so aus
(Ab diesem Punkt ist es sinnvoll, alles in Textform zu bringen).)) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(), /* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port );
Beängstigend Jetzt werden wir herausfinden, was was ist - es wird aufhören, beängstigend zu sein. Tatsächlich gibt es in diesem Text drei verschiedene Gruppen. Erinnern wir uns an die Übersetzung der Dokumentation. Wie sah der Datenpfad auf dem Bild aus? Ich werde in der Abbildung sofort die Orte vermerken, zu denen die Gruppen „1“, „2“ und „3“ gehören.

Tatsächlich sind die Eingaben die erste Gruppe von Ports im Verilog-Code. Vergleichen Sie die Namen am Ausgang des Eingangsmultiplexers (in der Abbildung „1“) und die Namen der Signale im Code.
Jetzt sind alle Eingänge Null. Wir müssen den Takteingang anschließen und können bis zu sechs Eingangsleitungen weiterleiten, wie dies im UDB-Editor geschehen ist. Diese Eingaben sind:
/* input */ .reset(1'b0), /* input */ .clk(1'b0), /* input [02:00] */ .cs_addr(3'b0), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0),
Die zweite Gruppe sind die Ausgänge. Die Namen im Code stimmen auch mit den Namen der Eingänge des Ausgangsmultiplexers "2" überein:
/* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(),
Nur die angegebene Datapath-Art hat die dritte Gruppe (die anderen haben keine, daher gibt es keine parallelen Daten). Dies sind interne Datenpfadsignale, über die Sie unabhängig voneinander verketten oder andere nützliche Aktionen ausführen können. Die Namen im Code stimmen auch mit den Namen der in der Figur verstreuten internen Signale überein. Wir geben durch einen von ihnen (der letzte in der Liste, sein Name ist
po ) parallele Daten direkt an die Beine des Chips aus.
/* input */ .ci(1'b0), // Carry in from previous stage /* output */ .co(), // Carry out to next stage /* input */ .sir(1'b0), // Shift in from right side /* output */ .sor(), // Shift out to right side /* input */ .sil(1'b0), // Shift in from left side /* output */ .sol(), // Shift out to left side /* input */ .msbi(1'b0), // MSB chain in /* output */ .msbo(), // MSB chain out /* input [01:00] */ .cei(2'b0), // Compare equal in from prev stage /* output [01:00] */ .ceo(), // Compare equal out to next stage /* input [01:00] */ .cli(2'b0), // Compare less than in from prv stage /* output [01:00] */ .clo(), // Compare less than out to next stage /* input [01:00] */ .zi(2'b0), // Zero detect in from previous stage /* output [01:00] */ .zo(), // Zero detect out to next stage /* input [01:00] */ .fi(2'b0), // 0xFF detect in from previous stage /* output [01:00] */ .fo(), // 0xFF detect out to next stage /* input [01:00] */ .capi(2'b0), // Software capture from previous stage /* output [01:00] */ .capo(), // Software capture to next stage /* input */ .cfbi(1'b0), // CRC Feedback in from previous stage /* output */ .cfbo(), // CRC Feedback out to next stage /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po() // Parallel data port );
Also. Während wir arbeiten, müssen wir einige dieser Ein- und Ausgänge mit unseren eigenen Entitäten verbinden, und der Rest - lassen Sie sie einfach in der Form, in der wir sie erstellt haben.
Verwenden des UDB-Editors als Referenz
Und jetzt haben wir eine Lücke, wir wissen, wo und was wir schreiben müssen. Es bleibt zu verstehen, was genau wir dort eingeben werden. Es kam vor, dass ich die Verilog-Sprache nicht jeden Tag verwende. Im Allgemeinen erinnere ich mich an alles, und das Schreiben von Grund auf ist für mich immer eine stressige Situation. Wenn das Projekt bereits läuft, wird alles in Erinnerung behalten, aber wenn ich nach ein paar Monaten Inaktivität etwas von vorne anfange, erinnere ich mich natürlich nicht mehr an die Syntaxdetails dieser bestimmten Sprache. Daher schlage ich vor, die Entwicklungsumgebung zu bitten, uns zu helfen.
Der UDB-Editor zur Selbstüberwachung erstellt Verilog-Code. Wir nutzen die Tatsache, dass Komponenten, die nicht an der Hauptschaltung beteiligt sind, nicht kompiliert werden, sodass wir im UDB-Editor eine Hilfskomponente erstellen können, die nicht in den Ausgabecode gelangt. Wir werden dort einen Automaten zeichnen, die Ein- und Ausgänge von Datapath grob anpassen und dann einfach den automatisch generierten Text in unser Verilog-Modul übertragen und alles kreativ ändern. Dies ist viel einfacher, als sich die Details der Verilog-Syntax zu merken und alles von Grund auf neu zu schreiben (obwohl jeder, der Verilog ständig verwendet, natürlich einfacher von Grund auf neu schreiben kann: Die kreative Fertigstellung ist, wie wir bald sehen werden, einfach, erfordert jedoch Zeit).
Also fangen wir an, eine Hilfskomponente herzustellen. Mit der üblichen Handbewegung fügen wir dem Projekt ein neues Element hinzu:

Dies wird ein UDB-Dokument sein, nennen wir es
UDBhelper :

Es ist Zeit, über die Maschine nachzudenken, die wir auf dem erstellten Blatt platzieren werden. Dazu müssen wir uns überlegen, welches Zeitdiagramm wir damit erstellen sollen:


Also. Zuerst müssen Sie das RS-Signal einstellen (da R / W in der Hardware auf Null gelötet ist). Warten Sie als nächstes auf tAS, erhöhen Sie dann das Signal E und stellen Sie die Daten ein (die Dateneinstellung in Bezug auf die positive Flanke E ist nicht begrenzt). Die Daten müssen mindestens tDSW auf dem Bus sein, danach muss das Signal E gelöscht werden. Die Daten müssen mindestens tDHW und mindestens tAH RS auf dem Bus bleiben.
RS ist das Befehls- oder Datenflag. Wenn RS Null ist, wird ein Befehl geschrieben, wenn es Eins ist, werden Daten geschrieben.
Ich schlage vor, Befehle über
FIFO0 und Daten über
FIFO1 zu senden . Dies widerspricht im Rahmen der aktuellen Aufgabe nichts. Dann hat die von mir vorgeschlagene endliche Zustandsmaschine die folgende Form:

Im
Ruhezustand befindet sich die Maschine noch in keinen FIFO-Daten. Wenn Daten in
FIFO0 erschienen
sind , gehen sie zu
LoadF0 , wo sie in Zukunft Daten von
FIFO0 nach A0 empfangen.
Während die Befehle übertragen werden, sollten die Daten nicht gesendet werden. Daher hat die Bedingung zum Empfangen von Daten eine niedrigere Priorität als die Bedingung zum Empfangen von Befehlen.

Daten werden in A1 im Zustand
LoadF1 empfangen (von
FIFO1 können sie nur zum Register A1 und nicht zum Register A0 gehen), und dann werden sie im Zustand
A1toA0 von A1 nach A0
kopiert .
Wie auch immer wir zum Punkt der Konvergenz der Pfeile gehen, wir haben Daten in A0. Sie werden bereits an die parallele Schnittstelle ausgegeben. Wir
spannen E (im Zustand
E_UP1 ), lassen E fallen (im Zustand
E_DOWN1 ). Als nächstes haben wir einen Zustand zum Austauschen von Knabbereien (
SWAP ), wonach E wieder ansteigt (
E_UP2 ). In diesem Zusammenhang habe ich acht Zustände erschöpft, die in drei Bits codiert werden können. Und wir erinnern uns, dass der dynamische Konfigurations-RAM von Datapath nur drei Adresseneingänge hat. Einige Tricks könnten angewendet werden, aber der Artikel ist bereits groß. Daher werden wir E erst zum zweiten Mal in den
Ruhezustand versetzen. Dann reichen uns acht Staaten völlig aus.
Wir setzen auch Datapath auf das Blatt und weisen seine Ein- und Ausgänge auf eine Weise zu, die in den vorherigen Artikeln bekannt ist. Hier sind die Eingaben:

Hier sind die Ausgänge:

Nichts Neues, alles wurde bereits in früheren Artikeln des Zyklus beschrieben. Wir haben also eine Lücke, auf deren Grundlage wir etwas Eigenes tun können. Um sicherzustellen, dass alles läuft, müssen wir unser System auf die oberste Ebene des Projekts bringen, sonst werden keine Fehler gefunden. Und in den ersten Experimenten ohne Fehler wird nicht funktionieren. Deshalb werden wir noch eine Hilfsaktion durchführen.
Die Beschreibung, wie die Schaltung hergestellt wird, geht über die Beschreibung der Arbeit mit UDB hinaus. Ich zeige Ihnen nur, welche Schaltung ich habe. Es gibt nur eine DMA-Einheit: Wenn Sie Befehle an das LCD senden, müssen Sie großen Pausen standhalten, sodass dies programmgesteuert noch einfacher ist. Für andere Anwendungen können Sie den zweiten DMA-Block einfach analog mit dem Signal
hung00 setzen .

Um den Zeitrahmen genau einzuhalten, habe ich eine Taktfrequenz von einem Megahertz gewählt. Es wäre möglich, eine Frequenz und höher zu nehmen, aber die Daten werden unter Bedingungen hoher Interferenz über lange Drähte übertragen, daher ist es besser, sich die Zeit zu nehmen, um die Daten vor und nach dem Gate mit einem Rand einzustellen. Wenn jemand meine Experimente auf demselben Steckbrett wiederholt - verwenden Sie nicht Port P3.2: Ein Kondensator ist an dieses Bein auf der Platine gelötet. Ich habe eine halbe Stunde lang getötet, bis ich herausgefunden habe, warum ich keinen Impuls E gebildet habe, den ich zuerst dort verbunden habe. Ich warf es auf P3.1 - alles funktionierte sofort. Mein Datenbus geht zu P3.7-P3.4, RS geht zu P3.3, also ging E ursprünglich zu P3.2 ...
Bitte schön. Wenn Sie nun versuchen, das Projekt zu kompilieren, erhalten wir vollständig vorhersehbare Fehler

Das System versucht also, etwas zu sammeln. Aber sie hat immer noch nichts zu sammeln. Wir fahren fort, den Code zu kopieren. Wechseln Sie dazu im UDB-Editor zur Registerkarte Verilog (diese Registerkarte befindet sich unter dem Fenster mit dem Blatt UDB-Editor):

Was ist dort bekannt? Ganz am Ende des Textes befindet sich der Körper des Automaten. Beginnen wir mit der Migration.
Platzieren Sie es auch unter Datapath: /* ==================== State Machine: SM ==================== */ always @ (posedge clock) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end E_Up1 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Down1 ; end end E_Down1 : begin if (( 1'b1 ) == 1'b1) begin SM <= SWAP ; end end SWAP : begin if (( 1'b1 ) == 1'b1) begin SM <= E_UP2 ; end end E_UP2 : begin if (( 1'b1 ) == 1'b1) begin SM <= Idle ; end end LoadF1 : begin if (( 1'b1 ) == 1'b1) begin SM <= A1toA0 ; end end A1toA0 : begin if (( 1'b1 ) == 1'b1) begin SM <= E_Up1 ; end end default : begin SM <= Idle; end endcase end
Oben befinden sich Deklarationen für diesen Code (Namen für Status, Ketten für Datapath, ein Register, das den Status eines Automaten codiert). Wir übertragen sie an die entsprechenden
Abschnitt unseres Codes: /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire hungry0; wire F0empty; wire hungry1; wire F1empty; wire Datapath_1_d0_load; wire Datapath_1_d1_load; wire Datapath_1_f0_load; wire Datapath_1_f1_load; wire Datapath_1_route_si; wire Datapath_1_route_ci; wire [2:0] Datapath_1_select; reg [2:0] SM;
Na und
Die Signalbindungsstelle ist übertragbar: /* ==================== Assignment of Combinatorial Variables ==================== */ assign Datapath_1_d0_load = (1'b0); assign Datapath_1_d1_load = (1'b0); assign Datapath_1_f0_load = (1'b0); assign Datapath_1_f1_load = (1'b0); assign Datapath_1_route_si = (1'b0); assign Datapath_1_route_ci = (1'b0); assign Datapath_1_select[0] = (SM[0]); assign Datapath_1_select[1] = (SM[1]); assign Datapath_1_select[2] = (SM[2]);
Es ist Zeit, Datapath anzuschließen. Aus dem UDB-Editor portierter Code eignet sich gut für die maschinelle Bearbeitung, jedoch nicht sehr gut für die manuelle Bearbeitung. Dort werden Ketten erstellt, die an einem Ende mit den Datenpfadeingaben und am anderen mit Konstanten verbunden sind. In dem vom
Datapath-Konfigurationstool erstellten Code (der alles für die manuelle Arbeit erledigt) sind jedoch alle Eingaben bereits direkt mit
Nullkonstanten verbunden. Ich werde also nur die Zeilen verbinden, die keine Konstanten sind, aber alles, was mit der Weiterleitung von Konstanten zu tun hat, aus dem übertragenen Text herausschneiden. Die Verbindung stellte sich folgendermaßen heraus (die Farbe hebt die Stellen hervor, die ich in Bezug auf die automatisch im Datapath-Konfigurationstool erstellten Stellen bearbeitet habe):

Gleicher Text: )) LCD_DP( /* input */ .reset(1'b0), /* input */ .clk(clk), /* input [02:00] */ .cs_addr(SM), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(hungry0), /* output */ .f0_blk_stat(F0empty), /* output */ .f1_bus_stat(hungry1), /* output */ .f1_blk_stat(F1empty),
Parallele Daten sind etwas komplizierter. Datapath verfügt über einen 8-Bit-Port, von dem nur vier herausgebracht werden müssen. Deshalb starten wir den Hilfsstromkreis und verbinden nur die Hälfte davon mit dem Ausgang:
wire [7:0] tempBus; assign LCD_D = tempBus[7:4];
Und verbinden Sie es so:

Gleicher Text: /* input [07:00] */ .pi(8'b0), // Parallel data port /* output [07:00] */ .po( tempBus) // Parallel data port );
Wir versuchen zu montieren (Shift + F6 oder über den Menüpunkt
Build-> Generate Application ). Wir bekommen den Fehler:

Wir haben die Ports
hungrig0 und
hungrig1 (die beim Erstellen der Komponente
angezeigt wurden ) sowie Ketten mit demselben Namen (wurden beim Ziehen aus dem Beispiel
angezeigt ). Entfernen Sie einfach diese Ketten (verlassen Sie die Ports). Und irgendwo ist das
Taktsignal durchgesickert, und wir haben diese Schaltung namens
clk .
Nachdem wir alle unnötigen Schaltkreise entfernt haben (diejenigen, die anfänglich Nullkonstanten in die Datenpfadeingänge geworfen haben, sowie
hungrig0 und
hungrig1 ), erhalten wir den folgenden Code für den Anfang unserer Datei:
// Your code goes here /* ==================== Wire and Register Declarations ==================== */ localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111; wire F0empty; wire F1empty; reg [2:0] SM; /* ==================== Assignment of Combinatorial Variables ==================== */ wire [7:0] tempBus; assign LCD_D = tempBus[7:4];
Und wenn ich die
Uhr im Körper der Maschine durch
clk ersetze, werde ich gleichzeitig alle Zeilen wegwerfen, die für die automatische Generierung gut sind, aber bei manueller Bearbeitung nur Verwirrung stiften (alle Vergleiche, die ein bedingungsloses Ergebnis
WAHR ergeben, und so weiter). Insbesondere können Sie im folgenden Beispiel etwa die Hälfte der Zeilen streichen (und einige
Anfang / Ende sind optional, manchmal werden sie benötigt, da wir Aktionen hinzufügen, die ich hervorgehoben habe):

Nach dem Kämmen nach dem obigen Prinzip (und dem Ersetzen der
Uhr durch
clk ) bleibt ein solcher Körper bestehen
(es ist kürzer geworden, was bedeutet, dass es leichter zu lesen ist): always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; end E_Down1 : begin SM <= SWAP ; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end
Während der Kompilierung wird uns nun mitgeteilt, dass die
Schaltkreise LCD_E und
LCD_RS nicht verbunden sind.
Eigentlich ist das wahr:

Es ist an der Zeit, der Zustandsmaschine Aktionen hinzuzufügen. Wir werden die Deklarationen der Ports, die den nicht verbundenen Ketten entsprechen, durch
reg ersetzen, da wir sie in den Körper der Maschine schreiben werden (dies ist die Syntax der Verilog-Sprache. Wenn wir schreiben, sollten die Daten einklicken, dafür benötigen wir einen Trigger und sie werden durch das Schlüsselwort
reg angegeben ):

Gleicher Text: module LCD4bit ( output hungry0, output hungry1, output [3:0] LCD_D, output reg LCD_E, output reg LCD_RS, input clk );
Und füllen Sie die Maschine mit Aktionen. Ich habe die obige Logik bereits gesagt, als ich den Übergangsgraphen des Automaten betrachtete, daher werde ich nur das Ergebnis zeigen:

Gleicher Text: always @ (posedge clk) begin : Idle_state_logic case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end LoadF0 : begin SM <= E_Up1 ; end E_Up1 : begin SM <= E_Down1 ; LCD_E <= 1'b1; end E_Down1 : begin SM <= SWAP ; LCD_E <= 1'b0; end SWAP : begin SM <= E_UP2 ; end E_UP2 : begin SM <= Idle ; LCD_E <= 1; end LoadF1 : begin SM <= A1toA0 ; end A1toA0 : begin SM <= E_Up1 ; end default : begin SM <= Idle; end endcase end
Ab diesem Moment beginnt das Projekt zusammenzubauen. Aber er wird noch nicht arbeiten. Bisher habe ich bekanntlich gesagt: "In diesem Zustand werden wir das Register vom FIFO laden", "In diesem Zustand wird A1 nach A0 kopiert", "In diesem Zustand werden die Knabbereien neu angeordnet". Im Allgemeinen habe ich viel gesprochen, aber bisher wurden keine Maßnahmen ergriffen. Es ist an der Zeit, sie zu erfüllen. Wir schauen uns an, wie die Staaten verschlüsselt wurden:
localparam [2:0] Idle = 3'b000; localparam [2:0] LoadF0 = 3'b001; localparam [2:0] LoadF1 = 3'b010; localparam [2:0] E_Up1 = 3'b100; localparam [2:0] A1toA0 = 3'b011; localparam [2:0] E_Down1 = 3'b101; localparam [2:0] SWAP = 3'b110; localparam [2:0] E_UP2 = 3'b111;
Öffnen Sie das Datapath-Konfigurationstool erneut :

Beginnen Sie mit der
Bearbeitung der CFGRAM- Zeilen. Beachten Sie beim Bearbeiten das Datenpfad-Schema:

Die roten Rahmen in der Abbildung unten (und die Pfeile in der Abbildung oben) Ich habe die korrigierten Bereiche (und den Datenpfad) für den Status
LoadF0 (Code 001,
dh Reg1 ) hervorgehoben. Ich habe Kommentare auch manuell eingegeben. Der Inhalt von F0 sollte in A0 sein

Mit grünen Rahmen und Pfeilen habe ich die Einstellungen und den Pfad für den Zustand LoadF1 (Code 010 -
Reg2 )
markiert .
Mit blauen Rahmen und Pfeilen habe ich die Einstellungen und den Pfad für den Zustand A1toA0 (Code 011 -
Reg3 )
markiert .
Die lila Rahmen und Pfeile I markierten die Einstellungen und den Pfad für den Status von SWAP (Code 110 -
Reg6 ).
Schließlich zeigen die orangefarbenen Pfeile den parallelen Datenpfad. Und für sie werden keine Maßnahmen ergriffen. Sie kommen immer aus der
SRCA . Wir haben fast immer A0 als
SRCA ausgewählt: Die Daten stammen aus A0. Um die Eingabedaten umzuleiten, müssten wir viele Hilfsaktionen ausführen, aber wir akzeptieren keine Daten.
Daher benötigen wir diese Aktionen hier nicht und jeder findet seine Liste in
AN82156 . Wir müssen auch keine statischen Datenpfadeinstellungen bearbeiten. Schließen Sie daher das
Datenpfad-Konfigurationstool .
Das ist alles. Konzipierte Hardware fertiggestellt. Erste Schritte bei der Entwicklung von C-Code.
Wechseln Sie dazu zur Registerkarte
Quelle und bearbeiten Sie die Datei
main.c.
Die regelmäßige LCD-Initialisierung und die Ausgabe von ABC-Zeichen sehen folgendermaßen aus (ich erinnere Sie daran, dass die Befehle an
FIFO0 gehen, die Dokumentation Pausen zwischen den Teams einfügen muss und die Daten an
FIFO1 gehen , ich habe nichts über die Pausen zwischen den Daten gefunden):
volatile uint8_t* pFIFO0 = (uint8_t*) LCD4bit_1_LCD_DP__F0_REG; volatile uint8_t* pFIFO1 = (uint8_t*) LCD4bit_1_LCD_DP__F1_REG; pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x33; CyDelay (100); pFIFO0[0] = 0x33; CyDelay (5); pFIFO0[0] = 0x20; CyDelay (5); pFIFO0[0] = 0x0C; // CyDelay (50); pFIFO0[0] = 0x01; // CyDelay (50); pFIFO1[0] = 'A'; pFIFO1[0] = 'B'; pFIFO1[0] = 'C';
Was? Warum wird nur das erste Zeichen auf dem Bildschirm angezeigt?

Und wenn Sie Verzögerungen zwischen der Datenausgabe hinzufügen, ist alles in Ordnung:

Das Oszilloskop hat nicht genügend Kanäle für solche Arbeiten. Wir überprüfen die Arbeit an einem logischen Analysator. Der Prozess der Datenaufzeichnung ist wie folgt.

Alle Daten sind vorhanden (drei Paketpaare). Die Zeit für die Installation und das Einrasten von Daten wird in ausreichendem Umfang zugewiesen. Im Allgemeinen wird aus Sicht der Zeitdiagramme alles richtig gemacht. Das wissenschaftliche Problem ist gelöst, die gewünschten Zeitdiagramme werden erstellt. Hier ist Engineering - nein. Der Grund dafür ist die Langsamkeit des im LCD installierten Prozessors. Fügen Sie zwischen den Bytes Verzögerungen hinzu.
Wir werden Verzögerungen mit einem 7-Bit-Zähler bilden und gleichzeitig trainieren, ihn einem solchen System hinzuzufügen. Lassen Sie uns nicht weniger als eine bestimmte Zeit im Ruhezustand sein, und ein Sieben-Bit-Zähler misst diese Zeit für uns. Und wieder werden wir nicht schreiben, sondern Code erstellen. Daher gehen wir erneut zur Hilfskomponente des UDB-Editors und fügen dem Arbeitsblatt einen Zähler hinzu, wobei die Parameter wie folgt festgelegt werden:

Dieser Zähler funktioniert immer (
Enable ist auf 1 gesetzt). Es wird jedoch geladen, wenn sich die Maschine im Zustand
E_UP2 befindet (danach fallen wir sofort in den
Ruhezustand ). Die Zeile
Count7_1_tc wird auf 1 erhöht, wenn der Zähler auf Null zählt.
Dies ist eine zusätzliche Bedingung für das Verlassen des
Ruhezustands . Die Abbildung enthält auch den Wert des Zeitraums, den wir jedoch nicht im Verilog-Code finden. Es muss in den C-Code eingegeben werden. Zuerst übertragen wir den automatisch generierten Verilog-Code, indem wir zur Registerkarte Verilog wechseln. Zunächst sollte der Zähler verbunden werden (wir sehen diesen Code am Anfang der Datei und verschieben ihn auch an den Anfang):
`define CY_BLK_DIR "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0" `include "$CYPRESS_DIR\..\psoc\content\CyComponentLibrary\CyComponentLibrary.cylib\Count7_v1_0\Count7_v1_0.v"
Wie die kreative Verfeinerung von Linien und Konstanten durchgeführt wird, wurde bereits beschrieben, daher zeige ich nur das Ergebnis. Hier sind die Ketten und Zuweisungen, die als Ergebnis hinzugefügt wurden (die restlichen Konstanten, also habe ich sie weggeworfen):
wire Count7_1_load; wire Count7_1_tc; assign Count7_1_load = (SM==E_UP2);
Und hier ist der Zähler selbst, der am Ende der Datei platziert ist. Alle Konstanten werden Ports direkt in dieser Deklaration zugewiesen:
Count7_v1_0 Count7_1 ( .en(1'b1), .load(Count7_1_load), .clock(clk), .reset(1'b0), .cnt(), .tc(Count7_1_tc)); defparam Count7_1.EnableSignal = 1; defparam Count7_1.LoadSignal = 1;
Damit dieser Zähler funktioniert, fügen wir automatisch eine zusätzliche Bedingung hinzu, um den
Ruhezustand zu beenden:

Gleicher Text: case(SM) Idle : begin LCD_E <= 0; if (( !F0empty ) == 1'b1) begin SM <= LoadF0 ; LCD_RS <= 0; end else if (( !F1empty &Count7_1_tc ) == 1'b1) begin SM <= LoadF1 ; LCD_RS <= 1; end end
Die API für den auf diese Weise hinzugefügten Zähler wird nicht erstellt, daher fügen wir der
Hauptfunktion zwei magische Linien hinzu, die ich im Bild und in der Ähnlichkeit mit dem, was ich in der API aus früheren Projekten gesehen habe, gebildet habe (die erste Zeile legt den geladenen Wert des Kontos fest, das gleiche Laden, die zweite startet den Zähler):
*((uint8_t*)LCD4bit_1_Count7_1_Counter7__PERIOD_REG) = 0x20; *((uint8_t*)LCD4bit_1_Count7_1_Counter7__CONTROL_AUX_CTL_REG) |= 0x20; // Start
Der Analysator zeigt, dass im modifizierten Fall die Verzögerung offensichtlich ist:

Das LCD hat auch alle drei Zeichen.
Die programmatische Zeichenausgabe im wirklichen Leben ist jedoch nicht akzeptabel. Wenn Sie sie nur zum FIFO hinzufügen, läuft sie über. Warten Sie, bis der FIFO leer ist - dies bedeutet große Verzögerungen für den Prozessorkern.
Der Prozessor arbeitet mit einer Frequenz von 72 MHz und Daten werden für 7-8 Taktzyklen mit einer Frequenz von 1 MHz ausgegeben. Daher muss der Text im wirklichen Leben mit DMA angezeigt werden. Hier bietet sich das Prinzip „Starten und Vergessen“ an. Alle Verzögerungen für das Zeitdiagramm werden von der UDB generiert, und der DMA-Controller bestimmt die Bereitschaft des FIFO, Daten an uns zu empfangen. Der Prozessorkern muss nur eine Zeile im Speicher bilden und DMA konfigurieren. Danach kann er andere Aufgaben ausführen, ohne sich um die Ausgabe auf dem LCD kümmern zu müssen.Fügen Sie den folgenden Code hinzu: static const char line[] = "This is a line"; /* Defines for DMA_D */ #define DMA_D_BYTES_PER_BURST 1 #define DMA_D_REQUEST_PER_BURST 1 /* Variable declarations for DMA_D */ /* Move these variable declarations to the top of the function */ uint8 DMA_D_Chan; uint8 DMA_D_TD[1]; /* DMA Configuration for DMA_D */ DMA_D_Chan = DMA_D_DmaInitialize(DMA_D_BYTES_PER_BURST, DMA_D_REQUEST_PER_BURST, HI16(line), HI16(LCD4bit_1_LCD_DP__F1_REG)); DMA_D_TD[0] = CyDmaTdAllocate(); CyDmaTdSetConfiguration(DMA_D_TD[0], sizeof(line)-1, CY_DMA_DISABLE_TD, CY_DMA_TD_INC_SRC_ADR); CyDmaTdSetAddress(DMA_D_TD[0], LO16((uint32)line), LO16((uint32)LCD4bit_1_LCD_DP__F1_REG)); CyDmaChSetInitialTd(DMA_D_Chan, DMA_D_TD[0]); CyDmaChEnable(DMA_D_Chan, 1);
Auf dem Bildschirm haben wir:
Fazit
Bei einem halbsynthetischen, aber realitätsnahen Beispiel haben wir beispielsweise den Codeentwicklungsmechanismus für UDB mithilfe eines alternativen Mechanismus beherrscht - dem Datapath Config Tool. Dieser Mechanismus ermöglicht im Gegensatz zum UDB-Editor den Zugriff auf absolut alle UDB-Verwaltungsfunktionen. Die Arbeit damit ist jedoch komplizierter als mit dem UDB-Editor. Die vom Autor des Artikels vorgeschlagene Methode ermöglicht es jedoch, Code nicht von Grund auf neu zu schreiben, sondern ihn einfach zu erstellen, wobei auf Hilfscode zurückgegriffen wird, der vom selben UDB-Editor erstellt wurde.Das beim Schreiben des Artikels erhaltene Testprojekt kann hier übernommen werden .