Retiming moves registers across combinational logic to balance stage delays; the outputs stay the same. A tool can only do it where the code leaves the registers free to move. Here is one three-rank multiply pipeline written two ways.
// Before: every datapath register is reset and written only when its valid bit is high.
module retime_blocked (
input logic clk, rst_n,
input logic in_v,
input logic [7:0] a, b,
output logic out_v,
output logic [15:0] y
);
logic v1, v2;
logic [7:0] a_r, b_r;
logic [15:0] mul_r;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin
v1 <= 0; v2 <= 0; out_v <= 0;
a_r <= '0; b_r <= '0; mul_r <= '0; y <= '0; // datapath reset
end else begin
v1 <= in_v; v2 <= v1; out_v <= v2;
if (in_v) begin a_r <= a; b_r <= b; end // enable mux on every data bit
if (v1) mul_r <= a_r * b_r;
if (v2) y <= mul_r;
end
endmodule// snippet
// After: only the valid bits are reset; the data registers are written every cycle.
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin v1 <= 0; v2 <= 0; out_v <= 0; end
else begin v1 <= in_v; v2 <= v1; out_v <= v2; end
always_ff @(posedge clk) begin // no reset, no enable
a_r <= a;
b_r <= b;
mul_r <= a_r * b_r;
y <= mul_r;
endBoth versions give the same y whenever out_v is high; consumers ignore y otherwise. The difference is what the tool may do with the 48 data flops. In the second version they are plain D flip-flops with one clock and nothing attached, so the tool may slide the mul_r and y ranks into the multiplier to balance the two stages. In the first, every data flop carries a hold mux and a reset value, and both pin it in place. The full second version is under Testbench below.
Read each panel as a register the tool would like to move, and the part of the circuit that forbids it.
Enable mux: the flop's output loops back into its own input. Moving the flop means moving the loop with it, which many flows will not do.
Mixed reset: moving r1 and r2 forward across f merges them into one flop. Its reset value would be f of 0 and an unknown, which is not defined.
The fix for both is in the second coding above: no enable on free-running data, and no reset on data registers.
Habit | Why it helps |
|---|---|
Reset only control registers; leave datapath registers unreset | Registers with different reset values cannot be moved across each other freely. |
Use one clock and one reset style per pipeline | Registers in different domains are never retimed together. |
Write datapath registers unconditionally and qualify their use with valid; keep enables for stalls and held state | An |
Keep control and data in the same stage count | A valid bit with fewer stages than its data breaks alignment after retiming. |
Do not put | The tool is forbidden from moving them. |
Avoid initial values that matter | Power-up values must match after moving a register. |
Add ranks. N extra output ranks add N cycles of consumption latency. Extend the valid delay by the same N and update consumers and the reference model.
Let the tool move them. Legal retiming then redistributes those registers while keeping the new latency and a consistent initial state.
The listing below already contains the extra output rank, so its contract is three-cycle latency. A supported synthesis flow may redistribute it when retiming is enabled and the register, reset and boundary constraints permit.
Because valid marks which data slots are real. If the two paths differ, valid will point at the wrong data (or a bubble) after the data moves.
The RTL below has three register ranks: capture, multiply, and one extra output rank. Accept at edge 0, update product after edge 1, update y/out_v after edge 2, consume at edge 3. The pre-edge checker independently predicts valid on every cycle (including bubbles), checks data and exact three-cycle latency, and drains. Directed isolated/back-to-back offers precede random traffic.
// The extra output rank changes the contract to three-cycle consumption latency.
// A supported tool may redistribute y's register while preserving this contract.
module retime_friendly (
input logic clk, rst_n,
input logic in_v,
input logic [7:0] a, b,
output logic out_v,
output logic [15:0] y
);
logic v1, v2;
logic [7:0] a_r, b_r;
logic [15:0] mul_r;
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) begin v1 <= 0; v2 <= 0; out_v <= 0; end
else begin v1 <= in_v; v2 <= v1; out_v <= v2; end // control path has the SAME 3 stages as data
always_ff @(posedge clk) begin // datapath: no reset, no enable
a_r <= a;
b_r <= b;
mul_r <= a_r * b_r;
y <= mul_r; // extra stage left for the tool to redistribute
end
endmodule
The testbench checks the authored RTL contract. It does not perform or prove a tool retiming transformation.
No mapped netlist, static timing report or measured frequency gain is supplied.