Programming lesson
ALU und Steuerwerk in Verilog: Ein praxisnaher Leitfaden für CDA 4205L Lab #7
Erfahren Sie Schritt für Schritt, wie Sie eine ALU und ein Steuerwerk (Control Unit) in Verilog für das Lab #7 von CDA 4205L entwerfen. Inklusive Testbench und typischen Fehlerquellen.
Einführung in ALU und Steuerwerk
Die Arithmetic Logic Unit (ALU) und das Steuerwerk (Control Unit) sind das Herz jeder CPU. In diesem Tutorial lernen Sie, wie Sie beide Komponenten in Verilog für das CDA 4205L Lab #7 modellieren. Wir verwenden dabei den RV32IM-Befehlssatz und simulieren mit Icarus Verilog auf EDA Playground. Aktuell (Juni 2026) sind solche Grundlagen auch für das Verständnis moderner KI-Beschleuniger und RISC-V-Prozessoren in Smartphones relevant.
Grundlagen der ALU
Die ALU führt arithmetische und logische Operationen auf zwei 32-Bit-Operanden (A und B) aus. Die Auswahl der Operation erfolgt über einen 4-Bit-Steuereingang. Typische Operationen sind Addition, Subtraktion, Multiplikation, Division, Bit-Shifts und Vergleiche. In Verilog realisieren wir dies mit einer case-Anweisung innerhalb eines always-Blocks.
Moduldefinition und Timescale
`timescale 1ns/1ns
module alu (
input signed [31:0] A, B,
input [3:0] operation,
output reg signed [31:0] ALUResult
);
Die timescale-Direktive definiert die Zeiteinheit für die Simulation. Alle Eingänge und Ausgänge sind als signed deklariert, um korrekte vorzeichenbehaftete Arithmetik zu gewährleisten.
Implementierung der ALU-Operationen
always @(*) begin
case (operation)
4'b0010: ALUResult = A + B; // add
4'b0110: ALUResult = A - B; // sub
4'b0111: ALUResult = A * B; // mul
4'b1111: ALUResult = A / B; // div
4'b0100: ALUResult = A << B; // sll
4'b0101: ALUResult = A >> B; // srl
4'b1110: ALUResult = A >>> B;// sra
4'b0000: ALUResult = A & B; // and
4'b0001: ALUResult = A | B; // or
4'b0011: ALUResult = A ^ B; // xor
4'b1000: ALUResult = (A == B) ? 32'd1 : 32'd0; // eq
4'b1001: ALUResult = (A != B) ? 32'd1 : 32'd0; // neq
4'b1010: ALUResult = (A < B) ? 32'd1 : 32'd0; // lt
4'b1011: ALUResult = (A >= B) ? 32'd1 : 32'd0; // geq
default: ALUResult = 0;
endcase
end
Beachten Sie, dass Division durch Null vermieden werden sollte. In der Praxis würde man eine Fehlerbehandlung einbauen, aber hier setzen wir einfach das Ergebnis auf 0.
Testbench für die ALU
Eine Testbench ist ein eigenes Modul ohne Ein-/Ausgänge, das die ALU mit Testdaten versorgt und die Ergebnisse überwacht. Wir deklarieren reg für Eingänge und wire für Ausgänge.
module alu_testbench;
reg signed [31:0] A, B;
reg [3:0] operation;
wire signed [31:0] ALUResult;
alu uut (
.A(A),
.B(B),
.operation(operation),
.ALUResult(ALUResult)
);
initial begin
$monitor($time, " A=%d, B=%d, op=%b, result=%d", A, B, operation, ALUResult);
A = 32'd10; B = 32'd3;
#5 operation = 4'b0010; // add
#5 operation = 4'b0110; // sub
#5 operation = 4'b0111; // mul
#5 operation = 4'b1111; // div
#5 operation = 4'b0100; // sll
#5 operation = 4'b0101; // srl
#5 operation = 4'b1110; // sra
#5 operation = 4'b0000; // and
#5 operation = 4'b0001; // or
#5 operation = 4'b0011; // xor
#5 operation = 4'b1000; // eq
#5 operation = 4'b1001; // neq
#5 operation = 4'b1010; // lt
#5 operation = 4'b1011; // geq
#10 $finish;
end
endmodule
Mit $monitor werden alle Änderungen automatisch ausgegeben. Die Verzögerungen (#5) simulieren Taktzyklen. Nach dem Test sollten Sie auch negative Zahlen testen, um die signed-Arithmetik zu prüfen.
Steuerwerk (Control Unit) in Verilog
Das Steuerwerk decodiert den Opcode der RISC-V-Instruktion und setzt die Steuersignale. Für dieses Lab gibt es 10 Steuerleitungen: ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, Jump, Jalr und ALUOp (2 Bit).
Moduldefinition
module control_unit (
input [6:0] opcode,
output reg ALUSrc,
output reg MemToReg,
output reg RegWrite,
output reg MemRead,
output reg MemWrite,
output reg Branch,
output reg Jump,
output reg Jalr,
output reg [1:0] ALUOp
);
Decodierung der Opcodes
Wir unterscheiden die Hauptinstruktionsklassen von RV32I: R-Typ, I-Typ (lw, addi, etc.), S-Typ (sw), B-Typ (beq, bne), J-Typ (jal) und I-Typ für jalr. Die Zuordnung der ALUOp-Bits erfolgt gemäß der Lab-Vorgabe.
always @(*) begin
// Standardwerte (alle 0)
{ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, Jump, Jalr} = 0;
ALUOp = 0;
case (opcode)
7'b0110011: begin // R-Typ (add, sub, etc.)
RegWrite = 1;
ALUOp = 2'b10;
end
7'b0000011: begin // I-Typ lw
ALUSrc = 1;
MemToReg = 1;
RegWrite = 1;
MemRead = 1;
ALUOp = 2'b00;
end
7'b0010011: begin // I-Typ addi, etc.
ALUSrc = 1;
RegWrite = 1;
ALUOp = 2'b10;
end
7'b0100011: begin // S-Typ sw
ALUSrc = 1;
MemWrite = 1;
ALUOp = 2'b00;
end
7'b1100011: begin // B-Typ beq, bne
Branch = 1;
ALUOp = 2'b01;
end
7'b1101111: begin // J-Typ jal
Jump = 1;
RegWrite = 1;
ALUOp = 2'b00;
end
7'b1100111: begin // I-Typ jalr
Jalr = 1;
RegWrite = 1;
ALUOp = 2'b00;
end
default: begin
// alle Signale bleiben 0
end
endcase
end
Testbench für das Steuerwerk
Ähnlich wie bei der ALU testen wir alle relevanten Opcodes.
module control_unit_testbench;
reg [6:0] opcode;
wire ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, Jump, Jalr;
wire [1:0] ALUOp;
control_unit uut (
.opcode(opcode),
.ALUSrc(ALUSrc),
.MemToReg(MemToReg),
.RegWrite(RegWrite),
.MemRead(MemRead),
.MemWrite(MemWrite),
.Branch(Branch),
.Jump(Jump),
.Jalr(Jalr),
.ALUOp(ALUOp)
);
initial begin
$monitor($time, " opcode=%b, ALUSrc=%b, MemToReg=%b, RegWrite=%b, MemRead=%b, MemWrite=%b, Branch=%b, Jump=%b, Jalr=%b, ALUOp=%b",
opcode, ALUSrc, MemToReg, RegWrite, MemRead, MemWrite, Branch, Jump, Jalr, ALUOp);
#5 opcode = 7'b0110011; // R-Typ
#5 opcode = 7'b0000011; // lw
#5 opcode = 7'b0010011; // addi
#5 opcode = 7'b0100011; // sw
#5 opcode = 7'b1100011; // beq
#5 opcode = 7'b1101111; // jal
#5 opcode = 7'b1100111; // jalr
#10 $finish;
end
endmodule
Häufige Fehler und Tipps
- Vergessen von `timescale: Ohne diese Direktive funktionieren Verzögerungen nicht korrekt.
- Falsche Port-Reihenfolge: Bei der Instanziierung muss die Reihenfolge der Ports mit dem Modul übereinstimmen oder man verwendet named ports (wie im Beispiel).
- Vergessen von signed: Ohne
signedwerden Vergleiche wieA < Bunsigned interpretiert. - Division durch Null: Vermeiden Sie dies oder behandeln Sie es im default-Zweig.
- Testbench ohne $monitor: Ohne diese Anweisung sehen Sie keine Ausgaben.
Zusammenfassung
Sie haben nun eine voll funktionsfähige ALU und ein Steuerwerk in Verilog entworfen und getestet. Diese Komponenten sind essenziell für das Verständnis von Prozessoren und bilden die Grundlage für komplexere Designs wie Pipelining oder Multicore-Systeme. Mit den heutigen Trends im Bereich RISC-V und Open-Source-Hardware sind diese Kenntnisse besonders wertvoll. Viel Erfolg bei Ihrem Lab #7!