This practice project contains alu32.sv (the hardware) and tb_alu32.sv (the testbench). Start with the hardware file, then read the testbench. No FPGA board is needed for simulation.
A bit holds 0 or 1. A vector groups bits together. logic [31:0] a declares 32 bits, numbered 31 down to 0; bit 31 is the most significant bit. With unsigned interpretation, these bits represent values from 0 through 4,294,967,295.
module alu32 (...) defines the hardware block and its connections, called ports. input signals enter the module and output signals leave it. The ALU takes a, b, and op and produces result and zero.
SystemVerilog literals give the width, base, and value:
32'd5 // 32-bit decimal 5
3'b000 // 3-bit binary 000: our ADD code
32'hFFFF_FFFF // 32-bit hexadecimal: all 32 bits are 1Each hexadecimal digit represents four bits. Underscores improve readability without changing the value. logic supports 0, 1, X (unknown), and Z (high impedance) in simulation; it does not by itself mean a register or stored memory.
Three selector bits encode eight choices because 2^3 = 8. The localparam declarations name those codes; they are constants, not input signals.
- 000: ADD
- 001: SUB
- 010: AND
- 011: OR
- 100: XOR
- 101: SLL (shift left logical)
- 110: SRL (shift right logical)
- 111: SLTU (set less than unsigned)
case (op) selects the expression used for the result. Think of the operation selector as controlling a multiplexer: hardware that chooses which candidate value reaches an output. Synthesis decides the actual gates and may share circuitry.
always_comb describes outputs calculated from current inputs. The simulator evaluates it initially and when its relevant inputs change. There is no clock in this ALU. Physical hardware still has propagation delay; combinational does not mean instantaneous.
Inside the block, blocking assignments (=) update values in statement order. We assign result first, then calculate zero from that result. Every case branch, including default, assigns result, and zero is always assigned. Leaving an output unassigned on a path can infer a latch that remembers an old value.
All eight binary operation codes are valid. A selector containing X or Z matches the default branch and produces zero. This fallback does not validate the operands or guarantee known outputs when operands contain unknown bits.
For op = 3'b000, a = 32'd3, and b = 32'd5, the result is 32'd8, with zero = 0.
The result has only 32 bits, so arithmetic wraps modulo 2^32. 32'hFFFF_FFFF + 32'd1 produces zero. 32'd0 - 32'd1 produces 32'hFFFF_FFFF. In this version that output is interpreted as the unsigned number 4,294,967,295; signed two's-complement interpretation would call the same bit pattern -1. We have no carry or overflow output.
AND (&), OR (|), and XOR (^) act on corresponding pairs of bits. Using four-bit examples for readability:
a 0101
b 0011
AND 0001 Both bits must be 1.
OR 0111 At least one bit must be 1.
XOR 0110 The bits must differ.
The ALU does this across all 32 bits. Do not substitute && for &: && evaluates the operands as true or false and produces a single logical answer, rather than 32 independent bitwise answers.
a << b[4:0] shifts left and a >> b[4:0] shifts right. Vacated positions receive zeros; bits shifted out are discarded. The >> operator performs a logical right shift and inserts zeros, even with a signed left operand. An arithmetic right shift uses >>> with a signed left operand.
b[4:0] selects the lowest five bits, which encode 0 through 31. For example, shifting 1 left by 4 gives 16. Shifting 32'h8000_0000 right by 4 gives 32'h0800_0000.
If b is 32, its lowest five bits are zero, so our ALU shifts by zero. This behavior comes from our chosen interface, not a universal rule for all shift operators.
(a < b) ? 32'd1 : 32'd0 means: if the comparison is true, output 1; otherwise output 0. Neither operand is declared signed, so the comparison is unsigned. 32'h8000_0000 is therefore larger than 32'h7FFF_FFFF.
zero = (result == 32'd0) is a comparison, not an assignment to result. It sets the flag to 1 whenever the selected result is zero. A false less-than comparison produces result 0 and therefore zero 1; the flag always describes the result, not the truth of an operation.
alu32 dut (...) instantiates our real module. Named connections such as .a(a) connect the module's port to a testbench signal. initial runs the stimulus sequence once. A helper task avoids repeating the drive/wait/check steps for all 30 test cases.
Each check_case supplies an operation, two operands, and a literal expected answer. It waits #1, which is 1 ns because of timeunit 1ns, before checking the outputs. This lets combinational simulation events settle; it is not a claim about the circuit's physical delay.
!== detects differences including X and Z, preventing unknown results from slipping through. Failures are counted, and $fatal(1, ...) ends simulation as a failure. The final pass message is printed only when all checks succeed. The waveform dump records signal values over simulated time in a VCD file.
The testbench is a simulation driver, not hardware to synthesize. Delays and display statements belong here, not in our ALU.
Actual status: Both sources were written and reviewed, but they have not been compiled or simulated. No common HDL simulator was found on PATH in this workspace. The 30 cases are prepared tests, not 30 verified passes.
With Icarus Verilog and its vvp runtime available, open PowerShell in the repository root, create the simulation output directory, and compile:
New-Item -ItemType Directory -Path work -Force | Out-Null
iverilog -g2012 -s tb_alu32 -o work/alu32.vvp alu32.sv tb_alu32.svOnly if compilation succeeds, run:
vvp work/alu32.vvp-g2012 selects SystemVerilog 2012 language support; -s tb_alu32 selects the testbench as the simulation root. See the official Icarus command-line flags and getting-started guide for the compiler/runtime workflow.
A successful run ends with PASS: all 30 checks passed. That is the expected message, not a result observed here. The waveform is written to work/alu32.vcd.
If using your class's simulator instead, add both .sv sources, enable SystemVerilog, select tb_alu32 as the top module, and run to completion. For a browser-based simulator, copy the ALU into its design pane and the testbench into its testbench pane. Change $dumpfile("work/alu32.vcd") to $dumpfile("alu32.vcd") if it has no work/ directory. Use a four-state simulator for the unknown-selector test; a two-state simulator cannot check X/Z behavior faithfully.
Before reading the answers, predict both result and zero:
- SUB with a = 8 and b = 8.
- XOR with a = 5 and b = 3.
- SLL with a = 1 and b = 32.
- SLTU with a =
32'hFFFF_FFFFand b = 1.
Answers: (1) result 0, zero 1; (2) result 6, zero 0; (3) result 1, zero 0; (4) result 0, zero 1.
Once you can trace an input through the selected branch and predict both outputs, you understand this first ALU. Later extensions can add signed operations, carry and overflow flags, structural adders, and a clocked wrapper.