The netlist contains a latch nobody drew, and a short path fails hold only after scan insertion. Both are sequential surprises found late.
The latch shows up as storage on a combinational output. The hold failure shows up on a path that passed before the scan muxes landed.
Surprise | Symptom | First suspicion |
|---|---|---|
Inferred latch | Storage on a combinational output | Incomplete assignment in a combinational block. |
Hold failure after scan | Short path fails hold only in scan mode | Scan skew and routing with arrival fixed in the EXAMPLE. |
A combinational block that leaves an output unassigned on some path must hold the old value. The tool builds a latch for that memory.
The Yosys block below proves it on one small design. The buggy variant holds one latch cell; the fixed variant holds none.
read_verilog <<EOV
module latcher(input wire en, input wire [7:0] a, output reg [7:0] y);
always @* begin
if (en) y = a;
end
endmodule
EOV
hierarchy -top latcher
proc
select -assert-count 1 t:$dlatch
design -reset
read_verilog <<EOV
module latcher(input wire en, input wire [7:0] a, output reg [7:0] y);
always @* begin
if (en) y = a;
else y = 8'h00;
end
endmodule
EOV
hierarchy -top latcher
proc
select -assert-none t:$dlatch
log PASS: latch present in buggy variant and absent in fixed variantHold slack is arrival minus required, where required covers hold time plus skew plus uncertainty. Scan insertion moves both sides: the added mux delay helps hold on arrival, while skew and routing hurt it.
The EXAMPLE fixes arrival at 0.16 ns to isolate a +0.10 ns skew growth. The calc block substitutes those parameters; the program below rechecks both slacks.
EXAMPLE parameters (not real silicon): data arrival fixed at 0.16 ns, hold time 0.02 ns, uncertainty 0.04 ns
scan changes arrival and skew both; arrival is fixed here to isolate the skew growth of 0.12 - 0.02 = +0.10 ns
required before = 0.02 + 0.02 + 0.04 = 0.08 ns (skew 0.02 ns)
hold slack before = 0.16 - 0.08 = +0.08 ns (passes)
required after scan = 0.02 + 0.12 + 0.04 = 0.18 ns (skew 0.12 ns)
hold slack after scan = 0.16 - 0.18 = -0.02 ns (fails by 0.02 ns)import sys
ARR = 0.16
HOLD = 0.02
UNC = 0.04
SKEW_BEFORE = 0.02
SKEW_AFTER = 0.12
def close(got, want):
return abs(got - want) < 1e-9
req_before = HOLD + SKEW_BEFORE + UNC
slack_before = ARR - req_before
req_after = HOLD + SKEW_AFTER + UNC
slack_after = ARR - req_after
bad = 0
if not close(req_before, 0.08):
bad = bad + 1
if not close(slack_before, 0.08):
bad = bad + 1
if not close(req_after, 0.18):
bad = bad + 1
if not close(slack_after, -0.02):
bad = bad + 1
if bad != 0:
print("FAIL: hold arithmetic broke on " + str(bad) + " checks")
sys.exit(1)
print("PASS: hold slack +0.08 ns before scan and -0.02 ns after")Fix the latch by assigning every output on every path, with an else branch or a default before the branch. The cost is stating the intended value explicitly.
Fix the hold violation with delay padding on the short path or a rebalanced scan clock. Padding costs area and power on a path that was fine before scan.
A latch holding y when en is low. An else branch assigning y removes it, proven by zero latch cells after the fix.
Before: 0.16 minus 0.08 is +0.08 ns. After: 0.16 minus 0.18 is -0.02 ns.