Skip to content

VHDL · Chapter 20.4 · Capstone Projects

Capstone: Pipelined ALU

The fourth capstone moves from interfaces and storage to computation, in the form of a pipelined ALU. It selects an operation by opcode, covering add, subtract, bitwise AND, OR, and XOR, shift, and compare, using the standard numeric arithmetic and parameterized by a width generic. The key architectural element is the pipeline. The work is split into balanced stages, roughly decode, compute, and flags, with a valid bit and aligned side data flowing alongside, so once full it produces one result per cycle at a high clock. It generates the standard condition flags for zero, carry, overflow, and negative, which must be computed correctly for signed and unsigned values alike. Building it applies arithmetic synthesis, pipelining and timing-driven design, and datapath and control structure. Verification checks every opcode against a reference model, with corner values, back-to-back operations, and flag correctness.

Intermediate16 min readVHDLCapstoneALUPipelineArithmeticVerification

1. Engineering intuition — an opcode-selected datapath on an assembly line

An ALU is a bank of operations sharing inputs, with an opcode choosing which result to keep — conceptually a big multiplexer over an adder, a logic block, a shifter, and a comparator. The capstone twist is making it fast by pipelining: rather than computing the whole thing in one deep combinational path, you split it into stages on an assembly line, so a new operation enters every cycle while earlier ones finish — high throughput at a high clock, at the cost of a fixed latency. The discipline that makes a pipeline correct is carrying a valid bit and any side data (like the opcode or a tag) alongside each operation through the stages, so results stay matched to their requests. And the ALU must produce the right flags — zero, carry, overflow, negative — which is where signed-vs-unsigned and width growth bite. Picture an opcode-selected datapath stretched across pipeline registers, and it falls into place.

2. Formal explanation — the pipelined ALU architecture

alu_architecture.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
entity alu is
    generic ( WIDTH : positive := 32 );
    port ( clk, rst : in  std_logic;
           in_valid : in  std_logic;  out_valid : out std_logic;
           opcode   : in  std_logic_vector(3 downto 0);
           a, b     : in  std_logic_vector(WIDTH-1 downto 0);
           result   : out std_logic_vector(WIDTH-1 downto 0);
           zero, carry, overflow, negative : out std_logic );
end entity;
-- OPS (numeric_std): ADD, SUB, AND, OR, XOR, SHL, SHR, CMP (signed/unsigned per opcode).
-- PIPELINE (balanced stages), valid + side data aligned (18.1):
--   STAGE 1 decode  : register a, b, opcode, in_valid.
--   STAGE 2 compute : selected op on the registered operands (add uses WIDTH+1 for carry, 16.5).
--   STAGE 3 flags   : register result + derive zero/carry/overflow/negative; out_valid follows.
-- FLAGS:
--   zero     = (result = 0)
--   carry    = WIDTH+1th bit of an unsigned add/sub
--   overflow = signed overflow (operand signs vs result sign)
--   negative = result MSB (signed)

The ALU is a width-generic, opcode-selected set of numeric_std operations split across balanced pipeline stages with a valid bit and aligned side data, producing zero/carry/overflow/negative flags. Add/subtract use the extra carry bit (WIDTH+1) so the carry/overflow are correct (16.5).

3. Production usage — pipelined compute with aligned valid and flags

alu_pipeline.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
process (clk) begin
    if rising_edge(clk) then
        if rst = '1' then v1 <= '0'; v2 <= '0';
        else
            -- STAGE 1: decode/register operands + valid (align opcode as side data)
            a1 <= a; b1 <= b; op1 <= opcode; v1 <= in_valid;
 
            -- STAGE 2: compute the selected op (extra bit for add/sub carry)
            case op1 is
                when OP_ADD => sum2 <= std_logic_vector(resize(unsigned(a1),WIDTH+1) + resize(unsigned(b1),WIDTH+1));
                when OP_SUB => sum2 <= std_logic_vector(resize(unsigned(a1),WIDTH+1) - resize(unsigned(b1),WIDTH+1));
                when OP_AND => sum2 <= '0' & (a1 and b1);
                when OP_XOR => sum2 <= '0' & (a1 xor b1);
                -- ... OR, shifts, compare ...
                when others => sum2 <= (others => '0');
            end case;
            op2 <= op1; v2 <= v1;                          -- side data + valid advance with the data
 
            -- STAGE 3: register result + flags
            result   <= sum2(WIDTH-1 downto 0);
            carry    <= sum2(WIDTH);                        -- carry-out from the extra bit
            zero     <= '1' when sum2(WIDTH-1 downto 0) = (WIDTH-1 downto 0 => '0') else '0';
            negative <= sum2(WIDTH-1);
            out_valid <= v2;                                -- valid marks a real result (handles fill/flush)
        end if;
    end if;
end process;

What hardware does this become? A multi-stage datapath: a decode/register stage, a compute stage that is an opcode-selected mux over an adder (with the extra carry bit), logic, shifter, and comparator, and a flags stage — each separated by registers so the unit runs at a high clock and delivers one result per cycle once full. The valid bit and aligned opcode travel with each operation so results never desync from their requests (18.1), and out_valid is low during fill/flush so downstream logic ignores partial slots. The flags fall out of the extra-bit arithmetic: carry from bit WIDTH, negative from the MSB, zero from a result compare, and overflow from the signed sign relationship. Width-generic, it serves an 8-bit or 64-bit datapath from one source.

4. Structural interpretation — the pipelined ALU stages

pipelined ALU: decode stage, opcode-selected compute stage, and flags stage with valid and opcode alignedvalid+opcode alignedvalid alignedstage 1: decoderegister a, b, opcode,validstage 2: computeopcode-selected op (+carrybit)stage 3: flagszero/carry/overflow/negativeresult + out_validone per cycle once full12
A pipelined ALU splits opcode-selected computation across balanced register stages. The decode stage registers the operands, opcode, and valid bit; the compute stage performs the operation selected by the opcode — an adder (with an extra carry bit), logic, shifter, and comparator multiplexed by the opcode — on the registered operands; the flags stage registers the result and derives zero, carry, overflow, and negative. A valid bit and the opcode travel alongside the data through every stage, so results stay matched to their requests and out_valid marks real results through fill and flush. A width generic sizes the whole datapath. This is the integrated ALU structure; the waveform below shows pipelined results with the valid lag and the flags.

5. Simulation interpretation — pipelined results, valid lag, and flags

Pipelined ALU: results appear after the fill latency, one per cycle, with flags

8 cycles
Pipelined ALU: results appear after the fill latency, one per cycle, with flagsops enter each cycle; out_valid low during the pipeline fillops enter each cycle; …first result R0 emerges after the fill latency; out_valid assertsfirst result R0 emerge…one result per cycle thereafter, each with its own flags (carry, zero, ...)one result per cycle t…clkin_valid11100000opADDSUBXORXORXORXORXORXORout_valid00111000result00R0R1R2R2R2R2flags00Z=0C=1Z=1Z=1Z=1Z=1t0t1t2t3t4t5t6t7
Operations enter the pipeline one per cycle; after the fixed fill latency the first result emerges and then one result follows every cycle, each carrying its own flags. out_valid (the valid bit propagated with the data) is low during fill and flush so downstream logic only consumes real results. The flags — here a carry from an add and a zero from a subtract that cancels — are derived from the extra-bit arithmetic at the final stage.

6. Debugging example — wrong flags and a desynchronized pipeline

Expected: correct results with correct flags, each matched to its operation. Observed: the carry or overflow flag is wrong (especially at min/max values), and/or under back-to-back operations a result is paired with the wrong opcode's flags or arrives a cycle off. Root cause: the flags were computed without the extra carry bit (so carry/overflow were truncated away, 16.5) or with the wrong signed/unsigned interpretation; and/or the opcode/valid side data was not aligned through the pipeline, so the flags stage used a different operation's opcode than the result it was flagging. Fix: compute add/subtract in WIDTH+1 bits so carry (bit WIDTH) and overflow (signed sign relationship) are correct, choose signed vs unsigned per the opcode, and align the opcode and valid through every pipeline stage so each result is flagged by its own operation. Engineering takeaway: ALU correctness needs the extra carry bit for proper carry/overflow flags and aligned side data so flags and results stay matched to their opcode — truncated arithmetic or misaligned pipeline data produces wrong flags and mispaired results.

alu_carry_and_alignment.vhd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- BUG: WIDTH-bit add drops the carry; opcode not pipelined -> flags belong to a different op.
-- FIX: compute in WIDTH+1 bits (carry = bit WIDTH); advance opcode + valid through every stage.
-- sum2 <= resize(unsigned(a1),WIDTH+1) + resize(unsigned(b1),WIDTH+1);  carry <= sum2(WIDTH);
-- op2 <= op1;  v2 <= v1;   -- side data aligned with the data it describes

7. Common mistakes & what to watch for

  • Flags without the extra bit. Compute add/subtract in WIDTH+1 bits so carry and overflow are correct; a same-width add truncates them (16.5).
  • Wrong signed/unsigned per opcode. Overflow and compare differ for signed vs unsigned; interpret operands by the opcode.
  • Unaligned side data. Advance the opcode and valid through every pipeline stage so each result is flagged by its own operation (18.1).
  • Forgetting the valid bit. Propagate out_valid so fill/flush slots are not mistaken for results.
  • Verifying only typical values. Check every opcode against a reference model at corner values (min/max/overflow/carry) and back-to-back, not just a few easy cases.

8. Engineering insight & continuity

The ALU capstone builds verified computation: a width-generic, opcode-selected numeric_std datapath split into balanced pipeline stages with aligned valid/side data, producing correct zero/carry/overflow/ negative flags via extra-bit arithmetic — proven per-opcode against a reference model across corner values and back-to-back operations. It is the model for any pipelined arithmetic block. The final two capstones integrate toward a system: the next lesson, Capstone: Memory Controller, builds a block that sequences external memory with timing and state, before the closing SoC-integration capstone.