| name | define-contracts |
| description | Designs the contracts that hold across performance model, behavior model, and RTL — single-source-of-truth interface DSL, hierarchy DSL, global constants, protocol annotations, state contracts, observability hooks, and alignment points. Activate when the user explicitly invokes /chipdev-method:define-contracts, or when they describe defining new module boundaries, designing an interface DSL, planning difftest probes, asking "怎么定义接口", "interface DSL", "single source of truth", or starting any chip frontend project that will have more than one artifact. |
Define Contracts
Use this skill when the user is about to commit any structure that more than
one artifact will share — interfaces, hierarchies, global constants, state
shape, observation hooks. Do not autoload.
This is the most important skill in the plugin. Contracts decided here
propagate to performance model, behavior model, RTL, and difftest. Drift
between artifacts is almost always rooted in a contract that was implicit
or convention-only.
How to use this skill in a response
When triggered:
- Identify which contract type the user is currently committing
(interface / state / observability / alignment).
- Recommend a DSL-driven approach over hand-written headers.
- Show the annotation pattern that fits their protocol.
- Point out the downstream artifacts that will consume the contract.
- Route to the relevant
build-* skill once contracts are defined.
If the user has already started hand-coding interfaces in C++ headers or
SystemVerilog interface blocks without a DSL, surface this as a risk
immediately. The cost of switching to a DSL early is hours; the cost
in Phase 4 is weeks.
Why contracts first [abstract]
Three observations drive this:
- The same interface appears in at least three places — performance
model, behavior model, RTL. Often a fourth (difftest probe header) and
a fifth (UVM verification environment). Hand-coding each copy guarantees
eventual drift.
- Connection code dominates by line count in cycle-accurate simulators.
Generated connection files routinely exceed 100 KB per module. Hand-writing
them is unmaintainable past ~10 modules.
- Protocol bugs are subtle and slow to surface. A
valid+ready mistake
in one place that should have been valid-only causes occasional
simulation hangs three months later. A formal annotation closes this.
The four contract types [abstract]
A complete contract for an interconnected design includes all four. Skipping
any one of them creates a class of integration failure.
1. Interface protocol contract
What signals carry data, what signals carry handshake, who drives what.
valid-only: sender drives valid, receiver always accepts. Use for
unbounded fan-out, broadcast, or upstream-rate-limited paths.
valid + ready: full handshake with backpressure. Use whenever the
receiver has a bounded queue.
- No-handshake: pure data, no flow control. Use only for combinational
decode paths, never across pipeline stages.
The annotation must travel with the interface definition, not as a
sidecar comment. See "DSL design rules" below.
2. State contract
What is the architectural state of this module? — the state that survives
across reset and is sampled during difftest.
- Register file, PC, CSR, memory, TLB, interrupt pending — these are
architectural.
- Pipeline registers, cache micro-state, buffer occupancy — these are
micro-architectural and must not appear in the state contract,
because the behavior model will not reproduce them.
3. Observability contract
Where can an external probe (difftest, dump, trace) sample the module?
- Output port edges (every transaction is observable).
- Commit / retire points (architectural state changes).
- Selected internal signals exposed as DPI-C functions or coverage points.
The observability contract is not the same as "every signal is exposed
during debug". It is the curated set of points where two artifacts must
agree.
4. Alignment contract
Across artifacts, where do they meet?
- Bit alignment: data layouts must be byte-identical.
- Cycle alignment (perf model ↔ RTL only): which cycle is the canonical
one — simulator step, RTL clock edge, or transaction edge?
- Event alignment (behavior ↔ RTL difftest): commit / retire / transaction
edge — this drives sampling point selection in
align-and-difftest.
Single-source-of-truth DSL [industry-pattern]
A proven pattern is to maintain three DSL layers that together generate
all downstream artifacts:
Layer A — Global constants
A small DSL for project-wide constants (number of cores, address widths,
memory size class). Emits headers in three formats:
- C / C++ macros for the simulator.
- SystemVerilog macros for RTL.
- Macro-language definitions for the higher-DSL layers (B and C below).
The same constant flows into all artifacts; changing it in one place updates
everywhere.
Layer B — Interface definition file
A SystemVerilog-flavored DSL for declaring interfaces:
(* prot_validRdy = 1 *)
interface CMD_PACKET_IF;
logic [7:0] tag;
logic [3:0] opcode;
logic [63:0] payload;
endinterface
Key elements:
- Protocol annotation as a leading attribute (
prot_valid,
prot_validRdy, prot_no). The parser inserts handshake signals
automatically — the writer never types valid or ready themselves.
- Bit-width strict typing — every field declares its width. The
generator can compute total wire counts and packed layouts.
- Optional field attributes:
(* diff_skip = 1 *) — skip this field in difftest comparison
(e.g., a sequence number that is not architectural).
(* ack = 1 *) — mark as a reverse-direction signal.
- Enums and packed unions for variant payloads, with
(* sel_… *)
attributes mapping enum values to union branches.
Each .idf (or whatever extension you choose) file emits:
- C++ struct + serialization (for the simulator).
- SystemVerilog
interface (for RTL).
- DPI-C probe header (for difftest).
- Optionally: a Python class (for verification env).
Layer C — Hierarchy definition file
A second DSL declaring how interfaces connect into modules and how modules
nest into the chip:
module SHADER_CORE(
CMD_PACKET_IF.in cmd_in,
DATA_RESPONSE_IF.out data_out
);
FRONTEND fe(.*);
PIPELINE pipe(.*);
BACKEND be(.*);
endmodule
Key elements:
- Port direction with
.in / .out selectors on the interface.
- Wildcard connection
.* for auto-name-matched binding (eliminates
most boilerplate).
- Macro expansion for replicated instances:
DEFINE_LANE(0); DEFINE_LANE(1); DEFINE_LANE(2); — same body
parameterized by index.
Each .hdf (or chosen extension) file emits:
- C++ base class with
proc_<port> callbacks (one per input).
- C++ Conn class with TLM-style socket binding.
- C++ implementation skeleton.
- SystemVerilog hierarchy.
- Module factory.
Layer integration: code generation pipeline [abstract]
Layer A (constants .gdf)
│ python emitter
▼
*.h *.vh *.macro — consumed by C++, RTL, and DSL layers below
Layer B (interfaces .idf) + Layer A macros
│ M4 / Jinja2 / chosen macro processor
▼
*.idf.proc
│ parser (FLEX/BISON or hand-written or ANTLR)
▼
*_idf.h *_idf.cpp *.svh — interface bindings for all artifacts
Layer C (hierarchy .hdf) + Layer A macros
│ M4 / Jinja2 expansion
▼
*.hdf.proc
│ parser
▼
{Mod}Base.{h,cpp} — generated abstract base class
{Mod}Conn.{h,cpp} — generated connection / socket wiring
{Mod}Impl.h — handwritten-implementation skeleton
CreateModule.{h,cpp} — factory function
{Hier}.sv — RTL hierarchy
This pipeline is the operational form of "Generation over hand-writing"
(invariant 1). Once it works end-to-end, adding a new module is:
- Write
.idf for new interfaces.
- Write
.hdf declaring the module and its ports.
- Hand-write
{Mod}Impl extending the generated {Mod}Base.
- Register and rebuild.
Steps 1, 2, 4 take minutes. Step 3 is where engineering judgment lives.
Tooling choices [industry-pattern]
| Stage | Mature options | Tradeoffs |
|---|
| Macro expansion | M4 / Jinja2 / ERB / Mako / Mustache | M4 is dependency-free and CMake-native; Jinja2 needs Python but is more readable. |
| Parser generator | FLEX + BISON / ANTLR / hand-written recursive descent | FLEX/BISON is mature, low-dependency, generates fast C; ANTLR is more powerful but pulls Java; hand-written gives flexibility but high maintenance. |
| Constants emitter | A small Python script reading a Python dict is enough. | Python is universal; YAML/TOML are alternatives if you want to forbid code in the constants file. |
| RTL DSL alternative | Chisel / SpinalHDL / Bluespec / Magma | Higher-level than IDF/HDF but constrains the team to one ecosystem; harder to also emit a SystemC simulator from the same source. |
A pragmatic default for a project that wants to emit both SystemC C++
and SystemVerilog from one source: GDF in Python + IDF/HDF in
SystemVerilog-flavored syntax + M4 + FLEX/BISON parser. This is dependency-light and battle-tested.
Common failure modes [abstract]
- DSL grafted on after interfaces already exist in code. Refactoring
to DSL retroactively is more painful than starting with a DSL. Always
start with Layer A and Layer B.
- Protocol annotations as comments instead of formal attributes. Comments
drift; attributes are parsed. Always make the protocol part of the DSL grammar.
- Hand-coding the connection layer "just for now". That code becomes
load-bearing and is then never replaced. Force generation from day one,
even when there's only one module.
- State contract blurred with micro-architectural state. Pipeline
buffers, cache state, and flop occupancy leak into the difftest reference,
guaranteeing false positives. Document the state contract explicitly.
- No
diff_skip or equivalent. Every field is compared in difftest;
legitimate differences (sequence numbers, latency-dependent counters)
flag as mismatches. Plan field-level skip semantics from day one.
- Single global namespace for interfaces. Becomes a war zone above ~50
interfaces. Use prefix conventions or namespace partitions.
See also
choose-artifact — what to build before defining contracts for it.
build-perf-model — how the performance model consumes generated bases.
build-behavior-model — how the behavior model uses the same contracts
with timing dropped.
build-rtl — how RTL consumes generated interface and hierarchy SV.
align-and-difftest — how the observability and alignment contracts
feed the difftest probe.
references/dsl-survey.md — survey of public chip-design DSLs.
references/interface-protocol-patterns.md — concrete protocol patterns.
references/observability-hook-design.md — probe contract design.