Where the register goes: retiming by hand
Move a pipeline register across logic without changing what the circuit computes, find the cut that balances the stages, and see why a feedback loop cannot be cut.
- Lesson
- R4.6
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A datapath needed a second register stage, and its designer put it at the output, where it was easy. The levels of logic stayed at 29 on this flow: the new stage held only wires. Moving the register back into the datapath brought the count to 21. The register count went up, and the results stayed the same.
| Where the second register sits | Flops | Cells | Levels of logic on this flow | Longest path runs |
|---|---|---|---|---|
| A: at the output | 26 | 199 | 29 | from a[1] to r_q[12] |
| B: after a + b | 30 | 202 | 23 | from s_q[2] to y[12] |
| C: after the multiply by 11 | 35 | 215 | 26 | from a[1] to m_q[11] |
| D: inside the multiply (3s, then 8s + 3s + c) | 40 | 208 | 21 | from t_q[1] to y[12] |
Measured with Yosys 0.70. Every version computes y = (a + b) * 11 + c with latency 2.
Moving a register across logic
Retiming moves registers through combinational logic without changing what the circuit computes. Move a register backward across an operator, and it becomes one register on each input of that operator. Move registers on every input of an operator forward, and they merge into one register on its output.
The test is simple: every path from an input to the output must cross the same number of registers as before. In the chain, a, b and c each pass through two registers. When the cut moves back across the final add, c needs its own register, c_q. Measured: leave c_q out of cut B and the bench finds no latency that matches.
- A: at the output, 29
All three operators sit in stage 1 and stage 2 is a copy. The longest path runs from
a[1]through every operator: 29 levels. - B: after a + b, 23
Stage 1 holds the first add, 15 levels alone. Stage 2 holds the multiply and the second add, 23 alone. c gets a register so its path still crosses two.
- C: after the multiply, 26
Now stage 1 is heavy: the add and the multiply, 26 levels alone, against 20 for stage 2. Worse than B.
- D: inside the multiply, 21
s * 11 is 8s + 3s. Stage 1 computes s and 3s; stage 2 adds 8s, 3s and c. The two stages are 18 and 20 levels alone, 21 together.
Read the diagram as text
- a, b: 8 bits each
- c: 8 bits
- s = a + b: 15 levels alone
- m = s * 11: 19 levels alone
- r = m + c: 20 levels alone
- y: the output register
- a, b → s = a + b: a, b
- s = a + b → m = s * 11: s
- m = s * 11 → r = m + c: m
- c → r = m + c: c
- r = m + c → y: r
Each operator alone: 15, 19 and 20 levels, 54 in sum; the whole chain measures 29. Levels of a chain overlap, so measure each cut.
The best cut balances the two stages. It rarely falls between two operators, because operators differ in depth. Here it sits inside the multiply, where the constant 11 splits into 8 and 3. Cut D costs 14 flops more than cut A and takes 8 levels off.
A loop cannot be cut
An accumulator adds each input into its own register, so the adder and the register form a loop. A register before the adder is legal: the input path crosses one more register, so latency grows by one. A register inside the loop changes the function. The sum then takes two edges to come round, so the circuit keeps two sums that take turns.
A register before the loop
x_q <= x;
acc <= acc + x_q;
Legal: 1 + 2 + ... + 10 still sums to 55, one edge later.
A register inside the loop
s_q <= acc + x;
acc <= s_q;
Two interleaved sums: measured, the result is 25, the sum of the odd inputs.
So a loop sets a floor that pipelining cannot lower. In R4.5 the MAC stopped at 35 levels, its accumulator's adder. To go lower you change the loop itself: a narrower accumulator, or a different way of adding.
The lab: move the cut
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active-low |
| a, b, c | input | [7:0] | the operands |
| y | output | [13:0] | y = (a + b) * 11 + c, for the inputs taken two edges earlier |
- Latency
- 2, fixed: every input-to-output path crosses two registers
- Parameters
- Goal: the fewest levels of logic on this flow for the same function and latency
- Corner cases
- all inputs 255, so y = 5865; reset holds y at 0
Run and measure the starter
Run prints 202 cases at latency 2. Synthesize reports 26 flops and 29 levels.
Move the cut back after a + b
Register
sandc, then compute y froms_qandc_q. Run again: same lines, latency 2. Synthesize: 30 flops, 23 levels.Try the cut after the multiply
Register m and c instead. Still latency 2, but 26 levels: stage 1 is now the heavy one.
Split the multiply
Register s, 3s and c; compute y as 8 times
s_qplust_qplusc_q. 40 flops, 21 levels.
Asked in interviews
What is retiming?
Moving registers through combinational logic without changing the function. Backward across an operator, one register becomes one per input; forward, registers on all inputs merge into one. Every input-to-output path must cross the same number of registers as before.
Where do you place a pipeline register?
Where it balances the logic on both sides, so the slower stage is as fast as possible. Measure the candidates. Here the best cut was inside a constant multiply: 21 levels, against 23 for the best cut between two operators.
Why can an accumulator not be pipelined further?
Its adder and register form a loop. A register inside the loop makes the sum take two edges to come round, which turns one accumulator into two interleaved ones. The loop's depth is a floor; change the arithmetic to lower it.
Try it
Edge 2 takes (1, 2, 3) and y shows (1 + 2) * 11 + 3 = 36 one edge later, at edge 3. Each result appears on the line after its inputs: two registers.
Every legal cut prints these same lines. Only the flops, the cells and the levels in the Gates tab change.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 a=0 b=0 c=0 y=0 e1 a=0 b=0 c=0 y=0 e2 a=1 b=2 c=3 y=0 e3 a=255 b=255 c=255 y=36 e4 a=10 b=20 c=30 y=5865 e5 a=0 b=0 c=0 y=360 e6 a=0 b=0 c=0 y=0 202 cases, y matches the model at latency 2Original design · 199 cells · 26 flip-flops · 0 latches · 29 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic rst_n = 1; // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
logic [7:0] a = 0, b = 0, c = 0;
logic [13:0] y;
logic [13:0] want[1:4]; // want[k]: the result for the inputs taken k - 1 edges ago
bit [4:1] match = '1; // match[k]: y has equaled want[k] at every edge so far
int cases = 0, latency = 0;
int unsigned seed = 7;
bit quiet = 0;
chain dut (.clk(clk), .rst_n(rst_n), .a(a), .b(b), .c(c), .y(y));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
for (int k = 4; k > 1; k--) want[k] = want[k-1];
want[1] = (a + b) * 11 + c; // the inputs this edge took
if (edge_n >= 5) begin // compare once four edges of inputs are known
cases++;
for (int k = 1; k <= 4; k++) if (y !== want[k]) match[k] = 0;
end
if (!quiet) $display("e%0d a=%0d b=%0d c=%0d y=%0d", edge_n, a, b, c, y);
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
a = 1; b = 2; c = 3; tick();
a = 255; b = 255; c = 255; tick();
a = 10; b = 20; c = 30; tick();
a = 0; b = 0; c = 0; tick();
tick();
quiet = 1; // 200 more inputs from a fixed seed, checked but not printed
for (int i = 0; i < 200; i++) begin
seed = seed * 1103515245 + 12345;
{a, b, c} = seed[31:8];
tick();
end
for (int k = 4; k >= 1; k--) if (match[k]) latency = k;
if (latency) $display("%0d cases, y matches the model at latency %0d", cases, latency);
else $display("%0d cases, y matches the model at no latency from 1 to 4", cases);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- Retiming moves registers through logic without changing the function: backward across an operator, one register becomes one per input; forward, the registers on every input merge into one.
- The check is that every input-to-output path crosses the same number of registers; forget one, such as c here, and the function changes.
- The best cut balances the stages and is often inside an operator; levels of a chain overlap, so measure each candidate: here 29, 23, 26 and 21.
- A register cannot go inside a feedback loop: the accumulator becomes two interleaved sums. The loop's depth is a floor that pipelining cannot lower.
- This flow never moves a register for you:
synthruns ABC without-dff, so registers stay where the RTL puts them.