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 |
|
2 | Multiply |
|
3 | Add |
|
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.
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.
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.
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.
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