Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions ball-design-guide/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
name: ball-design-guide
description: "Design methodology for a new Buckyball ball: contract questions, funct7/ballId selection, isa/ctest templates, bemu golden model, Blink RTL wrapper, core wiring, MLIRTest, regression stems, verification-report reading. Use when designing, implementing, wiring, delivering or debugging a ball."
---

# Ball Design Guide

How to implement one new Buckyball ball (operator) so it builds, registers and passes CI. This is methodology and structure; live repository facts (registries, encodings, regression tables, the frozen intrinsic enum) are fetched through the `bb-knowledge` skill, never memorized here. Work happens against a local checkout; commands below use `$BB` for its root.

## Scope boundary: ball delivery vs model-side lowering

The ball's delivery endpoint is the dual phase (c-bemu / rtl) plus MLIRTest. Whether the model pipeline lowers an operator to this ball depends on a four-gate pattern chain in the compiler (linalg→tile recognition, Tile dialect op, tile→ball hook, bank-SSA sharding emitter — see `knowledge/shared/model-to-ball-pipeline.md`), which lives outside the ball write-set. The ball implementer does not own it and must not promise it: deliver the ball, and when the chain is missing state plainly that the model path is unreachable — never imply the model will lower to the ball.

## When to read which file

- This file: the stage-by-stage flow, the rules that gate each stage, and the PR-body evidence-manifest template.
- `references/templates.md`: the exact shapes to copy (isa header, ctest, bemu crate, RTL wrapper) and how to view the live templates.
- `references/wiring.md`: stage 4 in full — registry rows, the five core wiring sites, the LLVM-export rule, MLIRTest triplets.
- `references/report-debug.md`: the five log layers and the seven failure patterns to read a failed verification report.

## Stage 0 — Lock the contract, then pick codes

Answer the five contract questions in writing and attribute each one to the brief (missing ones must be marked, never invented): operator semantics; ISA field and shape source; element width and per-iter footprint; illegal-input table (same checks on every layer); output layout and naming.

Pick `ballId`, `funct7`, `inBW`/`outBW` only after checking the live tree, never from memory:

- Check the reserved set first: `bb-knowledge` has the recipe (base ISA files named with two-digit funct7 prefixes — `[0-9][0-9]_*.c`, not a literal `NN_*.c` glob — `BallISA.scala` InitFunct, the analysis-side ISA table). A collision with a base ISA value only surfaces as a `ValueError` during `--analysis` — registration and builds stay green until then.
- Run `buckyball_isa_occupancy` for the occupancy map. `freeRanges` means *unclaimed* only — it does not subtract framework-reserved values.
- funct7 is a 7-bit CUSTOM_3 field: `[6:4]`=enable, `[3:0]`=opcode. Enable legend: `000` none, `001` 1rd, `010` 1wr, `011` 1rd+1wr, `100` 2rd+1wr, `101..111` reserved. The registry row's `inBW`/`outBW` must supply the ports the instruction family needs.
- funct7 must not collide with the target core registry's ballISA rows (veto). Cross-core reuse of a funct7 for the same mnemonic is legal and shows up in `conflicts` as `mnemonic-collision`.
- ballId numbers from 0 with no holes; one ball may hang several funct7 rows off one `ballIdMappings` row.

## Stage 1 — C tests first, macros via mnemonic

- Copy the live templates: isa header from `$BB/examples/balls/relu/workloads/isa/relu.h`, ctest from `$BB/examples/balls/relu/workloads/ctests/relu_test.c`, registration list from the same dir's `CMakeLists.txt` (`add_buckyball_ctests`). See `references/templates.md`.
- Never hardcode a funct7 number in a ball's isa header or its `.mlir` files: `#define X_FUNC7 50`, `BB_FUNC7(50)`, `funct7 = 50` and `BUCKYBALL_INSTRUCTION_*(.., 50)` as the last argument are all violations — the value is generated into `ballISA.h` from the registry, so a literal silently survives a renumbering. Use `BB_FUNC7(<MNEMONIC>)` only.
- One ctest `.c` file ≤ 100 lines — the build enforces it (`buckyball_enforce_ctest_line_limit`), and moving functional code into `.h` to evade it is explicitly forbidden. Split into focused tests.
- Two test shapes: `small` (short shape, hand-written vectors, boundaries, illegal inputs) and `bank` (random vectors, iter≈BANK_LINES, bemu only — never in the verilator list).

## Stage 2 — bemu golden model

- `emu/src/lib.rs` must carry exactly three symbols the generated dispatcher chains: `const BALL_CLASS: &str` (exact string equality with the registry `ballClass`), `execute_known` and `cycles_after_issue`, both returning `Option<u64>` (miss = `None`). There is no core-side emu file to wire; the dispatch chain is generated at build time.
- One instruction file per funct7. The numeral lives only in the registry row; lib.rs dispatches by mnemonic, so the instruction file carries no funct7 constant and the file name does not matter to dispatch.
- `exec` must `panic!` on illegal input — no sentinel returns. `.unwrap_or(..)` / `Ok(None)` shapes are flagged as non-blocking warnings by the audit (a saturation clamp and a swallowed error are for a human to tell apart).

## Stage 3 — RTL wrapper and compute unit

- Files go under `$BB/examples/balls/<ball>/arch/src/main/scala/`; `arch/build.sbt` globs them, no registration. BBus instantiates by reflecting the registry `ballClass` FQCN with a `(GlobalConfig)` constructor, so `package` + `class` must spell it exactly.
- Wrapper shape: `@instantiable class XBall(b: GlobalConfig) extends Module with HasBlink`, look up `inBW`/`outBW` from `b.ballDomain.ballIdMappings`, `io = IO(new BlinkIO(b, inBW, outBW))`, tie off unused ports. See `references/templates.md`.
- Hard constraints (self-check list for stage 3): SRAM read is 1 cycle (`resp.valid` the cycle after `req.fire`, never same-cycle data); latch every field on `cmdReq.fire` including rob_id; FSM `idle → read → compute → write → complete → idle` with correct `status.idle/running`; explicit widths (`+&`); block same-bank read/write that would destroy source data.

## Stage 4 — Register, then wire

Full detail in `references/wiring.md`. The shape: three registry edits (mappings row, `ballNum` +1, ISA rows), then the ball-local minimum compiler set (exactly one dialect `*.td` + `Transforms/LegalizeForLLVMExport.cpp`), then on a single-core chip the core-side five wiring sites — sites 3, 4, 5 are build gates (missing 3 = link-time `undefined reference`; missing 4 or 5 = compiled but never called), sites 1 and 2 are include/doc surfaces. Then MLIRTest triplets and both regression stems.

- The LLVM-export form is one: the dialect `*.td` must **not** inherit `Buckyball_IntrOpBase` (that emits an unconditional `llvm::Intrinsic::riscv_bb_<mnemonic>` reference; the fork's enum is frozen and cannot be extended by a ball). Emit `CustomIntrOp` + `buckyball_target::getBuckyballFunct7("<MNEMONIC>")` in `LegalizeForLLVMExport.cpp`. Check the actual enum via `bb-knowledge`'s intrinsic recipe.
- regression stem = `<chip>-<target>-ctest-<stem>-<baremetal|linux>`; mlirtest stem = `<chip>-<target>-mlirtest-<id>-<baremetal|linux>`. `<target>` is the compiler target name — derive it from `_target_name(core) = core.role or core.pkg` (see `bb-knowledge`), not from the chip directory name.

## Reading a failed verification report

The attribution tree lives in the design contract's report section (first question: did bemu pass?). For the RTL lane, the entry path is the five log layers and the seven failure patterns — `references/report-debug.md` lists each pattern with its tell and where to fix.

## Evidence manifest template (PR body)

Copy this block into the PR body and fill in the `<…>` slots; delete any optional line you do not use (a leftover placeholder is judged "unfilled template" and PRE-FAILs). The shape rules the machine checks (`validate-manifest.mjs`) are listed in the ball-designer playbook's staged-delivery section; field semantics are owned by the verify-runner prompt.

```
stage: ball
# phase 取值:c-bemu | rtl
phase: c-bemu
# round 可选,正整数,第几轮;不用就整行删
round: 1
# chip 必填:examples/chips/ 下的实物目录名(不是 core 名,也没有缺省)
chip: <chip>
# probe 可选、可多行,逐 stem 声明 probe 预算(分钟,缺省 3,验证侧语义解释;
# 该 stem 走 sim + analysis 成对步骤)。只允许写在无 phase / c-bemu / bind 轮,
# rtl 轮写它 = PRE-FAIL;不用就整行删
probe: <stem> <分钟>
# perf 可选,单 token:本轮是性能轮。rtl 轮对 pmc-evidence.elapsed_avg(本轮自产,
# 不要求 probe: 行),c-bemu 轮对 probe-evidence.cycles(必须有同 stem 的 probe: 行)
perf: <stem>
# ball-expect 可选、可多行:逐 stem 声明 ball 落点期望(mnemonic 大写下划线、逗号
# 分隔)——probe 轮零事件(空流)时它就是该 stem 记 PASS 还是 FAIL 的分界;stem
# 必须是本轮真会跑的那个(即本轮 probe: / perf: 声明过的 stem),否则 PRE-FAIL;
# 不用就整行删
ball-expect: <stem> <MNEMONIC>
- 改了哪些文件:逐条路径
- 预期应跑的测试:本 ball 全部 ctest 与 mlirtest 的 binary stem 逐个列出,
命名 <chip>-<target>-ctest-<stem>-baremetal(如 pebble 的 transpose:
pebble-pebble-ctest-transpose_i8_16x16_test-baremetal;<target> = 编译器
target 名 = core.role or core.pkg,按上文「chip 回归 TOML」段那条生成链取,
别拿 chip 名顶替)与 <chip>-<target>-mlirtest-<bank|ball>_<name>-baremetal
- audit 输出摘要
```

## Rules that are easy to violate

- The old "default to toy" assumption is gone: the evidence list's `chip:` is required and must name a real `examples/chips/<chip>/` directory.
- Do not quote repository facts from memory — recipes exist precisely because the tree moved before. `buckyball_ball_audit` judges what it can read (nine structural checks) and reports what it cannot as not judged; a fresh submodule state (`git submodule status` showing `+`) means the intrinsic enum is unreadable until aligned.
30 changes: 30 additions & 0 deletions ball-design-guide/references/report-debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Reading a verification report: log layers and failure patterns

The report's carrier (PASS → PR review comment, FAIL → PR comment), its section structure and the attribution tree ("did bemu pass?") live in the design contract's report-reading rules. This file is the RTL-side debugging detail: the five log layers and the seven failure patterns.

## The five log layers (RTL lane only)

Trace layers are written by the RTL-side DPI and only appear in verilator/bebop runs — bemu has none of them:

1. `bbdev/server.log` — build and compile errors first;
2. `stdout.log` — PASSED/FAILED output, panics;
3. `disasm.log` — the custom3 instruction stream: check the `mvin → op → mvout → fence` order;
4. `$BB/log/<timestamp>-*/bdb.ndjson` — trace lines by type:
- `{"type":"itrace"}` — clk / event / rob_id / funct / bank_enable / pc / rs1 / rs2;
- `{"type":"mtrace"}` / `{"type":"mtrace_issue"}` — bank addresses and data;
- `{"type":"pmctrace"}` — elapsed, the RTL-side real-time counter (the only value that can falsify emu `latency`);
5. waveform.

## The seven failure patterns

Work through them in order, against the log layers:

1. **Ball never responds** — `itrace` shows ISSUE but no COMPLETE for the op. Look for a stuck FSM state or a never-fired response; check `status.idle/running` mapping first.
2. **All-zero output** — data comes back but every element is 0. Distinguish from (3) by checking whether the value is written at all; likely the `mvin`/`mvout` addressing, the row width (16B vs 64B), or a zero-filled accumulation.
3. **Output unchanged** — mvout delivers the input unchanged. The op's read of bank data never happened (SRAM handshake), or the compute wrote to the wrong bank.
4. **Partial data wrong** — a slice of elements is off. Tracking iter / stride / boundaries: an off-by-one in the loop bounds, the stride, or the bank row mapping.
5. **SRAM 1-cycle timing** — data wrong by exactly one row/line. `resp.valid` timing: read data on the next cycle after `req.fire`, never same-cycle.
6. **bank_id conflict** — two requests address the same bank, or an op overlaps its own read/write bank. Block same-bank read/write pair in the wrapper; same-bank conflicting ops must be rejected or serialized.
7. **rob_id not latched** — completion goes to the wrong rob_id. `cmdReq.fire` must latch every field including rob_id; a `fire`-guarded pass-through loses it.

After identifying the layer and pattern, fix the owning layer only: bemu golden-model problems are never fixed in RTL (gold = ctest semantics), and a `latency` estimate is never patched to match RTL measurements — a mismatch at an order of magnitude is reported as a residual risk instead.
58 changes: 58 additions & 0 deletions ball-design-guide/references/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Templates: exact shapes to copy

View the live templates before writing anything — they are the ground truth and they move. Every path below is a stable anchor; read the file, do not recall its content.

## ISA header (stage 1)

Live template: `$BB/examples/balls/relu/workloads/isa/relu.h`

Shape:
- include `<bbhw/isa/bb_func7.h>` and `<bbhw/isa/isa.h>`;
- macro body encodes the instruction via `BUCKYBALL_INSTRUCTION_R_R` with BB_BANK0 / BB_BANK1 / BB_ITER;
- the funct7 argument is only ever `BB_FUNC7(<MNEMONIC>)` — a mnemonic resolved through the registry, never a number.

## ctest (stage 1)

Live template: `$BB/examples/balls/relu/workloads/ctests/relu_test.c`, registered in `$BB/examples/balls/relu/workloads/ctests/CMakeLists.txt` via `add_buckyball_ctests`.

Shape:
- `#include <isa/<ball>.h>` to get the ball's macros;
- sequence: `bb_mem_alloc → bb_mvin → bb_op → bb_mvout → bb_fence`, compare against the software expected value, print PASSED/FAILED;
- one `.c` ≤ 100 lines (`CTEST_MAX_LINES`); the same-directory `CMakeLists.txt` must list every `.c` with `add_buckyball_ctests`.

## bemu crate (stage 2)

Live template: `$BB/examples/balls/relu/emu/src/lib.rs` and `$BB/examples/balls/relu/emu/src/50_relu.rs`.

Shape (`lib.rs`):
- `pub const BALL_CLASS: &str = "<fqcn>"` — string-identical to the registry row's `ballClass`;
- `#[path = "<funct7>_<ball>.rs"] mod <name>;` per instruction file;
- `execute_known` / `cycles_after_issue` returning `Option<u64>`, `None` on a ballClass/funct mismatch;
- `exec` panic on illegal input.

Naming note: the numbered filename prefix is a local convention, not an upstream rule — some upstream files are bare-named, and the prefix need not equal the registered funct7. Dispatch is by mnemonic; only the registry ballISA row owns the number.

## RTL wrapper (stage 3)

Live template: `$BB/examples/balls/relu/arch/src/main/scala/ReluBall.scala` (+ compute unit in `Relu.scala`).

Shape:
- `@instantiable class <X>Ball(b: GlobalConfig) extends Module with HasBlink`;
- `package` + `class` spell the registry `ballClass` exactly;
- `inBW`/`outBW` from `b.ballDomain.ballIdMappings` keyed by ballName;
- `io = IO(new BlinkIO(b, inBW, outBW))`; tie off unconnected ports (subRobReq / mmioRead).

## dialect TD (stage 4)

Live templates: `$BB/examples/balls/relu/compiler/` — the dialect folder, `Transforms/LegalizeForLLVMExport.cpp`, plus the core-side usage in `$BB/examples/cores/pebble/compiler/`.

Shape:
- exactly one `*.td` under `compiler/src/Dialect/Buckyball/`; the op must not inherit `Buckyball_IntrOpBase` — emit through the generic `CustomIntrOp` + `buckyball_target::getBuckyballFunct7("<MNEMONIC>")` form in `LegalizeForLLVMExport.cpp` (live proofs: every ball's legalize file).

## MLIRTest triplet (stage 4)

Live template: `$BB/examples/balls/transpose/workloads/mlir_tests/` (bank + ball directories, each with `transpose_16x16_i8.mlir` + `_main.cpp` + `CMakeLists.txt`; the group root `mlir_tests/CMakeLists.txt` only does `add_subdirectory(bank)` + `add_subdirectory(ball)`).

Shape per test:
- `<name>.mlir` — bank layer holds the bank Op, ball layer the lowered ball Op; no funct7 literals in the lit `// CHECK` lines either;
- `<name>_main.cpp`; group `CMakeLists.txt` sets `BUCKYBALL_MLIR_GROUP_TARGET balls-mlir-tests-build`, `BUCKYBALL_MLIR_TEST_PREFIX bank|ball`, then `add_buckyball_mlir_test(<name> TARGET ${BUCKYBALL_MLIR_ACTIVE_TARGET})`.
Loading