mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
- Introduced common setup script for Yosys () to manage global variables and configurations. - Added output filtering script () to enhance log readability. - Created technology initialization script () for handling different PDKs. - Developed main synthesis script () to orchestrate the synthesis flow using Yosys. - Added a configuration makefile () for synthesis settings. - Updated Yosys makefile () to integrate new scripts and configurations. - Adjusted paths in and RTL filelists to reflect new directory structure under . - Modified various scripts and test files to accommodate the new PDK paths and ensure compatibility. - Enhanced warning normalization in the analysis script to preserve PDK signatures.
196 lines
7.5 KiB
Python
196 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import posixpath
|
|
import re
|
|
import sys
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
from scripts.setup_helpers import atomic_write # noqa: E402
|
|
|
|
|
|
WARNING_TOOLS = ("iverilog", "verilator", "rtl-lint", "vcs", "yosys", "opensta")
|
|
|
|
|
|
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
|
TIMESTAMP_RE = re.compile(r"^\[[^]]+\]\s*")
|
|
LOCATION_RE = re.compile(
|
|
r"(?P<path>(?:[A-Za-z]:)?[^\s:]+?\."
|
|
r"(?:c|cc|cpp|cxx|h|hpp|py|sv|svh|tcl|v|vh)):\d+(?::\d+)?",
|
|
re.IGNORECASE,
|
|
)
|
|
GENERATED_MPW_PATH_RE = re.compile(
|
|
r"\$BUILD/generated/mpw/[A-Za-z0-9_-]+/(?P<tree>core|ip)/(?P<path>[^\s:]+)"
|
|
)
|
|
MPW_INSTANCE_FILE_SUFFIX_RE = re.compile(r"_username[0-9]+(?=\.(?:sv|svh|v|vh)$)")
|
|
|
|
|
|
def warning_line(tool: str, line: str) -> tuple[str, str] | None:
|
|
patterns = {
|
|
"verilator": re.compile(r"%Warning-([A-Z0-9_]+):\s*(.*)"),
|
|
"rtl-lint": re.compile(r"%Warning-([A-Z0-9_]+):\s*(.*)"),
|
|
"iverilog": re.compile(r"\b(warning|sorry):\s*(.*)", re.IGNORECASE),
|
|
"yosys": re.compile(r"\b(?:ABC:\s*)?Warning:\s*(.*)", re.IGNORECASE),
|
|
"opensta": re.compile(r"\bWarning\s+(\d+):\s*(.*)", re.IGNORECASE),
|
|
"vcs": re.compile(r"\bWarning(?:-\[([^]]+)\])?:\s*(.*)", re.IGNORECASE),
|
|
}
|
|
match = patterns[tool].search(line)
|
|
if not match:
|
|
return None
|
|
if tool in ("verilator", "rtl-lint", "opensta"):
|
|
return match.group(1), match.group(2)
|
|
if tool == "iverilog":
|
|
return match.group(1).lower(), match.group(2)
|
|
if tool == "vcs":
|
|
return match.group(1) or "warning", match.group(2)
|
|
return "warning", match.group(1)
|
|
|
|
|
|
def normalize(root: Path, identifier: str, message: str) -> str:
|
|
value = ANSI_RE.sub("", TIMESTAMP_RE.sub("", message.strip()))
|
|
resolved_root = str(root.resolve())
|
|
value = re.sub(
|
|
re.escape(resolved_root) + r"/build/[^/\s:]+",
|
|
"$BUILD",
|
|
value,
|
|
)
|
|
value = value.replace(resolved_root, "$ROOT")
|
|
value = value.replace("$ROOT/physical/pdk/", "$ROOT/pdk/")
|
|
value = GENERATED_MPW_PATH_RE.sub(_normalize_generated_mpw_path, value)
|
|
value = LOCATION_RE.sub(lambda match: match.group("path") + ":<line>", value)
|
|
value = re.sub(r"\s+", " ", value)
|
|
return f"{identifier}:{value}"
|
|
|
|
|
|
def _normalize_generated_mpw_path(match: re.Match[str]) -> str:
|
|
"""Map isolated MPW copies back to their managed source locations."""
|
|
relative_path = posixpath.normpath(match.group("path"))
|
|
relative_path = MPW_INSTANCE_FILE_SUFFIX_RE.sub("", relative_path)
|
|
return f"$ROOT/rtl/managed/mpw/{match.group('tree')}/{relative_path}"
|
|
|
|
|
|
def scan(tool: str, logs: list[Path], root: Path) -> dict[str, object]:
|
|
counts: Counter[str] = Counter()
|
|
examples: dict[str, str] = {}
|
|
for log in logs:
|
|
if not log.is_file():
|
|
continue
|
|
for line in log.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
parsed = warning_line(tool, line)
|
|
if parsed is None:
|
|
continue
|
|
normalized = normalize(root, *parsed)
|
|
signature = hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:16]
|
|
counts[signature] += 1
|
|
examples.setdefault(signature, normalized)
|
|
entries = [
|
|
{"signature": signature, "count": count, "example": examples[signature]}
|
|
for signature, count in sorted(counts.items())
|
|
]
|
|
return {"tool": tool, "logs": [str(path) for path in logs], "entries": entries}
|
|
|
|
|
|
def discover(variant_root: Path) -> dict[str, list[Path]]:
|
|
return {
|
|
"iverilog": sorted((variant_root / "sim/iverilog").glob("*/*.log")),
|
|
"verilator": sorted((variant_root / "sim/verilator").glob("*.log")),
|
|
"rtl-lint": sorted((variant_root / "lint/verilator").glob("lint.log")),
|
|
"vcs": sorted((variant_root / "sim/vcs").glob("*/*.log")),
|
|
"yosys": sorted((variant_root / "syn/yosys").glob("*.log")),
|
|
"opensta": sorted((variant_root / "sta/opensta").glob("*.log")),
|
|
}
|
|
|
|
|
|
def write_baseline(args: argparse.Namespace) -> int:
|
|
result = scan(args.tool, [path.resolve() for path in args.log], args.root.resolve())
|
|
baseline = {
|
|
"schema_version": 1,
|
|
"profile": args.profile,
|
|
"tool": args.tool,
|
|
"entries": result["entries"],
|
|
}
|
|
atomic_write(args.output, json.dumps(baseline, indent=2, sort_keys=True) + "\n")
|
|
print(f"warning baseline: {args.output.resolve()}")
|
|
return 0
|
|
|
|
|
|
def check(args: argparse.Namespace) -> int:
|
|
root = args.root.resolve()
|
|
variant_root = args.variant_root.resolve()
|
|
tools = [args.tool] if args.tool else WARNING_TOOLS
|
|
logs = discover(variant_root)
|
|
reports: dict[str, object] = {}
|
|
failures: list[str] = []
|
|
for tool in tools:
|
|
if not logs[tool]:
|
|
reports[tool] = {"status": "not_run", "entries": []}
|
|
continue
|
|
current = scan(tool, logs[tool], root)
|
|
baseline_path = root / "quality/warnings" / args.profile / f"{tool}.json"
|
|
baseline_entries: dict[str, int] = {}
|
|
if baseline_path.is_file():
|
|
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
|
baseline_entries = {entry["signature"]: entry["count"] for entry in baseline["entries"]}
|
|
current_entries = {entry["signature"]: entry["count"] for entry in current["entries"]}
|
|
new = sorted(set(current_entries) - set(baseline_entries))
|
|
increased = sorted(
|
|
signature
|
|
for signature, count in current_entries.items()
|
|
if signature in baseline_entries and count > baseline_entries[signature]
|
|
)
|
|
if new or increased:
|
|
failures.append(tool)
|
|
reports[tool] = {
|
|
**current,
|
|
"status": "failed" if new or increased else "passed",
|
|
"baseline": str(baseline_path),
|
|
"new_signatures": new,
|
|
"increased_signatures": increased,
|
|
"resolved_signatures": sorted(set(baseline_entries) - set(current_entries)),
|
|
}
|
|
output = {
|
|
"schema_version": 1,
|
|
"profile": args.profile,
|
|
"status": "failed" if failures else "passed",
|
|
"failed_tools": failures,
|
|
"tools": reports,
|
|
}
|
|
atomic_write(args.output, json.dumps(output, indent=2, sort_keys=True) + "\n")
|
|
print(f"warning check: {output['status']} ({args.output.resolve()})")
|
|
return int(bool(failures))
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="Manage normalized EDA warning baselines")
|
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
baseline = subparsers.add_parser("baseline")
|
|
baseline.add_argument("--root", type=Path, default=ROOT)
|
|
baseline.add_argument("--profile", required=True)
|
|
baseline.add_argument("--tool", choices=WARNING_TOOLS, required=True)
|
|
baseline.add_argument("--log", type=Path, action="append", required=True)
|
|
baseline.add_argument("--output", type=Path, required=True)
|
|
check_parser = subparsers.add_parser("check")
|
|
check_parser.add_argument("--root", type=Path, default=ROOT)
|
|
check_parser.add_argument("--profile", required=True)
|
|
check_parser.add_argument("--variant-root", type=Path, required=True)
|
|
check_parser.add_argument("--tool", choices=WARNING_TOOLS)
|
|
check_parser.add_argument("--output", type=Path, required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
return write_baseline(args) if args.command == "baseline" else check(args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|