chipgraph 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""Read a failing log into structured facts: issues, check results, simulation failures.
|
|
2
|
+
|
|
3
|
+
Three kinds of input are understood, and may be mixed:
|
|
4
|
+
|
|
5
|
+
- a tool log (Verilator and other HDL tools, `make`, a shell): issues come from the
|
|
6
|
+
existing log parsers (`VerilatorParser`, and a `GenericRegexParser` for the common
|
|
7
|
+
`file:line: [error:] message` shape), plus located-less `%Error:` lines;
|
|
8
|
+
- the text output of `chipgraph check` (`FAIL <check> <block> N issues` and indented
|
|
9
|
+
`file:line [rule] message` lines), or its `--json` output (`CheckResult` objects);
|
|
10
|
+
- a simulation run: runtime messages (`[time] %Error`, `Verilog $stop`, `Assertion
|
|
11
|
+
failed`, `UVM_ERROR`, ...), a self-checking testbench's failure lines (`FAIL ...`,
|
|
12
|
+
`... expected X got Y`), and the messages of its failed assertions (a testbench that
|
|
13
|
+
reports with `$error` or `assert` rather than a `FAIL` line).
|
|
14
|
+
|
|
15
|
+
Nothing here decides a class; `rules` does, from these facts.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
|
|
24
|
+
from chipgraph.adapters.parser import GenericRegexParser, VerilatorParser
|
|
25
|
+
from chipgraph.core.contracts import CheckResult, Issue
|
|
26
|
+
from chipgraph.core.contracts.types import CheckStatus
|
|
27
|
+
|
|
28
|
+
MAX_EXCERPT_LINES = 60
|
|
29
|
+
MAX_EXCERPT_CHARS = 4000
|
|
30
|
+
FIRST_ERROR_LINES = 8
|
|
31
|
+
|
|
32
|
+
_ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]")
|
|
33
|
+
|
|
34
|
+
_CHECK_LINE = re.compile(
|
|
35
|
+
r"^(?P<status>PASS|FAIL|ERROR|SKIPPED)\s+(?P<check>\S+)\s+(?P<block>\S+)\s+\d+ issues?\s*$"
|
|
36
|
+
)
|
|
37
|
+
_CHECK_ISSUE = re.compile(
|
|
38
|
+
r"^\s{2,}(?P<file>\S+?):(?P<line>\d+|-)\s{2}\[(?P<rule>[^\]]*)\]\s(?P<msg>.*)$"
|
|
39
|
+
)
|
|
40
|
+
_STATUS: dict[str, CheckStatus] = {
|
|
41
|
+
"PASS": "pass",
|
|
42
|
+
"FAIL": "fail",
|
|
43
|
+
"ERROR": "error",
|
|
44
|
+
"SKIPPED": "skipped",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_HDL_LINE = (
|
|
48
|
+
r"^(?P<file>[^\s:'\"]+\.(?:sv|svh|v|vh|vhd|vhdl)):(?P<line>\d+):(?:\d+:)?\s*"
|
|
49
|
+
r"(?:(?P<severity>[Ee]rror|[Ww]arning)\s*:?\s*)?(?P<msg>\S.*)$"
|
|
50
|
+
)
|
|
51
|
+
"""`file:line[:col]: [error|warning:] message`, as iverilog, slang, verible print it."""
|
|
52
|
+
|
|
53
|
+
_UNLOCATED = re.compile(r"^\s*%(?P<kind>Error|Fatal)(?:-(?P<rule>[A-Za-z0-9_]+))?:\s+(?P<msg>.*)$")
|
|
54
|
+
_LOCATED = re.compile(r"^[^\s:]+:\d+(?::\d+)?:")
|
|
55
|
+
_SUMMARY = re.compile(r"^Exiting due to\b")
|
|
56
|
+
|
|
57
|
+
_RUNTIME = re.compile(
|
|
58
|
+
r"^\s*\[\s*\d+(?:\.\d+)?\s*(?:[a-z]?s)?\]\s*%(?:Error|Fatal|Warning)"
|
|
59
|
+
r"|Verilog \$(?:stop|fatal)\b"
|
|
60
|
+
r"|\bAssertion failed\b"
|
|
61
|
+
r"|^\s*- S i m u l a t i o n\b"
|
|
62
|
+
r"|\bUVM_(?:ERROR|FATAL)\b"
|
|
63
|
+
r"|^\s*\*\* (?:Error|Fatal):.*\bTime:"
|
|
64
|
+
r"|\$finish called\b"
|
|
65
|
+
)
|
|
66
|
+
"""Messages only a running simulation prints (not a compiler or a linter)."""
|
|
67
|
+
|
|
68
|
+
_SIM_FAILURE = re.compile(
|
|
69
|
+
r"^\s*(?:\[[^\]]*\]\s*)?(?:FAIL(?:ED|URE)?|MISMATCH)\b"
|
|
70
|
+
r"|(?i:\bexpected\b.*\b(?:got|actual|but (?:got|was|read))\b)"
|
|
71
|
+
r"|\bUVM_(?:ERROR|FATAL)\b(?!\s*:\s*0\b)"
|
|
72
|
+
)
|
|
73
|
+
"""A self-checking testbench's failure line."""
|
|
74
|
+
|
|
75
|
+
_ASSERTION_FAILED = re.compile(r"\bAssertion failed in (?P<scope>[\w.$]+)\s*:\s*(?P<msg>.*\S)")
|
|
76
|
+
_RUNTIME_ERROR = re.compile(
|
|
77
|
+
r"^\s*\[[^\]]*\]\s*%(?:Error|Fatal)(?:-[A-Za-z0-9_]+)?:\s*(?:[^\s:]+:\d+(?::\d+)?:\s*)?"
|
|
78
|
+
r"(?P<msg>.*\S)"
|
|
79
|
+
r"|^\s*\*\* (?:Error|Fatal):\s*(?:[^\s:()]+\(\d+\):\s*)?(?P<msg2>.*?\S)\s*(?:\bTime:.*)?$"
|
|
80
|
+
)
|
|
81
|
+
"""A running simulation's assertion or `$error`/`$fatal` message: Verilator's `Assertion
|
|
82
|
+
failed in <scope>: <message>`, a time-stamped `%Error:`, a `** Error: ... Time:` (not the
|
|
83
|
+
`Verilog $stop` line after it)."""
|
|
84
|
+
|
|
85
|
+
_NO_FACT = frozenset(
|
|
86
|
+
{"assert", "assertion", "check", "checks", "error", "fail", "failed", "failure", "test"}
|
|
87
|
+
)
|
|
88
|
+
"""Words that say a check failed, not what failed."""
|
|
89
|
+
|
|
90
|
+
_WORDS = re.compile(r"\w+")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _assertion_message(line: str) -> str | None:
|
|
94
|
+
"""The message of a simulation assertion line, or None for none or one that only
|
|
95
|
+
says that something failed (`<tb>: 1 check(s) failed`, `'assert' failed`)."""
|
|
96
|
+
if re.search(r"Verilog \$(?:stop|fatal)\b|\$finish called\b", line):
|
|
97
|
+
return None
|
|
98
|
+
scope = ""
|
|
99
|
+
match = _ASSERTION_FAILED.search(line)
|
|
100
|
+
if match is not None:
|
|
101
|
+
scope, msg = match.group("scope"), match.group("msg")
|
|
102
|
+
else:
|
|
103
|
+
match = _RUNTIME_ERROR.search(line)
|
|
104
|
+
if match is None:
|
|
105
|
+
return None
|
|
106
|
+
msg = match.group("msg") or match.group("msg2") or ""
|
|
107
|
+
msg = msg.strip()
|
|
108
|
+
scope_words = {w.lower() for w in _WORDS.findall(scope)}
|
|
109
|
+
facts = [
|
|
110
|
+
w
|
|
111
|
+
for w in _WORDS.findall(msg)
|
|
112
|
+
if len(w) > 1
|
|
113
|
+
and not w.isdigit()
|
|
114
|
+
and w.lower() not in _NO_FACT
|
|
115
|
+
and w.lower() not in scope_words
|
|
116
|
+
]
|
|
117
|
+
return msg if facts else None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
_ERRORISH = re.compile(
|
|
121
|
+
r"%(?:Error|Fatal|Warning)|\berror\b|\bFAIL|\bERROR\b|make: \*\*\*|not found"
|
|
122
|
+
r"|No such file|timed out|Killed|Permission denied|expected\b.*\bgot\b",
|
|
123
|
+
re.IGNORECASE,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
_NOISE = re.compile(
|
|
127
|
+
r"^\s*(?::\s*)?\.\.\. (?:For (?:warning|error) description see|Use \"/\* verilator"
|
|
128
|
+
r"|See the manual at)"
|
|
129
|
+
)
|
|
130
|
+
"""Verilator's hint lines: links and how to waive, never what failed."""
|
|
131
|
+
|
|
132
|
+
_HDL_PARSER = GenericRegexParser(pattern=_HDL_LINE, severity_group="severity")
|
|
133
|
+
_VERILATOR_PARSER = VerilatorParser()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class CheckRun:
|
|
138
|
+
"""One `chipgraph check` result found in the log."""
|
|
139
|
+
|
|
140
|
+
check_id: str
|
|
141
|
+
block: str | None
|
|
142
|
+
status: CheckStatus
|
|
143
|
+
issues: tuple[Issue, ...] = ()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class ParsedLog:
|
|
148
|
+
"""What `parse_log` found in a log."""
|
|
149
|
+
|
|
150
|
+
text: str
|
|
151
|
+
"""The log as read (ANSI colours removed)."""
|
|
152
|
+
tool_issues: tuple[Issue, ...] = ()
|
|
153
|
+
"""Located compile/lint issues printed by the tools (not by a running simulation)."""
|
|
154
|
+
unlocated_errors: tuple[str, ...] = ()
|
|
155
|
+
"""`%Error:` messages that name no file:line (summary lines left out)."""
|
|
156
|
+
runtime_issues: tuple[Issue, ...] = ()
|
|
157
|
+
"""Located messages of a running simulation (`Verilog $stop`, assertion failures)."""
|
|
158
|
+
checks: tuple[CheckRun, ...] = ()
|
|
159
|
+
"""`chipgraph check` results found in the log (text or JSON)."""
|
|
160
|
+
sim_failures: tuple[str, ...] = ()
|
|
161
|
+
"""A self-checking testbench's failure lines."""
|
|
162
|
+
sim_assertions: tuple[str, ...] = ()
|
|
163
|
+
"""The messages of a running simulation's failed assertions and `$error`/`$fatal`
|
|
164
|
+
calls (`Assertion failed in <scope>: <message>` gives `<message>`), without the
|
|
165
|
+
ones that only say that something failed."""
|
|
166
|
+
runtime_lines: tuple[str, ...] = ()
|
|
167
|
+
"""Lines only a running simulation prints."""
|
|
168
|
+
first_error: str = ""
|
|
169
|
+
"""The first error line and the lines after it."""
|
|
170
|
+
excerpt: str = ""
|
|
171
|
+
"""The log shortened for a model: hint lines dropped, long logs cut around errors."""
|
|
172
|
+
lines: tuple[str, ...] = field(default=(), repr=False)
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def sim_ran(self) -> bool:
|
|
176
|
+
"""True when the log shows a simulation that ran (and failed a check)."""
|
|
177
|
+
return bool(self.runtime_lines or self.sim_failures)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def failing_checks(self) -> tuple[CheckRun, ...]:
|
|
181
|
+
"""The check results that did not pass: `fail` or `error`."""
|
|
182
|
+
return tuple(c for c in self.checks if c.status in ("fail", "error"))
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def issues(self) -> tuple[Issue, ...]:
|
|
186
|
+
"""Every located issue: the tools', then the failing checks', then the runtime's."""
|
|
187
|
+
out = list(self.tool_issues)
|
|
188
|
+
for check in self.failing_checks:
|
|
189
|
+
out.extend(check.issues)
|
|
190
|
+
out.extend(self.runtime_issues)
|
|
191
|
+
return tuple(out)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _json_checks(text: str) -> tuple[tuple[CheckRun, ...], str] | None:
|
|
195
|
+
"""`CheckResult` JSON (one object or a list): the runs and their log tails."""
|
|
196
|
+
stripped = text.strip()
|
|
197
|
+
if not stripped or stripped[0] not in "[{":
|
|
198
|
+
return None
|
|
199
|
+
try:
|
|
200
|
+
data = json.loads(stripped)
|
|
201
|
+
except ValueError:
|
|
202
|
+
return None
|
|
203
|
+
# One CheckResult, a list of them (`chipgraph check --json`), or the MCP `check`
|
|
204
|
+
# tool's `{"ok": ..., "results": [...]}`.
|
|
205
|
+
pending: list[object] = list(data) if isinstance(data, list) else [data]
|
|
206
|
+
runs: list[CheckRun] = []
|
|
207
|
+
tails: list[str] = []
|
|
208
|
+
while pending:
|
|
209
|
+
item = pending.pop(0)
|
|
210
|
+
if isinstance(item, dict) and isinstance(item.get("results"), list):
|
|
211
|
+
pending[:0] = item["results"]
|
|
212
|
+
continue
|
|
213
|
+
try:
|
|
214
|
+
result = CheckResult.model_validate(item)
|
|
215
|
+
except ValueError:
|
|
216
|
+
continue
|
|
217
|
+
runs.append(CheckRun(result.check_id, None, result.status, result.issues))
|
|
218
|
+
if result.log_tail:
|
|
219
|
+
tails.append(result.log_tail)
|
|
220
|
+
if not runs:
|
|
221
|
+
return None
|
|
222
|
+
return tuple(runs), "\n".join(tails)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _check_runs(lines: list[str]) -> tuple[tuple[CheckRun, ...], set[int]]:
|
|
226
|
+
"""`chipgraph check` text results, and the indexes of the lines they used."""
|
|
227
|
+
runs: list[CheckRun] = []
|
|
228
|
+
used: set[int] = set()
|
|
229
|
+
current: tuple[str, str | None, CheckStatus] | None = None
|
|
230
|
+
issues: list[Issue] = []
|
|
231
|
+
|
|
232
|
+
def close() -> None:
|
|
233
|
+
if current is not None:
|
|
234
|
+
runs.append(CheckRun(current[0], current[1], current[2], tuple(issues)))
|
|
235
|
+
|
|
236
|
+
for index, line in enumerate(lines):
|
|
237
|
+
head = _CHECK_LINE.match(line)
|
|
238
|
+
if head is not None:
|
|
239
|
+
close()
|
|
240
|
+
block = head.group("block")
|
|
241
|
+
current = (
|
|
242
|
+
head.group("check"),
|
|
243
|
+
None if block == "-" else block,
|
|
244
|
+
_STATUS[head["status"]],
|
|
245
|
+
)
|
|
246
|
+
issues = []
|
|
247
|
+
used.add(index)
|
|
248
|
+
continue
|
|
249
|
+
item = _CHECK_ISSUE.match(line) if current is not None else None
|
|
250
|
+
if item is not None:
|
|
251
|
+
file = item.group("file")
|
|
252
|
+
line_no = item.group("line")
|
|
253
|
+
issues.append(
|
|
254
|
+
Issue(
|
|
255
|
+
file=None if file == "-" else file,
|
|
256
|
+
line=None if line_no == "-" else int(line_no),
|
|
257
|
+
rule=item.group("rule"),
|
|
258
|
+
msg=item.group("msg").strip(),
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
used.add(index)
|
|
262
|
+
continue
|
|
263
|
+
if current is not None and line.strip():
|
|
264
|
+
close()
|
|
265
|
+
current = None
|
|
266
|
+
issues = []
|
|
267
|
+
close()
|
|
268
|
+
return tuple(runs), used
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _excerpt(lines: list[str]) -> str:
|
|
272
|
+
kept = [line for line in lines if not _NOISE.match(line)]
|
|
273
|
+
while kept and not kept[-1].strip():
|
|
274
|
+
kept.pop()
|
|
275
|
+
if len(kept) > MAX_EXCERPT_LINES:
|
|
276
|
+
marks = [i for i, line in enumerate(kept) if _ERRORISH.search(line)]
|
|
277
|
+
keep: set[int] = set(range(10)) | set(range(len(kept) - 10, len(kept)))
|
|
278
|
+
for i in marks:
|
|
279
|
+
keep.update(range(max(0, i - 2), min(len(kept), i + 3)))
|
|
280
|
+
out: list[str] = []
|
|
281
|
+
last = -1
|
|
282
|
+
for i in sorted(keep):
|
|
283
|
+
if i != last + 1:
|
|
284
|
+
out.append("…")
|
|
285
|
+
out.append(kept[i])
|
|
286
|
+
last = i
|
|
287
|
+
if len(out) >= MAX_EXCERPT_LINES:
|
|
288
|
+
out.append("…")
|
|
289
|
+
break
|
|
290
|
+
kept = out
|
|
291
|
+
text = "\n".join(kept)
|
|
292
|
+
if len(text) > MAX_EXCERPT_CHARS:
|
|
293
|
+
text = text[: MAX_EXCERPT_CHARS - 1] + "…"
|
|
294
|
+
return text
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _first_error(lines: list[str]) -> str:
|
|
298
|
+
for index, line in enumerate(lines):
|
|
299
|
+
if _NOISE.match(line):
|
|
300
|
+
continue
|
|
301
|
+
if _ERRORISH.search(line) and not line.lstrip().startswith("ok "):
|
|
302
|
+
block = [ln for ln in lines[index : index + FIRST_ERROR_LINES] if not _NOISE.match(ln)]
|
|
303
|
+
return "\n".join(block).rstrip()
|
|
304
|
+
return ""
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _dedupe(issues: list[Issue]) -> tuple[Issue, ...]:
|
|
308
|
+
seen: set[tuple[str | None, int | None, str]] = set()
|
|
309
|
+
out: list[Issue] = []
|
|
310
|
+
for issue in issues:
|
|
311
|
+
key = (issue.file, issue.line, issue.msg)
|
|
312
|
+
if key not in seen:
|
|
313
|
+
seen.add(key)
|
|
314
|
+
out.append(issue)
|
|
315
|
+
return tuple(out)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def parse_log(text: str) -> ParsedLog:
|
|
319
|
+
"""Read `text` (a tool log, `chipgraph check` output, or its JSON) into a `ParsedLog`."""
|
|
320
|
+
text = _ANSI.sub("", text).replace("\r\n", "\n")
|
|
321
|
+
json_runs = _json_checks(text)
|
|
322
|
+
checks: tuple[CheckRun, ...] = ()
|
|
323
|
+
if json_runs is not None:
|
|
324
|
+
checks, text = json_runs
|
|
325
|
+
lines = text.split("\n")
|
|
326
|
+
text_runs, used = _check_runs(lines)
|
|
327
|
+
checks = (*checks, *text_runs)
|
|
328
|
+
|
|
329
|
+
tool: list[Issue] = []
|
|
330
|
+
runtime: list[Issue] = []
|
|
331
|
+
runtime_lines: list[str] = []
|
|
332
|
+
sim_failures: list[str] = []
|
|
333
|
+
sim_assertions: list[str] = []
|
|
334
|
+
unlocated: list[str] = []
|
|
335
|
+
for index, line in enumerate(lines):
|
|
336
|
+
if index in used:
|
|
337
|
+
continue
|
|
338
|
+
if _RUNTIME.search(line):
|
|
339
|
+
runtime_lines.append(line.strip())
|
|
340
|
+
message = _assertion_message(line)
|
|
341
|
+
if message is not None and message not in sim_assertions:
|
|
342
|
+
sim_assertions.append(message)
|
|
343
|
+
if _SIM_FAILURE.search(line):
|
|
344
|
+
sim_failures.append(line.strip())
|
|
345
|
+
bare = _UNLOCATED.match(line)
|
|
346
|
+
if bare is not None:
|
|
347
|
+
msg = bare.group("msg").strip()
|
|
348
|
+
if not _LOCATED.match(msg) and not _SUMMARY.match(msg):
|
|
349
|
+
unlocated.append(line.strip())
|
|
350
|
+
for issue in (*_VERILATOR_PARSER.parse(line), *_HDL_PARSER.parse(line)):
|
|
351
|
+
if _RUNTIME.search(line) or _RUNTIME.search(issue.msg):
|
|
352
|
+
runtime.append(issue)
|
|
353
|
+
else:
|
|
354
|
+
tool.append(issue)
|
|
355
|
+
|
|
356
|
+
return ParsedLog(
|
|
357
|
+
text=text,
|
|
358
|
+
tool_issues=_dedupe(tool),
|
|
359
|
+
unlocated_errors=tuple(unlocated),
|
|
360
|
+
runtime_issues=_dedupe(runtime),
|
|
361
|
+
checks=checks,
|
|
362
|
+
sim_failures=tuple(sim_failures),
|
|
363
|
+
sim_assertions=tuple(sim_assertions),
|
|
364
|
+
runtime_lines=tuple(runtime_lines),
|
|
365
|
+
first_error=_first_error(lines),
|
|
366
|
+
excerpt=_excerpt(lines),
|
|
367
|
+
lines=tuple(lines),
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
__all__ = ["CheckRun", "ParsedLog", "parse_log"]
|