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:
Mit nichtblockierenden Anweisungen sieht das in Verilog dagegen so aus:
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:
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
