Beyond RTL Lint: 90+ Rules for SystemVerilog Assertions, Constraints, and Coverage

Back in 2023, we started building open-source lint tools for the parts of SystemVerilog that commercial linters tend to treat as an afterthought — assertions, constraint blocks, and functional coverage. What began as a handful of rules has grown into a substantial rule set: SVALint, CNSTLint, and FCOVLint now collectively cover over 90 lint rules, all built on the Verible parser and the BYOL (Build Your Own Linter) framework.

This post highlights the rules that catch real bugs, silent misbehaviour, and tool-specific pitfalls — not naming conventions. Full rationale, violation examples, and correct usage are in the linked docs.


SVALint — Assertion Linting (52 rules)

Repo: github.com/AsFigo/svalint  |  Docs: asfigo.github.io/svalint/

SVA is deceptively easy to write incorrectly. These rules catch issues that compile cleanly, pass elaboration, and then silently misbehave in simulation or formal verification.

Rule IDBug it catches
DELAY_BEFORE_ROSE
DELAY_BEFORE_FELL
DELAY_BEFORE_CHANGED
$rose(sig) samples two consecutive clocks. Without a preceding ##1, the assertion may fire on the very first active clock before sig has a defined previous value — causing spurious failures at time zero or after reset.
FUNC_AVOID_WEAK_EVENTUALLY The weak eventually operator passes vacuously if simulation ends before the condition is observed. Use s_eventually (strong form) to require the condition to actually be witnessed.
FUNC_MISSING_FAIL_ABLK Without an else fail action block, a failing assertion produces only a generic simulator error with no design-context information — no signal values, no transaction state.
FUNC_AVOID_$_RANGE_IN_CONSEQ_A An assertion with an unbounded $ range in its consequent can never fail — it will always find a future cycle where the condition holds. This makes the assertion useless for verification.
STYLE_NO_CLK_WITHOUT_EDGE @(clk) without an edge qualifier samples both rising and falling edges, causing the assertion to fire twice per cycle with confusing results.

CNSTLint — Constraint Block Linting (20 rules)

Repo: github.com/AsFigo/cnstlint  |  Docs: asfigo.github.io/cnstlint/

Constraint bugs are particularly insidious because randomize() returns 1 even when the result is wrong — the solver satisfied some solution, just not the one you intended.

Rule IDBug it catches
CNST_DIST_ON_ENUM_TYPE_VLT Verilator does not reliably honour dist{} weights on enum-typed rand fields. Confirmed across 2000 randomization trials: declared weights 20/50/30 produced observed frequencies of 25/28/47.
CNST_NO_ENUM_IN_WITH_CLAUSE_VLT Comparing an enum literal inside a with (...) clause crashes Verilator with an internal compiler error. No graceful diagnostic — the build simply fails.
FUNC_CNST_WRONG_OPER_PRE SV operator precedence places == above ?:. Without explicit parentheses, lhs == expr ? a : b is parsed as (lhs == expr) ? a : b — a conditional whose condition is the equality check — not the likely intent. The solver satisfies the constraint in an unexpected way with no source-level indication.
CNST_ADDITIVE_OVERFLOW_IN_BOUND_VLT A + B <= MAX can silently pass when the sum wraps — SV constraint addition is performed in the operand's native width without automatic promotion. Applies to any simulator. Rearrange to A <= MAX - B to eliminate the addition.

FCOVLint — Functional Coverage Linting (20 rules)

Repo: github.com/AsFigo/fcovlint  |  Docs: asfigo.github.io/fcovlint/

Coverage holes and inflated coverage numbers are equally damaging. These rules catch patterns that cause covergroups to sample incorrectly or report misleading results.

Rule IDBug it catches
AF_FCOV_PERF_NO_IMPL_SAMP @(event) in the covergroup header couples sampling to the definition and removes precise control. Explicit cg.sample() calls let you decide exactly when a meaningful sample is recorded.
AF_FCOV_FUNC_PER_INSTANCE Without option.per_instance = 1, all instances share one coverage bucket. One busy instance can fill all bins while other instances are never exercised.
AF_FCOV_TRANS_MULTI_VAL_VLT Verilator supports only single-value transition bins. A comma-separated list on either side of => (e.g., (1,2 => 3,4)) is silently ignored — the bin never accumulates hits.
AF_FCOV_CLASS_MEM_REF_VLT Verilator does not support coverpoints that reference class member variables via the implicit this pointer. The coverpoint silently produces wrong results.
AF_FCOV_FUNC_NO_MERGE_INST type_option.merge_instances = 1 collapses per-instance coverage data into a single type-level report, masking per-instance holes — one active instance covers for all.

Getting Started

All three tools are open-source, MIT-licensed, and built on Verible — no proprietary parser or license required.

pip install anytree tomli
# Verible: https://github.com/chipsalliance/verible/releases

python3 bin/svalint.py  -t my_assertions.sv
python3 bin/cnstlint.py -t my_class.sv
python3 bin/fcovlint.py -t my_coverage.sv

Rules can be selectively disabled via a config.toml file. The full rule reference — rationale, violation examples, correct usage, and severity — is in the documentation linked above.

Contributions and feedback welcome via GitHub issues.


SVALint · CNSTLint · FCOVLint — AsFigo Technologies, UK  

Comments

Popular posts from this blog

Cracking the UVM-Verilator Code: 50+ IPs, AI Guardrails, and the Open-Source North Star

Open-Source Chip Design & Verification Bootcamp — Programme & Speakers

Call for Collaboration: Seeking Public UVM Environments for Verilator 5.0+ Porting