BTA Design Services | DV Environment
Created: October 2025
Version: 3.0-hierarchical
Updated: October 16, 2025
This is a modular, plugin-based linting framework that provides a flexible architecture for adding and managing multiple linters and rules.
- Modular Architecture - Each rule is in its own file
- Plugin System - Easy to add new linters
- Configuration-Driven - Enable/disable rules via JSON
- Unified Interface - Consistent API across all linters
- Per-Rule Configuration - Fine-grained control over severity and behavior
- Extensible - Simple to add custom rules and linters
| Variable | Meaning |
|---|---|
TB_LINT_PROJECT_CONFIG |
Path to a directory of project JSON configs (e.g. lint_config.json). If unset, tb_lint prints a warning (defaults to stdout, stderr when --json so stdout stays valid JSON) and falls back to <tb_lint_install>/configs. If set but not a directory, a warning is printed and the same default is used. |
When -c / --config is omitted, the root file $TB_LINT_PROJECT_CONFIG/lint_config.json is used if it exists; otherwise the bundled configs/lint_config.json. The “env not set” warning is shown once per run even if -c is passed (so the variable is still optional for path resolution when using an explicit config file).
tb_lint/
├── configs/ # Configuration files
│ ├── lint_config.json # Root config (default, hierarchical)
│ ├── naturaldocs.json # NaturalDocs linter config
│ └── verible.json # Verible linter config
│
├── core/ # Core framework
│ ├── __init__.py
│ ├── base_rule.py # Abstract base for rules
│ ├── base_linter.py # Abstract base for linters
│ ├── linter_registry.py # Plugin registry
│ └── config_manager.py # Configuration management (hierarchical)
│
├── linters/ # Linter implementations
│ ├── __init__.py
│ ├── naturaldocs_linter.py # NaturalDocs linter adapter (AST-based)
│ └── verible_linter.py # Verible linter adapter (external tool)
│
├── rules/ # Rule implementations
│ ├── naturaldocs/ # NaturalDocs rules (one per file)
│ │ ├── __init__.py
│ │ ├── file_header.py # File header rules
│ │ ├── include_guards.py # Include guard rules
│ │ ├── package_docs.py # Package documentation
│ │ ├── class_docs.py # Class documentation
│ │ ├── function_docs.py # Function documentation
│ │ ├── task_docs.py # Task documentation
│ │ ├── constraint_docs.py # Constraint documentation
│ │ ├── typedef_docs.py # Typedef documentation
│ │ ├── variable_docs.py # Variable documentation
│ │ └── parameter_docs.py # Parameter documentation
│ │
│ └── verible/ # Verible rules (external tool)
│ └── .gitkeep
│
├── example/ # Examples for developers
│ ├── example_custom_rule.py # Example: Custom rule
│ ├── example_custom_linter.py # Example: Custom linter
│ └── README.md # Developer guide
│
├── test/ # Test files
│ ├── good_example.sv # Valid SystemVerilog
│ ├── bad_example*.sv # Invalid SystemVerilog examples
│ ├── *_violations_test.sv # Intentional violation tests
│ ├── sv_files.txt # File list (8 files)
│ └── test_files.txt # File list (2 files)
│
├── tb_lint.py # Main entry point
├── run_all_tests.sh # One-line test runner
├── verible_verilog_syntax.py # Verible syntax wrapper
├── QUICKSTART.md # Quick start guide
├── ARCHITECTURE.md # Architecture documentation
├── README_MODULAR.md # Complete user guide (this file)
└── README.md # Main documentation index
# Run all linters on files
python3 tb_lint.py -f file_list.txt
# Run specific linter
python3 tb_lint.py --linter naturaldocs file.sv
# Use custom configuration
python3 tb_lint.py --config my_config.json -f files.txt
# Enable colored output
python3 tb_lint.py --color -f files.txt
# Strict mode (warnings as errors)
python3 tb_lint.py --strict -f files.txt
# JSON output
python3 tb_lint.py --json -f files.txt
# List available linters
python3 tb_lint.py --list-linterspython3 tb_lint.py -f files.txt -o results.txtThe framework supports two configuration approaches:
All settings in one file (lint_config_modular.json):
{
"project": {
"name": "Project Name",
"company": "Company Name"
},
"global": {
"strict_mode": false,
"use_color": true
},
"linters": {
"naturaldocs": {
"enabled": true,
"file_header": {
"company_pattern": "BTA",
"email_domain": "@btadesignservices.com"
},
"rules": {
"[ND_FILE_HDR_MISS]": {
"enabled": true,
"severity": "ERROR"
}
}
}
}
}Root config links to individual linter configs for better organization.
Root Config (lint_config_hierarchical.json):
{
"project": {
"name": "Design Verification Environment",
"company": "BTA Design Services"
},
"global": {
"strict_mode": false,
"use_color": true
},
"linters": {
"naturaldocs": {
"enabled": true,
"config_file": "configs/naturaldocs.json"
},
"verible": {
"enabled": false,
"config_file": "configs/verible.json"
}
}
}Individual Linter Config (configs/naturaldocs.json):
{
"file_header": {
"company_pattern": "BTA",
"email_domain": "@btadesignservices.com"
},
"rules": {
"[ND_FILE_HDR_MISS]": {
"enabled": true,
"severity": "ERROR"
},
"[ND_CLASS_MISS]": {
"enabled": true,
"severity": "WARNING"
}
}
}Benefits of Hierarchical:
- Easy to enable/disable linters from root config
- Separate linter-specific settings into dedicated files
- Teams can customize individual linters independently
- Better organization for large projects
- Settings from linked configs are merged automatically
Use a shared base config with a project config that overrides it.
Common config (lint_config.common.json):
{
"project": {
"name": "<PROJECT_NAME>",
"description": "<PROJECT_DESCRIPTION>"
},
"global": {
"strict_mode": false,
"use_color": true,
"exclude_paths": []
},
"linters": {
"naturaldocs": {
"enabled": true,
"config_file": "configs/naturaldocs.json"
},
"verible": {
"enabled": true,
"config_file": "configs/verible.json"
}
}
}Project config (lint_config.project.json):
{
"extends": "lint_config.common.json",
"project": {
"name": "my_project",
"description": "Project-specific tb_lint config"
},
"global": {
"exclude_paths": [
"dv/tb_common/regmap/sv/uvmreg"
]
}
}CLI alternative:
python3 tb_lint.py --base-config lint_config.common.json --config lint_config.project.json -f files.txtMerge semantics (project precedence):
- Dict values are merged recursively.
- Scalar values (string/number/bool) from project replace common values.
- Lists from project replace common lists.
- Missing project keys are inherited from common.
Each rule can be configured independently:
"rules": {
"[ND_FILE_HDR_MISS]": {
"enabled": true, // Enable/disable this rule
"severity": "ERROR" // ERROR, WARNING, or INFO
},
"[ND_CLASS_MISS]": {
"enabled": false, // Disable this rule
"severity": "WARNING"
}
}Create a new file in rules/naturaldocs/my_new_rule.py:
"""
My new rule description
Company: BTA Design Services
"""
from core.base_rule import BaseRule, RuleViolation, RuleSeverity
class MyNewRule(BaseRule):
"""Check for my custom requirement"""
@property
def rule_id(self) -> str:
return "[ND_MY_RULE]"
@property
def description(self) -> str:
return "Description of what this rule checks"
def default_severity(self) -> RuleSeverity:
return RuleSeverity.ERROR
def check(self, file_path: str, file_content: str, context: any) -> list:
"""Implement your checking logic here"""
violations = []
# Your checking logic
if some_condition:
violations.append(self.create_violation(
file_path=file_path,
line=line_number,
message="Violation message"
))
return violationsAdd to rules/naturaldocs/__init__.py:
from .my_new_rule import MyNewRule
__all__ = [..., 'MyNewRule']Add to linters/naturaldocs_linter.py in _register_rules():
def _register_rules(self):
# ... existing rules ...
self.add_rule(MyNewRule())Add to lint_config_modular.json:
"rules": {
"[ND_MY_RULE]": {
"enabled": true,
"severity": "ERROR"
}
}That's it! Your new rule is now integrated.
Create linters/my_linter.py:
"""
My Custom Linter
Company: BTA Design Services
"""
from core.base_linter import BaseLinter
from core.linter_registry import register_linter
@register_linter
class MyLinter(BaseLinter):
"""My custom linter implementation"""
@property
def name(self) -> str:
return "mylinter"
@property
def supported_extensions(self) -> list:
return ['.sv', '.svh']
def _register_rules(self):
"""Register rules for this linter"""
# Add your rules here
self.add_rule(MyRule1())
self.add_rule(MyRule2())
def prepare_context(self, file_path: str, file_content: str):
"""Prepare context for rules (e.g., parse AST)"""
# Your preparation logic
return context_objectAdd to linters/__init__.py:
from .my_linter import MyLinter
__all__ = [..., 'MyLinter']Add to lint_config_modular.json:
"linters": {
"mylinter": {
"enabled": true,
"rules": {
// Your linter-specific rules
}
}
}The linter is automatically discovered and can be used immediately!
================================================================================
Running naturaldocs linter...
================================================================================
File: my_file.sv
my_file.sv:1:0: [ND_FILE_HDR_MISS] ERROR: Missing 'File:' keyword in header
my_file.sv:45:0: [ND_CLASS_MISS] WARNING: Class 'my_class' without 'Class:' documentation
================================================================================
naturaldocs Summary
================================================================================
Files checked: 1
Errors: 1
Warnings: 1
Info: 0
{
"linters": {
"naturaldocs": {
"files_checked": 1,
"files_failed": 0,
"errors": 1,
"warnings": 1,
"info": 0,
"violations": [
{
"file": "my_file.sv",
"line": 1,
"column": 0,
"severity": "ERROR",
"message": "Missing 'File:' keyword in header",
"rule_id": "[ND_FILE_HDR_MISS]"
}
]
}
},
"summary": {
"total_files_checked": 1,
"total_errors": 1,
"total_warnings": 1
}
}Rules follow a consistent naming pattern:
- Prefix:
[ND_*]for NaturalDocs,[VB_*]for Verible - Format:
[PREFIX_CATEGORY_TYPE] - Examples:
[ND_FILE_HDR_MISS]- NaturalDocs file header missing[ND_CLASS_MISS]- NaturalDocs class documentation missing[VB_LINE_LENGTH]- Verible line length violation
# Create a simple test script
from rules.naturaldocs.file_header import FileHeaderRule
rule = FileHeaderRule({'company_pattern': 'BTA'})
violations = rule.check('test.sv', file_content, None)
print(f"Found {len(violations)} violations")from linters.naturaldocs_linter import NaturalDocsLinter
config = {'file_header': {'company_pattern': 'BTA'}}
linter = NaturalDocsLinter(config)
result = linter.lint_file('test.sv')
print(f"Errors: {result.error_count}, Warnings: {result.warning_count}")All rules inherit from BaseRule:
class MyRule(BaseRule):
@property
def rule_id(self) -> str:
"""Unique identifier like '[ND_MY_RULE]'"""
@property
def description(self) -> str:
"""Human-readable description"""
def default_severity(self) -> RuleSeverity:
"""Default severity (ERROR, WARNING, INFO)"""
def check(self, file_path, file_content, context) -> List[RuleViolation]:
"""Implement checking logic"""All linters inherit from BaseLinter:
class MyLinter(BaseLinter):
@property
def name(self) -> str:
"""Linter name"""
@property
def supported_extensions(self) -> List[str]:
"""File extensions this linter handles"""
def _register_rules(self):
"""Register rules: self.add_rule(MyRule())"""
def prepare_context(self, file_path, file_content):
"""Prepare context for rules (e.g., AST)"""- Single Responsibility - Each rule checks one thing
- Clear Messages - Violation messages should be actionable
- Efficient - Avoid redundant parsing or processing
- Configurable - Use config for thresholds and patterns
- Well-Documented - Include docstrings and comments
- Reuse Core - Leverage base classes and utilities
- Error Handling - Gracefully handle parse failures
- Performance - Cache expensive operations
- Testing - Test with various file types
- Configuration - Support flexible configuration
- Defaults - Provide sensible defaults
- Documentation - Document all config options
- Validation - Validate config on load
- Versioning - Track config format versions
- Examples - Provide example configurations
- Legacy Documentation:
README.md - Configuration:
lint_config_modular.json - Core Framework:
core/ - Rules:
rules/naturaldocs/ - Linters:
linters/
Copyright (c) 2025 BTA Design Services | October 2025 Project: DV Environment Version: 2.0-modular