Skip to content

rtl/alu.sv — Integer ALU (RV32IM)

Combinational base ALU (ADD/SUB/logic/shifts/compare) plus two multi-cycle units: a 3-cycle MUL/MULH and a 34-cycle DIV/DIVU/REM/REMU, both modeled as stall-counter state machines rather than pipelined datapaths.

Where it fits

Instantiated once by riscv_core.sv as alu_unit, driven from the EX stage.

Ports

Direction Name Width Description
in clk, rst_n 1, 1 Clock / reset
in a, b 32, 32 Operands
in alu_control 4 Operation select (see table)
in math_start 1 Fires once to kick off a MUL/MULH/DIV/DIVU/REM/REMU
out math_busy 1 High for the op's full latency
out result 32 Combinational result, or the registered MUL/DIV result once ready
out zero 1 result == 0

Functionality — operation table

alu_control[3:0] Name Operation Latency
4'd0 ALU_ADD a + b 0 (comb)
4'd1 ALU_SUB a - b 0
4'd2 ALU_AND a & b 0
4'd3 ALU_OR a \| b 0
4'd4 ALU_XOR a ^ b 0
4'd5 ALU_SLT signed a < b → 1/0 0
4'd6 ALU_SLTU unsigned a < b → 1/0 0
4'd7 ALU_SLL a << b[4:0] 0
4'd8 ALU_SRL a >> b[4:0] (logical) 0
4'd9 ALU_SRA a >>> b[4:0] (arithmetic) 0
4'd10 ALU_MUL a × b lower 32 bits 3 cycles
4'd11 ALU_MULH a × b upper 32 bits (signed) 3 cycles
4'd12 ALU_DIV signed quotient 34 cycles
4'd13 ALU_DIVU unsigned quotient 34 cycles
4'd14 ALU_REM signed remainder 34 cycles
4'd15 ALU_REMU unsigned remainder 34 cycles

Critical timing rule: math_busy de-asserts on the same cycle the result becomes valid on result. The core samples result when math_busy==0 and the previous cycle it was 1 (math_done in riscv_core.sv).

Divider edge cases: division by zero → quotient 0xFFFFFFFF (-1), remainder = dividend. Signed overflow (INT_MIN / -1) → quotient INT_MIN, remainder 0. Uses Verilog's built-in //% operators for correctness (this module targets simulation, not synthesis — the counter provides the latency model, not a real iterative datapath).

Example — DIV latency

cycle 0:  math_start=1, alu_control=ALU_DIV -> div_a_reg/div_b_reg latch, div_cnt=34
cycles 1-32: div_cnt counts down, math_busy=1 (pipeline frozen upstream)
cycle 33: div_cnt==1 -> div_result_reg = $signed(a) / $signed(b), div_result_valid=1
cycle 34: math_busy falls -> riscv_core samples `result` this cycle

Total observed latency from math_start to usable result: 34 cycles, matching docs/cpu.md's hazard table and the HALT_DRAIN_CYCLES=40 margin used in tb/tb_server_dispatch_unit.py to let a trailing DIV/REM fully retire before checking registers.