mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Enhanced the RTL style checker script to include new diagnostic rules for function declarations, primary design units, and synthesis exclusions. - Added functions to handle audit issues, ensuring that the audit records match the current inventory of RTL files. - Updated test cases to reflect changes in RTL file names and paths, ensuring consistency with the new naming conventions. - Introduced additional tests for style violations, including mismatched primary design units and synthesizable initial blocks. - Adjusted file paths in various test files to align with the new RTL structure and naming conventions.
279 lines
8.3 KiB
Python
279 lines
8.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT = ROOT / "scripts/check_rtl_style.py"
|
|
AUDIT = ROOT / "rtl/rtl_style_audit.json"
|
|
|
|
|
|
def run_style(
|
|
tmp_path: Path, source: str, *, enforce_naming: bool = False
|
|
) -> subprocess.CompletedProcess[str]:
|
|
(tmp_path / "rtl/ip/test.sv").parent.mkdir(parents=True)
|
|
(tmp_path / "rtl/ip/test.sv").write_text(source, encoding="utf-8")
|
|
(tmp_path / "rtl/rtl_style_manifest.json").write_text(
|
|
json.dumps(
|
|
{
|
|
"schema_version": 1,
|
|
"profiles": {
|
|
"owned": {
|
|
"roots": ["rtl/ip"],
|
|
"suffixes": [".sv"],
|
|
"format": "verible",
|
|
"lint": "strict",
|
|
}
|
|
},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
subprocess.run(["git", "init", "-q", str(tmp_path)], check=True)
|
|
subprocess.run(["git", "-C", str(tmp_path), "add", "."], check=True)
|
|
command = [sys.executable, str(SCRIPT), "--root", str(tmp_path), "--profile", "owned"]
|
|
if enforce_naming:
|
|
command.append("--enforce-naming")
|
|
return subprocess.run(
|
|
command,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def test_owned_style_rejects_positional_connections(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" dffr #(1) u_reg (clk_i, 1'b1, 1'b0, data_o);\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "module-port" in result.stderr
|
|
|
|
|
|
def test_owned_style_allows_named_connections(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" dffr #(1) u_reg (.clk_i(clk_i), .rst_n_i(1'b1), .dat_i(1'b0), .dat_o(data_o));\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_owned_style_rejects_forbidden_constructs(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" always_comb begin\n"
|
|
" data_o = 1'b0;\n"
|
|
" casex (data_o)\n"
|
|
" 1'bx: data_o = 1'b1;\n"
|
|
" endcase\n"
|
|
" end\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "casex" in result.stderr
|
|
|
|
|
|
def test_owned_style_rejects_non_ascii_and_long_lines(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" logic data_q;\n"
|
|
" // 非 ASCII comment\n"
|
|
" assign data_o = data_q; // " + ("x" * 101) + "\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "non-ASCII" in result.stderr
|
|
assert "100 characters" in result.stderr
|
|
|
|
|
|
def test_owned_style_enforces_staged_naming_contract(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"`define BAD_MACRO 1\n"
|
|
"module badModule(\n"
|
|
" input logic clk,\n"
|
|
" output logic data_o\n"
|
|
");\n"
|
|
" logic s_state_q;\n"
|
|
" bad_if if0 ();\n"
|
|
"endmodule\n",
|
|
enforce_naming=True,
|
|
)
|
|
assert result.returncode == 1
|
|
assert "module name" in result.stderr
|
|
assert "port 'clk'" in result.stderr
|
|
assert "interface instance" in result.stderr
|
|
assert "macro 'BAD_MACRO'" in result.stderr
|
|
|
|
|
|
def test_owned_style_accepts_project_naming_contract(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"`define RETROSOC_TEST__VALUE 1\n"
|
|
"module test(\n"
|
|
" input logic clk_i,\n"
|
|
" output logic data_o\n"
|
|
");\n"
|
|
" logic s_state_d;\n"
|
|
" logic s_state_q;\n"
|
|
" test_if u_test_if ();\n"
|
|
"endmodule\n",
|
|
enforce_naming=True,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_owned_style_allows_protocol_localparams(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test #(parameter int DataWidth = 32) ("
|
|
"input logic clk_i, output logic data_o);\n"
|
|
" localparam logic [1:0] FSM_IDLE = 2'd0;\n"
|
|
" localparam logic [1:0] FSM_RESP = 2'd1;\n"
|
|
" assign data_o = clk_i;\n"
|
|
"endmodule\n",
|
|
enforce_naming=True,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_owned_style_allows_interface_members_and_include_guards(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"`ifndef GPIO_DEFINE_SVH\n"
|
|
"`define GPIO_DEFINE_SVH\n"
|
|
"interface test ();\n"
|
|
" logic cmd_valid;\n"
|
|
" modport dut(input cmd_valid);\n"
|
|
"endinterface\n"
|
|
"`endif\n",
|
|
enforce_naming=True,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_owned_style_rejects_mismatched_primary_design_unit(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module wrong_name(input logic clk_i, output logic data_o);\n"
|
|
" assign data_o = clk_i;\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "RTL-FILE-002" in result.stderr
|
|
|
|
|
|
def test_owned_style_rejects_synthesizable_initial_block(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" initial begin\n"
|
|
" data_o = clk_i;\n"
|
|
" end\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "RTL-SV-007" in result.stderr
|
|
|
|
|
|
def test_owned_style_allows_verification_only_initial_block(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" assign data_o = clk_i;\n"
|
|
"`ifndef SYNTHESIS\n"
|
|
" initial begin\n"
|
|
" if (clk_i) $error(\"unexpected clock\");\n"
|
|
" end\n"
|
|
"`endif\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_owned_style_rejects_unpaired_format_directive(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" // verilog_format: on\n"
|
|
" assign data_o = clk_i;\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "RTL-FMT-005" in result.stderr
|
|
|
|
|
|
def test_owned_style_requires_formatter_exception_rationale(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" // verilog_format: off\n"
|
|
" assign data_o = clk_i;\n"
|
|
" // verilog_format: on\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "RTL-FMT-007" in result.stderr
|
|
|
|
|
|
def test_owned_style_requires_automatic_typed_functions(tmp_path: Path) -> None:
|
|
result = run_style(
|
|
tmp_path,
|
|
"module test(input logic clk_i, output logic data_o);\n"
|
|
" function logic invert(input logic value_i);\n"
|
|
" return !value_i;\n"
|
|
" endfunction\n"
|
|
" assign data_o = invert(clk_i);\n"
|
|
"endmodule\n",
|
|
)
|
|
assert result.returncode == 1
|
|
assert "RTL-SV-008" in result.stderr
|
|
|
|
|
|
def test_owned_style_audit_matches_current_inventory() -> None:
|
|
audit = json.loads(AUDIT.read_text(encoding="utf-8"))
|
|
assert audit["schema_version"] == 1
|
|
assert audit["policy"] == "docs/rtl-coding-style.md"
|
|
assert audit["profile"] == "owned"
|
|
assert {
|
|
"owner",
|
|
"related_commit",
|
|
"expiry",
|
|
"removal_plan",
|
|
}.issubset(audit["reviewed_boundary_record"])
|
|
|
|
audited_paths = {
|
|
str(Path(directory) / name)
|
|
for directory, names in audit["inventory"].items()
|
|
for name in names
|
|
}
|
|
actual_paths = {
|
|
str(path.relative_to(ROOT))
|
|
for root in (ROOT / "rtl/ip", ROOT / "rtl/mini/top")
|
|
for path in root.rglob("*")
|
|
if path.is_file() and path.suffix in {".sv", ".svh"}
|
|
}
|
|
assert audited_paths == actual_paths
|
|
assert {
|
|
"RTL-FILE",
|
|
"RTL-SV",
|
|
"RTL-NAME",
|
|
"RTL-STRUCT",
|
|
"RTL-COMB",
|
|
"RTL-SEQ",
|
|
"RTL-WIDTH",
|
|
"RTL-CDC",
|
|
"RTL-VERIFY",
|
|
"RTL-FMT",
|
|
}.issubset({rule["id"] for rule in audit["rules"]})
|