Skip to content

About

A beginner-friendly 32-bit ALU with eight operations, a zero flag, and a self-checking testbench.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

32-bit SystemVerilog ALU

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.

1. Bits, widths, and ports

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 1

Each 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.

2. Choose the operation

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.

3. Combinational logic

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.

4. Addition and subtraction

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.

5. Bitwise operations

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.

6. Shifts

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.

7. Comparison and the zero flag

(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.

8. Read the testbench

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.

9. Run the simulation

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.sv

Only 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.

10. Try predicting these results

Before reading the answers, predict both result and zero:

  1. SUB with a = 8 and b = 8.
  2. XOR with a = 5 and b = 3.
  3. SLL with a = 1 and b = 32.
  4. SLTU with a = 32'hFFFF_FFFF and 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.

About

A beginner-friendly 32-bit ALU with eight operations, a zero flag, and a self-checking testbench.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages