FPGA

Verilog 4 Bit ALU Design

By CourseTron Team ·  6 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: A 4-bit ALU is one adder/subtractor plus a bank of bitwise gates, selected by a 3-bit opcode, with Zero, Negative, Carry and Overflow flags derived from the result; written as a purely combinational Verilog block, it synthesizes to roughly a dozen LUTs on an FPGA.

    What the ALU actually has to do

    The arithmetic logic unit is the part of a datapath that turns two operands and a control code into a result and a handful of status bits. A 4-bit version is small enough to verify exhaustively (only 28 = 256 operand pairs per opcode) yet contains every idea that scales to 32 or 64 bits: operation select, carry propagation, signed versus unsigned interpretation and flag generation. If you later want the wider version, see our structural modeling in Verilog guide and the 8-bit ALU walkthrough.

    We will use a 3-bit opcode, which gives eight operations. Choosing the encoding is a real design decision: grouping all subtract-style operations under one bit lets a single control signal drive the adder's B-input inversion and carry-in, saving gates.

    OpcodeOperationResult
    000ADDA + B
    001SUBA + ~B + 1
    010ANDA & B
    011ORA | B
    100XORA ^ B
    101NOT A~A
    110SHLA << 1
    111SHRA >> 1
    4-bit ALU: operation units feed a result multiplexer A[3:0] B[3:0] Adder / Subtractor AND / OR / XOR / NOT Shifter MUX select = op[2:0] Y[3:0] Flags: Z N C V CourseTron · coursetron.com
    All operation units compute in parallel; the opcode only chooses which result reaches Y.

    The Verilog: one combinational block

    An ALU has no memory, so model it combinationally. Use always @(*), assign every output on every path, and include a default case. Skipping either creates an inferred latch, which timing tools such as Synopsys Design Compiler or Vivado will flag. The reason inferred latches appear is explained in our blocking vs non-blocking article: combinational logic uses blocking assignments.

    The key trick is to widen the arithmetic to 5 bits so the carry falls out naturally:

    module alu4 (
      input  [3:0] a, b,
      input  [2:0] op,
      output reg [3:0] y,
      output reg z, n, c, v
    );
      reg [4:0] t;
      always @(*) begin
        t = 5'd0; c = 1'b0; v = 1'b0;
        case (op)
          3'b000: begin t = {1'b0,a} + {1'b0,b};
                  c = t[4];
                  v = (a[3]==b[3]) && (t[3]!=a[3]); end
          3'b001: begin t = {1'b0,a} + {1'b0,~b} + 5'd1;
                  c = t[4];
                  v = (a[3]!=b[3]) && (t[3]!=a[3]); end
          3'b010: t = {1'b0, a & b};
          3'b011: t = {1'b0, a | b};
          3'b100: t = {1'b0, a ^ b};
          3'b101: t = {1'b0, ~a};
          3'b110: begin t = {a, 1'b0}; c = a[3]; end
          3'b111: begin t = {1'b0, 1'b0, a[3:1]}; c = a[0]; end
          default: t = 5'd0;
        endcase
        y = t[3:0];
        z = (y == 4'd0);
        n = y[3];
      end
    endmodule

    Key fact: for subtraction the adder's carry-out means "no borrow". A - B with A ≥ B (unsigned) produces C = 1, the opposite of what many beginners expect.

    Flags: what each one really tells you

    • Z (zero): a 4-input NOR of the result. It costs one LUT and drives every equality compare and conditional branch.
    • N (negative): simply Y[3], meaningful only if you treat values as two's complement (range -8 to +7).
    • C (carry): the unsigned overflow indicator. 9 + 8 = 17 needs 5 bits, so C = 1.
    • V (signed overflow): set when two operands of the same sign give a result of the opposite sign, or equivalently when carry into bit 3 differs from carry out of bit 3. 5 + 4 = 9 does not fit in +7, so 0101 + 0100 = 1001 sets V = 1 and N = 1 even though C = 0.

    C and V answer different questions. The same bit pattern is simultaneously an unsigned and a signed number, and the hardware cannot know which you meant, so it reports both. If you are unsure how widening affects signed values, read sign extension in Verilog.

    Signed overflow: 5 + 4 in 4-bit two's complement 0 1 0 1 (A = +5) + 0 1 0 0 (B = +4) 1 0 0 1 (Y = -7?) Carry into bit 3 = 1 Carry out of bit 3 = 0 V = 1 xor 0 = 1 (overflow) Flags: Z=0 N=1 C=0 V=1 Unsigned reading is correct (9), signed reading is wrong. CourseTron · coursetron.com
    The carry into and out of the sign bit disagree, so V flags that the signed result is invalid.

    Watching the carry ripple

    The + operator hides a chain of four full adders. Each stage computes sum = a ^ b ^ cin and cout = ab + cin(a ^ b). The carry must pass through every stage in series, which is why delay grows linearly with width. Synthesis tools usually recognise the pattern and map it to dedicated carry logic instead of generic LUTs.

    Ripple carry: the carry hops one stage at a time FA bit 0 FA bit 1 FA bit 2 FA bit 3 Cout Cin Red dot = carry bit. Total delay = 4 x per-stage delay. CourseTron · coursetron.com
    Animated: a carry entering bit 0 must wait for each stage before reaching Cout.

    Synthesis considerations

    • Latch avoidance: default assignments at the top of the block, as above, guarantee full coverage.
    • Resource sharing: ADD and SUB must share one adder. Writing two separate + expressions in different case arms may give two adders in a naive flow; computing a + (op[0] ? ~b : b) + op[0] once forces sharing. Design Compiler and Genus often share automatically, but do not rely on it.
    • Mapping: on a Xilinx 7-series FPGA with 6-input LUTs, each output bit depends on roughly 3 + 3 + 2 inputs, so Y[3:0] fits about 4 to 8 LUTs plus carry-chain primitives. Expect well under 20 LUTs in total.
    • Timing: the critical path is operand input, through the carry chain, into the flag logic (V depends on the last carry). Registering inputs and outputs gives a clean synchronous interface; the stages are covered in FPGA design flow stages.
    • Simulation: include `timescale 1ns/1ps; the rules are in timescale directive in Verilog.

    Key fact: in ASIC flows, tools like Synopsys DesignWare or Cadence Genus can replace ripple carry with carry-lookahead when timing demands it, trading area for speed. At 4 bits the difference is negligible; at 64 bits it is decisive.

    Verification that catches real bugs

    Because the input space is only 256 pairs per opcode, write a self-checking testbench that loops over every a, b and op, computes a reference in behavioural code using integers, and compares Y and all four flags. That is 2048 vectors and runs in milliseconds in Questa, VCS or Xcelium. Pay particular attention to corner cases: 0 - 1 (C = 0, N = 1), 7 + 1 (V = 1), 8 - 1 (V = 1 for -8 - 1) and 15 + 1 (C = 1, Z = 1). Engineers at Avecas design services use the same exhaustive approach for small datapath blocks before moving to constrained-random verification for wide ones. For structured training, ChipXpert runs hands-on RTL and verification batches.

    A common interview follow-up: how would you add a flag-preserving compare? Answer: run opcode SUB, keep the flags, discard Y. No new hardware is needed.

    Ready to build, simulate and synthesize your own datapaths? Explore the hands-on FPGA and Verilog tracks in our CourseTron courses.

    FAQ

    Why use a 5-bit temporary for a 4-bit adder?

    The extra top bit captures the carry-out without extra logic, so the carry flag is just t[4].

    What is the difference between the carry and overflow flags?

    Carry signals unsigned overflow; overflow signals signed two's-complement overflow. They are computed differently and can be set independently.

    Should an ALU be sequential or combinational?

    Combinational. Registers belong around it in the datapath, which also makes timing closure easier.

    How many LUTs will a 4-bit ALU use?

    Typically under 20 LUTs on a 6-input LUT FPGA, depending on the operation set and flag logic.

    Advertisement