Tutorial: FPGAs mit Verilog programmieren

Alles andere hier herein
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Staatsapparat

Beitrag von Micha »

So langsam nähert sich die Phase der trivialen Beispiele hier ihrem Ende. Bevor ich zum nächsten Beispiel komme, möchte ich aber noch paar konzeptionelle Dinge sowie etwas Verilog Syntax darstellen.

Wenn man sich mit Schaltsystemen beschäftigt wird einem früher oder später der neudeutsche Begriff "state machine" unterkommen. Die Übersetzung "Staatsapparat" ist vielleicht für den gelernten DDR-Bürger naheliegend, aber trotzdem vollkommen daneben. Tatsächlich wird "state machine" korrekt als Zustandsautomat übersetzt. In den letzten beiden Beispielen wurde nicht ganz ohne Grund das Beispiel "Nachttischlame" aufgegriffen und modelliert. So eine Nachttischlampe ist tatsächlich der einfachste denkbare Zustandsautomat. Es gibt INPUT: was in dem Fall der Taster ist, es gibt in dem Fall zwei ZUSTÄNDE: eingeschaltet oder ausgeschaltet, und es gibt OUTPUT: die Glühbirne bzw. LED, wird in dem Fall direkt aus dem Zustand bedient. Gemäß wissenschaftlicher Definition handelt es sich hier um einen Medwedev-Automaten. Aber wen interressiert schon Wissenschaft (hust) ;) In Elektronikerkreisen wird so ein Ding als Flip-Flop bezeichnet, was ins Deutsche zurückübersetzt wiederum "Badelatsch" bedeutet :?
Das Konzept des Zustandsautomaten wird bei den folgenden Beispielen immer mal wieder ein wenig zum Vorschein kommen. Spätestens dann wenn es zum Modellieren einer CPU kommt geht ohne dieses Konzept bzw. dessen effektive Anwendung garnichts mehr.

Zurück zur Sprache Verilog. Wenn dort eine Variable als wire oder reg definiert wird, erinnert das sehr an eine herkömmliche Programmiersprache:

Code: Alles auswählen

 reg  A;
 wire B;
Der Punkt an dem Vorsicht geboten ist, ist hier der Wertebereich! Man kann in A oder B nicht etwa 3,14159 speichern, nicht mal eine Zahl wie 1024 geht da rein. Solche "Register" und "Drähte" können immer nur 0 oder 1, also nur ein Bitburger aufnehmen bzw. weiterleiten. Ich zeige jetzt einfach mal ein paar Code-Schnipsel, wie man in Verilog aus einfachen Signalen komplexere Daten zaubern kann:

Code: Alles auswählen

 reg  [7:0] Akkumulator;
 wire [15:0] Adressbus;
 ...
 assign Adressbus = 16'h07FF;     // nicht sehr sinnvoll, aber syntaktisch korrekt
 always @ (posedge clk) Akkumulator <= Akkumulator + 1;
Hier wird ein 8 Bit breites Register und ein 16 Bit breiter Bus definiert. Ziemlich suggestiv, oder? Und dann wird noch mal an sinnfreien Beispielen wiederholt, wie man ein wire bzw. ein reg mit Inhalten füttern kann. Beim wire ist es immer die "dauerhafte" assign Zuweisung. Register müssen dagegen in ereignis-getriggerten always Blöcken mit Zuweisungen versorgt werden.
Die Konstante 16'h07FF ist Beispiel für ein weiteres oft verwendetes Ding in Verilog: Die Dezimalzahl vor dem ' gibt die Anzahl der Bits, also die "Datenbreite" an. Nach dem ' folgt die eigentliche Konstante, eingeleitet durch eine Kennzeichnung, um welches Zahlensystem es sich bei der folgenden Angabe handelt. Es gibt b,o,d,h.
Hausaufgabe: findet bitte heraus, welche Bedeutung die Buchstaben b,o,d sowie h in diesem Zusammenhang haben. *warnurspass*
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Ein Byte, hexadezimal dargestellt

Beitrag von Micha »

Im Beispiel 4 soll ein Byte in hexadezimaler Darstellung auf eine 7-Segment-Anzeige ausgegeben werden. Damit das Ganze nicht so langweilig wird, werden ausserdem zwei Taster zum Inkrementieren bzw. Dekrementieren des auszugebenden Wertes definiert. Hier ist erst mal der Quelltext des Hauptmoduls:

Code: Alles auswählen

/* Beispiel 4: Ansteuerung einer gemultiplexten 7-Segment-Anzeige, mittels Inkrement und Dekrement Tastern;
   von den 8 vorhandenen Ziffern werden nur 2 als Hex-Ziffern genutzt um den Wertebereich eines Bytes darzustellen
*/

`include "utils/debouncer.v"  // der Debouncer aus Bsp. 3, ausgelagert

module main (
 input  wire CLK_in,
 input  wire KeyA,
 input  wire KeyB,
 output reg  [7:0] seg,	   // Ziffern-Segmente
 output reg  [7:0] digit	// Ziffern-Position im 8-stelligen Display
);
 wire Key_inc;							// entprelltes Signal fuer INC
 wire Key_dec;	   					// entprelltes Signal fuer DEC
 reg  [7:0] myByte;
 reg  [15:0] countdown;				// Zaehler für Display-Multiplexerei
 reg  [2:0]  Pos;						// Ziffernposition, Wertebereich {0 bis 7}

 // je eine Instanz der Entprellung pro Taster:
 PushButton_Debouncer DebounceDec (.clk(CLK_in), .PB(KeyB), .PB_down(Key_dec));
 PushButton_Debouncer DebounceInc (.clk(CLK_in), .PB(KeyA), .PB_down(Key_inc));
 
 always @ (posedge CLK_in) begin
 if (Key_dec) myByte <= myByte - 1;
 if (Key_inc) myByte <= myByte + 1;
 countdown = countdown - 1;
 if (countdown == 0) begin
   digit <= ~(1 << Pos);		// activate next digit
	
	case(Pos)
	 0: seg <= 8'hFF;	// Ziffern 0 bis 5 hier ungenutzt, FF bedeutet 
	 1: seg <= 8'hFF;   // alle Leuchtsegmente sind ausgeschaltet
	 2: seg <= 8'hFF;
	 3: seg <= 8'hFF;
	 4: seg <= 8'hFF;
	 5: seg <= 8'hFF;
	 6: seg <= bin2hex(myByte[7:4]);  // hoeherwertiges Nibble
	 7: seg <= bin2hex(myByte[3:0]);  // niederwertiges Nibble
	endcase 
	
	Pos <= Pos + 1;
 end 
end 
 
// ----------
// Umwandlung binäre 4 Bit Zahl in Hex-Ziffer 
// ausgelagert in eine Funktion
function [7:0] bin2hex (input [3:0] x);
begin 
  case (x)
   4'h0: bin2hex =8'd192;
	4'h1: bin2hex =8'd249;
	4'h2: bin2hex =8'd164;
	4'h3: bin2hex =8'd176;
	4'h4: bin2hex =8'd153;
	4'h5: bin2hex =8'd146;
	4'h6: bin2hex =8'd130;
	4'h7: bin2hex =8'd248;
	4'h8: bin2hex =8'd128;
	4'h9: bin2hex =8'd144;
	4'hA: bin2hex =8'd136;
	4'hB: bin2hex =8'd131;
	4'hC: bin2hex =8'd198;
	4'hD: bin2hex =8'd161;
	4'hE: bin2hex =8'd134;
	4'hF: bin2hex =8'd142;
  endcase
end  
endfunction 
 
endmodule // Ende Hauptmodul
Anmerkungen zum Code:
1. Das Modul zum Tasten-Entprellen - immer noch das gleiche wie aus Bsp. 3 - ist hier in eine Include Datei ausgelagert worden. Wenn so ein Modul ausreichend getestet ist und seine Sache macht - aus den Augen, aus dem Sinn! Als Black Box tut es seinen Dienst, der eigentliche Projektcode wird dadurch schlanker.

2. Während in den bisherigen Beispielen das Hauptmodul immer noch asynchron arbeitete, hat hier jetzt schliesslich doch die "Clock" Einzug gehalten. Früher oder später kriegen wir euch alle - komplexere FPGA Projekte gehen früher oder später auf synchrones Design. Typisches Erkennungsmerkmal im Code: always @ (posedge clk) wobei die konkrete Bezeichnung der Clock natürlich variieren kann.

3. Ein paar neue Sprachelemente - insbesondere die case Anweisung haben sich hier in das Beispiel eingeschlichen. Ich denke/hoffe der Code ist selbsterklärend. Ansonsten einfach im FPGA-Diskussion Thread fragen ;)
Dateianhänge
Das Byte in freier Wildbahn
Das Byte in freier Wildbahn
Bsp4.zip
Archiv mit dem Projektcode
(1.78 KiB) 472-mal heruntergeladen
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

bevor es ans nächste Beispiel geht...

Beitrag von Micha »

Das nächste Beispiel wird ziemlich spannend, versprochen! Da geht es um eine CPU mit RAM und Anzeige sowie Tastern zum Bedienen. Klingt nach den bisherigen simplen Beispielen utopisch, stimmts? War es für mich selber auch, bis vor zwei Tagen. Eigentlich hatte ich mich schon ziemlich in die Idee verliebt, einen PDP11 Prozessor auf FPGA zu basteln und diesen noch vor Ende des Jahrzehnts fertigzustellen. "Not because it is easy but because it is hard..." um mal Kennedy zu zitieren.
Eigentlich würde ich furchtbar gern und am liebsten sofort anfangen, einen PDP11 Prozessor zu basteln. Aber das ist ne wirklich große und komplexe Aufgabe, überhaupt nicht für so ein Tutorial geeeignet wo es drum geht, in begreifbaren Schritten was zu lernen.
Als Hauke in dem Diskussions-Thread angeregt hatte, doch für den Anfang auch mal über CPLDs nachzudenken erschien mir das zunächst viel zu popelig. Aber dann hab ich ein bisschen recherchiert und bin so über die MCPU gestolpert. Das ist ein Projekt, auf opencores.com zu finden, in dem eine minimalistische CPU auf einem eher bescheidenen CPLD realisiert wurde. Das Projekt hat Lehrcharakter - mir fällt jetzt spontan am ehesten der LC80 als Vergleich ein, obwohl der Vergleich hinkt.
Mir wurde klar, dass ich einige Zutaten für das Drum und Dran hier schon vorgestellt habe: Entprellte Taster zur Eingabe, eine 8-stellige 7-Segment-Anzeige. Hinzu kommt dann die MCPU - ein einfacher Prozesser dessen Quelltext auf eine Seite passt, sowie die Ansteuerung von SRAM, was ebenfalls sehr einfach geht.

Ich hänge die MCPU Projektbeschreibung hier mal an. Für Interessierte schon mal was, um sich einzulesen. Allerdings werd ich den Prozessorcode mit gleicher Funktionalität komplett neu schreiben. Ich find das Konzept genial, aber den Originalcode Sch***. Unter der Vorgabe, den Code auf eine Seite zu pressen hat dessen Lesbarkeit arg gelitten.

Stay tuned ;)
Dateianhänge
MCPU.pdf
MCPU Projektbeschreibung
(165.23 KiB) 585-mal heruntergeladen
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

vorher noch 'n paar Grundlagen

Beitrag von Micha »

Bevor es wieder richtig losgeht ist es vielleicht ganz gut, ein paar weitere Grundlagen hier reinzukleckern.

Blockierende und nichtblockierende Zuweisungen zu Registern

Vielleicht ist jemandem aufgefallen, dass in den Beispielen manchmal = und manchmal <= für Zuweisungen verwendet wird. Das erste ist die sog. blockierende Zuweisung, das zweite die nichtblockierende Zuweisung. Mit der ersten kann man Sequenzen schreiben, in der weiter unten im Text stehende Zuweisungen mit den Erbgebnissen von weiter oben rechnen können. Mit der zweiten Art kann man simultane Aktionen definieren. Klassisches Beispiel - zwei Variablen vertauschen. Mit blockierenden Anweisungen würde das so aussehen, wie in einer konventionellen Programmiersprache:

Code: Alles auswählen

 tmp = a;
 a = b;
 b = tmp;
Mit nichtblockierenden Anweisungen sieht das in Verilog dagegen so aus:

Code: Alles auswählen

 a <= b;
 b <= a;
Ziemlich cool, oder? Eine einfache Faustregel, wann man blockierende bzw. nichtblockierende Zuweisung verwenden soll habe ich bisher allerdings leider noch nicht gefunden. Diese beiden Arten von Zuweisungen gibt es auf jeden Fall nur für reg(ister). Für wire, also Drähte gibt es nur eine Art von dauerhafter Zuweisung:

Code: Alles auswählen

assign wireX = <Ausdruck>;
Absolutes NoGo: Multiple Assignments
Nichts neues für Digitalelektronik-Bastler: man kann eine Signalleitung zwar auf mehrere Empfänger verteilen, aber man darf niemals zwei oder mehr Signalleitungen einfach zusammenführen. Es wäre ungewiss, was im Konfliktfall für ein resultierendes Signal herauskäme. In Verilog muss man erst mal ein Gefühl entwickeln, wie und wann sowas passiert. Das folgende Beispiel wird einem der Compiler mit einer Fehlermeldung der Art "multiple assignments to x" um die Ohren hauen, obwohl es auf den ersten Blick garnicht so verkehrt aussieht:

Code: Alles auswählen

// funktioniert so nicht!
 always @ (posedge key1) x <= 1;
 always @ (posedge key2) x <= 0;
Es könnten ja zumindest theoretisch beide Taster genau zeitgleich gedrückt werden - was bekommt x dann zugewiesen?
Wie folgt wird es vom Compiler akzeptiert:

Code: Alles auswählen

 always @ (posedge clock) begin
  if (key1) x <= 1;
  else if (key2) x <= 0;
 end // des always blocks
Mehrere Anweisungen die zusammengehören werden in Verilog in begin ... end Blöcke eingeschlossen. Als Programmierer hat man die Wahl, die Dinger sehr großzügig im Zweifelsfall einzusetzen, oder nur dort wo sie wirklich zwingend notwendig sind. Wenn man es logisch korrekt macht, funktioniert beides. Das ist überhaupt auch so ein Thema:

Kompliziert vs. einfach
Im folgenden Codeschnipsel soll für ein 32 Bit breites Register die Parität bestimmt werden, also ob die Zahl der gesetzten Bits gerade oder ungerade ist:

Code: Alles auswählen

// Variante 1: eine Funktion, die die Paritaet berechnet, funktioniert:
function parity;
input [31:0] data;
integer i;
begin
  parity = 1;
  for (i= 0; i < 32; i = i + 1) begin
    parity = parity ^ data[i];
  end
end
endfunction

Code: Alles auswählen

//Variante 2: viel einfacher, funktioniert ebenfalls:
  wire parity = ~^data;
In der zweiten Variante wird ein wire definiert und gleich bei der Definition dauerhaft einem Ausdruck zugewiesen, so dass eine separate assign Zuweisung nicht mehr notwendig ist. Der hier unäre bitweise Operator ^ "xodert" sich durch das gesamte Register data Bit für Bit durch, bis ein Ein-Bit-Endergebnis vorliegt. Das wird dann noch durch den Negationsoperator ~ umgeklappt, um ein high-aktives Paritätssignal zu bekommen.
Die zweite Variante ist bezüglich Verstehen allerdings eine echte Kopfnuss. Wer selbstständig auf solche Ideen kommt, hat einem Platz im Programmierer-Olymp sicher. Davon bin ich noch ewig weit weg - hab das Beispiel bloss abgeschrieben ;)

Signalrichtung
Im richtigen Leben gibt es Eingangssignale, Ausgangssignale und bidirektionale Signale. Bei den Ports eines Moduls in Verilog muss man die Datenrichtungen definieren, dafür gibt es entsprechend die Schlüsselworte input, output und inout.

Man muss allerdings beachten, dass bidirektionale Signale nur an der Aussenkante des FPGA, also an dessen Pins möglich sind. Im Innern gibt es technologiebedingt nur unidirektionale Signale. Will man Daten zwischen mehreren Modulen im Innern des FPGA hin- und hersenden, braucht man für jede Richtung eine eigene Signalleitung bzw. Bus. Bidirektionale Signale sowie deren Handbhabung werden uns demnächst bei der Ansteuerung eines externen SRAM über den Weg laufen ;)
Zuletzt geändert von Micha am Fr 31. Mai 2013, 09:58, insgesamt 3-mal geändert.
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

High Level Design

Beitrag von Micha »

Wer ein wenig in den Artikel zur MCPU hineingelesen hat, wird eventuell folgende Erkenntnisse mitgenommen haben:

* MCPU ist ein minimalistisches, als Prozessor voll funktionales Design
* der Code ist sehr effizient formuliert aber nicht ganz einfach zu verstehen
* Die Ansteuerung von externem SRAM ist bereits vollständig in dem Beispielcode des Prozessors enthalten
* ansonsten ist das Ding jedoch autistisch - der Artikel gibt keine Informationen zu weiterer I/O Peripherie

Unser Ziel soll es sein, die MCPU auf einem FPGA Testboard zu implementieren. Dazu wird ein (hoffentlich) besser lesbarer Quelltext für die CPU selber gehören. Wenn der länger wird als das Original - na und ;) Ausserdem werden wir bei der Verwendung von externem SRAM als Hauptspeicher bleiben. Diese Entscheidung treffe ich willkürlich. 64 Byte Hauptspeicher kann man ohne weiteres auch direkt im FPGA anlegen. Das würde das Design sogar erheblich einfacher machen - aber zu einfach ist dann auch wieder doof - stimmts?
Last but not least benötigen wir etwas I/O, um mit der MCPU experimentieren zu können. Mir schweben da konkret folgende Dinge vor:

* zwei Betriebsmodi "Edit" und "Run". Im ersten kann man den Inhalt des Hauptspeichers Byte für Byte bearbeiten, im zweiten ein Programm schrittweise ausführen. Für Eingaben werden ein paar Taster auf dem Tesboard verwendet.
* als Anzeige werden wir die 8stellige 7-Segmentanzeige verwenden. Man könnte deren Dezimalpunkte "missbrauchen" um bestimmte Teile der Anzeige visuell zu trennen, z.B. so: 1E.FF um Adresse und Speicherinhalt abzugrenzen. Vermutlich lässt sich hier der Code aus Beispiel 4 wiederverwenden, zumindest als Startpunkt.

Ich werd dann mal anfangen, Stück für Stück folgende 4 Module zu entwickeln:
DISPctrl - der Controller für das 8stellige 7-Segment-Display.
Editor - der Programm-Editor.
RAMctrl - der Controller für den SRAM, der seine Dienste für MCPU und Edit bereitstellen wird.
MCPU - die eigentliche CPU, allerdings ohne direkte SRAM Ansteuerung - da hier ggf. auch noch der Editor zugreifen möchte.

... ähm, räusper: nicht 4 sondern 5 Module: wir brauchen ja auch noch ein Hauptmodul! Die oberste Ebene, auf der alles halbwegs sinnvoll elektrisch zusammengefriemelt wird. Das nenn ich einfach main, in Erinnerung an gute alte C Zeiten ;)

Zusätzlich kommt noch bisschen anderer Kleckerkram auf uns zu - die Taster sind wieder mal zu entprellen, und die Signale zwischen MCPU/Editor und SRAMctrl müssen eventuell je nach Betriebsart umgeschaltet werden. Für letzteres benötigen wir etwas, das in der Fachsprache "Multiplexer" genannt wird, machmal auch verkürzt als MUX bezeichnet.

So, das war jetzt das Hai-Löffel-Däsein in seiner allerabschdrackdesten Form.
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

MCPU System, erste Teile

Beitrag von Micha »

Das MCPU-System wird nun Stück für Stück entstehen. Dabei ist es möglich, auch schon mit Teilsystemen erste Tests durchzuführen - diese Herangehensweise kennen wir ja bestens von Aufnahme und Inbetriebnahme des Hive ;)

Damit der erste Bauabschnitt überhaupt verständlich wird, muss ich etwas ausholen und die geplante Bedienphilosophie des Systems vorstellen. Das System wird zwei Modi haben. Den "Edit" Modus, wo man im Speicher navigieren und Daten sowie Programmbefehle eingeben kann. Sowie den "CPU" Modus, wo man Programme schrittweise ausführen kann. Wobei "schrittweise" in dem Fall sogar noch weiter ins Detail geht als Maschinenbefehle - es wird möglich sein, die einzelnen Phasen eines Maschinenbefehls zu "erforschen". Die beiden Fotos zeigen Beispiele, was das Display in den beiden Modi anzeigen wird: im Edit Modus steht ganz links ein E (als Orientierungshilfe), sowie eine Speicheradresse und deren Dateninhalt, getrennt durch einen Punkt. Mit zwei Tastern wird es möglich sein, die Adresse zu inkrementieren/dekrementieren. Zwei weitere Taster werden dazu dienen, je eine der Ziffern des Datenbytes zu inkrementieren.
Im CPU Modus sind ein paar mehr Informationen im Display unterzubringen. Aus Platzgründen wird auf einen Modus-Buchstaben verzichtet. Jeweils durch einen Punkt getrennt, werden folgende Informationen angezeigt: Prozessorstatus.Carry.Instruktion.Adresse.Akku - das zweite Foto gibt ein Beispiel.

In der ersten Ausbaustufe wird das Displaymodul aus Beispiel 4 so erweitert, dass es in der Lage ist, für beide Modi passende Informationen auszugeben. Ausserdem wird eine Umschaltung realisiert. Das Hauptmodul wird gefake'te Daten für beide Modi bereitstellen, so dass man beim Umlegen des Schalters sehen kann ob Umschaltung sowie Anzeige korrekt arbeiten. Hier ist der Code des Hauptmoduls:

Code: Alles auswählen

/* MCPU Projekt fuers Cyclone II Chinaboard
   Hauptmodul, fuer erste einfache Tests von Teilfunktionen:
   
   Umschalter EDIT/CPU sowie Display
*/

`include "utils/busmux.v"     // Bus-Multiplexer
`include "utils/debouncer.v"  // Entprellung fuer Taster und Umschalter
`include "moduls/DISPctrl.v"  // Steuerung 7-Seg-Anzeige

module main (
 input  wire clk,	// Systemtakt
 input  wire Switch1,	// Umschalter EDIT/CPU
 output wire [7:0] seg,
 output wire [7:0] digit 
);

wire [5:0] Addr;
wire [7:0] Data;
wire Modus; 

 Debouncer SW1 (.clk(clk), .PB(Switch1), .PB_state(Modus) );
 DISPctrl Anzeige  (.clk(clk),
                    .Addr(Addr),
						  .Data(Data),
						  .mode(Modus),
						  .CPUstate(stat),
						  .Carry(Carry),
						  .Instr(code),
						  .seg(seg),
						  .digit(digit));

 BusMux #(14) Mux1 (.sel(Modus), 
                    .InpA({Addr1,Data1}),
						  .InpB({Addr2,Data2}),
						  .Out({Addr,Data}) );

 // vorlaeufige Testdaten fuers Display:
 wire [5:0] Addr1 = 6'h13;
 wire [7:0] Data1 = 8'h35;
 wire [5:0] Addr2 = 6'h19;
 wire [7:0] Data2 = 8'hFF;
 wire Carry = 1;
 wire [1:0] stat = 2'h2;	// Prozessor-Status
 wire [7:0] code = 8'h4B;	// akt. Maschinencode
 
endmodule
Zunächst folgende Anmerkungen: ich habe angefangen, das Projekt in Unterverzeichnisse zu strukturiern, was man an den include Anweisungen sehen kann. Ein Unterverzeichnis utils für kleine Helferlein wie den Debouncer, sowie ein Unterverzeichnis moduls für projektspezifische Bausteine. Verzeichnisse werden im Unix-Stil durch den "normalen" Schrägstrich getrennt, auch wenn man unter Windows entwickelt.
Die Verwendung von "white spaces" ist in Verilog genauso wie in C: Leerzeichen, Tab und sogar Zeilenumbruch werden ignoriert. Daher kann man die Ports eines Moduls entweder in eine Zeile schreiben. So wie im Beispiel beim Debouncer. Oder man schreibt die Liste der Ports vertikal. So wird es hier beim Display und dem Umschalter gemacht.
Ebenfalls sehr bequem in Verilog: man kann mitttels geschweifter Klammern aus Einzelsignalen, Bussen, sowie Konstanten umfangreichere Busse zusammenbasteln. Sowas geht auf beiden Seiten einer Zuweisung - was auch Sinn macht, wenn man es sich nur als Schaltung vorstellt:

Code: Alles auswählen

// Beispiele zur Verwendung von { }
 reg a;
 reg b;
 wire [3:0] meinBus;
 wire x;
 wire y;
 ...
 assign meinBus = {2'b0,b,a};   // die oberen 2 Bit werden hier mit Nullen belegt
 assign {x,y} = meinBus[2:1];    // zwei Bit aus der Mitte von meinBus auf zwei Einzelsignale
In dem Code weiter oben findet sich eine praktische Anwendung. Dort wird nur ein Multiplexer verwendet, der gleich in einem Aufwasch Daten und Adressen umschaltet.
Der komplette Beispielcode, einschliesslich hier nicht besprochener Bestandteile, findet sich in dem angehängten ZIP.
Dateianhänge
Anzeige, im Edit Modus
Anzeige, im Edit Modus
Anzeige, im CPU Modus
Anzeige, im CPU Modus
Bsp5_Ausbaustufe1.zip
ZIP mit den Quelltexten
(2.76 KiB) 511-mal heruntergeladen
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Mischen Äkomplischd ;)

Beitrag von Micha »

In den letzten Tagen hab ich weiter an dem MCPU Projekt gebastelt und schließlich alles zum laufen gebracht.
*Froi*
In den Anlagen sind vorab die Verilog Quelltexte des Prozessors sowie zwei Beispiel-Maschinenprogramme enthalten. Detailierte Erklärungen folgen dieser Tage.
Dateianhänge
MCPU_ProgrBSP.pdf
Beispiele für 2 einfache MCPU Maschinenprogramme
(7.31 KiB) 494-mal heruntergeladen
MCPU.zip
Verilog Quelltexte
(9.03 KiB) 479-mal heruntergeladen
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Synthese und Simulation

Beitrag von Micha »

Mit dem MCPU Projekt bin ich vermutlich an der Obergrenze der "einfachen" Projekte angestoßen. Nach paar Tagen überlegen, coden, testen hat das Ganze schliesslich funktioniert. Der komplette Code ist ja in dem Beitrag weiter oben als zip enthalten. Ich möchte den Code nicht komplett diskutieren, sondern lediglich ein paar Anmerkungen machen:

Wer sich den Code anschaut wird ganz sicher die reichlich verwendeten Präprozessor-Deklarationen bemerken. Das ist wieder eine Ähnlichkeit zu C. Der Einsatz dieser Deklarationen kann Code sehr viel besser lesbar machen. Oder sogar Leben retten! *warnurspass*, aber im Ernst: die verwendete Deklaration:

`default_nettype none

ist in Verilog das, was in VBA z.B. OPTION EXPLICIT ist - der freiwillig auferlegte Zwang, alle Dinge explizit zu deklariern. Wer schon mal stundenlang in "altmodischen" Basic einer Ungereimtheit auf der Spur war, die sich schliesslich als Tippfehler bei einem Variablennamen herausstellte, der weiss wovon ich spreche.

Im Unterschied zu C wird in Verilog nicht # als Zeichen vor diesen Anweisungen verwendet sondern ` . Denn das Zeichen # ist in Verilog bereits anderweitig vergeben. Wenn wir nach dem "wieso" fragen, wird es sehr sehr prinzipiell. Denn im Grunde hab ich in diesem ganzen Thread bisher eine Sache völlig unter den Teppich gekehrt. Alles was ich hier bisher dargestellt habe fällt unter den Begriff Synthese. Also Code den man schreibt, um einem FPGA oder CPLD Leben einzuhauchen, damit er eine bestimmte Funktionalität erhält. Solcher Code wurde soweit nach bestem Wissen und Gewissen entwickelt und per "trial & error" gestestet.
Ein Profi auf dem Gebiet würde einem mitteilen, das so etwas für relativ einfache Projekte eventuell funktioniert (daher mein erster Satz im Posting), aber man damit irgendwann an Grenzen stoßen wird. Wenn komplexe Sachen nicht funktionieren und man sich den Kopf zerbricht und probiert und frickelt, und es trotzdem nicht funktionieren mag. Logik kann eine verdammt hinterhältige Angelegenheit sein...

HDL Sprachen wie VHDL oder Verilog haben tatsächlich zwei Aspekte: Simulation (noch garnicht besprochen) und Synthese. Bisher ging es ausschliesslich um letzteres. Ich habe schon von einigen Einsteigern zu hören bekommen, dass sie zunächst so angefangen haben. Aber worum genau geht es dann bei dem Thema Simulation?
Um es mal einfach auszudrücken: Code, der nicht mit dem Ziel geschrieben wird, ihn in den FPGA-Chip "hineinzubraten" sondern der zum Testen des Projekts vorgesehen ist. Zu einer ordentlichen Entwicklungsumgebung gehört neben dem Compiler auch ein Simulator. Das kann man sich so in etwa wie einen Logik-Analyzer vorstellen, nur leistungsfähiger und komplexer. Der Entwickler beschreibt Szenarien als Folgen von Eingangssignalen und das Programm simuliert dann, wie sich der Projektcode verhält. Zu einem professionell entwickelten Projekt gehört immer eine sog. "Testbench".

An diesem Punkt steht das Tutorial hier am Scheideweg - es gäbe durchaus schwerpunktmäßig noch einige interessante, rel einfache Synthese-Projekte: z.B. wie "macht" man VGA, oder das Projekt einer Stoppuhr. Also weitere Code-Beispiele wie bisher. Oder alternativ Schwerpunkt auf Beispielen zum Thema Simulation. Glaube ich muss da noch mal drüber schlafen bevor ich das entscheide...
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Re: Tutorial: FPGAs mit Verilog programmieren

Beitrag von Micha »

Naja, inzwischen hab ich ein wenig in die Thematik Simulation heineingerochen. Sobald man selber komplexere Projekte erfolgreich realisieren will ist es ein "Muss", sich mit Simulation zu beschäftigen. Da es Ziel dieses Tutorials ist, anhand einfacher Beispiele in die Thematik konfigurierbare Hardware einzuführen, werde ich Simulation allerdings zunächst weiterhin aussenvor lassen und mich auf Beispiele aus dem Bereich der Synthese konzentriern. Denn auch wenn mancher Profi anderes behauptet - man kommt (zumindest im Hobbybereich) auch ohne Abläufe schrittweise zu simulieren schon ziemlich weit. Zumindest entspricht das meiner eigenen Lernweise - ich hab gestern einen VGA Zeichensatzmodus zustande gebracht, ganz ohne die Logik zu simulieren. Soo wahnsinnig komplex ist das allerdings auch nicht, wie man sehen wird. Mit dem Thema VGA kommt auch der Bedarf nach RAM, mit Zeichensatz eventuell zusätzlich auch ROM.
Aber lassen wir es langsam angehen, zunächst habe ich erst mal zwei bunte Bilder vorzuzeigen. Auf dem ersten Bild wird der Zeichensatz byteweise direkt als Farbinformation ausgegeben. Das war ein erster Test. Das zweite Bild zeigt dann tatsächlich den Zeichensatz, die Logik muss sich dazu durch eine zusätzliche Indirektion quälen: aus dem Textspeicher kommt der ASCII Code des Zeichens an der aktuellen Bildschirmposition, mit diesem Code werden dann die Bitmuster des Zeichens aus dem Zeichensatz-ROM geholt. Ganz schön wirr, oder?

Ich geh jetzt erst mal Code aufräumen - der sieht nach der gestrigen spontanen Session noch etwas wirr aus. Danach gibts dann hier Stück für Stück weitere Details.
Dateianhänge
Erster Test: Darstellung von Zeichensatz-Bytes als Farben
Erster Test: Darstellung von Zeichensatz-Bytes als Farben
Ein Zeichensatz, mittels FPGA dargestellt.
Ein Zeichensatz, mittels FPGA dargestellt.
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Benutzeravatar
Micha
Beiträge: 813
Registriert: Sa 24. Mär 2012, 21:45
Wohnort: Merseburg
Kontaktdaten:

Re: Tutorial: FPGAs mit Verilog programmieren

Beitrag von Micha »

Hier ist nun endlich das etwas aufgeräumte VGA Beispiel.
Es werden passende Signale für den 640x480 VGA Modus generiert. Ausserdem wird ein Zeichensatz-Rom verwendet (8x8 pixel pro Zeichen), der Bildschirminhalt wird in einem Dual-Port-RAM angelegt, der die passende Größe von mindestens 2400 Byte besitzt um 30 Zeilen a 80 Zeichen darzustellen.

Die Quelltexte des Projekts sind in dem ZIP Archiv (Anlage) enthalten. Dabei ist main.v das Hauptmodul. In dem Unterverzeichnis utils stecken die rel. simplen Quelltexte, die RAM und ROM definieren. Möchte man in so einem Projekt solche Ressourcen wie internen Speicher des FPGA verwenden, hat man verschiedene Möglichkeiten:
A) Man kann es nach Gutdünken selber als Quelltext schreiben, mit dem Risiko dass der Compiler dann generische LE für Speicher missbraucht
B) Man kann per Mega-Wizard ein Modul erzeugen, mit einem Interface-Quelltext, der wenig sagt und herstellerspezifisch ist
C) Man kann einen Quelltext verwenden, der vom Compiler als RAM oder ROM erkannt und entsprechend umgesetzt wird.
Hier in diesem Projekt wird von Variante C) Gebrauch gemacht. Ist meiner Meinung nach die beste Lösung, bietet zumindest die Chance für portablen Quelltext.

Mir war irgendwann aufgefallen, dass ich mit einem 8x8 Zeichensatz und einem 8x16 Feld pro Zeichen auf dem VGA Schirm was geiles anstellen kann: nämlich das Zeichen nur in jeder 2ten Zeile darstellen, sowie in den leeren Bereichen den "Elektronenstrahl" mit einem minimalen Helligkeitswert darstellen. Das Bild illustriert das.

Der Inhalt des Bildwiederhol-Speichers wird vorläufig nur aus einer Textdatei screen.dmp statisch dargestellt. Die nächste Aktion wird darin bestehen, einen PS/2 Tastaturtreiber zu basteln, um interaktv Zeichen in den BWS schreiben zu können...
Dateianhänge
Ein &quot;Flachmann&quot; simuliert eine Röhre ;-)
Ein "Flachmann" simuliert eine Röhre ;-)
VGA_Textmode.zip
Die Verilog Quelltexte des Projekts
(3.09 KiB) 499-mal heruntergeladen
Also vonder Sache här tätch jetz ma behaupten "Mischn ägomplischd" un so...
Antworten