2026-05-21
Stack Overflow: View Question
Tags: verilog, signed, hdl, register-transfer-level, saturation-arithmetic
Score: 0 | Views: 103
The asker wrote a parameterized signed saturating truncator: take a WIN-bit signed input, produce a WOUT-bit signed output that clamps to MAX/MIN if the input falls outside the representable range, and sign-extends when WOUT >= WIN. Icarus and Verilator simulate the module correctly, but SynplifyPro synthesizes hardware whose behavior diverges from both simulators — a classic "passes sim, fails on the board" trap.
Why this is interesting: Saturation logic is one of the most error-prone idioms in RTL because it mixes three sharp edges of the Verilog language at once:
signed attribute if any operand is unsigned. A single literal like 1'b1 can promote the whole expression to unsigned and invert the meaning of a < against a negative value.WIN, WOUT) make this hard to eyeball.Direction toward an answer:
±(2^(WOUT-1)), ±(2^(WOUT-1))-1, and the extreme values of WIN. Compare RTL sim against a Synplify post-synthesis netlist simulation (not just on-hardware behavior) — that pins down whether the bug is in synthesis or in P&R.$signed(...) casts on each side of < and >. Verify literals are written as WIN'sd... rather than bare decimals.upper_bits == {WIN-WOUT+1{in[WIN-1]}} — a sign-replication check that avoids comparisons entirely.Gotchas: when WOUT >= WIN the saturation branch must be unreachable, but generate-time conditionals often still synthesize the dead arm and can introduce unsigned literals into the always block. Wrap the saturation in a generate if so the dead logic vanishes from the netlist. Also: if any intermediate is declared reg rather than reg signed, sign info is lost across the assignment regardless of the RHS.
