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,596 @@
|
|
|
1
|
+
"""The interface a testbench is written against, without the RTL (DESIGN.md 5.3 F1, 4.4).
|
|
2
|
+
|
|
3
|
+
`interface_for(module, ...)` is pure: it reads the Design Model it is given and, only
|
|
4
|
+
when it needs one, asks `declaration` for the module's declaration. The port list comes
|
|
5
|
+
from the first of these that has it:
|
|
6
|
+
|
|
7
|
+
1. ``spec``: the block's spec ports in the Design Model (`port:spec.<block>.<name>`);
|
|
8
|
+
2. ``model_rtl``: the module's RTL port entities already in the Design Model (they came
|
|
9
|
+
from declarations, by `chipgraph ingest`);
|
|
10
|
+
3. ``rtl_declaration``: only when neither exists (a new module, a model not ingested, a
|
|
11
|
+
port list missing), the module's declaration read from the RTL with pyslang
|
|
12
|
+
(`chipgraph.packs.dv.declaration`: the header only, never the body).
|
|
13
|
+
|
|
14
|
+
Every port carries its `source`. When the spec ports are used and RTL ports are known
|
|
15
|
+
(2 or 3), each spec port that the RTL declares differently gets the note `rtl differs`,
|
|
16
|
+
with the RTL's direction and width only when they came from the model (2), never when
|
|
17
|
+
they came from reading the RTL (3); ports only the RTL has are counted in `notes`. The
|
|
18
|
+
difference itself is the `ports_diff` check's to report: nothing is resolved here.
|
|
19
|
+
|
|
20
|
+
`spec_slice(model, block)` is the block's spec side of the Design Model for a role that
|
|
21
|
+
must not see RTL: requirements (id, text, where), registers and fields, interrupts,
|
|
22
|
+
memory regions, clock and reset. No module, RTL port or RTL file is in it.
|
|
23
|
+
|
|
24
|
+
`project_interface(...)` wires `interface_for` to a project: the module (instance param
|
|
25
|
+
`module`, else the block's top module in the model, else the `top` template of the
|
|
26
|
+
profile's `tb_static` adapter) and the RTL files a declaration may come from (that
|
|
27
|
+
module's file in the model, the `tb_static` adapter's `files`/`filelist`, else the
|
|
28
|
+
profile layout's `filelist` and `rtl` entries for the block). The engine calls it for a
|
|
29
|
+
testbench task's context and `tb_static` calls it for its port check, so both see the
|
|
30
|
+
same interface.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import re
|
|
36
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Any, Literal
|
|
39
|
+
|
|
40
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
41
|
+
|
|
42
|
+
from chipgraph.adapters.tool.filelist import FilelistError, read_filelist
|
|
43
|
+
from chipgraph.core.model.entities import (
|
|
44
|
+
BlockEntity,
|
|
45
|
+
FieldEntity,
|
|
46
|
+
InterruptEntity,
|
|
47
|
+
MemoryRegionEntity,
|
|
48
|
+
ModuleEntity,
|
|
49
|
+
PortEntity,
|
|
50
|
+
RegisterEntity,
|
|
51
|
+
RequirementEntity,
|
|
52
|
+
ResetEntity,
|
|
53
|
+
)
|
|
54
|
+
from chipgraph.core.model.model import DesignModel
|
|
55
|
+
from chipgraph.packs.dv.declaration import ModuleDeclaration, extract_declaration
|
|
56
|
+
|
|
57
|
+
PortSource = Literal["spec", "model_rtl", "rtl_declaration"]
|
|
58
|
+
|
|
59
|
+
RTL_DIFFERS = "rtl differs"
|
|
60
|
+
"""The note on a spec port the RTL declares differently (see `ports_diff`)."""
|
|
61
|
+
MISSING_IN_RTL = "missing in rtl"
|
|
62
|
+
"""The note on a spec port the RTL does not declare."""
|
|
63
|
+
|
|
64
|
+
CLOCK_PATTERNS = (r"^(?:i_)?clk", r"_clk(?:_i)?$", r"^clock")
|
|
65
|
+
"""Port names taken as a clock when the model names none."""
|
|
66
|
+
RESET_PATTERNS = (r"^(?:i_)?rst", r"_rst(?:_n|_ni)?$", r"^reset")
|
|
67
|
+
"""Port names taken as a reset when the model names none."""
|
|
68
|
+
|
|
69
|
+
LayoutValue = str | tuple[str, ...] | list[str]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class InterfacePort(BaseModel):
|
|
73
|
+
"""One port of the interface, with where it was learnt."""
|
|
74
|
+
|
|
75
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
76
|
+
|
|
77
|
+
schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
|
|
78
|
+
name: str = Field(description="The port name: `dut.<name>` in a cocotb test.")
|
|
79
|
+
direction: str | None = Field(default=None, description="input, output, inout (or ref).")
|
|
80
|
+
width: int | str | None = Field(default=None, description="Bits, or an expression.")
|
|
81
|
+
type: str = Field(default="", description="Declared type, from a declaration only.")
|
|
82
|
+
packed: str = Field(default="", description="Packed dimensions, from a declaration only.")
|
|
83
|
+
unpacked: str = Field(default="", description="Unpacked dimensions, from a declaration.")
|
|
84
|
+
description: str = Field(default="", description="The spec's description of the port.")
|
|
85
|
+
source: PortSource = Field(description="Where the port was learnt.")
|
|
86
|
+
note: str | None = Field(default=None, description="`rtl differs` / `missing in rtl`.")
|
|
87
|
+
rtl: dict[str, Any] | None = Field(
|
|
88
|
+
default=None,
|
|
89
|
+
description="The RTL's direction and width when they differ (model RTL ports only).",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class InterfaceParameter(BaseModel):
|
|
94
|
+
"""A parameter of the module's header (from a declaration only)."""
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
97
|
+
|
|
98
|
+
schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
|
|
99
|
+
name: str = Field(description="The parameter name.")
|
|
100
|
+
kind: str = Field(description="'parameter', 'localparam' or 'type'.")
|
|
101
|
+
type: str = Field(default="", description="Its declared type.")
|
|
102
|
+
default: str | None = Field(default=None, description="Its default expression.")
|
|
103
|
+
source: PortSource = Field(default="rtl_declaration", description="Where it was learnt.")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Interface(BaseModel):
|
|
107
|
+
"""The ports (and header parameters) a testbench may drive and sample."""
|
|
108
|
+
|
|
109
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
110
|
+
|
|
111
|
+
schema_version: Literal[1] = Field(default=1, description="Schema version of this model.")
|
|
112
|
+
module: str | None = Field(default=None, description="The module under test (toplevel).")
|
|
113
|
+
block: str | None = Field(default=None, description="Its block.")
|
|
114
|
+
source: PortSource | None = Field(
|
|
115
|
+
default=None, description="Where the port list came from; None when none was found."
|
|
116
|
+
)
|
|
117
|
+
ports: tuple[InterfacePort, ...] = Field(default=(), description="The ports, in order.")
|
|
118
|
+
parameters: tuple[InterfaceParameter, ...] = Field(
|
|
119
|
+
default=(), description="The module header's parameters (from a declaration)."
|
|
120
|
+
)
|
|
121
|
+
clock: str | None = Field(default=None, description="The clock port, if known.")
|
|
122
|
+
reset: str | None = Field(default=None, description="The reset port, if known.")
|
|
123
|
+
reset_active_low: bool | None = Field(default=None, description="Whether reset is low.")
|
|
124
|
+
notes: tuple[str, ...] = Field(default=(), description="What the reader should know.")
|
|
125
|
+
truncated: bool = Field(default=False, description="Whether the declaration was cut.")
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def port_names(self) -> frozenset[str]:
|
|
129
|
+
"""Every port name."""
|
|
130
|
+
return frozenset(p.name for p in self.ports)
|
|
131
|
+
|
|
132
|
+
def view(self) -> dict[str, Any]:
|
|
133
|
+
"""The interface as a task's context shows it: unset port fields left out."""
|
|
134
|
+
out = self.model_dump(mode="json", exclude={"schema_version", "ports", "parameters"})
|
|
135
|
+
out["ports"] = [
|
|
136
|
+
p.model_dump(mode="json", exclude_defaults=True, exclude={"schema_version"})
|
|
137
|
+
| {"source": p.source}
|
|
138
|
+
for p in self.ports
|
|
139
|
+
]
|
|
140
|
+
out["parameters"] = [
|
|
141
|
+
p.model_dump(mode="json", exclude_defaults=True, exclude={"schema_version"})
|
|
142
|
+
| {"source": p.source}
|
|
143
|
+
for p in self.parameters
|
|
144
|
+
]
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
DeclarationLoader = Callable[[str | None], ModuleDeclaration | None]
|
|
149
|
+
"""Reads the declaration of a module (None: the only one) from the RTL, on demand."""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# --- the model ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _spec_ports(model: DesignModel, block: str) -> list[PortEntity]:
|
|
156
|
+
prefix = f"port:spec.{block}."
|
|
157
|
+
ports = [p for p in model.by_kind("port") if isinstance(p, PortEntity)]
|
|
158
|
+
found = [p for p in ports if p.key.startswith(prefix)]
|
|
159
|
+
return sorted(found, key=lambda p: (p.source.line or 0, p.key))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _module(model: DesignModel, name: str) -> ModuleEntity | None:
|
|
163
|
+
for entity in model.by_kind("module"):
|
|
164
|
+
if isinstance(entity, ModuleEntity) and entity.name == name:
|
|
165
|
+
return entity
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _rtl_ports(model: DesignModel, module: str) -> list[PortEntity]:
|
|
170
|
+
key = f"module:{module}"
|
|
171
|
+
ports = [p for p in model.by_kind("port") if isinstance(p, PortEntity) and p.module == key]
|
|
172
|
+
return sorted(ports, key=lambda p: (p.source.line or 0, p.key))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def top_module(model: DesignModel, block: str) -> str | None:
|
|
176
|
+
"""The block's top RTL module in the model: the one module it owns that no other
|
|
177
|
+
module it owns instantiates (None when there is not exactly one)."""
|
|
178
|
+
key = f"block:{block}"
|
|
179
|
+
owned = [m for m in model.by_kind("module") if isinstance(m, ModuleEntity) and m.block == key]
|
|
180
|
+
inner = {r.dst for m in owned for r in model.get_relations(src=m.key, kind="instantiates")}
|
|
181
|
+
roots = [m for m in owned if m.key not in inner]
|
|
182
|
+
return roots[0].name if len(roots) == 1 else None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _block_of(model: DesignModel, module: str) -> str | None:
|
|
186
|
+
entity = _module(model, module)
|
|
187
|
+
if entity is None or entity.block is None:
|
|
188
|
+
return None
|
|
189
|
+
return entity.block.partition(":")[2] or None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _block_entity(model: DesignModel, block: str) -> BlockEntity | None:
|
|
193
|
+
entity = model.get(f"block:{block}")
|
|
194
|
+
return entity if isinstance(entity, BlockEntity) else None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# --- clock and reset -----------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _matches(name: str, patterns: Iterable[str]) -> bool:
|
|
201
|
+
return any(re.search(p, name, re.IGNORECASE) for p in patterns)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _clock_reset(
|
|
205
|
+
model: DesignModel | None, block: str | None, names: list[str]
|
|
206
|
+
) -> tuple[str | None, str | None, bool | None]:
|
|
207
|
+
clock = reset = None
|
|
208
|
+
active_low: bool | None = None
|
|
209
|
+
if model is not None and block is not None:
|
|
210
|
+
entity = _block_entity(model, block)
|
|
211
|
+
if entity is not None:
|
|
212
|
+
c, r = entity.attrs.get("clock"), entity.attrs.get("reset")
|
|
213
|
+
clock = c if isinstance(c, str) and c in names else None
|
|
214
|
+
reset = r if isinstance(r, str) and r in names else None
|
|
215
|
+
if reset is not None:
|
|
216
|
+
found = model.get(f"reset:{reset}")
|
|
217
|
+
if isinstance(found, ResetEntity):
|
|
218
|
+
active_low = found.active_low
|
|
219
|
+
if clock is None:
|
|
220
|
+
clock = next((n for n in names if _matches(n, CLOCK_PATTERNS)), None)
|
|
221
|
+
if reset is None:
|
|
222
|
+
reset = next((n for n in names if n != clock and _matches(n, RESET_PATTERNS)), None)
|
|
223
|
+
if reset is not None and active_low is None:
|
|
224
|
+
active_low = re.search(r"(?:_n|_ni|_b|n)$", reset.lower()) is not None
|
|
225
|
+
return clock, reset, active_low
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# --- the provider --------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _from_declaration(decl: ModuleDeclaration) -> list[InterfacePort]:
|
|
232
|
+
return [
|
|
233
|
+
InterfacePort(
|
|
234
|
+
name=p.name,
|
|
235
|
+
direction=p.direction,
|
|
236
|
+
width=p.width,
|
|
237
|
+
type=p.type,
|
|
238
|
+
packed=p.packed,
|
|
239
|
+
unpacked=p.unpacked,
|
|
240
|
+
source="rtl_declaration",
|
|
241
|
+
)
|
|
242
|
+
for p in decl.ports
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _parameters(decl: ModuleDeclaration | None) -> tuple[InterfaceParameter, ...]:
|
|
247
|
+
if decl is None:
|
|
248
|
+
return ()
|
|
249
|
+
return tuple(
|
|
250
|
+
InterfaceParameter(name=p.name, kind=p.kind, type=p.type, default=p.default)
|
|
251
|
+
for p in decl.parameters
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _differs(spec: PortEntity, direction: str | None, width: int | str | None) -> bool:
|
|
256
|
+
if spec.direction is not None and direction is not None and spec.direction != direction:
|
|
257
|
+
return True
|
|
258
|
+
return isinstance(spec.width, int) and isinstance(width, int) and spec.width != width
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _spec_interface(
|
|
262
|
+
spec_ports: list[PortEntity],
|
|
263
|
+
rtl: list[PortEntity] | None,
|
|
264
|
+
decl: ModuleDeclaration | None,
|
|
265
|
+
) -> tuple[list[InterfacePort], list[str]]:
|
|
266
|
+
"""The spec ports, each compared with the RTL's when the RTL's are known."""
|
|
267
|
+
known: dict[str, tuple[str | None, int | str | None]] | None = None
|
|
268
|
+
from_model = rtl is not None
|
|
269
|
+
if rtl is not None:
|
|
270
|
+
known = {p.name: (p.direction, p.width) for p in rtl}
|
|
271
|
+
elif decl is not None:
|
|
272
|
+
known = {p.name: (p.direction, p.width) for p in decl.ports}
|
|
273
|
+
ports: list[InterfacePort] = []
|
|
274
|
+
for spec in spec_ports:
|
|
275
|
+
desc = spec.attrs.get("description")
|
|
276
|
+
note: str | None = None
|
|
277
|
+
rtl_value: dict[str, Any] | None = None
|
|
278
|
+
if known is not None:
|
|
279
|
+
if spec.name not in known:
|
|
280
|
+
note = MISSING_IN_RTL
|
|
281
|
+
elif _differs(spec, *known[spec.name]):
|
|
282
|
+
note = RTL_DIFFERS
|
|
283
|
+
if from_model:
|
|
284
|
+
rtl_value = {"direction": known[spec.name][0], "width": known[spec.name][1]}
|
|
285
|
+
ports.append(
|
|
286
|
+
InterfacePort(
|
|
287
|
+
name=spec.name,
|
|
288
|
+
direction=spec.direction,
|
|
289
|
+
width=spec.width,
|
|
290
|
+
description=desc if isinstance(desc, str) else "",
|
|
291
|
+
source="spec",
|
|
292
|
+
note=note,
|
|
293
|
+
rtl=rtl_value,
|
|
294
|
+
)
|
|
295
|
+
)
|
|
296
|
+
notes: list[str] = []
|
|
297
|
+
if known is not None:
|
|
298
|
+
extra = sorted(set(known) - {p.name for p in spec_ports})
|
|
299
|
+
if extra and from_model:
|
|
300
|
+
notes.append(
|
|
301
|
+
"the RTL has ports the spec does not list (not part of this interface; "
|
|
302
|
+
f"ports_diff reports them): {', '.join(extra)}"
|
|
303
|
+
)
|
|
304
|
+
elif extra:
|
|
305
|
+
notes.append(
|
|
306
|
+
f"the RTL declares {len(extra)} port(s) the spec does not list (not part of "
|
|
307
|
+
"this interface; ports_diff reports them)"
|
|
308
|
+
)
|
|
309
|
+
if any(p.note for p in ports):
|
|
310
|
+
notes.append(
|
|
311
|
+
f"ports marked '{RTL_DIFFERS}' or '{MISSING_IN_RTL}' disagree with the RTL; the "
|
|
312
|
+
"spec is what the test checks, ports_diff reports the difference"
|
|
313
|
+
)
|
|
314
|
+
return ports, notes
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def interface_for(
|
|
318
|
+
module: str | None,
|
|
319
|
+
*,
|
|
320
|
+
model: DesignModel | None = None,
|
|
321
|
+
block: str | None = None,
|
|
322
|
+
declaration: DeclarationLoader | ModuleDeclaration | None = None,
|
|
323
|
+
) -> Interface:
|
|
324
|
+
"""The interface of `module` (or of `block`'s top module), learnt without the RTL body.
|
|
325
|
+
|
|
326
|
+
`declaration` is called (with the module name, or None for "the only module") only
|
|
327
|
+
when the model has no RTL ports of the module: for the port list when the spec has
|
|
328
|
+
none either, else to compare the spec ports with.
|
|
329
|
+
"""
|
|
330
|
+
if model is not None and block is None and module is not None:
|
|
331
|
+
block = _block_of(model, module)
|
|
332
|
+
if model is not None and module is None and block is not None:
|
|
333
|
+
module = top_module(model, block)
|
|
334
|
+
|
|
335
|
+
def load() -> ModuleDeclaration | None:
|
|
336
|
+
if declaration is None or isinstance(declaration, ModuleDeclaration):
|
|
337
|
+
return declaration
|
|
338
|
+
return declaration(module)
|
|
339
|
+
|
|
340
|
+
spec_ports = _spec_ports(model, block) if model is not None and block is not None else []
|
|
341
|
+
rtl_ports = _rtl_ports(model, module) if model is not None and module is not None else []
|
|
342
|
+
decl: ModuleDeclaration | None = None
|
|
343
|
+
notes: list[str] = []
|
|
344
|
+
source: PortSource | None
|
|
345
|
+
if spec_ports:
|
|
346
|
+
source = "spec"
|
|
347
|
+
if not rtl_ports:
|
|
348
|
+
decl = load()
|
|
349
|
+
ports, notes = _spec_interface(spec_ports, rtl_ports or None, decl)
|
|
350
|
+
elif rtl_ports:
|
|
351
|
+
source = "model_rtl"
|
|
352
|
+
ports = [
|
|
353
|
+
InterfacePort(name=p.name, direction=p.direction, width=p.width, source="model_rtl")
|
|
354
|
+
for p in rtl_ports
|
|
355
|
+
]
|
|
356
|
+
notes.append("the spec lists no ports for this block: the RTL's declared ports are used")
|
|
357
|
+
else:
|
|
358
|
+
decl = load()
|
|
359
|
+
if decl is not None:
|
|
360
|
+
source = "rtl_declaration"
|
|
361
|
+
ports = _from_declaration(decl)
|
|
362
|
+
notes.append(
|
|
363
|
+
"neither the spec nor the Design Model lists this module's ports: they were "
|
|
364
|
+
"read from its declaration (header only)"
|
|
365
|
+
)
|
|
366
|
+
else:
|
|
367
|
+
source = None
|
|
368
|
+
ports = []
|
|
369
|
+
notes.append(
|
|
370
|
+
"no interface found: the spec lists no ports, the Design Model has none "
|
|
371
|
+
"(run `chipgraph ingest`), and no RTL declaration of the module was found; "
|
|
372
|
+
"report needs_human"
|
|
373
|
+
)
|
|
374
|
+
if decl is not None and module is None:
|
|
375
|
+
module = decl.module
|
|
376
|
+
clock, reset, active_low = _clock_reset(model, block, [p.name for p in ports])
|
|
377
|
+
return Interface(
|
|
378
|
+
module=module,
|
|
379
|
+
block=block,
|
|
380
|
+
source=source,
|
|
381
|
+
ports=tuple(ports),
|
|
382
|
+
parameters=_parameters(decl),
|
|
383
|
+
clock=clock,
|
|
384
|
+
reset=reset,
|
|
385
|
+
reset_active_low=active_low,
|
|
386
|
+
notes=tuple(notes),
|
|
387
|
+
truncated=decl.truncated if decl is not None else False,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# --- the spec slice ------------------------------------------------------------------------
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _where(entity: Any) -> str | None:
|
|
395
|
+
src = entity.source
|
|
396
|
+
if src.file is None:
|
|
397
|
+
return None
|
|
398
|
+
return f"{src.file}:{src.line}" if src.line else src.file
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _requirement_keys(model: DesignModel, block: str) -> list[str]:
|
|
402
|
+
key = f"block:{block}"
|
|
403
|
+
keys = {
|
|
404
|
+
r.key
|
|
405
|
+
for r in model.by_kind("requirement")
|
|
406
|
+
if isinstance(r, RequirementEntity) and r.attrs.get("block") == key
|
|
407
|
+
}
|
|
408
|
+
return sorted(keys)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def block_requirements(model: DesignModel, block: str) -> list[dict[str, Any]]:
|
|
412
|
+
"""The block's requirements: id, text and where the spec states them."""
|
|
413
|
+
found: list[dict[str, Any]] = []
|
|
414
|
+
for key in _requirement_keys(model, block):
|
|
415
|
+
req = model.get(key)
|
|
416
|
+
if not isinstance(req, RequirementEntity):
|
|
417
|
+
continue
|
|
418
|
+
found.append(
|
|
419
|
+
{
|
|
420
|
+
"id": req.name,
|
|
421
|
+
"text": req.text or "",
|
|
422
|
+
"where": _where(req),
|
|
423
|
+
"id_source": req.attrs.get("id_source"),
|
|
424
|
+
}
|
|
425
|
+
)
|
|
426
|
+
return found
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def spec_slice(model: DesignModel, block: str) -> dict[str, Any]:
|
|
430
|
+
"""The block's spec side of the Design Model (see the module docstring)."""
|
|
431
|
+
key = f"block:{block}"
|
|
432
|
+
entity = _block_entity(model, block)
|
|
433
|
+
registers: list[dict[str, Any]] = []
|
|
434
|
+
for reg in sorted(
|
|
435
|
+
(r for r in model.by_kind("register") if isinstance(r, RegisterEntity) and r.block == key),
|
|
436
|
+
key=lambda r: (str(r.offset), r.name),
|
|
437
|
+
):
|
|
438
|
+
fields = [
|
|
439
|
+
{
|
|
440
|
+
"name": f.name,
|
|
441
|
+
"msb": f.msb,
|
|
442
|
+
"lsb": f.lsb,
|
|
443
|
+
"access": f.access,
|
|
444
|
+
"reset": f.reset_value,
|
|
445
|
+
}
|
|
446
|
+
for f in sorted(
|
|
447
|
+
(
|
|
448
|
+
f
|
|
449
|
+
for f in model.by_kind("field")
|
|
450
|
+
if isinstance(f, FieldEntity) and f.register == reg.key
|
|
451
|
+
),
|
|
452
|
+
key=lambda f: (f.lsb or 0, f.name),
|
|
453
|
+
)
|
|
454
|
+
]
|
|
455
|
+
registers.append(
|
|
456
|
+
{
|
|
457
|
+
"name": reg.name,
|
|
458
|
+
"offset": reg.offset,
|
|
459
|
+
"access": reg.access,
|
|
460
|
+
"reset": reg.reset_value,
|
|
461
|
+
"width": reg.width,
|
|
462
|
+
"fields": fields,
|
|
463
|
+
"where": _where(reg),
|
|
464
|
+
}
|
|
465
|
+
)
|
|
466
|
+
interrupts = [
|
|
467
|
+
{"name": i.name, "line": i.line}
|
|
468
|
+
for i in model.by_kind("interrupt")
|
|
469
|
+
if isinstance(i, InterruptEntity) and i.block == key
|
|
470
|
+
]
|
|
471
|
+
regions = [
|
|
472
|
+
{"name": m.name, "base": m.base, "size": m.size}
|
|
473
|
+
for m in model.by_kind("memory_region")
|
|
474
|
+
if isinstance(m, MemoryRegionEntity) and m.block == key
|
|
475
|
+
]
|
|
476
|
+
attrs = entity.attrs if entity is not None else {}
|
|
477
|
+
return {
|
|
478
|
+
"block": block,
|
|
479
|
+
"bus": attrs.get("bus"),
|
|
480
|
+
"clock": attrs.get("clock"),
|
|
481
|
+
"reset": attrs.get("reset"),
|
|
482
|
+
"requirements": block_requirements(model, block),
|
|
483
|
+
"registers": registers,
|
|
484
|
+
"interrupts": interrupts,
|
|
485
|
+
"memory_regions": regions,
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# --- a project's sources -------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _fill(template: str, block: str | None) -> str:
|
|
493
|
+
if block is None:
|
|
494
|
+
return template
|
|
495
|
+
return template.replace("{block}", block).replace("{BLOCK}", block.upper())
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _templates(value: Any) -> list[str]:
|
|
499
|
+
if isinstance(value, str):
|
|
500
|
+
return [value] if value else []
|
|
501
|
+
if isinstance(value, (list, tuple)):
|
|
502
|
+
return [v for v in value if isinstance(v, str) and v]
|
|
503
|
+
return []
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _filelist_sources(root: Path, rel: str) -> list[Path]:
|
|
507
|
+
path = root / rel
|
|
508
|
+
if not path.is_file():
|
|
509
|
+
return []
|
|
510
|
+
found: list[Path] = []
|
|
511
|
+
styles: tuple[Literal["filelist", "cwd"], ...] = ("filelist", "cwd")
|
|
512
|
+
for style in styles:
|
|
513
|
+
try:
|
|
514
|
+
listed = read_filelist(path, relative_to=style, root=root)
|
|
515
|
+
except FilelistError:
|
|
516
|
+
continue
|
|
517
|
+
found.extend(p for p in listed.sources if p.is_file() and p not in found)
|
|
518
|
+
return found
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def rtl_files(
|
|
522
|
+
root: Path,
|
|
523
|
+
*,
|
|
524
|
+
model: DesignModel | None,
|
|
525
|
+
module: str | None,
|
|
526
|
+
block: str | None,
|
|
527
|
+
args: Mapping[str, Any],
|
|
528
|
+
layout: Mapping[str, Any],
|
|
529
|
+
) -> list[Path]:
|
|
530
|
+
"""The RTL files a declaration of `module` may be read from, in order."""
|
|
531
|
+
files: list[Path] = []
|
|
532
|
+
|
|
533
|
+
def add(path: Path) -> None:
|
|
534
|
+
if path.is_file() and path not in files:
|
|
535
|
+
files.append(path)
|
|
536
|
+
|
|
537
|
+
if model is not None and module is not None:
|
|
538
|
+
entity = _module(model, module)
|
|
539
|
+
if entity is not None and entity.file:
|
|
540
|
+
add(root / entity.file)
|
|
541
|
+
for template in _templates(args.get("files")):
|
|
542
|
+
for path in sorted(root.glob(_fill(template, block))):
|
|
543
|
+
add(path)
|
|
544
|
+
filelists = _templates(args.get("filelist")) or _templates(layout.get("filelist"))
|
|
545
|
+
for template in filelists:
|
|
546
|
+
for path in _filelist_sources(root, _fill(template, block)):
|
|
547
|
+
add(path)
|
|
548
|
+
for template in _templates(layout.get("rtl")):
|
|
549
|
+
for path in sorted(root.glob(_fill(template, block))):
|
|
550
|
+
add(path)
|
|
551
|
+
return files
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def project_interface(
|
|
555
|
+
root: Path,
|
|
556
|
+
*,
|
|
557
|
+
model: DesignModel | None,
|
|
558
|
+
block: str | None,
|
|
559
|
+
params: Mapping[str, str],
|
|
560
|
+
args: Mapping[str, Any],
|
|
561
|
+
layout: Mapping[str, Any],
|
|
562
|
+
) -> Interface:
|
|
563
|
+
"""The interface of a testbench task in a project (see the module docstring).
|
|
564
|
+
|
|
565
|
+
`args` are the profile's `tb_static` adapter args (`top`, `files`, `filelist`),
|
|
566
|
+
`layout` the profile's layout for the block, `params` the rule instance's.
|
|
567
|
+
"""
|
|
568
|
+
module = params.get("module") or None
|
|
569
|
+
if module is None and model is not None and block is not None:
|
|
570
|
+
module = top_module(model, block)
|
|
571
|
+
if module is None:
|
|
572
|
+
top = args.get("top")
|
|
573
|
+
module = _fill(top, block) if isinstance(top, str) and top else None
|
|
574
|
+
|
|
575
|
+
def load(name: str | None) -> ModuleDeclaration | None:
|
|
576
|
+
files = rtl_files(root, model=model, module=name, block=block, args=args, layout=layout)
|
|
577
|
+
return extract_declaration(files, name)
|
|
578
|
+
|
|
579
|
+
return interface_for(module, model=model, block=block, declaration=load)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
__all__ = [
|
|
583
|
+
"MISSING_IN_RTL",
|
|
584
|
+
"RTL_DIFFERS",
|
|
585
|
+
"DeclarationLoader",
|
|
586
|
+
"Interface",
|
|
587
|
+
"InterfaceParameter",
|
|
588
|
+
"InterfacePort",
|
|
589
|
+
"PortSource",
|
|
590
|
+
"block_requirements",
|
|
591
|
+
"interface_for",
|
|
592
|
+
"project_interface",
|
|
593
|
+
"rtl_files",
|
|
594
|
+
"spec_slice",
|
|
595
|
+
"top_module",
|
|
596
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
name: dv
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
description: >-
|
|
5
|
+
Design verification (DESIGN.md 8.2): the rule `dv/tb_module`, a cocotb test for each
|
|
6
|
+
block written by the testbench Author from the spec and the interface only (never the
|
|
7
|
+
RTL), and its skill `dv/cocotb`. The interface comes from the Design Model, or from the
|
|
8
|
+
module's declaration only (`interface.py`, `declaration.py`); the `tb_static` check
|
|
9
|
+
reads the written test without a simulator.
|
|
10
|
+
provides:
|
|
11
|
+
rules:
|
|
12
|
+
- rules
|
|
13
|
+
skills:
|
|
14
|
+
- skills/dv
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# The testbench of one block, written without seeing its RTL (DESIGN.md 5.1, 5.3 F1;
|
|
2
|
+
# task M2-06).
|
|
3
|
+
#
|
|
4
|
+
# For each block a `tb-author` task writes a cocotb 2.1 test module from the block's spec
|
|
5
|
+
# side of the Design Model (`block/{block}`: requirements with ids, registers and fields,
|
|
6
|
+
# interrupts, clock and reset), the module's interface (`interface/{block}`: the spec's
|
|
7
|
+
# ports, else the model's RTL ports, else the module's declaration only, read by the
|
|
8
|
+
# engine with pyslang) and the block's spec documents (the profile layout's `spec`). It
|
|
9
|
+
# never gets an rtl-kind input: the role's read policy denies `rtl`, the rule validator
|
|
10
|
+
# refuses such an input, `get_context` refuses to serve one, and the guard refuses reads
|
|
11
|
+
# of the RTL paths. What `submit` tells it of a failed check is filtered the same way.
|
|
12
|
+
#
|
|
13
|
+
# Params: `block` (from `foreach: blocks`); `module`, optional: the module under test.
|
|
14
|
+
# Without it the interface is the block's top module (in the Design Model, else the
|
|
15
|
+
# `tb_static` adapter's `top` template). The Planner's per-module expansion (M2-03, wired
|
|
16
|
+
# in M2-11) will set `module` and write one test per module; until then the test file is
|
|
17
|
+
# named after the block.
|
|
18
|
+
#
|
|
19
|
+
# The output is in the block's testbench directory, `{layout.tb}`: the directory part of
|
|
20
|
+
# the first `layout.tb` template of the block (QSoC's `dv/{block}/**` gives `dv/pwm`),
|
|
21
|
+
# `dv/{block}` when the profile has none (`chipgraph.packs.digital_rtl.layout`).
|
|
22
|
+
#
|
|
23
|
+
# Checks: `tb_static` (no simulator, no RTL: the file compiles, imports cocotb, has a
|
|
24
|
+
# `@cocotb.test()` coroutine, uses only interface ports, reads no file, cites every
|
|
25
|
+
# requirement id). A profile that sets `adapters.tb_static.sim` also runs the test on the
|
|
26
|
+
# RTL with that sim adapter once the static checks pass (see `checks/tb_static.py`).
|
|
27
|
+
rule: tb_module
|
|
28
|
+
kind: agent
|
|
29
|
+
role: tb-author
|
|
30
|
+
description: >-
|
|
31
|
+
Write a cocotb test module for the block from its spec, requirements and interface
|
|
32
|
+
only: one test per requirement group, each citing its requirement ids.
|
|
33
|
+
foreach: blocks
|
|
34
|
+
inputs:
|
|
35
|
+
- model: "block/{block}"
|
|
36
|
+
- model: "interface/{block}"
|
|
37
|
+
outputs:
|
|
38
|
+
- "{layout.tb}/test_{block}.py"
|
|
39
|
+
checks: [tb_static]
|
|
40
|
+
skills: [dv/cocotb]
|
|
41
|
+
budget:
|
|
42
|
+
tries: 3
|