Lint Your RTL#

Linting checks your RTL for syntax and style problems without compiling it, and it is the quickest way to get a real result out of SiliconCompiler. No PDK, no cloud account, and – unusually – no EDA tools to install. The default linter, slang, ships as a Python package that comes with SiliconCompiler itself, so this works immediately after pip install.

A run takes about ten milliseconds.

The script#

Save this next to a Verilog file called blinky.v:

from siliconcompiler import Design, Lint
from siliconcompiler.flows.lintflow import LintFlow

design = Design("blinky")
design.set_dataroot("root", __file__)
with design.active_dataroot("root"), design.active_fileset("rtl"):
    design.set_topmodule("blinky")
    design.add_file("blinky.v")

project = Lint(design)
project.add_fileset("rtl")
project.set_flow(LintFlow())

project.run()
project.summary()

The shape is the same as every other SiliconCompiler script – describe the design, pick a flow, run – with two differences: the project type is Lint rather than ASIC, and no target is loaded, because there is no technology to target.

What it tells you#

On clean sources the run says so and stops:

| INFO     | job0 | lint | 0 | Number of errors: 0
| INFO     | job0 | lint | 0 | Number of warnings: 0
| INFO     | job0 | lint | 0 | Finished task in 0.01s

Give it something broken – a mistyped signal name and an out-of-range bit select – and you get the diagnosis with the source line:

| ERROR | blinky.v:13:24: error: use of undeclared identifier 'count'
| ERROR |             counter <= count + 1'b1;
| ERROR |                        ^~~~~
| ERROR | blinky.v:14:32: warning: cannot refer to element 8 of 'reg[7:0]' [-Windex-oob]
| ERROR |             led     <= counter[N];
| ERROR |                                ^
| INFO  | Number of errors: 2

Both counts are recorded as metrics[metric,errors] and [metric,warnings] – so they appear in Project.summary() and can be read back afterwards. Read them from the history object Project.run() returns: job-scoped metrics are reset on the live project when the job finishes, so the completed values live there.

history = project.run()
history.get("metric", "errors", step="lint", index="0")

That is what makes linting worth scripting rather than running by hand: the result is a number you can gate on.

Choosing the linter#

LintFlow takes a tool argument:

project.set_flow(LintFlow())                      # slang (default)
project.set_flow(LintFlow(tool="verilator"))      # verilator
project.set_flow(LintFlow(tool="all"))            # both, as separate nodes

slang needs nothing installed. verilator is an external tool and has to be available on your machine, but it catches a different class of problem, which is what tool="all" is for – it builds one node per linter and runs them side by side.

The lint script above passes -Weverything to slang, so you are seeing everything it has to say.

In a real build script#

examples/heartbeat exposes linting as one target among many, so the same design can be linted, synthesized, simulated or hardened without editing anything:

def lint(N: Optional[str] = None):
    """Runs the linting flow on the Heartbeat design.

    Linting checks the Verilog source code for syntax errors and style
    issues without performing a full synthesis.

    Args:
        N (str, optional): The value for the Verilog parameter 'N'.
            Defaults to None, which uses the value set in the design schema.
    """
    # Create a project instance tailored for linting.
    project = Lint()

    # Instantiate the design configuration.
    hb = HeartbeatDesign()

    # Associate the design with the project.
    project.set_design(hb)
    # Add the necessary fileset for this flow.
    project.add_fileset("rtl")

    # Optionally override the 'N' parameter from the command line.
    if N is not None:
        hb.set_param("N", N, fileset="rtl")

    # Configure the project to use the linting flow.
    project.set_flow(LintFlow())

    # Execute the flow.
    project.run()
    # Display a summary of the results.
    project.summary()
cd examples/heartbeat
smake lint            # or: smake lint --N 16

See Run a build script’s targets without editing it for how that works, and Example designs for the rest of what this one can do.

Next#