Consider the output y = f(g(x)), where g is slow and f is fast. The only register sits after f, so the critical path runs through g and f into the register.
Moving the register backward across f places it between g and f. The path splits into input → g → register, then register → f → output.
Moving it the other way is forward retiming. The full code sits under Testbench below.
The operands a, b and c are 8 bits, so the product a*b needs up to 16 bits and y is 16 bits wide (the largest value is 255 × 255 + 255 = 65280). The product register p_r is 16 bits too; a narrower register would silently truncate the product.
Both versions have the same latency from inputs to y as seen by a synchronous sampler. The difference is where the delay sits: before, one path of multiply plus add; after, a multiply path into the register and an add path out of it. Settled values agree, but y0 is a register output while y1 is a combinational sum of registers, so clock to output delay and glitches between clocks can differ.
Use illustrative maximum delays, not measured cells: latest input arrival 0.4 ns after the upstream launch edge, multiply 2.4 ns, add 0.8 ns, register setup 0.1 ns, clock-to-Q 0.2 ns, and output route 0.2 ns. Target period is 3.0 ns. The system initially requires y at the output port by 1.0 ns after this block's capture edge, reserving the rest of the downstream cycle for external logic/setup. Assume zero skew/uncertainty here; real STA must include them and also check minimum-delay hold paths.
Budget / cost | Before: register after add | After: registers before add |
|---|---|---|
Input-to-register requirement | 0.4 + 2.4 + 0.8 + 0.1 = 3.7 ns | 0.4 + 2.4 + 0.1 = 2.9 ns (multiply path dominates c) |
Input setup slack at 3.0 ns | -0.7 ns: fails | +0.1 ns: passes this model |
Register-to-output arrival | 0.2 + 0.2 = 0.4 ns | 0.2 + 0.8 + 0.2 = 1.2 ns |
Output slack against 1.0 ns required | +0.6 ns: passes | -0.2 ns: fails |
Data register bits | 16 | 16 product + 8 c = 24 (+8 bits) |
Each bar is one path drawn to scale, left to right in nanoseconds; the dashed marks above and below it show the limit it must finish before. A bar that runs past its marks has negative slack.
Before: the 0.8 ns add sits on the input path, which ends 0.7 ns past the period line. The output path has 0.6 ns to spare.
After: the add box moves from the input bar to the output bar. The input path now fits by 0.1 ns, but the output path misses its 1.0 ns requirement by 0.2 ns.
The move only shifts work between boundaries. Whether it helps depends on who owns the output budget.
With the original output budget, reject the move: it merely relocates the failure. If the downstream owner can allow 1.4 ns instead, output slack becomes +0.2 ns and the retimed version meets both modeled boundaries at 3.0 ns, while the original needs at least 3.7 ns for its input path. That is a conditional timing choice with eight extra register bits. Confirm actual constraints, mapped delays, hold closure and reset/initial-state equivalence before accepting it.
Rule | Detail |
|---|---|
A register moved backward across a gate with several inputs is duplicated on each input | Here |
A register moved forward across a gate with several inputs requires registers on all inputs | Otherwise some inputs would be delayed and others not. |
Initial values must be consistent | A register with a reset value may only move if an equivalent value exists on the other side. |
Registers in different clock domains stay put | Retiming never crosses a clock boundary. |
c_r?The adder has two inputs. The register moved to the adder's input side must be on both; otherwise c would arrive one cycle earlier than the product and be added to the wrong item.
Both versions plus the testbench that checks their settled outputs match when sampled after the clock edge. The registers have no reset, so the first two cycles are skipped as unknown startup. Downstream logic must sample y synchronously.
module before_retime (
input logic clk, rst_n,
input logic [7:0] a, b, c,
output logic [15:0] y
);
// register at the end: path = (a*b) + c all in one cycle
logic [15:0] r;
always_ff @(posedge clk) r <= (a * b) + c; // 8x8 product needs 16 bits; 255*255 + 255 = 65280 still fits
assign y = r;
endmodule
module after_retime (
input logic clk, rst_n,
input logic [7:0] a, b, c,
output logic [15:0] y
);
// registers moved backward across the adder: multiply | reg | add
logic [15:0] p_r; // full 16-bit product (255*255 = 65025)
logic [7:0] c_r;
always_ff @(posedge clk) begin
p_r <= a * b; // path 1 = multiply only
c_r <= c; // c must pass through the same register rank
end
assign y = p_r + c_r; // path 2 = add only (after the register)
endmodule
The delays are illustrative maximums, not measured cells.
The 1.4 ns output budget case is a conditional timing choice, not a measured frequency, area or power result.