Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations57
  • Combinational Design58
  • Sequential Design61
    • Flip-Flops and Registers
    • Counters and Timers
    • Shift Registers and LFSRs
    • Pipelining
      • Throughput and Latency
      • A Three-Stage Arithmetic Pipeline
      • Bubble insertion
      • Valid/Ready Flow Control
      • Pipelined MAC
      • Key takeaways
      • Precision Verification
      • Common pitfalls
      • Worked Examples and Waveforms
      • Corner Cases and Stress Tests
      • Integration and Sign-off Notes
    • Stalls, Bubbles and Hazards
    • Retiming
  • FSM & Control66
  • Timing & Clocking64
  • Reset & CDC58
  • Memory & Buses94
  • Advanced RTL60
  • Senior & Interview175
  • Glossary and Notation
Loading lesson

‹Pipelining
  1. Topics
  2. RTL Design
  3. Sequential Design
  4. Pipelining
  5. A Three-Stage Arithmetic Pipeline

Part 3 · Sequential · Intermediate

A Three-Stage Arithmetic Pipeline

A three-stage multiply-add pipeline with valid tracking and a randomized reference check.

On this page

    Why this matters

    Register the operand c in stage 1 but use it directly in stage 3 and you add a different item's c to the product. Every operand needed later must travel with its data.

    After this lesson you can: split y = a*b + c into three stages, carry a valid bit and the late operand along, and predict when each result is consumed.

    Splitting y = a·b + c

    The multiply is the slow operation, so it gets its own stage. A register rank follows each of the three stages.

    Stage

    Work

    Registers written

    1

    Capture the operands

    a1, b1, c1, v1

    2

    Multiply

    p2 = a1 * b1, c2 = c1, v2

    3

    Add

    y = p2 + c2, out_v

    The three-stage multiply-add pipeline: rank 1 captures a, b and c, rank 2 holds the product p2 and the delayed c2, rank 3 holds y; the valid flops v1, v2 and out_v run in a lane of their own underneath

    Read each column as one stage: logic on the left of a register rank, the rank itself on the right. The bottom lane is the valid bit for whatever sits in the column above it.

    • c rides along: c1 and c2 do no work. They exist only so c reaches the adder in the same cycle as its own product.

    • One valid flop per rank: v1, v2 and out_v line up with ranks 1, 2 and 3, so out_v is high exactly when y holds a real result.

    • Latency 3: an item needs three edges to reach y, and a new item can enter at every edge behind it.

    The operand c is unused until stage 3, so registers carry it alongside its product. A valid bit travels with the data and marks the cycles holding real results.

    Use edge 0 as the first acceptance. Nonblocking assignments capture operands after edge 0, update product and delayed c after edge 1, and update y and out_v after edge 2. The first result is then available during the interval before edge 3; a downstream register consumes it at edge 3. The waveform and scoreboard use this pre-edge consumption convention, not post-update availability.

    • Cycle 0: inputs a = 2, b = 3, c = 1 enter.

    • Cycle 1: the second operation (4, 5, 2) enters.

    • Cycle 2: in_v is low, a gap cycle; held values are ignored.

    • Cycle 3 (pre-edge): the third operation (7, 2, 3) enters; the first result 7 is consumed with out_v already high.

    • Cycle 4 (pre-edge): the second result 0x16 (22) is consumed.

    • Cycle 5: a gap cycle; out_v is low.

    • Cycle 6 (pre-edge): the third result 0x11 (17) is consumed.

    • Cycles 7–9: idle; out_v stays low. Unconditionally written y may change and is ignored.

    Tip

    Reset only the valid bits. The data registers are always overwritten before they are used, and consumers must ignore y while out_v is low, so leaving data unreset is safe here. A data reset can add area and slow timing on many targets, though the exact cost depends on the device and reset network. It also leaves them free to be retimed later.

    Balancing the stages

    • Give the slow multiply its own stage; the add then has slack.

    • Split a slow multiplier further: partial products first, then compression and the final add (see the Wallace tree idea).

    • Trust the timing report for the critical stage, not intuition.

    Common mistakes

    Common pitfall

    If c were registered in stage 1 but used directly in stage 3 you would add a different item's c to the product. Every operand needed later must be delayed by the same number of stages as the data it belongs with. Mis-aligned pipelines are the most common pipelining bug.

    Check yourself In the waveform, why does y show x in the first cycles even though nothing is wrong?

    The datapath registers are not reset, so before the first valid data passes through them they hold unknown values. out_v is 0 then, which is what tells the consumer to ignore y.

    Testbench

    The waveform's three offers and gap are driven first, followed by randomized traffic checked against a queue-based reference. Each accepted input records its rising-edge cycle; the corresponding result must be consumed exactly three cycles later. The clock is 10 ns; monitors sample before NBA updates, and falling-edge stimulus avoids a sampling race.

    mac_pipe.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    module mac_pipe (
      input  logic        clk, rst_n,
      input  logic        in_v,
      input  logic [3:0]  a, b, c,
      output logic        out_v,
      output logic [8:0]  y
    );
      // valid bits are reset; datapath registers are not (they are always written before use)
      logic        v1, v2;
      logic [3:0]  a1, b1, c1, c2;
      logic [7:0]  p2;
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin v1 <= 1'b0; v2 <= 1'b0; out_v <= 1'b0; end
        else        begin v1 <= in_v; v2 <= v1;   out_v <= v2;   end
    
      always_ff @(posedge clk) begin
        a1 <= a;  b1 <= b;  c1 <= c;            // stage 1: capture
        p2 <= a1 * b1;  c2 <= c1;                // stage 2: multiply, carry c along
        y  <= p2 + {4'b0, c2};                   // stage 3: add
      end
    endmodule
    

    Remember

    • Give the slow multiply its own stage; the add then has slack.

    • Delay each late operand by the same number of stages as the data it belongs with.

    • Reset only the valid bits; consumers ignore y while out_v is low.

    Keep going

    • Practice: Design a 3-stage pipelined ALU with stall support

    • Code lab: Pipelined multiplier stage

    • Interview: run a mock interview on this topic

    Related topics

    • Valid/ready flow control

    Previous

    147 / 694

    Next: Bubble insertion

    Up next

    Up nextBubble insertion

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Valid/Ready Flow Control
    • Pipelined MAC
    • Key takeaways
    Pre-edge samples: y = a*b + c, consumption latency 3 cycles; hexclkin_va247b352c123out_vy (hex)x71611
    Each column is sampled immediately before its rising edge. Inputs are accepted at edges 0, 1 and 3; downstream consumes results (7, 0x16 = 22, 0x11 = 17) at edges 3, 4 and 6. Register updates occur after the edge, between these observations.