Skip to content
Merged
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
67 changes: 55 additions & 12 deletions .claude/commands/demo-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ and add it to the Projects table instead.

## 1. Research the Project

Use `bin/curl-read` for simple fetches and `bin/curl-read-python-head` when you
need to filter/extract JSON fields:

```bash
# Fetch README (first 100 lines)
bin/curl-read-python-head https://raw.githubusercontent.com/<owner>/<repo>/<branch>/README.md "data" 100

# Repo tree — extract paths matching a pattern
bin/curl-read-python-head \
"https://api.github.com/repos/<owner>/<repo>/git/trees/<branch>?recursive=1" \
"[e['path'] for e in json.loads(data).get('tree',[]) if e['path'].endswith('.v')]" 50

# Latest commit hash
bin/curl-read-python-head \
"https://api.github.com/repos/<owner>/<repo>/commits?per_page=1" \
"json.loads(data)[0]['sha']"
```

- Fetch the project's README and repo structure
- Find synthesizable Verilog/SystemVerilog RTL files
- Look in directories like: `rtl/`, `src/`, `hdl/`, `verilog/`, `gold/`
Expand Down Expand Up @@ -52,20 +70,31 @@ Examples:
3. **Run synthesis** to get cell counts per module
4. **Then decide** what to build as separate macros

### Speculative macro decomposition
### Pre-synthesis RTL scan — ballpark the hierarchy

Before running synthesis, scan the RTL for obviously large repeated modules
that will dominate the design. Systolic arrays, SRAM banks, vector units,
and FPUs with many identical instances are safe bets. Setting these up as
`demo_sram()` targets from the start saves a full synthesis iteration —
the survey synth can take a long time on large designs, and discovering
after the fact that you need macros means starting over.
**Before running synthesis**, use `scripts/analyze_hierarchy.py` to scan the
Verilog and ballpark the design complexity. This is instant and tells you
whether you need macros before waiting for a potentially long synthesis run.

Look for:
- **Systolic arrays / PE arrays** — N×N grids of MAC units
```bash
# Fetch the RTL and run the hierarchy scanner
bin/curl-read "<raw_verilog_url>" > /tmp/design.v
python3 scripts/analyze_hierarchy.py /tmp/design.v --top <module>
```

The script reports:
- Module hierarchy tree with line counts and instance counts
- Large modules (>200 lines) — candidates for separate macros
- Repeated modules (>2 instances) — likely SRAM/datapath elements
- Memory-like modules (by name pattern) — potential `demo_sram()` targets

**Decision rules from the scan:**
- **No modules >200 lines, no repeats >2x**: flat design, no macros needed
- **Repeated modules with >50 lines**: speculative `demo_sram()` candidates
- **Systolic arrays / PE arrays** — N×N grids of MAC units → definitely macros
- **SRAM banks** — any module with `sram`, `mem`, `ram`, `rf` in the name
- **Replicated compute units** — processing clusters, lanes, tiles
- **Large arithmetic** — multipliers, dividers, FPUs
- **Large arithmetic** — multipliers, dividers, FPUs >200 lines

If the top module instantiates 4+ copies of something, or if a module
contains an N×N array of sub-instances, it's almost certainly worth
Expand Down Expand Up @@ -128,7 +157,7 @@ Focus only on the project being added.

- Pin to a specific commit hash (NEVER a branch name)
- Use `http_archive` (not `git_repository` — avoids system git dependency)
- Compute sha256 of the archive: `curl -sL <url> | sha256sum`
- Compute sha256 of the archive: `bin/curl-read <url> | sha256sum`
- Add `build_file = "//<project>:external.BUILD.bazel"` pointing to a local BUILD file
- If the project generates Verilog from Python/Chisel/etc., also add rules_python or rules_scala deps as needed

Expand Down Expand Up @@ -317,14 +346,27 @@ demo_gallery_image(
bazel build //<project>:<top_module>_gallery
```

Once the design matures, switch to routing for the final gallery image:
**Copy the image to `docs/`** so the README and gallery can reference it:

```bash
mkdir -p docs/<project>
cp bazel-bin/<project>/<top_module>_gallery.webp docs/<project>/place.webp
```

Once the design matures, switch the BUILD target to routing and update the copy:

```starlark
demo_gallery_image(
name = "<top_module>_gallery",
src = ":<top_module>_route",
)
```

```bash
bazel build //<project>:<top_module>_gallery
cp bazel-bin/<project>/<top_module>_gallery.webp docs/<project>/route.webp
```

## 10. Create <project>/README.md — REQUIRED

**Do not skip this step.** `/demo-update` depends on this file existing.
Expand Down Expand Up @@ -384,6 +426,7 @@ Append to the last row (or start a new row if it has 5 entries):
- **`sources` overrides `arguments` for the same key**: When `demo_sram` or `demo_hierarchical` sets a make variable like `PDN_TCL` in the base `arguments` dict (e.g., to a platform default), you can override it by providing the same key in the `sources` dict with a local file label (e.g., `"PDN_TCL": [":my-pdn.tcl"]`). The `sources` dict wins — no need to set `"PDN_TCL": ""` in arguments (which would cause a "couldn't read file" error).
- **Say when you're spelunking**: If you find yourself searching through bazel cache dirs, sandbox paths, or doing `find` across `.cache/bazel/` to locate a file, STOP and tell the user: "I'm spelunking through the cache to find X." The user likely knows the canonical location and can point you there directly. This saves time and produces a better SSOT reference for the skill.
- **Docker image by default**: All projects use OpenROAD from the Docker image — no from-source builds. Use `bazelisk run @bazel-orfs//:bump` to update. If a specific project hits an OpenROAD bug, it can build a patched OpenROAD from source and pass `openroad = "@openroad//:openroad"` in `orfs.default()`, but this is a project-specific workaround, not the default.
- **Gallery images hide power/ground**: All per-stage gallery scripts (`scripts/*_image.tcl`) hide power and ground nets for cleaner screenshots. The only exception is `floorplan_image.tcl`, which shows PDN intentionally. The generic fallback `gallery_image.tcl` also hides power/ground. Stage-to-script mapping is in `defs.bzl:_STAGE_SCRIPTS`.

## 12. Update This Skill

Expand Down
2 changes: 2 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"Bash(bin/bazel:*)",
"Bash(bin/grep-tail:*)",
"Bash(bin/openroad:*)",
"Bash(bin/curl-read:*)",
"Bash(bin/curl-read-python-head:*)",
"Read",
"Glob",
"Grep",
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,8 @@ git commands operate on THAT repo, not openroad-demo.
you're in with `git rev-parse --show-toplevel`.
- **Cross-repo reads**: use `bin/git-read <dir> <subcmd>` from the
openroad-demo root.
- **Fetch remote files**: use `bin/curl-read <https-url>` — read-only
HTTPS fetch (silent, follows redirects). Use for GitHub READMEs, API
metadata, and archive checksums.
- **bazelisk** commands require the openroad-demo root (where
MODULE.bazel lives). They fail with a clear error elsewhere.
14 changes: 14 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,20 @@ http_archive(
urls = ["https://github.com/profitmonk/tensor_accelerator/archive/ae1078d296a995925c81f5ae643a4574d5971589.tar.gz"],
)

http_archive(
name = "genben",
build_file = "//genben:external.BUILD.bazel",
patch_cmds = [
# Remove _ref suffix from all module names so the top module is just "CPU"
"sed -i 's/_ref\\b//g' data/Design/GA_94/ref.v",
# Fix trailing commas before ); in port lists (illegal in strict Verilog)
"sed -i -E ':a;N;$!ba;s/,([[:space:]]*\\);)/\\1/g' data/Design/GA_94/ref.v",
],
sha256 = "22a27982fc10d9fba27828a955a551d017bf0195bede283a0f9ae58de50cead8",
strip_prefix = "GenBen-a103f9a892e31bd1c410fe23663bc89f029c6fe9",
urls = ["https://github.com/ChatDesignVerification/GenBen/archive/a103f9a892e31bd1c410fe23663bc89f029c6fe9.tar.gz"],
)

http_archive(
name = "pymtl3",
build_file = "//pymtl3:external.BUILD.bazel",
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ results. See [PHILOSOPHY.md](PHILOSOPHY.md) for the approach.
| [pymtl3](pymtl3/) | Stream checksum unit (pymtl3) | ASAP7 | — | 0.42 GHz | 1,678 | 192 | `46ea257` | `ea5ca68` | 2026-03-24 | Done |
| [cva6](cva6/) | RISC-V CPU (CVA6 cv32a60x, HPDcache) | ASAP7 | — | 0.29 GHz | 80,306 | 15,189 | | `02e11e3` | 2026-03-25 | Place |
| [tensor_accelerator](tensor_accelerator/) | 4-level hierarchical TPU (4×TPC, systolic arrays) | ASAP7 | — | — | — | 44,562 | | `ae1078d` | 2026-03-25 | CTS |
| [genben](genben/) | 5-stage pipelined RISC-V CPU (GenBen GA_94) | ASAP7 | — | — | 14,032 | — | | `a103f9a` | 2026-03-25 | Place |

<details>
<summary>Column descriptions</summary>
Expand Down Expand Up @@ -99,9 +100,9 @@ _Run `/demo-update <project>` to refresh statistics after a build._
|:--------:|:--------:|:--------:|:--------:|:--------:|
| [<img src="docs/gemmini_8x8/route.webp" width="150">](gemmini_8x8/) | [<img src="docs/gemmini_8x8_abutted/route.webp" width="150">](gemmini_8x8_abutted/) | [<img src="docs/gemmini/route.webp" width="150">](gemmini/) | [<img src="docs/coralnpu/route.webp" width="150">](coralnpu/) | [<img src="docs/pymtl3/route.webp" width="150">](pymtl3/) |

| [cva6](cva6/) | [tensor_accelerator](tensor_accelerator/) |
|:--------:|:--------:|
| [<img src="docs/cva6/route.webp" width="150">](cva6/) | [<img src="docs/tensor_accelerator/place.webp" width="150">](tensor_accelerator/) |
| [cva6](cva6/) | [tensor_accelerator](tensor_accelerator/) | [genben](genben/) |
|:--------:|:--------:|:--------:|
| [<img src="docs/cva6/route.webp" width="150">](cva6/) | [<img src="docs/tensor_accelerator/place.webp" width="150">](tensor_accelerator/) | [<img src="docs/genben/place.webp" width="150">](genben/) |

## Upcoming

Expand Down
18 changes: 18 additions & 0 deletions bin/curl-read
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
# Read-only curl wrapper for use in Claude Code permissions.
# Usage: curl-read <https-url> [curl-flags...]
# Example: curl-read https://raw.githubusercontent.com/owner/repo/main/README.md
# curl-read https://api.github.com/repos/owner/repo/git/trees/main?recursive=1
#
# Only allows HTTPS URLs. No authentication headers or tokens.
set -euo pipefail

url="$1"; shift || { echo "curl-read: missing URL" >&2; exit 1; }

# Require https://
case "$url" in
https://*) ;;
*) echo "curl-read: only https:// URLs are allowed" >&2; exit 1 ;;
esac

exec curl -sL "$url" "$@"
143 changes: 143 additions & 0 deletions bin/curl-read-python-head
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Read-only curl + Python filter + head.

Usage:
curl-read-python-head <https-url> <python-expr> [max-lines]

Fetches <https-url> via curl (read-only, HTTPS only), pipes the response
through a Python expression, and prints at most <max-lines> lines (default: all).

The Python expression receives:
- `data` — the raw response body (str)
- `json` — the json module (pre-imported)
- `sys` — the sys module (pre-imported)
- `re` — the re module (pre-imported)

The expression must evaluate to an iterable of strings (one per output line)
or a single string (printed as-is).

Examples:
# Extract file paths containing "GA_94" from a GitHub tree
curl-read-python-head \\
"https://api.github.com/repos/owner/repo/git/trees/main?recursive=1" \\
"[e['path'] for e in json.loads(data).get('tree',[]) if 'GA_94' in e['path']]" \\
30

# First 50 lines of a README
curl-read-python-head \\
"https://raw.githubusercontent.com/owner/repo/main/README.md" \\
"data" \\
50

Safety: this script is intentionally read-only.
- Only HTTPS URLs are allowed
- curl runs with -sL (silent, follow redirects) — no writes
- The Python expression runs in a restricted namespace with no builtins
that perform I/O beyond print/len/sorted/etc.
"""

import ast
import json
import re
import subprocess
import sys


def is_read_only_expr(expr: str) -> bool:
"""Reject expressions that contain obviously dangerous constructs."""
try:
tree = ast.parse(expr, mode="eval")
except SyntaxError:
# Not a valid expression — reject
return False

for node in ast.walk(tree):
# Block calls to known dangerous builtins
if isinstance(node, ast.Name) and node.id in (
"exec", "eval", "compile", "__import__", "open",
"breakpoint", "exit", "quit",
):
return False
# Block attribute access to dunder methods (e.g., __class__, __subclasses__)
if isinstance(node, ast.Attribute) and node.attr.startswith("__"):
return False
return True


def main():
if len(sys.argv) < 3:
print("Usage: curl-read-python-head <https-url> <python-expr> [max-lines]",
file=sys.stderr)
sys.exit(1)

url = sys.argv[1]
expr = sys.argv[2]
max_lines = int(sys.argv[3]) if len(sys.argv) > 3 else 0 # 0 = unlimited

# HTTPS only
if not url.startswith("https://"):
print("curl-read-python-head: only https:// URLs are allowed", file=sys.stderr)
sys.exit(1)

# Validate expression is read-only
if not is_read_only_expr(expr):
print("curl-read-python-head: expression rejected (unsafe construct detected)",
file=sys.stderr)
sys.exit(1)

# Fetch URL
result = subprocess.run(
["curl", "-sL", url],
capture_output=True, text=True, timeout=30,
)
if result.returncode != 0:
print(f"curl-read-python-head: curl failed (exit {result.returncode})",
file=sys.stderr)
if result.stderr:
print(result.stderr, file=sys.stderr)
sys.exit(1)

data = result.stdout

# Evaluate expression in restricted namespace
safe_builtins = {
"len": len, "sorted": sorted, "reversed": reversed,
"list": list, "dict": dict, "set": set, "tuple": tuple,
"str": str, "int": int, "float": float, "bool": bool,
"enumerate": enumerate, "zip": zip, "map": map, "filter": filter,
"range": range, "min": min, "max": max, "sum": sum,
"any": any, "all": all, "isinstance": isinstance, "type": type,
"print": print, "repr": repr, "abs": abs, "round": round,
"True": True, "False": False, "None": None,
}

namespace = {
"__builtins__": safe_builtins,
"data": data,
"json": json,
"re": re,
"sys": sys,
}

try:
output = eval(compile(expr, "<expr>", "eval"), namespace) # noqa: S307
except Exception as e:
print(f"curl-read-python-head: expression error: {e}", file=sys.stderr)
sys.exit(1)

# Print output
if isinstance(output, str):
lines = output.splitlines()
elif hasattr(output, "__iter__"):
lines = [str(item) for item in output]
else:
lines = [str(output)]

for i, line in enumerate(lines):
if max_lines and i >= max_lines:
break
print(line)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ _STAGE_SCRIPTS = {
"cts": "//scripts:cts_image.tcl",
"grt": "//scripts:grt_image.tcl",
"route": "//scripts:route_image.tcl",
"final": "//scripts:route_image.tcl",
}

def demo_gallery_image(
Expand Down
Binary file added docs/genben/place.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions genben/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load("//:defs.bzl", "demo_flow", "demo_gallery_image", "demo_stage_images")

demo_flow(
name = "CPU",
verilog_files = ["@genben//:rtl"],
arguments = {
# --- Synthesis ---
"SYNTH_HIERARCHICAL": "1",
"SYNTH_MINIMUM_KEEP_SIZE": "0",
# --- Floorplan & Placement ---
"CORE_UTILIZATION": "40",
"PLACE_DENSITY": "0.65",
# --- Speed defaults (remove to improve QoR) ---
"GPL_ROUTABILITY_DRIVEN": "0",
"GPL_TIMING_DRIVEN": "0",
"SKIP_CTS_REPAIR_TIMING": "1",
"SKIP_INCREMENTAL_REPAIR": "1",
"SKIP_LAST_GASP": "1",
"FILL_CELLS": "",
"TAPCELL_TCL": "",
},
sources = {
"SDC_FILE": [":constraints.sdc"],
},
substeps = True,
)

demo_gallery_image(
name = "CPU_gallery",
src = ":CPU_place",
)

demo_stage_images(
name = "CPU_images",
module = "CPU",
stages = ["floorplan", "place", "cts", "route"],
)
Loading
Loading