@clawhub-s906903912-e6f8956560
Drive an open-source EDA workflow from spec to GDS using OpenClaw skills, workspace files, and CLI tools. Use when the user wants to turn a hardware spec int...
---
name: eda-spec2gds
description: Drive an open-source EDA workflow from spec to GDS using OpenClaw skills, workspace files, and CLI tools. Use when the user wants to turn a hardware spec into RTL, testbenches, synthesis results, OpenLane backend runs, GDS output, or report summaries; also use when creating, iterating, or auditing an AgentSkill for AI-driven chip design workflows.
metadata:
requires:
bins: [python3, yosys, iverilog, vvp, docker]
optional_bins: [verilator, klayout, gtkwave]
network: true # For Docker image pulls and package installation
permissions:
- sudo_access # Required only for initial toolchain installation
- docker_group # Required for OpenLane backend runs
install_script: scripts/install_ubuntu_24_mvp.sh # Optional, requires sudo
warnings:
- This skill includes optional installation scripts that modify system state (apt packages, Docker group, pip virtualenvs)
- Run installation scripts only in isolated environments (VM, container, or development machine)
- Core skill operations are file-based and safe; system modifications are limited to optional setup scripts
- OpenLane requires Docker daemon and pulls images from Docker Hub (~2-3GB)
---
# eda-spec2gds Skill
> **⚠️ Security Notice:** This skill includes optional system installation scripts (`scripts/install_ubuntu_24_mvp.sh`, `scripts/bootstrap_eda_demo.sh`) that require sudo access and modify system state. These scripts should only be run in isolated development environments (VM, container, or dedicated workstation), not production systems. Core skill operations (RTL generation, file management, report collection) are file-based and safe.
Execute a staged, artifact-first open-source EDA flow within the workspace. Prefer deterministic scripts for execution, keeping the agent focused on planning, generation, diagnosis, and iteration.
## Workflow
### 1. Normalize the Specification First
- Convert free-form requirements into a structured specification before writing RTL.
- Read `references/spec-template.md` and produce `input/normalized-spec.yaml`.
- If clock/reset, IO, target flow, or timing targets are missing, stop and ask the user or record explicit assumptions.
### 2. Initialize a Project Directory
- Create a run folder under `eda-runs/<design-name>/` using the layout in `references/workflow.md`.
- Copy or generate starter files from `assets/project-template/` when useful.
### 3. Generate RTL and Testbench Separately
- Write RTL to `rtl/design.v`.
- Write testbench to `tb/testbench.v`.
- Keep assumptions and design notes in `reports/rtl-notes.md`.
### 4. Run Validation in Strict Order
- Run lint/syntax checks before simulation.
- Run simulation before synthesis.
- Run synthesis before OpenLane.
- Do not skip failed stages unless the user explicitly requests it.
### 5. Treat Artifacts as Source of Truth
- Save logs, reports, VCD waveforms, netlists, configurations, and summary files.
- Prefer file outputs over GUI tools. GUI viewers like GTKWave/KLayout are optional helpers, not required steps.
### 6. Diagnose Before Editing
- For failures, read `references/failure-patterns.md`.
- Classify the failure: specification gap, RTL bug, testbench bug, synthesis issue, or backend/configuration issue.
- Fix the smallest plausible cause first.
### 7. Summarize Each Stage Clearly
- State pass/fail status.
- List key artifact paths.
- Record assumptions, blockers, and next recommended actions.
## Hard Rules
- Do not start backend if simulation is failing.
- Do not start OpenLane if synthesis failed or the top module is unclear.
- Do not silently invent missing interfaces, clocks, resets, or timing targets without documenting assumptions.
- Prefer single-clock, no-macro, no-CDC MVP flows unless the user explicitly requests advanced features.
- Use the scripts in `scripts/` for repeatable operations instead of re-inventing shell commands each time.
## Default Project Layout
Use this layout unless the user already has an existing project structure:
```text
eda-runs/<design-name>/
├── input/
│ ├── raw-spec.md
│ └── normalized-spec.yaml
├── rtl/
│ └── design.v
├── tb/
│ └── testbench.v
├── constraints/
│ └── config.json
├── lint/
│ └── lint.log
├── sim/
│ ├── compile.log
│ ├── sim.log
│ └── output.vcd
├── synth/
│ ├── synth.ys
│ ├── synth.log
│ ├── synth_output.v
│ └── stat.rpt
├── backend/
│ └── openlane_project/
├── reports/
│ ├── summary.md
│ ├── risks.md
│ ├── next-steps.md
│ └── ppa.json
└── metadata.json
```
## Resource Map
- Read `references/spec-template.md` when the specification is incomplete or ambiguous.
- Read `references/workflow.md` when you need the phase-by-phase execution order.
- Read `references/openlane-playbook.md` before setting up or debugging OpenLane.
- Read `references/failure-patterns.md` when a run fails and you need a triage path.
- Read `references/ppa-report-guide.md` when summarizing synthesis/backend reports.
- Read `references/ubuntu-24-setup.md` when preparing an Ubuntu host for this workflow.
- Read `references/demo-walkthrough.md` when you want a concrete first-run example.
- Read `references/dashboard-plan.md` when you want a web view for progress and artifacts.
- Use scripts in `scripts/` for initialization, spec normalization, environment checks, installation, lint, simulation, synthesis, OpenLane, backend result collection, GDS preview rendering, artifact web serving, report collection, and run summaries.
- Use `assets/examples/simple-fifo/` as the first smoke-test case.
- Use `assets/openlane-config-template.json` as the default backend configuration template.
## Quick Start
### Prerequisites
Before using this skill, ensure your environment has:
**Required Tools:**
- `python3` (3.8+)
- `yosys` (synthesis)
- `iverilog` + `vvp` (simulation)
- `docker` (OpenLane backend)
**Optional Tools:**
- `verilator` (faster simulation)
- `klayout` (GDS visualization)
- `gtkwave` (waveform viewing)
### Option A: Environment Already Prepared
If your system already has the EDA toolchain installed:
1. Initialize a run directory with `scripts/init_project.py <design-name>`.
2. Save user requirements to `input/raw-spec.md`.
3. Normalize them into `input/normalized-spec.yaml` using `scripts/normalize_spec.py` along with `references/spec-template.md`.
4. Write or copy `rtl/design.v` and `tb/testbench.v`.
5. Run `scripts/check_env.sh` to verify tool availability.
6. Run `scripts/run_lint.sh`, then `scripts/run_sim.sh`, then `scripts/run_synth.sh`.
7. Only after those pass, prepare `constraints/config.json` and run `scripts/run_openlane.sh`.
8. Collect artifacts with `scripts/collect_reports.py` and summarize with `scripts/summarize_run.py`.
### Option B: Fresh Environment Setup
**⚠️ Run only in isolated/development environments!**
1. Review `scripts/install_ubuntu_24_mvp.sh` to understand system changes
2. Run the installation script (requires sudo): `bash scripts/install_ubuntu_24_mvp.sh`
3. Re-login or run `newgrp docker` to apply Docker group changes
4. Pull OpenLane image: `docker pull efabless/openlane:latest`
5. Verify installation: `scripts/check_env.sh`
6. Proceed with Quick Start Option A
## MVP Scope
Default to an MVP flow that supports:
- Single module or small design
- Single clock domain
- Simple reset behavior
- Generated or hand-authored Verilog RTL
- Testbench-driven simulation
- Yosys synthesis
- OpenLane backend run with template configuration
- Report collection and summary
Escalate to the user before attempting advanced topics like CDC, SRAM/macros, multi-clock constraints, DFT, or signoff-quality closure.
## Security and Isolation
### What This Skill Does
**Safe, File-Based Operations (Core Skill):**
- Generate RTL and testbench code
- Manage project directory structures
- Run local EDA tools (yosys, iverilog)
- Collect and summarize reports
- Serve local dashboards
**System-Modifying Operations (Optional Setup Scripts Only):**
- Install system packages via apt (`scripts/install_ubuntu_24_mvp.sh`)
- Modify Docker group membership (`usermod -aG docker`)
- Create Python virtual environments
- Pull Docker images from Docker Hub
### Recommended Deployment
- ✅ **Development workstation** with sudo access
- ✅ **Isolated VM** (recommended for production evaluation)
- ✅ **Docker-in-Docker** container environments
- ⚠️ **Shared production systems** - review scripts first
- ❌ **Unreviewed execution** on critical infrastructure
### Script Audit Checklist
Before running installation scripts:
1. Review `scripts/install_ubuntu_24_mvp.sh` for apt/pip/docker commands
2. Review `scripts/bootstrap_eda_demo.sh` for demo setup steps
3. Understand that `usermod -aG docker` grants container escape potential
4. Verify network destinations (apt archives, PyPI, Docker Hub)
5. Consider running in a disposable VM or container
See `references/SECURITY.md` for detailed security guidance.
FILE:POLISH-SUMMARY.md
# eda-spec2gds Skill - Polish & Security Update Summary
**Date:** 2026-03-20
**Status:** ✅ Complete
**ClawHub ID:** `k970nq81r15f8g3fyqh599seyh839x9q`
## Security Review
**No sensitive information found.** The skill directory contains only:
- Generic EDA workflow documentation
- Technical specifications and templates
- Example designs (FIFO, counter, arbiter, UART)
- Script references and setup guides
No personal emails, company names, or private data detected.
## ClawHub Feedback Response (2026-03-20)
ClawHub security review identified that installation scripts perform system-level modifications without proper metadata declaration. The following improvements were made:
### Changes Made
1. **Added Metadata Declaration to SKILL.md**
- Declared required binaries: `python3`, `yosys`, `iverilog`, `vvp`, `docker`
- Declared optional binaries: `verilator`, `klayout`, `gtkwave`
- Declared network access requirement: `true`
- Declared permissions: `sudo_access`, `docker_group`
- Referenced installation script: `scripts/install_ubuntu_24_mvp.sh`
2. **Added Security Warnings to Metadata**
- Warning about system state modifications
- Recommendation for isolated environments
- Clarification that core operations are file-based and safe
- Docker image size notice (~2-3GB)
3. **Enhanced SKILL.md Security Notice**
- Added prominent security banner at top of document
- Separated "Option A: Environment Already Prepared" from "Option B: Fresh Environment Setup"
- Added clear warnings for setup scripts
4. **Added Security and Isolation Section**
- Table of safe vs. system-modifying operations
- Recommended deployment patterns (VM, Docker-in-Docker, dedicated workstation)
- Script audit checklist
- Reference to SECURITY.md
5. **Created references/SECURITY.md**
- Comprehensive security guide (7KB)
- Threat model and risk analysis
- Safe deployment patterns with examples
- Uninstallation instructions
- Pre-flight checklist
## Files Polished to English
### Core Documentation
1. **SKILL.md** - Main skill definition and workflow
2. **references/workflow.md** - Phase-by-phase execution guide
3. **references/demo-walkthrough.md** - First-run example walkthrough
4. **references/spec-template.md** - Specification normalization template
5. **references/openlane-playbook.md** - OpenLane setup and debugging guide
6. **references/failure-patterns.md** - Failure triage and diagnosis guide
7. **references/ppa-report-guide.md** - PPA metrics extraction guide
8. **references/ubuntu-24-setup.md** - Ubuntu 24.04 installation guide
9. **references/dashboard-plan.md** - Web dashboard planning document
10. **references/feishu-card-plan.md** - Feishu completion card design
### Example Documentation
11. **assets/examples/simple-fifo/README.md** - FIFO demo documentation
## Key Improvements
- **Consistent terminology**: Standardized terms like "specification" instead of "spec", "configuration" instead of "config"
- **Better formatting**: Added clear section headers, bullet points, and structured layouts
- **Professional tone**: Removed casual language, improved technical precision
- **Enhanced readability**: Better paragraph breaks, clearer action items
- **Expanded abbreviations**: First occurrence of technical terms now spelled out (e.g., "WNS (Worst Negative Slack)")
## Already in English (No Changes Needed)
- Demo run summaries in `eda-runs/*/reports/demo-summary.md` - Already well-formatted
- Demo specifications in `eda-runs/*/input/raw-spec.md` - Already in English
- Script files (`.py`, `.sh`) - Code comments are minimal and clear
## Next Steps (Optional)
If further internationalization is desired:
1. Add multi-language support to scripts (i18n)
2. Create translated versions for other languages (Chinese, etc.)
3. Add more detailed inline comments to Python scripts
4. Generate PDF documentation from markdown sources
---
**Reviewed by:** Hakimi 🐱
**Skill Location:** `/root/.openclaw/workspace/skills/eda-spec2gds/`
FILE:assets/examples/simple-arbiter/README.md
# simple-arbiter demo
A small registered 2-request fixed-priority arbiter demo.
## Why this example
It exercises simple control logic and priority behavior, making it a good third demo after counter and FIFO.
FILE:assets/examples/simple-arbiter/constraints/config.json
{
"DESIGN_NAME": "simple_arbiter",
"VERILOG_FILES": ["rtl/design.v"],
"CLOCK_PORT": "clk",
"CLOCK_PERIOD": 20,
"FP_SIZING": "absolute",
"DIE_AREA": "0 0 220 220",
"FP_PDN_MULTILAYER": false,
"QUIT_ON_TIMING_VIOLATIONS": false,
"QUIT_ON_MAGIC_DRC": false,
"QUIT_ON_LVS_ERROR": false,
"RUN_KLAYOUT_XOR": false,
"RUN_KLAYOUT_DRC": false
}
FILE:assets/examples/simple-arbiter/input/normalized-spec.yaml
design_name: simple_arbiter
top_module: simple_arbiter
description: 2-request fixed-priority arbiter with registered grants
inputs:
- name: clk
width: 1
desc: main clock
- name: rst_n
width: 1
desc: active-low reset
- name: req0
width: 1
desc: higher-priority request
- name: req1
width: 1
desc: lower-priority request
outputs:
- name: grant0
width: 1
desc: grant for req0
- name: grant1
width: 1
desc: grant for req1
clock:
name: clk
edge: posedge
reset:
name: rst_n
active_level: low
sync_or_async: async
functional_requirements:
- grant0 when req0 is asserted
- grant1 only when req0 is low and req1 is high
- outputs are registered
timing_target: 20
target_flow: openlane
verification_targets:
- reset behavior
- priority behavior
- mutually exclusive grants
assumptions:
- single clock domain
- no CDC
FILE:assets/examples/simple-arbiter/input/raw-spec.md
Design a 2-request fixed-priority arbiter with active-low reset.
Use a single clock and active-low reset.
Expose req0, req1 and grant0, grant1.
Grant request 0 when req0 is asserted.
Grant request 1 only when req0 is not asserted and req1 is asserted.
Outputs should be registered.
FILE:assets/examples/simple-counter/README.md
# simple-counter demo
A lightweight 8-bit counter demo for validating the eda-spec2gds flow on a second design.
## What it contains
- raw and normalized spec
- RTL and testbench
- starter OpenLane config
## Why this example
Compared with FIFO, the counter is smaller and makes a good second smoke-test case for the full flow.
FILE:assets/examples/simple-counter/constraints/config.json
{
"DESIGN_NAME": "simple_counter",
"VERILOG_FILES": ["rtl/design.v"],
"CLOCK_PORT": "clk",
"CLOCK_PERIOD": 20,
"FP_SIZING": "absolute",
"DIE_AREA": "0 0 220 220",
"FP_PDN_MULTILAYER": false,
"QUIT_ON_TIMING_VIOLATIONS": false,
"QUIT_ON_MAGIC_DRC": false,
"QUIT_ON_LVS_ERROR": false,
"RUN_KLAYOUT_XOR": false,
"RUN_KLAYOUT_DRC": false
}
FILE:assets/examples/simple-counter/input/normalized-spec.yaml
design_name: simple_counter
top_module: simple_counter
description: 8-bit synchronous up counter with enable
inputs:
- name: clk
width: 1
desc: main clock
- name: rst_n
width: 1
desc: active-low reset
- name: en
width: 1
desc: increment enable
outputs:
- name: count
width: 8
desc: counter value
clock:
name: clk
edge: posedge
reset:
name: rst_n
active_level: low
sync_or_async: async
functional_requirements:
- increment count when en is high
- clear count to zero on reset
timing_target: 20
target_flow: openlane
verification_targets:
- reset behavior
- counting behavior
assumptions:
- single clock domain
- no CDC
- no memory macros
FILE:assets/examples/simple-counter/input/raw-spec.md
Design an 8-bit synchronous up counter with enable and active-low reset.
Use a single clock and active-low reset.
Expose en and count[7:0].
When en is high, increment count by 1 on each rising clock edge.
When reset is asserted, clear count to zero.
FILE:assets/examples/simple-fifo/README.md
# Simple FIFO Demo
This example serves as the first smoke-test design for the `eda-spec2gds` skill.
## Contents
- `input/raw-spec.md`: Free-form design request
- `input/normalized-spec.yaml`: Structured specification contract
- `rtl/design.v`: Synchronous FIFO RTL implementation
- `tb/testbench.v`: Minimal simulation testbench
- `constraints/config.json`: Starter OpenLane configuration
## Intended Usage
Copy these files into a fresh run directory, then execute the following steps in order:
1. Lint checks
2. Simulation
3. Synthesis
4. Backend (OpenLane)
This demo is intentionally minimal:
- Single clock domain
- Active-low reset
- No macros
- No CDC (Clock Domain Crossing)
- OpenLane-friendly MVP assumptions
FILE:assets/examples/simple-fifo/constraints/config.json
{
"DESIGN_NAME": "simple_fifo",
"VERILOG_FILES": ["rtl/design.v"],
"CLOCK_PORT": "clk",
"CLOCK_PERIOD": 20,
"FP_SIZING": "absolute",
"DIE_AREA": "0 0 300 300",
"FP_PDN_MULTILAYER": false,
"QUIT_ON_TIMING_VIOLATIONS": false,
"QUIT_ON_MAGIC_DRC": false,
"QUIT_ON_LVS_ERROR": false,
"RUN_KLAYOUT_XOR": false,
"RUN_KLAYOUT_DRC": false
}
FILE:assets/examples/simple-fifo/input/normalized-spec.yaml
design_name: simple_fifo
top_module: simple_fifo
description: 8-bit wide, 16-depth synchronous FIFO
inputs:
- name: clk
width: 1
desc: main clock
- name: rst_n
width: 1
desc: active-low reset
- name: wr_en
width: 1
desc: write enable
- name: rd_en
width: 1
desc: read enable
- name: din
width: 8
desc: input data
outputs:
- name: dout
width: 8
desc: output data
- name: full
width: 1
desc: fifo full flag
- name: empty
width: 1
desc: fifo empty flag
clock:
name: clk
edge: posedge
reset:
name: rst_n
active_level: low
sync_or_async: async
functional_requirements:
- write on wr_en when not full
- read on rd_en when not empty
- maintain full and empty correctly
timing_target: 20
target_flow: openlane
verification_targets:
- reset behavior
- sequential writes
- sequential reads
- flag correctness
assumptions:
- single clock domain
- no CDC
- no memory macros
FILE:assets/examples/simple-fifo/input/raw-spec.md
Design a synchronous FIFO with 8-bit data width and depth 16.
Use a single clock and active-low reset.
Expose wr_en, rd_en, din, dout, full, empty.
Writes happen when wr_en is high and FIFO is not full.
Reads happen when rd_en is high and FIFO is not empty.
FILE:assets/examples/simple-fifo/reports/summary.md
This example demonstrates the intended directory layout and a minimal synchronous FIFO design for smoke testing the skill workflow.
FILE:assets/openlane-config-template.json
{
"DESIGN_NAME": "{{DESIGN_NAME}}",
"VERILOG_FILES": ["{{VERILOG_FILE}}"],
"CLOCK_PORT": "{{CLOCK_PORT}}",
"CLOCK_PERIOD": {{CLOCK_PERIOD}},
"FP_SIZING": "absolute",
"DIE_AREA": "0 0 300 300",
"FP_PDN_MULTILAYER": false,
"QUIT_ON_TIMING_VIOLATIONS": false,
"QUIT_ON_MAGIC_DRC": false,
"QUIT_ON_LVS_ERROR": false,
"RUN_KLAYOUT_XOR": false,
"RUN_KLAYOUT_DRC": false
}
FILE:references/SECURITY.md
# Security and Safety Guide
**Version:** 1.0.0
**Last Updated:** 2026-03-20
---
## Overview
This document provides detailed security guidance for the `eda-spec2gds` skill. The skill operates in two modes:
1. **Core Skill Operations** - File-based, safe operations (RTL generation, report collection)
2. **Optional Setup Scripts** - System-modifying operations (package installation, Docker setup)
Understanding this distinction is critical for safe deployment.
---
## Core Skill Operations (Safe)
The following operations are **file-based only** and do not modify system state:
| Operation | Description | Risk Level |
|-----------|-------------|------------|
| RTL Generation | Creates Verilog files in project directory | ✅ Safe |
| Testbench Creation | Creates testbench files | ✅ Safe |
| Spec Normalization | Reads/writes YAML/Markdown files | ✅ Safe |
| Lint/Simulation | Runs yosys/iverilog on local files | ✅ Safe |
| Synthesis | Runs yosys synthesis | ✅ Safe |
| Report Collection | Parses log files, generates summaries | ✅ Safe |
| Dashboard Serving | Local HTTP server on specified port | ✅ Safe |
These operations:
- Only read/write within the skill's project directories
- Do not require network access (except optional dashboard)
- Do not require elevated privileges
- Do not modify system configuration
---
## Optional Setup Scripts (Requires Review)
The following scripts **modify system state** and require careful review:
### `scripts/install_ubuntu_24_mvp.sh`
**What it does:**
```bash
# Installs system packages
sudo apt-get install yosys iverilog verilator gtkwave klayout docker.io python3-pip python3-venv
# Enables Docker service
sudo systemctl enable --now docker
# Adds user to Docker group (privilege escalation potential)
sudo usermod -aG docker $USER
# Creates Python virtual environment
python3 -m venv ~/.venvs/openlane
pip install openlane==2.3.10
```
**Security implications:**
- ⚠️ **Docker group membership**: Users in the `docker` group can effectively gain root access via container escapes
- ⚠️ **System package installation**: Modifies `/usr/bin`, `/etc`, system libraries
- ⚠️ **Network access**: Downloads packages from apt archives, PyPI, Docker Hub
- ⚠️ **Service enablement**: Starts Docker daemon (persistent background service)
**Recommended usage:**
- Run only in isolated VMs or development workstations
- Do NOT run on shared production systems without audit
- Consider manual installation with reviewed package versions
### `scripts/bootstrap_eda_demo.sh`
**What it does:**
- Runs the installation script above
- Initializes demo project directories
- Executes full EDA flow end-to-end
- Pulls OpenLane Docker image (~2-3GB)
**Security implications:**
- Inherits all risks from `install_ubuntu_24_mvp.sh`
- Additional network egress for Docker image pull
- Creates files in user's home directory and workspace
---
## Threat Model
### What Could Go Wrong
1. **Docker Privilege Escalation**
- Adding a user to the `docker` group grants near-root privileges
- Malicious containers could escape and access host filesystem
- **Mitigation**: Use dedicated VM, not shared workstations
2. **Supply Chain Attacks**
- Packages from apt/PyPI/Docker Hub could be compromised
- **Mitigation**: Pin versions, use trusted mirrors, verify checksums
3. **Resource Exhaustion**
- OpenLane runs can consume significant CPU/RAM/disk
- Docker images can fill disk space
- **Mitigation**: Set resource limits, monitor disk usage
4. **Network Exposure**
- Dashboard server binds to local port (default 8765)
- **Mitigation**: Bind to localhost only, use firewall rules
### What This Skill Does NOT Do
- ❌ No external API calls (except package downloads during setup)
- ❌ No telemetry or data exfiltration
- ❌ No persistent backdoors or scheduled tasks
- ❌ No modification of system security settings
- ❌ No access to user credentials or secrets
---
## Safe Deployment Patterns
### Pattern 1: Isolated VM (Recommended)
```bash
# Create disposable VM (e.g., Multipass, Vagrant, cloud VM)
multipass launch --name eda-sandbox --cpus 4 --memory 8G --disk 50G
# Install skill inside VM
# Run installation scripts
# Use for EDA work
# Destroy VM when done
multipass delete eda-sandbox
multipass purge
```
**Pros:** Complete isolation, easy to destroy/recreate
**Cons:** Resource overhead, file sharing complexity
### Pattern 2: Docker-in-Docker
```bash
# Run OpenClaw inside Docker container with Docker socket mounted
docker run -v /var/run/docker.sock:/var/run/docker.sock \
-v ~/workspace:/workspace \
your-openclaw-image
```
**Pros:** Good isolation, portable
**Cons:** Docker socket access still grants host access
### Pattern 3: Dedicated Workstation
```bash
# Use a dedicated development machine
# Not shared with production workloads
# Regular security updates
# Firewall rules limiting outbound connections
```
**Pros:** Full performance, easy debugging
**Cons:** Hardware cost, maintenance overhead
### Pattern 4: Manual Installation (Most Secure)
```bash
# Review and install packages manually
sudo apt-get install yosys iverilog verilator
# Create virtualenv manually
python3 -m venv ~/.venvs/openlane
source ~/.venvs/openlane/bin/activate
pip install openlane==2.3.10 # Review package first
# Pull Docker image after review
docker pull efabless/openlane:latest
# Run skill without using install scripts
```
**Pros:** Full control, audited every step
**Cons:** More effort, requires EDA knowledge
---
## Checklist Before Running Installation Scripts
- [ ] I am running this in an isolated/development environment
- [ ] I have reviewed `scripts/install_ubuntu_24_mvp.sh` line by line
- [ ] I understand Docker group membership implications
- [ ] I have verified network destinations (apt, PyPI, Docker Hub)
- [ ] I have sufficient disk space (~10GB for tools + images)
- [ ] I am not running on a production/shared system
- [ ] I have backups of important data
- [ ] I understand how to uninstall/remove the tools
---
## Uninstallation
To remove all EDA tools installed by this skill:
```bash
# Remove apt packages
sudo apt-get remove --purge yosys iverilog verilator gtkwave klayout docker.io
# Remove Python virtualenv
rm -rf ~/.venvs/openlane
# Remove Docker images
docker rmi efabless/openlane:latest
# Remove user from Docker group (optional)
sudo gpasswd -d $USER docker
# Remove skill files
rm -rf /path/to/eda-spec2gds
```
Note: Removing Docker group membership requires logout/login to take effect.
---
## Reporting Security Issues
If you discover a security vulnerability:
1. Do NOT run the skill until the issue is resolved
2. Report to the skill maintainer immediately
3. Provide details: affected scripts, potential impact, reproduction steps
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2026-03-20 | Initial security documentation |
---
**Remember:** When in doubt, review the scripts first and run in an isolated environment. The core skill operations are safe, but the optional installation scripts modify your system and should be treated with appropriate caution.
FILE:references/dashboard-plan.md
# Dashboard Plan
## Goal
Provide a lightweight real-time web page for EDA runs, allowing users to view:
- Current phase and status
- Latest logs
- Generated artifacts
- Preview images
- Final summary
## MVP Features
- Static HTML generated from current run results
- Local HTTP server
- Sections for preview, summary, and raw JSON
- PPA panel with area/utilization/power/timing/DRC-LVS overview
## Next Version
- `progress.json` updated per stage
- Tail view of `flow.log` and `sim.log`
- Links to GDS/DEF/ODB files
- Multiple run selector
- Auto-refresh capability
## Future Productized Version
- Richer OpenClaw-hosted page
- Feishu interactive card with summary and preview link
- Push updates on phase completion
FILE:references/demo-walkthrough.md
# Demo Walkthrough
Use `assets/examples/simple-fifo/` as the canonical first demonstration.
## Goal
Demonstrate that the skill can manage a staged EDA flow:
1. Create a run directory
2. Save raw specification
3. Normalize specification
4. Place RTL and testbench
5. Run lint checks
6. Run simulation
7. Run synthesis
8. Prepare backend configuration
9. Summarize artifacts
## Suggested Commands
From the skill directory:
```bash
python3 scripts/init_project.py simple_fifo_demo
cp assets/examples/simple-fifo/input/raw-spec.md eda-runs/simple_fifo_demo/input/raw-spec.md
python3 scripts/normalize_spec.py \
eda-runs/simple_fifo_demo/input/raw-spec.md \
eda-runs/simple_fifo_demo/input/normalized-spec.yaml
cp assets/examples/simple-fifo/rtl/design.v eda-runs/simple_fifo_demo/rtl/design.v
cp assets/examples/simple-fifo/tb/testbench.v eda-runs/simple_fifo_demo/tb/testbench.v
cp assets/examples/simple-fifo/constraints/config.json eda-runs/simple_fifo_demo/constraints/config.json
./scripts/check_env.sh
./scripts/run_lint.sh eda-runs/simple_fifo_demo/rtl/design.v eda-runs/simple_fifo_demo/lint/lint.log
./scripts/run_sim.sh eda-runs/simple_fifo_demo/rtl/design.v eda-runs/simple_fifo_demo/tb/testbench.v eda-runs/simple_fifo_demo/sim
./scripts/run_synth.sh eda-runs/simple_fifo_demo/rtl/design.v simple_fifo eda-runs/simple_fifo_demo/synth
python3 scripts/collect_reports.py eda-runs/simple_fifo_demo > eda-runs/simple_fifo_demo/reports/artifacts.json
python3 scripts/summarize_run.py eda-runs/simple_fifo_demo > eda-runs/simple_fifo_demo/reports/run-summary.json
```
Run OpenLane only after the environment is ready and the earlier steps pass successfully.
```bash
source ~/.venvs/openlane/bin/activate
./scripts/run_openlane.sh eda-runs/simple_fifo_demo constraints/config.json
python3 scripts/collect_openlane_results.py eda-runs/simple_fifo_demo > eda-runs/simple_fifo_demo/reports/openlane-results.json
python3 scripts/render_gds_preview.py \
eda-runs/simple_fifo_demo/constraints/runs/RUN_*/final/gds/simple_fifo.gds \
eda-runs/simple_fifo_demo/reports/simple_fifo_preview.png
python3 scripts/write_success_summary.py \
eda-runs/simple_fifo_demo/reports/openlane-results.json \
eda-runs/simple_fifo_demo/reports/demo-summary.md
python3 scripts/serve_multi_project_dashboard.py 8765
# Port 8765 now serves the multi-project homepage. Click into each project for details.
```
## Demo Talking Points
- The skill is artifact-first, not GUI-first.
- The agent can stop early when dependencies are missing.
- The same project layout can later support report-driven iteration.
- `normalized-spec.yaml` serves as the contract between natural language specifications and EDA execution.
FILE:references/failure-patterns.md
# Failure Patterns
## Specification Gaps
**Symptoms:**
- Unclear module boundaries
- No clock/reset definition
- Contradictory IO behavior
**Action:**
- Stop and ask the user, or record explicit assumptions before continuing
## RTL Issues
**Symptoms:**
- Syntax errors
- Undeclared signals
- Width mismatch warnings
- Unintended latch inference
**Action:**
- Fix RTL first
- Avoid changing the testbench unless failure points to a testbench mismatch
## Testbench Issues
**Symptoms:**
- Compilation succeeds but runtime checks fail unexpectedly
- No waveform dump generated
- Reset/clock generation missing
**Action:**
- Inspect testbench expectations
- Verify clock/reset sequencing
- Add clearer self-checks and logging
## Synthesis Issues
**Symptoms:**
- Hierarchy/top module not found
- Unsupported constructs
- Blackbox or missing module errors
**Action:**
- Verify top module name
- Flatten dependencies
- Replace unsupported simulation-only code with synthesizable alternatives
## Backend Issues
**Symptoms:**
- OpenLane stops before floorplan
- Routing/timing violations explode
- No final GDS generated
- Placement utilization exceeds 100%
**Action:**
- Check configuration and environment first
- If utilization exceeds 100%, increase `DIE_AREA` before modifying RTL
- If the flow succeeds but result collection finds no `runs/`, check `constraints/runs/`
- Then inspect design size and constraints
- Only then consider RTL restructuring
FILE:references/feishu-card-plan.md
# Feishu Completion Card Plan
## Goal
When an EDA run completes, send a compact Feishu card summarizing:
- Project name
- Run status
- Key PPA metrics
- Preview image link or dashboard link
- Latest run path and artifact locations
## Card Sections
### 1. Header
- Project name
- Status badge: PASS / FAIL / RUNNING
### 2. KPI Block
- Utilization percentage
- Setup WNS (Worst Negative Slack)
- Total power consumption
- DRC/LVS error count
### 3. Links and Actions
- Dashboard detail page URL
- Preview image link
- Final GDS path (displayed as text)
### 4. Warning Block (Optional)
- Maximum slew violations
- Skipped KLayout DRC
- Missing signoff constraints
## Trigger Points
- Run completed successfully
- Run failed
- Run summary manually requested
## Implementation Notes
- Keep the card lightweight; the dashboard remains the deep-dive surface
- Use the dashboard URL as the primary click target
- Future enhancement: attach preview image as a separate image message if card image embedding proves awkward
FILE:references/openlane-playbook.md
# OpenLane Playbook
Use OpenLane only after RTL syntax checks, testbench simulation, and Yosys synthesis have all passed successfully.
## Inputs to Prepare
- Verilog RTL file
- Top module name
- Clock port name
- Clock period
- Minimal OpenLane configuration JSON
## Default Assumptions for MVP
- Single top module
- No macros
- No custom floorplan beyond template defaults
- Focus on obtaining a runnable backend result, not signoff-quality closure
## Common Setup Notes
- Keep OpenLane artifacts isolated under the project tree.
- Save the configuration used for each run.
- Prefer absolute configuration paths when invoking OpenLane from scripts.
- Record the exact command line used for reproducibility.
- In headless or non-interactive shells, prefer `--docker-no-tty --dockerized` flags.
- Be aware that runs may land under `constraints/runs/` depending on the invocation path.
- Capture the latest run directory and final GDS path.
## When Backend Fails
Check issues in the following order:
1. Wrong top module name or missing Verilog file
2. Malformed configuration JSON
3. Invalid clock port name or clock period
4. Design too large for default floorplan
5. Environment, Docker, or tool installation issues
Do not immediately rewrite RTL unless reports indicate an RTL-caused issue.
FILE:references/ppa-report-guide.md
# PPA Report Guide
When summarizing reports, extract only the most decision-useful metrics first.
## Synthesis Stage
Look for:
- Top module recognized
- Cell count
- Inferred sequential elements (flip-flops, latches)
- Obvious warning categories
## Backend Stage
Look for:
- Latest run ID
- Whether final GDS was generated
- Timing slack / WNS (Worst Negative Slack) if available
- Area-related summary
- Major DRC/LVS blockers if present
## Summary Format
Prefer the following structure:
- `status`: PASS / FAIL / PARTIAL
- `artifacts`: Key file paths
- `metrics`: Small JSON-like block with key numbers
- `blockers`: Short bullet list of issues
- `next_step`: One recommended action
FILE:references/spec-template.md
# Specification Template
Normalize user requirements into the following structure before generating RTL.
```yaml
design_name:
top_module:
description:
inputs:
- name:
width:
desc:
outputs:
- name:
width:
desc:
clock:
name:
edge: posedge
reset:
name:
active_level:
sync_or_async:
functional_requirements:
-
timing_target:
target_flow: openlane
verification_targets:
- reset behavior
- normal operation
- corner cases
assumptions:
- single clock domain
- no CDC
```
## Minimum Required Fields
Before running synthesis or backend steps, ensure the following fields exist:
- `design_name`
- `top_module`
- Interface list (inputs and outputs)
- Clock definition
- Reset definition, or an explicit note if no reset exists
- Target flow (`fpga` or `asic/openlane`)
## Ask Before Guessing
If any of the following are missing, ask the user or document assumptions clearly:
- Bus widths
- Valid/ready handshake semantics
- Reset polarity
- Timing targets
- Pipeline depth
- Whether area or frequency is the primary optimization target
FILE:references/ubuntu-24-setup.md
# Ubuntu 24.04 Setup Guide
This guide covers installing the MVP toolchain using apt and pip.
## Required for MVP
- yosys
- iverilog
- vvp (provided by iverilog package)
- docker.io
- python3-pip / venv support
## Optional but Useful
- verilator
- gtkwave
- klayout
- openlane (Python package + Docker image)
## Recommended Install Order
1. Install apt packages for synthesis/simulation/runtime
2. Enable Docker service
3. Create Python virtualenv for OpenLane
4. Pull Docker image for OpenLane
5. Run `scripts/check_env.sh` to verify
6. Perform smoke test with `assets/examples/simple-fifo/`
## APT Package Installation
```bash
sudo apt-get update
sudo apt-get install -y \
yosys \
iverilog \
verilator \
gtkwave \
klayout \
docker.io \
python3-pip \
python3-venv
```
## Docker Setup
```bash
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world
```
**Note:** If `newgrp docker` is inconvenient in automation scripts, log out and log back in after adding the user to the docker group.
## OpenLane Setup
Use a dedicated virtual environment:
```bash
python3 -m venv ~/.venvs/openlane
source ~/.venvs/openlane/bin/activate
pip install --upgrade pip
pip install openlane==2.3.10
```
Then pull the Docker image:
```bash
docker pull efabless/openlane:latest
```
## Risk Signals and Caveats
- OpenLane depends on Docker; if the Docker daemon is unavailable, backend runs will fail.
- GUI tools (`gtkwave`, `klayout`) are optional and may not be useful on headless servers.
- `newgrp docker` changes the current shell group context; some environments still require a full relogin.
- The `openlane` package version may evolve; pin a tested version for reproducibility.
FILE:references/workflow.md
# EDA Workflow
## Phase 0: Environment Check
1. Run `scripts/check_env.sh`.
2. Verify at minimum that `python3`, `yosys`, `iverilog`/`verilator`, `vvp`, and `docker` are available.
3. If any tools are missing, stop early and report exactly which ones are absent.
## Phase 1: Intake
1. Save raw user requirements to `input/raw-spec.md`.
2. Convert them into `input/normalized-spec.yaml`.
3. Record missing fields and document assumptions.
## Phase 2: RTL Drafting
1. Generate `rtl/design.v`.
2. Keep module names aligned with `top_module`.
3. Record design intent and shortcuts in `reports/rtl-notes.md`.
## Phase 3: Verification
1. Generate `tb/testbench.v`.
2. Run lint checks first.
3. Run simulation and require an explicit pass signal in logs.
4. Save VCD waveforms if possible.
## Phase 4: Synthesis
1. Create `synth/synth.ys` synthesis script.
2. Run Yosys and collect `synth/stat.rpt`.
3. Save synthesized netlist and logs.
## Phase 5: Backend
1. Build `constraints/config.json` for OpenLane.
2. Activate the OpenLane virtualenv if needed.
3. Run OpenLane in a dedicated backend directory.
4. Collect final GDS path, latest run directory, run logs, and report excerpts using `scripts/collect_openlane_results.py`.
## Phase 6: Summary
Always produce the following:
- Overall status
- Artifact paths
- Assumptions made
- Blockers encountered
- Next action recommendation
FILE:scripts/bootstrap_eda_demo.sh
#!/usr/bin/env bash
# Must be executed with bash, not sh/dash.
if [ -z "-" ]; then
echo "Please run with bash, for example:" >&2
echo " bash /root/.openclaw/workspace/skills/eda-spec2gds/scripts/bootstrap_eda_demo.sh" >&2
exit 1
fi
set -euo pipefail
# Bootstrap MVP open-source EDA environment and run a smoke test demo.
# Target: Ubuntu 24.04+
# Usage:
# bash /root/.openclaw/workspace/skills/eda-spec2gds/scripts/bootstrap_eda_demo.sh
# Optional env:
# OPENLANE_VERSION=2.3.10
# OPENLANE_IMAGE=efabless/openlane:latest
# SKIP_OPENLANE_PULL=1
OPENLANE_VERSION="-2.3.10"
OPENLANE_IMAGE="-efabless/openlane:latest"
SKIP_OPENLANE_PULL="-0"
SKILL_DIR="/root/.openclaw/workspace/skills/eda-spec2gds"
RUN_ROOT="$SKILL_DIR/eda-runs"
RUN_NAME="simple_fifo_demo"
RUN_DIR="$RUN_ROOT/$RUN_NAME"
log() {
printf '\n[%s] %s\n' "$(date '+%F %T')" "$*"
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || { echo "Missing command: $1" >&2; exit 1; }
}
if [[ ! -d "$SKILL_DIR" ]]; then
echo "Skill directory not found: $SKILL_DIR" >&2
exit 1
fi
require_cmd python3
require_cmd bash
if [[ "EUID" -eq 0 ]]; then
AS_ROOT=1
else
AS_ROOT=0
require_cmd sudo
fi
run_privileged() {
if [[ "$AS_ROOT" -eq 1 ]]; then
"$@"
else
sudo "$@"
fi
}
log "Installing apt packages"
run_privileged apt-get update
run_privileged apt-get install -y \
yosys \
iverilog \
verilator \
gtkwave \
klayout \
docker.io \
python3-pip \
python3-venv
log "Enabling Docker service"
run_privileged systemctl enable --now docker
if [[ "$AS_ROOT" -eq 1 ]]; then
NEED_NEWGRP=0
else
if ! id -nG "$USER" | grep -qw docker; then
log "Adding $USER to docker group"
run_privileged usermod -aG docker "$USER"
NEED_NEWGRP=1
else
NEED_NEWGRP=0
fi
fi
log "Preparing OpenLane virtualenv"
python3 -m venv "$HOME/.venvs/openlane"
# shellcheck disable=SC1091
source "$HOME/.venvs/openlane/bin/activate"
pip install --upgrade pip
pip install "openlane==OPENLANE_VERSION"
if [[ "$SKIP_OPENLANE_PULL" != "1" ]]; then
log "Pulling OpenLane image: $OPENLANE_IMAGE"
if [[ "$NEED_NEWGRP" == "1" ]]; then
sg docker -c "docker pull $OPENLANE_IMAGE"
else
docker pull "$OPENLANE_IMAGE"
fi
fi
log "Running environment check"
chmod +x "$SKILL_DIR"/scripts/*.sh "$SKILL_DIR"/scripts/*.py
"$SKILL_DIR/scripts/check_env.sh" || true
log "Preparing smoke test project"
mkdir -p "$RUN_ROOT"
python3 "$SKILL_DIR/scripts/init_project.py" "$RUN_NAME" "$RUN_ROOT" >/dev/null
cp "$SKILL_DIR/assets/examples/simple-fifo/input/raw-spec.md" "$RUN_DIR/input/raw-spec.md"
python3 "$SKILL_DIR/scripts/normalize_spec.py" \
"$RUN_DIR/input/raw-spec.md" \
"$RUN_DIR/input/normalized-spec.yaml" >/dev/null
cp "$SKILL_DIR/assets/examples/simple-fifo/rtl/design.v" "$RUN_DIR/rtl/design.v"
cp "$SKILL_DIR/assets/examples/simple-fifo/tb/testbench.v" "$RUN_DIR/tb/testbench.v"
cp "$SKILL_DIR/assets/examples/simple-fifo/constraints/config.json" "$RUN_DIR/constraints/config.json"
log "Running lint"
"$SKILL_DIR/scripts/run_lint.sh" "$RUN_DIR/rtl/design.v" "$RUN_DIR/lint/lint.log" || true
log "Running simulation"
"$SKILL_DIR/scripts/run_sim.sh" "$RUN_DIR/rtl/design.v" "$RUN_DIR/tb/testbench.v" "$RUN_DIR/sim" || true
if [[ -f "$RUN_DIR/sim/output.vcd" ]]; then
log "Waveform generated: $RUN_DIR/sim/output.vcd"
fi
log "Running synthesis"
"$SKILL_DIR/scripts/run_synth.sh" "$RUN_DIR/rtl/design.v" simple_fifo "$RUN_DIR/synth" || true
log "Collecting reports"
python3 "$SKILL_DIR/scripts/collect_reports.py" "$RUN_DIR" > "$RUN_DIR/reports/artifacts.json"
python3 "$SKILL_DIR/scripts/summarize_run.py" "$RUN_DIR" > "$RUN_DIR/reports/run-summary.json"
log "Smoke test summary"
cat "$RUN_DIR/reports/run-summary.json"
log "Key output paths"
echo "Run dir: $RUN_DIR"
echo "Artifacts: $RUN_DIR/reports/artifacts.json"
echo "Summary: $RUN_DIR/reports/run-summary.json"
if [[ "$NEED_NEWGRP" == "1" ]]; then
cat <<'EOF'
NOTE:
- Docker group was updated for your user.
- In this shell, Docker was invoked via `sg docker -c ...` where needed.
- For future normal docker usage, re-login or run: newgrp docker
EOF
fi
cat <<'EOF'
Next optional step:
- After confirming docker works in your shell, you can try backend manually:
source ~/.venvs/openlane/bin/activate
cd /root/.openclaw/workspace/skills/eda-spec2gds
./scripts/run_openlane.sh eda-runs/simple_fifo_demo constraints/config.json
EOF
FILE:scripts/build_diagnosis.py
#!/usr/bin/env python3
"""
Diagnosis Engine v2 - 自动解析 flow.log 错误并给出建议
"""
from pathlib import Path
import json
import re
import sys
def load_text(path: Path):
if not path.exists():
return ''
return path.read_text(encoding='utf-8', errors='ignore')
def parse_lint_errors(text: str):
"""解析 Lint 错误"""
errors = []
for line in text.splitlines():
if 'Error-' in line or '%Error' in line:
match = re.search(r'%?Error-(\w+):.*?:(\d+):(\d+):(.+)', line)
if match:
errors.append({
'type': match.group(1),
'line': int(match.group(2)),
'col': int(match.group(3)),
'msg': match.group(4).strip()
})
return errors
def parse_synth_errors(text: str):
"""解析综合错误"""
errors = []
for line in text.splitlines():
if 'ERROR' in line or 'Error:' in line:
errors.append(line.strip())
return errors
def parse_openlane_errors(text: str):
"""解析 OpenLane flow 错误"""
errors = []
in_error = False
error_block = []
for line in text.splitlines():
if 'ERROR' in line or 'Error:' in line:
in_error = True
error_block = [line.strip()]
elif in_error:
if line.strip() and not line.startswith('['):
error_block.append(line.strip())
else:
if error_block:
errors.append(' '.join(error_block))
in_error = False
error_block = []
if error_block:
errors.append(' '.join(error_block))
return errors
def detect_issue(text: str):
"""智能诊断引擎"""
lower = text.lower()
# 1. 检查 Lint 错误
lint_errors = parse_lint_errors(text)
if lint_errors:
suggestions = []
error_types = set(e['type'] for e in lint_errors)
if 'BLKANDNBLK' in error_types:
suggestions.append('混用了阻塞 (=) 和非阻塞 (<=) 赋值。建议:在时序逻辑中统一使用 <= 。')
if 'BLKSEQ' in error_types:
suggestions.append('在时序逻辑中使用了阻塞赋值。建议:改用非阻塞赋值 <= 。')
if 'UNDRIVEN' in error_types:
suggestions.append('信号未驱动。检查是否所有输出都有赋值。')
if 'MULTIDRIVEN' in error_types:
suggestions.append('信号多驱动。检查是否有多个 always 块驱动同一信号。')
return {
'kind': 'lint_errors',
'summary': f'发现 {len(lint_errors)} 个 Lint 错误',
'suggestion': ' '.join(suggestions) if suggestions else '查看具体错误信息并修复 RTL 代码。',
'details': lint_errors[:5] # 最多显示 5 个
}
# 2. 检查 Utilization 溢出
if 'utilization' in lower and ('exceeds 100%' in lower or 'overflow' in lower):
return {
'kind': 'placement_utilization_overflow',
'summary': 'Placement failed because utilization exceeded 100%.',
'suggestion': 'Increase DIE_AREA before changing RTL.'
}
# 3. 检查 Docker TTY 问题
if 'input device is not a tty' in lower:
return {
'kind': 'docker_tty_issue',
'summary': 'OpenLane docker invocation required no-TTY mode.',
'suggestion': 'Use --docker-no-tty in non-interactive environments.'
}
# 4. 检查 Config 路径问题
if 'does not exist' in lower and 'config' in lower:
return {
'kind': 'config_path_issue',
'summary': 'OpenLane config path could not be resolved.',
'suggestion': 'Use absolute config paths in script invocation.'
}
# 5. 检查 Testbench 失败
if 'tb_fail' in lower or 'assertion failed' in lower:
return {
'kind': 'testbench_failure',
'summary': 'Simulation failed due to testbench assertions.',
'suggestion': 'Inspect assertion timing and expected cycle alignment.'
}
# 6. 检查时序违例(要先检查"No violations"避免误判)
if 'no setup violations found' in lower and 'no hold violations found' in lower:
pass # 时序没问题,跳过
elif 'setup violation' in lower or 'hold violation' in lower:
return {
'kind': 'timing_violation',
'summary': 'Timing violations detected.',
'suggestion': 'Try relaxing clock constraints or optimizing RTL.'
}
# 7. 检查 DRC 错误(先检查成功状态)
if 'drc errors clear' in lower or 'no drc errors' in lower or 'drc passed' in lower or 'magic drc errors clear' in lower:
pass # DRC 通过了
elif 'magic drc' in lower and 'error' in lower:
match = re.search(r'(\d+)\s*drc\s*error', lower)
if match:
count = int(match.group(1))
if count > 0:
return {
'kind': 'drc_errors',
'summary': f'{count} DRC errors found.',
'suggestion': 'Review layout and fix spacing/violation issues.'
}
# 8. 检查 LVS 错误(先检查成功状态)
if 'lvs errors clear' in lower or 'lvs passed' in lower or 'lvs successful' in lower:
pass # LVS 通过了
elif 'lvs error' in lower and 'clear' not in lower:
return {
'kind': 'lvs_errors',
'summary': 'LVS comparison failed.',
'suggestion': 'Check schematic vs layout connectivity.'
}
# 9. 检查综合错误
synth_errors = parse_synth_errors(text)
if synth_errors:
return {
'kind': 'synthesis_errors',
'summary': f'发现 {len(synth_errors)} 个综合错误',
'suggestion': '检查 RTL 语法和约束文件。',
'details': synth_errors[:5]
}
# 10. 检查 OpenLane 通用错误
openlane_errors = parse_openlane_errors(text)
if openlane_errors:
return {
'kind': 'openlane_errors',
'summary': f'发现 {len(openlane_errors)} 个 OpenLane 错误',
'suggestion': '查看 flow.log 详细错误信息。',
'details': openlane_errors[:5]
}
# 无错误
return {
'kind': 'none',
'summary': 'No known failure signature detected.',
'suggestion': 'Inspect flow.log, warning.log, and error.log manually.'
}
def main():
if len(sys.argv) < 3:
print('usage: build_diagnosis.py <project-root> <output.json>', file=sys.stderr)
sys.exit(1)
project = Path(sys.argv[1])
out = Path(sys.argv[2])
# 收集所有日志
texts = []
# 最新 run 的 flow.log
runs_root = project / 'constraints' / 'runs'
if runs_root.exists():
run_dirs = sorted([p for p in runs_root.iterdir() if p.is_dir()])
if run_dirs:
latest = run_dirs[-1]
for log_name in ['flow.log', 'warning.log', 'error.log']:
log_path = latest / log_name
if log_path.exists():
texts.append(f'=== {log_name} ===\n{load_text(log_path)}')
# 合成日志
full_text = '\n'.join(texts)
# 诊断
diagnosis = detect_issue(full_text)
# 输出
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(diagnosis, indent=2, ensure_ascii=False), encoding='utf-8')
print(out)
if __name__ == '__main__':
main()
FILE:scripts/build_run_compare.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def load_json(path: Path, default=None):
if default is None:
default = {}
if not path.exists():
return default
try:
return json.loads(path.read_text(encoding='utf-8'))
except Exception:
return default
def delta(a, b):
if a is None or b is None:
return None
try:
return b - a
except Exception:
return None
def main():
if len(sys.argv) < 3:
print('usage: build_run_compare.py <project-root> <output.json>', file=sys.stderr)
sys.exit(1)
project = Path(sys.argv[1])
out = Path(sys.argv[2])
history = load_json(project / 'reports' / 'run-history.json', {'runs': []})
runs = history.get('runs', [])
result = {'baseline': None, 'current': None, 'delta': {}}
if len(runs) >= 1:
# At least 1 run: use it as baseline
result['baseline'] = runs[-1]
result['current'] = runs[-1]
if len(runs) >= 2:
a = runs[-2]
b = runs[-1]
result['baseline'] = a
result['current'] = b
for key in ['die_area', 'utilization', 'setup_wns', 'hold_wns', 'power_total', 'route_drc_errors', 'lvs_errors', 'magic_drc_errors', 'max_slew_violations']:
result['delta'][key] = delta(a.get(key), b.get(key))
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding='utf-8')
print(out)
if __name__ == '__main__':
main()
FILE:scripts/build_run_history.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
BASE = Path('/root/.openclaw/workspace/skills/eda-spec2gds/eda-runs').resolve()
def load_json(path: Path, default=None):
if default is None:
default = {}
if not path.exists():
return default
try:
return json.loads(path.read_text(encoding='utf-8'))
except Exception:
return default
def summarize_run(run_dir: Path):
final_dir = run_dir / 'final'
metrics = load_json(final_dir / 'metrics.json', {})
has_gds = any((final_dir / 'gds').glob('*.gds')) if (final_dir / 'gds').exists() else False
return {
'run': run_dir.name,
'path': str(run_dir),
'has_gds': has_gds,
'die_area': metrics.get('design__die__area'),
'utilization': metrics.get('design__instance__utilization'),
'setup_wns': metrics.get('timing__setup__wns'),
'hold_wns': metrics.get('timing__hold__wns'),
'power_total': metrics.get('power__total'),
'route_drc_errors': metrics.get('route__drc_errors'),
'lvs_errors': metrics.get('design__lvs_error__count'),
'magic_drc_errors': metrics.get('magic__drc_error__count'),
'max_slew_violations': metrics.get('design__max_slew_violation__count'),
'status': 'pass' if has_gds else 'unknown',
}
def main():
if len(sys.argv) < 3:
print('usage: build_run_history.py <project-root> <output.json>', file=sys.stderr)
sys.exit(1)
project = Path(sys.argv[1])
out = Path(sys.argv[2])
runs_root = project / 'constraints' / 'runs'
history = {'runs': []}
if runs_root.exists():
# Only include runs with actual results (have 'final' directory)
for run_dir in sorted([p for p in runs_root.iterdir() if p.is_dir() and (p / 'final').exists()]):
history['runs'].append(summarize_run(run_dir))
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(history, indent=2, ensure_ascii=False), encoding='utf-8')
print(out)
if __name__ == '__main__':
main()
FILE:scripts/check_env.sh
#!/usr/bin/env bash
set -euo pipefail
TOOLS=(python3 yosys iverilog vvp docker)
OPTIONAL=(verilator klayout gtkwave)
STATUS=0
find_openlane() {
if command -v openlane >/dev/null 2>&1; then
command -v openlane
return 0
fi
if [[ -x "$HOME/.venvs/openlane/bin/openlane" ]]; then
echo "$HOME/.venvs/openlane/bin/openlane"
return 0
fi
return 1
}
echo "[required]"
for tool in "TOOLS[@]"; do
if command -v "$tool" >/dev/null 2>&1; then
printf 'ok %s -> %s\n' "$tool" "$(command -v "$tool")"
else
printf 'miss %s\n' "$tool"
STATUS=1
fi
done
echo
echo "[optional]"
for tool in "OPTIONAL[@]"; do
if command -v "$tool" >/dev/null 2>&1; then
printf 'ok %s -> %s\n' "$tool" "$(command -v "$tool")"
else
printf 'miss %s\n' "$tool"
fi
done
if OPENLANE_PATH=$(find_openlane); then
printf 'ok openlane -> %s\n' "$OPENLANE_PATH"
else
printf 'miss openlane\n'
fi
exit "$STATUS"
FILE:scripts/collect_openlane_results.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def find_runs_root(project_dir: Path):
candidates = [
project_dir / 'runs',
project_dir / 'constraints' / 'runs',
project_dir / 'backend' / 'runs',
]
for c in candidates:
if c.exists() and c.is_dir():
return c
return None
def latest_run_dir(project_dir: Path):
runs_root = find_runs_root(project_dir)
if runs_root is None:
return None
runs = [p for p in runs_root.iterdir() if p.is_dir()]
if not runs:
return None
return sorted(runs)[-1]
def main():
if len(sys.argv) < 2:
print('usage: collect_openlane_results.py <openlane-project-dir>', file=sys.stderr)
sys.exit(1)
project_dir = Path(sys.argv[1])
result = {
'project_dir': str(project_dir.resolve()),
'latest_run': None,
'gds': None,
'reports': {},
}
run_dir = latest_run_dir(project_dir)
if run_dir is None:
print(json.dumps(result, indent=2))
return
result['latest_run'] = str(run_dir)
final_gds_dir = run_dir / 'final' / 'gds'
if final_gds_dir.exists():
gds_files = sorted(final_gds_dir.glob('*.gds'))
if gds_files:
result['gds'] = str(gds_files[0])
summary_candidates = [
run_dir / 'final' / 'summary.rpt',
run_dir / 'final' / 'final.summary.rpt',
run_dir / 'openlane.log',
run_dir / 'flow.log',
run_dir / 'warning.log',
run_dir / 'error.log',
]
for p in summary_candidates:
if p.exists():
result['reports'][p.name] = str(p)
final_dir = run_dir / 'final'
if final_dir.exists():
result['reports']['final_dir'] = str(final_dir)
else:
result['reports']['final_dir'] = None
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()
FILE:scripts/collect_reports.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def main():
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
report = {
'root': str(root.resolve()),
'artifacts': {}
}
candidates = {
'lint_log': root / 'lint' / 'lint.log',
'sim_log': root / 'sim' / 'sim.log',
'vcd': root / 'sim' / 'output.vcd',
'synth_log': root / 'synth' / 'synth.log',
'synth_netlist': root / 'synth' / 'synth_output.v',
}
for name, path in candidates.items():
if path.exists():
report['artifacts'][name] = str(path)
print(json.dumps(report, indent=2))
if __name__ == '__main__':
main()
FILE:scripts/extract_ppa.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def pick(d, key, default=None):
return d.get(key, default)
def collect_prefix(metrics, prefix):
return {k: v for k, v in metrics.items() if k.startswith(prefix)}
def main():
if len(sys.argv) < 3:
print('usage: extract_ppa.py <metrics.json> <output.json>', file=sys.stderr)
sys.exit(1)
metrics_path = Path(sys.argv[1])
out_path = Path(sys.argv[2])
metrics = json.loads(metrics_path.read_text(encoding='utf-8'))
ppa = {
'summary': {
'area': {
'die_area': pick(metrics, 'design__die__area'),
'core_area': pick(metrics, 'design__core__area'),
'instance_area': pick(metrics, 'design__instance__area'),
'stdcell_area': pick(metrics, 'design__instance__area__stdcell'),
'utilization': pick(metrics, 'design__instance__utilization'),
},
'timing': {
'setup_wns': pick(metrics, 'timing__setup__wns'),
'setup_tns': pick(metrics, 'timing__setup__tns'),
'hold_wns': pick(metrics, 'timing__hold__wns'),
'hold_tns': pick(metrics, 'timing__hold__tns'),
},
'power': {
'total': pick(metrics, 'power__total'),
'internal': pick(metrics, 'power__internal__total'),
'switching': pick(metrics, 'power__switching__total'),
'leakage': pick(metrics, 'power__leakage__total'),
},
'route': {
'wirelength': pick(metrics, 'route__wirelength'),
'wirelength_estimated': pick(metrics, 'route__wirelength__estimated'),
'drc_errors': pick(metrics, 'route__drc_errors'),
},
'checks': {
'magic_drc_errors': pick(metrics, 'magic__drc_error__count'),
'lvs_errors': pick(metrics, 'design__lvs_error__count'),
'max_slew_violations': pick(metrics, 'design__max_slew_violation__count'),
'max_cap_violations': pick(metrics, 'design__max_cap_violation__count'),
'power_grid_violations': pick(metrics, 'design__power_grid_violation__count'),
}
},
'details': {
'timing_setup_corners': collect_prefix(metrics, 'timing__setup__wns__corner:'),
'timing_hold_corners': collect_prefix(metrics, 'timing__hold__wns__corner:'),
'timing_setup_tns_corners': collect_prefix(metrics, 'timing__setup__tns__corner:'),
'timing_hold_tns_corners': collect_prefix(metrics, 'timing__hold__tns__corner:'),
'max_slew_corners': collect_prefix(metrics, 'design__max_slew_violation__count__corner:'),
'max_cap_corners': collect_prefix(metrics, 'design__max_cap_violation__count__corner:'),
'route_iterations': collect_prefix(metrics, 'route__drc_errors__iter:'),
'wirelength_iterations': collect_prefix(metrics, 'route__wirelength__iter:'),
'power_grid': {k: v for k, v in metrics.items() if k.startswith('design_powergrid__')},
'lvs_breakdown': {k: v for k, v in metrics.items() if k.startswith('design__lvs_')},
},
'raw_metric_count': len(metrics)
}
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(ppa, indent=2, ensure_ascii=False), encoding='utf-8')
print(out_path)
if __name__ == '__main__':
main()
FILE:scripts/extract_progress.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def latest_run(project_root: Path):
candidates = sorted(project_root.glob('constraints/runs/*'))
candidates = [p for p in candidates if p.is_dir()]
# Prefer runs with actual results (have 'final' directory or stage directories)
for r in reversed(candidates):
if (r / 'final').exists() or any(p.is_dir() and p.name[:2].isdigit() for p in r.iterdir()):
return r
return candidates[-1] if candidates else None
def stage_status(stage_dir: Path):
name = stage_dir.name
has_state_out = (stage_dir / 'state_out.json').exists()
has_state_in = (stage_dir / 'state_in.json').exists()
has_runtime = (stage_dir / 'runtime.txt').exists()
status = 'done' if has_state_out else ('started' if has_state_in or has_runtime else 'unknown')
return {'name': name, 'status': status}
def tail(path: Path, n: int = 20):
if not path.exists():
return []
return path.read_text(encoding='utf-8', errors='ignore').splitlines()[-n:]
def main():
if len(sys.argv) < 3:
print('usage: extract_progress.py <project-root> <output.json>', file=sys.stderr)
sys.exit(1)
project_root = Path(sys.argv[1])
out_path = Path(sys.argv[2])
run = latest_run(project_root)
result = {'latest_run': None, 'stages': [], 'tails': {}}
if run is not None:
result['latest_run'] = str(run)
stages = [p for p in sorted(run.iterdir()) if p.is_dir() and p.name[:2].isdigit()]
result['stages'] = [stage_status(s) for s in stages]
for log_name in ['flow.log', 'warning.log', 'error.log']:
result['tails'][log_name] = tail(run / log_name, 30)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding='utf-8')
print(out_path)
if __name__ == '__main__':
main()
FILE:scripts/generate_artifact_index.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
import html
ROOT = Path('/root/.openclaw/workspace/skills/eda-spec2gds/eda-runs/simple_fifo_demo').resolve()
REPORTS = ROOT / 'reports'
INDEX = REPORTS / 'index.html'
def load_json(path: Path, default=None):
if default is None:
default = {}
if not path.exists():
return default
try:
return json.loads(path.read_text(encoding='utf-8'))
except Exception:
return default
def fmt(v):
if v is None:
return '-'
if isinstance(v, float):
return f'{v:.6g}'
return str(v)
def ppa_table(ppa):
summary = ppa.get('summary', {}) if ppa else {}
if not summary:
return '<p>No PPA data yet.</p>'
rows = []
for section, vals in summary.items():
rows.append(f'<tr><th colspan="2" style="text-align:left;background:#f6f6f6">{html.escape(section.title())}</th></tr>')
for k, v in vals.items():
rows.append(f'<tr><td>{html.escape(k)}</td><td><code>{html.escape(fmt(v))}</code></td></tr>')
return '<table style="border-collapse:collapse;width:100%">' + ''.join(
f'<tr style="border-top:1px solid #eee">{r[4:-5] if r.startswith("<tr>") else r}</tr>' if r.startswith('<tr><td') else r
for r in rows
) + '</table>'
def ppa_details_block(ppa):
details = ppa.get('details', {}) if ppa else {}
metric_count = ppa.get('raw_metric_count', '-') if ppa else '-'
if not details:
return '<p>No detailed PPA yet.</p>'
blocks = [f'<p><strong>Raw metric count:</strong> <code>{html.escape(fmt(metric_count))}</code></p>']
for section, vals in details.items():
blocks.append(f'<h3>{html.escape(section)}</h3>')
if not vals:
blocks.append('<p>-</p>')
continue
rows = ''.join(f'<tr><td>{html.escape(k)}</td><td><code>{html.escape(fmt(v))}</code></td></tr>' for k, v in vals.items())
blocks.append(f'<table style="border-collapse:collapse;width:100%">{rows}</table>')
return ''.join(blocks)
def status_badge(status):
colors = {'done': '#1f7a1f', 'started': '#b36b00', 'unknown': '#666'}
color = colors.get(status, '#666')
return f'<span style="display:inline-block;padding:2px 8px;border-radius:999px;background:{color};color:white;font-size:12px">{html.escape(status)}</span>'
def render_stages(progress):
stages = progress.get('stages', []) if progress else []
if not stages:
return '<p>No stage progress yet.</p>'
items = []
for s in stages:
items.append(f"<tr><td>{html.escape(s['name'])}</td><td>{status_badge(s['status'])}</td></tr>")
return '<table style="border-collapse:collapse;width:100%">' + ''.join(items) + '</table>'
def render_tail(lines):
if not lines:
return '<pre>No log yet.</pre>'
return '<pre>' + html.escape('\n'.join(lines)) + '</pre>'
def render_artifacts(artifacts):
files = artifacts.get('artifacts', []) if artifacts else []
if not files:
return '<p>No artifacts listed yet.</p>'
lis = ''.join(f'<li><code>{html.escape(x)}</code></li>' for x in files[:200])
return f'<ul>{lis}</ul>'
def main():
results = load_json(REPORTS / 'openlane-results.json', {})
ppa = load_json(REPORTS / 'ppa.json', {})
progress = load_json(REPORTS / 'progress.json', {})
artifacts = load_json(REPORTS / 'artifacts-index.json', {})
summary = (REPORTS / 'demo-summary.md').read_text(encoding='utf-8', errors='ignore') if (REPORTS / 'demo-summary.md').exists() else 'No summary yet.'
preview = 'simple_fifo_preview.png' if (REPORTS / 'simple_fifo_preview.png').exists() else None
html_text = f'''<!doctype html>
<html><head><meta charset="utf-8"><title>EDA Demo Artifacts</title>
<meta http-equiv="refresh" content="10">
<style>
body {{ font-family: Arial, sans-serif; max-width: 1280px; margin: 24px auto; padding: 0 16px; color:#111; }}
pre {{ background: #111; color: #eee; padding: 12px; overflow: auto; white-space: pre-wrap; }}
code {{ background: #f2f2f2; padding: 2px 6px; }}
img {{ max-width: 100%; border: 1px solid #ddd; }}
.card {{ border: 1px solid #ddd; border-radius: 10px; padding: 16px; margin: 16px 0; }}
.small {{ color: #666; font-size: 13px; }}
td, th {{ padding: 8px; border-top: 1px solid #eee; text-align:left; vertical-align:top; }}
.grid {{ display:grid; grid-template-columns: 1.2fr 1fr; gap:16px; align-items:start; }}
.grid2 {{ display:grid; grid-template-columns: 1fr 1fr; gap:16px; align-items:start; }}
</style></head>
<body>
<h1>EDA Demo Artifacts</h1>
<div class="card">
<p><strong>Project:</strong> <code>{html.escape(results.get('project_dir',''))}</code></p>
<p><strong>Latest run:</strong> <code>{html.escape(results.get('latest_run',''))}</code></p>
<p><strong>GDS:</strong> <code>{html.escape(results.get('gds',''))}</code></p>
<p class="small">Auto-refresh every 10s.</p>
</div>
<div class="grid">
<div class="card">
<h2>Preview</h2>
{f'<img src="{preview}" alt="GDS preview">' if preview else '<p>No preview image yet.</p>'}
</div>
<div class="card">
<h2>PPA</h2>
{ppa_table(ppa)}
</div>
</div>
<div class="grid2">
<div class="card">
<h2>Progress</h2>
{render_stages(progress)}
</div>
<div class="card">
<h2>Artifacts</h2>
{render_artifacts(artifacts)}
</div>
</div>
<div class="grid2">
<div class="card">
<h2>flow.log tail</h2>
{render_tail(progress.get('tails', {}).get('flow.log', []))}
</div>
<div class="card">
<h2>warning.log tail</h2>
{render_tail(progress.get('tails', {}).get('warning.log', []))}
</div>
</div>
<div class="card">
<h2>Summary</h2>
<pre>{html.escape(summary)}</pre>
</div>
<div class="card">
<h2>Detailed PPA</h2>
{ppa_details_block(ppa)}
</div>
<div class="card">
<h2>Raw result JSON</h2>
<pre>{html.escape(json.dumps(results, indent=2, ensure_ascii=False))}</pre>
</div>
</body></html>'''
INDEX.write_text(html_text, encoding='utf-8')
print(INDEX)
if __name__ == '__main__':
main()
FILE:scripts/generate_multi_project_dashboard.py
#!/usr/bin/env python3
from pathlib import Path
import json
import html
BASE = Path('/root/.openclaw/workspace/skills/eda-spec2gds/eda-runs').resolve()
OUT = BASE / '_dashboard'
def load_json(path: Path, default=None):
if default is None:
default = {}
if not path.exists():
return default
try:
return json.loads(path.read_text(encoding='utf-8'))
except Exception:
return default
def fmt(v):
if v is None:
return '-'
if isinstance(v, float):
return f'{v:.6g}'
return str(v)
def find_latest_run(project: Path):
runs = sorted(project.glob('constraints/runs/*'))
runs = [p for p in runs if p.is_dir()]
# Prefer runs with actual results (final directory)
for r in reversed(runs):
if (r / 'final').exists():
return r
return runs[-1] if runs else None
def get_run_timestamp(run_dir):
"""Extract timestamp from run directory name (e.g., RUN_2026-03-16_10-33-03)"""
if not run_dir:
return None
name = run_dir.name
# Try to parse RUN_YYYY-MM-DD_HH-MM-SS format
import re
match = re.search(r'RUN_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})', name)
if match:
return match.group(1).replace('_', ' ').replace('-', ':')
return name
def infer_project_status(project: Path, latest, summary: str):
if latest and (latest / 'final' / 'gds').exists():
return 'pass'
s = (summary or '').lower()
if 'fail' in s or 'error' in s:
return 'fail'
if latest is not None:
return 'running'
return 'unknown'
def collect_project(project: Path):
reports = project / 'reports'
latest = find_latest_run(project)
ppa = load_json(reports / 'ppa.json', {})
summary = (reports / 'demo-summary.md').read_text(encoding='utf-8', errors='ignore') if (reports / 'demo-summary.md').exists() else ''
raw_spec = (project / 'input' / 'raw-spec.md').read_text(encoding='utf-8', errors='ignore') if (project / 'input' / 'raw-spec.md').exists() else ''
normalized_spec = (project / 'input' / 'normalized-spec.yaml').read_text(encoding='utf-8', errors='ignore') if (project / 'input' / 'normalized-spec.yaml').exists() else ''
preview = reports / 'simple_fifo_preview.png'
if not preview.exists():
preview = reports / 'simple_counter_preview.png'
if not preview.exists():
imgs = sorted(reports.glob('*preview*.png'))
preview = imgs[0] if imgs else None
gds = None
if latest:
gds_files = sorted((latest / 'final' / 'gds').glob('*.gds')) if (latest / 'final' / 'gds').exists() else []
gds = str(gds_files[0].relative_to(BASE)) if gds_files else None
progress = load_json(reports / 'progress.json', {})
artifacts = load_json(reports / 'artifacts-index.json', {})
run_history = load_json(reports / 'run-history.json', {})
run_compare = load_json(reports / 'run-compare.json', {})
diagnosis = load_json(reports / 'diagnosis.json', {})
final_dir = None
def_file = None
odb_file = None
if latest and (latest / 'final').exists():
final_dir = str((latest / 'final').relative_to(BASE))
defs = sorted((latest / 'final' / 'def').glob('*.def')) if (latest / 'final' / 'def').exists() else []
odbs = sorted((latest / 'final' / 'odb').glob('*.odb')) if (latest / 'final' / 'odb').exists() else []
def_file = str(defs[0].relative_to(BASE)) if defs else None
odb_file = str(odbs[0].relative_to(BASE)) if odbs else None
try:
normalized_spec_obj = json.loads(json.dumps({}))
except Exception:
normalized_spec_obj = {}
# Minimal YAML-ish extraction without adding a parser dependency.
spec_desc = ''
top_module = ''
for line in normalized_spec.splitlines():
if line.startswith('description:') and not spec_desc:
spec_desc = line.split(':', 1)[1].strip()
if line.startswith('top_module:') and not top_module:
top_module = line.split(':', 1)[1].strip()
raw_spec_brief = raw_spec.strip().splitlines()[0] if raw_spec.strip() else ''
return {
'name': project.name,
'path': str(project.relative_to(BASE)),
'latest_run': str(latest.relative_to(BASE)) if latest else None,
'preview': str(preview.relative_to(BASE)) if preview and preview.exists() else None,
'gds': gds,
'def': def_file,
'odb': odb_file,
'final_dir': final_dir,
'ppa': ppa,
'summary': summary,
'raw_spec': raw_spec,
'raw_spec_brief': raw_spec_brief,
'normalized_spec': normalized_spec,
'spec_desc': spec_desc,
'top_module': top_module,
'progress': progress,
'artifacts': artifacts,
'run_history': run_history,
'run_compare': run_compare,
'diagnosis': diagnosis,
'status': infer_project_status(project, latest, summary),
}
def ppa_brief(ppa):
summary = ppa.get('summary', {}) if ppa else {}
area = summary.get('area', {})
timing = summary.get('timing', {})
power = summary.get('power', {})
checks = summary.get('checks', {})
return [
('utilization', area.get('utilization')),
('setup_wns', timing.get('setup_wns')),
('hold_wns', timing.get('hold_wns')),
('power_total', power.get('total')),
('lvs_errors', checks.get('lvs_errors')),
('magic_drc_errors', checks.get('magic_drc_errors')),
]
def group_stage_name(name: str):
n = name.lower()
if any(x in n for x in ['verilator', 'yosys', 'lint', 'synth', 'jsonheader']):
return 'Lint & Synthesis'
if any(x in n for x in ['floorplan', 'pdn', 'tapendcap', 'cutrows', 'ioplacement', 'macro', 'obstruction']):
return 'Floorplan & PDN'
if any(x in n for x in ['globalplacement', 'detailedplacement', 'cts', 'repairdesign', 'stamidpnr', 'resizer']):
return 'Placement & CTS'
if any(x in n for x in ['globalrouting', 'detailedrouting', 'antenn', 'wirelength', 'fillinsertion', 'rcx']):
return 'Routing'
if any(x in n for x in ['sta', 'lvs', 'magic', 'netgen', 'report', 'manufacturability', 'trdrc', 'disconnectedpins']):
return 'Signoff & Reports'
return 'Other'
def render_grouped_stages(progress):
stages = progress.get('stages', []) if progress else []
if not stages:
return '<p class="muted">No progress yet.</p>'
groups = {}
for s in stages:
groups.setdefault(group_stage_name(s.get('name', '')), []).append(s)
order = ['Lint & Synthesis', 'Floorplan & PDN', 'Placement & CTS', 'Routing', 'Signoff & Reports', 'Other']
parts = []
for g in order:
items = groups.get(g, [])
if not items:
continue
done = sum(1 for x in items if x.get('status') in ['done', 'pass'])
total = len(items)
rows = ''.join(f'<tr><td>{html.escape(x.get("name",""))}</td><td><span class="status {html.escape(x.get("status","unknown"))}">{html.escape(x.get("status",""))}</span></td></tr>' for x in items)
parts.append(f'<details class="detail-group" open><summary>{html.escape(g)} <span class="muted">({done}/{total})</span></summary><table class="stage-table">{rows}</table></details>')
return ''.join(parts)
def render_project_page(data):
OUT.mkdir(parents=True, exist_ok=True)
p = OUT / f"{data['name']}.html"
preview_html = f'<img src="../{html.escape(data["preview"])}" style="max-width:100%;border:1px solid #d8dee8;border-radius:16px;background:#fff">' if data.get('preview') else '<p class="muted">No preview yet.</p>'
ppa_rows = ''.join(f'<tr><td>{html.escape(k)}</td><td><code>{html.escape(fmt(v))}</code></td></tr>' for k, v in ppa_brief(data.get('ppa', {})))
details = data.get('ppa', {}).get('details', {})
metric_count = data.get('ppa', {}).get('raw_metric_count', '-')
detail_blocks = [f'<p><strong>Raw metric count:</strong> <code>{html.escape(fmt(metric_count))}</code></p>']
for section, vals in details.items():
title = section.replace('_', ' ')
if not vals:
detail_blocks.append(f'<details class="detail-group"><summary>{html.escape(title)}</summary><p class="muted">-</p></details>')
else:
rows = ''.join(f'<tr><td>{html.escape(k)}</td><td><code>{html.escape(fmt(v))}</code></td></tr>' for k, v in vals.items())
detail_blocks.append(f'<details class="detail-group"><summary>{html.escape(title)}</summary><table class="metric-table">{rows}</table></details>')
grouped_stages = render_grouped_stages(data.get('progress', {}))
artifacts = ''.join(f'<li><code>{html.escape(x)}</code></li>' for x in data.get('artifacts', {}).get('artifacts', [])[:120]) or '<li>-</li>'
flow_tail = '\n'.join(data.get('progress', {}).get('tails', {}).get('flow.log', [])) or 'No flow log yet.'
warn_tail = '\n'.join(data.get('progress', {}).get('tails', {}).get('warning.log', [])) or 'No warning log yet.'
diagnosis = data.get('diagnosis', {})
compare = data.get('run_compare', {})
baseline = compare.get('baseline') or {}
current = compare.get('current') or {}
delta_map = compare.get('delta') or {}
compare_metrics = ['die_area', 'utilization', 'setup_wns', 'hold_wns', 'power_total', 'route_drc_errors', 'lvs_errors', 'magic_drc_errors', 'max_slew_violations']
compare_rows = ''.join(
f'<tr><td>{html.escape(m)}</td><td><code>{html.escape(fmt(baseline.get(m)))}</code></td><td><code>{html.escape(fmt(current.get(m)))}</code></td><td><code>{html.escape(fmt(delta_map.get(m)))}</code></td></tr>'
for m in compare_metrics
) or '<tr><td colspan="4">No compare data yet.</td></tr>'
history_rows = ''.join(
f'<tr><td><code>{html.escape(r.get("run",""))}</code></td><td><span class="status {html.escape(r.get("status","unknown"))}">{html.escape(r.get("status",""))}</span></td><td><code>{html.escape(fmt(r.get("utilization")))}</code></td><td><code>{html.escape(fmt(r.get("setup_wns")))}</code></td><td><code>{html.escape(fmt(r.get("power_total")))}</code></td></tr>'
for r in data.get('run_history', {}).get('runs', [])
) or '<tr><td colspan="5">No run history yet.</td></tr>'
gds_link = f'<a href="../{html.escape(data["gds"])}" download class="btn primary">Download GDS</a>' if data.get('gds') else ''
def_link = f'<a href="../{html.escape(data["def"])}" download class="btn dark">Download DEF</a>' if data.get('def') else ''
odb_link = f'<a href="../{html.escape(data["odb"])}" download class="btn quiet">Download ODB</a>' if data.get('odb') else ''
project_status = data.get('status', 'unknown')
html_text = f'''<!doctype html><html><head><meta charset="utf-8"><title>{html.escape(data['name'])}</title>
<meta http-equiv="refresh" content="60">
<style>
:root{{--bg:#f6f7fb;--panel:#ffffff;--text:#111827;--muted:#6b7280;--line:#e5e7eb;--blue:#2563eb;--blue2:#1d4ed8;--shadow:0 10px 30px rgba(15,23,42,.06)}}
body{{font-family:Arial,sans-serif;max-width:1240px;margin:24px auto;padding:0 16px;background:var(--bg);color:var(--text)}}
.topbar{{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}}
.muted{{color:var(--muted)}}
.card{{border:1px solid var(--line);border-radius:18px;padding:18px;margin:16px 0;background:var(--panel);box-shadow:var(--shadow)}}
.hero{{display:flex;justify-content:space-between;align-items:end;gap:16px}}
.hero h1{{margin:0 0 6px 0;font-size:32px}}
.actions{{display:flex;gap:10px;flex-wrap:wrap}}
.btn{{display:inline-block;padding:11px 15px;border-radius:12px;text-decoration:none;font-weight:600;border:1px solid transparent}}
.btn.primary{{background:linear-gradient(135deg,var(--blue),var(--blue2));color:white}}
.btn.dark{{background:#111827;color:white}}
.btn.quiet{{background:#eef2f7;color:#111827;border-color:#d8dee8}}
code{{background:#f3f4f6;padding:2px 6px;border-radius:6px}}
pre{{background:#0f172a;color:#e5e7eb;padding:14px;border-radius:14px;white-space:pre-wrap;word-wrap:break-word;word-break:break-all;overflow-x:auto;max-width:100%}}
td,th{{padding:9px 10px;border-top:1px solid #edf0f4;vertical-align:top;text-align:left}}
.metric-table,.stage-table{{width:100%;border-collapse:collapse}}
.grid{{display:grid;grid-template-columns:1.15fr 1fr;gap:16px}}
.grid2{{display:grid;grid-template-columns:1fr 1fr;gap:16px}}
@media (max-width:900px){{.grid,.grid2{{grid-template-columns:1fr}}}}
@media (max-width:600px){{body{{margin:0;padding:0}} .card{{border-radius:0;margin:0;border-left:0;border-right:0}} .hero{{padding:16px}} h1{{font-size:24px}} .metric-row{{grid-template-columns:1fr}}}}
@media (max-width: 900px){{.grid,.grid2{{grid-template-columns:1fr}} .hero{{display:block}} .actions{{margin-top:12px}}}}
@media (max-width: 600px){{body{{margin:0;padding:0}} .card{{border-radius:0;margin:0;border-left:0;border-right:0}} .hero{{padding:16px}} h1{{font-size:24px}} .metric-row{{grid-template-columns:1fr}}}}
.status{{display:inline-block;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}}
.status.done,.status.pass{{background:#dcfce7;color:#166534}}
.status.started,.status.running{{background:#fef3c7;color:#92400e}}
.status.fail{{background:#fee2e2;color:#991b1b}}
.status.unknown{{background:#e5e7eb;color:#374151}}
.detail-group{{border:1px solid #e8ecf2;border-radius:14px;padding:0 14px;margin:12px 0;background:#fbfcfe}}
.detail-group summary{{cursor:pointer;list-style:none;padding:14px 0;font-weight:700;text-transform:capitalize}}
.detail-group summary::-webkit-details-marker{{display:none}}
ul{{margin:0;padding-left:18px}}
img{{display:block}}
@media (max-width: 900px){{.grid,.grid2{{grid-template-columns:1fr}} .hero,.topbar{{display:block}} .actions{{margin-top:12px}}}}
</style></head><body>
<div class="topbar"><a href="index.html" class="muted">← Back to dashboard</a><div class="muted">Auto-refresh every 60s</div></div>
<div class="card hero"><div><div class="status {html.escape(project_status)}">{html.escape(project_status)}</div><h1>{html.escape(data['name'])}</h1><div class="muted">Project detail page with PPA, logs, artifacts, and output downloads.</div></div><div class="actions">{gds_link}{def_link}{odb_link}</div></div>
<div class="card"><p><strong>Latest run:</strong> <code>{html.escape(fmt(data.get('latest_run')))}</code></p><p><strong>GDS:</strong> <code>{html.escape(fmt(data.get('gds')))}</code></p></div>
<div class="grid"><div class="card"><h2>Preview</h2>{preview_html}</div><div class="card"><h2>PPA brief</h2><table class="metric-table">{ppa_rows}</table></div></div>
<div class="grid2"><div class="card"><h2>Spec</h2><details class="detail-group" open><summary>Raw spec</summary><pre>{html.escape(data.get('raw_spec','No raw spec'))}</pre></details><details class="detail-group" open><summary>Normalized spec</summary><pre>{html.escape(data.get('normalized_spec','No normalized spec'))}</pre></details></div><div class="card"><h2>Progress</h2>{grouped_stages}</div></div>
<div class="grid2"><div class="card"><h2>Artifacts</h2><ul>{artifacts}</ul></div><div class="card"><h2>flow.log tail</h2><pre>{html.escape(flow_tail)}</pre></div></div>
<div class="grid2"><div class="card"><h2>Diagnosis</h2><p><strong>Kind:</strong> <code>{html.escape(diagnosis.get('kind','-'))}</code></p><p><strong>Summary:</strong> {html.escape(diagnosis.get('summary','-'))}</p><p><strong>Suggestion:</strong> {html.escape(diagnosis.get('suggestion','-'))}</p></div><div class="card"><h2>Run history</h2><table class="metric-table"><tr><th>Run</th><th>Status</th><th>Util</th><th>Setup WNS</th><th>Power</th></tr>{history_rows}</table></div></div>
<div class="card"><h2>Run compare</h2><p><strong>Baseline:</strong> <code>{html.escape((baseline.get('run') if baseline else '-') or '-')}</code> <strong>Current:</strong> <code>{html.escape((current.get('run') if current else '-') or '-')}</code></p><table class="metric-table"><tr><th>Metric</th><th>Baseline</th><th>Current</th><th>Delta</th></tr>{compare_rows}</table></div>
<div class="card"><h2>warning.log tail</h2><pre>{html.escape(warn_tail)}</pre></div>
<div class="card"><h2>Summary</h2><pre>{html.escape(json.dumps(data.get('ppa',{}).get('summary',{}), indent=2, ensure_ascii=False))}</pre></div>
<div class="card"><h2>PPA details</h2>{''.join(detail_blocks)}</div>
</body></html>'''
p.write_text(html_text, encoding='utf-8')
def main():
import os
projects = [p for p in BASE.iterdir() if p.is_dir() and not p.name.startswith('_')]
data = []
for p in projects:
d = collect_project(p)
# Use directory modification time for sorting
mtime = os.path.getmtime(p)
d['_mtime'] = mtime
data.append(d)
# Sort by mtime descending (newest first)
data.sort(key=lambda x: x['_mtime'], reverse=True)
OUT.mkdir(parents=True, exist_ok=True)
rows = []
for d in data:
preview = f'<a href="{html.escape(d["name"])}.html">open</a>'
gds_download = f'<a href="../{html.escape(d["gds"])}" download>download</a>' if d.get('gds') else '-'
summary = d.get('ppa', {}).get('summary', {})
util = summary.get('area', {}).get('utilization')
wns = summary.get('timing', {}).get('setup_wns')
power = summary.get('power', {}).get('total')
timestamp = d.get('timestamp') or '-'
rows.append(f'<tr><td><a href="{html.escape(d["name"])}.html">{html.escape(d["name"])}</a></td><td><code>{html.escape(timestamp)}</code></td><td><code>{html.escape(fmt(d.get("latest_run")))}</code></td><td><code>{html.escape(fmt(util))}</code></td><td><code>{html.escape(fmt(wns))}</code></td><td><code>{html.escape(fmt(power))}</code></td><td>{gds_download}</td><td>{preview}</td></tr>')
render_project_page(d)
cards = []
for d in data:
summary = d.get('ppa', {}).get('summary', {})
util = summary.get('area', {}).get('utilization')
wns = summary.get('timing', {}).get('setup_wns')
power = summary.get('power', {}).get('total')
status = d.get('status', 'unknown')
spec_brief = d.get('raw_spec_brief') or d.get('spec_desc') or 'No spec summary'
top_module = d.get('top_module') or '-'
gds_link = f'<a class="mini-btn" href="../{html.escape(d["gds"])}" download>Download GDS</a>' if d.get('gds') else '<span class="muted">No GDS</span>'
preview = f'<img src="../{html.escape(d["preview"])}" alt="preview">' if d.get('preview') else '<div class="preview-empty">No preview</div>'
cards.append(
'<div class="project-card">'
f'<div class="preview-wrap">{preview}</div>'
f'<div class="project-head"><div><div class="status {html.escape(status)}">{html.escape(status)}</div><h3><a href="{html.escape(d["name"])}.html">{html.escape(d["name"])}</a></h3><div class="muted small">{html.escape(fmt(d.get("latest_run")))}</div><div class="muted small" style="margin-top:6px">{html.escape(spec_brief)}</div><div class="muted small">top module: <code>{html.escape(top_module)}</code></div></a></h3><div class="muted small">{html.escape(fmt(d.get("latest_run")))}</div></div><a class="detail-link" href="{html.escape(d["name"])}.html">Open</a></div>'
'<div class="metric-row">'
f'<div class="metric"><span>Utilization</span><strong>{html.escape(fmt(util))}</strong></div>'
f'<div class="metric"><span>Setup WNS</span><strong>{html.escape(fmt(wns))}</strong></div>'
f'<div class="metric"><span>Total Power</span><strong>{html.escape(fmt(power))}</strong></div>'
'</div>'
f'<div class="card-actions">{gds_link}</div>'
'</div>'
)
index = OUT / 'index.html'
html_text = '<!doctype html><html><head><meta charset="utf-8"><title>EDA Dashboard</title><meta http-equiv="refresh" content="60"><style>:root{--bg:#f6f7fb;--panel:#fff;--text:#111827;--muted:#6b7280;--line:#e5e7eb;--shadow:0 12px 32px rgba(15,23,42,.07);--blue:#2563eb;--blue-soft:#eef2ff}body{font-family:Arial,sans-serif;max-width:1280px;margin:24px auto;padding:0 16px;background:var(--bg);color:var(--text)}.hero{display:flex;justify-content:space-between;align-items:end;gap:16px}.muted{color:var(--muted)}.small{font-size:12px}.card{border:1px solid var(--line);background:var(--panel);border-radius:18px;padding:18px;margin:16px 0;box-shadow:var(--shadow)}.kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:16px 0}.kpi{background:white;border:1px solid var(--line);border-radius:18px;padding:16px;box-shadow:var(--shadow)}.kpi .label{font-size:12px;color:var(--muted)}.kpi .value{font-size:28px;font-weight:700;margin-top:8px}.projects{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px;margin-top:18px}.project-card{background:white;border:1px solid var(--line);border-radius:20px;overflow:hidden;box-shadow:var(--shadow)}.preview-wrap{aspect-ratio:16/9;background:linear-gradient(180deg,#f8fafc,#eef2f7);display:flex;align-items:center;justify-content:center;border-bottom:1px solid var(--line)}.preview-wrap img{width:100%;height:100%;object-fit:contain}.preview-empty{color:var(--muted)}.project-head{display:flex;justify-content:space-between;gap:12px;padding:16px 16px 8px 16px;align-items:start}.project-head h3{margin:8px 0 6px 0;font-size:20px}.project-head a{text-decoration:none;color:inherit}.detail-link,.mini-btn{display:inline-block;padding:9px 12px;border-radius:10px;text-decoration:none;font-weight:600}.detail-link{background:var(--blue-soft);color:#1d4ed8}.mini-btn{background:#111827;color:#fff}.metric-row{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;padding:0 16px 16px 16px}.metric{border:1px solid var(--line);background:#fafbfc;border-radius:14px;padding:12px}.metric span{display:block;font-size:12px;color:var(--muted);margin-bottom:6px}.metric strong{font-size:18px}.card-actions{padding:0 16px 16px 16px}.section-title{margin:24px 0 8px 0;font-size:18px}.status{display:inline-block;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}.status.done,.status.pass{background:#dcfce7;color:#166534}.status.started,.status.running{background:#fef3c7;color:#92400e}.status.fail{background:#fee2e2;color:#991b1b}.status.unknown{background:#e5e7eb;color:#374151}@media (max-width:1000px){.projects{grid-template-columns:1fr}.kpis{grid-template-columns:1fr 1fr}.hero{display:block}}</style></head><body><div class="hero"><div><h1>EDA Multi-Project Dashboard</h1><p class="muted">Overview of active demo projects, latest runs, top-line PPA, and downloadable outputs.</p></div><div class="muted">Auto-refresh every 60s</div></div><div class="kpis"><div class="kpi"><div class="label">Projects</div><div class="value">' + str(len(data)) + '</div></div><div class="kpi"><div class="label">Runs with GDS</div><div class="value">' + str(sum(1 for d in data if d.get('gds'))) + '</div></div><div class="kpi"><div class="label">Detail Pages</div><div class="value">' + str(len(data)) + '</div></div><div class="kpi"><div class="label">Dashboard</div><div class="value">MVP</div></div></div><div class="section-title">Projects (sorted by time, newest first)</div><div class="projects">' + ''.join(cards) + '</div></body></html>'
index.write_text(html_text, encoding='utf-8')
print(index)
if __name__ == '__main__':
main()
FILE:scripts/init_project.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
TEMPLATE_DIRS = [
"input", "rtl", "tb", "constraints", "lint", "sim", "synth", "backend", "reports"
]
def main():
if len(sys.argv) < 2:
print("usage: init_project.py <design-name> [base-dir]", file=sys.stderr)
sys.exit(1)
design_name = sys.argv[1]
base_dir = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("eda-runs")
root = base_dir / design_name
root.mkdir(parents=True, exist_ok=True)
for d in TEMPLATE_DIRS:
(root / d).mkdir(parents=True, exist_ok=True)
metadata = {
"design_name": design_name,
"status": "initialized"
}
(root / "metadata.json").write_text(json.dumps(metadata, indent=2), encoding="utf-8")
print(root)
if __name__ == "__main__":
main()
FILE:scripts/install_ubuntu_24_mvp.sh
#!/usr/bin/env bash
set -euo pipefail
# Install MVP open-source EDA toolchain on Ubuntu 24.04.
# Run as a sudo-capable user: bash scripts/install_ubuntu_24_mvp.sh
if [[ "EUID" -eq 0 ]]; then
echo "Please run as a regular user with sudo access, not root." >&2
exit 1
fi
sudo apt-get update
sudo apt-get install -y \
yosys \
iverilog \
verilator \
gtkwave \
klayout \
docker.io \
python3-pip \
python3-venv
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
python3 -m venv "$HOME/.venvs/openlane"
# shellcheck disable=SC1091
source "$HOME/.venvs/openlane/bin/activate"
pip install --upgrade pip
pip install openlane==2.3.10
echo
echo "Installation finished. Next steps:"
echo "1. Re-login or run: newgrp docker"
echo "2. Pull image: docker pull efabless/openlane:latest"
echo "3. Verify env: skills/eda-spec2gds/scripts/check_env.sh"
FILE:scripts/list_artifacts.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def main():
if len(sys.argv) < 3:
print('usage: list_artifacts.py <project-root> <output.json>', file=sys.stderr)
sys.exit(1)
root = Path(sys.argv[1])
out = Path(sys.argv[2])
files = []
for p in sorted(root.rglob('*')):
if p.is_file() and any(x in p.suffix.lower() for x in ['.gds', '.def', '.odb', '.png', '.md', '.json', '.vcd', '.log', '.v']):
files.append(str(p.relative_to(root)))
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps({'artifacts': files}, indent=2, ensure_ascii=False), encoding='utf-8')
print(out)
if __name__ == '__main__':
main()
FILE:scripts/normalize_spec.py
#!/usr/bin/env python3
from pathlib import Path
import sys
import re
TEMPLATE = """design_name: {design_name}
top_module: {top_module}
description: {description}
inputs:
- name: clk
width: 1
desc: main clock
- name: rst_n
width: 1
desc: active-low reset
outputs: []
clock:
name: clk
edge: posedge
reset:
name: rst_n
active_level: low
sync_or_async: async
functional_requirements:
- fill me
timing_target: 20
target_flow: openlane
verification_targets:
- reset behavior
assumptions:
- generated from free-form spec, review before backend
"""
def guess_design_name(text: str) -> str:
lower = text.lower()
if "fifo" in lower:
return "simple_fifo"
if "counter" in lower:
return "counter"
patterns = [
r"(?:module|called|named)\s+([A-Za-z_][A-Za-z0-9_]*)",
r"top[_ -]?module\s*(?:is|=)?\s*([A-Za-z_][A-Za-z0-9_]*)",
]
for pattern in patterns:
m = re.search(pattern, text, re.I)
if m:
return m.group(1)
return "design_top"
def guess_description(text: str) -> str:
first = " ".join(text.strip().splitlines()[:2]).strip()
return first or "hardware block from free-form spec"
def main():
if len(sys.argv) < 3:
print("usage: normalize_spec.py <raw-spec.md> <normalized-spec.yaml>", file=sys.stderr)
sys.exit(1)
raw_path = Path(sys.argv[1])
out_path = Path(sys.argv[2])
text = raw_path.read_text(encoding="utf-8")
design_name = guess_design_name(text)
desc = guess_description(text)
yaml_text = TEMPLATE.format(design_name=design_name, top_module=design_name, description=desc.replace(':', '-'))
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(yaml_text, encoding="utf-8")
print(out_path)
if __name__ == "__main__":
main()
FILE:scripts/open_preview_server.sh
#!/usr/bin/env bash
set -euo pipefail
PORT="-8765"
python3 /root/.openclaw/workspace/skills/eda-spec2gds/scripts/serve_multi_project_dashboard.py "$PORT"
FILE:scripts/render_gds_preview.py
#!/usr/bin/env python3
from pathlib import Path
import subprocess
import sys
import tempfile
import textwrap
def main():
if len(sys.argv) < 3:
print("usage: render_gds_preview.py <input.gds> <output.png> [size]", file=sys.stderr)
sys.exit(1)
gds = Path(sys.argv[1]).resolve()
out = Path(sys.argv[2]).resolve()
size = int(sys.argv[3]) if len(sys.argv) > 3 else 1600
if not gds.exists():
print(f"input GDS not found: {gds}", file=sys.stderr)
sys.exit(1)
out.parent.mkdir(parents=True, exist_ok=True)
script = textwrap.dedent(f'''
import pya
view = pya.LayoutView()
view.load_layout(r"{str(gds)}", 0)
view.max_hier()
view.zoom_fit()
view.save_image(r"{str(out)}", {size}, {size})
''')
with tempfile.NamedTemporaryFile('w', suffix='.py', delete=False) as f:
f.write(script)
script_path = f.name
try:
subprocess.run([
'klayout', '-b', '-nc', '-r', script_path
], check=True)
finally:
Path(script_path).unlink(missing_ok=True)
print(out)
if __name__ == '__main__':
main()
FILE:scripts/run_lint.sh
#!/usr/bin/env bash
set -euo pipefail
# usage: run_lint.sh <rtl-file> <log-file>
RTL_FILE="-"
LOG_FILE="-lint.log"
if [[ -z "$RTL_FILE" ]]; then
echo "usage: run_lint.sh <rtl-file> <log-file>" >&2
exit 1
fi
mkdir -p "$(dirname "$LOG_FILE")"
if command -v verilator >/dev/null 2>&1; then
verilator --lint-only "$RTL_FILE" >"$LOG_FILE" 2>&1
elif command -v iverilog >/dev/null 2>&1; then
iverilog -t null "$RTL_FILE" >"$LOG_FILE" 2>&1
else
echo "No lint-capable tool found (need verilator or iverilog)" >"$LOG_FILE"
exit 2
fi
echo "lint_ok" >>"$LOG_FILE"
FILE:scripts/run_openlane.sh
#!/usr/bin/env bash
set -euo pipefail
# usage: run_openlane.sh <project-dir> <config-json>
PROJECT_DIR="-"
CONFIG_JSON="-"
if [[ -z "$PROJECT_DIR" || -z "$CONFIG_JSON" ]]; then
echo "usage: run_openlane.sh <project-dir> <config-json>" >&2
exit 1
fi
if command -v openlane >/dev/null 2>&1; then
OPENLANE_CMD="$(command -v openlane)"
elif [[ -x "$HOME/.venvs/openlane/bin/openlane" ]]; then
OPENLANE_CMD="$HOME/.venvs/openlane/bin/openlane"
else
echo "openlane not found in PATH or ~/.venvs/openlane/bin/openlane" >&2
exit 1
fi
PROJECT_DIR="$(cd "$PROJECT_DIR" && pwd)"
CONFIG_PATH="$(cd "$PROJECT_DIR" && python3 - <<'PY' "$CONFIG_JSON"
import os, sys
print(os.path.abspath(sys.argv[1]))
PY
)"
cd "$PROJECT_DIR"
"$OPENLANE_CMD" --docker-no-tty --dockerized "$CONFIG_PATH"
FILE:scripts/run_sim.sh
#!/usr/bin/env bash
set -euo pipefail
# usage: run_sim.sh <rtl-file> <tb-file> <work-dir>
RTL_FILE="-"
TB_FILE="-"
WORK_DIR="-sim"
if [[ -z "$RTL_FILE" || -z "$TB_FILE" ]]; then
echo "usage: run_sim.sh <rtl-file> <tb-file> <work-dir>" >&2
exit 1
fi
mkdir -p "$WORK_DIR"
iverilog -o "$WORK_DIR/sim.out" "$RTL_FILE" "$TB_FILE" >"$WORK_DIR/compile.log" 2>&1
(
cd "$WORK_DIR"
vvp ./sim.out > sim.log 2>&1 || true
)
FILE:scripts/run_synth.sh
#!/usr/bin/env bash
set -euo pipefail
# usage: run_synth.sh <rtl-file> <top-module> <work-dir>
RTL_FILE="-"
TOP="-"
WORK_DIR="-synth"
if [[ -z "$RTL_FILE" || -z "$TOP" ]]; then
echo "usage: run_synth.sh <rtl-file> <top-module> <work-dir>" >&2
exit 1
fi
mkdir -p "$WORK_DIR"
cat > "$WORK_DIR/synth.ys" <<EOF
read_verilog $RTL_FILE
hierarchy -check -top $TOP
synth -top $TOP
stat
write_verilog $WORK_DIR/synth_output.v
EOF
yosys -s "$WORK_DIR/synth.ys" >"$WORK_DIR/synth.log" 2>&1
FILE:scripts/serve_demo_artifacts.py
#!/usr/bin/env python3
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
import subprocess
import sys
import os
ROOT = Path('/root/.openclaw/workspace/skills/eda-spec2gds/eda-runs/simple_fifo_demo').resolve()
REPORTS = ROOT / 'reports'
def latest_metrics_path():
candidates = sorted(ROOT.glob('constraints/runs/*/final/metrics.json'))
return candidates[-1] if candidates else None
def refresh_artifacts():
metrics = latest_metrics_path()
if metrics is not None:
subprocess.run([
'python3',
'/root/.openclaw/workspace/skills/eda-spec2gds/scripts/extract_ppa.py',
str(metrics),
str(REPORTS / 'ppa.json')
], check=False)
subprocess.run([
'python3',
'/root/.openclaw/workspace/skills/eda-spec2gds/scripts/extract_progress.py',
str(ROOT),
str(REPORTS / 'progress.json')
], check=False)
subprocess.run([
'python3',
'/root/.openclaw/workspace/skills/eda-spec2gds/scripts/list_artifacts.py',
str(ROOT),
str(REPORTS / 'artifacts-index.json')
], check=False)
subprocess.run([
'python3',
'/root/.openclaw/workspace/skills/eda-spec2gds/scripts/generate_artifact_index.py'
], check=True)
class Handler(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path in ['/', '/index.html']:
refresh_artifacts()
return super().do_GET()
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765
REPORTS.mkdir(parents=True, exist_ok=True)
refresh_artifacts()
os.chdir(str(REPORTS))
server = ThreadingHTTPServer(('0.0.0.0', port), Handler)
print(f'Serving {REPORTS} on http://0.0.0.0:{port}/')
print(f'Open http://<server-ip>:{port}/index.html')
server.serve_forever()
if __name__ == '__main__':
main()
FILE:scripts/serve_multi_project_dashboard.py
#!/usr/bin/env python3
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
import subprocess
import os
import sys
BASE = Path('/root/.openclaw/workspace/skills/eda-spec2gds/eda-runs').resolve()
OUT = BASE / '_dashboard'
def refresh():
subprocess.run(['python3', '/root/.openclaw/workspace/skills/eda-spec2gds/scripts/generate_multi_project_dashboard.py'], check=True)
class Handler(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path in ['/', '/index.html'] or self.path.endswith('.html'):
refresh()
return super().do_GET()
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8766
OUT.mkdir(parents=True, exist_ok=True)
refresh()
os.chdir(str(BASE))
server = ThreadingHTTPServer(('0.0.0.0', port), Handler)
print(f'Serving multi-project dashboard on http://0.0.0.0:{port}/_dashboard/index.html')
server.serve_forever()
if __name__ == '__main__':
main()
FILE:scripts/summarize_run.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def tail(path: Path, n: int = 20):
if not path.exists():
return None
lines = path.read_text(encoding='utf-8', errors='ignore').splitlines()
return lines[-n:]
def main():
root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
summary = {
'status': 'PARTIAL',
'artifacts': {},
'hints': []
}
for rel in ['lint/lint.log', 'sim/sim.log', 'synth/synth.log']:
p = root / rel
if p.exists():
summary['artifacts'][rel] = str(p)
summary[f'{rel}_tail'] = tail(p)
if (root / 'synth' / 'synth_output.v').exists():
summary['status'] = 'PASS'
elif (root / 'sim' / 'sim.log').exists():
summary['hints'].append('simulation exists but synthesis netlist missing')
print(json.dumps(summary, indent=2))
if __name__ == '__main__':
main()
FILE:scripts/write_success_summary.py
#!/usr/bin/env python3
from pathlib import Path
import json
import sys
def tail(path: Path, n: int = 30):
if not path.exists():
return []
return path.read_text(encoding='utf-8', errors='ignore').splitlines()[-n:]
def main():
if len(sys.argv) < 3:
print('usage: write_success_summary.py <openlane-results.json> <output.md>', file=sys.stderr)
sys.exit(1)
results_path = Path(sys.argv[1])
out_path = Path(sys.argv[2])
results = json.loads(results_path.read_text(encoding='utf-8'))
flow_log = Path(results['reports'].get('flow.log', '')) if results.get('reports') else None
warning_log = Path(results['reports'].get('warning.log', '')) if results.get('reports') else None
error_log = Path(results['reports'].get('error.log', '')) if results.get('reports') else None
lines = []
lines.append('# EDA Run Summary')
lines.append('')
lines.append(f"- Project: `{results.get('project_dir')}`")
lines.append(f"- Latest run: `{results.get('latest_run')}`")
lines.append(f"- Final GDS: `{results.get('gds')}`")
lines.append(f"- Final dir: `{results.get('reports', {}).get('final_dir')}`")
lines.append('')
lines.append('## Status')
lines.append('')
lines.append('- OpenLane flow completed and final GDS was generated.')
lines.append('')
if warning_log and warning_log.exists():
warnings = tail(warning_log, 20)
if warnings:
lines.append('## Warning tail')
lines.append('')
lines.extend([f'- {w}' for w in warnings if w.strip()])
lines.append('')
if error_log and error_log.exists():
errors = [e for e in tail(error_log, 20) if e.strip()]
if errors:
lines.append('## Error tail')
lines.append('')
lines.extend([f'- {e}' for e in errors])
lines.append('')
if flow_log and flow_log.exists():
flow_tail = tail(flow_log, 20)
if flow_tail:
lines.append('## Flow tail')
lines.append('')
lines.extend([f'- {x}' for x in flow_tail if x.strip()])
lines.append('')
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
print(out_path)
if __name__ == '__main__':
main()
Agent Skill 设计模式模板库。当用户要创建新 skill、优化现有技能结构、或需要技能设计指导时激活。提供 5 个可复用模式:Tool Wrapper、Generator、Reviewer、Inversion、Pipeline。
---
name: skill-patterns
description: Agent Skill design pattern template library. Activates when users want to create new skills, optimize existing skill structures, or need skill design guidance. Provides 5 reusable patterns: Tool Wrapper, Generator, Reviewer, Inversion, Pipeline.
metadata:
version: 1.1.0
author: Auther
license: MIT
tags: [skill-design, templates, best-practices, agent-development, adk, patterns]
trigger-keywords: [create skill, skill template, skill design, agent pattern, skill pattern, skill structure, skill framework, build a skill]
---
You are an Agent Skill design expert. Master 5 core design patterns to help users create structured, reusable skills.
## Core Capabilities
When users need to create or optimize a skill, **must** load pattern documents from `references/` directory to get complete templates.
## 5 Design Patterns
| Pattern | Purpose | Trigger Scenarios |
|-----|------|---------|
| **Tool Wrapper** | Inject domain expertise/norms | User mentions specific frameworks, team conventions |
| **Generator** | Generate structured content | Writing reports, documents, scaffolding |
| **Reviewer** | Review/audit/score | Code review, quality checks |
| **Inversion** | Interview first, then execute | Complex tasks with unclear requirements |
| **Pipeline** | Multi-step sequential execution | Document generation, data transformation |
## Usage Flow
### 1. Understand User Needs
Ask user: What type of skill do you want to create? Or what problem do you want to solve?
### 2. Recommend Patterns
Recommend the most suitable pattern based on user needs (combinable):
- Need to inject domain knowledge? → Tool Wrapper
- Need fixed output format? → Generator
- Need review/checklist? → Reviewer
- Unclear requirements? → Inversion
- Multi-step workflow? → Pipeline
### 3. Load Templates
Load the complete template for the corresponding pattern (`references/<pattern>.md`)
### 4. Guide Creation
Guide users to create following the template structure:
- SKILL.md entry file
- references/ directory (conventions/checklists)
- assets/ directory (template files)
- scripts/ directory (optional helper scripts)
### 5. Output Checklist
Use `references/creation-checklist.md` to verify skill completeness
## Pattern Combination Examples
- **Generator + Reviewer**: Auto self-check after generation
- **Inversion + Generator**: Interview to collect variables first, then fill template
- **Pipeline + Reviewer**: Check quality after each step
- **Tool Wrapper + Pipeline**: Load different conventions for each step
## Standard Directory Structure
```
skills/<skill-name>/
├── SKILL.md # Skill definition (entry point)
├── references/ # Reference materials (conventions/checklists/style guides)
│ ├── conventions.md
│ ├── checklist.md
│ └── style-guide.md
├── assets/ # Output templates
│ ├── template.md
│ └── plan-template.md
└── scripts/ # Optional: helper scripts
└── validate.py
```
## Example Output
When user says "I want to create a code review skill":
1. Recommend pattern: **Reviewer**
2. Load: `references/reviewer-pattern.md`
3. Guide creation:
- SKILL.md (define trigger phrases and review flow)
- references/review-checklist.md (review checklist)
4. Provide template examples
---
*Design patterns source: Google ADK Best Practices*
FILE:README.md
# Agent Skill Design Pattern Templates
Based on Google ADK and ecosystem best practices, providing 5 reusable skill design patterns.
## 🎯 Use Cases
- Need structured templates when creating new Agent Skills
- Optimize existing skill design and quality
- Standardize skill development within teams
- Learn best practices for Agent Skill design
## 📦 Included Patterns
| Pattern | Purpose | Typical Scenarios |
|-----|------|---------|
| [Tool Wrapper](./references/tool-wrapper-pattern.md) | Inject domain expertise | Framework conventions, team agreements, API usage guides |
| [Generator](./references/generator-pattern.md) | Generate structured content | Technical reports, documentation, scaffolding, Commit Messages |
| [Reviewer](./references/reviewer-pattern.md) | Review/audit/score | Code Review, security audits, quality checks |
| [Inversion](./references/inversion-pattern.md) | Interview first, then execute | Project planning with unclear requirements, complex system design |
| [Pipeline](./references/pipeline-pattern.md) | Multi-step sequential execution | Document generation, code migration, data transformation |
## 🚀 Quick Start
### 1. Activate the Skill
Mention these keywords in conversation to activate:
- create skill
- skill template
- skill design
- agent pattern
- skill pattern
- skill structure
### 2. Choose a Pattern
The skill will recommend the most suitable design pattern based on your needs, or combine multiple patterns.
### 3. Create Following Templates
Follow the structure and flow of the recommended pattern to create your skill.
## 📁 Directory Structure
```
skill-patterns/
├── SKILL.md # Skill entry point
├── README.md # Usage guide
└── references/
├── tool-wrapper-pattern.md # Pattern 1: Tool Wrapper
├── generator-pattern.md # Pattern 2: Generator
├── reviewer-pattern.md # Pattern 3: Reviewer
├── inversion-pattern.md # Pattern 4: Inversion
├── pipeline-pattern.md # Pattern 5: Pipeline
└── creation-checklist.md # Creation checklist
```
## 💡 Pattern Combination Examples
- **Generator + Reviewer**: Auto quality check after generating reports
- **Inversion + Generator**: Interview requirements first, then generate solution
- **Pipeline + Reviewer**: Review quality after each step
- **Tool Wrapper + Pipeline**: Load different conventions for each step
## 📊 Decision Tree
```
Is user request clear?
├─ Yes → Need specific domain knowledge?
│ ├─ Yes → Tool Wrapper
│ └─ No → Need fixed output format?
│ ├─ Yes → Generator
│ └─ No → Is it a review task?
│ ├─ Yes → Reviewer
│ └─ No → Single-step execution (no pattern needed)
└─ No → Need multi-turn requirement gathering?
├─ Yes → Inversion
└─ No → Have mandatory multi-step sequence?
├─ Yes → Pipeline
└─ No → Inversion (clarify first)
```
## 📖 Source
Design patterns based on: Google Cloud Tech - "5 Agent Skill design patterns every ADK developer should know"
## 📝 Version
- v1.1.0 - English release with 5 core patterns + creation checklist
## 🤝 Contributing
Welcome to submit new pattern variations and improvement suggestions!
FILE:references/creation-checklist.md
# Skill Creation Checklist
Use this checklist to verify that a newly created skill is complete.
## Basic Structure
- [ ] `SKILL.md` exists and contains required fields
- [ ] `name` field: skill identifier (lowercase, hyphen-separated)
- [ ] `description` field: clearly explains purpose and trigger conditions
- [ ] `metadata.trigger-keywords` or `metadata.trigger-phrases`: activation word list
- [ ] Directory structure follows standards (references/, assets/ as needed)
## SKILL.md Content Quality
- [ ] Clearly explains when to activate (trigger conditions)
- [ ] Clearly explains core capabilities/responsibilities
- [ ] Has execution flow or step instructions
- [ ] Has output format requirements (if applicable)
- [ ] Has correct/incorrect example comparisons (if applicable)
- [ ] Explains which references/assets files need to be loaded
## Pattern Matching
Based on skill type, confirm the correct pattern is selected:
- [ ] **Tool Wrapper**: Has references/conventions.md or similar convention files
- [ ] **Generator**: Has assets/template.md and references/style-guide.md
- [ ] **Reviewer**: Has references/checklist.md with clear severity levels (error/warning/info)
- [ ] **Inversion**: Has clear question list and interview flow, prohibits premature execution
- [ ] **Pipeline**: Has clear step sequence and checkpoints (⏸️)
## Maintainability
- [ ] Conventions/checklists separated from main logic (for independent updates)
- [ ] Templates use variable placeholders (e.g., `{{title}}`)
- [ ] Has version number (in SKILL.md metadata or filename)
- [ ] Has clear directory organization and naming
## Testing & Validation
- [ ] Trigger word test: activate with trigger words, confirm skill responds correctly
- [ ] Flow test: execute complete flow per skill instructions
- [ ] Boundary test: test handling of ambiguous input, error input
- [ ] Output validation: confirm output format meets expectations
## Documentation Completeness
- [ ] README.md (if needed, explains skill purpose and usage)
- [ ] Example files (if needed, shows typical input/output)
- [ ] Changelog (if needed, records version evolution)
## Pre-release Checks
- [ ] No sensitive information (API Keys, passwords, internal URLs)
- [ ] No hardcoded paths (use relative paths)
- [ ] .gitignore configured correctly (exclude secrets, temp files)
- [ ] Code/scripts executable (if scripts/ directory exists)
---
## Quick Scoring
**Structure Completeness**: _/5 (5 basic structure items)
**Content Quality**: _/5 (SKILL.md quality)
**Pattern Matching**: _/5 (pattern selection & implementation)
**Maintainability**: _/5 (separation & organization)
**Test Coverage**: _/5 (testing validation)
**Total Score**: __/25
- 20-25 points: Ready to publish
- 15-19 points: Needs improvement
- <15 points: Recommend refactor
FILE:references/generator-pattern.md
# Pattern 2: Generator
## Core Purpose
Force **structurally consistent** output, solving the "every generation has different format" problem.
## Use Cases
- Technical report/document generation
- API documentation
- Project scaffolding
- Standardized emails/announcements
- Commit Message generation
## Directory Structure
```
skills/report-generator/
├── SKILL.md
├── references/
│ └── style-guide.md # Style guide (tone/format)
└── assets/
└── report-template.md # Output template
```
## SKILL.md Template
```markdown
---
name: report-generator
description: Generate structured technical reports. Activates when users request to write, create, or draft reports, summaries, or analysis documents.
metadata:
pattern: generator
output-format: markdown
trigger-phrases: [write report, generate document, create summary, draft a report, write a summary]
---
You are a technical report generator. **Strictly follow these steps**:
## Step 1: Load Style Guide
Load `references/style-guide.md` to get tone and format conventions.
## Step 2: Load Template
Load `assets/report-template.md` to get required output structure.
## Step 3: Collect Missing Information
Ask user for following information (if not provided):
- Topic or core content
- Key findings or data points
- Target audience (technical/management/general)
- Expected length (brief/detailed)
## Step 4: Fill Template
Fill each field of template per style guide rules.
**Prohibit** omitting any sections in template.
## Step 5: Output
Return complete Markdown document.
## Output Requirements
- Use Markdown format
- All section headers match template
- Reference tone conventions from style guide
- If data exists, present with tables or lists
```
## assets/report-template.md Template
```markdown
# {{title}}
## Executive Summary
{{Core conclusions within 200 words}}
## Background
{{Problem background/context}}
## Methodology
{{Analysis methods/tools/data sources}}
## Key Findings
{{List 3-5 core findings as bullet points}}
## Detailed Analysis
{{In-depth analysis, can be split into subsections}}
## Recommendations & Next Steps
{{Actionable recommendations}}
## Appendix
{{Supplementary materials/reference links}}
---
*Generated: {{date}}*
*Author: {{generator}}*
```
## references/style-guide.md Template
```markdown
# Technical Report Style Guide
## Tone Conventions
- **Technical audience**: Professional, precise, may include terminology
- **Management audience**: Conclusions first, avoid technical details, emphasize ROI
- **General audience**: Accessible, use analogies, explain terminology
## Format Conventions
- Headers use Sentence case (only first letter capitalized)
- Code blocks must specify language
- Tables must have headers
- Use > format for quotes
## Prohibited Content
- Avoid vague words like "might", "perhaps"
- Don't use first person (I/we)
- Don't add unverified data
## Length Control
- Executive summary: ≤200 words
- Key findings: 3-5 items, each ≤50 words
- Detailed analysis: adjust per topic, but each section ≤800 words
```
## Variant: Interactive Generation
```markdown
## Step 3 (Interactive Version): Step-by-Step Confirmation
After filling each section, show to user and ask:
"Does this section meet your expectations? Need any adjustments?"
Continue to next section after user confirms.
```
## Pros & Cons
| Pros | Cons |
|-----|------|
| Highly consistent output | Templates rigid, low flexibility |
| Newcomers can generate professional content | Need to maintain template library |
| Easy to automate acceptance | High template design cost |
## Pattern Combinations
### Generator + Inversion
Use Inversion pattern to collect requirements first, then use Generator to fill template:
```markdown
## Phase 1: Requirements Gathering (Inversion)
Ask 5 questions to understand user needs
## Phase 2: Generate Report (Generator)
Fill template with collected information
```
### Generator + Reviewer
Auto self-check after generation:
```markdown
## Step 6: Quality Check
Load `references/quality-checklist.md` for self-check:
- [ ] All sections complete
- [ ] No spelling errors
- [ ] Data has source citations
If issues found, fix and re-output.
```
---
## Checklist
- [ ] `assets/` directory has complete templates
- [ ] `references/` directory has style guide
- [ ] SKILL.md specifies step sequence
- [ ] Has missing information handling logic (ask user)
- [ ] Output format is clear (Markdown/JSON/etc.)
FILE:references/inversion-pattern.md
# Pattern 4: Inversion
## Core Purpose
**Interview first, then execute**, preventing Agent from guessing when requirements are unclear, forcing complete context collection.
## Use Cases
- Project planning with vague requirements
- Complex system design
- Personalized tasks (need user preferences)
- Multi-constraint tasks
## Directory Structure
```
skills/project-planner/
├── SKILL.md
└── assets/
└── plan-template.md # Final output template (optional)
```
## SKILL.md Template
```markdown
---
name: project-planner
description: Gather requirements through structured questioning, then generate project plan. Activates when users say "I want to build", "help me plan", "design a system", "start new project".
metadata:
pattern: inversion
interaction: multi-turn
trigger-phrases: [I want to, help me plan, design a, build a, plan a, design a system]
---
You are conducting a structured requirements interview. **Strictly follow these rules**:
## ⛔ Prohibitions
- **Prohibit** starting design or writing code before interview completes
- **Prohibit** asking all questions at once (ask 1 at a time)
- **Prohibit** skipping any questions
## ✅ Requirements
- Ask only 1 question at a time, wait for user answer before next
- If user answer is vague, ask follow-up for clarification
- After interview completes, summarize requirements for user confirmation
---
## Phase 1 — Problem Discovery (ask one by one, wait for each answer)
Ask following questions in order, **do not skip any**:
**Q1**: "What problem does this project solve for users?"
→ Wait for user answer
**Q2**: "Who are the primary users? What is their technical level?"
→ Wait for user answer
**Q3**: "Expected scale? (daily active users, data volume, request frequency)"
→ Wait for user answer
---
## Phase 2 — Technical Constraints (starts only after all Phase 1 answered)
**Q4**: "What deployment environment? (local/cloud/edge)"
→ Wait for user answer
**Q5**: "Any technology stack preferences? (language/framework/database)"
→ Wait for user answer
**Q6**: "Non-negotiable requirements? (latency/availability/compliance/budget)"
→ Wait for user answer
---
## Phase 3 — Requirements Confirmation (after all questions answered)
1. Summarize all collected requirements
2. Ask user: "Is above requirements summary accurate? Any omissions or adjustments needed?"
3. Iterate based on user feedback until user confirms
---
## Phase 4 — Generate Plan (after user confirms requirements)
1. Load `assets/plan-template.md` to get output format
2. Fill each section with collected requirements
3. Present complete plan
4. Ask: "Does this plan meet your expectations? What needs adjustment?"
5. Iterate based on feedback until user confirms
```
## assets/plan-template.md Template
```markdown
# {{project_name}} - Project Plan
## 1. Project Overview
- **Problem Statement**: {{Q1 answer}}
- **Target Users**: {{Q2 answer}}
- **Expected Scale**: {{Q3 answer}}
## 2. Technical Architecture
- **Deployment Environment**: {{Q4 answer}}
- **Technology Stack**: {{Q5 answer}}
- **Architecture Diagram**: {{generate architecture diagram description}}
## 3. Core Features
- {{feature list}}
## 4. Constraints & Requirements
- **Performance Requirements**: {{Q6 answer}}
- **Compliance Requirements**: {{Q6 answer}}
- **Budget Constraints**: {{Q6 answer}}
## 5. Milestone Plan
| Phase | Deliverables | Estimated Time |
|-----|--------|---------|
| Phase 1 | ... | ... |
| Phase 2 | ... | ... |
## 6. Risks & Mitigation
- **Technical Risks**: ...
- **Resource Risks**: ...
## 7. Next Actions
1. ...
2. ...
---
*Generated: {{date}}*
```
## Variant: Simplified Inversion
For lightweight tasks:
```markdown
## Quick Interview (3 questions)
Before starting, I need to understand:
1. "What is your primary goal?"
2. "Any technical constraints or preferences?"
3. "How do you define success? (acceptance criteria)"
Please answer in order, then I'll provide a solution.
```
## Variant: Branching Interview
Dynamically adjust questions based on user answers:
```markdown
## Phase 1 — Problem Discovery
**Q1**: "What is the main problem with this project?"
→ If user answers "performance issues", enter performance optimization branch
→ If user answers "missing features", enter feature design branch
→ If user answers "too expensive", enter cost optimization branch
```
## Dialogue State Tracking
For complex interviews, recommend tracking state:
```markdown
## Current State
- Phase: 2/3
- Questions answered: Q1, Q2, Q3, Q4
- Questions pending: Q5, Q6
- Requirements confirmed: No
```
## Pros & Cons
| Pros | Cons |
|-----|------|
| Avoids understanding偏差 | Many dialogue turns, time-consuming |
| Strong user involvement | Users may get impatient |
| Output better matches expectations | Question design needs experience |
## Combination with Generator
```markdown
## Inversion → Generator Flow
Phase 1-3: Inversion gathers requirements
↓
Phase 4: Generator fills template
↓
Phase 5: User confirms → If not satisfied, return to Phase 1 to re-gather
```
## Example Dialogue
```
User: I want to build a blog system
Agent: Great! Let me ask a few questions to understand your needs.
**Q1**: What problem does this blog system primarily solve for users?
Is it a personal blog, enterprise content platform, or other type?
User: Enterprise content platform for publishing technical articles and case studies
Agent: Understood!
**Q2**: Who are the primary users? What is their technical level?
Are they internal employees, customers, or public readers?
User: Mainly potential customers, medium technical level
Agent: Got it!
**Q3**: Expected scale? Like daily visitors, number of articles?
User: Expected 1000 DAU, initially about 50 articles
Agent: ...(continues with Phase 2 technical constraint questions)
```
---
## Checklist
- [ ] Clearly prohibit premature execution ("DO NOT start until...")
- [ ] Questions organized in phases
- [ ] Ask only 1 question at a time
- [ ] Has requirements confirmation step
- [ ] Has final output template (optional)
- [ ] Supports iterative feedback
FILE:references/pipeline-pattern.md
# Pattern 5: Pipeline
## Core Purpose
Force **multi-step sequential execution** with checkpoints, preventing skipping critical steps.
## Use Cases
- Document generation (parse→generate→assemble→QC)
- Code migration/refactoring
- Data transformation workflows
- Multi-stage processing tasks
## Directory Structure
```
skills/doc-pipeline/
├── SKILL.md
├── references/
│ ├── docstring-style.md # Used in Step 2
│ └── quality-checklist.md # Used in Step 4
└── assets/
└── api-doc-template.md # Used in Step 3
```
## SKILL.md Template
```markdown
---
name: doc-pipeline
description: Generate API documentation from Python source code through multi-step pipeline. Activates when users request to document modules, generate API docs, or create documentation from code.
metadata:
pattern: pipeline
steps: 4
trigger-phrases: [generate docs, document this, create API docs]
---
You are running a documentation generation pipeline. **Strictly execute each step in order, prohibit skipping**.
## ⛔ Global Rules
- **Prohibit** skipping any steps
- **Prohibit** continuing when step fails
- **Prohibit** proceeding to next step without user confirmation (if checkpoint exists)
- After each step completes, show results to user and explain next step
---
## Step 1 — Parse & Inventory Generation
**Task**: Analyze user's Python code, extract all public classes, functions, constants
**Output**:
```
Detected following public API:
- [ ] class UserManager
- [ ] def authenticate_user()
- [ ] def create_session()
- [ ] const MAX_RETRY = 3
Please confirm: Is this the complete public API you want documented?
Anything to add or exclude?
```
**Checkpoint**: ⏸️ Wait for user confirmation
---
## Step 2 — Generate Docstrings
**Task**: Generate docstrings for each function missing documentation
**Prerequisite**: Load `references/docstring-style.md`
**Execution**:
For each function:
1. Generate docstring per style guide
2. Include: parameter descriptions, return values, exceptions, examples
**Output**:
```
Generated docstrings for following functions:
### authenticate_user()
```python
def authenticate_user(username: str, password: str) -> User:
"""Validate user credentials and return user object.
Args:
username: Username (email format)
password: User password (plaintext)
Returns:
User: User object on successful authentication
Raises:
AuthenticationError: When credentials invalid
Example:
>>> user = authenticate_user("[email protected]", "pass123")
"""
```
**Checkpoint**: ⏸️ Ask user: "Do these docstrings meet expectations? Need adjustments?"
---
## Step 3 — Assemble Documentation
**Prerequisite**: User confirmed Step 2
**Task**: Load `assets/api-doc-template.md`, compile complete documentation
**Output**: Complete API reference document (Markdown format)
---
## Step 4 — Quality Check
**Task**: Self-check against `references/quality-checklist.md`
**Check Items**:
- [ ] Every public symbol has documentation
- [ ] Every parameter has type and description
- [ ] Every function has at least 1 usage example
- [ ] No spelling errors
- [ ] Consistent formatting
**Output**:
```
Quality Check Results:
✅ All checks passed
OR
⚠️ Found 2 issues:
1. create_session() missing example
2. Spelling error: line 35 "authentcate" → "authenticate"
Auto-fixed, please confirm.
```
**Checkpoint**: ⏸️ Wait for user confirmation
---
## Step 5 — Final Delivery
Present complete document, ask:
"Documentation complete! Need to export to other formats (PDF/HTML) or make other adjustments?"
```
## references/docstring-style.md Template
```markdown
# Docstring Style Guide
## Format Conventions
- Use Google style
- First line: One-sentence summary (start with verb)
- After blank line: Detailed description (optional)
- Args/Returns/Raises/Example sections
## Example
```python
def process_data(data: list[dict], threshold: float = 0.5) -> dict:
"""Process raw data and return aggregated results.
Perform filtering, transformation, and aggregation on input data.
Args:
data: Raw data list, each element is a dictionary
threshold: Filtering threshold, range 0-1, default 0.5
Returns:
Dictionary containing following keys:
- total_count: Total number of processed data
- filtered_count: Number after filtering
- aggregated: Aggregation results
Raises:
ValueError: When data format invalid or threshold out of range
Example:
>>> result = process_data([{"value": 1}, {"value": 2}], 0.3)
>>> result["total_count"]
2
"""
```
## Prohibited Content
- Don't start with "This function..."
- Don't repeat information already expressed in function name
- Don't use vague words ("might", "perhaps")
```
## references/quality-checklist.md Template
```markdown
# Documentation Quality Checklist
## Completeness
- [ ] All public classes, functions, constants have documentation
- [ ] All parameters have type annotations and descriptions
- [ ] All return values have descriptions
- [ ] All exceptions have descriptions
## Accuracy
- [ ] Example code is executable
- [ ] Parameter descriptions match actual usage
- [ ] No outdated information
## Readability
- [ ] No spelling errors
- [ ] Consistent formatting
- [ ] Consistent terminology
## Usefulness
- [ ] Each function has at least 1 example
- [ ] Complex logic has explanations
- [ ] Has usage scenario descriptions
```
## Variant: Automated Pipeline
Automatic flow without user confirmation:
```markdown
## Automatic Mode
Step 1 → Step 2 → Step 3 → Step 4 → Output
Automatically proceed to next step after each step completes, output results once at the end.
Suitable for high-trust, low-tolerance scenarios.
```
## Variant: Conditional Branching Pipeline
```markdown
## Step 2 — Conditional Processing
If code is Python → Generate Google-style docstrings
If code is JavaScript → Generate JSDoc comments
If code is Verilog → Generate comment headers
```
## Pros & Cons
| Pros | Cons |
|-----|------|
| Controllable flow, stable quality | Many steps, time-consuming |
| Each step independently verifiable | Users need multiple confirmations |
| Easy to locate problem steps | Flow rigid, hard to adjust flexibly |
## Combination with Reviewer
```markdown
## Pipeline + Reviewer
Step 1: Parse
Step 2: Generate
Step 3: Assemble
Step 4: Reviewer review (load checklist to score)
Step 5: If score<8, return to Step 2 to regenerate
Step 6: Deliver
```
---
## Checklist
- [ ] Step sequence is clear
- [ ] Each step has clear input/output
- [ ] Checkpoints clearly marked (⏸️)
- [ ] Has failure handling logic
- [ ] Load references/assets on demand
- [ ] Final output format is clear
---
## Pattern Comparison Summary
| Pattern | Core Characteristics | Best Scenarios |
|-----|---------|---------|
| Tool Wrapper | Load knowledge on-demand | Framework conventions/team agreements |
| Generator | Template filling | Document/report generation |
| Reviewer | Checklist-based review | Code Review/audits |
| Inversion | Interview first, then execute | Tasks with unclear requirements |
| Pipeline | Multi-step + checkpoints | Complex transformation workflows |
FILE:references/reviewer-pattern.md
# Pattern 3: Reviewer
## Core Purpose
Separate **review criteria** from **review logic**, using external checklists for modular auditing.
## Use Cases
- Code Review
- Security audits (OWASP)
- Compliance checks
- Design reviews
- Documentation quality checks
## Directory Structure
```
skills/code-reviewer/
├── SKILL.md
└── references/
└── review-checklist.md # Review checklist
```
## SKILL.md Template
```markdown
---
name: code-reviewer
description: Python code quality review. Activates when users submit code for feedback, request review, or need code audit.
metadata:
pattern: reviewer
severity-levels: [error, warning, info]
trigger-phrases: [code review, review this, check code, audit this]
---
You are a Python code reviewer. **Strictly follow this flow**:
## Step 1: Load Checklist
Load `references/review-checklist.md` to get complete review criteria.
## Step 2: Understand Code Intent
Read user's code first, understand its function and goals.
**Prohibit** finding faults without understanding intent.
## Step 3: Apply Checklist Item by Item
For each rule in checklist:
1. Check if code complies
2. If violation, record: line number + severity + reason + fix suggestion
## Step 4: Generate Structured Report
Output format as follows:
### 📊 Overview
- **Function**: What this code does
- **Overall quality**: One-sentence evaluation
### 🚨 Errors (must fix)
{{List all error-level issues}}
### ⚠️ Warnings (recommended to fix)
{{List all warning-level issues}}
### ℹ️ Tips (consider optimizing)
{{List all info-level issues}}
### 📈 Score
**X/10** - Scoring rationale
### 🎯 Top 3 Recommendations
{{Top 3 improvement suggestions ranked by impact}}
```
## references/review-checklist.md Template
```markdown
# Python Code Review Checklist v2.0
## P0 - Errors (must fix)
### Security
- [ ] Hardcoded passwords/keys/API tokens
- [ ] SQL injection risks (string-concatenated SQL)
- [ ] Command injection risks (os.system with user input)
- [ ] Sensitive information printed to logs
### Correctness
- [ ] Unhandled exceptions (bare except)
- [ ] Resources not released (files/connections not closed)
- [ ] Race condition risks
- [ ] Boundary conditions not handled (empty lists/None/negative numbers)
## P1 - Warnings (recommended to fix)
### Readability
- [ ] Functions over 50 lines
- [ ] Nesting over 4 levels
- [ ] Unclear variable naming (single letter/meaningless)
- [ ] Missing type annotations
### Performance
- [ ] Repeated calculations inside loops
- [ ] Unnecessary list copies
- [ ] Using list instead of generators
- [ ] N+1 query problems
### Maintainability
- [ ] Duplicate code (DRY principle)
- [ ] Magic numbers (undefined constants)
- [ ] Overly long parameter lists (>5)
- [ ] Missing docstrings
## P2 - Tips (can optimize)
### Best Practices
- [ ] Could use standard library instead of custom implementation
- [ ] Could use more Pythonic写法
- [ ] Could add unit tests
- [ ] Could add type hints
```
## Variants: Domain-Specific Reviewer
### Security Audit
```markdown
references/security-checklist.md
- OWASP Top 10
- Authentication/authorization checks
- Data encryption checks
- Log audit checks
```
### Frontend Design Review
```markdown
references/design-checklist.md
- Responsive layout
- Accessibility (a11y)
- Color contrast
- Interaction feedback
- Loading state handling
```
### EDA Code Review
```markdown
references/rtl-checklist.md
- Synthesizability checks
- Timing constraints
- Reset strategy
- Clock domain crossing
- Area optimization suggestions
```
## Output Example
```markdown
### 📊 Overview
- **Function**: User login validation, includes password hashing and JWT generation
- **Overall quality**: Core logic correct, but has 2 security risks
### 🚨 Errors (must fix)
1. **Line 15** - Hardcoded JWT_SECRET
- Risk: Key leak allows Token forgery
- Fix: Read from environment variable `os.environ.get("JWT_SECRET")`
2. **Line 28** - Bare except catches all exceptions
- Risk: Masks real errors, hard to debug
- Fix: Catch specific exception types `except AuthenticationError:`
### ⚠️ Warnings (recommended to fix)
1. **Line 10** - Function 65 lines, recommend splitting
2. **Line 33** - Missing type annotations
### 📈 Score
**6/10** - Functional but has serious security issues
### 🎯 Top 3 Recommendations
1. Remove hardcoded keys immediately (security risk)
2. Add input validation and parameter checks
3. Split function into validate_user() + generate_token()
```
## Pros & Cons
| Pros | Cons |
|-----|------|
| Checklists can be updated independently | Checklist design needs domain experts |
| Reusable (change checklist = change scenario) | May generate大量 low-value tips |
| Structured output, easy to automate | Severity classification may be subjective |
## Automation Extensions
### CI Integration
```bash
# Review output in JSON format for CI parsing
{
"score": 6,
"errors": [...],
"warnings": [...],
"info": [...]
}
```
### LLM-as-a-judge Integration
```markdown
## Step 5: Secondary Validation
Send review results to another LLM instance:
"Please evaluate if above review is reasonable, any omissions or misjudgments?"
```
---
## Checklist
- [ ] `references/checklist.md` exists with clear categorization
- [ ] Severity levels clear (error/warning/info)
- [ ] SKILL.md requires understanding code intent before review
- [ ] Output format is structured
- [ ] Has scoring mechanism and Top recommendations
FILE:references/tool-wrapper-pattern.md
# Pattern 1: Tool Wrapper
## Core Purpose
Inject domain-specific expertise for particular libraries/frameworks into the Agent, **load on-demand**, without occupying everyday conversation context.
## Use Cases
- Team internal coding conventions
- Framework best practices (FastAPI/React/Verilog)
- API usage conventions
- Domain-specific terminology
## Directory Structure
```
skills/api-expert/
├── SKILL.md
└── references/
└── conventions.md
```
## SKILL.md Template
```markdown
---
name: api-expert
description: FastAPI development best practices. Activates when users build, review, or debug FastAPI applications, REST APIs, or Pydantic models.
metadata:
pattern: tool-wrapper
domain: fastapi
trigger-keywords: [fastapi, pydantic, REST API, endpoint, dependency injection]
---
You are a FastAPI development expert. Apply the following conventions to user's code or questions.
## Core Conventions
When user requests involve FastAPI, **must** load `references/conventions.md` to get complete convention list.
## When Reviewing Code
1. Load convention file
2. Check user code against each convention
3. For each violation: cite specific rule + provide fix suggestion
## When Writing Code
1. Load convention file
2. Strictly follow each convention
3. Add type annotations to all function signatures
4. Use Annotated style for dependency injection
## Example Output Format
```python
# ❌ Wrong example
@app.get("/users")
def get_users():
return db.query(User).all()
# ✅ Correct example
@app.get("/users", response_model=list[UserSchema])
async def get_users(
db: Annotated[AsyncSession, Depends(get_db)]
) -> list[UserSchema]:
result = await db.execute(select(User))
return result.scalars().all()
```
```
## references/conventions.md Template
```markdown
# FastAPI Team Conventions v1.0
## 1. Project Structure
- Use `src/` layout
- Split routes by feature module: `routes/users.py`, `routes/items.py`
- Schema definitions in `schemas/` directory
## 2. Async Conventions
- All I/O operations must use async/await
- Database sessions use AsyncSession
- Prohibit calling synchronous blocking methods in async functions
## 3. Error Handling
- Use HTTPException to throw standard status codes
- Custom exception handlers registered centrally in `exceptions.py`
- Error responses include: `detail`, `error_code`, `timestamp`
## 4. Dependency Injection
- Database connections, authentication, logging all use Depends()
- Dependency function naming: `get_xxx()`
- Use Annotated[Type, Depends(func)] style
## 5. Pydantic Models
- All requests/responses must be wrapped with Schema
- Prohibit returning ORM objects directly
- Use model_config = ConfigDict(from_attributes=True)
## 6. Testing Conventions
- Use pytest + httpx.TestClient
- At least one test per endpoint
- Mock external dependencies, don't rely on real database
```
## Activation Condition Design
Specify trigger keywords in `description`:
```yaml
description: >
FastAPI development best practices.
Activation words: fastapi, pydantic, REST API, endpoint, dependency injection,
routes, schema, Depends, HTTPException, async
```
## Pros & Cons
| Pros | Cons |
|-----|------|
| Load on-demand, saves tokens | Depends on keyword matching accuracy |
| Conventions maintained independently, easy to update | Multiple Tool Wrappers may conflict |
| Combinable (activate multiple simultaneously) | Needs clear trigger word design |
## Variants
### Variant A: Multi-Convention Switching
```markdown
Load corresponding conventions based on tech stack user mentions:
- FastAPI → `references/fastapi-conventions.md`
- Django → `references/django-conventions.md`
- Flask → `references/flask-conventions.md`
```
### Variant B: Team-Specific Conventions
```markdown
You are a [Company Name] backend development assistant.
**Must** prioritize internal conventions in `references/team-conventions.md`,
then reference general best practices.
```
---
## Checklist
- [ ] `description` contains clear trigger keywords
- [ ] `references/` directory exists with concrete content
- [ ] SKILL.md clearly explains when to load conventions
- [ ] Has correct/incorrect comparison examples
- [ ] Conventions can be updated independently without modifying SKILL.md