Programming lesson
Programmierbarer BCD-Zähler in Verilog: Schritt-für-Schritt-Tutorial für FPGA-Synthese (CDA4203L)
Lerne, wie du einen programmierbaren 0-99 BCD-Zähler in Verilog entwirfst, simulierst und auf einem FPGA-Board synthetisierst. Inklusive Testbench und Timing-Analyse.
Einführung: Warum ein programmierbarer BCD-Zähler?
Im Lab 3 des Kurses CDA4203L geht es um fortgeschrittenes sequentielles Hardware-Design mit Verilog und FPGA-Synthese. Ein programmierbarer BCD-Zähler (Binary-Coded Decimal) ist ein klassisches Beispiel, das die Konzepte von Zustandsautomaten, Zählern und Binär-zu-BCD-Konvertierung vereint. Stell dir vor, du entwickelst eine Echtzeitanzeige für eine Gaming-Plattform: Der Zähler könnte die Punktzahl in einem eSports-Turnier darstellen, die von 0 bis 99 läuft und bei einem festgelegten Maximum stoppt – genau wie der max_count-Eingang in dieser Aufgabe.
Im Mai 2026 sind FPGAs in vielen Bereichen relevant: von KI-Beschleunigern bis zu Embedded Systems in Smart-Home-Geräten. Dieses Tutorial führt dich durch die Erstellung eines solchen Zählers, basierend auf der Aufgabenstellung von CDA4203L. Du lernst, wie du vorgegebene Module (Binary_bcd.v, Counter.v) nutzt und die fehlenden Module (Prog_counter.v, Final_bcd_counter.v) selbst schreibst.
Überblick über die Module
Das Top-Level-Design besteht aus vier Hauptmodulen:
- Counter.v – Ein einfacher 7-Bit-Binärzähler mit Enable und synchronem Reset.
- Prog_counter.v – Ein programmierbarer Zähler, der bei Erreichen eines Maximalwerts stoppt.
- Binary_bcd.v – Konvertiert den binären Zählerstand in zwei BCD-Ziffern (vollständig vorgegeben).
- Final_bcd_counter.v – Die Top-Level-Instanziierung, die alles verbindet.
Die Eingänge sind: run (Schalter SW7), max_count (7 Bit, Schalter SW6–SW0) und der Takt. Die Ausgänge sind digit_2 (Zehnerstelle) und digit_1 (Einerstelle), jeweils 4 Bit.
Schritt 1: Den programmierbaren Zähler erstellen (Prog_counter.v)
Die Datei Prog_counter.v ist das Herzstück. Sie muss den 7-Bit-Zähler aus Counter.v instanziieren und Logik hinzufügen, die den Zähler bei Erreichen von max_count stoppt. Der run-Eingang steuert den Modus: Bei run=0 wird der Zähler auf 0 zurückgesetzt und der Benutzer kann max_count setzen; bei run=1 zählt der Zähler von 0 bis max_count und stoppt dann.
Hier ein möglicher Code für Prog_counter.v:
module Prog_counter(
input wire clk,
input wire rst_n,
input wire run,
input wire [6:0] max_count,
output wire [6:0] count_out
);
wire enable;
wire [6:0] counter_out;
// Instanziierung des 7-Bit-Zählers
Counter #(.WIDTH(7)) u_counter (
.clk(clk),
.rst_n(rst_n),
.enable(enable),
.count(counter_out)
);
// Steuerlogik
assign enable = run && (counter_out < max_count);
assign count_out = (run) ? counter_out : 7'd0;
endmoduleErklärung: Der Zähler ist nur aktiv, wenn run=1 und der aktuelle Zählerstand kleiner als max_count ist. Sobald der Zähler max_count erreicht, wird enable auf 0 gesetzt und der Zähler stoppt. Bei run=0 wird count_out auf 0 gesetzt (über die assign-Anweisung). Der Reset rst_n setzt den Zähler asynchron zurück.
Schritt 2: Den Top-Level-BCD-Zähler erstellen (Final_bcd_counter.v)
In Final_bcd_counter.v werden der programmierbare Zähler und der Binär-zu-BCD-Konverter verbunden. Der Konverter erwartet einen 7-Bit-Eingang und liefert zwei 4-Bit-Ausgänge für Zehner und Einer.
module Final_bcd_counter(
input wire clk,
input wire rst_n,
input wire run,
input wire [6:0] max_count,
output wire [3:0] digit_2, // Zehner
output wire [3:0] digit_1 // Einer
);
wire [6:0] count_bin;
// Programmierbarer Zähler
Prog_counter u_prog (
.clk(clk),
.rst_n(rst_n),
.run(run),
.max_count(max_count),
.count_out(count_bin)
);
// Binär-zu-BCD-Konverter (vorgegeben)
Binary_bcd u_bcd (
.binary_in(count_bin),
.ones(digit_1),
.tens(digit_2)
);
endmoduleDamit ist die Logik komplett. Jetzt müssen wir das Design testen.
Schritt 3: Testbench und Simulation
Eine gründliche Testbench ist entscheidend, um alle Bedingungen zu prüfen. Hier ein Beispiel für eine Testbench, die verschiedene Szenarien abdeckt:
module tb_Final_bcd_counter;
reg clk;
reg rst_n;
reg run;
reg [6:0] max_count;
wire [3:0] digit_2, digit_1;
Final_bcd_counter uut (
.clk(clk),
.rst_n(rst_n),
.run(run),
.max_count(max_count),
.digit_2(digit_2),
.digit_1(digit_1)
);
// Taktgenerator (100 MHz -> 10 ns Periode)
initial clk = 0;
always #5 clk = ~clk;
// Testablauf
initial begin
// Initialisierung
rst_n = 0;
run = 0;
max_count = 7'd50;
#20 rst_n = 1;
// Test 1: Set-Modus (run=0) - Ausgabe sollte 0 sein
#20;
if (digit_2 !== 4'd0 || digit_1 !== 4'd0) $display("Test 1 fehlgeschlagen: Erwarte 00");
// Test 2: Starte Zähler mit max_count=50
run = 1;
#500; // Warte 50 Taktzyklen (bei 100 MHz: 500 ns)
// Nach 50 Takten sollte der Zähler bei 50 stehen und dann stoppen
if (digit_2 !== 4'd5 || digit_1 !== 4'd0) $display("Test 2 fehlgeschlagen: Erwarte 50");
// Test 3: Weiterzählen sollte nicht passieren
#100;
if (digit_2 !== 4'd5 || digit_1 !== 4'd0) $display("Test 3 fehlgeschlagen: Zähler sollte bei 50 stoppen");
// Test 4: Zurücksetzen und neuen Max-Wert setzen
run = 0;
max_count = 7'd99;
#20;
if (digit_2 !== 4'd0 || digit_1 !== 4'd0) $display("Test 4 fehlgeschlagen: Erwarte 00 nach Reset");
// Test 5: Zähler bis 99 laufen lassen
run = 1;
#1000; // 100 Taktzyklen
if (digit_2 !== 4'd9 || digit_1 !== 4'd9) $display("Test 5 fehlgeschlagen: Erwarte 99");
// Test 6: max_count Änderung während run=1 sollte ignoriert werden
max_count = 7'd10;
#100;
if (digit_2 !== 4'd9 || digit_1 !== 4'd9) $display("Test 6 fehlgeschlagen: Änderung ignoriert");
$display("Alle Tests bestanden!");
$finish;
end
endmoduleSimuliere die Testbench mit einem Tool wie ModelSim oder Vivado. Überprüfe die Waveforms, um sicherzustellen, dass der Zähler korrekt startet, stoppt und die BCD-Ausgabe stimmt.
Schritt 4: Synthese und FPGA-Implementierung
Für die Synthese auf dem Anvyl-Board benötigst du eine UCF-Datei, die die Pins zuweist. Hier ein Auszug:
NET "clk" LOC = "D11"; # 100 MHz Takt
NET "rst_n" LOC = "T10"; # z.B. Button
NET "run" LOC = "P8"; # SW7
NET "max_count[6]" LOC = "P7"; # SW6
... # Weitere Switches und LEDs
NET "digit_2[3]" LOC = "R4"; # LED7
... # Weitere LEDs
Nach der Synthese lade das Bitstream auf das FPGA und teste die Funktionalität. Die LEDs zeigen die BCD-Ziffern an: LED7–LED4 für die Zehnerstelle, LED3–LED0 für die Einerstelle.
Häufige Fehler und Debugging-Tipps
- Zähler stoppt nicht: Überprüfe die Enable-Logik. Stelle sicher, dass
enablenur aktiv ist, wenncount < max_count. - BCD-Ausgabe falsch: Der Konverter
Binary_bcdarbeitet korrekt, aber stelle sicher, dass der Eingangcount_bin7 Bit breit ist. - Reset-Verhalten: Bei
run=0muss der Zähler auf 0 gesetzt werden, nicht nur der Ausgang.
Fazit
Du hast nun einen voll funktionsfähigen programmierbaren BCD-Zähler in Verilog entworfen. Dieses Projekt ist ein wichtiger Schritt zum Verständnis von FPGA-Design und sequentiellen Schaltungen. Die Fähigkeiten, die du hier lernst – wie das Kombinieren vorgegebener Module und das Schreiben eigener Steuerlogik – sind in der Industrie gefragt, sei es für Embedded Systems, KI-Beschleuniger oder Gaming-Hardware.
Vergiss nicht, deine Ergebnisse auf dem FPGA zu demonstrieren und ein Video für den Bericht zu erstellen. Viel Erfolg im Lab 3!