Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Quick Start: Your First Green Build
  • Part 1: How Chips Get Made6
  • Part 2: Your Workstation5
  • Part 3: Specify and Architect3
  • Part 4: The Design Loop6
    • Compile, Elaborate, Simulate, Synthesize, Lint
    • First Simulation
    • Waveforms with GTKWave
    • Lint with Verilator
    • First Synthesis Check
    • Simulation Code vs Synthesizable Code
  • Part 5: The Verification Ladder6
  • Part 6: Automation and Reproducibility5
  • Part 7: Hand-Off to the Back-End3
  • Part 8: When Things Go Wrong3
  • Part 9: Project and Next Steps3
  • Reference4
Loading lesson

‹Part 4: The Design Loop
  1. Topics
  2. Start Here: RTL and Verification
  3. Part 4: The Design Loop
  4. Lint with Verilator

Part 4 · The Design Loop · Working (W)

Lint with Verilator

Lint reads your source without running it and flags suspicious patterns. Run Verilator with --lint-only, read two width warnings, and fix both with one change.

On this page

    Note

    Level: Working (W) · Answers: Can a tool find bugs without running the design?

    Before this: Waveforms with GTKWave

    Can a tool find bugs without running the design?

    Your simulation passes, yet one line adds an 8-bit constant into a 4-bit counter. Every test fed values that fit, so no check ever noticed the extra bits. A second tool reads the same line and objects without running anything: that is the gap this lesson closes.

    Lint reads the text, never the run

    Lint is static analysis of your source: it reads the text and reports patterns that often mean bugs, with no testbench and no simulated time. A width mismatch widens a value or trims it silently. An unused or undriven signal suggests a forgotten connection. A latch is storage the tools infer where you meant plain combinational logic, usually from an incomplete branch. None of these needs a run to spot, and all of them are cheaper to fix before simulation.

    The linter in this course is Verilator, run with --lint-only so it analyzes instead of simulating, -Wall so it reports every warning class, and --top-module naming the design. The Verilator documentation lists every warning code the tool can emit; this lesson needs exactly two of them.

    The figure below shows the line this lesson breaks and the two warnings it produces. Each warning names a column: match the column to the token it complains about before reading on.

    One line, two warnings. Match each warning to the token it complains about: the widening constant and the assignment that trims the sum back down. Then read the one-line fix at the bottom.

    Run it: a clean design reports nothing to fix

    Lint the course counter as written. The command and its answer:

    Diagram
    # captured terminal output
    $ verilator --lint-only -Wall --top-module mini_controller rtl/mini_controller.sv && echo lint clean
    - V e r i l a t i o n   R e p o r t: Verilator 5.048 2026-04-26 rev vUNKNOWN-built20260426
    - Verilator: Built from 0.033 MB sources in 2 modules, into 0.008 MB in 3 C++ files needing 0.000 MB
    - Verilator: Walltime 0.003 s (elab=0.000, cvt=0.001, bld=0.000); cpu 0.003 s on 1 threads; allocated 9.359 MB
    lint clean

    This output came from Verilator 5.048 on macOS. The command asks for analysis only, with all warnings on and the counter as the top. The middle lines are the tool's own report header; the last line is the shell confirming success, because Verilator exits quietly when there is nothing to flag.

    Does a clean lint prove the design counts?

    Predict first Lint reports nothing on the counter. Before reading on: does that prove the counter counts correctly?

    No. Lint checks the text for suspicious patterns, never the intent behind it. A counter that adds two instead of one uses perfectly ordinary widths and lints clean, yet fails simulation on every counting edge. Lint asks whether the code looks wrong; only a testbench asks whether it behaves wrong.

    Break it on purpose: widen one constant

    Change the increment from a 4-bit constant to an 8-bit one. In a small module holding this counter line, the increment reads count <= count + 8'd1;. Lint it:

    Diagram
    # captured terminal output
    $ verilator --lint-only -Wall --top-module w widthdemo/w.sv
    %Warning-WIDTHEXPAND: widthdemo/w.sv:2:45: Operator ADD expects 8 bits on the LHS, but LHS's VARREF 'count' generates 4 bits.
                                             : ... note: In instance 'w'
        2 |     always_ff @(posedge clk) count <= count + 8'd1;
          |                                             ^
                          ... For warning description see https://verilator.org/warn/WIDTHEXPAND?v=5.048
                          ... Use "/* verilator lint_off WIDTHEXPAND */" and lint_on around source to disable this message.
    %Warning-WIDTHTRUNC: widthdemo/w.sv:2:36: Operator ASSIGNDLY expects 4 bits on the Assign RHS, but Assign RHS's ADD generates 8 bits.
                                            : ... note: In instance 'w'
        2 |     always_ff @(posedge clk) count <= count + 8'd1;
          |                                    ^~
                         ... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=5.048
                         ... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message.
    %Error: Exiting due to 2 warning(s)
    [exit status 1]

    Same tool and platform. Two warnings point at the same line from opposite ends. The WIDTHEXPAND warning at column 45 says the addition widens to 8 bits: the constant is 8 bits wide, so the sum follows it. The WIDTHTRUNC warning at column 36 says the 8-bit sum is assigned back into the 4-bit count, so the top four bits are cut away. The last two lines are the verdict: with -Wall warnings are fatal, so Verilator exits with an error status instead of passing.

    Both warnings describe one mismatch, so one change clears both. Match the constant to the count:

    design.sv
    // snippet
    count <= count + 4'd1;

    This listing is the corrected line: the constant is 4 bits wide like the count, so the sum never widens and there is nothing left to cut. The course counter carries exactly this line, and its lint run above is clean, which is the proof that the pattern behind both warnings is gone.

    Three ways lint gets ignored

    • Skipping lint because simulation passes. Simulation checks only the cases it drives; lint checks the text. The 8-bit constant passes every test whose values fit in four bits and still describes a wider adder than you meant.

    • Silencing a warning instead of fixing it. Verilator lets you wrap lines in lint-off comments, but a waiver is a documented exception for a deliberate construct, and the waiver policy lives in RTL: Coding Lint. Fix the code first; only a construct you mean gets a waiver.

    • Starting a comment with the word verilator. The tool reads a comment whose first word is verilator as a control pragma and stops with an error instead of linting. Reword the comment so another word comes first.

    Rule: fix the warning instead of silencing it.

    Check yourself Both warnings point at one line. How many changes fix them, and what is the change?

    One: change 8'd1 to 4'd1 so the constant matches the 4-bit count. The sum stays 4 bits wide, so the expand warning has nothing to widen and the trunc warning has nothing to cut.

    Check yourself Lint is clean and the simulation fails. Is that a contradiction?

    No. Lint judges the text; the testbench judges the behavior. A clean lint with a failing test means the bug is in the logic or the intent, like an increment of two, not in a suspicious pattern.

    Check yourself A warning flags a construct you fully intend. What do you do?

    Document it and waive it narrowly around those lines, following the waiver policy in RTL: Coding Lint. Keep -Wall on for everything else: a blanket disable trades one justified exception for silence about the next real bug.

    Interview

    Asked: "What does lint catch that simulation might not?"

    Weak answer: It finds all bugs without needing a testbench.

    Strong answer: It flags suspicious patterns in the text, such as width mismatches, unused or undriven signals, and unintended latches, without running anything, including bugs whose trigger values no test ever drives. It cannot judge intent: a wrong but plausible constant passes lint and needs a testbench.

    Follow-ups: (1) When would you waive a warning instead of fixing it? (2) Your lint is clean and the test fails: where do you look?

    Key takeaways

    • Lint reads the source without a testbench and flags patterns that often mean bugs.

    • With -Wall, warnings are fatal: one width fix can clear several warnings at once.

    • Fix each warning where you wrote it; waive only deliberate exceptions with a written reason.

    Keep going

    Next: Lesson 4.5, First Synthesis Check. Lint approves the text. The next lesson asks what hardware the text describes.

    Related topics

    • RTL: Coding Lint

    • Design Loop: First Simulation

    Previous

    21 / 47

    Next: First Synthesis Check

    Up next

    Up nextFirst Synthesis Check

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Compile, Elaborate, Simulate, Synthesize, Lint
    • First Simulation
    • Simulation Code vs Synthesizable Code