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,272 @@
|
|
|
1
|
+
"""What an `rtl-author` task gets in its context (DESIGN.md 5.1, 5.3; task M2-04).
|
|
2
|
+
|
|
3
|
+
Only the inputs an RTL module is written from, never a testbench:
|
|
4
|
+
|
|
5
|
+
- `module`: the module's entry of the block's approved plan (summary, requirement ids,
|
|
6
|
+
interface ports, dependencies, write set, budget), with the plan's top, assumptions
|
|
7
|
+
and open questions;
|
|
8
|
+
- `requirements`: the module's requirements, with their ids and text (the Design
|
|
9
|
+
Model's; the plan names them);
|
|
10
|
+
- `interface`: the module's ports. A port the Design Model knows (the block's spec
|
|
11
|
+
ports, else its top RTL module's) is shown as the model has it (`source: model`); an
|
|
12
|
+
internal port between the block's modules is shown as the approved plan gives it
|
|
13
|
+
(`source: plan`). Clock and reset come from the model's block;
|
|
14
|
+
- `dependencies`: the modules it depends on, declarations only: name and ports, from
|
|
15
|
+
the Design Model when it knows the module, else from the approved plan;
|
|
16
|
+
- `naming`: the project's naming rules, rendered as text (`naming_text`): the dynamic
|
|
17
|
+
part of the `org/naming` skill;
|
|
18
|
+
- `style`: the exemplars' text (`style.exemplars`, each capped, labelled "imitate this
|
|
19
|
+
style") and the style profile measured from them (`chipgraph.checks.style`). An
|
|
20
|
+
exemplar the role must not read (`is_denied`, e.g. a testbench) is refused: its entry
|
|
21
|
+
says so and holds no text.
|
|
22
|
+
|
|
23
|
+
`spec_side` is the block's spec slice (registers, interrupts, clock and reset) with the
|
|
24
|
+
requirements cut to the module's: what the model input `block/<block>` shows.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import json
|
|
30
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from chipgraph.checks._naming_rules import NamingRules
|
|
35
|
+
from chipgraph.checks.naming import _load_rules, _resolve_rules_path, _RulesError
|
|
36
|
+
from chipgraph.checks.plan_check import PlanEnv, PlanEnvError, check_plan_file
|
|
37
|
+
from chipgraph.checks.style import style_profile
|
|
38
|
+
from chipgraph.core.config.loader import ResolvedProfile
|
|
39
|
+
from chipgraph.core.contracts import RuleSpec
|
|
40
|
+
from chipgraph.core.model import DesignModel
|
|
41
|
+
from chipgraph.packs.digital_rtl.plan.model import Plan, PlanModule
|
|
42
|
+
from chipgraph.packs.dv.interface import spec_slice
|
|
43
|
+
|
|
44
|
+
MAX_EXEMPLAR_CHARS = 12_000
|
|
45
|
+
"""Longest text of one exemplar in the context; longer files are cut."""
|
|
46
|
+
MAX_EXEMPLARS_CHARS = 36_000
|
|
47
|
+
"""Most exemplar text in one context; exemplars past it are listed without text."""
|
|
48
|
+
DEFAULT_APPROVED = "plan/{block}.approved.json"
|
|
49
|
+
|
|
50
|
+
IMITATE = (
|
|
51
|
+
"imitate this style: indentation, begin/end placement, port declarations, reset and "
|
|
52
|
+
"clocking, signal suffixes, comments. Do not copy its logic."
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _naming_ref(resolved: ResolvedProfile, block: str) -> str | None:
|
|
57
|
+
profile = resolved.for_block(block)
|
|
58
|
+
if profile.naming.rules is not None:
|
|
59
|
+
return profile.naming.rules
|
|
60
|
+
cfg = profile.adapters.get("naming")
|
|
61
|
+
dumped = cfg.model_dump() if cfg is not None else {}
|
|
62
|
+
rules = dumped.get("rules")
|
|
63
|
+
return rules if dumped.get("use") == "naming" and isinstance(rules, str) else None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def naming_text(rules: NamingRules, ref: str) -> str:
|
|
67
|
+
"""The naming rules as instructions a model can follow."""
|
|
68
|
+
lines = [
|
|
69
|
+
f"Naming rules ({rules.document} v{rules.version}, from {ref}). Every name you "
|
|
70
|
+
"declare must follow them; the `naming` check rejects the module otherwise.",
|
|
71
|
+
"",
|
|
72
|
+
"Allowed names, per kind (the whole name must match the pattern):",
|
|
73
|
+
]
|
|
74
|
+
for kind, rule in rules.identifiers.items():
|
|
75
|
+
lines.append(f"- {kind}: `{rule.pattern}` ({rule.rule}): {rule.message}")
|
|
76
|
+
if rules.lexical:
|
|
77
|
+
lines.extend(["", "Lexical rules, for every name:"])
|
|
78
|
+
for lex in rules.lexical:
|
|
79
|
+
unless = f", unless it matches `{lex.allow_if_matches}`" if lex.allow_if_matches else ""
|
|
80
|
+
lines.append(f"- {lex.rule}: no match of `{lex.forbid_pattern}`{unless}: {lex.message}")
|
|
81
|
+
if rules.vocabulary is not None and rules.vocabulary.replace:
|
|
82
|
+
words = ", ".join(f"{bad} -> {good}" for bad, good in rules.vocabulary.replace.items())
|
|
83
|
+
lines.extend(["", f"Vocabulary ({rules.vocabulary.rule}): {words}"])
|
|
84
|
+
lines.extend(
|
|
85
|
+
[
|
|
86
|
+
"",
|
|
87
|
+
f"Do not use the exemption comment `// {rules.ignore_comment}`: it is for people.",
|
|
88
|
+
]
|
|
89
|
+
)
|
|
90
|
+
return "\n".join(lines) + "\n"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _naming(root: Path, resolved: ResolvedProfile, block: str) -> dict[str, Any] | None:
|
|
94
|
+
ref = _naming_ref(resolved, block)
|
|
95
|
+
if ref is None:
|
|
96
|
+
return None
|
|
97
|
+
try:
|
|
98
|
+
rules, _ = _load_rules(_resolve_rules_path(ref, root))
|
|
99
|
+
except _RulesError as exc:
|
|
100
|
+
return {"rules": ref, "note": str(exc)}
|
|
101
|
+
return {"rules": ref, "text": naming_text(rules, ref)}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _exemplars(
|
|
105
|
+
root: Path,
|
|
106
|
+
resolved: ResolvedProfile,
|
|
107
|
+
is_denied: Callable[[str], bool],
|
|
108
|
+
) -> dict[str, Any]:
|
|
109
|
+
style = resolved.profile.style
|
|
110
|
+
entries: list[dict[str, Any]] = []
|
|
111
|
+
texts: dict[str, str] = {}
|
|
112
|
+
used = 0
|
|
113
|
+
for rel in style.exemplars:
|
|
114
|
+
entry: dict[str, Any] = {"path": rel}
|
|
115
|
+
path = root / rel
|
|
116
|
+
if is_denied(rel):
|
|
117
|
+
entry["refused"] = (
|
|
118
|
+
f"{rel} is a file this role must not read (a testbench or test): it is not "
|
|
119
|
+
"shown. Remove it from the profile's style.exemplars."
|
|
120
|
+
)
|
|
121
|
+
elif not path.is_file():
|
|
122
|
+
entry["missing"] = True
|
|
123
|
+
else:
|
|
124
|
+
text = path.read_text(encoding="utf-8", errors="replace")
|
|
125
|
+
texts[rel] = text
|
|
126
|
+
if used >= MAX_EXEMPLARS_CHARS:
|
|
127
|
+
entry["omitted"] = "the context's exemplar budget is used up"
|
|
128
|
+
else:
|
|
129
|
+
cap = min(MAX_EXEMPLAR_CHARS, MAX_EXEMPLARS_CHARS - used)
|
|
130
|
+
if len(text) > cap:
|
|
131
|
+
entry["truncated"] = True
|
|
132
|
+
text = text[:cap]
|
|
133
|
+
used += len(text)
|
|
134
|
+
entry["label"] = IMITATE
|
|
135
|
+
entry["content"] = text
|
|
136
|
+
entries.append(entry)
|
|
137
|
+
out: dict[str, Any] = {"exemplars": entries, "enforce": list(style.enforce)}
|
|
138
|
+
if texts:
|
|
139
|
+
profile = style_profile(texts)
|
|
140
|
+
out["profile"] = profile.describe()
|
|
141
|
+
return out
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _ports(ports: Sequence[Any], source: str) -> list[dict[str, Any]]:
|
|
145
|
+
return [
|
|
146
|
+
{"name": p.name, "direction": p.direction, "width": p.width, "source": source}
|
|
147
|
+
for p in ports
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _interface(
|
|
152
|
+
plan: Plan, module: PlanModule, env: PlanEnv, slice_: Mapping[str, Any]
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
model_ports = {p.name: p for p in env.interface}
|
|
155
|
+
ports: list[dict[str, Any]] = []
|
|
156
|
+
for port in module.interface.ports:
|
|
157
|
+
known = model_ports.get(port.name)
|
|
158
|
+
if known is not None and not port.internal:
|
|
159
|
+
ports.extend(_ports([known], "model"))
|
|
160
|
+
else:
|
|
161
|
+
ports.extend(_ports([port], "plan"))
|
|
162
|
+
return {
|
|
163
|
+
"module": module.name,
|
|
164
|
+
"top": plan.top == module.name,
|
|
165
|
+
"model_interface": env.interface_source,
|
|
166
|
+
"ports": ports,
|
|
167
|
+
"clock": slice_.get("clock"),
|
|
168
|
+
"reset": slice_.get("reset"),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _dependencies(plan: Plan, module: PlanModule, env: PlanEnv) -> list[dict[str, Any]]:
|
|
173
|
+
existing = {m.name: m for m in env.existing_modules}
|
|
174
|
+
out: list[dict[str, Any]] = []
|
|
175
|
+
for name in dict.fromkeys(module.depends_on):
|
|
176
|
+
found = existing.get(name)
|
|
177
|
+
planned = plan.module(name)
|
|
178
|
+
if found is not None:
|
|
179
|
+
out.append({"name": name, "source": "model", "ports": _ports(found.ports, "model")})
|
|
180
|
+
elif planned is not None:
|
|
181
|
+
out.append(
|
|
182
|
+
{
|
|
183
|
+
"name": name,
|
|
184
|
+
"source": "plan",
|
|
185
|
+
"summary": planned.summary,
|
|
186
|
+
"ports": _ports(planned.interface.ports, "plan"),
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
return out
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def approved_plan_path(root: Path, block: str, approved: str | None = None) -> str | None:
|
|
193
|
+
"""The plan file an approved plan artifact names (None when there is none)."""
|
|
194
|
+
rel = (approved or DEFAULT_APPROVED).replace("{block}", block)
|
|
195
|
+
path = root / rel
|
|
196
|
+
if not path.is_file():
|
|
197
|
+
return None
|
|
198
|
+
try:
|
|
199
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
200
|
+
except (OSError, ValueError):
|
|
201
|
+
return None
|
|
202
|
+
plan = data.get("plan") if isinstance(data, dict) else None
|
|
203
|
+
return plan if isinstance(plan, str) else None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def rtl_author_context(
|
|
207
|
+
root: Path,
|
|
208
|
+
resolved: ResolvedProfile,
|
|
209
|
+
rules: Sequence[RuleSpec],
|
|
210
|
+
*,
|
|
211
|
+
block: str,
|
|
212
|
+
module: str | None,
|
|
213
|
+
model: DesignModel | None,
|
|
214
|
+
is_denied: Callable[[str], bool],
|
|
215
|
+
approved: str | None = None,
|
|
216
|
+
) -> dict[str, Any]:
|
|
217
|
+
"""The `rtl` section of an `rtl-author` task's context (see the module docstring)."""
|
|
218
|
+
out: dict[str, Any] = {"block": block, "module_name": module}
|
|
219
|
+
out["naming"] = _naming(root, resolved, block)
|
|
220
|
+
out["style"] = _exemplars(root, resolved, is_denied)
|
|
221
|
+
slice_ = spec_slice(model, block) if model is not None else {}
|
|
222
|
+
plan_path = approved_plan_path(root, block, approved)
|
|
223
|
+
if module is None or plan_path is None:
|
|
224
|
+
out["note"] = "no approved plan entry for this task: write from the spec and model"
|
|
225
|
+
out["requirements"] = list(slice_.get("requirements", []))
|
|
226
|
+
return out
|
|
227
|
+
try:
|
|
228
|
+
report = check_plan_file(
|
|
229
|
+
root, block, plan_path=plan_path, resolved=resolved, rules=rules, check_existing=False
|
|
230
|
+
)
|
|
231
|
+
except PlanEnvError as exc:
|
|
232
|
+
out["note"] = str(exc)
|
|
233
|
+
return out
|
|
234
|
+
plan, env = report.plan, report.env
|
|
235
|
+
entry = plan.module(module) if plan is not None else None
|
|
236
|
+
if plan is None or env is None or entry is None:
|
|
237
|
+
out["note"] = f"module {module!r} is not in the plan {plan_path}"
|
|
238
|
+
return out
|
|
239
|
+
wanted = set(entry.reqs)
|
|
240
|
+
out["module"] = {
|
|
241
|
+
**entry.model_dump(mode="json"),
|
|
242
|
+
"plan": plan_path,
|
|
243
|
+
"plan_top": plan.top,
|
|
244
|
+
"plan_assumptions": list(plan.assumptions),
|
|
245
|
+
"plan_open_questions": list(plan.open_questions),
|
|
246
|
+
}
|
|
247
|
+
out["requirements"] = [
|
|
248
|
+
{"id": r.ref, "text": r.text} for r in env.requirements if r.refs & wanted
|
|
249
|
+
]
|
|
250
|
+
out["interface"] = _interface(plan, entry, env, slice_)
|
|
251
|
+
out["dependencies"] = _dependencies(plan, entry, env)
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def spec_side(model: DesignModel, block: str, reqs: Sequence[str] | None) -> dict[str, Any]:
|
|
256
|
+
"""The block's spec slice with only the requirements in `reqs` (all when None)."""
|
|
257
|
+
slice_ = spec_slice(model, block)
|
|
258
|
+
if reqs is not None:
|
|
259
|
+
wanted = set(reqs)
|
|
260
|
+
slice_["requirements"] = [r for r in slice_["requirements"] if r["id"] in wanted]
|
|
261
|
+
return slice_
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
"IMITATE",
|
|
266
|
+
"MAX_EXEMPLARS_CHARS",
|
|
267
|
+
"MAX_EXEMPLAR_CHARS",
|
|
268
|
+
"approved_plan_path",
|
|
269
|
+
"naming_text",
|
|
270
|
+
"rtl_author_context",
|
|
271
|
+
"spec_side",
|
|
272
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# A block's pull request (DESIGN.md 6.1; task M2-11a).
|
|
2
|
+
#
|
|
3
|
+
# For each block with an approved, valid plan (`foreach: plan.block`), once every module's
|
|
4
|
+
# RTL, the testbench and the Critic's review are built (the resolver adds the module
|
|
5
|
+
# files and the testbench as inputs), the `open_pr` tool builds the branch
|
|
6
|
+
# `chipgraph/<run id>/<block>` in a workspace outside the user's checkout (HEAD, index
|
|
7
|
+
# and working tree untouched), with exactly those files; with `review: {use: github}` it
|
|
8
|
+
# pushes it to `review.remote` (default origin; an https remote needs
|
|
9
|
+
# CHIPGRAPH_GITHUB_TOKEN, given to git through GIT_ASKPASS only) and opens the PR into
|
|
10
|
+
# `review.base` (idempotent), with the plan, requirement coverage, check results and the
|
|
11
|
+
# Critic's verdict as its body. It writes `pr/{block}.json`: branch, head commit, PR number
|
|
12
|
+
# and URL. A re-run with changed files moves the same branch forward (never a force push)
|
|
13
|
+
# and reuses the open PR.
|
|
14
|
+
#
|
|
15
|
+
# The gate `pr:{block}` is on `digital-rtl/pr_gate`. chipgraph never merges.
|
|
16
|
+
rule: open_pr
|
|
17
|
+
kind: gen
|
|
18
|
+
description: >-
|
|
19
|
+
Commit the block's RTL and testbench on a branch of their own, push it and open a pull
|
|
20
|
+
request whose body says what the plan, the checks and the Critic found.
|
|
21
|
+
foreach: plan.block
|
|
22
|
+
inputs:
|
|
23
|
+
- artifact: "reports/review/{block}.json"
|
|
24
|
+
outputs:
|
|
25
|
+
- "pr/{block}.json"
|
|
26
|
+
run:
|
|
27
|
+
use: open_pr
|
|
28
|
+
args:
|
|
29
|
+
review: "reports/review/{block}.json"
|
|
30
|
+
out: "pr/{block}.json"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# The Planner (DESIGN.md 5.1, 5.3; task M2-03).
|
|
2
|
+
#
|
|
3
|
+
# For each block, a `planner` task reads the block's slice of the Design Model
|
|
4
|
+
# (requirements, interface ports, existing modules) and writes one file: the block's plan
|
|
5
|
+
# (`Plan`, schemas/formats/plan.schema.json). Its context also gives the layout paths a
|
|
6
|
+
# module may write, the files that already exist there, the limits (profile `plan:`),
|
|
7
|
+
# the naming rule for module names and the plan schema.
|
|
8
|
+
#
|
|
9
|
+
# The plan is checked at once by `plan_check` (enable it in the profile:
|
|
10
|
+
# `adapters: { plan_check: { use: plan_check } }`); its issues are the redo text of the
|
|
11
|
+
# next try. A plan that passes is not used yet: `digital-rtl/plan_expand` waits on the
|
|
12
|
+
# gate `plan:{block}` until a person reads `chipgraph plan show <block>` and approves.
|
|
13
|
+
rule: plan
|
|
14
|
+
kind: agent
|
|
15
|
+
role: planner
|
|
16
|
+
description: >-
|
|
17
|
+
Split the block into modules: what each does, which requirements it covers, its ports
|
|
18
|
+
from the Design Model, what it depends on, the files it writes and its budget; list the
|
|
19
|
+
assumptions, the open questions and every requirement left unassigned, with the reason.
|
|
20
|
+
foreach: blocks
|
|
21
|
+
inputs:
|
|
22
|
+
- model: "block/{block}"
|
|
23
|
+
outputs:
|
|
24
|
+
- "plan/{block}.plan.yml"
|
|
25
|
+
skills: [plan/modules]
|
|
26
|
+
checks: [plan_check]
|
|
27
|
+
budget:
|
|
28
|
+
tries: 3
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# The human gate on a block's plan (DESIGN.md 6.1; task M2-03).
|
|
2
|
+
#
|
|
3
|
+
# A `gen` rule whose only file input is the plan, gated by `plan:{block}`. The gate covers
|
|
4
|
+
# a rule instance's file inputs, so approving it pins the plan file's hash:
|
|
5
|
+
#
|
|
6
|
+
# chipgraph plan show <block> # read it first
|
|
7
|
+
# chipgraph approve plan:<block> --instance 'digital-rtl/plan_expand[block=<block>]'
|
|
8
|
+
#
|
|
9
|
+
# Editing the plan afterwards changes its hash and puts the gate back to waiting. Once
|
|
10
|
+
# approved, the `plan_expand` tool checks the plan again and writes the approved plan
|
|
11
|
+
# artifact below. Rules that say `foreach: plan.modules` then get one instance per
|
|
12
|
+
# module (params `block`, `module`), in dependency order, each bounded by the module's
|
|
13
|
+
# write set; with no approved, valid plan they get none.
|
|
14
|
+
rule: plan_expand
|
|
15
|
+
kind: gen
|
|
16
|
+
description: >-
|
|
17
|
+
After a person approves the block's plan, check it again and write the approved plan
|
|
18
|
+
(modules in build order, their dependencies and write sets).
|
|
19
|
+
foreach: blocks
|
|
20
|
+
inputs:
|
|
21
|
+
- artifact: "plan/{block}.plan.yml"
|
|
22
|
+
outputs:
|
|
23
|
+
- "plan/{block}.approved.json"
|
|
24
|
+
gate: "plan:{block}"
|
|
25
|
+
run:
|
|
26
|
+
use: plan_expand
|
|
27
|
+
args:
|
|
28
|
+
plan: "plan/{block}.plan.yml"
|
|
29
|
+
out: "plan/{block}.approved.json"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# The human gate on a block's pull request (DESIGN.md 6.1; task M2-11a).
|
|
2
|
+
#
|
|
3
|
+
# Its input is the PR record `open_pr` wrote (and, from `foreach: plan.block`, the block's
|
|
4
|
+
# files). With `review: {use: github}` the gate `pr:{block}` is the pull request's review:
|
|
5
|
+
# approved when a reviewer approves at the PR's current head commit (a later push makes
|
|
6
|
+
# the approval stale), rejected when one requests changes. With the `file` adapter (no
|
|
7
|
+
# GitHub, offline) a person approves it here:
|
|
8
|
+
#
|
|
9
|
+
# chipgraph approve pr:<block>
|
|
10
|
+
#
|
|
11
|
+
# Once approved, `pr_gate` writes the approved head below. Merging stays a person's act.
|
|
12
|
+
rule: pr_gate
|
|
13
|
+
kind: gen
|
|
14
|
+
description: >-
|
|
15
|
+
Wait for a person to approve the block's pull request at its current head, then
|
|
16
|
+
record what was approved.
|
|
17
|
+
foreach: plan.block
|
|
18
|
+
inputs:
|
|
19
|
+
- artifact: "pr/{block}.json"
|
|
20
|
+
outputs:
|
|
21
|
+
- "pr/{block}.approved.json"
|
|
22
|
+
gate: "pr:{block}"
|
|
23
|
+
run:
|
|
24
|
+
use: pr_gate
|
|
25
|
+
args:
|
|
26
|
+
pr: "pr/{block}.json"
|
|
27
|
+
out: "pr/{block}.approved.json"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# The Critic review of one block's change (DESIGN.md 4.8, 5.1; task M2-09).
|
|
2
|
+
#
|
|
3
|
+
# For each block: a `critic` task gets, with a fresh context (no conversation, no author
|
|
4
|
+
# reasoning, no earlier review), the block's diff (base ref vs the working tree, only the
|
|
5
|
+
# block's files), its spec slice (requirements, interface, registers from the Design
|
|
6
|
+
# Model) and its model slice. The Critic writes no file: it replies with a JSON review
|
|
7
|
+
# (`ReviewReport`, schemas/roles/ReviewReport.schema.json); `submit` validates it and the
|
|
8
|
+
# engine writes it to the one output below. An invalid reply is rejected and uses a try.
|
|
9
|
+
#
|
|
10
|
+
# The base ref is the instance param `base` when there is one, otherwise the merge-base
|
|
11
|
+
# of HEAD with the default branch (so on the default branch itself: HEAD, and the diff is
|
|
12
|
+
# the uncommitted change). Findings never block the build (DESIGN.md 4.8): a valid
|
|
13
|
+
# review is accepted whatever its verdict; people read the verdict.
|
|
14
|
+
#
|
|
15
|
+
# `foreach: plan.blocks` (M2-11a): one instance per block. A block with an approved plan
|
|
16
|
+
# takes every module's files and its testbench as inputs, so its review runs after all
|
|
17
|
+
# of them (target `rtl:<block>`); a block without one is reviewed as it is.
|
|
18
|
+
rule: review
|
|
19
|
+
kind: agent
|
|
20
|
+
role: critic
|
|
21
|
+
description: >-
|
|
22
|
+
Review the block's change against its requirements, interface and Design Model; reply
|
|
23
|
+
with a structured review, which the engine writes as the block's review report.
|
|
24
|
+
foreach: plan.blocks
|
|
25
|
+
inputs:
|
|
26
|
+
- model: "block/{block}"
|
|
27
|
+
outputs:
|
|
28
|
+
- "reports/review/{block}.json"
|
|
29
|
+
skills: [review/diff]
|
|
30
|
+
budget:
|
|
31
|
+
tries: 2
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# The RTL of one planned module, written without seeing its testbench (DESIGN.md 5.1,
|
|
2
|
+
# 5.3, 8.6 V3; task M2-04).
|
|
3
|
+
#
|
|
4
|
+
# For each module of a block's approved plan (`foreach: plan.modules`, params `block` and
|
|
5
|
+
# `module`), an `rtl-author` task writes the module's RTL. Its context holds only what an
|
|
6
|
+
# RTL module is written from: the module's entry of the approved plan (the resolver adds
|
|
7
|
+
# `plan/{block}.approved.json` as an input), the model slice (`block/{block}`: the block's
|
|
8
|
+
# registers, clock and reset, with the requirements cut to the module's;
|
|
9
|
+
# `interface/{module}`: its ports from the Design Model, or the approved plan for an
|
|
10
|
+
# internal port; the declarations of the modules it depends on), the block's spec
|
|
11
|
+
# documents (the profile layout's `spec`), the naming rules as text and the profile's
|
|
12
|
+
# `style.exemplars`. Never a testbench: the role denies `tb` artifacts, the profile's
|
|
13
|
+
# `layout.tb` paths and files named like tests (`test_*.py`, `tb_*.sv`, `*_tb.sv`, ...);
|
|
14
|
+
# the rule validator refuses such an input, `get_context` refuses to serve one, the guard
|
|
15
|
+
# refuses reads of them, and what `submit` tells the author of a failed check is
|
|
16
|
+
# filtered the same way (behavioural text such as `expected 0x2, got 0x0` is kept).
|
|
17
|
+
#
|
|
18
|
+
# The output is the module's file in the block's RTL directory, `{layout.rtl}`: the
|
|
19
|
+
# directory part of the profile's `layout.rtl` for the block (QSoC's
|
|
20
|
+
# `design/{block}/rtl/*.sv` gives `design/pwm/rtl`), `rtl` when the profile has none
|
|
21
|
+
# (`chipgraph.packs.digital_rtl.layout`). The plan must list it in the module's writes
|
|
22
|
+
# (`plan_check` says so), and the graph refuses an output outside them.
|
|
23
|
+
#
|
|
24
|
+
# Besides the approved plan, the resolver adds the profile's `style.exemplars` and its
|
|
25
|
+
# naming rules file as inputs: editing one makes the module stale, so it is written
|
|
26
|
+
# again in the new style (an exemplar the plan itself rewrites is left out).
|
|
27
|
+
#
|
|
28
|
+
# Checks, in this order, each an adapter id of the profile:
|
|
29
|
+
# format the project's formatter on the outputs, run by the engine before the
|
|
30
|
+
# other checks (`chipgraph.checks.format`: `{ use: format }`, formatter from
|
|
31
|
+
# `style.formatter`, default verible-verilog-format when on PATH)
|
|
32
|
+
# lint the project's lint (tinysoc: `make lint BLOCK={block}`)
|
|
33
|
+
# naming the project's naming rule (`{ use: naming, rules: ..., scope: [...] }`)
|
|
34
|
+
# style the outputs against `style.exemplars` (`chipgraph.checks.style`:
|
|
35
|
+
# `{ use: style }`; errors only for the keys in `style.enforce`)
|
|
36
|
+
# hardcode no contract number typed by hand (`{ use: hardcode, scope: [...] }`)
|
|
37
|
+
rule: rtl_module
|
|
38
|
+
kind: agent
|
|
39
|
+
role: rtl-author
|
|
40
|
+
description: >-
|
|
41
|
+
Write the module's synthesizable RTL from its approved plan entry, its requirements,
|
|
42
|
+
its interface and the declarations it connects to, following the naming rules and the
|
|
43
|
+
style of the exemplars; cite each requirement id next to the logic that implements it.
|
|
44
|
+
foreach: plan.modules
|
|
45
|
+
inputs:
|
|
46
|
+
- model: "block/{block}"
|
|
47
|
+
- model: "interface/{module}"
|
|
48
|
+
outputs:
|
|
49
|
+
- "{layout.rtl}/{module}.sv"
|
|
50
|
+
skills: [lang-sv/rtl, org/naming]
|
|
51
|
+
checks: [format, lint, naming, style, hardcode]
|
|
52
|
+
budget:
|
|
53
|
+
tries: 3
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: lang-sv/rtl
|
|
3
|
+
description: How to write one synthesizable SystemVerilog module from its plan entry, requirements and interface, in the project's style.
|
|
4
|
+
roles: [rtl-author]
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
Write one synthesizable SystemVerilog module per output file (`outputs[0]`), named as
|
|
8
|
+
`rtl.module.name`. You see the plan, the requirements, the interface, the naming rules and
|
|
9
|
+
the style exemplars; you never see a testbench, and must not guess one.
|
|
10
|
+
|
|
11
|
+
What to use from the context:
|
|
12
|
+
|
|
13
|
+
- `rtl.module`: the module's entry of the approved plan: its summary, the requirement ids
|
|
14
|
+
it covers (`reqs`), its ports, the modules it depends on and the files it may write.
|
|
15
|
+
Stay inside it: no file outside `outputs`, no port the entry does not list, no logic
|
|
16
|
+
that belongs to another module of the plan.
|
|
17
|
+
- `rtl.interface.ports`: the module's ports, exactly (name, direction, width). A port with
|
|
18
|
+
`source: model` is the Design Model's; one with `source: plan` is internal to the block,
|
|
19
|
+
as a person approved it. Do not add, drop or rename a port.
|
|
20
|
+
- `rtl.dependencies`: the modules this one instantiates or connects to, declarations only.
|
|
21
|
+
Instantiate them with named port connections (`.port(signal)`), every port connected.
|
|
22
|
+
- `rtl.requirements`: what the module must do. Implement each one, and put its id in a
|
|
23
|
+
comment next to the logic that implements it: `// REQ-TIM-001: ...`.
|
|
24
|
+
- `rtl.style.exemplars`: files of this project. **Follow their style where it differs
|
|
25
|
+
from your habits**: indentation, `begin` placement, how ports are declared and aligned,
|
|
26
|
+
the reset (asynchronous or synchronous, active low or high, its name) and the clocked
|
|
27
|
+
block, signal suffixes (`_q`/`_d`, `_r`/`_next`, ...), the comment header, blank lines,
|
|
28
|
+
line length. `rtl.style.profile` is the same, measured; the `style` check compares your
|
|
29
|
+
file with it (the keys in `rtl.style.enforce` fail the check).
|
|
30
|
+
|
|
31
|
+
Synthesizable RTL:
|
|
32
|
+
|
|
33
|
+
- `logic` for every signal; `always_ff` for registers, `always_comb` for logic, `assign`
|
|
34
|
+
for simple wires. No `initial` blocks for state, no `#` delays, no `$display`.
|
|
35
|
+
- One clock and one reset convention, the exemplars' (asynchronous or synchronous, the
|
|
36
|
+
active level, the reset's name); every register is reset.
|
|
37
|
+
- No latches: in `always_comb`, give every signal a value on every path (a default
|
|
38
|
+
assignment first, or a `default` branch in every `case`).
|
|
39
|
+
- Every output is driven; every input bit is used (see lint, below).
|
|
40
|
+
- Width-explicit literals (`8'd0`, `1'b1`, `32'h0000_0010`); parameters (`localparam`) for
|
|
41
|
+
every constant with a meaning, not magic numbers; never retype an address or size the
|
|
42
|
+
spec names as a number: the `hardcode` check rejects it.
|
|
43
|
+
- Nonblocking `<=` in `always_ff`, blocking `=` in `always_comb`, never mixed.
|
|
44
|
+
|
|
45
|
+
Lint is Verilator with `-Wall`, and any warning fails the check:
|
|
46
|
+
|
|
47
|
+
- Every input bit is used, or consumed on purpose: for bits the module ignores, write
|
|
48
|
+
`logic unused_ok; assign unused_ok = &{1'b0, wdata[31:8]};` (Verilator ignores a signal
|
|
49
|
+
whose name contains `unused`). Do the same for a part of an internal signal you never
|
|
50
|
+
read.
|
|
51
|
+
- No signal declared and never read, no width mismatch in an assignment or comparison
|
|
52
|
+
(size both sides, or slice and extend explicitly), no output left unconnected on an
|
|
53
|
+
instance (connect it to a named `unused_*` signal).
|
|
54
|
+
- No `lint_off` pragmas, unless the style exemplars use them.
|
|
55
|
+
|
|
56
|
+
The engine runs the project's formatter on your file, then lint, naming, style and
|
|
57
|
+
hardcode. If the requirements or the interface are ambiguous or contradict each other, do
|
|
58
|
+
not guess: stop and report `needs_human` with the question.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: org/naming
|
|
3
|
+
description: How to name modules, ports, parameters and signals from the project's naming rules in the context.
|
|
4
|
+
roles: [rtl-author]
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
Names come from the project's naming rules, never from memory or another project's
|
|
8
|
+
habits. The rules are in your context as text: `rtl.naming.text` (from the file
|
|
9
|
+
`rtl.naming.rules`).
|
|
10
|
+
|
|
11
|
+
How to read them:
|
|
12
|
+
|
|
13
|
+
- "Allowed names, per kind": one line per kind of name you declare: `module`, `port`,
|
|
14
|
+
`parameter`, `localparam`, `enum_value`, `instance`, `signal`, `memory`, `genvar`.
|
|
15
|
+
The whole name must match the pattern of its kind. A port is checked as a port, a
|
|
16
|
+
signal as a signal: the check classifies each declaration by its syntax.
|
|
17
|
+
- "Lexical rules": apply to every name you declare (case, active-low marks, indices).
|
|
18
|
+
- "Vocabulary": a word on the left must not appear in a name; use the word on the right.
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
- The module name and the port names are fixed by `rtl.module` and `rtl.interface`; they
|
|
23
|
+
already follow the rules. Use them exactly.
|
|
24
|
+
- Name every other signal, parameter and instance by its kind's pattern. When the style
|
|
25
|
+
exemplars use a suffix the rules allow (`_q`, `_d`, `_r`, `_next`), use the exemplars'.
|
|
26
|
+
- When the rules and the exemplars disagree, the rules win: the `naming` check rejects a
|
|
27
|
+
name, the `style` check only warns unless the profile enforces that key.
|
|
28
|
+
- When `rtl.naming` is empty, the project has no naming rules: follow the exemplars.
|
|
29
|
+
- Never use the exemption comment the rules name.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: plan/modules
|
|
3
|
+
description: How to split a block into modules and write its plan file.
|
|
4
|
+
roles: [planner]
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
Write the block's plan to `outputs[0]` as YAML that follows `plan.schema` in your
|
|
8
|
+
context. A person reads it (`chipgraph plan show`) and approves it before any module
|
|
9
|
+
is built, so it must be complete and honest.
|
|
10
|
+
|
|
11
|
+
1. **Interface first (F1).** Copy ports from `plan.interface.ports` exactly: name,
|
|
12
|
+
direction, width. Never invent a port. The `top` module carries the block's
|
|
13
|
+
interface. A helper port between two modules of the block is `internal: true`, and
|
|
14
|
+
is never on `top`.
|
|
15
|
+
2. **Requirements.** Assign every requirement in `plan.requirements` to the module that
|
|
16
|
+
implements it, by its `id` (the REQ id; for an inferred requirement, its key such as
|
|
17
|
+
`timer.h1a2b3c4d`). One you cannot place goes in `unassigned_reqs` with a reason. Do
|
|
18
|
+
not invent requirements.
|
|
19
|
+
3. **Write sets (F2).** Each module lists every file its nodes write, and no two modules
|
|
20
|
+
write the same file. Every path matches one of `plan.layout`. Each module's writes
|
|
21
|
+
include the files in `plan.module_outputs` (fill in `{block}` and `{module}`). Never
|
|
22
|
+
write a file in `plan.other_rules_write`. A file in `plan.existing_files` is written
|
|
23
|
+
only on purpose: say `replaces: true` on that write.
|
|
24
|
+
4. **Dependencies.** `depends_on` names modules of this plan that must be built first
|
|
25
|
+
(a module needs the interfaces of the modules it instantiates). No cycles.
|
|
26
|
+
5. **Size.** Stay inside `plan.limits`: at most `max_modules` modules, a dependency
|
|
27
|
+
chain of at most `max_depth` modules, and module `budget.tries` adding up to at most
|
|
28
|
+
`max_total_tries`. Prefer few modules with clear boundaries over many small ones.
|
|
29
|
+
6. **Names.** Module names follow `plan.naming` when it is set.
|
|
30
|
+
7. **Say what you assumed (F4).** List every assumption in `assumptions`, and anything
|
|
31
|
+
a person must decide in `open_questions` (the approver must read them).
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
schema_version: 1
|
|
37
|
+
block: timer
|
|
38
|
+
top: timer_top
|
|
39
|
+
modules:
|
|
40
|
+
- name: timer_top
|
|
41
|
+
summary: Register interface and the irq output; instantiates the counter.
|
|
42
|
+
reqs: [REQ-TIM-004]
|
|
43
|
+
interface:
|
|
44
|
+
ports:
|
|
45
|
+
- { name: clk, direction: input, width: 1 }
|
|
46
|
+
depends_on: [timer_counter]
|
|
47
|
+
writes:
|
|
48
|
+
- { path: rtl/timer_top.sv }
|
|
49
|
+
budget: { tries: 3, tier: medium }
|
|
50
|
+
- name: timer_counter
|
|
51
|
+
summary: The 32-bit counter and its compare.
|
|
52
|
+
reqs: [REQ-TIM-001]
|
|
53
|
+
interface:
|
|
54
|
+
ports:
|
|
55
|
+
- { name: clk, direction: input, width: 1 }
|
|
56
|
+
- { name: count, direction: output, width: 32, internal: true }
|
|
57
|
+
writes:
|
|
58
|
+
- { path: rtl/timer_counter.sv }
|
|
59
|
+
assumptions: [The counter wraps at 2^32.]
|
|
60
|
+
open_questions: []
|
|
61
|
+
unassigned_reqs:
|
|
62
|
+
- { req: REQ-TIM-005, reason: The read mux belongs to the bus wrapper, not planned here. }
|
|
63
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: review/diff
|
|
3
|
+
description: How to review a block's diff against its requirements, interface and Design Model.
|
|
4
|
+
roles: [critic]
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
Review the diff in `review.diff` against `review.spec` (the block's requirements, its
|
|
8
|
+
interface and its registers) and `review.model`. Read a changed file in full when the
|
|
9
|
+
hunk alone does not show enough.
|
|
10
|
+
|
|
11
|
+
Look for, in this order:
|
|
12
|
+
|
|
13
|
+
- **spec/RTL mismatch**: behaviour a requirement states that the new code does not do,
|
|
14
|
+
or does differently (`spec_mismatch`; `missing_req` when a requirement's behaviour is
|
|
15
|
+
gone or never written).
|
|
16
|
+
- **reset**: a register's reset value differs from the spec's `Reset` column (`reset`).
|
|
17
|
+
- **width**: a signal, port or field narrower or wider than the spec says, a
|
|
18
|
+
truncating or extending assignment (`width`).
|
|
19
|
+
- **counters and compares**: off by one, `<` for `<=`, the wrong step (`counter`).
|
|
20
|
+
- **register map**: a register decoded at the wrong offset, a wrong access type, an
|
|
21
|
+
address decode that does not match the memory map (`register_map`).
|
|
22
|
+
- **bit order**: fields or bits swapped or reversed against the spec (`bit_order`).
|
|
23
|
+
- **latches**: a combinational block that does not assign every output on every path
|
|
24
|
+
(`latch`).
|
|
25
|
+
- **CDC**: a signal crossing clock domains without a synchroniser (`cdc`).
|
|
26
|
+
- **hard-coded values**: a literal where the spec has a register, parameter or named
|
|
27
|
+
constant (`hard_coded`).
|
|
28
|
+
- **naming** against the project's rules, and **test gaps**: a changed requirement with
|
|
29
|
+
no test (`naming`, `test_gap`). Anything else: `other`.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Every comment is on a `file:line` inside the diff, and its `evidence` quotes what the
|
|
34
|
+
claim rests on: the spec line (`doc/...md:68 While CTRL.EN = 1, ...`) or the code line.
|
|
35
|
+
No evidence, no comment.
|
|
36
|
+
- Name the requirement in `req_id` when there is one.
|
|
37
|
+
- `blocker` or `major`: the design is wrong against the spec. `minor`: a real but small
|
|
38
|
+
problem. `nit`: optional. Do not comment on formatting, whitespace or comments that a
|
|
39
|
+
formatter or lint covers, and do not invent requirements the spec does not state.
|
|
40
|
+
- When the diff is correct, say so: no comments, `reviewed` lists every file of the diff,
|
|
41
|
+
verdict `approve`.
|