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,384 @@
|
|
|
1
|
+
"""The `decide()` question for a failing log: choices `infra|rtl|tb|spec`, and its context.
|
|
2
|
+
|
|
3
|
+
The context is what a model needs and no more (DESIGN 5.4, shortened logs): the failing
|
|
4
|
+
check, the log excerpt, the parsed issues and, for a simulation failure, the spec lines the
|
|
5
|
+
Design Model and the indexed documents hold about what failed (`/ask`'s retrieval), plus
|
|
6
|
+
the chip-level map entries of the registers and blocks the failure names: a simulation
|
|
7
|
+
mismatch is only decidable against the spec (is the testbench's expectation or the
|
|
8
|
+
design's answer the one the spec gives?), and a test of a top-level design drives its
|
|
9
|
+
blocks through the chip's address map.
|
|
10
|
+
|
|
11
|
+
A simulation failure is a self-checking testbench's `FAIL ...` / `expected X got Y` line
|
|
12
|
+
or the message of a failed assertion (`$error`, `assert`): `failure_lines`.
|
|
13
|
+
|
|
14
|
+
Spec lines never come from RTL or testbench files (those are the two sides being judged)
|
|
15
|
+
and never from an 'nda' file. The question id is `triage.<hash of the log and check>`, so
|
|
16
|
+
the same log asks the same question and a recorded answer is picked up again.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import re
|
|
23
|
+
from collections.abc import Iterable
|
|
24
|
+
|
|
25
|
+
from chipgraph.app.context import AppContext
|
|
26
|
+
from chipgraph.app.errors import AppError
|
|
27
|
+
from chipgraph.core.contracts.types import DataLabel
|
|
28
|
+
from chipgraph.core.engine.decide import Question
|
|
29
|
+
from chipgraph.core.model.entities import EntityBase
|
|
30
|
+
from chipgraph.packs.assist.ask._project import AskProject
|
|
31
|
+
from chipgraph.packs.assist.ask.contract import AskSource
|
|
32
|
+
from chipgraph.packs.assist.ask.retrieve import retrieve
|
|
33
|
+
from chipgraph.packs.assist.triage.contract import LABELS, SpecLine
|
|
34
|
+
from chipgraph.packs.assist.triage.parse import ParsedLog
|
|
35
|
+
from chipgraph.packs.assist.triage.rules import TriageFacts, is_rtl_path, is_tb_path, where
|
|
36
|
+
|
|
37
|
+
QUESTION_PREFIX = "triage."
|
|
38
|
+
MAX_SPEC_LINES = 10
|
|
39
|
+
MAX_MAP_LINES = 6
|
|
40
|
+
MAX_SPEC_TEXT = 240
|
|
41
|
+
MAX_ISSUES = 12
|
|
42
|
+
MAX_FAILURES = 6
|
|
43
|
+
|
|
44
|
+
PROMPT = """\
|
|
45
|
+
A chip design run failed. Which side has to change to fix it?
|
|
46
|
+
- infra: the environment or the flow, not the design: a missing tool or file, a wrong \
|
|
47
|
+
path, a missing make target, a time limit, a crash or a licence problem.
|
|
48
|
+
- rtl: the RTL design is wrong: lint or compile errors in RTL sources, or a simulation \
|
|
49
|
+
result that disagrees with the spec.
|
|
50
|
+
- tb: the testbench or test is wrong: it does not compile, drives the design otherwise \
|
|
51
|
+
than the spec describes (a wrong address, port or reset), or expects a value the spec \
|
|
52
|
+
does not give.
|
|
53
|
+
- spec: the specification (MAS, chip contract) is wrong or incomplete, or contradicts the \
|
|
54
|
+
RTL and its users.
|
|
55
|
+
For a simulation mismatch, compare the testbench's stimulus and expected value with the \
|
|
56
|
+
spec lines: if the spec agrees with the expected value, the RTL is wrong; if the spec \
|
|
57
|
+
agrees with what the design returned, or the testbench drove the design in a way the spec \
|
|
58
|
+
does not describe, the testbench is wrong."""
|
|
59
|
+
"""The question every triage asks a model (choices: `LABELS`)."""
|
|
60
|
+
|
|
61
|
+
_IDENT = re.compile(r"\w+(?:[-.]\w+)*")
|
|
62
|
+
|
|
63
|
+
_FAIL_WORD = re.compile(r"^\s*(?:\[[^\]]*\]\s*)?(?:FAIL(?:ED|URE)?|MISMATCH)\b[:\s]*")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def question_id(parsed: ParsedLog, check_id: str | None) -> str:
|
|
67
|
+
"""`triage.<16 hex>`: a hash of the check id and the log text."""
|
|
68
|
+
digest = hashlib.sha256(f"{check_id or ''}\n{parsed.text}".encode()).hexdigest()
|
|
69
|
+
return QUESTION_PREFIX + digest[:16]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _not_spec(path: str, facts: TriageFacts) -> bool:
|
|
73
|
+
"""RTL, testbench and filelist files are not spec sources."""
|
|
74
|
+
return (
|
|
75
|
+
is_rtl_path(path, facts.tb_patterns)
|
|
76
|
+
or is_tb_path(path, facts.tb_patterns)
|
|
77
|
+
or path.endswith(".f")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _file_of(citation: str | None) -> str | None:
|
|
82
|
+
if not citation or citation.startswith("model:"):
|
|
83
|
+
return None
|
|
84
|
+
return citation.rsplit(":", 1)[0] if re.search(r":\d+(?:-\d+)?$", citation) else citation
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def failure_lines(parsed: ParsedLog) -> tuple[str, ...]:
|
|
88
|
+
"""What the simulation says failed: the self-check failure lines, then the messages of
|
|
89
|
+
its failed assertions (a testbench that reports with `$error` or `assert`)."""
|
|
90
|
+
lines = list(parsed.sim_failures)
|
|
91
|
+
for message in parsed.sim_assertions:
|
|
92
|
+
if not any(message in line for line in lines):
|
|
93
|
+
lines.append(message)
|
|
94
|
+
return tuple(lines)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def spec_lines(
|
|
98
|
+
ctx: AppContext, parsed: ParsedLog, facts: TriageFacts
|
|
99
|
+
) -> tuple[tuple[SpecLine, ...], tuple[DataLabel, ...]]:
|
|
100
|
+
"""The spec sources about a simulation failure, and their data labels.
|
|
101
|
+
|
|
102
|
+
The `/ask` retrieval for the failure lines (`failure_lines`), then the chip-level map
|
|
103
|
+
entries of what they name (`chip_map_sources`): the registers, and the address map
|
|
104
|
+
of the blocks and of the buses they sit on. A test of a top-level design drives the
|
|
105
|
+
blocks through that map, so it is only decidable against it.
|
|
106
|
+
|
|
107
|
+
Empty when the log shows no simulation failure, or the project has no Design Model
|
|
108
|
+
yet (`chipgraph ingest`): the question is then asked without them.
|
|
109
|
+
"""
|
|
110
|
+
failures = failure_lines(parsed)
|
|
111
|
+
if not failures:
|
|
112
|
+
return (), ()
|
|
113
|
+
try:
|
|
114
|
+
project = AskProject.load(ctx)
|
|
115
|
+
except AppError:
|
|
116
|
+
return (), ()
|
|
117
|
+
texts = [_FAIL_WORD.sub("", line) for line in failures[:3]]
|
|
118
|
+
query = " ".join(t for t in texts if t)
|
|
119
|
+
sources = retrieve(project, query, limit=MAX_SPEC_LINES * 2).sources if query.strip() else ()
|
|
120
|
+
lines: list[SpecLine] = []
|
|
121
|
+
labels: set[DataLabel] = set()
|
|
122
|
+
_collect(project, sources, facts, MAX_SPEC_LINES, lines, labels, set())
|
|
123
|
+
named = "\n".join((*failures, *parsed.runtime_lines))
|
|
124
|
+
_collect(
|
|
125
|
+
project,
|
|
126
|
+
chip_map_sources(project, named),
|
|
127
|
+
facts,
|
|
128
|
+
len(lines) + MAX_MAP_LINES,
|
|
129
|
+
lines,
|
|
130
|
+
labels,
|
|
131
|
+
{line.citation for line in lines},
|
|
132
|
+
by_citation=True,
|
|
133
|
+
)
|
|
134
|
+
return tuple(lines), tuple(sorted(labels))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def spec_sources(
|
|
138
|
+
ctx: AppContext, query: str, facts: TriageFacts, limit: int = MAX_SPEC_LINES
|
|
139
|
+
) -> tuple[tuple[SpecLine, ...], tuple[DataLabel, ...]]:
|
|
140
|
+
"""Spec-side sources for `query` (`/ask` retrieval, no RTL or testbench file)."""
|
|
141
|
+
if not query.strip():
|
|
142
|
+
return (), ()
|
|
143
|
+
try:
|
|
144
|
+
project = AskProject.load(ctx)
|
|
145
|
+
except AppError:
|
|
146
|
+
return (), ()
|
|
147
|
+
context = retrieve(project, query, limit=limit * 2)
|
|
148
|
+
lines: list[SpecLine] = []
|
|
149
|
+
labels: set[DataLabel] = set()
|
|
150
|
+
_collect(project, context.sources, facts, limit, lines, labels, set())
|
|
151
|
+
return tuple(lines), tuple(sorted(labels))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _collect(
|
|
155
|
+
project: AskProject,
|
|
156
|
+
sources: Iterable[AskSource],
|
|
157
|
+
facts: TriageFacts,
|
|
158
|
+
limit: int,
|
|
159
|
+
lines: list[SpecLine],
|
|
160
|
+
labels: set[DataLabel],
|
|
161
|
+
seen: set[str],
|
|
162
|
+
*,
|
|
163
|
+
by_citation: bool = False,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Add the spec-side `sources` to `lines` (and their labels), up to `limit` lines.
|
|
166
|
+
|
|
167
|
+
A source whose location is in `seen` already is skipped, or with `by_citation` one
|
|
168
|
+
whose citation is (two entities read from one table row are both kept).
|
|
169
|
+
"""
|
|
170
|
+
for source in sources:
|
|
171
|
+
if len(lines) >= limit:
|
|
172
|
+
break
|
|
173
|
+
files = [f for f in (_file_of(source.citation), _file_of(source.defined_at)) if f]
|
|
174
|
+
if any(_not_spec(f, facts) for f in files):
|
|
175
|
+
continue
|
|
176
|
+
# A document line the model entity was read from says the same thing again.
|
|
177
|
+
location = source.defined_at or source.citation
|
|
178
|
+
key = source.citation if by_citation else location
|
|
179
|
+
if key in seen:
|
|
180
|
+
continue
|
|
181
|
+
seen.update((key, location))
|
|
182
|
+
text = " ".join(source.text.split())
|
|
183
|
+
if len(text) > MAX_SPEC_TEXT:
|
|
184
|
+
text = text[: MAX_SPEC_TEXT - 1] + "…"
|
|
185
|
+
defined_at = source.defined_at if source.defined_at != source.citation else None
|
|
186
|
+
lines.append(SpecLine(citation=source.citation, text=text, defined_at=defined_at))
|
|
187
|
+
labels.update(project.labels.label_for(f) for f in files)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# --- the chip-level map of what a failure names -------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _words(text: str) -> list[str]:
|
|
194
|
+
"""The identifiers of `text`, in order, without repeats (`CTRL.EN` is one)."""
|
|
195
|
+
words: list[str] = []
|
|
196
|
+
for match in _IDENT.finditer(text):
|
|
197
|
+
word = match.group(0).strip("-.")
|
|
198
|
+
if word and word not in words:
|
|
199
|
+
words.append(word)
|
|
200
|
+
return words
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _named_blocks(project: AskProject, words: list[str]) -> list[str]:
|
|
204
|
+
"""The keys of the blocks the words name, in order: by the name of the block, of one
|
|
205
|
+
of its modules, or of an instance of one.
|
|
206
|
+
|
|
207
|
+
A word that names nothing is split, at `.` and `-` then at `_`, and its parts are
|
|
208
|
+
tried: a top-level port or instance is usually named after its block (`<block>_irq`,
|
|
209
|
+
`u_<block>`, `dut.u_<block>.q`).
|
|
210
|
+
"""
|
|
211
|
+
owners: dict[str, list[str]] = {}
|
|
212
|
+
|
|
213
|
+
def own(name: str, block: str | None) -> None:
|
|
214
|
+
if name and block and project.get(block) is not None:
|
|
215
|
+
owners.setdefault(name.lower(), []).append(block)
|
|
216
|
+
|
|
217
|
+
for entity in sorted(project.model.by_kind("block"), key=lambda e: e.key):
|
|
218
|
+
if project.visible(entity):
|
|
219
|
+
own(entity.name, entity.key)
|
|
220
|
+
for module in sorted(project.model.by_kind("module"), key=lambda e: e.key):
|
|
221
|
+
if project.visible(module):
|
|
222
|
+
own(module.name, project.block_of(module))
|
|
223
|
+
for relation in project.model.relations:
|
|
224
|
+
child = project.get(relation.dst) if relation.kind == "instantiates" else None
|
|
225
|
+
if child is not None:
|
|
226
|
+
own(str(relation.attrs.get("instance_name", "")), project.block_of(child))
|
|
227
|
+
|
|
228
|
+
blocks: list[str] = []
|
|
229
|
+
|
|
230
|
+
def named(word: str) -> bool:
|
|
231
|
+
found = owners.get(word.lower(), [])
|
|
232
|
+
blocks.extend(b for b in found if b not in blocks)
|
|
233
|
+
return bool(found)
|
|
234
|
+
|
|
235
|
+
for word in words:
|
|
236
|
+
if named(word):
|
|
237
|
+
continue
|
|
238
|
+
for segment in re.split(r"[.\-]+", word):
|
|
239
|
+
if segment and not named(segment):
|
|
240
|
+
for part in segment.split("_"):
|
|
241
|
+
if part:
|
|
242
|
+
named(part)
|
|
243
|
+
return blocks
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _related_blocks(project: AskProject, blocks: list[str]) -> list[str]:
|
|
247
|
+
"""`blocks`, then their IP blocks or instances (`instance_of`), then the other blocks
|
|
248
|
+
on the buses they connect to: the address map a test of the chip addresses them by."""
|
|
249
|
+
related = list(blocks)
|
|
250
|
+
|
|
251
|
+
def add(key: str) -> None:
|
|
252
|
+
if key not in related and project.get(key) is not None:
|
|
253
|
+
related.append(key)
|
|
254
|
+
|
|
255
|
+
for relation in project.model.relations:
|
|
256
|
+
if relation.kind == "instance_of":
|
|
257
|
+
if relation.src in blocks:
|
|
258
|
+
add(relation.dst)
|
|
259
|
+
elif relation.dst in blocks:
|
|
260
|
+
add(relation.src)
|
|
261
|
+
buses = [r.src for r in project.model.relations if r.kind == "connects" and r.dst in related]
|
|
262
|
+
for relation in project.model.relations:
|
|
263
|
+
if relation.kind == "connects" and relation.src in buses:
|
|
264
|
+
add(relation.dst)
|
|
265
|
+
return related
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _named_registers(project: AskProject, words: list[str], blocks: list[str]) -> list[EntityBase]:
|
|
269
|
+
"""The registers the words name exactly (`CTRL`, or `CTRL.EN` for a field of it): of
|
|
270
|
+
the named blocks (or their IP blocks) when there are any, else of any block."""
|
|
271
|
+
names = set(words) | {w.split(".")[0] for w in words if "." in w}
|
|
272
|
+
found = [
|
|
273
|
+
r
|
|
274
|
+
for r in sorted(project.model.by_kind("register"), key=lambda e: e.key)
|
|
275
|
+
if r.name in names and project.visible(r)
|
|
276
|
+
]
|
|
277
|
+
if blocks:
|
|
278
|
+
return [r for r in found if project.block_of(r) in blocks]
|
|
279
|
+
return found
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def chip_map_sources(project: AskProject, text: str) -> list[AskSource]:
|
|
283
|
+
"""The chip-level map entries of what `text` names, most specific first.
|
|
284
|
+
|
|
285
|
+
The registers it names (of the blocks it names, when it names any); the memory
|
|
286
|
+
regions of the blocks it names (`_named_blocks`) or whose registers it names, of
|
|
287
|
+
their IP blocks or instances (`instance_of`), and of the other blocks on the same
|
|
288
|
+
buses.
|
|
289
|
+
"""
|
|
290
|
+
words = _words(text)
|
|
291
|
+
blocks = _named_blocks(project, words)
|
|
292
|
+
registers = _named_registers(project, words, _related_blocks(project, blocks))
|
|
293
|
+
for register in registers:
|
|
294
|
+
block = project.block_of(register)
|
|
295
|
+
if block is not None and block not in blocks:
|
|
296
|
+
blocks.append(block)
|
|
297
|
+
related = _related_blocks(project, blocks)
|
|
298
|
+
order = {key: index for index, key in enumerate(related)}
|
|
299
|
+
regions = sorted(
|
|
300
|
+
(
|
|
301
|
+
r
|
|
302
|
+
for r in project.model.by_kind("memory_region")
|
|
303
|
+
if project.visible(r) and project.block_of(r) in order
|
|
304
|
+
),
|
|
305
|
+
key=lambda r: (order[project.block_of(r) or ""], r.key),
|
|
306
|
+
)
|
|
307
|
+
return [
|
|
308
|
+
AskSource(
|
|
309
|
+
citation=f"model:{entity.key}",
|
|
310
|
+
kind="model",
|
|
311
|
+
text=project.summary(entity),
|
|
312
|
+
origin="lookup",
|
|
313
|
+
defined_at=project.defined_at(entity),
|
|
314
|
+
)
|
|
315
|
+
for entity in (*registers, *regions)
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def build_context(parsed: ParsedLog, facts: TriageFacts, specs: tuple[SpecLine, ...] = ()) -> str:
|
|
320
|
+
"""The text a model reads with the question."""
|
|
321
|
+
parts: list[str] = []
|
|
322
|
+
checks = parsed.failing_checks
|
|
323
|
+
if checks:
|
|
324
|
+
named = ", ".join(
|
|
325
|
+
f"{c.check_id} ({facts.kind_of(c.check_id)}, {c.status})"
|
|
326
|
+
+ (f" for block {c.block}" if c.block else "")
|
|
327
|
+
for c in checks
|
|
328
|
+
)
|
|
329
|
+
parts.append(f"Failing check: {named}")
|
|
330
|
+
elif facts.check_id:
|
|
331
|
+
parts.append(f"Failing check: {facts.check_id} ({facts.kind_of(facts.check_id)})")
|
|
332
|
+
parts.append(f"Log excerpt:\n{parsed.excerpt or '(empty log)'}")
|
|
333
|
+
issues = parsed.issues[:MAX_ISSUES]
|
|
334
|
+
if issues:
|
|
335
|
+
rows = [
|
|
336
|
+
f"- {where(i) or '(no file)'} [{i.rule or '-'}] {i.severity}: {i.msg}" for i in issues
|
|
337
|
+
]
|
|
338
|
+
parts.append("Parsed issues:\n" + "\n".join(rows))
|
|
339
|
+
failures = failure_lines(parsed)
|
|
340
|
+
if failures:
|
|
341
|
+
rows = [f"- {line}" for line in failures[:MAX_FAILURES]]
|
|
342
|
+
parts.append("Simulation self-check failures:\n" + "\n".join(rows))
|
|
343
|
+
if specs:
|
|
344
|
+
rows = [
|
|
345
|
+
f"- {s.citation}" + (f" ({s.defined_at})" if s.defined_at else "") + f": {s.text}"
|
|
346
|
+
for s in specs
|
|
347
|
+
]
|
|
348
|
+
parts.append(
|
|
349
|
+
"Spec lines (from the project's Design Model and specification documents):\n"
|
|
350
|
+
+ "\n".join(rows)
|
|
351
|
+
)
|
|
352
|
+
elif failures:
|
|
353
|
+
parts.append("Spec lines: none found (no Design Model, or nothing matched).")
|
|
354
|
+
return "\n\n".join(parts)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def build_question(
|
|
358
|
+
parsed: ParsedLog,
|
|
359
|
+
facts: TriageFacts,
|
|
360
|
+
*,
|
|
361
|
+
specs: tuple[SpecLine, ...] = (),
|
|
362
|
+
labels: tuple[DataLabel, ...] = (),
|
|
363
|
+
) -> Question:
|
|
364
|
+
"""The `decide()` question for `parsed`."""
|
|
365
|
+
return Question(
|
|
366
|
+
id=question_id(parsed, facts.check_id),
|
|
367
|
+
prompt=PROMPT,
|
|
368
|
+
choices=LABELS,
|
|
369
|
+
context=build_context(parsed, facts, specs),
|
|
370
|
+
labels=tuple(sorted(set(labels))),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
__all__ = [
|
|
375
|
+
"PROMPT",
|
|
376
|
+
"QUESTION_PREFIX",
|
|
377
|
+
"build_context",
|
|
378
|
+
"build_question",
|
|
379
|
+
"chip_map_sources",
|
|
380
|
+
"failure_lines",
|
|
381
|
+
"question_id",
|
|
382
|
+
"spec_lines",
|
|
383
|
+
"spec_sources",
|
|
384
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""The deterministic parts of a triage report: the summary, the suggestion, the evidence.
|
|
2
|
+
|
|
3
|
+
The summary is a template over the parsed log (what failed, where), never model text. The
|
|
4
|
+
suggestion follows DESIGN 5.2 per label: `infra` -> fix the environment, then retry
|
|
5
|
+
without counting a try; `rtl`/`tb` -> the file:line to fix; `spec` -> the spec line, and
|
|
6
|
+
ask the spec owner (a person decides a spec change).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from chipgraph.core.contracts import Issue
|
|
12
|
+
from chipgraph.packs.assist.triage.contract import SpecLine, TriageLabel
|
|
13
|
+
from chipgraph.packs.assist.triage.parse import ParsedLog
|
|
14
|
+
from chipgraph.packs.assist.triage.rules import (
|
|
15
|
+
RuleHit,
|
|
16
|
+
TriageFacts,
|
|
17
|
+
is_rtl_path,
|
|
18
|
+
is_tb_path,
|
|
19
|
+
where,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
MAX_EVIDENCE = 10
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _issue_text(issue: Issue) -> str:
|
|
26
|
+
loc = where(issue)
|
|
27
|
+
rule = f" [{issue.rule}]" if issue.rule else ""
|
|
28
|
+
return f"{loc or '(no file)'}{rule} {issue.msg}".strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _count(n: int, word: str) -> str:
|
|
32
|
+
return f"{n} {word}" + ("" if n == 1 else "s")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def summary(parsed: ParsedLog) -> str:
|
|
36
|
+
"""What failed and where, in one or two sentences."""
|
|
37
|
+
checks = parsed.failing_checks
|
|
38
|
+
if checks:
|
|
39
|
+
first = checks[0]
|
|
40
|
+
scope = f" for block {first.block}" if first.block else ""
|
|
41
|
+
verb = "could not run" if first.status == "error" else "failed"
|
|
42
|
+
text = f"check {first.check_id}{scope} {verb} with {_count(len(first.issues), 'issue')}"
|
|
43
|
+
if first.issues:
|
|
44
|
+
text += f"; first: {_issue_text(first.issues[0])}"
|
|
45
|
+
if len(checks) > 1:
|
|
46
|
+
text += f" (and {_count(len(checks) - 1, 'other failing check')})"
|
|
47
|
+
return text
|
|
48
|
+
if parsed.sim_failures:
|
|
49
|
+
text = (
|
|
50
|
+
f"simulation failed {_count(len(parsed.sim_failures), 'self-check')}; first: "
|
|
51
|
+
f"{parsed.sim_failures[0]}"
|
|
52
|
+
)
|
|
53
|
+
stop = next((i for i in parsed.runtime_issues if i.file), None)
|
|
54
|
+
if stop is not None:
|
|
55
|
+
text += f" (stopped at {where(stop)})"
|
|
56
|
+
return text
|
|
57
|
+
errors = [i for i in parsed.tool_issues if i.severity == "error"]
|
|
58
|
+
warnings = [i for i in parsed.tool_issues if i.severity == "warning"]
|
|
59
|
+
if parsed.tool_issues:
|
|
60
|
+
top = (errors or warnings or list(parsed.tool_issues))[0]
|
|
61
|
+
return (
|
|
62
|
+
f"{_count(len(errors), 'error')} and {_count(len(warnings), 'warning')} from the "
|
|
63
|
+
f"tool; first: {_issue_text(top)}"
|
|
64
|
+
)
|
|
65
|
+
if parsed.unlocated_errors:
|
|
66
|
+
return f"the tool stopped: {parsed.unlocated_errors[0]}"
|
|
67
|
+
if parsed.first_error:
|
|
68
|
+
return f"the run failed: {parsed.first_error.splitlines()[0].strip()}"
|
|
69
|
+
return "no error message found in the log"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def evidence(
|
|
73
|
+
label: TriageLabel | None,
|
|
74
|
+
parsed: ParsedLog,
|
|
75
|
+
facts: TriageFacts,
|
|
76
|
+
hit: RuleHit | None,
|
|
77
|
+
specs: tuple[SpecLine, ...] = (),
|
|
78
|
+
) -> tuple[str, ...]:
|
|
79
|
+
"""The `file:line` locations behind the label: the rule's, else the parsed ones."""
|
|
80
|
+
out: list[str] = list(hit.evidence) if hit is not None else []
|
|
81
|
+
if not out and label != "infra":
|
|
82
|
+
issues = list(parsed.issues)
|
|
83
|
+
if label == "tb":
|
|
84
|
+
issues = [
|
|
85
|
+
i for i in issues if i.file and is_tb_path(i.file, facts.tb_patterns)
|
|
86
|
+
] or issues
|
|
87
|
+
elif label == "rtl":
|
|
88
|
+
issues = [
|
|
89
|
+
i for i in issues if i.file and is_rtl_path(i.file, facts.tb_patterns)
|
|
90
|
+
] or issues
|
|
91
|
+
for issue in issues:
|
|
92
|
+
loc = where(issue)
|
|
93
|
+
if loc and loc not in out:
|
|
94
|
+
out.append(loc)
|
|
95
|
+
if label == "spec" or (label in ("rtl", "tb") and parsed.sim_failures):
|
|
96
|
+
out.extend(s.location for s in specs[:3] if s.location not in out)
|
|
97
|
+
return tuple(out[:MAX_EVIDENCE])
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _first_spec_location(parsed: ParsedLog, facts: TriageFacts, specs: tuple[SpecLine, ...]) -> str:
|
|
101
|
+
for issue in parsed.issues:
|
|
102
|
+
if (
|
|
103
|
+
issue.file
|
|
104
|
+
and not is_rtl_path(issue.file, facts.tb_patterns)
|
|
105
|
+
and not is_tb_path(issue.file, facts.tb_patterns)
|
|
106
|
+
):
|
|
107
|
+
return where(issue)
|
|
108
|
+
if specs:
|
|
109
|
+
return specs[0].location
|
|
110
|
+
return next((where(i) for i in parsed.issues if i.file), "")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _first_location(issues: tuple[Issue, ...], wanted: str, facts: TriageFacts) -> str:
|
|
114
|
+
# Errors first: a warning next to an error is rarely what has to change.
|
|
115
|
+
for issue in sorted(issues, key=lambda i: i.severity != "error"):
|
|
116
|
+
if not issue.file:
|
|
117
|
+
continue
|
|
118
|
+
if wanted == "tb" and is_tb_path(issue.file, facts.tb_patterns):
|
|
119
|
+
return where(issue)
|
|
120
|
+
if wanted == "rtl" and is_rtl_path(issue.file, facts.tb_patterns):
|
|
121
|
+
return where(issue)
|
|
122
|
+
return ""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def suggestion(
|
|
126
|
+
label: TriageLabel,
|
|
127
|
+
parsed: ParsedLog,
|
|
128
|
+
facts: TriageFacts,
|
|
129
|
+
hit: RuleHit | None,
|
|
130
|
+
specs: tuple[SpecLine, ...] = (),
|
|
131
|
+
) -> str:
|
|
132
|
+
"""What to do next for `label`."""
|
|
133
|
+
rerun = f"rerun `{facts.check_id}`" if facts.check_id else "rerun it"
|
|
134
|
+
if label == "infra":
|
|
135
|
+
fix = hit.hint if hit is not None and hit.hint else "fix the environment the run needs"
|
|
136
|
+
return (
|
|
137
|
+
f"Not a design problem: {fix}. Then {rerun}; the retry does not count a try "
|
|
138
|
+
"(DESIGN 5.2)."
|
|
139
|
+
)
|
|
140
|
+
if label == "spec":
|
|
141
|
+
loc = _first_spec_location(parsed, facts, specs)
|
|
142
|
+
at = f" at {loc}" if loc else ""
|
|
143
|
+
return (
|
|
144
|
+
f"The spec is the side to change{at}. Ask the spec owner to confirm before "
|
|
145
|
+
"anything is changed: a spec change is a person's decision."
|
|
146
|
+
)
|
|
147
|
+
side = "testbench" if label == "tb" else "RTL"
|
|
148
|
+
if parsed.sim_failures:
|
|
149
|
+
against = f" against {specs[0].location}" if specs else " against the spec"
|
|
150
|
+
return (
|
|
151
|
+
f"Fix the {side} behind the failing self-check ({parsed.sim_failures[0]}); "
|
|
152
|
+
f"check it{against}, then {rerun}."
|
|
153
|
+
)
|
|
154
|
+
loc = _first_location(parsed.issues, label, facts)
|
|
155
|
+
if loc:
|
|
156
|
+
return f"Fix the {side} at {loc}, then {rerun}."
|
|
157
|
+
return f"Fix the {side} named in the log, then {rerun}."
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
__all__ = ["evidence", "suggestion", "summary"]
|