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,400 @@
|
|
|
1
|
+
"""`foreach: plan.modules`: one rule instance per module of an approved plan (DESIGN.md 5.3).
|
|
2
|
+
|
|
3
|
+
The gate `plan:<block>` hangs on the `gen` rule `digital-rtl/plan_expand`, whose only
|
|
4
|
+
file input is the plan: approving it pins the plan file's hash, and editing the plan
|
|
5
|
+
afterwards puts the gate back to waiting (the existing gate behaviour, `gate.py`).
|
|
6
|
+
|
|
7
|
+
`plan_modules` is what the project's resolver calls for `plan.modules`. For each block
|
|
8
|
+
it reads the plan only when that gate is approved at the plan's current hash, and only
|
|
9
|
+
when the plan still passes `plan_check` (limits included: defence in depth). Anything
|
|
10
|
+
else (no plan, not approved, edited since, invalid, over a limit) expands to nothing.
|
|
11
|
+
The graph is rebuilt on every build and `next_task`, so a plan that changes simply
|
|
12
|
+
yields no instances until it is approved again. Each module's entry carries:
|
|
13
|
+
|
|
14
|
+
- params `block` and `module`;
|
|
15
|
+
- the module's write set (`writes`): the instance's outputs must stay inside it;
|
|
16
|
+
- its `depends_on` as ordering edges to the same rule's instances of those modules;
|
|
17
|
+
- the approved plan artifact (`plan_expand`'s output) as an input, so every expanded
|
|
18
|
+
instance runs after the gate and the expansion;
|
|
19
|
+
- the profile's `style.exemplars` and naming rules file as inputs (M2-11a), so editing
|
|
20
|
+
one makes the module stale and it is written again. An exemplar an approved plan
|
|
21
|
+
writes, or one a role of a `plan.modules` agent rule must not read, is left out;
|
|
22
|
+
- the block's layout directories as template values (`{layout.rtl}`, `{layout.tb}`,
|
|
23
|
+
`chipgraph.packs.digital_rtl.layout`).
|
|
24
|
+
|
|
25
|
+
Two more selectors over the same approved plans (M2-11a), whose entries take every
|
|
26
|
+
module's write set and the block's testbench (the outputs of the `tb-author` rules over
|
|
27
|
+
`blocks`) as inputs, so their instances run after all of them:
|
|
28
|
+
|
|
29
|
+
- `plan.block`: one entry per block with an approved, valid plan (`digital-rtl/open_pr`,
|
|
30
|
+
`digital-rtl/pr_gate`); none for the others;
|
|
31
|
+
- `plan.blocks`: one entry per block of the profile; a block without an approved plan
|
|
32
|
+
gets a plain entry, with no extra input (`digital-rtl/review`, which also reviews a
|
|
33
|
+
change no plan made).
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
39
|
+
from dataclasses import dataclass, field
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
from chipgraph.app.context import AppContext
|
|
43
|
+
from chipgraph.app.errors import AppError
|
|
44
|
+
from chipgraph.checks.plan_check import (
|
|
45
|
+
DEFAULT_PLAN_PATH,
|
|
46
|
+
PLAN_MODULES,
|
|
47
|
+
PlanEnvError,
|
|
48
|
+
PlanReport,
|
|
49
|
+
check_plan_file,
|
|
50
|
+
dependency_depth,
|
|
51
|
+
topo_order,
|
|
52
|
+
)
|
|
53
|
+
from chipgraph.core.config.loader import ResolvedProfile
|
|
54
|
+
from chipgraph.core.config.models import PlanCfg
|
|
55
|
+
from chipgraph.core.contracts import RuleInstance, RuleSpec
|
|
56
|
+
from chipgraph.core.engine.gate import GateStatus, gate_id_for
|
|
57
|
+
from chipgraph.core.engine.graph import ForeachEntry, GraphError, build_graph, kind_for
|
|
58
|
+
from chipgraph.core.runtime.roles import denied_kind, find_role
|
|
59
|
+
from chipgraph.packs.digital_rtl.layout import LayoutError, fill_vars, layout_vars
|
|
60
|
+
from chipgraph.packs.digital_rtl.plan.model import Plan
|
|
61
|
+
|
|
62
|
+
PLAN_RULE = "digital-rtl/plan"
|
|
63
|
+
"""The agent rule that writes a block's plan."""
|
|
64
|
+
EXPAND_RULE = "digital-rtl/plan_expand"
|
|
65
|
+
"""The `gen` rule behind the gate `plan:<block>`; it writes the approved plan artifact."""
|
|
66
|
+
PLAN_BLOCK = "plan.block"
|
|
67
|
+
"""The selector over the blocks with an approved, valid plan."""
|
|
68
|
+
PLAN_BLOCKS = "plan.blocks"
|
|
69
|
+
"""The selector over every block, with the plan's files as inputs when it is approved."""
|
|
70
|
+
TB_ROLE = "tb-author"
|
|
71
|
+
"""The role of the rules whose outputs are a block's testbench."""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _Blocks:
|
|
75
|
+
def __init__(self, blocks: Iterable[str]) -> None:
|
|
76
|
+
self._blocks = sorted(blocks)
|
|
77
|
+
|
|
78
|
+
def expand(self, expr: str) -> list[dict[str, str]]:
|
|
79
|
+
if expr != "blocks":
|
|
80
|
+
raise GraphError(f"rule {EXPAND_RULE!r} must be 'foreach: blocks', not {expr!r}")
|
|
81
|
+
return [{"block": b} for b in self._blocks]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class PlanGate:
|
|
86
|
+
"""A block's plan gate: its id, the instance it is evaluated on, and the paths."""
|
|
87
|
+
|
|
88
|
+
block: str
|
|
89
|
+
gate_id: str
|
|
90
|
+
instance: RuleInstance
|
|
91
|
+
plan_path: str
|
|
92
|
+
approved_path: str
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def plan_gates(
|
|
96
|
+
rules: Iterable[RuleSpec], blocks: Iterable[str], *, repo: str = "."
|
|
97
|
+
) -> dict[str, PlanGate]:
|
|
98
|
+
"""Every block's `PlanGate`, from the loaded `digital-rtl/plan_expand` rule.
|
|
99
|
+
|
|
100
|
+
Raises `AppError` when the rule is not loaded (the `digital-rtl` pack is off) or has
|
|
101
|
+
no gate.
|
|
102
|
+
"""
|
|
103
|
+
rule = next((r for r in rules if r.id == EXPAND_RULE), None)
|
|
104
|
+
if rule is None:
|
|
105
|
+
raise AppError(
|
|
106
|
+
f"foreach 'plan.modules' needs rule {EXPAND_RULE!r}: add the 'digital-rtl' pack "
|
|
107
|
+
"to the profile's packs"
|
|
108
|
+
)
|
|
109
|
+
if rule.gate is None:
|
|
110
|
+
raise AppError(f"rule {EXPAND_RULE!r} must have a gate (plan:{{block}})")
|
|
111
|
+
try:
|
|
112
|
+
graph = build_graph([rule], _Blocks(blocks), repo=repo)
|
|
113
|
+
except GraphError as exc:
|
|
114
|
+
raise AppError(str(exc)) from exc
|
|
115
|
+
gates: dict[str, PlanGate] = {}
|
|
116
|
+
for instance in graph.instances.values():
|
|
117
|
+
block = instance.params["block"]
|
|
118
|
+
plan_path = next(
|
|
119
|
+
(ref.path for ref in instance.inputs if ref.path is not None),
|
|
120
|
+
DEFAULT_PLAN_PATH.format(block=block),
|
|
121
|
+
)
|
|
122
|
+
approved = instance.outputs[0].path or ""
|
|
123
|
+
gates[block] = PlanGate(
|
|
124
|
+
block=block,
|
|
125
|
+
gate_id=gate_id_for(rule.gate, instance.params),
|
|
126
|
+
instance=instance,
|
|
127
|
+
plan_path=plan_path,
|
|
128
|
+
approved_path=approved,
|
|
129
|
+
)
|
|
130
|
+
return gates
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def gate_status(ctx: AppContext, gate: PlanGate) -> GateStatus:
|
|
134
|
+
"""The status of a block's plan gate, at the plan's current hash."""
|
|
135
|
+
return ctx.gates.status(gate.gate_id, gate.instance)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def within_limits(report: PlanReport, limits: PlanCfg) -> bool:
|
|
139
|
+
"""The plan is inside the profile's limits (checked again, apart from `plan_check`)."""
|
|
140
|
+
plan = report.plan
|
|
141
|
+
if plan is None:
|
|
142
|
+
return False
|
|
143
|
+
return (
|
|
144
|
+
len(plan.modules) <= limits.max_modules
|
|
145
|
+
and dependency_depth(plan) <= limits.max_depth
|
|
146
|
+
and sum(m.budget.tries for m in plan.modules) <= limits.max_total_tries
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class ApprovedPlan:
|
|
152
|
+
"""A block's plan, approved at its current hash and still valid."""
|
|
153
|
+
|
|
154
|
+
gate: PlanGate
|
|
155
|
+
plan: Plan
|
|
156
|
+
vars: Mapping[str, str] = field(default_factory=dict)
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def block(self) -> str:
|
|
160
|
+
return self.gate.block
|
|
161
|
+
|
|
162
|
+
def module_writes(self) -> tuple[str, ...]:
|
|
163
|
+
"""Every file of the plan's write sets, in build order."""
|
|
164
|
+
return tuple(
|
|
165
|
+
path
|
|
166
|
+
for name in topo_order(self.plan)
|
|
167
|
+
if (module := self.plan.module(name)) is not None
|
|
168
|
+
for path in module.write_paths
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _vars(resolved: ResolvedProfile, block: str) -> dict[str, str]:
|
|
173
|
+
try:
|
|
174
|
+
return layout_vars(resolved, block)
|
|
175
|
+
except LayoutError as exc:
|
|
176
|
+
raise AppError(str(exc)) from exc
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def approved_plan(
|
|
180
|
+
ctx: AppContext, rules: Sequence[RuleSpec], gate: PlanGate
|
|
181
|
+
) -> ApprovedPlan | None:
|
|
182
|
+
"""The block's plan when it is approved at its current hash and valid, else None."""
|
|
183
|
+
if gate_status(ctx, gate) != "approved":
|
|
184
|
+
return None
|
|
185
|
+
resolved = ctx.require_profile()
|
|
186
|
+
try:
|
|
187
|
+
report = check_plan_file(
|
|
188
|
+
ctx.root,
|
|
189
|
+
gate.block,
|
|
190
|
+
plan_path=gate.plan_path,
|
|
191
|
+
resolved=resolved,
|
|
192
|
+
rules=rules,
|
|
193
|
+
check_existing=False, # checked before approval; now the files are the plan's
|
|
194
|
+
)
|
|
195
|
+
except PlanEnvError:
|
|
196
|
+
return None
|
|
197
|
+
if not report.ok or report.plan is None or not within_limits(report, resolved.profile.plan):
|
|
198
|
+
return None
|
|
199
|
+
return ApprovedPlan(gate=gate, plan=report.plan, vars=_vars(resolved, gate.block))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def approved_plans(ctx: AppContext, rules: Sequence[RuleSpec]) -> dict[str, ApprovedPlan]:
|
|
203
|
+
"""Every block's `ApprovedPlan`, for the blocks that have one."""
|
|
204
|
+
resolved = ctx.require_profile()
|
|
205
|
+
gates = plan_gates(rules, resolved.profile.blocks, repo=ctx.store.repo)
|
|
206
|
+
found: dict[str, ApprovedPlan] = {}
|
|
207
|
+
for block in sorted(gates):
|
|
208
|
+
approved = approved_plan(ctx, rules, gates[block])
|
|
209
|
+
if approved is not None:
|
|
210
|
+
found[block] = approved
|
|
211
|
+
return found
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _naming_file(root: Path, resolved: ResolvedProfile, block: str) -> str | None:
|
|
215
|
+
"""The naming rules file the profile names, repo-relative, when it is in the repo."""
|
|
216
|
+
from chipgraph.checks.naming import _resolve_rules_path, _RulesError
|
|
217
|
+
from chipgraph.packs.digital_rtl.rtl.context import _naming_ref
|
|
218
|
+
|
|
219
|
+
ref = _naming_ref(resolved, block)
|
|
220
|
+
if ref is None:
|
|
221
|
+
return None
|
|
222
|
+
try:
|
|
223
|
+
path = _resolve_rules_path(ref, root)
|
|
224
|
+
except _RulesError:
|
|
225
|
+
return None
|
|
226
|
+
try:
|
|
227
|
+
rel = path.resolve().relative_to(root.resolve()).as_posix()
|
|
228
|
+
except ValueError:
|
|
229
|
+
return None
|
|
230
|
+
return rel if path.is_file() else None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def style_inputs(
|
|
234
|
+
ctx: AppContext,
|
|
235
|
+
rules: Sequence[RuleSpec],
|
|
236
|
+
block: str,
|
|
237
|
+
*,
|
|
238
|
+
planned: Iterable[str] = (),
|
|
239
|
+
) -> tuple[str, ...]:
|
|
240
|
+
"""The style files a planned module is written after: exemplars and naming rules.
|
|
241
|
+
|
|
242
|
+
Only files that exist; never one in `planned` (a file an approved plan writes:
|
|
243
|
+
it would be an input of the module that writes it), and never one a role of an
|
|
244
|
+
agent rule over `plan.modules` must not read.
|
|
245
|
+
"""
|
|
246
|
+
resolved = ctx.require_profile()
|
|
247
|
+
roles = [
|
|
248
|
+
role
|
|
249
|
+
for rule in rules
|
|
250
|
+
if rule.foreach == PLAN_MODULES and rule.kind == "agent" and rule.role is not None
|
|
251
|
+
if (role := find_role(rule.role)) is not None
|
|
252
|
+
]
|
|
253
|
+
skip = set(planned)
|
|
254
|
+
candidates = [*resolved.for_block(block).style.exemplars]
|
|
255
|
+
naming = _naming_file(ctx.root, resolved, block)
|
|
256
|
+
if naming is not None:
|
|
257
|
+
candidates.append(naming)
|
|
258
|
+
out: list[str] = []
|
|
259
|
+
for rel in dict.fromkeys(candidates):
|
|
260
|
+
if rel in skip or not (ctx.root / rel).is_file():
|
|
261
|
+
continue
|
|
262
|
+
if any(denied_kind(role, kind_for(rel), rel) is not None for role in roles):
|
|
263
|
+
continue
|
|
264
|
+
out.append(rel)
|
|
265
|
+
return tuple(out)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def module_entries(
|
|
269
|
+
ctx: AppContext,
|
|
270
|
+
rules: Sequence[RuleSpec],
|
|
271
|
+
approved: ApprovedPlan,
|
|
272
|
+
*,
|
|
273
|
+
planned: Iterable[str] = (),
|
|
274
|
+
) -> list[ForeachEntry]:
|
|
275
|
+
"""The `plan.modules` entries of one block's approved plan."""
|
|
276
|
+
plan = approved.plan
|
|
277
|
+
style = style_inputs(ctx, rules, approved.block, planned=planned)
|
|
278
|
+
entries: list[ForeachEntry] = []
|
|
279
|
+
for name in topo_order(plan):
|
|
280
|
+
module = plan.module(name)
|
|
281
|
+
assert module is not None
|
|
282
|
+
entries.append(
|
|
283
|
+
ForeachEntry(
|
|
284
|
+
params={"block": approved.block, "module": module.name},
|
|
285
|
+
inputs=(approved.gate.approved_path, *style),
|
|
286
|
+
after=tuple(
|
|
287
|
+
{"block": approved.block, "module": dep}
|
|
288
|
+
for dep in dict.fromkeys(module.depends_on)
|
|
289
|
+
),
|
|
290
|
+
writes=frozenset(module.write_paths),
|
|
291
|
+
vars=dict(approved.vars),
|
|
292
|
+
)
|
|
293
|
+
)
|
|
294
|
+
return entries
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def block_entries(ctx: AppContext, rules: Sequence[RuleSpec], gate: PlanGate) -> list[ForeachEntry]:
|
|
298
|
+
"""The `plan.modules` entries of one block: none unless its plan is approved and valid."""
|
|
299
|
+
approved = approved_plan(ctx, rules, gate)
|
|
300
|
+
if approved is None:
|
|
301
|
+
return []
|
|
302
|
+
return module_entries(ctx, rules, approved, planned=approved.module_writes())
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def testbench_files(
|
|
306
|
+
rules: Sequence[RuleSpec], block: str, values: Mapping[str, str]
|
|
307
|
+
) -> tuple[str, ...]:
|
|
308
|
+
"""The block's testbench: the outputs of the `tb-author` rules over `blocks`."""
|
|
309
|
+
out: list[str] = []
|
|
310
|
+
for rule in sorted(rules, key=lambda r: r.id):
|
|
311
|
+
if rule.kind == "agent" and rule.role == TB_ROLE and rule.foreach == "blocks":
|
|
312
|
+
out.extend(fill_vars(t, {**values, "block": block}) for t in rule.outputs)
|
|
313
|
+
return tuple(dict.fromkeys(out))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def block_files(
|
|
317
|
+
rules: Sequence[RuleSpec], approved: ApprovedPlan
|
|
318
|
+
) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
|
319
|
+
"""The block's RTL (every module's write set) and testbench files."""
|
|
320
|
+
return approved.module_writes(), testbench_files(rules, approved.block, approved.vars)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def plan_block_entry(rules: Sequence[RuleSpec], approved: ApprovedPlan) -> ForeachEntry:
|
|
324
|
+
"""The `plan.block` / `plan.blocks` entry of a block with an approved plan."""
|
|
325
|
+
rtl, tb = block_files(rules, approved)
|
|
326
|
+
return ForeachEntry(
|
|
327
|
+
params={"block": approved.block},
|
|
328
|
+
inputs=tuple(dict.fromkeys((approved.gate.approved_path, *rtl, *tb))),
|
|
329
|
+
vars=dict(approved.vars),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@dataclass
|
|
334
|
+
class PlanExpansion:
|
|
335
|
+
"""The approved plans of a project, read once per graph, and the entries they give."""
|
|
336
|
+
|
|
337
|
+
ctx: AppContext
|
|
338
|
+
rules: Sequence[RuleSpec]
|
|
339
|
+
_approved: dict[str, ApprovedPlan] | None = None
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def approved(self) -> dict[str, ApprovedPlan]:
|
|
343
|
+
if self._approved is None:
|
|
344
|
+
self._approved = approved_plans(self.ctx, self.rules)
|
|
345
|
+
return self._approved
|
|
346
|
+
|
|
347
|
+
def modules(self) -> list[ForeachEntry]:
|
|
348
|
+
"""`plan.modules`: every module of every approved plan."""
|
|
349
|
+
planned = {p for a in self.approved.values() for p in a.module_writes()}
|
|
350
|
+
entries: list[ForeachEntry] = []
|
|
351
|
+
for block in sorted(self.approved):
|
|
352
|
+
entries.extend(
|
|
353
|
+
module_entries(self.ctx, self.rules, self.approved[block], planned=planned)
|
|
354
|
+
)
|
|
355
|
+
return entries
|
|
356
|
+
|
|
357
|
+
def blocks_with_plan(self) -> list[ForeachEntry]:
|
|
358
|
+
"""`plan.block`: one entry per block with an approved plan."""
|
|
359
|
+
return [plan_block_entry(self.rules, self.approved[b]) for b in sorted(self.approved)]
|
|
360
|
+
|
|
361
|
+
def every_block(self) -> list[ForeachEntry]:
|
|
362
|
+
"""`plan.blocks`: every block; an approved plan's files as inputs when it has one."""
|
|
363
|
+
resolved = self.ctx.require_profile()
|
|
364
|
+
entries: list[ForeachEntry] = []
|
|
365
|
+
for block in sorted(resolved.profile.blocks):
|
|
366
|
+
approved = self.approved.get(block)
|
|
367
|
+
if approved is not None:
|
|
368
|
+
entries.append(plan_block_entry(self.rules, approved))
|
|
369
|
+
else:
|
|
370
|
+
entries.append(ForeachEntry(params={"block": block}, vars=_vars(resolved, block)))
|
|
371
|
+
return entries
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def plan_modules(ctx: AppContext, rules: Sequence[RuleSpec]) -> list[ForeachEntry]:
|
|
375
|
+
"""`foreach: plan.modules`: every module of every block's approved, valid plan."""
|
|
376
|
+
return PlanExpansion(ctx, rules).modules()
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
__all__ = [
|
|
380
|
+
"EXPAND_RULE",
|
|
381
|
+
"PLAN_BLOCK",
|
|
382
|
+
"PLAN_BLOCKS",
|
|
383
|
+
"PLAN_RULE",
|
|
384
|
+
"TB_ROLE",
|
|
385
|
+
"ApprovedPlan",
|
|
386
|
+
"PlanExpansion",
|
|
387
|
+
"PlanGate",
|
|
388
|
+
"approved_plan",
|
|
389
|
+
"approved_plans",
|
|
390
|
+
"block_entries",
|
|
391
|
+
"block_files",
|
|
392
|
+
"gate_status",
|
|
393
|
+
"module_entries",
|
|
394
|
+
"plan_block_entry",
|
|
395
|
+
"plan_gates",
|
|
396
|
+
"plan_modules",
|
|
397
|
+
"style_inputs",
|
|
398
|
+
"testbench_files",
|
|
399
|
+
"within_limits",
|
|
400
|
+
]
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""`chipgraph plan show <block>`: what the approver of gate `plan:<block>` reads.
|
|
2
|
+
|
|
3
|
+
The summary: the gate's status (and the command that approves it), the check's errors
|
|
4
|
+
and warnings, the modules (summary, requirements, dependencies, write sets, ports,
|
|
5
|
+
budget) in build order, requirement coverage, the limits used, the assumptions and the
|
|
6
|
+
open questions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from chipgraph.app.build import load_rules
|
|
14
|
+
from chipgraph.app.context import AppContext
|
|
15
|
+
from chipgraph.app.errors import AppError
|
|
16
|
+
from chipgraph.checks.plan_check import (
|
|
17
|
+
PlanEnvError,
|
|
18
|
+
check_plan_file,
|
|
19
|
+
dependency_depth,
|
|
20
|
+
topo_order,
|
|
21
|
+
)
|
|
22
|
+
from chipgraph.packs.digital_rtl.plan.resolver import gate_status, plan_gates
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def plan_summary(ctx: AppContext, block: str) -> dict[str, Any]:
|
|
26
|
+
"""The summary of `block`'s plan, as plain data (`--json` prints it as is)."""
|
|
27
|
+
resolved = ctx.require_profile()
|
|
28
|
+
if block not in resolved.profile.blocks:
|
|
29
|
+
raise AppError(
|
|
30
|
+
f"unknown block {block!r}; blocks: {', '.join(sorted(resolved.profile.blocks))}"
|
|
31
|
+
)
|
|
32
|
+
rules = load_rules(ctx)
|
|
33
|
+
gate = plan_gates(rules, resolved.profile.blocks, repo=ctx.store.repo)[block]
|
|
34
|
+
try:
|
|
35
|
+
report = check_plan_file(
|
|
36
|
+
ctx.root, block, plan_path=gate.plan_path, resolved=resolved, rules=rules
|
|
37
|
+
)
|
|
38
|
+
except PlanEnvError as exc:
|
|
39
|
+
raise AppError(str(exc)) from exc
|
|
40
|
+
status = gate_status(ctx, gate)
|
|
41
|
+
summary: dict[str, Any] = {
|
|
42
|
+
"block": block,
|
|
43
|
+
"path": report.path,
|
|
44
|
+
"exists": report.exists,
|
|
45
|
+
"sha256": report.sha256,
|
|
46
|
+
"gate": {
|
|
47
|
+
"id": gate.gate_id,
|
|
48
|
+
"status": status,
|
|
49
|
+
"instance": gate.instance.instance_id,
|
|
50
|
+
"approve": (
|
|
51
|
+
f"chipgraph approve {gate.gate_id} --instance '{gate.instance.instance_id}'"
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
"ok": report.ok,
|
|
55
|
+
"errors": [_issue(i) for i in report.issues if i.severity == "error"],
|
|
56
|
+
"warnings": [_issue(i) for i in report.issues if i.severity == "warning"],
|
|
57
|
+
}
|
|
58
|
+
plan = report.plan
|
|
59
|
+
if plan is None:
|
|
60
|
+
return summary
|
|
61
|
+
limits = resolved.profile.plan
|
|
62
|
+
order = topo_order(plan)
|
|
63
|
+
summary.update(
|
|
64
|
+
{
|
|
65
|
+
"top": plan.top,
|
|
66
|
+
"order": order,
|
|
67
|
+
"modules": [
|
|
68
|
+
{
|
|
69
|
+
"name": m.name,
|
|
70
|
+
"summary": m.summary,
|
|
71
|
+
"reqs": list(m.reqs),
|
|
72
|
+
"depends_on": list(m.depends_on),
|
|
73
|
+
"writes": [w.model_dump(mode="json") for w in m.writes],
|
|
74
|
+
"ports": [p.model_dump(mode="json") for p in m.interface.ports],
|
|
75
|
+
"budget": m.budget.model_dump(mode="json"),
|
|
76
|
+
}
|
|
77
|
+
for name in order
|
|
78
|
+
if (m := plan.module(name)) is not None
|
|
79
|
+
],
|
|
80
|
+
"coverage": _coverage(report),
|
|
81
|
+
"limits": {
|
|
82
|
+
"modules": [len(plan.modules), limits.max_modules],
|
|
83
|
+
"depth": [dependency_depth(plan), limits.max_depth],
|
|
84
|
+
"tries": [sum(m.budget.tries for m in plan.modules), limits.max_total_tries],
|
|
85
|
+
},
|
|
86
|
+
"assumptions": list(plan.assumptions),
|
|
87
|
+
"open_questions": list(plan.open_questions),
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
return summary
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _issue(issue: Any) -> str:
|
|
94
|
+
where = f"{issue.file}:{issue.line}" if issue.line else str(issue.file)
|
|
95
|
+
return f"{where} [{issue.rule}] {issue.msg}"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _coverage(report: Any) -> list[dict[str, Any]]:
|
|
99
|
+
"""Each block requirement: the modules covering it, or why it is unassigned, or missing."""
|
|
100
|
+
plan = report.plan
|
|
101
|
+
env = report.env
|
|
102
|
+
if plan is None or env is None:
|
|
103
|
+
return []
|
|
104
|
+
rows: list[dict[str, Any]] = []
|
|
105
|
+
for req in env.requirements:
|
|
106
|
+
modules = [m.name for m in plan.modules if req.refs & set(m.reqs)]
|
|
107
|
+
reasons = [u.reason for u in plan.unassigned_reqs if u.req in req.refs]
|
|
108
|
+
rows.append(
|
|
109
|
+
{
|
|
110
|
+
"req": req.ref,
|
|
111
|
+
"modules": modules,
|
|
112
|
+
"unassigned": reasons[0] if reasons and not modules else None,
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
return rows
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_text(summary: dict[str, Any]) -> str:
|
|
119
|
+
"""The summary as text, for a person."""
|
|
120
|
+
lines = [f"plan for block {summary['block']}: {summary['path']}"]
|
|
121
|
+
gate = summary["gate"]
|
|
122
|
+
lines.append(f"gate {gate['id']}: {gate['status']}")
|
|
123
|
+
if not summary["exists"]:
|
|
124
|
+
lines.append("no plan yet: build rule digital-rtl/plan for this block first")
|
|
125
|
+
return "\n".join(lines) + "\n"
|
|
126
|
+
lines.append(f"check: {'ok' if summary['ok'] else 'FAILS'}")
|
|
127
|
+
for error in summary["errors"]:
|
|
128
|
+
lines.append(f" error {error}")
|
|
129
|
+
for warning in summary["warnings"]:
|
|
130
|
+
lines.append(f" warning {warning}")
|
|
131
|
+
if "modules" not in summary:
|
|
132
|
+
return "\n".join(lines) + "\n"
|
|
133
|
+
|
|
134
|
+
limits = summary["limits"]
|
|
135
|
+
lines.append(
|
|
136
|
+
f"top: {summary['top']} modules {limits['modules'][0]}/{limits['modules'][1]}, "
|
|
137
|
+
f"depth {limits['depth'][0]}/{limits['depth'][1]}, "
|
|
138
|
+
f"tries {limits['tries'][0]}/{limits['tries'][1]}"
|
|
139
|
+
)
|
|
140
|
+
lines.append("")
|
|
141
|
+
lines.append("modules (build order):")
|
|
142
|
+
for m in summary["modules"]:
|
|
143
|
+
lines.append(f" {m['name']}: {m['summary']}")
|
|
144
|
+
lines.append(f" reqs: {', '.join(m['reqs']) or '-'}")
|
|
145
|
+
lines.append(f" depends on: {', '.join(m['depends_on']) or '-'}")
|
|
146
|
+
writes = [w["path"] + (" (replaces)" if w["replaces"] else "") for w in m["writes"]]
|
|
147
|
+
lines.append(f" writes: {', '.join(writes)}")
|
|
148
|
+
ports = [
|
|
149
|
+
f"{p['name']}:{p['direction']}[{p['width']}]" + (" internal" if p["internal"] else "")
|
|
150
|
+
for p in m["ports"]
|
|
151
|
+
]
|
|
152
|
+
lines.append(f" ports: {', '.join(ports) or '-'}")
|
|
153
|
+
lines.append(f" budget: {m['budget']['tries']} tries, tier {m['budget']['tier']}")
|
|
154
|
+
lines.append("")
|
|
155
|
+
lines.append("requirement coverage:")
|
|
156
|
+
for row in summary["coverage"]:
|
|
157
|
+
if row["modules"]:
|
|
158
|
+
lines.append(f" {row['req']}: {', '.join(row['modules'])}")
|
|
159
|
+
elif row["unassigned"]:
|
|
160
|
+
lines.append(f" {row['req']}: unassigned ({row['unassigned']})")
|
|
161
|
+
else:
|
|
162
|
+
lines.append(f" {row['req']}: NOT COVERED")
|
|
163
|
+
if not summary["coverage"]:
|
|
164
|
+
lines.append(" (the block has no requirements in the Design Model)")
|
|
165
|
+
lines.append("")
|
|
166
|
+
lines.append("assumptions:")
|
|
167
|
+
lines.extend(f" - {a}" for a in summary["assumptions"] or ["none"])
|
|
168
|
+
lines.append("open questions (read before approving):")
|
|
169
|
+
lines.extend(f" - {q}" for q in summary["open_questions"] or ["none"])
|
|
170
|
+
if gate["status"] != "approved":
|
|
171
|
+
lines.append("")
|
|
172
|
+
lines.append(f"approve: {gate['approve']}")
|
|
173
|
+
return "\n".join(lines) + "\n"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
__all__ = ["plan_summary", "render_text"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""The pull request of a block's planned RTL (M2-11a): its branch, its body, its gate.
|
|
2
|
+
|
|
3
|
+
- `open`: the `open_pr` tool of rule `digital-rtl/open_pr`: builds the branch
|
|
4
|
+
`chipgraph/<run id>/<block>` from the block's RTL and testbench files without touching
|
|
5
|
+
the user's checkout, pushes it, opens the PR and writes `pr/<block>.json`;
|
|
6
|
+
- `gate`: the `pr_gate` tool of rule `digital-rtl/pr_gate`, gated by `pr:<block>`;
|
|
7
|
+
- `body`: the PR body: the plan, requirement coverage, check results and the Critic's
|
|
8
|
+
verdict, factual and short.
|
|
9
|
+
|
|
10
|
+
Nothing here merges a pull request, or asks GitHub to.
|
|
11
|
+
"""
|