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 sitsFlopsCellsLevels of logic on this flowLongest path runs
A: at the output2619929from a[1] to r_q[12]
B: after a + b3020223from s_q[2] to y[12]
C: after the multiply by 113521526from a[1] to m_q[11]
D: inside the multiply (3s, then 8s + 3s + c)4020821from 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.

Four places for the second registerArrows carry data from the inputs to y. Each step lights the stage-2 logic for one cut; y is always the last register.
Four places for the second registerArrows carry data from the inputs to y. Each step lights the stage-2 logic for one cut; y is always the last register. manager a, b 8 bits each manager c 8 bits bridge s = a + b 15 levels alone bridge m = s * 11 19 levels alone bridge r = m + c 20 levels alone completer y the output register a, bsmcr
  1. 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.

  2. 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.

  3. 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.

  4. 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

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
a, b, cinput[7:0]the operands
youtput[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
  1. Run and measure the starter

    Run prints 202 cases at latency 2. Synthesize reports 26 flops and 29 levels.

  2. Move the cut back after a + b

    Register s and c, then compute y from s_q and c_q. Run again: same lines, latency 2. Synthesize: 30 flops, 23 levels.

  3. Try the cut after the multiply

    Register m and c instead. Still latency 2, but 26 levels: stage 1 is now the heavy one.

  4. Split the multiply

    Register s, 3s and c; compute y as 8 times s_q plus t_q plus c_q. 40 flops, 21 levels.

Asked in interviews
  1. 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.

  2. 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.

  3. 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.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: 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 2

Original 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

Predict the outputCut B gets one more register at its output, so the chain has three stages. Edge 1 takes a = 1, b = 2, c = 3, and every later input is 0. The program prints y and the edge where y first becomes nonzero. What is the output line?
module chain3 (input logic clk, rst_n, input logic [7:0] a, b, c, output logic [13:0] y);
  logic [8:0]  s_q;
  logic [7:0]  c_q;
  logic [13:0] r_q;
  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin s_q <= '0; c_q <= '0; r_q <= '0; y <= '0; end
    else begin
      s_q <= 9'(a) + 9'(b);            // stage 1: the add
      c_q <= c;
      r_q <= 14'(s_q * 4'd11 + c_q);   // stage 2: the multiply and the second add
      y   <= r_q;                      // stage 3: a register added at the output
    end
endmodule
module tb;
  logic clk = 0, rst_n = 1;
  logic [7:0] a = 0, b = 0, c = 0;
  logic [13:0] y;
  int n = 0;
  chain3 dut (.clk(clk), .rst_n(rst_n), .a(a), .b(b), .c(c), .y(y));
  task automatic step(); #1 clk = 1; #1 clk = 0; n++; endtask
  initial begin
    #1 rst_n = 0; #1 rst_n = 1;
    a = 1; b = 2; c = 3; step();       // edge 1 takes the only nonzero input
    a = 0; b = 0; c = 0;
    while (y == 0) step();
    $display("y=%0d after edge %0d", y, n);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of an accumulator. The bench adds 1, 2, ..., 10 on ten edges, waits four edges, and expects sum = 55. Which option fails?
ConceptA designer moves the second register of the chain from the output back across the final add, registering m but not c. What happens?
ConceptWhy does a feedback loop set a floor on the levels of logic that pipelining cannot lower?
ConceptThe four cuts of the chain measured 29, 23, 26 and 21 levels with 26, 30, 35 and 40 flops. What did retiming keep the same in every version?

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: synth runs ABC without -dff, so registers stay where the RTL puts them.