Digital Design / Module 11: Designing for silicon
61. Lint and code quality
Catch bugs before simulation.
See it move
The idea
Lint tools check RTL against a set of rules, with no simulation and no test vectors. They can find a whole class of bugs in seconds, and they often catch issues that simulation can't.
What lint catches
| Issue | Why it matters |
|---|---|
| Width mismatch | Values get silently cut or extended |
| Multiple drivers | Conflicts cause x in simulation, and contention in hardware |
| Inferred | Usually unintended |
| Incomplete sensitivity list | Simulation and gates disagree |
| Blocking in clocked block | Races and lost stages |
| Unused signals | Often a real wiring mistake |
| Unintended clock or reset logic | Glitches, hard timing |
Width rules
Verilog silently resizes values, and this causes real failures.
wire [7:0] a, b;
wire [7:0] sum = a + b; // the carry is lost
wire [8:0] ok = a + b; // keeps the carryBe explicit, and use sized constants like 8'd1 instead of plain 1.
Stating your assumptions
SystemVerilog lets you turn assumptions into checks. unique case says that exactly one branch matches, and priority case says that at least one must, in order. Tools warn when reality differs. Use them only when you mean them, though, since they also permit optimisations that assume the claim is true.
A team habit
Run lint before every commit, and keep the design warning-free. Once a block has 200 known warnings, nobody will notice the new, important one. If a warning is truly fine, waive that specific one with a written reason, and don't turn off the rule.
Common mistake
Treating lint as a last step before tapeout. Issues are far cheaper to fix on the day they're written. Lint also isn't a replacement for CDC and RDC tools, which check clock and reset crossings specifically. Work through the examples in the animation above, and see whether you can spot each issue first.
Key ideas
- Width mismatches and multiple drivers are common.
- Unique and priority keywords make assumptions explicit.
- Treat warnings as work to resolve.
Your exercise
Interview questions
coreWhy does width mismatch matter?
Silent truncation or extension can produce wrong values without any error.
basica and b are 8-bit, and sum8 is declared as wire [7:0] sum8 = a + b;. What is sum8 for a = 200, b = 100, and why is there no error?
The real sum is 300, which needs 9 bits. The result is cut to 8 bits, so sum8 = 300 - 256 = 44. Verilog silently truncates to the declared width, so this is legal code and a compiler usually says nothing. A lint width rule flags it, and the fix is a 9-bit result.
basicWhat can a lint tool find that simulation might not, and what is it not a replacement for?
Lint reads the code against rules, with no test vectors, so it finds every instance of a pattern at once: width mismatches, multiple drivers, unintended latches, incomplete sensitivity lists, blocking assignments in clocked blocks and unused signals. Simulation only finds a bug if a test happens to reach it and check it. Lint does not replace CDC and RDC checks, which look specifically at clock and reset crossings.
coreA testbench checks sum8 from the previous question with this line, over 1000 random pairs, and reports no failures. Why does the test miss the lost carry?
if (sum8 !== a + b) errors = errors + 1;
In the comparison, the expression a + b is sized to the widest operand, which is 8 bits, so the reference sum also loses its carry. The design and the check make the same mistake, so they always agree. Comparing against a wider expected value, for example a 32-bit integer, makes about half of random pairs fail, because about half of them carry. A test only finds a bug that the test itself can see.
coreWhat does a simulator show for m when p = 1 and q = 0, and what is the hardware risk?
wire m; assign m = p; assign m = q;
The two continuous assignments both drive the same wire, and they disagree, so simulation shows X. In hardware, two drivers fighting cause contention, with an undefined level and wasted current. Lint reports multiple drivers on a net, whatever values a test happens to apply. The fix is to decide which source drives m, or use a mux.
coreWhat happens to r in this block when s is 2 or 3?
always @(*)
case (s)
2'd0: r = 8'h11;
2'd1: r = 8'h22;
endcaseFor s = 2 or 3, no branch assigns r, so it must keep its previous value, which means a latch is inferred. In simulation r simply holds whatever was there last. That is usually unintended, and it makes timing and testing harder. Adding a default branch, or assigning r a value before the case, removes the latch.
coreWhat do unique case and priority case in SystemVerilog tell the tools, and when are they dangerous?
unique case says exactly one branch will match, and priority case says at least one will, checked in order. Simulators and tools warn when the claim is broken, which turns an assumption into a check. The danger is that synthesis may also optimise as if the claim is always true. If the claim is false, the hardware can behave differently from the RTL simulation, so use them only when you mean them.
advanceda and b are 8-bit. What is avg for a = 200, b = 100, and how would you fix it?
wire [7:0] avg = (a + b) >> 1;
The expression is sized to the widest operand in context, the 8-bit result, so a + b is computed in 8 bits and becomes 44 before the shift. avg is therefore 22 instead of 150. Fixes are to compute the sum in 9 bits first, for example wire [8:0] s9 = a + b; then take s9[8:1], or write ({1'b0, a} + b) >> 1 so the sum has room for the carry. Lint width rules catch this, while a quick test with small values would not.
advancedx is declared reg signed [7:0] and holds -3. What does y become?
reg signed [7:0] x; wire signed [9:0] y = x + 4'd2;
If any operand is unsigned, the whole expression is treated as unsigned. 4'd2 is unsigned, so x is extended with zeros instead of its sign, giving 253 + 2 = 255 rather than -1. The fix is to make every operand signed, for example x + $signed({1'b0, 4'd2}), which gives -1. Lint tools commonly warn about mixing signed and unsigned operands for exactly this reason.