Module 149 min

RTL Lint

RTL lint explained: the static checks that catch bugs before simulation and synthesis, common lint rules (latches, sensitivity, multi-driver, width), lint vs simulation vs formal, and how to handle waivers.

Lint is a static analysis of your RTL that catches whole classes of bugs and bad style before you run a single test. It reads the code, not a simulation, and flags problems that would otherwise cost hours later: an accidental latch, a signal driven from two places, a width mismatch that silently truncates a bus. It is the cheapest quality gate in the flow, and running it on every commit keeps the RTL clean for both simulation and synthesis.

What lint checks

  • Unintended latches from an incomplete if or case in combinational logic.
  • Incomplete or wrong sensitivity lists (a reason to use always_comb and always_ff).
  • Multi-driven nets: a signal driven from more than one place.
  • Width mismatches where an assignment or a port connection silently truncates or extends.
  • Undriven, unconnected, or unused signals and ports.
  • Blocking vs non-blocking misuse (blocking in sequential logic, or non-blocking in combinational).
  • Case problems: overlapping items, a missing default, non-full or non-parallel case.
  • Unreachable or dead code, and constant expressions that hint at a typo.
  • Reset and X-propagation issues that a simulation can accidentally hide.

Why lint early

Every problem lint finds is a bug you do not have to chase later. An unintended latch can pass a directed test and still fail in the field; a width mismatch can drop the top bits of a bus and only surface on a corner case. Lint finds these in seconds, structurally, before any vector runs. It also enforces a house coding standard so a large team writes RTL the same way, which is exactly what synthesis and the downstream tools want to see.

Lint vs simulation vs formal

The three catch different things. Lint is static and structural: no testbench, no vectors, just rules on the code, so it is fast but only sees what the structure reveals. Simulation is dynamic: it exercises behaviour with stimulus, so it finds functional bugs, but only on the paths your tests actually hit. Formal proves properties mathematically across all inputs. Lint is the first and cheapest gate, run before the others, and a complement to them rather than a replacement.

Lint findsExampleWhy it matters
Unintended latchA combinational output with no else on every pathBreaks timing and testability
Multi-driverTwo always blocks driving one netUnpredictable value, synthesis conflict
Width mismatch8-bit value assigned to a 4-bit signalSilent data loss
Missing defaultA case with no default branchInferred latch or undefined behaviour
Undriven signalDeclared and read, but never assignedFloating logic and X-propagation
Watch out

Do not blanket-waive lint warnings just to reach zero. Each waiver should be reviewed and justified, because a waived rule is a check you have switched off. A clean report full of unexamined waivers is more dangerous than a noisy one, because it looks safe when it is not.

Pro tip

Run lint in CI on every commit and fail the build on any new violation. Common tools are Synopsys SpyGlass and VC SpyGlass, the Cadence equivalents, and the open-source Verilator (verilator --lint-only) for a quick free check. Keeping lint at zero from day one is far easier than clearing thousands of warnings just before tape-out.