feat(core): integrate core modules, update UI and docs
Browse files- README.md +150 -6
- docs/COOL_MODE.md +0 -36
- docs/INSTALL.md +0 -82
- docs/INVESTOR_PITCH.md +0 -82
- docs/VIBECODER_GUIDE.md +0 -33
- server/api.py +202 -8
- smart_nic_prompt.txt +13 -0
- src/agentic/agents/architect.py +3 -3
- src/agentic/agents/designer.py +3 -1
- src/agentic/agents/testbench_designer.py +18 -19
- src/agentic/agents/verifier.py +29 -3
- src/agentic/cli.py +12 -9
- src/agentic/config.py +1 -1
- src/agentic/core/__init__.py +28 -0
- src/agentic/core/architect.py +424 -0
- src/agentic/core/deep_debugger.py +820 -0
- src/agentic/core/react_agent.py +454 -0
- src/agentic/core/self_reflect.py +522 -0
- src/agentic/core/waveform_expert.py +680 -0
- src/agentic/golden_lib/template_matcher.py +4 -4
- src/agentic/golden_lib/templates/spi_master.v +8 -3
- src/agentic/orchestrator.py +401 -210
- src/agentic/tools/vlsi_tools.py +652 -33
- tests/test_tier1_upgrade.py +0 -581
- training/README.md +0 -97
- training/agentic_sft_config.yaml +0 -74
- training/collect_training_data.py +0 -266
- training/generate_reasoning.py +0 -294
- training/verilog_rewards_enhanced.py +0 -290
- web/package-lock.json +1534 -68
- web/package.json +2 -0
- web/src/App.tsx +171 -62
- web/src/components/BuildMonitor.tsx +63 -11
- web/src/components/ChipSummary.tsx +81 -7
- web/src/index.css +1512 -23
- web/src/pages/Benchmarking.tsx +79 -29
- web/src/pages/Dashboard.tsx +100 -22
- web/src/pages/DesignStudio.tsx +179 -3
- web/src/pages/Documentation.tsx +459 -0
- web/src/pages/Fabrication.tsx +10 -10
README.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
| 4 |

|
| 5 |

|
| 6 |

|
|
|
|
| 7 |
|
| 8 |
-
AgentIC converts natural-language hardware intent into RTL, verification artifacts, and OpenLane physical implementation with autonomous repair loops.
|
| 9 |
|
| 10 |
-
This README reflects the **Tier-1 upgrade**: strict
|
| 11 |
|
| 12 |
## Why this version is different
|
| 13 |
|
|
@@ -18,6 +19,62 @@ AgentIC is now built to avoid two expensive failure modes:
|
|
| 18 |
|
| 19 |
Tier-1 addresses both.
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
## Tier-1 upgrade highlights
|
| 22 |
|
| 23 |
- **Fail-closed mode is first-class** (`--strict-gates` default).
|
|
@@ -48,6 +105,30 @@ Tier-1 addresses both.
|
|
| 48 |
- PR smoke checks,
|
| 49 |
- nightly full-flow path.
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
## Architecture (easy view)
|
| 52 |
|
| 53 |
```mermaid
|
|
@@ -89,6 +170,45 @@ flowchart TD
|
|
| 89 |
PIVOT -->|pivot cap exceeded| FAIL
|
| 90 |
```
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
## Autonomous repair model
|
| 93 |
|
| 94 |
AgentIC is not just an error printer. It has repair loops with decision logic.
|
|
@@ -115,11 +235,22 @@ AgentIC is not just an error printer. It has repair loops with decision logic.
|
|
| 115 |
| Startup | required tools + environment must resolve |
|
| 116 |
| RTL Fix | syntax + lint + semantic rigor must pass |
|
| 117 |
| Verification | TB contract + simulation must pass |
|
| 118 |
-
| Formal |
|
| 119 |
-
| Coverage |
|
| 120 |
| Regression | regression failures are blocking |
|
| 121 |
| Signoff | DRC/LVS/STA/power/IR/LEC all contribute to final pass/fail |
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
## PDK portability model
|
| 124 |
|
| 125 |
AgentIC uses an adapter-style OSS-PDK profile model.
|
|
@@ -278,9 +409,22 @@ AgentIC/
|
|
| 278 |
βββ src/agentic/
|
| 279 |
β βββ cli.py
|
| 280 |
β βββ config.py
|
| 281 |
-
β βββ orchestrator.py
|
| 282 |
β βββ agents/
|
| 283 |
-
β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
βββ tests/test_tier1_upgrade.py
|
| 285 |
βββ scripts/ci/
|
| 286 |
βββ .github/workflows/ci.yml
|
|
|
|
| 4 |

|
| 5 |

|
| 6 |

|
| 7 |
+

|
| 8 |
|
| 9 |
+
AgentIC converts natural-language hardware intent into RTL, verification artifacts, and OpenLane physical implementation with autonomous repair loops, multi-agent collaboration, and research-grade core modules.
|
| 10 |
|
| 11 |
+
This README reflects the **Tier-1 + Self-Healing vNext + Multi-Agent Architecture upgrade**: strict core gates, bounded loop control, collaborative agent crews, structured spec decomposition (SID), self-reflective hardening retry, tool-equipped agents, and Verilator-safe verification pipeline.
|
| 12 |
|
| 13 |
## Why this version is different
|
| 14 |
|
|
|
|
| 19 |
|
| 20 |
Tier-1 addresses both.
|
| 21 |
|
| 22 |
+
## Core Modules (Research-Grade Pipeline)
|
| 23 |
+
|
| 24 |
+
AgentIC includes five research-grade core modules in `src/agentic/core/` β all wired into the orchestrator pipeline:
|
| 25 |
+
|
| 26 |
+
| Module | Based On | Purpose | Integration Point |
|
| 27 |
+
|--------|----------|---------|-------------------|
|
| 28 |
+
| **ArchitectModule** | Spec2RTL-Agent | Structured spec decomposition β validated JSON contract (SID) | `do_spec()` β primary path |
|
| 29 |
+
| **SelfReflectPipeline** | Self-Reflection Retry | Autonomous retry with convergence tracking, failure fingerprinting, stagnation detection | `do_hardening()` β wraps OpenLane |
|
| 30 |
+
| **ReActAgent** | ReAct (Yao et al., 2023) | Structured ThoughtβActionβObservation reasoning framework | Available for all agent loops |
|
| 31 |
+
| **WaveformExpertModule** | VerilogCoder AST-tracing | VCD parsing + Pyverilog AST back-trace to find failing signal/line | Simulation failure diagnosis |
|
| 32 |
+
| **DeepDebuggerModule** | FVDebug balanced analysis | SymbiYosys + causal graphs + For-and-Against protocol | Formal verification debugging |
|
| 33 |
+
|
| 34 |
+
### ArchitectModule (Structured Spec Decomposition)
|
| 35 |
+
|
| 36 |
+
Before writing any Verilog, the ArchitectModule reads the input spec and produces a **Structured Information Dictionary (SID)** in JSON:
|
| 37 |
+
|
| 38 |
+
```json
|
| 39 |
+
{
|
| 40 |
+
"design_name": "uart_tx",
|
| 41 |
+
"chip_family": "UART",
|
| 42 |
+
"top_module": "uart_tx",
|
| 43 |
+
"sub_modules": [{
|
| 44 |
+
"name": "uart_tx",
|
| 45 |
+
"ports": [{"name": "clk", "direction": "input", "width": "1"}, ...],
|
| 46 |
+
"functional_logic": "Complete behavioral description...",
|
| 47 |
+
"fsm_states": [{"name": "IDLE", "transitions": [...]}]
|
| 48 |
+
}],
|
| 49 |
+
"verification_hints": ["Test baud rate accuracy at Β±2%"]
|
| 50 |
+
}
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
This JSON contract becomes the **single source of truth** for all downstream agents β eliminating ambiguity and hallucination.
|
| 54 |
+
|
| 55 |
+
### SelfReflectPipeline (Hardening Recovery)
|
| 56 |
+
|
| 57 |
+
When OpenLane hardening fails, the pipeline doesn't just give up:
|
| 58 |
+
|
| 59 |
+
1. **Categorizes** the failure (timing violation, routing congestion, DRC, etc.)
|
| 60 |
+
2. **Reflects** using LLM β structured root-cause analysis with convergence history
|
| 61 |
+
3. **Proposes** corrective actions (area expansion, constraint relaxation, RTL pipelining)
|
| 62 |
+
4. **Applies** fixes and retries (up to 3 times)
|
| 63 |
+
5. **Detects stagnation** β aborts early if metrics are diverging
|
| 64 |
+
|
| 65 |
+
## Multi-Agent Collaboration
|
| 66 |
+
|
| 67 |
+
All agents now have **tools** (syntax checker + file reader) and work in **collaborative crews**:
|
| 68 |
+
|
| 69 |
+
| Agent | Tools | Collaboration |
|
| 70 |
+
|-------|-------|---------------|
|
| 71 |
+
| RTL Designer | `syntax_check`, `read_file` | 2-agent Crew with RTL Reviewer |
|
| 72 |
+
| RTL Reviewer | `syntax_check`, `read_file` | Reviews designer output before committing |
|
| 73 |
+
| Testbench Designer | `syntax_check`, `read_file` | Verilator-safe methodology, self-verifies |
|
| 74 |
+
| Error Analyst | `syntax_check`, `read_file` | Diagnoses failures with file reading |
|
| 75 |
+
| Verification Engineer | `syntax_check`, `read_file` | SVA assertions, Verilator-compatible |
|
| 76 |
+
| Regression Architect | `syntax_check`, `read_file` | Creates corner-case test plans |
|
| 77 |
+
|
| 78 |
## Tier-1 upgrade highlights
|
| 79 |
|
| 80 |
- **Fail-closed mode is first-class** (`--strict-gates` default).
|
|
|
|
| 105 |
- PR smoke checks,
|
| 106 |
- nightly full-flow path.
|
| 107 |
|
| 108 |
+
## New in Self-Healing vNext (March 2026)
|
| 109 |
+
|
| 110 |
+
- **Multi-agent collaborative crews**: RTL generation uses a 2-agent Crew (Designer + Reviewer). Error analysis diagnosis feeds directly into the fixer agent's prompt.
|
| 111 |
+
- **All agents have tools**: Designer, Testbench, Verifier, Error Analyst, and Regression agents all have `syntax_check` and `read_file` tools β they can self-verify their output before returning it.
|
| 112 |
+
- **ArchitectModule integration**: `do_spec()` now uses the structured SID decomposer β produces a validated JSON contract with ports, FSM states, sub-modules, and verification hints.
|
| 113 |
+
- **SelfReflectPipeline integration**: `do_hardening()` wraps OpenLane with self-reflective retry β failure categorization, convergence tracking, and stagnation detection.
|
| 114 |
+
- **Verilator-safe verification pipeline**: TB prompts, agent backstories, and static gates are all aligned β no more contradictions between what the LLM is told to generate and what the compiler accepts.
|
| 115 |
+
- **Universal stage exception guard**: each state handler executes through a safe wrapper that retries essential stages and skips non-essential stages when needed.
|
| 116 |
+
- **Formal self-healing loop**:
|
| 117 |
+
- SVA preflight/solver failures trigger bounded SVA regeneration,
|
| 118 |
+
- persistent formal issues degrade gracefully to coverage instead of hard-stop.
|
| 119 |
+
- **Coverage anti-regression guard**:
|
| 120 |
+
- candidate TBs must pass compile gate,
|
| 121 |
+
- candidate coverage must not regress beyond guardrail,
|
| 122 |
+
- best TB snapshot is restored automatically.
|
| 123 |
+
- **Coverage thresholds now profile-driven**:
|
| 124 |
+
- branch gate uses profile threshold (not hardcoded 95%),
|
| 125 |
+
- toggle gate is skipped for Verilator-style backends where toggle metrics are unavailable.
|
| 126 |
+
- **Verification recovery hardening**:
|
| 127 |
+
- repeated simulation fingerprints trigger deterministic TB fallback,
|
| 128 |
+
- RTL/TB write failures are retried instead of immediate fail.
|
| 129 |
+
- **OpenLane config-path robustness**:
|
| 130 |
+
- host config paths are translated to Docker-mounted `/openlane/...` paths.
|
| 131 |
+
|
| 132 |
## Architecture (easy view)
|
| 133 |
|
| 134 |
```mermaid
|
|
|
|
| 170 |
PIVOT -->|pivot cap exceeded| FAIL
|
| 171 |
```
|
| 172 |
|
| 173 |
+
## Complete flow (current)
|
| 174 |
+
|
| 175 |
+
1. **INIT**
|
| 176 |
+
- Startup self-check validates toolchain, environment, and selected profile.
|
| 177 |
+
2. **SPEC**
|
| 178 |
+
- ArchitectModule decomposes spec into Structured Information Dictionary (SID/JSON).
|
| 179 |
+
- Validated JSON contract with ports, FSM states, sub-modules, verification hints.
|
| 180 |
+
- Fallback to Crew-based MAS generation if SID decomposition fails.
|
| 181 |
+
3. **RTL_GEN**
|
| 182 |
+
- Golden-template matching first, LLM RTL generation fallback.
|
| 183 |
+
4. **RTL_FIX**
|
| 184 |
+
- Syntax/lint/semantic checks with bounded repair loop and strategy pivoting.
|
| 185 |
+
5. **VERIFICATION**
|
| 186 |
+
- TB static + compile gate, simulation run, multi-class failure diagnosis (TB/RTL/ports/timing/architecture), deterministic + LLM-assisted recovery.
|
| 187 |
+
6. **FORMAL_VERIFY**
|
| 188 |
+
- SVA generation β Yosys conversion β preflight validation β SymbiYosys run.
|
| 189 |
+
- On failures: bounded SVA regeneration before graceful degrade.
|
| 190 |
+
7. **COVERAGE_CHECK**
|
| 191 |
+
- Adapter-based coverage, profile thresholds, anti-regression TB improvement loop.
|
| 192 |
+
- On repeated non-closure: restores best TB and continues.
|
| 193 |
+
8. **REGRESSION** (optional by mode)
|
| 194 |
+
- Directed scenario generation and execution.
|
| 195 |
+
9. **SDC_GEN**
|
| 196 |
+
- Generates timing constraints for synthesis/STA.
|
| 197 |
+
10. **FLOORPLAN**
|
| 198 |
+
- LLM + heuristic floorplan estimation and TCL artifact generation.
|
| 199 |
+
11. **HARDENING**
|
| 200 |
+
- OpenLane run wrapped with SelfReflectPipeline β auto-retry with root-cause analysis, convergence tracking, and stagnation detection.
|
| 201 |
+
12. **CONVERGENCE_REVIEW**
|
| 202 |
+
- Assesses WNS/TNS/congestion trend and triggers pivots when needed.
|
| 203 |
+
13. **ECO_PATCH** (if signoff/convergence requires)
|
| 204 |
+
- Applies focused ECO corrections and re-runs implementation path.
|
| 205 |
+
14. **SIGNOFF**
|
| 206 |
+
- DRC/LVS/STA/Power/IR + LEC aggregation to final pass/fail.
|
| 207 |
+
15. **SUCCESS / FAIL**
|
| 208 |
+
- Emits final artifact map and benchmark metrics snapshot.
|
| 209 |
+
|
| 210 |
+
Across all stages, a **safe dispatcher** guards unexpected exceptions with bounded retry/skip policy.
|
| 211 |
+
|
| 212 |
## Autonomous repair model
|
| 213 |
|
| 214 |
AgentIC is not just an error printer. It has repair loops with decision logic.
|
|
|
|
| 235 |
| Startup | required tools + environment must resolve |
|
| 236 |
| RTL Fix | syntax + lint + semantic rigor must pass |
|
| 237 |
| Verification | TB contract + simulation must pass |
|
| 238 |
+
| Formal | bounded self-heal first; persistent failures can degrade to coverage path |
|
| 239 |
+
| Coverage | profile-driven closure loop with anti-regression; best-effort proceed after bounded attempts |
|
| 240 |
| Regression | regression failures are blocking |
|
| 241 |
| Signoff | DRC/LVS/STA/power/IR/LEC all contribute to final pass/fail |
|
| 242 |
|
| 243 |
+
## Before vs now (upgrade summary)
|
| 244 |
+
|
| 245 |
+
| Capability | Before | Now |
|
| 246 |
+
|---|---|---|
|
| 247 |
+
| Stage crash handling | Global try/except only | Per-stage guarded execution with retry/skip policy |
|
| 248 |
+
| Formal failures | Could hard-stop in strict mode | Regenerates SVA and degrades gracefully when exhausted |
|
| 249 |
+
| Coverage improvement | Could accept worse TBs | Compile-gated, anti-regression, best-TB restore |
|
| 250 |
+
| Coverage branch gate | Hardcoded high branch target | Uses profile threshold (`balanced/aggressive/relaxed`) |
|
| 251 |
+
| Verilator toggle gate | Could fail on missing toggle realism | Toggle gate bypass where backend lacks toggle fidelity |
|
| 252 |
+
| OpenLane config pathing | Host-path mismatch risk in Docker | Host path remapped to Docker `/openlane` namespace |
|
| 253 |
+
|
| 254 |
## PDK portability model
|
| 255 |
|
| 256 |
AgentIC uses an adapter-style OSS-PDK profile model.
|
|
|
|
| 409 |
βββ src/agentic/
|
| 410 |
β βββ cli.py
|
| 411 |
β βββ config.py
|
| 412 |
+
β βββ orchestrator.py # 3400+ line state machine (16 states)
|
| 413 |
β βββ agents/
|
| 414 |
+
β β βββ designer.py # RTL Designer agent (with tools)
|
| 415 |
+
β β βββ testbench_designer.py # TB Designer agent (Verilator-safe)
|
| 416 |
+
β β βββ verifier.py # Error Analyst + Verification + Regression
|
| 417 |
+
β β βββ doc_agent.py
|
| 418 |
+
β β βββ sdc_agent.py
|
| 419 |
+
β βββ core/ # Research-grade pipeline modules
|
| 420 |
+
β β βββ architect.py # Spec2RTL SID decomposer
|
| 421 |
+
β β βββ react_agent.py # ReAct reasoning framework
|
| 422 |
+
β β βββ self_reflect.py # Self-reflection retry pipeline
|
| 423 |
+
β β βββ deep_debugger.py # FVDebug balanced analysis
|
| 424 |
+
β β βββ waveform_expert.py # VCD + AST waveform tracing
|
| 425 |
+
β βββ tools/vlsi_tools.py # 3400+ lines of EDA tool wrappers
|
| 426 |
+
βββ server/ # FastAPI backend (SSE streaming)
|
| 427 |
+
βββ web/ # React 19 + Vite 7 frontend
|
| 428 |
βββ tests/test_tier1_upgrade.py
|
| 429 |
βββ scripts/ci/
|
| 430 |
βββ .github/workflows/ci.yml
|
docs/COOL_MODE.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
| 1 |
-
# π₯ Cooling Down: How to Run Locally Safely
|
| 2 |
-
|
| 3 |
-
**No, your computer will not "blast".**
|
| 4 |
-
Modern CPUs and GPUs have safety sensors. If they get too hot (usually 100Β°C+), they will automatically slow down ("thermal throttle") or shut off the computer to prevent damage.
|
| 5 |
-
|
| 6 |
-
However, running a 5GB model like `deepseek-r1` pins your processor to 100% usage, which generates max heat.
|
| 7 |
-
|
| 8 |
-
## Solution: Use a "Lighter" Brain
|
| 9 |
-
Since you need to run strictly locally ($0 cost), the best way to reduce heat is to use a **smaller model**.
|
| 10 |
-
|
| 11 |
-
A 1.5B or 3B model requires **much less math** per second than a 7B/8B model. It will run faster and generate less heat.
|
| 12 |
-
|
| 13 |
-
### Recommended Models for Coding (Low Heat)
|
| 14 |
-
1. **DeepSeek Coder 1.3B** (Tiny, very fast, decent at Verilog)
|
| 15 |
-
2. **Qwen 2.5 Coder 3B** (Excellent balance of smarts and speed)
|
| 16 |
-
|
| 17 |
-
### How to Switch
|
| 18 |
-
1. **Open Terminal and Pull a Tiny Model:**
|
| 19 |
-
```bash
|
| 20 |
-
# Try the 1.3 Billion parameter version (approx 700MB - 1GB)
|
| 21 |
-
ollama pull deepseek-coder:1.3b
|
| 22 |
-
|
| 23 |
-
# OR try Qwen 3B (approx 2GB) - Better quality
|
| 24 |
-
ollama pull qwen2.5-coder:3b
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
2. **Update AgentIC to use it:**
|
| 28 |
-
Open `src/agentic/config.py` and change `LLM_MODEL`:
|
| 29 |
-
```python
|
| 30 |
-
# LLM_MODEL = "ollama/deepseek-r1" <-- Comment this out (Heat: High)
|
| 31 |
-
LLM_MODEL = "ollama/deepseek-coder:1.3b" # <-- Use this (Heat: Low)
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
3. **Physical Tips:**
|
| 35 |
-
* Prop up the back of your laptop for airflow.
|
| 36 |
-
* Use a cooling pad if you have one.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/INSTALL.md
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
# Installation & Portability Guide
|
| 2 |
-
|
| 3 |
-
Use this guide to set up **AgentIC** on a new machine.
|
| 4 |
-
|
| 5 |
-
## 1. System Requirements
|
| 6 |
-
* **Operating System**: Linux (Ubuntu 20.04/22.04 LTS) or Windows with **WSL2** (Ubuntu).
|
| 7 |
-
* *Note: Pure Windows is not supported due to Electronic Design Automation (EDA) tool dependencies.*
|
| 8 |
-
* **Memory**: 8GB RAM minimum (16GB recommended for Physical Design).
|
| 9 |
-
* **Disk Space**: ~10GB (mostly for Docker images and PDKs).
|
| 10 |
-
|
| 11 |
-
## 2. Core Dependencies
|
| 12 |
-
Install the required system tools before setting up the Python environment.
|
| 13 |
-
|
| 14 |
-
### Ubuntu / Debian / WSL2:
|
| 15 |
-
```bash
|
| 16 |
-
sudo apt update
|
| 17 |
-
sudo apt install -y git make python3 python3-venv python3-pip
|
| 18 |
-
sudo apt install -y iverilog build-essential
|
| 19 |
-
```
|
| 20 |
-
|
| 21 |
-
### Docker (Critical for OpenLane)
|
| 22 |
-
AgentIC uses OpenLane (running in Docker) to turn Verilog into GDSII layouts.
|
| 23 |
-
1. **Install Docker Desktop** (Windows/Mac) or **Docker Engine** (Linux).
|
| 24 |
-
2. **Verify installation**:
|
| 25 |
-
```bash
|
| 26 |
-
docker run hello-world
|
| 27 |
-
```
|
| 28 |
-
3. **Linux/WSL2 users**: Ensure your user is in the docker group so you don't need `sudo`:
|
| 29 |
-
```bash
|
| 30 |
-
sudo usermod -aG docker $USER
|
| 31 |
-
# Log out and log back in for this to take effect
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
## 3. Python Environment Setup
|
| 35 |
-
|
| 36 |
-
1. **Clone the Repository**:
|
| 37 |
-
```bash
|
| 38 |
-
git clone https://github.com/Vickyrrrrrr/AgentIC.git
|
| 39 |
-
cd AgentIC
|
| 40 |
-
```
|
| 41 |
-
|
| 42 |
-
2. **Create and Activate Virtual Environment**:
|
| 43 |
-
```bash
|
| 44 |
-
python3 -m venv agentic_env
|
| 45 |
-
source agentic_env/bin/activate
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
3. **Install Python Dependencies**:
|
| 49 |
-
```bash
|
| 50 |
-
pip install -r requirements.txt
|
| 51 |
-
```
|
| 52 |
-
|
| 53 |
-
4. **Install GDSTK (Layout Viewer)**:
|
| 54 |
-
If `pip install gdstk` fails, you may need cmake:
|
| 55 |
-
```bash
|
| 56 |
-
sudo apt install cmake
|
| 57 |
-
pip install gdstk
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
## 4. Configuration (.env)
|
| 61 |
-
|
| 62 |
-
You need to provide your LLM API keys.
|
| 63 |
-
1. Create a file named `.env` in the root `AgentIC` directory.
|
| 64 |
-
2. Add your keys (example for Groq):
|
| 65 |
-
```ini
|
| 66 |
-
# .env file
|
| 67 |
-
OPENAI_API_BASE=https://api.groq.com/openai/v1
|
| 68 |
-
OPENAI_API_KEY=gsk_your_groq_api_key_here
|
| 69 |
-
OPENAI_MODEL_NAME=llama-3.3-70b-versatile
|
| 70 |
-
```
|
| 71 |
-
|
| 72 |
-
## 5. Verification
|
| 73 |
-
To ensure everything is working:
|
| 74 |
-
|
| 75 |
-
1. **Test the Agent Logic**:
|
| 76 |
-
```bash
|
| 77 |
-
python3 main.py build --name test_counter --desc "A simple 4-bit up counter"
|
| 78 |
-
```
|
| 79 |
-
2. **Test the Web UI**:
|
| 80 |
-
```bash
|
| 81 |
-
streamlit run app.py
|
| 82 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/INVESTOR_PITCH.md
DELETED
|
@@ -1,82 +0,0 @@
|
|
| 1 |
-
# AgentIC: The AI-Driven Text-to-Silicon Disruption
|
| 2 |
-
|
| 3 |
-
## Executive Summary
|
| 4 |
-
AgentIC represents a paradigm shift in semiconductor design. By orchestrating a crew of specialized AI agents through an autonomous, self-healing pipeline, it transforms natural language specifications into verified, manufacturable chip layouts (GDSII). While traditional Electronic Design Automation (EDA) giants like Cadence and Synopsys dominate the bleeding-edge (3nm/5nm) high-performance node markets, AgentIC drastically democratizes and accelerates the production of chips in mature, dominant nodes (130nm, 65nm, 28nm) serving edge AI, IoT, automotive, and defense sectors.
|
| 5 |
-
|
| 6 |
-
---
|
| 7 |
-
|
| 8 |
-
## 1. The Realities of the EDA Industry: AgentIC vs. Giants (Cadence/Synopsys)
|
| 9 |
-
|
| 10 |
-
Is AgentIC on the exact same level as Synopsys or Cadence? **No, and it doesn't need to be to capture immense market value.**
|
| 11 |
-
|
| 12 |
-
Cadence and Synopsys provide ultra-precise tools for sub-5nm nodes. Their environments cost millions of dollars, demand PhD-level operators, and take months/years to yield a tapeout. Their focus is squeezing absolute maximum Performance-Power-Area (PPA) scaling for mega-chips (e.g., Nvidia H100s, Apple M3s).
|
| 13 |
-
|
| 14 |
-
**AgentIC's disruption lies in democratizing custom Silicon for the remaining 80% of the market** (IoT, sensors, specialized defense processors, analog mixed-signal processing wrappers) built on economical, mature tech nodes (like SkyWater 130nm).
|
| 15 |
-
|
| 16 |
-
### The Cost and Time Chasm
|
| 17 |
-
|
| 18 |
-
| Metric | Traditional EDA (Cadence/Synopsys) | AgentIC (Autonomous) |
|
| 19 |
-
|--------|-----------------------------------|----------------------|
|
| 20 |
-
| **Operator Requirement** | Expert Verification/Physical Design Team | Single prompt engineer/system architect |
|
| 21 |
-
| **Typical Target Node** | 14nm to 2nm (Bleeding-edge) | 130nm to 28nm (Mature/Economical) |
|
| 22 |
-
| **PPA Optimization** | Pushed to theoretical physical limits | Sub-optimal, but production-ready |
|
| 23 |
-
| **Silicon Tapeout Speed** | Months to Years | Minutes to Hours |
|
| 24 |
-
| **Annual Licensing Cost** | $1M - $10M+ per site/team | $0 (Open-Source Core) + Token API Cost |
|
| 25 |
-
|
| 26 |
-
---
|
| 27 |
-
|
| 28 |
-
## 2. Technical Benchmarks: The Speed & Accuracy Revolution
|
| 29 |
-
|
| 30 |
-
AgentIC eliminates the "Human-in-the-Loop" for redundant syntax and verification bounding. By integrating formal verification (SymbiYosys) directly with the AI, the orchestrator proves properties rather than relying on flawed human-written heuristics.
|
| 31 |
-
|
| 32 |
-
### Syntax & Logical Accuracy
|
| 33 |
-
|
| 34 |
-
```mermaid
|
| 35 |
-
pie title "Logic Bug Escape Rate"
|
| 36 |
-
"Legacy Flow (Manual UVM)" : 10
|
| 37 |
-
"AgentIC (Formal Verif)" : 1
|
| 38 |
-
```
|
| 39 |
-
|
| 40 |
-
* **Syntax Error Rate (Pre-Lint):** Legacy human iteration suffers ~15-20% syntax failure out the gate. AgentIC's LLM pre-trained models drop this to **< 5%**.
|
| 41 |
-
* **Linting & DRC Compliance:** Legacy requires iterative manual ticket resolution. AgentIC enforces a **100% auto-resolved** loop.
|
| 42 |
-
* **Logic Bug Escape:** Formal verification shrinks escaped logic flaws by a factor of 10.
|
| 43 |
-
|
| 44 |
-
### Iteration Speed (Idea to GDSII Layout)
|
| 45 |
-
|
| 46 |
-
```mermaid
|
| 47 |
-
gantt
|
| 48 |
-
title Time to Tapeout: 32-bit APB PWM Controller
|
| 49 |
-
dateFormat YYYY-MM-DD
|
| 50 |
-
section Traditional Big-Firm
|
| 51 |
-
RTL Design :active, 2026-01-01, 14d
|
| 52 |
-
UVM Verification :2026-01-15, 14d
|
| 53 |
-
Physical Design :2026-01-29, 7d
|
| 54 |
-
section AgentIC (Auto)
|
| 55 |
-
Prompt to GDSII :crit, 2026-01-01, 1d
|
| 56 |
-
```
|
| 57 |
-
|
| 58 |
-
In a recent case study tracking an `apb_pwm_controller` tapeout over the Sky130 nom process:
|
| 59 |
-
* **Legacy Estimation:** 3 to 5 weeks.
|
| 60 |
-
* **AgentIC Actual Run:** **~15 Minutes** (yielding a verified ~5.9 MB GDSII layout with 0 LVS, 0 Setup/Hold, and 0 DRC violations).
|
| 61 |
-
|
| 62 |
-
---
|
| 63 |
-
|
| 64 |
-
## 3. The Criticisms (Honest Evaluation)
|
| 65 |
-
|
| 66 |
-
For an investor, it is crucial to understand AgentIC's current ceiling:
|
| 67 |
-
1. **PPA Efficiency Penalty:** Because AgentIC relies on AI inference to generate RTL and utilizes the open-source OpenLane physical synthesis flow, the resulting dies are typically **10% to 30% larger and consume more power** than a human-optimized, Synopsys-synthesized equivalent.
|
| 68 |
-
2. **Advanced Node Incompatibility:** AgentIC currently wraps tools compatible with open PDKs (130nm, 45nm, etc.). Proprietary PDKs for 3nm TSMC gates cannot trivially be piped directly into this open pipeline without NDA breaches and major tool overhauls.
|
| 69 |
-
3. **Complex State Explosions:** Large Systems-on-Chip (SoCs) with billions of gates confound current LLM contexts. AgentIC excels at IP blocks, accelerators, peripherals, and mid-tier processors (RISC-V cores, NPU grids).
|
| 70 |
-
|
| 71 |
-
---
|
| 72 |
-
|
| 73 |
-
## 4. The Market Opportunity & Go-To-Market
|
| 74 |
-
|
| 75 |
-
We aren't competing with Cadence for Qualcomm's next smartphone chip. We are competing against the *barrier to entry* for creating silicon.
|
| 76 |
-
|
| 77 |
-
**Target Customers:**
|
| 78 |
-
* **Defense & Aerospace:** Custom, radiation-hardened control hardware designed offline iteratively in hours without risking IP leaks via third-party design houses.
|
| 79 |
-
* **Research Institutions & Startups:** Validating silicon concepts without needing a $2M seed round just to buy a Synopsys license block.
|
| 80 |
-
* **Automotive/IoT:** Custom sensor interfaces built rapidly on mature 130nm/65nm nodes where extreme density isn't required but time-to-market is.
|
| 81 |
-
|
| 82 |
-
By maintaining AgentIC as a proprietary wrapper around massive, distributed computing inferences (Qwen Cloud / VeriReason), we can deploy this as a **Silicon-as-a-Service (SaaS)** platform. Companies submit a natural language prompt, and hours later receive a verified, DRC-clean blueprint ready to send to a foundry like SkyWater or GlobalFoundries.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/VIBECODER_GUIDE.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
# VibeCoder Guide
|
| 2 |
-
|
| 3 |
-
## Switch Simulation Backend from iverilog to Verilator
|
| 4 |
-
|
| 5 |
-
### Problem
|
| 6 |
-
`iverilog` cannot handle SystemVerilog. The LLM fix loop wastes retries downgrading valid SV to Verilog-2001.
|
| 7 |
-
|
| 8 |
-
### Solution: Deterministic Tool Selection
|
| 9 |
-
|
| 10 |
-
| Stage | Tool | Why |
|
| 11 |
-
|-------|------|-----|
|
| 12 |
-
| Syntax Check (RTL) | **Verilator** | Full SV support |
|
| 13 |
-
| Syntax Check (TB) | **Verilator** | Full SV support |
|
| 14 |
-
| RTL Simulation | **Verilator** | Compiles RTL+TB together |
|
| 15 |
-
| GLS Simulation | **iverilog** | PDK models use `#1` delays that Verilator rejects |
|
| 16 |
-
|
| 17 |
-
> [!IMPORTANT]
|
| 18 |
-
> GLS **must** keep iverilog. PDK cell models (sky130) use `specify` blocks and `#delay` syntax which Verilator does not support. This is not auto-detectable per chip β it's a fundamental tool limitation. The split is deterministic and permanent.
|
| 19 |
-
|
| 20 |
-
### Changes implemented
|
| 21 |
-
|
| 22 |
-
#### [vlsi_tools.py](file:///home/vickynishad/AgentIC/src/agentic/tools/vlsi_tools.py)
|
| 23 |
-
|
| 24 |
-
1. **`run_syntax_check()`:** Replaced `iverilog` with `verilator --lint-only --timing`
|
| 25 |
-
2. **`run_simulation()`:** Replaced `iverilog`+`vvp` with `verilator --binary --timing` + direct execution
|
| 26 |
-
3. **`run_simulation_with_coverage()`:** Same as above + `--coverage`
|
| 27 |
-
4. **`run_gls_simulation()`:** Kept `iverilog` unchanged
|
| 28 |
-
5. **Auto-fix regexes:** Removed SV-to-Verilog downgrade hacks
|
| 29 |
-
|
| 30 |
-
#### [orchestrator.py](file:///home/vickynishad/AgentIC/src/agentic/orchestrator.py)
|
| 31 |
-
|
| 32 |
-
- Removed `_try_autonomous_sv_fix()` method (no longer needed)
|
| 33 |
-
- Removed SV compatibility fallback logic
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server/api.py
CHANGED
|
@@ -42,11 +42,31 @@ TRAINING_JSONL = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "
|
|
| 42 |
BUILD_STATES_ORDER = [
|
| 43 |
"INIT", "SPEC", "RTL_GEN", "RTL_FIX", "VERIFICATION",
|
| 44 |
"FORMAL_VERIFY", "COVERAGE_CHECK", "REGRESSION",
|
|
|
|
| 45 |
"FLOORPLAN", "HARDENING", "CONVERGENCE_REVIEW",
|
| 46 |
"ECO_PATCH", "SIGNOFF", "SUCCESS",
|
| 47 |
]
|
| 48 |
TOTAL_STEPS = len(BUILD_STATES_ORDER)
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
def _get_llm():
|
| 52 |
"""Mirrors CLI's get_llm() β tries cloud first, falls back to local.
|
|
@@ -113,10 +133,58 @@ class BuildRequest(BaseModel):
|
|
| 113 |
description: str
|
| 114 |
skip_openlane: bool = False
|
| 115 |
full_signoff: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
|
| 118 |
# βββ Build Runner ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 119 |
-
def _run_agentic_build(job_id: str,
|
| 120 |
"""Runs the full AgentIC build in a background thread, emitting events."""
|
| 121 |
try:
|
| 122 |
from agentic.orchestrator import BuildOrchestrator
|
|
@@ -137,11 +205,25 @@ def _run_agentic_build(job_id: str, design_name: str, description: str, skip_ope
|
|
| 137 |
_emit_event(job_id, "checkpoint", "INIT", f"π€ AgentIC Compute Engine selected: {llm_name}", step=1)
|
| 138 |
|
| 139 |
orchestrator = BuildOrchestrator(
|
| 140 |
-
name=design_name,
|
| 141 |
-
desc=description,
|
| 142 |
llm=llm,
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
event_sink=event_sink,
|
| 146 |
)
|
| 147 |
orchestrator.run()
|
|
@@ -154,7 +236,7 @@ def _run_agentic_build(job_id: str, design_name: str, description: str, skip_ope
|
|
| 154 |
|
| 155 |
# Gather result
|
| 156 |
success = orchestrator.state.name == "SUCCESS"
|
| 157 |
-
result = _build_result_summary(orchestrator, design_name, success)
|
| 158 |
JOB_STORE[job_id]["result"] = result
|
| 159 |
JOB_STORE[job_id]["status"] = "done" if success else "failed"
|
| 160 |
|
|
@@ -163,7 +245,7 @@ def _run_agentic_build(job_id: str, design_name: str, description: str, skip_ope
|
|
| 163 |
_emit_event(job_id, final_type, orchestrator.state.name, final_msg, step=TOTAL_STEPS)
|
| 164 |
|
| 165 |
# ββ Auto-export to training JSONL ββββββββββββββββββββββββββ
|
| 166 |
-
_export_training_record(job_id, design_name, description, result, orchestrator)
|
| 167 |
|
| 168 |
except Exception as e:
|
| 169 |
import traceback
|
|
@@ -178,6 +260,16 @@ def _build_result_summary(orchestrator, design_name: str, success: bool) -> dict
|
|
| 178 |
artifacts = orchestrator.artifacts or {}
|
| 179 |
history = orchestrator.build_history or []
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
summary = {
|
| 182 |
"success": success,
|
| 183 |
"design_name": design_name,
|
|
@@ -192,6 +284,7 @@ def _build_result_summary(orchestrator, design_name: str, success: bool) -> dict
|
|
| 192 |
"congestion": s.congestion, "area_um2": s.area_um2, "power_w": s.power_w}
|
| 193 |
for s in (orchestrator.convergence_history or [])
|
| 194 |
],
|
|
|
|
| 195 |
"total_steps": len(history),
|
| 196 |
"strategy": orchestrator.strategy.value if orchestrator.strategy else "",
|
| 197 |
"build_time_s": int(time.time()) - (history[0].timestamp if history else int(time.time())),
|
|
@@ -276,6 +369,105 @@ def read_root():
|
|
| 276 |
return {"message": "AgentIC API is online", "version": "3.0.0"}
|
| 277 |
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
@app.post("/build")
|
| 280 |
def trigger_build(req: BuildRequest):
|
| 281 |
"""Start a new chip build. Returns job_id immediately."""
|
|
@@ -301,9 +493,11 @@ def trigger_build(req: BuildRequest):
|
|
| 301 |
"created_at": int(time.time()),
|
| 302 |
}
|
| 303 |
|
|
|
|
|
|
|
| 304 |
thread = threading.Thread(
|
| 305 |
target=_run_agentic_build,
|
| 306 |
-
args=(job_id,
|
| 307 |
daemon=True,
|
| 308 |
)
|
| 309 |
thread.start()
|
|
|
|
| 42 |
BUILD_STATES_ORDER = [
|
| 43 |
"INIT", "SPEC", "RTL_GEN", "RTL_FIX", "VERIFICATION",
|
| 44 |
"FORMAL_VERIFY", "COVERAGE_CHECK", "REGRESSION",
|
| 45 |
+
"SDC_GEN",
|
| 46 |
"FLOORPLAN", "HARDENING", "CONVERGENCE_REVIEW",
|
| 47 |
"ECO_PATCH", "SIGNOFF", "SUCCESS",
|
| 48 |
]
|
| 49 |
TOTAL_STEPS = len(BUILD_STATES_ORDER)
|
| 50 |
|
| 51 |
+
STAGE_META: Dict[str, Dict[str, str]] = {
|
| 52 |
+
"INIT": {"label": "Initializing Workspace", "icon": "π§"},
|
| 53 |
+
"SPEC": {"label": "Architectural Planning", "icon": "π"},
|
| 54 |
+
"RTL_GEN": {"label": "RTL Generation", "icon": "π»"},
|
| 55 |
+
"RTL_FIX": {"label": "RTL Syntax Fixing", "icon": "π¨"},
|
| 56 |
+
"VERIFICATION": {"label": "Verification & Testbench", "icon": "π§ͺ"},
|
| 57 |
+
"FORMAL_VERIFY": {"label": "Formal Verification", "icon": "π"},
|
| 58 |
+
"COVERAGE_CHECK": {"label": "Coverage Analysis", "icon": "π"},
|
| 59 |
+
"REGRESSION": {"label": "Regression Testing", "icon": "π"},
|
| 60 |
+
"SDC_GEN": {"label": "SDC Generation", "icon": "π"},
|
| 61 |
+
"FLOORPLAN": {"label": "Floorplanning", "icon": "πΊοΈ"},
|
| 62 |
+
"HARDENING": {"label": "GDSII Hardening", "icon": "ποΈ"},
|
| 63 |
+
"CONVERGENCE_REVIEW": {"label": "Convergence Review", "icon": "π―"},
|
| 64 |
+
"ECO_PATCH": {"label": "ECO Patch", "icon": "π©Ή"},
|
| 65 |
+
"SIGNOFF": {"label": "DRC/LVS Signoff", "icon": "β
"},
|
| 66 |
+
"SUCCESS": {"label": "Build Complete", "icon": "π"},
|
| 67 |
+
"FAIL": {"label": "Build Failed", "icon": "β"},
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
|
| 71 |
def _get_llm():
|
| 72 |
"""Mirrors CLI's get_llm() β tries cloud first, falls back to local.
|
|
|
|
| 133 |
description: str
|
| 134 |
skip_openlane: bool = False
|
| 135 |
full_signoff: bool = False
|
| 136 |
+
max_retries: int = 5
|
| 137 |
+
show_thinking: bool = False
|
| 138 |
+
min_coverage: float = 80.0
|
| 139 |
+
strict_gates: bool = True
|
| 140 |
+
pdk_profile: str = "sky130"
|
| 141 |
+
max_pivots: int = 2
|
| 142 |
+
congestion_threshold: float = 10.0
|
| 143 |
+
hierarchical: str = "auto"
|
| 144 |
+
tb_gate_mode: str = "strict"
|
| 145 |
+
tb_max_retries: int = 3
|
| 146 |
+
tb_fallback_template: str = "uvm_lite"
|
| 147 |
+
coverage_backend: str = "auto" # From SIM_BACKEND_DEFAULT
|
| 148 |
+
coverage_fallback_policy: str = "fail_closed" # From COVERAGE_FALLBACK_POLICY_DEFAULT
|
| 149 |
+
coverage_profile: str = "balanced" # From COVERAGE_PROFILE_DEFAULT
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _repo_root() -> str:
|
| 153 |
+
return os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _docs_index() -> Dict[str, Dict[str, str]]:
|
| 157 |
+
root = _repo_root()
|
| 158 |
+
return {
|
| 159 |
+
"readme": {
|
| 160 |
+
"title": "README",
|
| 161 |
+
"section": "Product",
|
| 162 |
+
"path": os.path.join(root, "README.md"),
|
| 163 |
+
"summary": "Full platform overview, flow, quality gates, and upgrade details.",
|
| 164 |
+
},
|
| 165 |
+
"web_guide": {
|
| 166 |
+
"title": "Web App Guide",
|
| 167 |
+
"section": "Web",
|
| 168 |
+
"path": os.path.join(root, "WEB_APP_GUIDE.md"),
|
| 169 |
+
"summary": "Web app architecture and usage guide.",
|
| 170 |
+
},
|
| 171 |
+
"install": {
|
| 172 |
+
"title": "Installation",
|
| 173 |
+
"section": "Setup",
|
| 174 |
+
"path": os.path.join(root, "docs", "INSTALL.md"),
|
| 175 |
+
"summary": "Installation and environment setup steps.",
|
| 176 |
+
},
|
| 177 |
+
"user_guide": {
|
| 178 |
+
"title": "User Guide",
|
| 179 |
+
"section": "Usage",
|
| 180 |
+
"path": os.path.join(root, "docs", "USER_GUIDE.md"),
|
| 181 |
+
"summary": "Operator guide for build flows and outputs.",
|
| 182 |
+
},
|
| 183 |
+
}
|
| 184 |
|
| 185 |
|
| 186 |
# βββ Build Runner ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 187 |
+
def _run_agentic_build(job_id: str, req: BuildRequest):
|
| 188 |
"""Runs the full AgentIC build in a background thread, emitting events."""
|
| 189 |
try:
|
| 190 |
from agentic.orchestrator import BuildOrchestrator
|
|
|
|
| 205 |
_emit_event(job_id, "checkpoint", "INIT", f"π€ AgentIC Compute Engine selected: {llm_name}", step=1)
|
| 206 |
|
| 207 |
orchestrator = BuildOrchestrator(
|
| 208 |
+
name=req.design_name,
|
| 209 |
+
desc=req.description,
|
| 210 |
llm=llm,
|
| 211 |
+
max_retries=req.max_retries,
|
| 212 |
+
verbose=req.show_thinking,
|
| 213 |
+
skip_openlane=req.skip_openlane,
|
| 214 |
+
full_signoff=req.full_signoff,
|
| 215 |
+
min_coverage=req.min_coverage,
|
| 216 |
+
strict_gates=req.strict_gates,
|
| 217 |
+
pdk_profile=req.pdk_profile,
|
| 218 |
+
max_pivots=req.max_pivots,
|
| 219 |
+
congestion_threshold=req.congestion_threshold,
|
| 220 |
+
hierarchical_mode=req.hierarchical,
|
| 221 |
+
tb_gate_mode=req.tb_gate_mode,
|
| 222 |
+
tb_max_retries=req.tb_max_retries,
|
| 223 |
+
tb_fallback_template=req.tb_fallback_template,
|
| 224 |
+
coverage_backend=req.coverage_backend,
|
| 225 |
+
coverage_fallback_policy=req.coverage_fallback_policy,
|
| 226 |
+
coverage_profile=req.coverage_profile,
|
| 227 |
event_sink=event_sink,
|
| 228 |
)
|
| 229 |
orchestrator.run()
|
|
|
|
| 236 |
|
| 237 |
# Gather result
|
| 238 |
success = orchestrator.state.name == "SUCCESS"
|
| 239 |
+
result = _build_result_summary(orchestrator, req.design_name, success)
|
| 240 |
JOB_STORE[job_id]["result"] = result
|
| 241 |
JOB_STORE[job_id]["status"] = "done" if success else "failed"
|
| 242 |
|
|
|
|
| 245 |
_emit_event(job_id, final_type, orchestrator.state.name, final_msg, step=TOTAL_STEPS)
|
| 246 |
|
| 247 |
# ββ Auto-export to training JSONL ββββββββββββββββββββββββββ
|
| 248 |
+
_export_training_record(job_id, req.design_name, req.description, result, orchestrator)
|
| 249 |
|
| 250 |
except Exception as e:
|
| 251 |
import traceback
|
|
|
|
| 260 |
artifacts = orchestrator.artifacts or {}
|
| 261 |
history = orchestrator.build_history or []
|
| 262 |
|
| 263 |
+
# Self-healing telemetry (derived from build history + artifacts)
|
| 264 |
+
lower_msgs = [h.message.lower() for h in history]
|
| 265 |
+
self_heal_stats = {
|
| 266 |
+
"stage_exception_count": sum("stage " in m and "exception" in m for m in lower_msgs),
|
| 267 |
+
"formal_regen_count": int(artifacts.get("formal_regen_count", 0) or 0),
|
| 268 |
+
"coverage_best_restore_count": sum("restoring best testbench" in m for m in lower_msgs),
|
| 269 |
+
"coverage_regression_reject_count": sum("tb regressed coverage" in m for m in lower_msgs),
|
| 270 |
+
"deterministic_tb_fallback_count": sum("deterministic tb fallback" in m for m in lower_msgs),
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
summary = {
|
| 274 |
"success": success,
|
| 275 |
"design_name": design_name,
|
|
|
|
| 284 |
"congestion": s.congestion, "area_um2": s.area_um2, "power_w": s.power_w}
|
| 285 |
for s in (orchestrator.convergence_history or [])
|
| 286 |
],
|
| 287 |
+
"self_heal": self_heal_stats,
|
| 288 |
"total_steps": len(history),
|
| 289 |
"strategy": orchestrator.strategy.value if orchestrator.strategy else "",
|
| 290 |
"build_time_s": int(time.time()) - (history[0].timestamp if history else int(time.time())),
|
|
|
|
| 369 |
return {"message": "AgentIC API is online", "version": "3.0.0"}
|
| 370 |
|
| 371 |
|
| 372 |
+
@app.get("/pipeline/schema")
|
| 373 |
+
def get_pipeline_schema():
|
| 374 |
+
"""Canonical pipeline schema for frontend timeline rendering."""
|
| 375 |
+
stages = [{"state": s, **STAGE_META.get(s, {"label": s, "icon": "β’"})} for s in BUILD_STATES_ORDER]
|
| 376 |
+
return {
|
| 377 |
+
"stages": stages,
|
| 378 |
+
"terminal_states": ["SUCCESS", "FAIL"],
|
| 379 |
+
"optional_stages": ["REGRESSION", "ECO_PATCH"],
|
| 380 |
+
"total_steps": TOTAL_STEPS,
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
@app.get("/build/options")
|
| 385 |
+
def get_build_options_contract():
|
| 386 |
+
"""Metadata contract for web build-option UI and docs sync."""
|
| 387 |
+
return {
|
| 388 |
+
"groups": [
|
| 389 |
+
{
|
| 390 |
+
"name": "Core",
|
| 391 |
+
"options": [
|
| 392 |
+
{"key": "strict_gates", "type": "boolean", "default": True, "description": "Enable strict gate enforcement with bounded self-healing."},
|
| 393 |
+
{"key": "full_signoff", "type": "boolean", "default": False, "description": "Run full physical signoff checks when available."},
|
| 394 |
+
{"key": "skip_openlane", "type": "boolean", "default": False, "description": "Skip physical implementation stages for faster RTL-only iteration."},
|
| 395 |
+
{"key": "max_retries", "type": "int", "default": 5, "min": 1, "max": 12, "description": "Max repair retries per stage."},
|
| 396 |
+
],
|
| 397 |
+
},
|
| 398 |
+
{
|
| 399 |
+
"name": "Coverage",
|
| 400 |
+
"options": [
|
| 401 |
+
{"key": "min_coverage", "type": "float", "default": 80.0, "min": 0.0, "max": 100.0, "description": "Minimum line coverage threshold."},
|
| 402 |
+
{"key": "coverage_profile", "type": "enum", "default": "balanced", "values": ["balanced", "aggressive", "relaxed"], "description": "Profile-based line/branch/toggle/function thresholds."},
|
| 403 |
+
{"key": "coverage_backend", "type": "enum", "default": "auto", "values": ["auto", "verilator", "iverilog"], "description": "Coverage simulator backend selection."},
|
| 404 |
+
{"key": "coverage_fallback_policy", "type": "enum", "default": "fail_closed", "values": ["fail_closed", "fallback_oss", "skip"], "description": "Behavior when coverage infra fails."},
|
| 405 |
+
],
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"name": "Verification",
|
| 409 |
+
"options": [
|
| 410 |
+
{"key": "tb_gate_mode", "type": "enum", "default": "strict", "values": ["strict", "relaxed"], "description": "TB compile/static gate mode."},
|
| 411 |
+
{"key": "tb_max_retries", "type": "int", "default": 3, "min": 1, "max": 10, "description": "TB-specific retry budget."},
|
| 412 |
+
{"key": "tb_fallback_template", "type": "enum", "default": "uvm_lite", "values": ["uvm_lite", "classic"], "description": "Deterministic fallback testbench template."},
|
| 413 |
+
],
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"name": "Physical",
|
| 417 |
+
"options": [
|
| 418 |
+
{"key": "pdk_profile", "type": "enum", "default": "sky130", "values": ["sky130", "gf180"], "description": "OSS PDK profile."},
|
| 419 |
+
{"key": "max_pivots", "type": "int", "default": 2, "min": 0, "max": 6, "description": "Convergence strategy pivot budget."},
|
| 420 |
+
{"key": "congestion_threshold", "type": "float", "default": 10.0, "min": 0.0, "max": 100.0, "description": "Congestion threshold for convergence review."},
|
| 421 |
+
{"key": "hierarchical", "type": "enum", "default": "auto", "values": ["auto", "on", "off"], "description": "Hierarchy planner mode."},
|
| 422 |
+
],
|
| 423 |
+
},
|
| 424 |
+
]
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
@app.get("/docs/index")
|
| 429 |
+
def get_docs_index():
|
| 430 |
+
"""List in-app documentation documents."""
|
| 431 |
+
docs = _docs_index()
|
| 432 |
+
items = []
|
| 433 |
+
for doc_id, meta in docs.items():
|
| 434 |
+
path = meta.get("path", "")
|
| 435 |
+
if os.path.exists(path):
|
| 436 |
+
items.append({
|
| 437 |
+
"id": doc_id,
|
| 438 |
+
"title": meta.get("title", doc_id),
|
| 439 |
+
"section": meta.get("section", "General"),
|
| 440 |
+
"summary": meta.get("summary", ""),
|
| 441 |
+
})
|
| 442 |
+
return {"docs": items}
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
@app.get("/docs/content/{doc_id}")
|
| 446 |
+
def get_doc_content(doc_id: str):
|
| 447 |
+
"""Return markdown content for one document by id."""
|
| 448 |
+
docs = _docs_index()
|
| 449 |
+
meta = docs.get(doc_id)
|
| 450 |
+
if not meta:
|
| 451 |
+
raise HTTPException(status_code=404, detail="Document not found")
|
| 452 |
+
|
| 453 |
+
path = meta.get("path", "")
|
| 454 |
+
if not path or not os.path.exists(path):
|
| 455 |
+
raise HTTPException(status_code=404, detail="Document file missing")
|
| 456 |
+
|
| 457 |
+
try:
|
| 458 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 459 |
+
content = f.read()
|
| 460 |
+
except OSError as e:
|
| 461 |
+
raise HTTPException(status_code=500, detail=f"Failed to read document: {e}")
|
| 462 |
+
|
| 463 |
+
return {
|
| 464 |
+
"id": doc_id,
|
| 465 |
+
"title": meta.get("title", doc_id),
|
| 466 |
+
"section": meta.get("section", "General"),
|
| 467 |
+
"content": content,
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
|
| 471 |
@app.post("/build")
|
| 472 |
def trigger_build(req: BuildRequest):
|
| 473 |
"""Start a new chip build. Returns job_id immediately."""
|
|
|
|
| 493 |
"created_at": int(time.time()),
|
| 494 |
}
|
| 495 |
|
| 496 |
+
req.design_name = design_name
|
| 497 |
+
|
| 498 |
thread = threading.Thread(
|
| 499 |
target=_run_agentic_build,
|
| 500 |
+
args=(job_id, req),
|
| 501 |
daemon=True,
|
| 502 |
)
|
| 503 |
thread.start()
|
smart_nic_prompt.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Design a high-performance networking chip for a smart NIC (Network Interface Card) with the following features:
|
| 2 |
+
|
| 3 |
+
1. Packet Parser: Hardware module to parse Ethernet, IPv4/IPv6, TCP/UDP headers, and extract fields (src/dst MAC, IP, ports, protocol, flags, etc.) at line rate (β₯100Gbps). Support programmable parsing rules for custom protocols.
|
| 4 |
+
2. QoS Engine: Implement multi-queue packet scheduling with strict priority and weighted fair queuing (WFQ). Support per-flow rate limiting, traffic shaping, and dynamic queue mapping based on parsed header fields. Provide statistics counters for each queue and flow.
|
| 5 |
+
3. Crypto Accelerator: Integrate a hardware crypto engine supporting AES-GCM (128/256), ChaCha20-Poly1305, and SHA-2 hashing. Enable inline encryption/decryption and authentication for selected flows, with key/context management and low-latency operation.
|
| 6 |
+
4. AXI/PCIe Interface: Expose a high-bandwidth AXI or PCIe interface for DMA to host memory. Support descriptor-based packet I/O and interrupt coalescing.
|
| 7 |
+
5. Control/Status: Provide a register interface for configuration (parsing rules, QoS policies, crypto keys), status monitoring, and error reporting.
|
| 8 |
+
|
| 9 |
+
Constraints:
|
| 10 |
+
- Target 28nm or 14nm process, area < 10mmΒ², power < 2W.
|
| 11 |
+
- Verilog RTL, synthesizable, with testbenches for all modules.
|
| 12 |
+
- Formal verification for packet parser and crypto engine.
|
| 13 |
+
- VCD waveform output for key scenarios.
|
src/agentic/agents/architect.py
CHANGED
|
@@ -4,12 +4,12 @@ from langchain_openai import ChatOpenAI
|
|
| 4 |
|
| 5 |
def get_architect_agent(llm, tools, verbose=False):
|
| 6 |
deepseek_llm = ChatOpenAI(
|
| 7 |
-
model="deepseek-ai/deepseek-v3.
|
| 8 |
base_url="https://integrate.api.nvidia.com/v1",
|
| 9 |
api_key=os.environ.get("NVIDIA_API_KEY", ""),
|
| 10 |
-
temperature=
|
| 11 |
model_kwargs={
|
| 12 |
-
"top_p": 0.
|
| 13 |
"extra_body": {"chat_template_kwargs": {"thinking": True}}
|
| 14 |
},
|
| 15 |
max_tokens=8192
|
|
|
|
| 4 |
|
| 5 |
def get_architect_agent(llm, tools, verbose=False):
|
| 6 |
deepseek_llm = ChatOpenAI(
|
| 7 |
+
model="deepseek-ai/deepseek-v3.2",
|
| 8 |
base_url="https://integrate.api.nvidia.com/v1",
|
| 9 |
api_key=os.environ.get("NVIDIA_API_KEY", ""),
|
| 10 |
+
temperature=1.0,
|
| 11 |
model_kwargs={
|
| 12 |
+
"top_p": 0.95,
|
| 13 |
"extra_body": {"chat_template_kwargs": {"thinking": True}}
|
| 14 |
},
|
| 15 |
max_tokens=8192
|
src/agentic/agents/designer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# agents/designer.py
|
| 2 |
from crewai import Agent
|
|
|
|
| 3 |
|
| 4 |
# Universal chip support: complete list of chip families the LLM must handle
|
| 5 |
CHIP_FAMILIES = """
|
|
@@ -98,5 +99,6 @@ def get_designer_agent(llm, goal, verbose=False, strategy="SV_MODULAR"):
|
|
| 98 |
backstory=backstory,
|
| 99 |
llm=llm,
|
| 100 |
verbose=verbose,
|
| 101 |
-
allow_delegation=False
|
|
|
|
| 102 |
)
|
|
|
|
| 1 |
# agents/designer.py
|
| 2 |
from crewai import Agent
|
| 3 |
+
from ..tools.vlsi_tools import syntax_check_tool, read_file_tool
|
| 4 |
|
| 5 |
# Universal chip support: complete list of chip families the LLM must handle
|
| 6 |
CHIP_FAMILIES = """
|
|
|
|
| 99 |
backstory=backstory,
|
| 100 |
llm=llm,
|
| 101 |
verbose=verbose,
|
| 102 |
+
allow_delegation=False,
|
| 103 |
+
tools=[syntax_check_tool, read_file_tool]
|
| 104 |
)
|
src/agentic/agents/testbench_designer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
# agents/testbench_designer.py
|
| 2 |
from crewai import Agent
|
|
|
|
| 3 |
|
| 4 |
TB_UNIVERSAL_RULES = """
|
| 5 |
TESTBENCH UNIVERSAL RULES (must follow for ANY chip type):
|
|
@@ -68,26 +69,23 @@ def get_testbench_agent(llm, goal, verbose=False, strategy="SV_MODULAR"):
|
|
| 68 |
else:
|
| 69 |
role = "UVM Verification Lead"
|
| 70 |
backstory = f"""You are a Senior Verification Engineer at a top semiconductor firm.
|
| 71 |
-
Your goal is 100% Functional Coverage
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
Your Methodology:
|
| 74 |
-
1. **
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
3. **Self-Checking**: TB MUST print "TEST PASSED" or "TEST FAILED". No waveform reliance.
|
| 86 |
-
4. **Coverage**: Use 'covergroup' with 'bins' for all states and transitions.
|
| 87 |
-
5. **Strict Gate Contract**:
|
| 88 |
-
- Include Transaction, Driver (or Monitor), and Scoreboard classes.
|
| 89 |
-
- Explicit PASS/FAIL markers required.
|
| 90 |
-
- Return only complete, compilable testbench code.
|
| 91 |
|
| 92 |
{TB_UNIVERSAL_RULES}
|
| 93 |
"""
|
|
@@ -98,5 +96,6 @@ def get_testbench_agent(llm, goal, verbose=False, strategy="SV_MODULAR"):
|
|
| 98 |
backstory=backstory,
|
| 99 |
llm=llm,
|
| 100 |
verbose=verbose,
|
| 101 |
-
allow_delegation=False
|
|
|
|
| 102 |
)
|
|
|
|
| 1 |
# agents/testbench_designer.py
|
| 2 |
from crewai import Agent
|
| 3 |
+
from ..tools.vlsi_tools import syntax_check_tool, read_file_tool
|
| 4 |
|
| 5 |
TB_UNIVERSAL_RULES = """
|
| 6 |
TESTBENCH UNIVERSAL RULES (must follow for ANY chip type):
|
|
|
|
| 69 |
else:
|
| 70 |
role = "UVM Verification Lead"
|
| 71 |
backstory = f"""You are a Senior Verification Engineer at a top semiconductor firm.
|
| 72 |
+
Your goal is 100% Functional Coverage with Verilator-compatible output.
|
| 73 |
+
|
| 74 |
+
CRITICAL: Your target compiler is Verilator 5.0+.
|
| 75 |
+
Verilator does NOT support: classes, interfaces, covergroups, program blocks, virtual, new(), rand.
|
| 76 |
+
You MUST use FLAT PROCEDURAL SystemVerilog only.
|
| 77 |
|
| 78 |
Your Methodology:
|
| 79 |
+
1. **Flat Procedural TB**: Use reg/wire declarations, initial blocks, and direct signal driving.
|
| 80 |
+
2. **Randomized Stimulus**: Use $urandom for random data generation (Verilator-safe).
|
| 81 |
+
3. **Self-Checking**: Compare DUT outputs against expected values with if-statements.
|
| 82 |
+
4. **Error Tracking**: Use `integer fail_count;` β increment on each check failure.
|
| 83 |
+
5. **PASS/FAIL**: Print "TEST PASSED" if fail_count==0, "TEST FAILED" otherwise.
|
| 84 |
+
6. **Timeout Watchdog**: Always add `initial begin #100000; $display("TEST FAILED: Timeout"); $finish; end`
|
| 85 |
+
7. **Waveform Dump**: Always add $dumpfile/$dumpvars.
|
| 86 |
+
|
| 87 |
+
NEVER USE: interface, class, virtual, covergroup, coverpoint, program, new(), rand, constraint.
|
| 88 |
+
These are NOT supported by Verilator and will cause immediate compile failure.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
{TB_UNIVERSAL_RULES}
|
| 91 |
"""
|
|
|
|
| 96 |
backstory=backstory,
|
| 97 |
llm=llm,
|
| 98 |
verbose=verbose,
|
| 99 |
+
allow_delegation=False,
|
| 100 |
+
tools=[syntax_check_tool, read_file_tool]
|
| 101 |
)
|
src/agentic/agents/verifier.py
CHANGED
|
@@ -1,12 +1,21 @@
|
|
| 1 |
from crewai import Agent
|
|
|
|
| 2 |
|
| 3 |
def get_verification_agent(llm, verbose=False):
|
| 4 |
return Agent(
|
| 5 |
role='Formal Verification Engineer',
|
| 6 |
-
goal='Ensure chip correctness using
|
| 7 |
-
backstory=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
llm=llm,
|
| 9 |
verbose=verbose,
|
|
|
|
| 10 |
allow_delegation=False
|
| 11 |
)
|
| 12 |
|
|
@@ -14,9 +23,18 @@ def get_error_analyst_agent(llm, verbose=False):
|
|
| 14 |
return Agent(
|
| 15 |
role='EDA Log Analyst',
|
| 16 |
goal='Analyze simulation/compilation logs and determine the root cause of failure (Design vs Testbench vs Tool).',
|
| 17 |
-
backstory=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
llm=llm,
|
| 19 |
verbose=verbose,
|
|
|
|
| 20 |
allow_delegation=False
|
| 21 |
)
|
| 22 |
|
|
@@ -37,9 +55,17 @@ def get_regression_agent(llm, goal, verbose=False):
|
|
| 37 |
- Edge cases (back-to-back operations, simultaneous events)
|
| 38 |
- Boundary conditions (full FIFO, empty buffer, max count)
|
| 39 |
- Stress tests (rapid toggling, sustained load)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
You output self-checking Verilog testbenches with clear PASS/FAIL markers.
|
| 41 |
Each test must print "TEST PASSED" on success or "TEST FAILED" on failure.""",
|
| 42 |
llm=llm,
|
| 43 |
verbose=verbose,
|
|
|
|
| 44 |
allow_delegation=False
|
| 45 |
)
|
|
|
|
| 1 |
from crewai import Agent
|
| 2 |
+
from ..tools.vlsi_tools import syntax_check_tool, read_file_tool
|
| 3 |
|
| 4 |
def get_verification_agent(llm, verbose=False):
|
| 5 |
return Agent(
|
| 6 |
role='Formal Verification Engineer',
|
| 7 |
+
goal='Ensure chip correctness using SVA-style inline assertions and rigorous log analysis.',
|
| 8 |
+
backstory="""Senior Verification Engineer targeting Verilator 5 simulation flow.
|
| 9 |
+
IMPORTANT CONSTRAINTS (Verilator compatibility):
|
| 10 |
+
- NEVER use: class, interface (inside modules), covergroup, program, rand, virtual
|
| 11 |
+
- Use inline SVA: assert property (@(posedge clk) condition);
|
| 12 |
+
- Use immediate assertions: assert(condition) else $error("...");
|
| 13 |
+
- All verification constructs must be Verilator 5 compatible
|
| 14 |
+
- Use flat procedural testbenches with reg/wire declarations
|
| 15 |
+
You have tools to read files and check syntax β USE THEM to verify your output compiles.""",
|
| 16 |
llm=llm,
|
| 17 |
verbose=verbose,
|
| 18 |
+
tools=[syntax_check_tool, read_file_tool],
|
| 19 |
allow_delegation=False
|
| 20 |
)
|
| 21 |
|
|
|
|
| 23 |
return Agent(
|
| 24 |
role='EDA Log Analyst',
|
| 25 |
goal='Analyze simulation/compilation logs and determine the root cause of failure (Design vs Testbench vs Tool).',
|
| 26 |
+
backstory="""Expert in parsing EDA tool error messages (Icarus Verilog, Verilator, Yosys).
|
| 27 |
+
You have access to file reading tools β USE THEM to read the actual RTL and TB source
|
| 28 |
+
files when analyzing errors. Don't guess at the code β read it.
|
| 29 |
+
Key diagnostic patterns:
|
| 30 |
+
- "Cannot find interface" = code uses interface but Verilator doesn't support it inside modules
|
| 31 |
+
- "Unsupported: class" = code uses SystemVerilog classes which Verilator rejects
|
| 32 |
+
- Port mismatch = TB instantiates ports not in RTL module declaration
|
| 33 |
+
- Undeclared identifier = signal used but not declared as reg/wire
|
| 34 |
+
Always recommend Verilator-compatible fixes (no classes, no interfaces inside modules).""",
|
| 35 |
llm=llm,
|
| 36 |
verbose=verbose,
|
| 37 |
+
tools=[syntax_check_tool, read_file_tool],
|
| 38 |
allow_delegation=False
|
| 39 |
)
|
| 40 |
|
|
|
|
| 55 |
- Edge cases (back-to-back operations, simultaneous events)
|
| 56 |
- Boundary conditions (full FIFO, empty buffer, max count)
|
| 57 |
- Stress tests (rapid toggling, sustained load)
|
| 58 |
+
|
| 59 |
+
VERILATOR COMPATIBILITY (MANDATORY):
|
| 60 |
+
- NEVER use: class, interface, covergroup, program, rand, virtual, new()
|
| 61 |
+
- Use flat procedural testbenches with reg/wire declarations
|
| 62 |
+
- Use initial/always blocks for stimulus and checking
|
| 63 |
+
- Instantiate DUT with positional or named port connections
|
| 64 |
+
|
| 65 |
You output self-checking Verilog testbenches with clear PASS/FAIL markers.
|
| 66 |
Each test must print "TEST PASSED" on success or "TEST FAILED" on failure.""",
|
| 67 |
llm=llm,
|
| 68 |
verbose=verbose,
|
| 69 |
+
tools=[syntax_check_tool, read_file_tool],
|
| 70 |
allow_delegation=False
|
| 71 |
)
|
src/agentic/cli.py
CHANGED
|
@@ -61,10 +61,9 @@ console = Console()
|
|
| 61 |
|
| 62 |
# Setup Brain
|
| 63 |
def get_llm():
|
| 64 |
-
"""Returns the LLM instance
|
| 65 |
-
1. NVIDIA
|
| 66 |
-
2.
|
| 67 |
-
3. VeriReason Local (Fallback)
|
| 68 |
"""
|
| 69 |
|
| 70 |
configs = [
|
|
@@ -87,18 +86,22 @@ def get_llm():
|
|
| 87 |
extra_t = {
|
| 88 |
"chat_template_kwargs": {"enable_thinking": True, "clear_thinking": False}
|
| 89 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
llm = LLM(
|
| 92 |
model=cfg["model"],
|
| 93 |
base_url=cfg["base_url"],
|
| 94 |
api_key=key if key and key != "NA" else "mock-key", # Local LLMs might use mock-key
|
| 95 |
-
temperature=0.
|
| 96 |
-
top_p=0.
|
| 97 |
-
max_completion_tokens=
|
| 98 |
-
max_tokens=
|
| 99 |
timeout=300,
|
| 100 |
extra_body=extra_t,
|
| 101 |
-
model_kwargs={"
|
| 102 |
)
|
| 103 |
console.print(f"[green]β AgentIC is working on your chip using {name}[/green]")
|
| 104 |
return llm
|
|
|
|
| 61 |
|
| 62 |
# Setup Brain
|
| 63 |
def get_llm():
|
| 64 |
+
"""Returns the LLM instance from the best available provider:
|
| 65 |
+
1. NVIDIA Cloud (e.g. Llama 3.3, DeepSeek)
|
| 66 |
+
2. Local Compute Engine (VeriReason/Ollama)
|
|
|
|
| 67 |
"""
|
| 68 |
|
| 69 |
configs = [
|
|
|
|
| 86 |
extra_t = {
|
| 87 |
"chat_template_kwargs": {"enable_thinking": True, "clear_thinking": False}
|
| 88 |
}
|
| 89 |
+
elif "deepseek-v3.2" in cfg["model"].lower():
|
| 90 |
+
extra_t = {
|
| 91 |
+
"chat_template_kwargs": {"thinking": True}
|
| 92 |
+
}
|
| 93 |
|
| 94 |
llm = LLM(
|
| 95 |
model=cfg["model"],
|
| 96 |
base_url=cfg["base_url"],
|
| 97 |
api_key=key if key and key != "NA" else "mock-key", # Local LLMs might use mock-key
|
| 98 |
+
temperature=0.2, # Standardized for RTL generation stability
|
| 99 |
+
top_p=0.7, # Optimized for code output
|
| 100 |
+
max_completion_tokens=8192,
|
| 101 |
+
max_tokens=8192,
|
| 102 |
timeout=300,
|
| 103 |
extra_body=extra_t,
|
| 104 |
+
model_kwargs={"presence_penalty": 0, "repetition_penalty": 1}
|
| 105 |
)
|
| 106 |
console.print(f"[green]β AgentIC is working on your chip using {name}[/green]")
|
| 107 |
return llm
|
src/agentic/config.py
CHANGED
|
@@ -12,7 +12,7 @@ DESIGNS_DIR = os.path.join(OPENLANE_ROOT, "designs")
|
|
| 12 |
SCRIPTS_DIR = os.path.join(WORKSPACE_ROOT, "scripts")
|
| 13 |
|
| 14 |
CLOUD_CONFIG = {
|
| 15 |
-
"model": os.environ.get("NVIDIA_MODEL", "
|
| 16 |
"base_url": os.environ.get("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1"),
|
| 17 |
"api_key": os.environ.get("NVIDIA_API_KEY", ""),
|
| 18 |
}
|
|
|
|
| 12 |
SCRIPTS_DIR = os.path.join(WORKSPACE_ROOT, "scripts")
|
| 13 |
|
| 14 |
CLOUD_CONFIG = {
|
| 15 |
+
"model": os.environ.get("NVIDIA_MODEL", "meta/llama-3.3-70b-instruct"),
|
| 16 |
"base_url": os.environ.get("NVIDIA_BASE_URL", "https://integrate.api.nvidia.com/v1"),
|
| 17 |
"api_key": os.environ.get("NVIDIA_API_KEY", ""),
|
| 18 |
}
|
src/agentic/core/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AgentIC Multi-Agent Core Modules
|
| 3 |
+
=================================
|
| 4 |
+
State-of-the-art pipeline modules based on Spec2RTL-Agent, VerilogCoder, and FVDebug.
|
| 5 |
+
|
| 6 |
+
Modules:
|
| 7 |
+
- architect: Spec2RTL Decomposer Agent (structured spec β JSON)
|
| 8 |
+
- waveform_expert: AST-based Waveform Tracing (Pyverilog + VCD back-trace)
|
| 9 |
+
- deep_debugger: FVDebug balanced analysis (SymbiYosys + causal graphs)
|
| 10 |
+
- react_agent: ReAct (Reasoning + Acting) framework for all agent loops
|
| 11 |
+
- self_reflect: Self-reflection retry pipeline with OpenLane convergence
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from .architect import ArchitectModule, StructuredSpecDict
|
| 15 |
+
from .waveform_expert import WaveformExpertModule
|
| 16 |
+
from .deep_debugger import DeepDebuggerModule
|
| 17 |
+
from .react_agent import ReActAgent, ReActStep
|
| 18 |
+
from .self_reflect import SelfReflectPipeline
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"ArchitectModule",
|
| 22 |
+
"StructuredSpecDict",
|
| 23 |
+
"WaveformExpertModule",
|
| 24 |
+
"DeepDebuggerModule",
|
| 25 |
+
"ReActAgent",
|
| 26 |
+
"ReActStep",
|
| 27 |
+
"SelfReflectPipeline",
|
| 28 |
+
]
|
src/agentic/core/architect.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Architect Module β Spec2RTL Decomposer Agent
|
| 3 |
+
=============================================
|
| 4 |
+
|
| 5 |
+
Based on: Spec2RTL-Agent (arXiv:2405.xxxxx)
|
| 6 |
+
|
| 7 |
+
Before writing any Verilog, this module reads the input specification (text/PDF)
|
| 8 |
+
and produces a Structured Information Dictionary (SID) in JSON format.
|
| 9 |
+
|
| 10 |
+
The SID explicitly defines:
|
| 11 |
+
- Top-level module name, parameters, ports
|
| 12 |
+
- Sub-module names, inputs, outputs, and functional logic
|
| 13 |
+
- FSM state maps, datapath descriptions, timing constraints
|
| 14 |
+
- Interface protocols and reset strategy
|
| 15 |
+
|
| 16 |
+
This JSON contract becomes the SINGLE SOURCE OF TRUTH for all downstream agents
|
| 17 |
+
(Coder, Verifier, Debugger) β eliminating ambiguity and hallucination.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import json
|
| 21 |
+
import re
|
| 22 |
+
import logging
|
| 23 |
+
import os
|
| 24 |
+
from dataclasses import dataclass, field, asdict
|
| 25 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 26 |
+
from crewai import Agent, Task, Crew, LLM
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# βββ Structured Information Dictionary Schema ββββββββββββββββββββββββ
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class PortDef:
|
| 35 |
+
"""Single port definition."""
|
| 36 |
+
name: str
|
| 37 |
+
direction: str # "input" | "output" | "inout"
|
| 38 |
+
width: str # e.g. "8", "DATA_WIDTH", "1"
|
| 39 |
+
description: str = ""
|
| 40 |
+
reset_value: str = "" # Only for output registers
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@dataclass
|
| 44 |
+
class ParameterDef:
|
| 45 |
+
"""Parameterisation slot."""
|
| 46 |
+
name: str
|
| 47 |
+
default: str
|
| 48 |
+
description: str = ""
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dataclass
|
| 52 |
+
class FSMStateDef:
|
| 53 |
+
"""Single FSM state."""
|
| 54 |
+
name: str
|
| 55 |
+
encoding: str = ""
|
| 56 |
+
description: str = ""
|
| 57 |
+
transitions: List[Dict[str, str]] = field(default_factory=list)
|
| 58 |
+
outputs: Dict[str, str] = field(default_factory=dict)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@dataclass
|
| 62 |
+
class SubModuleDef:
|
| 63 |
+
"""One sub-module (including the top-level module itself)."""
|
| 64 |
+
name: str
|
| 65 |
+
description: str = ""
|
| 66 |
+
parameters: List[ParameterDef] = field(default_factory=list)
|
| 67 |
+
ports: List[PortDef] = field(default_factory=list)
|
| 68 |
+
functional_logic: str = "" # Natural language description
|
| 69 |
+
rtl_skeleton: str = "" # Verilog skeleton (optional)
|
| 70 |
+
fsm_states: List[FSMStateDef] = field(default_factory=list)
|
| 71 |
+
internal_signals: List[Dict[str, str]] = field(default_factory=list)
|
| 72 |
+
instantiates: List[str] = field(default_factory=list) # Sub-module names
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@dataclass
|
| 76 |
+
class StructuredSpecDict:
|
| 77 |
+
"""
|
| 78 |
+
Complete Structured Information Dictionary for a chip design.
|
| 79 |
+
This is the JSON contract between the Architect β Coder β Verifier pipeline.
|
| 80 |
+
"""
|
| 81 |
+
design_name: str
|
| 82 |
+
chip_family: str # e.g. "counter", "FIFO", "UART", "AES", "RISC-V"
|
| 83 |
+
description: str
|
| 84 |
+
top_module: str
|
| 85 |
+
reset_style: str = "sync" # "sync" | "async"
|
| 86 |
+
clock_name: str = "clk"
|
| 87 |
+
reset_name: str = "rst_n"
|
| 88 |
+
reset_polarity: str = "active_low"
|
| 89 |
+
parameters: List[ParameterDef] = field(default_factory=list)
|
| 90 |
+
sub_modules: List[SubModuleDef] = field(default_factory=list)
|
| 91 |
+
interface_protocol: str = "" # "AXI4-Stream" | "APB" | "wishbone" | "custom"
|
| 92 |
+
timing_notes: str = ""
|
| 93 |
+
verification_hints: List[str] = field(default_factory=list) # Hints for TB agent
|
| 94 |
+
|
| 95 |
+
def to_json(self) -> str:
|
| 96 |
+
return json.dumps(asdict(self), indent=2)
|
| 97 |
+
|
| 98 |
+
@classmethod
|
| 99 |
+
def from_json(cls, json_str: str) -> "StructuredSpecDict":
|
| 100 |
+
data = json.loads(json_str)
|
| 101 |
+
# Reconstruct nested dataclasses
|
| 102 |
+
params = [ParameterDef(**p) for p in data.pop("parameters", [])]
|
| 103 |
+
subs = []
|
| 104 |
+
for sm in data.pop("sub_modules", []):
|
| 105 |
+
sm_params = [ParameterDef(**p) for p in sm.pop("parameters", [])]
|
| 106 |
+
sm_ports = [PortDef(**p) for p in sm.pop("ports", [])]
|
| 107 |
+
sm_fsm = [FSMStateDef(**s) for s in sm.pop("fsm_states", [])]
|
| 108 |
+
subs.append(SubModuleDef(parameters=sm_params, ports=sm_ports,
|
| 109 |
+
fsm_states=sm_fsm, **sm))
|
| 110 |
+
return cls(parameters=params, sub_modules=subs, **data)
|
| 111 |
+
|
| 112 |
+
def validate(self) -> Tuple[bool, List[str]]:
|
| 113 |
+
"""Validate the SID for completeness and consistency."""
|
| 114 |
+
errors: List[str] = []
|
| 115 |
+
if not self.design_name:
|
| 116 |
+
errors.append("design_name is empty")
|
| 117 |
+
if not self.top_module:
|
| 118 |
+
errors.append("top_module is empty")
|
| 119 |
+
if not self.sub_modules:
|
| 120 |
+
errors.append("No sub_modules defined")
|
| 121 |
+
for sm in self.sub_modules:
|
| 122 |
+
if not sm.name:
|
| 123 |
+
errors.append("Sub-module has empty name")
|
| 124 |
+
if not sm.ports:
|
| 125 |
+
errors.append(f"Sub-module '{sm.name}' has no ports")
|
| 126 |
+
if not sm.functional_logic:
|
| 127 |
+
errors.append(f"Sub-module '{sm.name}' has no functional_logic")
|
| 128 |
+
# Check clk/rst on sequential modules
|
| 129 |
+
port_names = {p.name for p in sm.ports}
|
| 130 |
+
if sm.fsm_states and self.clock_name not in port_names:
|
| 131 |
+
errors.append(f"Sub-module '{sm.name}' has FSM but no '{self.clock_name}' port")
|
| 132 |
+
return len(errors) == 0, errors
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# βββ Decomposer Prompt Templates βββββββββββββββββββββββββββββββββββββ
|
| 136 |
+
|
| 137 |
+
DECOMPOSE_SYSTEM_PROMPT = """\
|
| 138 |
+
You are a Principal VLSI Architect performing Spec-to-RTL decomposition.
|
| 139 |
+
|
| 140 |
+
TASK: Given a natural-language chip specification, produce a COMPLETE Structured
|
| 141 |
+
Information Dictionary (SID) in **valid JSON format**.
|
| 142 |
+
|
| 143 |
+
The JSON MUST follow this EXACT schema:
|
| 144 |
+
{schema}
|
| 145 |
+
|
| 146 |
+
MANDATORY RULES:
|
| 147 |
+
1. Every module (including top-level) MUST appear in "sub_modules" with ALL fields populated.
|
| 148 |
+
2. Every sub-module MUST have at minimum: name, ports (with direction and width), functional_logic.
|
| 149 |
+
3. For sequential designs, clk and rst_n ports are MANDATORY.
|
| 150 |
+
4. FSM modules MUST list ALL states with transitions and outputs.
|
| 151 |
+
5. Use "parameters" for configurable widths/depths β NEVER hardcode magic numbers.
|
| 152 |
+
6. "functional_logic" must be a COMPLETE natural-language specification of the behavior,
|
| 153 |
+
not a placeholder like "implements counter logic".
|
| 154 |
+
7. Return ONLY the JSON object β no markdown fences, no commentary.
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
DECOMPOSE_USER_PROMPT = """\
|
| 158 |
+
DESIGN NAME: {design_name}
|
| 159 |
+
SPECIFICATION: {spec_text}
|
| 160 |
+
|
| 161 |
+
Produce the complete Structured Information Dictionary (JSON) for this chip design.
|
| 162 |
+
Decompose into sub-modules where architecturally appropriate (e.g., separate datapath,
|
| 163 |
+
controller, interface adapter). For simple designs, a single top-level module suffices.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# βββ The Architect Module ββββββββββββββββββββββββββββββββββββββββββββ
|
| 168 |
+
|
| 169 |
+
class ArchitectModule:
|
| 170 |
+
"""
|
| 171 |
+
Spec2RTL Decomposer Agent.
|
| 172 |
+
|
| 173 |
+
Reads a natural language specification and produces a StructuredSpecDict
|
| 174 |
+
(JSON) that defines every sub-module, port, parameter, and FSM state
|
| 175 |
+
BEFORE any Verilog is written.
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
# Minimal JSON schema description for the LLM prompt
|
| 179 |
+
_SCHEMA_DESC = json.dumps({
|
| 180 |
+
"design_name": "str",
|
| 181 |
+
"chip_family": "str (counter|ALU|FIFO|FSM|UART|SPI|AXI|crypto|processor|SoC|...)",
|
| 182 |
+
"description": "str",
|
| 183 |
+
"top_module": "str (Verilog identifier)",
|
| 184 |
+
"reset_style": "sync|async",
|
| 185 |
+
"clock_name": "str",
|
| 186 |
+
"reset_name": "str",
|
| 187 |
+
"reset_polarity": "active_low|active_high",
|
| 188 |
+
"parameters": [{"name": "str", "default": "str", "description": "str"}],
|
| 189 |
+
"sub_modules": [{
|
| 190 |
+
"name": "str (Verilog identifier)",
|
| 191 |
+
"description": "str",
|
| 192 |
+
"parameters": [{"name": "str", "default": "str", "description": "str"}],
|
| 193 |
+
"ports": [{"name": "str", "direction": "input|output|inout",
|
| 194 |
+
"width": "str", "description": "str", "reset_value": "str"}],
|
| 195 |
+
"functional_logic": "COMPLETE natural-language description of behavior",
|
| 196 |
+
"rtl_skeleton": "optional Verilog snippet",
|
| 197 |
+
"fsm_states": [{"name": "str", "encoding": "str", "description": "str",
|
| 198 |
+
"transitions": [{"condition": "str", "next_state": "str"}],
|
| 199 |
+
"outputs": {"signal": "value"}}],
|
| 200 |
+
"internal_signals": [{"name": "str", "width": "str", "purpose": "str"}],
|
| 201 |
+
"instantiates": ["sub_module_name"]
|
| 202 |
+
}],
|
| 203 |
+
"interface_protocol": "str",
|
| 204 |
+
"timing_notes": "str",
|
| 205 |
+
"verification_hints": ["str"]
|
| 206 |
+
}, indent=2)
|
| 207 |
+
|
| 208 |
+
def __init__(self, llm: LLM, verbose: bool = False, max_retries: int = 3):
|
| 209 |
+
self.llm = llm
|
| 210 |
+
self.verbose = verbose
|
| 211 |
+
self.max_retries = max_retries
|
| 212 |
+
|
| 213 |
+
def decompose(self, design_name: str, spec_text: str,
|
| 214 |
+
save_path: Optional[str] = None) -> StructuredSpecDict:
|
| 215 |
+
"""
|
| 216 |
+
Main entry point: decompose a natural-language spec into a StructuredSpecDict.
|
| 217 |
+
|
| 218 |
+
Args:
|
| 219 |
+
design_name: Verilog-safe design name.
|
| 220 |
+
spec_text: Natural language specification (or existing MAS).
|
| 221 |
+
save_path: Optional path to save the JSON artifact.
|
| 222 |
+
|
| 223 |
+
Returns:
|
| 224 |
+
Validated StructuredSpecDict.
|
| 225 |
+
"""
|
| 226 |
+
logger.info(f"[Architect] Decomposing spec for '{design_name}'")
|
| 227 |
+
|
| 228 |
+
system_prompt = DECOMPOSE_SYSTEM_PROMPT.format(schema=self._SCHEMA_DESC)
|
| 229 |
+
user_prompt = DECOMPOSE_USER_PROMPT.format(
|
| 230 |
+
design_name=design_name,
|
| 231 |
+
spec_text=spec_text[:12000], # Truncate to fit context
|
| 232 |
+
)
|
| 233 |
+
|
| 234 |
+
sid = None
|
| 235 |
+
last_error = ""
|
| 236 |
+
|
| 237 |
+
for attempt in range(1, self.max_retries + 1):
|
| 238 |
+
logger.info(f"[Architect] Decompose attempt {attempt}/{self.max_retries}")
|
| 239 |
+
|
| 240 |
+
# Build the CrewAI agent for this attempt
|
| 241 |
+
retry_context = ""
|
| 242 |
+
if last_error:
|
| 243 |
+
retry_context = (
|
| 244 |
+
f"\n\nPREVIOUS ATTEMPT FAILED WITH:\n{last_error}\n"
|
| 245 |
+
"Fix the issues and return a corrected JSON."
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
agent = Agent(
|
| 249 |
+
role="Spec2RTL Decomposer",
|
| 250 |
+
goal=f"Produce a complete Structured Information Dictionary for {design_name}",
|
| 251 |
+
backstory=(
|
| 252 |
+
"You are a world-class VLSI architect who converts natural-language "
|
| 253 |
+
"chip specifications into precise, machine-readable JSON contracts. "
|
| 254 |
+
"You never leave fields empty or use placeholders."
|
| 255 |
+
),
|
| 256 |
+
llm=self.llm,
|
| 257 |
+
verbose=self.verbose,
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
task = Task(
|
| 261 |
+
description=system_prompt + "\n\n" + user_prompt + retry_context,
|
| 262 |
+
expected_output="Valid JSON matching the Structured Information Dictionary schema",
|
| 263 |
+
agent=agent,
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
try:
|
| 267 |
+
raw = str(Crew(agents=[agent], tasks=[task]).kickoff())
|
| 268 |
+
sid = self._parse_response(raw, design_name)
|
| 269 |
+
|
| 270 |
+
# Validate
|
| 271 |
+
ok, errs = sid.validate()
|
| 272 |
+
if not ok:
|
| 273 |
+
last_error = "Validation errors:\n" + "\n".join(f" - {e}" for e in errs)
|
| 274 |
+
logger.warning(f"[Architect] Validation failed: {errs}")
|
| 275 |
+
sid = None
|
| 276 |
+
continue
|
| 277 |
+
|
| 278 |
+
logger.info(f"[Architect] Successfully decomposed into "
|
| 279 |
+
f"{len(sid.sub_modules)} sub-modules")
|
| 280 |
+
break
|
| 281 |
+
|
| 282 |
+
except Exception as e:
|
| 283 |
+
last_error = f"Parse/execution error: {str(e)}"
|
| 284 |
+
logger.warning(f"[Architect] Attempt {attempt} failed: {e}")
|
| 285 |
+
continue
|
| 286 |
+
|
| 287 |
+
if sid is None:
|
| 288 |
+
# Fallback: create a minimal SID from the spec text
|
| 289 |
+
logger.warning("[Architect] All attempts failed β generating fallback SID")
|
| 290 |
+
sid = self._fallback_sid(design_name, spec_text)
|
| 291 |
+
|
| 292 |
+
# Persist artifact
|
| 293 |
+
if save_path:
|
| 294 |
+
os.makedirs(os.path.dirname(save_path), exist_ok=True)
|
| 295 |
+
with open(save_path, "w") as f:
|
| 296 |
+
f.write(sid.to_json())
|
| 297 |
+
logger.info(f"[Architect] SID saved to {save_path}")
|
| 298 |
+
|
| 299 |
+
return sid
|
| 300 |
+
|
| 301 |
+
def _parse_response(self, raw: str, design_name: str) -> StructuredSpecDict:
|
| 302 |
+
"""Extract JSON from LLM response (may contain markdown fences)."""
|
| 303 |
+
text = raw.strip()
|
| 304 |
+
|
| 305 |
+
# Strip markdown fences
|
| 306 |
+
json_match = re.search(r'```(?:json)?\s*([\s\S]*?)```', text)
|
| 307 |
+
if json_match:
|
| 308 |
+
text = json_match.group(1).strip()
|
| 309 |
+
|
| 310 |
+
# Try to find the outermost JSON object
|
| 311 |
+
brace_start = text.find('{')
|
| 312 |
+
brace_end = text.rfind('}')
|
| 313 |
+
if brace_start >= 0 and brace_end > brace_start:
|
| 314 |
+
text = text[brace_start:brace_end + 1]
|
| 315 |
+
|
| 316 |
+
data = json.loads(text)
|
| 317 |
+
|
| 318 |
+
# Ensure design_name is set
|
| 319 |
+
if not data.get("design_name"):
|
| 320 |
+
data["design_name"] = design_name
|
| 321 |
+
if not data.get("top_module"):
|
| 322 |
+
data["top_module"] = design_name
|
| 323 |
+
|
| 324 |
+
return StructuredSpecDict.from_json(json.dumps(data))
|
| 325 |
+
|
| 326 |
+
def _fallback_sid(self, design_name: str, spec_text: str) -> StructuredSpecDict:
|
| 327 |
+
"""Generate a minimal SID when LLM decomposition fails."""
|
| 328 |
+
return StructuredSpecDict(
|
| 329 |
+
design_name=design_name,
|
| 330 |
+
chip_family="unknown",
|
| 331 |
+
description=spec_text[:2000],
|
| 332 |
+
top_module=design_name,
|
| 333 |
+
reset_style="sync",
|
| 334 |
+
parameters=[],
|
| 335 |
+
sub_modules=[
|
| 336 |
+
SubModuleDef(
|
| 337 |
+
name=design_name,
|
| 338 |
+
description=spec_text[:2000],
|
| 339 |
+
ports=[
|
| 340 |
+
PortDef(name="clk", direction="input", width="1", description="System clock"),
|
| 341 |
+
PortDef(name="rst_n", direction="input", width="1", description="Active-low reset"),
|
| 342 |
+
],
|
| 343 |
+
functional_logic=spec_text[:2000],
|
| 344 |
+
)
|
| 345 |
+
],
|
| 346 |
+
verification_hints=["Requires manual specification review β auto-decomposition failed"],
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
def enrich_with_pdf(self, pdf_path: str) -> str:
|
| 350 |
+
"""
|
| 351 |
+
Extract text from a PDF specification document.
|
| 352 |
+
|
| 353 |
+
Uses basic text extraction (no heavy dependencies).
|
| 354 |
+
Falls back to reading the file as plain text if PDF parsing unavailable.
|
| 355 |
+
"""
|
| 356 |
+
try:
|
| 357 |
+
import subprocess
|
| 358 |
+
result = subprocess.run(
|
| 359 |
+
["pdftotext", "-layout", pdf_path, "-"],
|
| 360 |
+
capture_output=True, text=True, timeout=30
|
| 361 |
+
)
|
| 362 |
+
if result.returncode == 0 and result.stdout.strip():
|
| 363 |
+
return result.stdout
|
| 364 |
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
| 365 |
+
pass
|
| 366 |
+
|
| 367 |
+
# Fallback: try reading as plain text
|
| 368 |
+
try:
|
| 369 |
+
with open(pdf_path, "r", errors="ignore") as f:
|
| 370 |
+
return f.read()
|
| 371 |
+
except Exception:
|
| 372 |
+
return ""
|
| 373 |
+
|
| 374 |
+
def sid_to_rtl_prompt(self, sid: StructuredSpecDict) -> str:
|
| 375 |
+
"""
|
| 376 |
+
Convert a SID into a detailed RTL generation prompt.
|
| 377 |
+
|
| 378 |
+
This is what gets fed to the Coder agent β it's a precise,
|
| 379 |
+
unambiguous specification derived from the JSON contract.
|
| 380 |
+
"""
|
| 381 |
+
sections = []
|
| 382 |
+
sections.append(f"# RTL Specification for {sid.top_module}")
|
| 383 |
+
sections.append(f"Chip Family: {sid.chip_family}")
|
| 384 |
+
sections.append(f"Description: {sid.description}")
|
| 385 |
+
sections.append(f"Reset: {sid.reset_style} ({sid.reset_polarity})")
|
| 386 |
+
sections.append(f"Interface: {sid.interface_protocol or 'custom'}")
|
| 387 |
+
|
| 388 |
+
if sid.parameters:
|
| 389 |
+
sections.append("\n## Global Parameters")
|
| 390 |
+
for p in sid.parameters:
|
| 391 |
+
sections.append(f" parameter {p.name} = {p.default} // {p.description}")
|
| 392 |
+
|
| 393 |
+
for sm in sid.sub_modules:
|
| 394 |
+
sections.append(f"\n## Module: {sm.name}")
|
| 395 |
+
sections.append(f" Description: {sm.description}")
|
| 396 |
+
|
| 397 |
+
if sm.parameters:
|
| 398 |
+
sections.append(" Parameters:")
|
| 399 |
+
for p in sm.parameters:
|
| 400 |
+
sections.append(f" parameter {p.name} = {p.default} // {p.description}")
|
| 401 |
+
|
| 402 |
+
sections.append(" Ports:")
|
| 403 |
+
for p in sm.ports:
|
| 404 |
+
rv = f" (reset: {p.reset_value})" if p.reset_value else ""
|
| 405 |
+
sections.append(f" {p.direction} [{p.width}] {p.name} β {p.description}{rv}")
|
| 406 |
+
|
| 407 |
+
sections.append(f" Functional Logic:\n {sm.functional_logic}")
|
| 408 |
+
|
| 409 |
+
if sm.fsm_states:
|
| 410 |
+
sections.append(" FSM States:")
|
| 411 |
+
for s in sm.fsm_states:
|
| 412 |
+
sections.append(f" {s.name}: {s.description}")
|
| 413 |
+
for t in s.transitions:
|
| 414 |
+
sections.append(f" β {t.get('next_state')} when {t.get('condition')}")
|
| 415 |
+
|
| 416 |
+
if sm.instantiates:
|
| 417 |
+
sections.append(f" Instantiates: {', '.join(sm.instantiates)}")
|
| 418 |
+
|
| 419 |
+
if sid.verification_hints:
|
| 420 |
+
sections.append("\n## Verification Hints")
|
| 421 |
+
for h in sid.verification_hints:
|
| 422 |
+
sections.append(f" - {h}")
|
| 423 |
+
|
| 424 |
+
return "\n".join(sections)
|
src/agentic/core/deep_debugger.py
ADDED
|
@@ -0,0 +1,820 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Deep Debugger Module β FVDebug Logic
|
| 3 |
+
=====================================
|
| 4 |
+
|
| 5 |
+
Based on: FVDebug (Formal Verification Debugging with Balanced Analysis)
|
| 6 |
+
|
| 7 |
+
When code fails formal checks (SymbiYosys), this module:
|
| 8 |
+
1. Runs SymbiYosys and parses the failure trace / counterexample.
|
| 9 |
+
2. Builds a Causal Graph of signals involved in the failure.
|
| 10 |
+
3. For EVERY suspicious signal, the agent MUST produce:
|
| 11 |
+
- 2 arguments FOR it being the bug root cause
|
| 12 |
+
- 2 arguments AGAINST it being the bug root cause
|
| 13 |
+
This is MANDATORY to prevent confirmation bias (the "For-and-Against" protocol).
|
| 14 |
+
4. Only after balanced analysis, the debugger decides on the fix.
|
| 15 |
+
|
| 16 |
+
Tools used: SymbiYosys (sby), Yosys (synthesis), Icarus Verilog (sim).
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import re
|
| 21 |
+
import json
|
| 22 |
+
import logging
|
| 23 |
+
import subprocess
|
| 24 |
+
import tempfile
|
| 25 |
+
from dataclasses import dataclass, field, asdict
|
| 26 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# βββ Data Structures βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class ForAgainstArgument:
|
| 35 |
+
"""A single argument for or against a signal being the root cause."""
|
| 36 |
+
stance: str # "FOR" | "AGAINST"
|
| 37 |
+
reasoning: str # The argument text
|
| 38 |
+
evidence: str = "" # Supporting evidence (line ref, VCD data, etc.)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class SuspiciousSignal:
|
| 43 |
+
"""A signal flagged as potentially responsible for the failure."""
|
| 44 |
+
name: str
|
| 45 |
+
module: str
|
| 46 |
+
line: int
|
| 47 |
+
expression: str = ""
|
| 48 |
+
for_arguments: List[ForAgainstArgument] = field(default_factory=list)
|
| 49 |
+
against_arguments: List[ForAgainstArgument] = field(default_factory=list)
|
| 50 |
+
verdict: str = "" # "BUG" | "NOT_BUG" | "UNCERTAIN"
|
| 51 |
+
confidence: float = 0.0 # 0.0 to 1.0
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class CausalGraphNode:
|
| 56 |
+
"""A node in the failure causal graph."""
|
| 57 |
+
signal: str
|
| 58 |
+
driver_type: str # "always_ff", "always_comb", "assign"
|
| 59 |
+
source_line: int
|
| 60 |
+
dependencies: List[str] # Signals feeding into this node
|
| 61 |
+
value_at_failure: str = ""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@dataclass
|
| 65 |
+
class CausalGraph:
|
| 66 |
+
"""Directed graph of signal dependencies involved in the failure."""
|
| 67 |
+
nodes: Dict[str, CausalGraphNode] = field(default_factory=dict)
|
| 68 |
+
root_signal: str = "" # The assertion/property that failed
|
| 69 |
+
failure_time: int = 0
|
| 70 |
+
counterexample: str = ""
|
| 71 |
+
|
| 72 |
+
def get_cone_of_influence(self, signal: str, max_depth: int = 8) -> List[str]:
|
| 73 |
+
"""Get all signals in the backward cone of influence."""
|
| 74 |
+
visited = set()
|
| 75 |
+
self._coi_walk(signal, visited, 0, max_depth)
|
| 76 |
+
return list(visited)
|
| 77 |
+
|
| 78 |
+
def _coi_walk(self, sig: str, visited: set, depth: int, max_depth: int):
|
| 79 |
+
if depth > max_depth or sig in visited:
|
| 80 |
+
return
|
| 81 |
+
visited.add(sig)
|
| 82 |
+
node = self.nodes.get(sig)
|
| 83 |
+
if node:
|
| 84 |
+
for dep in node.dependencies:
|
| 85 |
+
self._coi_walk(dep, visited, depth + 1, max_depth)
|
| 86 |
+
|
| 87 |
+
def to_mermaid(self) -> str:
|
| 88 |
+
"""Export the causal graph as a Mermaid diagram."""
|
| 89 |
+
lines = ["graph TD"]
|
| 90 |
+
for sig, node in self.nodes.items():
|
| 91 |
+
safe_sig = sig.replace("[", "_").replace("]", "_").replace(".", "_")
|
| 92 |
+
for dep in node.dependencies:
|
| 93 |
+
safe_dep = dep.replace("[", "_").replace("]", "_").replace(".", "_")
|
| 94 |
+
lines.append(f" {safe_dep} --> {safe_sig}")
|
| 95 |
+
return "\n".join(lines)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@dataclass
|
| 99 |
+
class FormalFailure:
|
| 100 |
+
"""Parsed result from a SymbiYosys formal verification run."""
|
| 101 |
+
property_name: str
|
| 102 |
+
property_type: str # "assert", "cover", "assume"
|
| 103 |
+
status: str # "FAIL", "PASS", "UNKNOWN", "ERROR"
|
| 104 |
+
counterexample_trace: str # Raw CEX trace text
|
| 105 |
+
failing_step: int = 0
|
| 106 |
+
signals_in_cex: List[str] = field(default_factory=list)
|
| 107 |
+
error_message: str = ""
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@dataclass
|
| 111 |
+
class DebugVerdict:
|
| 112 |
+
"""Final verdict from the Deep Debugger."""
|
| 113 |
+
root_cause_signal: str
|
| 114 |
+
root_cause_line: int
|
| 115 |
+
root_cause_file: str
|
| 116 |
+
fix_description: str
|
| 117 |
+
confidence: float
|
| 118 |
+
causal_graph: CausalGraph
|
| 119 |
+
suspicious_signals: List[SuspiciousSignal]
|
| 120 |
+
balanced_analysis_log: str # Full for-against reasoning for audit
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
# βββ SymbiYosys Interface ββββββββββββββββββββββββββββββββββββββββββββ
|
| 124 |
+
|
| 125 |
+
class SymbiYosysRunner:
|
| 126 |
+
"""
|
| 127 |
+
Runs SymbiYosys formal verification and parses results.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
def __init__(self, sby_bin: str = "sby", yosys_bin: str = "yosys"):
|
| 131 |
+
self.sby_bin = sby_bin
|
| 132 |
+
self.yosys_bin = yosys_bin
|
| 133 |
+
|
| 134 |
+
def run_formal(self, sby_config_path: str, work_dir: str = "") -> FormalFailure:
|
| 135 |
+
"""
|
| 136 |
+
Run SymbiYosys and return parsed failure info.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
sby_config_path: Path to the .sby config file
|
| 140 |
+
work_dir: Working directory for sby output
|
| 141 |
+
|
| 142 |
+
Returns:
|
| 143 |
+
FormalFailure with status, CEX trace, and signal list.
|
| 144 |
+
"""
|
| 145 |
+
if not work_dir:
|
| 146 |
+
work_dir = os.path.dirname(sby_config_path)
|
| 147 |
+
|
| 148 |
+
# Clean previous run directory
|
| 149 |
+
sby_name = os.path.splitext(os.path.basename(sby_config_path))[0]
|
| 150 |
+
sby_work = os.path.join(work_dir, sby_name)
|
| 151 |
+
if os.path.isdir(sby_work):
|
| 152 |
+
import shutil
|
| 153 |
+
shutil.rmtree(sby_work, ignore_errors=True)
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
result = subprocess.run(
|
| 157 |
+
[self.sby_bin, "-f", sby_config_path],
|
| 158 |
+
capture_output=True,
|
| 159 |
+
text=True,
|
| 160 |
+
cwd=work_dir,
|
| 161 |
+
timeout=300,
|
| 162 |
+
)
|
| 163 |
+
output = result.stdout + "\n" + result.stderr
|
| 164 |
+
return self._parse_sby_output(output, sby_work, sby_name)
|
| 165 |
+
|
| 166 |
+
except subprocess.TimeoutExpired:
|
| 167 |
+
return FormalFailure(
|
| 168 |
+
property_name="timeout",
|
| 169 |
+
property_type="assert",
|
| 170 |
+
status="ERROR",
|
| 171 |
+
counterexample_trace="",
|
| 172 |
+
error_message="SymbiYosys timed out after 300s",
|
| 173 |
+
)
|
| 174 |
+
except FileNotFoundError:
|
| 175 |
+
return FormalFailure(
|
| 176 |
+
property_name="missing_tool",
|
| 177 |
+
property_type="assert",
|
| 178 |
+
status="ERROR",
|
| 179 |
+
counterexample_trace="",
|
| 180 |
+
error_message=f"SymbiYosys binary not found at '{self.sby_bin}'",
|
| 181 |
+
)
|
| 182 |
+
except Exception as e:
|
| 183 |
+
return FormalFailure(
|
| 184 |
+
property_name="exception",
|
| 185 |
+
property_type="assert",
|
| 186 |
+
status="ERROR",
|
| 187 |
+
counterexample_trace="",
|
| 188 |
+
error_message=str(e),
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
def _parse_sby_output(self, output: str, work_dir: str, name: str) -> FormalFailure:
|
| 192 |
+
"""Parse SymbiYosys stdout/stderr into a FormalFailure."""
|
| 193 |
+
status = "UNKNOWN"
|
| 194 |
+
prop_name = ""
|
| 195 |
+
prop_type = "assert"
|
| 196 |
+
cex_trace = ""
|
| 197 |
+
failing_step = 0
|
| 198 |
+
signals = []
|
| 199 |
+
|
| 200 |
+
# Determine overall status
|
| 201 |
+
if "DONE (PASS" in output:
|
| 202 |
+
status = "PASS"
|
| 203 |
+
elif "DONE (FAIL" in output:
|
| 204 |
+
status = "FAIL"
|
| 205 |
+
elif "DONE (ERROR" in output or "ERROR" in output:
|
| 206 |
+
status = "ERROR"
|
| 207 |
+
|
| 208 |
+
# Extract failing property
|
| 209 |
+
m = re.search(r'Assert failed in .+?: (.+)', output)
|
| 210 |
+
if m:
|
| 211 |
+
prop_name = m.group(1).strip()
|
| 212 |
+
|
| 213 |
+
# Extract failing step
|
| 214 |
+
m = re.search(r'BMC failed at step\s+(\d+)', output)
|
| 215 |
+
if not m:
|
| 216 |
+
m = re.search(r'Induction failed at step\s+(\d+)', output)
|
| 217 |
+
if m:
|
| 218 |
+
failing_step = int(m.group(1))
|
| 219 |
+
|
| 220 |
+
# Try to read the VCD counterexample
|
| 221 |
+
cex_vcd = os.path.join(work_dir, "engine_0", "trace.vcd")
|
| 222 |
+
if not os.path.exists(cex_vcd):
|
| 223 |
+
cex_vcd = os.path.join(work_dir, "engine_0", "trace0.vcd")
|
| 224 |
+
if os.path.exists(cex_vcd):
|
| 225 |
+
try:
|
| 226 |
+
with open(cex_vcd, "r", errors="replace") as f:
|
| 227 |
+
cex_trace = f.read()[:10000] # Truncate for context
|
| 228 |
+
# Extract signal names from VCD
|
| 229 |
+
signals = re.findall(r'\$var\s+\w+\s+\d+\s+\S+\s+(\w+)', cex_trace)
|
| 230 |
+
except Exception:
|
| 231 |
+
pass
|
| 232 |
+
|
| 233 |
+
# Try text-based counterexample
|
| 234 |
+
if not cex_trace:
|
| 235 |
+
cex_txt = os.path.join(work_dir, "engine_0", "trace.txt")
|
| 236 |
+
if os.path.exists(cex_txt):
|
| 237 |
+
try:
|
| 238 |
+
with open(cex_txt, "r") as f:
|
| 239 |
+
cex_trace = f.read()[:10000]
|
| 240 |
+
except Exception:
|
| 241 |
+
pass
|
| 242 |
+
|
| 243 |
+
return FormalFailure(
|
| 244 |
+
property_name=prop_name,
|
| 245 |
+
property_type=prop_type,
|
| 246 |
+
status=status,
|
| 247 |
+
counterexample_trace=cex_trace,
|
| 248 |
+
failing_step=failing_step,
|
| 249 |
+
signals_in_cex=signals,
|
| 250 |
+
error_message="" if status != "ERROR" else output[-500:],
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
def generate_sby_config(
|
| 254 |
+
self,
|
| 255 |
+
design_name: str,
|
| 256 |
+
rtl_files: List[str],
|
| 257 |
+
properties_file: str = "",
|
| 258 |
+
mode: str = "bmc",
|
| 259 |
+
depth: int = 20,
|
| 260 |
+
engine: str = "smtbmc",
|
| 261 |
+
) -> str:
|
| 262 |
+
"""
|
| 263 |
+
Generate a .sby configuration file content.
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
design_name: Top module name
|
| 267 |
+
rtl_files: List of Verilog source files
|
| 268 |
+
properties_file: Optional SVA properties file
|
| 269 |
+
mode: "bmc" | "prove" | "cover"
|
| 270 |
+
depth: BMC depth
|
| 271 |
+
engine: "smtbmc" | "aiger" | "abc"
|
| 272 |
+
"""
|
| 273 |
+
files_section = "\n".join(rtl_files)
|
| 274 |
+
if properties_file:
|
| 275 |
+
files_section += f"\n{properties_file}"
|
| 276 |
+
|
| 277 |
+
return f"""[tasks]
|
| 278 |
+
{mode}
|
| 279 |
+
|
| 280 |
+
[options]
|
| 281 |
+
{mode}:
|
| 282 |
+
mode {mode}
|
| 283 |
+
depth {depth}
|
| 284 |
+
|
| 285 |
+
[engines]
|
| 286 |
+
{mode}:
|
| 287 |
+
{engine}
|
| 288 |
+
|
| 289 |
+
[script]
|
| 290 |
+
read -formal {' '.join(os.path.basename(f) for f in rtl_files)}
|
| 291 |
+
{f'read -formal {os.path.basename(properties_file)}' if properties_file else ''}
|
| 292 |
+
prep -top {design_name}
|
| 293 |
+
|
| 294 |
+
[files]
|
| 295 |
+
{files_section}
|
| 296 |
+
"""
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
# βββ Causal Graph Builder ββββββββββββββββββββββββββββββββββββββββββββ
|
| 300 |
+
|
| 301 |
+
class CausalGraphBuilder:
|
| 302 |
+
"""
|
| 303 |
+
Builds a causal graph from RTL + formal failure trace.
|
| 304 |
+
|
| 305 |
+
The causal graph connects the failing assertion to the cone of
|
| 306 |
+
signals that contributed to the failure, enabling systematic
|
| 307 |
+
root-cause isolation.
|
| 308 |
+
"""
|
| 309 |
+
|
| 310 |
+
def __init__(self):
|
| 311 |
+
self._assignments: List[Dict[str, Any]] = []
|
| 312 |
+
|
| 313 |
+
def build(
|
| 314 |
+
self,
|
| 315 |
+
rtl_path: str,
|
| 316 |
+
failure: FormalFailure,
|
| 317 |
+
) -> CausalGraph:
|
| 318 |
+
"""Build a causal graph from RTL and formal failure."""
|
| 319 |
+
graph = CausalGraph(
|
| 320 |
+
root_signal=failure.property_name,
|
| 321 |
+
failure_time=failure.failing_step,
|
| 322 |
+
counterexample=failure.counterexample_trace[:2000],
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
# Parse RTL assignments
|
| 326 |
+
self._parse_rtl(rtl_path)
|
| 327 |
+
|
| 328 |
+
# Build graph nodes from assignments
|
| 329 |
+
for asgn in self._assignments:
|
| 330 |
+
sig = asgn["signal"]
|
| 331 |
+
graph.nodes[sig] = CausalGraphNode(
|
| 332 |
+
signal=sig,
|
| 333 |
+
driver_type=asgn["type"],
|
| 334 |
+
source_line=asgn["line"],
|
| 335 |
+
dependencies=asgn["deps"],
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
# If we have CEX signals, annotate values
|
| 339 |
+
if failure.signals_in_cex:
|
| 340 |
+
for sig_name in failure.signals_in_cex:
|
| 341 |
+
if sig_name in graph.nodes:
|
| 342 |
+
graph.nodes[sig_name].value_at_failure = "in_cex"
|
| 343 |
+
|
| 344 |
+
return graph
|
| 345 |
+
|
| 346 |
+
def _parse_rtl(self, rtl_path: str):
|
| 347 |
+
"""Parse RTL to extract signal assignments (regex-based)."""
|
| 348 |
+
self._assignments.clear()
|
| 349 |
+
if not os.path.exists(rtl_path):
|
| 350 |
+
return
|
| 351 |
+
|
| 352 |
+
try:
|
| 353 |
+
with open(rtl_path, "r") as f:
|
| 354 |
+
lines = f.readlines()
|
| 355 |
+
except Exception:
|
| 356 |
+
return
|
| 357 |
+
|
| 358 |
+
in_ff = False
|
| 359 |
+
in_comb = False
|
| 360 |
+
|
| 361 |
+
for i, line in enumerate(lines, 1):
|
| 362 |
+
s = line.strip()
|
| 363 |
+
|
| 364 |
+
if re.search(r'always_ff\b|always\s*@\s*\(\s*posedge', s):
|
| 365 |
+
in_ff = True
|
| 366 |
+
in_comb = False
|
| 367 |
+
elif re.search(r'always_comb\b|always\s*@\s*\(\*\)', s):
|
| 368 |
+
in_comb = True
|
| 369 |
+
in_ff = False
|
| 370 |
+
elif s.startswith("end") and (in_ff or in_comb):
|
| 371 |
+
in_ff = False
|
| 372 |
+
in_comb = False
|
| 373 |
+
|
| 374 |
+
# Continuous assign
|
| 375 |
+
m = re.match(r'\s*assign\s+(\w+)\s*=\s*(.+?)\s*;', s)
|
| 376 |
+
if m:
|
| 377 |
+
sig, rval = m.groups()
|
| 378 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 379 |
+
self._assignments.append({
|
| 380 |
+
"signal": sig, "rvalue": rval, "type": "assign",
|
| 381 |
+
"line": i, "deps": deps,
|
| 382 |
+
})
|
| 383 |
+
continue
|
| 384 |
+
|
| 385 |
+
# Non-blocking
|
| 386 |
+
m = re.match(r'\s*(\w+)\s*<=\s*(.+?)\s*;', s)
|
| 387 |
+
if m:
|
| 388 |
+
sig, rval = m.groups()
|
| 389 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 390 |
+
self._assignments.append({
|
| 391 |
+
"signal": sig, "rvalue": rval,
|
| 392 |
+
"type": "always_ff" if in_ff else "always_comb",
|
| 393 |
+
"line": i, "deps": deps,
|
| 394 |
+
})
|
| 395 |
+
continue
|
| 396 |
+
|
| 397 |
+
# Blocking in always
|
| 398 |
+
if in_comb or in_ff:
|
| 399 |
+
m = re.match(r'\s*(\w+)\s*=\s*(.+?)\s*;', s)
|
| 400 |
+
if m:
|
| 401 |
+
sig, rval = m.groups()
|
| 402 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 403 |
+
self._assignments.append({
|
| 404 |
+
"signal": sig, "rvalue": rval,
|
| 405 |
+
"type": "always_comb" if in_comb else "always_ff",
|
| 406 |
+
"line": i, "deps": deps,
|
| 407 |
+
})
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
# βββ Balanced Analysis Engine ββββββββββββββββββββββββββββββββββββββββ
|
| 411 |
+
|
| 412 |
+
FOR_AGAINST_PROMPT = """\
|
| 413 |
+
You are performing root-cause analysis on a formal verification failure.
|
| 414 |
+
|
| 415 |
+
MANDATORY PROTOCOL: For the suspicious signal below, you MUST write:
|
| 416 |
+
- EXACTLY 2 arguments FOR it being the root cause of the bug
|
| 417 |
+
- EXACTLY 2 arguments AGAINST it being the root cause of the bug
|
| 418 |
+
|
| 419 |
+
Then give your VERDICT: BUG | NOT_BUG | UNCERTAIN (with confidence 0.0-1.0)
|
| 420 |
+
|
| 421 |
+
This balanced analysis is REQUIRED to prevent confirmation bias.
|
| 422 |
+
|
| 423 |
+
SIGNAL UNDER ANALYSIS:
|
| 424 |
+
Name: {signal_name}
|
| 425 |
+
Module: {module}
|
| 426 |
+
Line: {line}
|
| 427 |
+
Expression: {expression}
|
| 428 |
+
Driver type: {driver_type}
|
| 429 |
+
Dependencies: {dependencies}
|
| 430 |
+
|
| 431 |
+
FAILURE CONTEXT:
|
| 432 |
+
Property: {property_name}
|
| 433 |
+
Failure step: {failure_step}
|
| 434 |
+
Counterexample signals: {cex_signals}
|
| 435 |
+
|
| 436 |
+
FULL RTL CONTEXT:
|
| 437 |
+
```verilog
|
| 438 |
+
{rtl_context}
|
| 439 |
+
```
|
| 440 |
+
|
| 441 |
+
Respond in this EXACT format:
|
| 442 |
+
FOR_1: <argument>
|
| 443 |
+
FOR_2: <argument>
|
| 444 |
+
AGAINST_1: <argument>
|
| 445 |
+
AGAINST_2: <argument>
|
| 446 |
+
VERDICT: <BUG|NOT_BUG|UNCERTAIN>
|
| 447 |
+
CONFIDENCE: <0.0 to 1.0>
|
| 448 |
+
REASONING: <one-line summary>
|
| 449 |
+
"""
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
class BalancedAnalyzer:
|
| 453 |
+
"""
|
| 454 |
+
Implements the mandatory "For-and-Against" protocol for every suspicious signal.
|
| 455 |
+
|
| 456 |
+
For every signal in the failure's cone of influence:
|
| 457 |
+
1. The LLM MUST produce 2 FOR arguments (why it could be the bug)
|
| 458 |
+
2. The LLM MUST produce 2 AGAINST arguments (why it might NOT be the bug)
|
| 459 |
+
3. Only then: verdict + confidence score
|
| 460 |
+
|
| 461 |
+
Signals are ranked by confidence and the highest-confidence BUG verdict
|
| 462 |
+
identifies the root cause.
|
| 463 |
+
"""
|
| 464 |
+
|
| 465 |
+
def __init__(self, llm, verbose: bool = False):
|
| 466 |
+
from crewai import Agent, Task, Crew
|
| 467 |
+
self.llm = llm
|
| 468 |
+
self.verbose = verbose
|
| 469 |
+
self._Agent = Agent
|
| 470 |
+
self._Task = Task
|
| 471 |
+
self._Crew = Crew
|
| 472 |
+
|
| 473 |
+
def analyze_signal(
|
| 474 |
+
self,
|
| 475 |
+
signal_name: str,
|
| 476 |
+
graph: CausalGraph,
|
| 477 |
+
failure: FormalFailure,
|
| 478 |
+
rtl_code: str,
|
| 479 |
+
) -> SuspiciousSignal:
|
| 480 |
+
"""
|
| 481 |
+
Run balanced for-and-against analysis on a single signal.
|
| 482 |
+
"""
|
| 483 |
+
node = graph.nodes.get(signal_name)
|
| 484 |
+
if not node:
|
| 485 |
+
return SuspiciousSignal(
|
| 486 |
+
name=signal_name, module="", line=0,
|
| 487 |
+
verdict="UNCERTAIN", confidence=0.0,
|
| 488 |
+
)
|
| 489 |
+
|
| 490 |
+
# Build context: extract surrounding lines from RTL
|
| 491 |
+
rtl_lines = rtl_code.split("\n")
|
| 492 |
+
ctx_start = max(0, node.source_line - 6)
|
| 493 |
+
ctx_end = min(len(rtl_lines), node.source_line + 5)
|
| 494 |
+
rtl_context = "\n".join(rtl_lines[ctx_start:ctx_end])
|
| 495 |
+
|
| 496 |
+
prompt = FOR_AGAINST_PROMPT.format(
|
| 497 |
+
signal_name=signal_name,
|
| 498 |
+
module="top",
|
| 499 |
+
line=node.source_line,
|
| 500 |
+
expression=f"{signal_name} driven by {node.driver_type}",
|
| 501 |
+
driver_type=node.driver_type,
|
| 502 |
+
dependencies=", ".join(node.dependencies),
|
| 503 |
+
property_name=failure.property_name,
|
| 504 |
+
failure_step=failure.failing_step,
|
| 505 |
+
cex_signals=", ".join(failure.signals_in_cex[:20]),
|
| 506 |
+
rtl_context=rtl_context,
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
agent = self._Agent(
|
| 510 |
+
role="Formal Verification Debugger",
|
| 511 |
+
goal=f"Analyze signal '{signal_name}' for root-cause determination",
|
| 512 |
+
backstory=(
|
| 513 |
+
"You are a senior formal verification engineer. You ALWAYS perform "
|
| 514 |
+
"balanced analysis: 2 FOR + 2 AGAINST arguments before any verdict. "
|
| 515 |
+
"This prevents confirmation bias in root-cause identification."
|
| 516 |
+
),
|
| 517 |
+
llm=self.llm,
|
| 518 |
+
verbose=self.verbose,
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
task = self._Task(
|
| 522 |
+
description=prompt,
|
| 523 |
+
expected_output="FOR_1, FOR_2, AGAINST_1, AGAINST_2, VERDICT, CONFIDENCE, REASONING",
|
| 524 |
+
agent=agent,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
try:
|
| 528 |
+
raw = str(self._Crew(agents=[agent], tasks=[task]).kickoff())
|
| 529 |
+
return self._parse_analysis(raw, signal_name, node)
|
| 530 |
+
except Exception as e:
|
| 531 |
+
logger.warning(f"[BalancedAnalyzer] Analysis failed for {signal_name}: {e}")
|
| 532 |
+
return SuspiciousSignal(
|
| 533 |
+
name=signal_name, module="", line=node.source_line,
|
| 534 |
+
expression=f"{node.driver_type} at line {node.source_line}",
|
| 535 |
+
verdict="UNCERTAIN", confidence=0.0,
|
| 536 |
+
)
|
| 537 |
+
|
| 538 |
+
def _parse_analysis(
|
| 539 |
+
self, raw: str, signal_name: str, node: CausalGraphNode
|
| 540 |
+
) -> SuspiciousSignal:
|
| 541 |
+
"""Parse the LLM's balanced analysis response."""
|
| 542 |
+
result = SuspiciousSignal(
|
| 543 |
+
name=signal_name,
|
| 544 |
+
module="top",
|
| 545 |
+
line=node.source_line,
|
| 546 |
+
expression=f"{node.driver_type} at line {node.source_line}",
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
# Extract FOR arguments
|
| 550 |
+
for i in (1, 2):
|
| 551 |
+
m = re.search(rf'FOR_{i}\s*:\s*(.+?)(?:\n|$)', raw, re.IGNORECASE)
|
| 552 |
+
if m:
|
| 553 |
+
result.for_arguments.append(
|
| 554 |
+
ForAgainstArgument(stance="FOR", reasoning=m.group(1).strip())
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
# Extract AGAINST arguments
|
| 558 |
+
for i in (1, 2):
|
| 559 |
+
m = re.search(rf'AGAINST_{i}\s*:\s*(.+?)(?:\n|$)', raw, re.IGNORECASE)
|
| 560 |
+
if m:
|
| 561 |
+
result.against_arguments.append(
|
| 562 |
+
ForAgainstArgument(stance="AGAINST", reasoning=m.group(1).strip())
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
+
# Extract verdict
|
| 566 |
+
m = re.search(r'VERDICT\s*:\s*(BUG|NOT_BUG|UNCERTAIN)', raw, re.IGNORECASE)
|
| 567 |
+
if m:
|
| 568 |
+
result.verdict = m.group(1).upper()
|
| 569 |
+
else:
|
| 570 |
+
result.verdict = "UNCERTAIN"
|
| 571 |
+
|
| 572 |
+
# Extract confidence
|
| 573 |
+
m = re.search(r'CONFIDENCE\s*:\s*([\d.]+)', raw, re.IGNORECASE)
|
| 574 |
+
if m:
|
| 575 |
+
try:
|
| 576 |
+
result.confidence = float(m.group(1))
|
| 577 |
+
except ValueError:
|
| 578 |
+
result.confidence = 0.5
|
| 579 |
+
else:
|
| 580 |
+
result.confidence = 0.5
|
| 581 |
+
|
| 582 |
+
# Validate: enforce mandatory 2+2 rule
|
| 583 |
+
if len(result.for_arguments) < 2:
|
| 584 |
+
logger.warning(
|
| 585 |
+
f"[BalancedAnalyzer] Signal '{signal_name}': only {len(result.for_arguments)} "
|
| 586 |
+
"FOR arguments (2 required) β reducing confidence"
|
| 587 |
+
)
|
| 588 |
+
result.confidence *= 0.5
|
| 589 |
+
if len(result.against_arguments) < 2:
|
| 590 |
+
logger.warning(
|
| 591 |
+
f"[BalancedAnalyzer] Signal '{signal_name}': only {len(result.against_arguments)} "
|
| 592 |
+
"AGAINST arguments (2 required) β reducing confidence"
|
| 593 |
+
)
|
| 594 |
+
result.confidence *= 0.5
|
| 595 |
+
|
| 596 |
+
return result
|
| 597 |
+
|
| 598 |
+
|
| 599 |
+
# βββ Deep Debugger Module ββββββββββββββββββββββββββββββββββββββββββββ
|
| 600 |
+
|
| 601 |
+
class DeepDebuggerModule:
|
| 602 |
+
"""
|
| 603 |
+
FVDebug: Formal Verification Deep Debugger with Balanced Analysis.
|
| 604 |
+
|
| 605 |
+
Pipeline:
|
| 606 |
+
1. Run SymbiYosys formal verification
|
| 607 |
+
2. Parse failure β build Causal Graph
|
| 608 |
+
3. Identify suspicious signals (cone of influence)
|
| 609 |
+
4. For each suspicious signal: mandatory For-and-Against analysis
|
| 610 |
+
5. Rank signals by confidence β identify root cause
|
| 611 |
+
6. Generate precise fix prompt
|
| 612 |
+
"""
|
| 613 |
+
|
| 614 |
+
def __init__(self, llm, sby_bin: str = "sby", yosys_bin: str = "yosys",
|
| 615 |
+
verbose: bool = False, max_signals_to_analyze: int = 5):
|
| 616 |
+
self.llm = llm
|
| 617 |
+
self.verbose = verbose
|
| 618 |
+
self.max_signals = max_signals_to_analyze
|
| 619 |
+
self.sby_runner = SymbiYosysRunner(sby_bin, yosys_bin)
|
| 620 |
+
self.graph_builder = CausalGraphBuilder()
|
| 621 |
+
self.analyzer = BalancedAnalyzer(llm, verbose)
|
| 622 |
+
|
| 623 |
+
def debug_formal_failure(
|
| 624 |
+
self,
|
| 625 |
+
rtl_path: str,
|
| 626 |
+
sby_config_path: str,
|
| 627 |
+
design_name: str,
|
| 628 |
+
rtl_code: str = "",
|
| 629 |
+
) -> Optional[DebugVerdict]:
|
| 630 |
+
"""
|
| 631 |
+
Full FVDebug pipeline: formal check β causal graph β balanced analysis β verdict.
|
| 632 |
+
|
| 633 |
+
Args:
|
| 634 |
+
rtl_path: Path to the RTL source file
|
| 635 |
+
sby_config_path: Path to the .sby configuration
|
| 636 |
+
design_name: Top module name
|
| 637 |
+
rtl_code: RTL source code (read from file if empty)
|
| 638 |
+
|
| 639 |
+
Returns:
|
| 640 |
+
DebugVerdict with root cause, fix, and full balanced analysis log.
|
| 641 |
+
"""
|
| 642 |
+
logger.info(f"[DeepDebugger] Starting FVDebug pipeline for {design_name}")
|
| 643 |
+
|
| 644 |
+
# Load RTL if not provided
|
| 645 |
+
if not rtl_code and os.path.exists(rtl_path):
|
| 646 |
+
with open(rtl_path, "r") as f:
|
| 647 |
+
rtl_code = f.read()
|
| 648 |
+
|
| 649 |
+
# Step 1: Run formal verification
|
| 650 |
+
logger.info("[DeepDebugger] Step 1: Running SymbiYosys formal checks")
|
| 651 |
+
failure = self.sby_runner.run_formal(sby_config_path)
|
| 652 |
+
|
| 653 |
+
if failure.status == "PASS":
|
| 654 |
+
logger.info("[DeepDebugger] All formal properties passed!")
|
| 655 |
+
return None # No debugging needed
|
| 656 |
+
|
| 657 |
+
if failure.status == "ERROR":
|
| 658 |
+
logger.error(f"[DeepDebugger] SymbiYosys error: {failure.error_message}")
|
| 659 |
+
return None
|
| 660 |
+
|
| 661 |
+
# Step 2: Build causal graph
|
| 662 |
+
logger.info("[DeepDebugger] Step 2: Building causal graph")
|
| 663 |
+
graph = self.graph_builder.build(rtl_path, failure)
|
| 664 |
+
|
| 665 |
+
# Step 3: Identify suspicious signals (cone of influence)
|
| 666 |
+
logger.info("[DeepDebugger] Step 3: Identifying suspicious signals")
|
| 667 |
+
if failure.property_name and failure.property_name in graph.nodes:
|
| 668 |
+
coi = graph.get_cone_of_influence(failure.property_name)
|
| 669 |
+
elif failure.signals_in_cex:
|
| 670 |
+
# Use CEX signals as starting points
|
| 671 |
+
coi = set()
|
| 672 |
+
for sig in failure.signals_in_cex[:5]:
|
| 673 |
+
if sig in graph.nodes:
|
| 674 |
+
coi.update(graph.get_cone_of_influence(sig))
|
| 675 |
+
coi = list(coi)
|
| 676 |
+
else:
|
| 677 |
+
# Fallback: analyze all signals
|
| 678 |
+
coi = list(graph.nodes.keys())
|
| 679 |
+
|
| 680 |
+
# Filter to most relevant signals
|
| 681 |
+
coi = [s for s in coi if s not in ("clk", "rst_n", "reset")]
|
| 682 |
+
coi = coi[:self.max_signals]
|
| 683 |
+
|
| 684 |
+
# Step 4: Balanced For-and-Against analysis for each signal
|
| 685 |
+
logger.info(f"[DeepDebugger] Step 4: Balanced analysis on {len(coi)} signals")
|
| 686 |
+
suspicious: List[SuspiciousSignal] = []
|
| 687 |
+
analysis_log_parts: List[str] = []
|
| 688 |
+
|
| 689 |
+
for sig_name in coi:
|
| 690 |
+
logger.info(f"[DeepDebugger] Analyzing signal: {sig_name}")
|
| 691 |
+
ss = self.analyzer.analyze_signal(sig_name, graph, failure, rtl_code)
|
| 692 |
+
suspicious.append(ss)
|
| 693 |
+
|
| 694 |
+
# Build audit log
|
| 695 |
+
analysis_log_parts.append(f"\n--- Signal: {sig_name} (line {ss.line}) ---")
|
| 696 |
+
for fa in ss.for_arguments:
|
| 697 |
+
analysis_log_parts.append(f" FOR: {fa.reasoning}")
|
| 698 |
+
for fa in ss.against_arguments:
|
| 699 |
+
analysis_log_parts.append(f" AGAINST: {fa.reasoning}")
|
| 700 |
+
analysis_log_parts.append(f" VERDICT: {ss.verdict} (confidence: {ss.confidence:.2f})")
|
| 701 |
+
|
| 702 |
+
# Step 5: Rank and select root cause
|
| 703 |
+
bugs = [s for s in suspicious if s.verdict == "BUG"]
|
| 704 |
+
bugs.sort(key=lambda s: s.confidence, reverse=True)
|
| 705 |
+
|
| 706 |
+
if bugs:
|
| 707 |
+
root = bugs[0]
|
| 708 |
+
fix_desc = (
|
| 709 |
+
f"Signal '{root.name}' at line {root.line} is the most likely root cause "
|
| 710 |
+
f"(confidence: {root.confidence:.2f}). "
|
| 711 |
+
f"FOR: {'; '.join(a.reasoning for a in root.for_arguments)}. "
|
| 712 |
+
f"Fix the {root.expression}."
|
| 713 |
+
)
|
| 714 |
+
else:
|
| 715 |
+
# No confident BUG verdict β use highest confidence UNCERTAIN
|
| 716 |
+
suspicious.sort(key=lambda s: s.confidence, reverse=True)
|
| 717 |
+
root = suspicious[0] if suspicious else SuspiciousSignal(
|
| 718 |
+
name="unknown", module="", line=0, verdict="UNCERTAIN"
|
| 719 |
+
)
|
| 720 |
+
fix_desc = (
|
| 721 |
+
f"No clear root cause identified. Most suspicious: '{root.name}' "
|
| 722 |
+
f"at line {root.line} (confidence: {root.confidence:.2f}). "
|
| 723 |
+
"Manual review recommended."
|
| 724 |
+
)
|
| 725 |
+
|
| 726 |
+
return DebugVerdict(
|
| 727 |
+
root_cause_signal=root.name,
|
| 728 |
+
root_cause_line=root.line,
|
| 729 |
+
root_cause_file=rtl_path,
|
| 730 |
+
fix_description=fix_desc,
|
| 731 |
+
confidence=root.confidence,
|
| 732 |
+
causal_graph=graph,
|
| 733 |
+
suspicious_signals=suspicious,
|
| 734 |
+
balanced_analysis_log="\n".join(analysis_log_parts),
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
def generate_fix_prompt(self, verdict: DebugVerdict, rtl_code: str) -> str:
|
| 738 |
+
"""
|
| 739 |
+
Generate a precise LLM fix prompt from the debug verdict.
|
| 740 |
+
|
| 741 |
+
Unlike generic "fix the formal error" prompts, this includes:
|
| 742 |
+
- The exact root-cause signal and line
|
| 743 |
+
- The balanced analysis reasoning
|
| 744 |
+
- The causal dependency chain
|
| 745 |
+
"""
|
| 746 |
+
parts = [
|
| 747 |
+
"# FORMAL VERIFICATION DEBUG FIX REQUEST",
|
| 748 |
+
"",
|
| 749 |
+
"## Root Cause (from FVDebug balanced analysis)",
|
| 750 |
+
f"Signal: {verdict.root_cause_signal}",
|
| 751 |
+
f"File: {verdict.root_cause_file}:{verdict.root_cause_line}",
|
| 752 |
+
f"Confidence: {verdict.confidence:.2f}",
|
| 753 |
+
f"Description: {verdict.fix_description}",
|
| 754 |
+
"",
|
| 755 |
+
"## Balanced Analysis Log",
|
| 756 |
+
verdict.balanced_analysis_log,
|
| 757 |
+
"",
|
| 758 |
+
"## Causal Graph (Mermaid)",
|
| 759 |
+
"```mermaid",
|
| 760 |
+
verdict.causal_graph.to_mermaid(),
|
| 761 |
+
"```",
|
| 762 |
+
"",
|
| 763 |
+
"## Instructions",
|
| 764 |
+
f"1. Fix signal '{verdict.root_cause_signal}' at line {verdict.root_cause_line}.",
|
| 765 |
+
"2. Ensure the fix satisfies ALL formal properties.",
|
| 766 |
+
"3. Do NOT break existing passing properties.",
|
| 767 |
+
"4. Return ONLY corrected Verilog inside ```verilog fences.",
|
| 768 |
+
"",
|
| 769 |
+
"## Current RTL",
|
| 770 |
+
"```verilog",
|
| 771 |
+
rtl_code,
|
| 772 |
+
"```",
|
| 773 |
+
]
|
| 774 |
+
return "\n".join(parts)
|
| 775 |
+
|
| 776 |
+
def debug_from_existing_failure(
|
| 777 |
+
self,
|
| 778 |
+
rtl_path: str,
|
| 779 |
+
failure: FormalFailure,
|
| 780 |
+
rtl_code: str = "",
|
| 781 |
+
) -> Optional[DebugVerdict]:
|
| 782 |
+
"""
|
| 783 |
+
Run balanced analysis on an already-parsed formal failure.
|
| 784 |
+
|
| 785 |
+
Use this when SymbiYosys has already been run and you have the
|
| 786 |
+
FormalFailure data β skips re-running sby.
|
| 787 |
+
"""
|
| 788 |
+
if not rtl_code and os.path.exists(rtl_path):
|
| 789 |
+
with open(rtl_path, "r") as f:
|
| 790 |
+
rtl_code = f.read()
|
| 791 |
+
|
| 792 |
+
graph = self.graph_builder.build(rtl_path, failure)
|
| 793 |
+
|
| 794 |
+
coi = list(graph.nodes.keys())
|
| 795 |
+
coi = [s for s in coi if s not in ("clk", "rst_n", "reset")]
|
| 796 |
+
coi = coi[:self.max_signals]
|
| 797 |
+
|
| 798 |
+
suspicious: List[SuspiciousSignal] = []
|
| 799 |
+
log_parts: List[str] = []
|
| 800 |
+
|
| 801 |
+
for sig in coi:
|
| 802 |
+
ss = self.analyzer.analyze_signal(sig, graph, failure, rtl_code)
|
| 803 |
+
suspicious.append(ss)
|
| 804 |
+
log_parts.append(f"Signal {sig}: {ss.verdict} ({ss.confidence:.2f})")
|
| 805 |
+
|
| 806 |
+
bugs = sorted([s for s in suspicious if s.verdict == "BUG"],
|
| 807 |
+
key=lambda s: s.confidence, reverse=True)
|
| 808 |
+
root = bugs[0] if bugs else (suspicious[0] if suspicious else
|
| 809 |
+
SuspiciousSignal(name="unknown", module="", line=0))
|
| 810 |
+
|
| 811 |
+
return DebugVerdict(
|
| 812 |
+
root_cause_signal=root.name,
|
| 813 |
+
root_cause_line=root.line,
|
| 814 |
+
root_cause_file=rtl_path,
|
| 815 |
+
fix_description=f"Root cause: {root.name} at line {root.line}",
|
| 816 |
+
confidence=root.confidence,
|
| 817 |
+
causal_graph=graph,
|
| 818 |
+
suspicious_signals=suspicious,
|
| 819 |
+
balanced_analysis_log="\n".join(log_parts),
|
| 820 |
+
)
|
src/agentic/core/react_agent.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ReAct Agent Framework β Reasoning and Acting
|
| 3 |
+
=============================================
|
| 4 |
+
|
| 5 |
+
Implements the ReAct prompting technique (Yao et al., 2023) for all
|
| 6 |
+
agent interactions in the AgentIC multi-agent pipeline.
|
| 7 |
+
|
| 8 |
+
ReAct Pattern:
|
| 9 |
+
Thought β Action β Observation β Thought β Action β ...
|
| 10 |
+
|
| 11 |
+
Each agent step follows this loop:
|
| 12 |
+
1. THOUGHT: Reason about the current state and what needs to happen
|
| 13 |
+
2. ACTION: Choose and execute one of the available tools
|
| 14 |
+
3. OBSERVATION: Observe the result of the action
|
| 15 |
+
4. Repeat until the task is complete or max steps reached
|
| 16 |
+
|
| 17 |
+
This replaces ad-hoc LLM prompting with structured, traceable reasoning.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import json
|
| 21 |
+
import re
|
| 22 |
+
import time
|
| 23 |
+
import logging
|
| 24 |
+
from dataclasses import dataclass, field, asdict
|
| 25 |
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
| 26 |
+
from enum import Enum
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# βββ Data Structures βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 32 |
+
|
| 33 |
+
class StepStatus(Enum):
|
| 34 |
+
THOUGHT = "THOUGHT"
|
| 35 |
+
ACTION = "ACTION"
|
| 36 |
+
OBSERVATION = "OBSERVATION"
|
| 37 |
+
FINAL_ANSWER = "FINAL_ANSWER"
|
| 38 |
+
ERROR = "ERROR"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class ReActStep:
|
| 43 |
+
"""A single step in the ReAct reasoning chain."""
|
| 44 |
+
step_num: int
|
| 45 |
+
status: StepStatus
|
| 46 |
+
content: str
|
| 47 |
+
action_name: str = ""
|
| 48 |
+
action_input: str = ""
|
| 49 |
+
observation: str = ""
|
| 50 |
+
timestamp: float = field(default_factory=time.time)
|
| 51 |
+
duration_s: float = 0.0
|
| 52 |
+
|
| 53 |
+
def to_dict(self) -> dict:
|
| 54 |
+
return {
|
| 55 |
+
"step": self.step_num,
|
| 56 |
+
"status": self.status.value,
|
| 57 |
+
"content": self.content,
|
| 58 |
+
"action_name": self.action_name,
|
| 59 |
+
"action_input": self.action_input[:500],
|
| 60 |
+
"observation": self.observation[:500],
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@dataclass
|
| 65 |
+
class ReActTrace:
|
| 66 |
+
"""Complete trace of a ReAct agent run."""
|
| 67 |
+
task_description: str
|
| 68 |
+
steps: List[ReActStep] = field(default_factory=list)
|
| 69 |
+
final_answer: str = ""
|
| 70 |
+
success: bool = False
|
| 71 |
+
total_steps: int = 0
|
| 72 |
+
total_duration_s: float = 0.0
|
| 73 |
+
error: str = ""
|
| 74 |
+
|
| 75 |
+
def to_json(self) -> str:
|
| 76 |
+
return json.dumps({
|
| 77 |
+
"task": self.task_description[:200],
|
| 78 |
+
"success": self.success,
|
| 79 |
+
"total_steps": self.total_steps,
|
| 80 |
+
"total_duration_s": round(self.total_duration_s, 2),
|
| 81 |
+
"steps": [s.to_dict() for s in self.steps],
|
| 82 |
+
"final_answer": self.final_answer[:2000],
|
| 83 |
+
"error": self.error,
|
| 84 |
+
}, indent=2)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# βββ Tool Registry βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 88 |
+
|
| 89 |
+
@dataclass
|
| 90 |
+
class ToolDef:
|
| 91 |
+
"""Definition of a tool available to the ReAct agent."""
|
| 92 |
+
name: str
|
| 93 |
+
description: str
|
| 94 |
+
function: Callable
|
| 95 |
+
parameters: Dict[str, str] = field(default_factory=dict) # param_name β description
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class ToolRegistry:
|
| 99 |
+
"""Registry of tools available to ReAct agents."""
|
| 100 |
+
|
| 101 |
+
def __init__(self):
|
| 102 |
+
self._tools: Dict[str, ToolDef] = {}
|
| 103 |
+
|
| 104 |
+
def register(self, name: str, description: str, func: Callable,
|
| 105 |
+
parameters: Optional[Dict[str, str]] = None):
|
| 106 |
+
self._tools[name] = ToolDef(
|
| 107 |
+
name=name,
|
| 108 |
+
description=description,
|
| 109 |
+
function=func,
|
| 110 |
+
parameters=parameters or {},
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
def get(self, name: str) -> Optional[ToolDef]:
|
| 114 |
+
return self._tools.get(name)
|
| 115 |
+
|
| 116 |
+
def list_tools(self) -> str:
|
| 117 |
+
"""Format tools for the ReAct prompt."""
|
| 118 |
+
lines = []
|
| 119 |
+
for name, tool in self._tools.items():
|
| 120 |
+
params = ", ".join(f"{k}: {v}" for k, v in tool.parameters.items())
|
| 121 |
+
lines.append(f" {name}({params}) β {tool.description}")
|
| 122 |
+
return "\n".join(lines)
|
| 123 |
+
|
| 124 |
+
def execute(self, name: str, input_str: str) -> str:
|
| 125 |
+
"""Execute a tool by name with the given input string."""
|
| 126 |
+
tool = self._tools.get(name)
|
| 127 |
+
if not tool:
|
| 128 |
+
return f"ERROR: Unknown tool '{name}'. Available: {', '.join(self._tools.keys())}"
|
| 129 |
+
try:
|
| 130 |
+
result = tool.function(input_str)
|
| 131 |
+
return str(result) if result is not None else "OK"
|
| 132 |
+
except Exception as e:
|
| 133 |
+
return f"ERROR: {name} failed: {str(e)}"
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# βββ ReAct Prompt Templates ββββββββββββββββββββββββββββββββββββββββββ
|
| 137 |
+
|
| 138 |
+
REACT_SYSTEM_PROMPT = """\
|
| 139 |
+
You are an expert VLSI agent using the ReAct (Reasoning and Acting) framework.
|
| 140 |
+
|
| 141 |
+
On each turn you must output ONE of:
|
| 142 |
+
Thought: <your reasoning about the current state>
|
| 143 |
+
Action: <tool_name>(<input>)
|
| 144 |
+
Final Answer: <your complete answer>
|
| 145 |
+
|
| 146 |
+
RULES:
|
| 147 |
+
1. Always start with a Thought before taking any Action.
|
| 148 |
+
2. After each Action, wait for the Observation before your next Thought.
|
| 149 |
+
3. You MUST use the available tools β do not hallucinate tool outputs.
|
| 150 |
+
4. When you have enough information, produce a Final Answer.
|
| 151 |
+
5. Maximum {max_steps} steps β be efficient.
|
| 152 |
+
6. If an action fails, reason about WHY and try a different approach.
|
| 153 |
+
|
| 154 |
+
Available Tools:
|
| 155 |
+
{tools}
|
| 156 |
+
|
| 157 |
+
TASK: {task}
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
REACT_OBSERVATION_PROMPT = """\
|
| 161 |
+
Observation: {observation}
|
| 162 |
+
|
| 163 |
+
Continue with your next Thought or provide your Final Answer.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# βββ ReAct Agent ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 168 |
+
|
| 169 |
+
class ReActAgent:
|
| 170 |
+
"""
|
| 171 |
+
General-purpose ReAct agent for the AgentIC pipeline.
|
| 172 |
+
|
| 173 |
+
Uses the ReAct (Reasoning + Acting) prompting technique to provide
|
| 174 |
+
structured, traceable reasoning for all agent interactions.
|
| 175 |
+
|
| 176 |
+
Usage:
|
| 177 |
+
agent = ReActAgent(llm, role="RTL Debugger")
|
| 178 |
+
agent.register_tool("syntax_check", "Check Verilog syntax", syntax_check_fn)
|
| 179 |
+
agent.register_tool("read_file", "Read a file", read_file_fn)
|
| 180 |
+
|
| 181 |
+
trace = agent.run("Fix the syntax error in counter.v")
|
| 182 |
+
print(trace.final_answer)
|
| 183 |
+
"""
|
| 184 |
+
|
| 185 |
+
def __init__(
|
| 186 |
+
self,
|
| 187 |
+
llm, # CrewAI LLM instance
|
| 188 |
+
role: str = "VLSI Agent",
|
| 189 |
+
max_steps: int = 10,
|
| 190 |
+
verbose: bool = False,
|
| 191 |
+
):
|
| 192 |
+
self.llm = llm
|
| 193 |
+
self.role = role
|
| 194 |
+
self.max_steps = max_steps
|
| 195 |
+
self.verbose = verbose
|
| 196 |
+
self.tools = ToolRegistry()
|
| 197 |
+
self._conversation: List[Dict[str, str]] = []
|
| 198 |
+
|
| 199 |
+
def register_tool(self, name: str, description: str, func: Callable,
|
| 200 |
+
parameters: Optional[Dict[str, str]] = None):
|
| 201 |
+
"""Register a tool available to this agent."""
|
| 202 |
+
self.tools.register(name, description, func, parameters)
|
| 203 |
+
|
| 204 |
+
def run(self, task: str, context: str = "") -> ReActTrace:
|
| 205 |
+
"""
|
| 206 |
+
Execute the ReAct loop for the given task.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
task: Natural language task description
|
| 210 |
+
context: Additional context (RTL code, error logs, etc.)
|
| 211 |
+
|
| 212 |
+
Returns:
|
| 213 |
+
ReActTrace with complete reasoning chain and final answer.
|
| 214 |
+
"""
|
| 215 |
+
trace = ReActTrace(task_description=task)
|
| 216 |
+
start_time = time.time()
|
| 217 |
+
|
| 218 |
+
# Build system prompt
|
| 219 |
+
system_prompt = REACT_SYSTEM_PROMPT.format(
|
| 220 |
+
max_steps=self.max_steps,
|
| 221 |
+
tools=self.tools.list_tools(),
|
| 222 |
+
task=task,
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
if context:
|
| 226 |
+
system_prompt += f"\n\nCONTEXT:\n{context[:8000]}"
|
| 227 |
+
|
| 228 |
+
self._conversation = [{"role": "system", "content": system_prompt}]
|
| 229 |
+
|
| 230 |
+
step_num = 0
|
| 231 |
+
while step_num < self.max_steps:
|
| 232 |
+
step_num += 1
|
| 233 |
+
step_start = time.time()
|
| 234 |
+
|
| 235 |
+
# Get LLM response
|
| 236 |
+
try:
|
| 237 |
+
response = self._call_llm()
|
| 238 |
+
except Exception as e:
|
| 239 |
+
trace.steps.append(ReActStep(
|
| 240 |
+
step_num=step_num,
|
| 241 |
+
status=StepStatus.ERROR,
|
| 242 |
+
content=f"LLM call failed: {str(e)}",
|
| 243 |
+
))
|
| 244 |
+
trace.error = str(e)
|
| 245 |
+
break
|
| 246 |
+
|
| 247 |
+
# Parse the response
|
| 248 |
+
thought, action_name, action_input, final_answer = self._parse_response(response)
|
| 249 |
+
|
| 250 |
+
# Handle FINAL ANSWER
|
| 251 |
+
if final_answer:
|
| 252 |
+
trace.steps.append(ReActStep(
|
| 253 |
+
step_num=step_num,
|
| 254 |
+
status=StepStatus.FINAL_ANSWER,
|
| 255 |
+
content=final_answer,
|
| 256 |
+
duration_s=time.time() - step_start,
|
| 257 |
+
))
|
| 258 |
+
trace.final_answer = final_answer
|
| 259 |
+
trace.success = True
|
| 260 |
+
break
|
| 261 |
+
|
| 262 |
+
# Handle THOUGHT
|
| 263 |
+
if thought:
|
| 264 |
+
trace.steps.append(ReActStep(
|
| 265 |
+
step_num=step_num,
|
| 266 |
+
status=StepStatus.THOUGHT,
|
| 267 |
+
content=thought,
|
| 268 |
+
duration_s=time.time() - step_start,
|
| 269 |
+
))
|
| 270 |
+
if self.verbose:
|
| 271 |
+
logger.info(f"[ReAct:{self.role}] Thought: {thought[:200]}")
|
| 272 |
+
|
| 273 |
+
# Handle ACTION
|
| 274 |
+
if action_name:
|
| 275 |
+
# Execute the tool
|
| 276 |
+
observation = self.tools.execute(action_name, action_input)
|
| 277 |
+
|
| 278 |
+
trace.steps.append(ReActStep(
|
| 279 |
+
step_num=step_num,
|
| 280 |
+
status=StepStatus.ACTION,
|
| 281 |
+
content=f"{action_name}({action_input[:200]})",
|
| 282 |
+
action_name=action_name,
|
| 283 |
+
action_input=action_input,
|
| 284 |
+
observation=observation[:2000],
|
| 285 |
+
duration_s=time.time() - step_start,
|
| 286 |
+
))
|
| 287 |
+
|
| 288 |
+
if self.verbose:
|
| 289 |
+
logger.info(f"[ReAct:{self.role}] Action: {action_name} β {observation[:200]}")
|
| 290 |
+
|
| 291 |
+
# Feed observation back
|
| 292 |
+
obs_prompt = REACT_OBSERVATION_PROMPT.format(
|
| 293 |
+
observation=observation[:4000]
|
| 294 |
+
)
|
| 295 |
+
self._conversation.append({"role": "assistant", "content": response})
|
| 296 |
+
self._conversation.append({"role": "user", "content": obs_prompt})
|
| 297 |
+
|
| 298 |
+
elif not thought and not final_answer:
|
| 299 |
+
# LLM produced something unparseable β nudge it
|
| 300 |
+
self._conversation.append({"role": "assistant", "content": response})
|
| 301 |
+
self._conversation.append({
|
| 302 |
+
"role": "user",
|
| 303 |
+
"content": (
|
| 304 |
+
"Your response didn't follow the ReAct format. "
|
| 305 |
+
"Please respond with one of:\n"
|
| 306 |
+
" Thought: <reasoning>\n"
|
| 307 |
+
" Action: <tool_name>(<input>)\n"
|
| 308 |
+
" Final Answer: <answer>"
|
| 309 |
+
),
|
| 310 |
+
})
|
| 311 |
+
|
| 312 |
+
trace.total_steps = step_num
|
| 313 |
+
trace.total_duration_s = time.time() - start_time
|
| 314 |
+
|
| 315 |
+
if not trace.success:
|
| 316 |
+
trace.error = trace.error or "Max steps reached without Final Answer"
|
| 317 |
+
# Use last thought/action as fallback answer
|
| 318 |
+
for step in reversed(trace.steps):
|
| 319 |
+
if step.content:
|
| 320 |
+
trace.final_answer = step.content
|
| 321 |
+
break
|
| 322 |
+
|
| 323 |
+
return trace
|
| 324 |
+
|
| 325 |
+
def _call_llm(self) -> str:
|
| 326 |
+
"""Call the LLM with the current conversation."""
|
| 327 |
+
from crewai import Agent, Task, Crew
|
| 328 |
+
|
| 329 |
+
# Build a single prompt from conversation history
|
| 330 |
+
prompt_parts = []
|
| 331 |
+
for msg in self._conversation:
|
| 332 |
+
if msg["role"] == "system":
|
| 333 |
+
prompt_parts.append(msg["content"])
|
| 334 |
+
elif msg["role"] == "user":
|
| 335 |
+
prompt_parts.append(f"\n{msg['content']}")
|
| 336 |
+
elif msg["role"] == "assistant":
|
| 337 |
+
prompt_parts.append(f"\nAssistant: {msg['content']}")
|
| 338 |
+
|
| 339 |
+
full_prompt = "\n".join(prompt_parts)
|
| 340 |
+
|
| 341 |
+
agent = Agent(
|
| 342 |
+
role=self.role,
|
| 343 |
+
goal="Follow the ReAct framework to complete the task",
|
| 344 |
+
backstory=f"Expert {self.role} using structured ReAct reasoning.",
|
| 345 |
+
llm=self.llm,
|
| 346 |
+
verbose=False, # We handle our own logging
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
task = Task(
|
| 350 |
+
description=full_prompt[-12000:], # Truncate to fit context
|
| 351 |
+
expected_output="A Thought, Action, or Final Answer following ReAct format",
|
| 352 |
+
agent=agent,
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
result = str(Crew(agents=[agent], tasks=[task]).kickoff())
|
| 356 |
+
return result
|
| 357 |
+
|
| 358 |
+
def _parse_response(self, response: str) -> Tuple[str, str, str, str]:
|
| 359 |
+
"""
|
| 360 |
+
Parse a ReAct response into (thought, action_name, action_input, final_answer).
|
| 361 |
+
|
| 362 |
+
Returns empty strings for components not present in the response.
|
| 363 |
+
"""
|
| 364 |
+
thought = ""
|
| 365 |
+
action_name = ""
|
| 366 |
+
action_input = ""
|
| 367 |
+
final_answer = ""
|
| 368 |
+
|
| 369 |
+
# Check for Final Answer
|
| 370 |
+
fa_match = re.search(r'Final\s+Answer\s*:\s*(.+)', response, re.DOTALL | re.IGNORECASE)
|
| 371 |
+
if fa_match:
|
| 372 |
+
final_answer = fa_match.group(1).strip()
|
| 373 |
+
return thought, action_name, action_input, final_answer
|
| 374 |
+
|
| 375 |
+
# Check for Thought
|
| 376 |
+
th_match = re.search(r'Thought\s*:\s*(.+?)(?=Action\s*:|Final\s+Answer\s*:|$)',
|
| 377 |
+
response, re.DOTALL | re.IGNORECASE)
|
| 378 |
+
if th_match:
|
| 379 |
+
thought = th_match.group(1).strip()
|
| 380 |
+
|
| 381 |
+
# Check for Action
|
| 382 |
+
act_match = re.search(r'Action\s*:\s*(\w+)\s*\((.+?)\)\s*$',
|
| 383 |
+
response, re.MULTILINE | re.IGNORECASE)
|
| 384 |
+
if not act_match:
|
| 385 |
+
# Try alternative format: Action: tool_name\nAction Input: input
|
| 386 |
+
act_match2 = re.search(r'Action\s*:\s*(\w+)', response, re.IGNORECASE)
|
| 387 |
+
inp_match = re.search(r'Action\s+Input\s*:\s*(.+?)(?=\n|$)', response,
|
| 388 |
+
re.DOTALL | re.IGNORECASE)
|
| 389 |
+
if act_match2:
|
| 390 |
+
action_name = act_match2.group(1).strip()
|
| 391 |
+
action_input = inp_match.group(1).strip() if inp_match else ""
|
| 392 |
+
else:
|
| 393 |
+
action_name = act_match.group(1).strip()
|
| 394 |
+
action_input = act_match.group(2).strip()
|
| 395 |
+
|
| 396 |
+
return thought, action_name, action_input, final_answer
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
# βββ Pre-built ReAct Agents for AgentIC Pipeline βββββββββββββββββββββ
|
| 400 |
+
|
| 401 |
+
def create_rtl_debugger_agent(llm, tools_dict: Dict[str, Callable],
|
| 402 |
+
verbose: bool = False) -> ReActAgent:
|
| 403 |
+
"""Create a ReAct agent pre-configured for RTL debugging."""
|
| 404 |
+
agent = ReActAgent(llm, role="RTL Debugger", max_steps=8, verbose=verbose)
|
| 405 |
+
|
| 406 |
+
default_tools = {
|
| 407 |
+
"syntax_check": ("Check Verilog syntax of a file path", {}),
|
| 408 |
+
"read_file": ("Read contents of a file path", {}),
|
| 409 |
+
"run_simulation": ("Run Icarus Verilog simulation for a design name", {}),
|
| 410 |
+
"trace_signal": ("Back-trace a signal through the RTL AST", {}),
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
for name, func in tools_dict.items():
|
| 414 |
+
desc, params = default_tools.get(name, (f"Execute {name}", {}))
|
| 415 |
+
agent.register_tool(name, desc, func, params)
|
| 416 |
+
|
| 417 |
+
return agent
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def create_formal_debugger_agent(llm, tools_dict: Dict[str, Callable],
|
| 421 |
+
verbose: bool = False) -> ReActAgent:
|
| 422 |
+
"""Create a ReAct agent pre-configured for formal verification debugging."""
|
| 423 |
+
agent = ReActAgent(llm, role="Formal Verification Debugger", max_steps=10, verbose=verbose)
|
| 424 |
+
|
| 425 |
+
default_tools = {
|
| 426 |
+
"run_formal": ("Run SymbiYosys formal verification on a .sby file", {}),
|
| 427 |
+
"read_file": ("Read contents of a file path", {}),
|
| 428 |
+
"analyze_signal": ("Run balanced for-and-against analysis on a signal", {}),
|
| 429 |
+
"build_causal_graph": ("Build causal graph from RTL and failure", {}),
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
for name, func in tools_dict.items():
|
| 433 |
+
desc, params = default_tools.get(name, (f"Execute {name}", {}))
|
| 434 |
+
agent.register_tool(name, desc, func, params)
|
| 435 |
+
|
| 436 |
+
return agent
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
def create_architect_agent(llm, tools_dict: Dict[str, Callable],
|
| 440 |
+
verbose: bool = False) -> ReActAgent:
|
| 441 |
+
"""Create a ReAct agent pre-configured for architectural decomposition."""
|
| 442 |
+
agent = ReActAgent(llm, role="Spec2RTL Architect", max_steps=6, verbose=verbose)
|
| 443 |
+
|
| 444 |
+
default_tools = {
|
| 445 |
+
"decompose_spec": ("Decompose a natural language spec into JSON SID", {}),
|
| 446 |
+
"validate_sid": ("Validate a Structured Information Dictionary", {}),
|
| 447 |
+
"read_spec": ("Read a specification file (text or PDF)", {}),
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
for name, func in tools_dict.items():
|
| 451 |
+
desc, params = default_tools.get(name, (f"Execute {name}", {}))
|
| 452 |
+
agent.register_tool(name, desc, func, params)
|
| 453 |
+
|
| 454 |
+
return agent
|
src/agentic/core/self_reflect.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Self-Reflect Pipeline β Autonomous Retry with Reflection
|
| 3 |
+
==========================================================
|
| 4 |
+
|
| 5 |
+
Implements the "Self-Reflect and Retry" pattern for the AgentIC pipeline.
|
| 6 |
+
|
| 7 |
+
When OpenLane synthesis/hardening fails, the system:
|
| 8 |
+
1. Captures the failure log and error category
|
| 9 |
+
2. Reflects on WHY it failed (structured root-cause analysis)
|
| 10 |
+
3. Generates a corrective action plan
|
| 11 |
+
4. Applies the fix and retries (up to 5 times)
|
| 12 |
+
5. Tracks a convergence history to avoid repeating failed approaches
|
| 13 |
+
|
| 14 |
+
The reflection uses gradient feedback from:
|
| 15 |
+
- Synthesis timing (WNS/TNS)
|
| 16 |
+
- Area utilization
|
| 17 |
+
- DRC/LVS violations
|
| 18 |
+
- Routing congestion
|
| 19 |
+
- Formal property results
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import os
|
| 23 |
+
import re
|
| 24 |
+
import json
|
| 25 |
+
import time
|
| 26 |
+
import hashlib
|
| 27 |
+
import logging
|
| 28 |
+
from dataclasses import dataclass, field, asdict
|
| 29 |
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
| 30 |
+
from enum import Enum
|
| 31 |
+
|
| 32 |
+
logger = logging.getLogger(__name__)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# βββ Data Structures βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
|
| 37 |
+
class FailureCategory(Enum):
|
| 38 |
+
"""Categories of failures the self-reflect pipeline handles."""
|
| 39 |
+
SYNTAX_ERROR = "syntax_error"
|
| 40 |
+
SIMULATION_FAIL = "simulation_fail"
|
| 41 |
+
FORMAL_PROPERTY_FAIL = "formal_property_fail"
|
| 42 |
+
SYNTHESIS_ERROR = "synthesis_error"
|
| 43 |
+
TIMING_VIOLATION = "timing_violation"
|
| 44 |
+
ROUTING_CONGESTION = "routing_congestion"
|
| 45 |
+
DRC_VIOLATION = "drc_violation"
|
| 46 |
+
LVS_MISMATCH = "lvs_mismatch"
|
| 47 |
+
AREA_OVERFLOW = "area_overflow"
|
| 48 |
+
POWER_VIOLATION = "power_violation"
|
| 49 |
+
UNKNOWN = "unknown"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@dataclass
|
| 53 |
+
class FailureAnalysis:
|
| 54 |
+
"""Structured analysis of a pipeline failure."""
|
| 55 |
+
category: FailureCategory
|
| 56 |
+
error_message: str
|
| 57 |
+
root_cause: str # Identified root cause
|
| 58 |
+
impact: str # What downstream effects this has
|
| 59 |
+
similar_past_failures: int # How many similar failures we've seen
|
| 60 |
+
is_repeating: bool # Are we stuck in a loop?
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@dataclass
|
| 64 |
+
class CorrectionAction:
|
| 65 |
+
"""A fix action to attempt."""
|
| 66 |
+
action_type: str # "modify_rtl", "adjust_config", "relax_constraints", "pivot_strategy"
|
| 67 |
+
description: str
|
| 68 |
+
target_file: str = ""
|
| 69 |
+
parameters: Dict[str, Any] = field(default_factory=dict)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@dataclass
|
| 73 |
+
class ReflectionEntry:
|
| 74 |
+
"""A single self-reflection cycle."""
|
| 75 |
+
attempt: int
|
| 76 |
+
failure: FailureAnalysis
|
| 77 |
+
reflection: str # The agent's reasoning about the failure
|
| 78 |
+
proposed_actions: List[CorrectionAction]
|
| 79 |
+
outcome: str = "" # "fixed" | "partial" | "failed" | "worse"
|
| 80 |
+
timestamp: float = field(default_factory=time.time)
|
| 81 |
+
metrics_before: Dict[str, Any] = field(default_factory=dict)
|
| 82 |
+
metrics_after: Dict[str, Any] = field(default_factory=dict)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
@dataclass
|
| 86 |
+
class ConvergenceMetrics:
|
| 87 |
+
"""Metrics tracked across retry iterations for convergence analysis."""
|
| 88 |
+
wns: float = 0.0 # Worst Negative Slack (ns)
|
| 89 |
+
tns: float = 0.0 # Total Negative Slack (ns)
|
| 90 |
+
area_um2: float = 0.0
|
| 91 |
+
power_w: float = 0.0
|
| 92 |
+
congestion_pct: float = 0.0
|
| 93 |
+
drc_count: int = 0
|
| 94 |
+
lvs_ok: bool = False
|
| 95 |
+
formal_pass: bool = False
|
| 96 |
+
sim_pass: bool = False
|
| 97 |
+
|
| 98 |
+
def is_improving(self, previous: "ConvergenceMetrics") -> bool:
|
| 99 |
+
"""Check if metrics are trending in the right direction."""
|
| 100 |
+
improvements = 0
|
| 101 |
+
regressions = 0
|
| 102 |
+
|
| 103 |
+
if self.wns > previous.wns:
|
| 104 |
+
improvements += 1
|
| 105 |
+
elif self.wns < previous.wns:
|
| 106 |
+
regressions += 1
|
| 107 |
+
|
| 108 |
+
if self.drc_count < previous.drc_count:
|
| 109 |
+
improvements += 1
|
| 110 |
+
elif self.drc_count > previous.drc_count:
|
| 111 |
+
regressions += 1
|
| 112 |
+
|
| 113 |
+
if self.congestion_pct < previous.congestion_pct:
|
| 114 |
+
improvements += 1
|
| 115 |
+
elif self.congestion_pct > previous.congestion_pct:
|
| 116 |
+
regressions += 1
|
| 117 |
+
|
| 118 |
+
if self.sim_pass and not previous.sim_pass:
|
| 119 |
+
improvements += 1
|
| 120 |
+
if self.formal_pass and not previous.formal_pass:
|
| 121 |
+
improvements += 1
|
| 122 |
+
|
| 123 |
+
return improvements > regressions
|
| 124 |
+
|
| 125 |
+
def to_dict(self) -> dict:
|
| 126 |
+
return asdict(self)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# βββ Reflection Prompt Templates βββββββββββββββββββββββββββββββββββββ
|
| 130 |
+
|
| 131 |
+
SELF_REFLECT_PROMPT = """\
|
| 132 |
+
You are a Self-Reflecting VLSI Agent. A pipeline stage has FAILED.
|
| 133 |
+
|
| 134 |
+
Your job is to:
|
| 135 |
+
1. ANALYZE the failure β identify the root cause
|
| 136 |
+
2. REFLECT on whether this is a repeating pattern
|
| 137 |
+
3. PROPOSE concrete corrective actions
|
| 138 |
+
4. ASSESS the risk of each action
|
| 139 |
+
|
| 140 |
+
FAILURE CONTEXT:
|
| 141 |
+
Category: {category}
|
| 142 |
+
Error: {error_message}
|
| 143 |
+
Attempt: {attempt}/{max_attempts}
|
| 144 |
+
|
| 145 |
+
CONVERGENCE HISTORY:
|
| 146 |
+
{convergence_history}
|
| 147 |
+
|
| 148 |
+
PREVIOUS REFLECTIONS (do NOT repeat the same fix):
|
| 149 |
+
{previous_reflections}
|
| 150 |
+
|
| 151 |
+
CURRENT RTL SUMMARY:
|
| 152 |
+
{rtl_summary}
|
| 153 |
+
|
| 154 |
+
Respond in this EXACT format:
|
| 155 |
+
ROOT_CAUSE: <one sentence>
|
| 156 |
+
REFLECTION: <2-3 sentences about what went wrong and why>
|
| 157 |
+
ACTION_1: <type>|<description>|<target_file>
|
| 158 |
+
ACTION_2: <type>|<description>|<target_file>
|
| 159 |
+
RISK_ASSESSMENT: <one sentence about what could go wrong with these fixes>
|
| 160 |
+
CONVERGENCE_TREND: IMPROVING | STAGNATING | DIVERGING
|
| 161 |
+
"""
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# βββ Self-Reflect Pipeline βββββββββββββββββββββββββββββββββββββββββββ
|
| 165 |
+
|
| 166 |
+
class SelfReflectPipeline:
|
| 167 |
+
"""
|
| 168 |
+
Self-reflection retry pipeline for OpenLane synthesis convergence.
|
| 169 |
+
|
| 170 |
+
When any stage fails, the pipeline:
|
| 171 |
+
1. Categorizes the failure
|
| 172 |
+
2. Reflects on root cause (using LLM)
|
| 173 |
+
3. Proposes and applies corrective actions
|
| 174 |
+
4. Retries up to max_retries times
|
| 175 |
+
5. Tracks convergence to detect stagnation
|
| 176 |
+
|
| 177 |
+
The reflection history prevents the agent from repeating the same
|
| 178 |
+
failed approach β each retry must try something different.
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
def __init__(
|
| 182 |
+
self,
|
| 183 |
+
llm,
|
| 184 |
+
max_retries: int = 5,
|
| 185 |
+
verbose: bool = False,
|
| 186 |
+
on_reflection: Optional[Callable] = None, # Callback for UI events
|
| 187 |
+
):
|
| 188 |
+
self.llm = llm
|
| 189 |
+
self.max_retries = max_retries
|
| 190 |
+
self.verbose = verbose
|
| 191 |
+
self.on_reflection = on_reflection # Optional event sink
|
| 192 |
+
|
| 193 |
+
self.reflections: List[ReflectionEntry] = []
|
| 194 |
+
self.convergence_history: List[ConvergenceMetrics] = []
|
| 195 |
+
self.failure_fingerprints: Dict[str, int] = {}
|
| 196 |
+
|
| 197 |
+
def run_with_retry(
|
| 198 |
+
self,
|
| 199 |
+
stage_name: str,
|
| 200 |
+
action_fn: Callable[[], Tuple[bool, str, Dict[str, Any]]],
|
| 201 |
+
fix_fn: Callable[[CorrectionAction], bool],
|
| 202 |
+
rtl_summary: str = "",
|
| 203 |
+
) -> Tuple[bool, str, List[ReflectionEntry]]:
|
| 204 |
+
"""
|
| 205 |
+
Execute a pipeline stage with self-reflective retry.
|
| 206 |
+
|
| 207 |
+
Args:
|
| 208 |
+
stage_name: Human-readable stage name (e.g., "OpenLane Hardening")
|
| 209 |
+
action_fn: The stage function. Returns (success, error_msg, metrics_dict)
|
| 210 |
+
fix_fn: Function that applies a CorrectionAction. Returns True if applied
|
| 211 |
+
rtl_summary: Current RTL code summary for context
|
| 212 |
+
|
| 213 |
+
Returns:
|
| 214 |
+
(success, final_message, reflection_history)
|
| 215 |
+
"""
|
| 216 |
+
logger.info(f"[SelfReflect] Starting {stage_name} with up to {self.max_retries} retries")
|
| 217 |
+
|
| 218 |
+
for attempt in range(1, self.max_retries + 1):
|
| 219 |
+
logger.info(f"[SelfReflect] {stage_name} attempt {attempt}/{self.max_retries}")
|
| 220 |
+
|
| 221 |
+
# Execute the stage
|
| 222 |
+
try:
|
| 223 |
+
success, error_msg, metrics = action_fn()
|
| 224 |
+
except Exception as e:
|
| 225 |
+
success = False
|
| 226 |
+
error_msg = f"Stage exception: {str(e)}"
|
| 227 |
+
metrics = {}
|
| 228 |
+
|
| 229 |
+
# Track metrics
|
| 230 |
+
cm = self._parse_metrics(metrics)
|
| 231 |
+
self.convergence_history.append(cm)
|
| 232 |
+
|
| 233 |
+
if success:
|
| 234 |
+
logger.info(f"[SelfReflect] {stage_name} PASSED on attempt {attempt}")
|
| 235 |
+
return True, f"Passed on attempt {attempt}", self.reflections
|
| 236 |
+
|
| 237 |
+
# Check for repeating failure
|
| 238 |
+
fp = self._fingerprint(error_msg)
|
| 239 |
+
self.failure_fingerprints[fp] = self.failure_fingerprints.get(fp, 0) + 1
|
| 240 |
+
is_repeating = self.failure_fingerprints[fp] >= 2
|
| 241 |
+
|
| 242 |
+
# Categorize failure
|
| 243 |
+
category = self._categorize_failure(error_msg)
|
| 244 |
+
analysis = FailureAnalysis(
|
| 245 |
+
category=category,
|
| 246 |
+
error_message=error_msg[:2000],
|
| 247 |
+
root_cause="",
|
| 248 |
+
impact="",
|
| 249 |
+
similar_past_failures=self.failure_fingerprints[fp],
|
| 250 |
+
is_repeating=is_repeating,
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
# Self-reflect
|
| 254 |
+
reflection_entry = self._reflect(
|
| 255 |
+
analysis, attempt, rtl_summary
|
| 256 |
+
)
|
| 257 |
+
self.reflections.append(reflection_entry)
|
| 258 |
+
|
| 259 |
+
# Emit event for UI
|
| 260 |
+
if self.on_reflection:
|
| 261 |
+
try:
|
| 262 |
+
self.on_reflection({
|
| 263 |
+
"type": "self_reflection",
|
| 264 |
+
"stage": stage_name,
|
| 265 |
+
"attempt": attempt,
|
| 266 |
+
"category": category.value,
|
| 267 |
+
"reflection": reflection_entry.reflection,
|
| 268 |
+
"actions": [a.description for a in reflection_entry.proposed_actions],
|
| 269 |
+
})
|
| 270 |
+
except Exception:
|
| 271 |
+
pass
|
| 272 |
+
|
| 273 |
+
# Check convergence β if diverging after 3+ attempts, abort early
|
| 274 |
+
if attempt >= 3 and self._is_diverging():
|
| 275 |
+
logger.warning(f"[SelfReflect] Convergence diverging after {attempt} attempts β aborting")
|
| 276 |
+
return False, f"Diverging after {attempt} attempts β aborting", self.reflections
|
| 277 |
+
|
| 278 |
+
# Apply corrective actions
|
| 279 |
+
applied_any = False
|
| 280 |
+
for action in reflection_entry.proposed_actions:
|
| 281 |
+
try:
|
| 282 |
+
if fix_fn(action):
|
| 283 |
+
applied_any = True
|
| 284 |
+
logger.info(f"[SelfReflect] Applied fix: {action.description}")
|
| 285 |
+
except Exception as e:
|
| 286 |
+
logger.warning(f"[SelfReflect] Fix failed: {action.description}: {e}")
|
| 287 |
+
|
| 288 |
+
if not applied_any:
|
| 289 |
+
logger.warning(f"[SelfReflect] No fixes could be applied on attempt {attempt}")
|
| 290 |
+
|
| 291 |
+
return False, f"Failed after {self.max_retries} attempts", self.reflections
|
| 292 |
+
|
| 293 |
+
def _categorize_failure(self, error_msg: str) -> FailureCategory:
|
| 294 |
+
"""Categorize a failure based on error message patterns."""
|
| 295 |
+
msg = error_msg.lower()
|
| 296 |
+
|
| 297 |
+
patterns = [
|
| 298 |
+
(r"syntax error|parse error|unexpected token", FailureCategory.SYNTAX_ERROR),
|
| 299 |
+
(r"test failed|simulation.*fail|mismatch", FailureCategory.SIMULATION_FAIL),
|
| 300 |
+
(r"assert.*fail|property.*fail|formal.*fail", FailureCategory.FORMAL_PROPERTY_FAIL),
|
| 301 |
+
(r"synthesis.*error|synth.*fail|yosys.*error", FailureCategory.SYNTHESIS_ERROR),
|
| 302 |
+
(r"timing|slack|wns|tns|setup.*violation|hold.*violation", FailureCategory.TIMING_VIOLATION),
|
| 303 |
+
(r"congestion|overflow|routing.*fail", FailureCategory.ROUTING_CONGESTION),
|
| 304 |
+
(r"drc.*violation|design rule", FailureCategory.DRC_VIOLATION),
|
| 305 |
+
(r"lvs.*mismatch|layout.*vs.*schematic", FailureCategory.LVS_MISMATCH),
|
| 306 |
+
(r"area.*overflow|die.*area|utilization.*exceed", FailureCategory.AREA_OVERFLOW),
|
| 307 |
+
(r"power.*violation|power.*exceed|ir.*drop", FailureCategory.POWER_VIOLATION),
|
| 308 |
+
]
|
| 309 |
+
|
| 310 |
+
for pattern, category in patterns:
|
| 311 |
+
if re.search(pattern, msg):
|
| 312 |
+
return category
|
| 313 |
+
|
| 314 |
+
return FailureCategory.UNKNOWN
|
| 315 |
+
|
| 316 |
+
def _reflect(
|
| 317 |
+
self,
|
| 318 |
+
analysis: FailureAnalysis,
|
| 319 |
+
attempt: int,
|
| 320 |
+
rtl_summary: str,
|
| 321 |
+
) -> ReflectionEntry:
|
| 322 |
+
"""Run LLM self-reflection on the failure."""
|
| 323 |
+
# Build convergence history string
|
| 324 |
+
conv_lines = []
|
| 325 |
+
for i, cm in enumerate(self.convergence_history):
|
| 326 |
+
conv_lines.append(
|
| 327 |
+
f" [{i+1}] WNS={cm.wns:.3f}ns DRC={cm.drc_count} "
|
| 328 |
+
f"cong={cm.congestion_pct:.1f}% sim={'PASS' if cm.sim_pass else 'FAIL'}"
|
| 329 |
+
)
|
| 330 |
+
conv_str = "\n".join(conv_lines[-5:]) or " No history yet"
|
| 331 |
+
|
| 332 |
+
# Build previous reflections string
|
| 333 |
+
prev_lines = []
|
| 334 |
+
for r in self.reflections[-3:]:
|
| 335 |
+
prev_lines.append(
|
| 336 |
+
f" [Attempt {r.attempt}] {r.failure.category.value}: "
|
| 337 |
+
f"{r.reflection[:100]}... β {r.outcome}"
|
| 338 |
+
)
|
| 339 |
+
prev_str = "\n".join(prev_lines) or " No previous reflections"
|
| 340 |
+
|
| 341 |
+
prompt = SELF_REFLECT_PROMPT.format(
|
| 342 |
+
category=analysis.category.value,
|
| 343 |
+
error_message=analysis.error_message[:1500],
|
| 344 |
+
attempt=attempt,
|
| 345 |
+
max_attempts=self.max_retries,
|
| 346 |
+
convergence_history=conv_str,
|
| 347 |
+
previous_reflections=prev_str,
|
| 348 |
+
rtl_summary=rtl_summary[:3000],
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
# Call LLM for reflection
|
| 352 |
+
try:
|
| 353 |
+
from crewai import Agent, Task, Crew
|
| 354 |
+
|
| 355 |
+
agent = Agent(
|
| 356 |
+
role="Self-Reflecting VLSI Agent",
|
| 357 |
+
goal="Analyze the failure and propose corrective actions",
|
| 358 |
+
backstory=(
|
| 359 |
+
"You are an expert at diagnosing ASIC design failures. "
|
| 360 |
+
"You analyze error patterns, identify root causes, and propose "
|
| 361 |
+
"targeted fixes. You never repeat a fix that already failed."
|
| 362 |
+
),
|
| 363 |
+
llm=self.llm,
|
| 364 |
+
verbose=self.verbose,
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
task = Task(
|
| 368 |
+
description=prompt,
|
| 369 |
+
expected_output="ROOT_CAUSE, REFLECTION, ACTION_1, ACTION_2, RISK_ASSESSMENT, CONVERGENCE_TREND",
|
| 370 |
+
agent=agent,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
raw = str(Crew(agents=[agent], tasks=[task]).kickoff())
|
| 374 |
+
return self._parse_reflection(raw, analysis, attempt)
|
| 375 |
+
|
| 376 |
+
except Exception as e:
|
| 377 |
+
logger.warning(f"[SelfReflect] LLM reflection failed: {e}")
|
| 378 |
+
return self._fallback_reflection(analysis, attempt)
|
| 379 |
+
|
| 380 |
+
def _parse_reflection(
|
| 381 |
+
self, raw: str, analysis: FailureAnalysis, attempt: int
|
| 382 |
+
) -> ReflectionEntry:
|
| 383 |
+
"""Parse LLM reflection response."""
|
| 384 |
+
# Extract root cause
|
| 385 |
+
m = re.search(r'ROOT_CAUSE\s*:\s*(.+?)(?:\n|$)', raw, re.IGNORECASE)
|
| 386 |
+
root_cause = m.group(1).strip() if m else "Unknown"
|
| 387 |
+
analysis.root_cause = root_cause
|
| 388 |
+
|
| 389 |
+
# Extract reflection
|
| 390 |
+
m = re.search(r'REFLECTION\s*:\s*(.+?)(?=ACTION|\Z)', raw,
|
| 391 |
+
re.DOTALL | re.IGNORECASE)
|
| 392 |
+
reflection = m.group(1).strip() if m else "Analysis inconclusive"
|
| 393 |
+
|
| 394 |
+
# Extract actions
|
| 395 |
+
actions: List[CorrectionAction] = []
|
| 396 |
+
for i in (1, 2, 3):
|
| 397 |
+
m = re.search(rf'ACTION_{i}\s*:\s*(.+?)(?:\n|$)', raw, re.IGNORECASE)
|
| 398 |
+
if m:
|
| 399 |
+
parts = m.group(1).strip().split("|")
|
| 400 |
+
action_type = parts[0].strip() if len(parts) > 0 else "modify_rtl"
|
| 401 |
+
desc = parts[1].strip() if len(parts) > 1 else parts[0].strip()
|
| 402 |
+
target = parts[2].strip() if len(parts) > 2 else ""
|
| 403 |
+
actions.append(CorrectionAction(
|
| 404 |
+
action_type=action_type,
|
| 405 |
+
description=desc,
|
| 406 |
+
target_file=target,
|
| 407 |
+
))
|
| 408 |
+
|
| 409 |
+
if not actions:
|
| 410 |
+
# Fallback: generate a default action based on category
|
| 411 |
+
actions = self._default_actions(analysis.category)
|
| 412 |
+
|
| 413 |
+
return ReflectionEntry(
|
| 414 |
+
attempt=attempt,
|
| 415 |
+
failure=analysis,
|
| 416 |
+
reflection=reflection,
|
| 417 |
+
proposed_actions=actions,
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
def _fallback_reflection(
|
| 421 |
+
self, analysis: FailureAnalysis, attempt: int
|
| 422 |
+
) -> ReflectionEntry:
|
| 423 |
+
"""Generate fallback reflection when LLM is unavailable."""
|
| 424 |
+
actions = self._default_actions(analysis.category)
|
| 425 |
+
return ReflectionEntry(
|
| 426 |
+
attempt=attempt,
|
| 427 |
+
failure=analysis,
|
| 428 |
+
reflection=f"Fallback reflection: {analysis.category.value} detected",
|
| 429 |
+
proposed_actions=actions,
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
def _default_actions(self, category: FailureCategory) -> List[CorrectionAction]:
|
| 433 |
+
"""Generate default corrective actions based on failure category."""
|
| 434 |
+
defaults = {
|
| 435 |
+
FailureCategory.SYNTAX_ERROR: [
|
| 436 |
+
CorrectionAction("modify_rtl", "Fix Verilog syntax errors"),
|
| 437 |
+
],
|
| 438 |
+
FailureCategory.SIMULATION_FAIL: [
|
| 439 |
+
CorrectionAction("modify_rtl", "Fix RTL logic to match expected behavior"),
|
| 440 |
+
CorrectionAction("modify_rtl", "Adjust testbench timing and reset sequence"),
|
| 441 |
+
],
|
| 442 |
+
FailureCategory.TIMING_VIOLATION: [
|
| 443 |
+
CorrectionAction("adjust_config", "Increase clock period"),
|
| 444 |
+
CorrectionAction("modify_rtl", "Pipeline critical path"),
|
| 445 |
+
],
|
| 446 |
+
FailureCategory.ROUTING_CONGESTION: [
|
| 447 |
+
CorrectionAction("adjust_config", "Reduce utilization target"),
|
| 448 |
+
CorrectionAction("adjust_config", "Increase die area by 20%"),
|
| 449 |
+
],
|
| 450 |
+
FailureCategory.DRC_VIOLATION: [
|
| 451 |
+
CorrectionAction("adjust_config", "Reduce placement density"),
|
| 452 |
+
CorrectionAction("adjust_config", "Enable DRC repair scripts"),
|
| 453 |
+
],
|
| 454 |
+
FailureCategory.AREA_OVERFLOW: [
|
| 455 |
+
CorrectionAction("adjust_config", "Increase die area"),
|
| 456 |
+
CorrectionAction("modify_rtl", "Reduce design complexity"),
|
| 457 |
+
],
|
| 458 |
+
}
|
| 459 |
+
return defaults.get(category, [
|
| 460 |
+
CorrectionAction("modify_rtl", "General RTL fix based on error log"),
|
| 461 |
+
])
|
| 462 |
+
|
| 463 |
+
def _parse_metrics(self, metrics: Dict[str, Any]) -> ConvergenceMetrics:
|
| 464 |
+
"""Parse raw metrics dict into ConvergenceMetrics."""
|
| 465 |
+
return ConvergenceMetrics(
|
| 466 |
+
wns=float(metrics.get("wns", 0)),
|
| 467 |
+
tns=float(metrics.get("tns", 0)),
|
| 468 |
+
area_um2=float(metrics.get("area_um2", 0)),
|
| 469 |
+
power_w=float(metrics.get("power_w", 0)),
|
| 470 |
+
congestion_pct=float(metrics.get("congestion_pct", 0)),
|
| 471 |
+
drc_count=int(metrics.get("drc_count", 0)),
|
| 472 |
+
lvs_ok=bool(metrics.get("lvs_ok", False)),
|
| 473 |
+
formal_pass=bool(metrics.get("formal_pass", False)),
|
| 474 |
+
sim_pass=bool(metrics.get("sim_pass", False)),
|
| 475 |
+
)
|
| 476 |
+
|
| 477 |
+
def _fingerprint(self, error_msg: str) -> str:
|
| 478 |
+
"""Generate a fingerprint for deduplicating errors."""
|
| 479 |
+
# Normalize: remove numbers, paths, timestamps
|
| 480 |
+
normalized = re.sub(r'\d+', 'N', error_msg[:500])
|
| 481 |
+
normalized = re.sub(r'/[\w/]+\.', 'FILE.', normalized)
|
| 482 |
+
return hashlib.sha256(normalized.encode()).hexdigest()[:16]
|
| 483 |
+
|
| 484 |
+
def _is_diverging(self) -> bool:
|
| 485 |
+
"""Check if the convergence history shows divergence (getting worse)."""
|
| 486 |
+
if len(self.convergence_history) < 3:
|
| 487 |
+
return False
|
| 488 |
+
|
| 489 |
+
recent = self.convergence_history[-3:]
|
| 490 |
+
|
| 491 |
+
# Check if DRC count is increasing
|
| 492 |
+
if all(recent[i].drc_count >= recent[i-1].drc_count
|
| 493 |
+
for i in range(1, len(recent))) and recent[-1].drc_count > 0:
|
| 494 |
+
return True
|
| 495 |
+
|
| 496 |
+
# Check if WNS is getting worse
|
| 497 |
+
if all(recent[i].wns <= recent[i-1].wns
|
| 498 |
+
for i in range(1, len(recent))) and recent[-1].wns < -1.0:
|
| 499 |
+
return True
|
| 500 |
+
|
| 501 |
+
return False
|
| 502 |
+
|
| 503 |
+
def get_summary(self) -> str:
|
| 504 |
+
"""Get a human-readable summary of the reflection history."""
|
| 505 |
+
if not self.reflections:
|
| 506 |
+
return "No reflections recorded."
|
| 507 |
+
|
| 508 |
+
lines = [f"Self-Reflection Summary ({len(self.reflections)} attempts):"]
|
| 509 |
+
for r in self.reflections:
|
| 510 |
+
lines.append(
|
| 511 |
+
f" [{r.attempt}] {r.failure.category.value}: {r.reflection[:80]}... "
|
| 512 |
+
f"β {r.outcome or 'pending'}"
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
if self.convergence_history:
|
| 516 |
+
last = self.convergence_history[-1]
|
| 517 |
+
lines.append(
|
| 518 |
+
f" Latest metrics: WNS={last.wns:.3f} DRC={last.drc_count} "
|
| 519 |
+
f"cong={last.congestion_pct:.1f}%"
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
return "\n".join(lines)
|
src/agentic/core/waveform_expert.py
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Coder & Waveform Expert Module β VerilogCoder Logic
|
| 3 |
+
=====================================================
|
| 4 |
+
|
| 5 |
+
Based on: VerilogCoder (AST-based Waveform Tracing)
|
| 6 |
+
|
| 7 |
+
When an Icarus Verilog simulation fails, this module:
|
| 8 |
+
1. Parses the generated RTL into an AST using Pyverilog.
|
| 9 |
+
2. Parses the VCD waveform to find the failing signal/time.
|
| 10 |
+
3. Back-traces the failing signal's RVALUE in the AST to identify
|
| 11 |
+
exactly which line of code caused the mismatch.
|
| 12 |
+
4. Produces a structured diagnosis for the LLM to fix.
|
| 13 |
+
|
| 14 |
+
Tools used: Pyverilog (AST), Icarus Verilog (simulation), VCD parsing.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import re
|
| 19 |
+
import json
|
| 20 |
+
import logging
|
| 21 |
+
import subprocess
|
| 22 |
+
import tempfile
|
| 23 |
+
from dataclasses import dataclass, field, asdict
|
| 24 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 25 |
+
|
| 26 |
+
logger = logging.getLogger(__name__)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# βββ VCD Waveform Parser (pure Python, no external deps) βββββββββββββ
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class VCDSignalChange:
|
| 33 |
+
"""A single value change in a VCD trace."""
|
| 34 |
+
time: int
|
| 35 |
+
signal_id: str
|
| 36 |
+
signal_name: str
|
| 37 |
+
value: str
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class VCDSignal:
|
| 42 |
+
"""Metadata for a VCD signal."""
|
| 43 |
+
id: str
|
| 44 |
+
name: str
|
| 45 |
+
width: int
|
| 46 |
+
scope: str
|
| 47 |
+
changes: List[VCDSignalChange] = field(default_factory=list)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class VCDParser:
|
| 51 |
+
"""
|
| 52 |
+
Lightweight VCD parser β extracts signal transitions from .vcd files.
|
| 53 |
+
No external dependencies required.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __init__(self):
|
| 57 |
+
self.signals: Dict[str, VCDSignal] = {} # id β VCDSignal
|
| 58 |
+
self.name_map: Dict[str, str] = {} # full_name β id
|
| 59 |
+
self.timescale: str = ""
|
| 60 |
+
self.current_time: int = 0
|
| 61 |
+
|
| 62 |
+
def parse(self, vcd_path: str) -> Dict[str, VCDSignal]:
|
| 63 |
+
"""Parse a VCD file and return signal map."""
|
| 64 |
+
if not os.path.exists(vcd_path):
|
| 65 |
+
logger.warning(f"VCD file not found: {vcd_path}")
|
| 66 |
+
return {}
|
| 67 |
+
|
| 68 |
+
self.signals.clear()
|
| 69 |
+
self.name_map.clear()
|
| 70 |
+
scope_stack: List[str] = []
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
with open(vcd_path, "r", errors="replace") as f:
|
| 74 |
+
in_defs = True
|
| 75 |
+
for line in f:
|
| 76 |
+
line = line.strip()
|
| 77 |
+
if not line:
|
| 78 |
+
continue
|
| 79 |
+
|
| 80 |
+
if in_defs:
|
| 81 |
+
if line.startswith("$timescale"):
|
| 82 |
+
self.timescale = line.replace("$timescale", "").replace("$end", "").strip()
|
| 83 |
+
elif line.startswith("$scope"):
|
| 84 |
+
parts = line.split()
|
| 85 |
+
if len(parts) >= 3:
|
| 86 |
+
scope_stack.append(parts[2])
|
| 87 |
+
elif line.startswith("$upscope"):
|
| 88 |
+
if scope_stack:
|
| 89 |
+
scope_stack.pop()
|
| 90 |
+
elif line.startswith("$var"):
|
| 91 |
+
self._parse_var(line, scope_stack)
|
| 92 |
+
elif line.startswith("$enddefinitions"):
|
| 93 |
+
in_defs = False
|
| 94 |
+
else:
|
| 95 |
+
# Value change section
|
| 96 |
+
if line.startswith("#"):
|
| 97 |
+
try:
|
| 98 |
+
self.current_time = int(line[1:])
|
| 99 |
+
except ValueError:
|
| 100 |
+
pass
|
| 101 |
+
elif line.startswith("b") or line.startswith("B"):
|
| 102 |
+
# Multi-bit: bVALUE ID
|
| 103 |
+
parts = line.split()
|
| 104 |
+
if len(parts) >= 2:
|
| 105 |
+
val = parts[0][1:] # strip 'b'
|
| 106 |
+
sig_id = parts[1]
|
| 107 |
+
self._record_change(sig_id, val)
|
| 108 |
+
elif len(line) >= 2 and line[0] in "01xXzZ":
|
| 109 |
+
# Single-bit: VALUE_ID (e.g., "1!")
|
| 110 |
+
val = line[0]
|
| 111 |
+
sig_id = line[1:]
|
| 112 |
+
self._record_change(sig_id, val)
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
logger.error(f"VCD parse error: {e}")
|
| 116 |
+
|
| 117 |
+
return self.signals
|
| 118 |
+
|
| 119 |
+
def _parse_var(self, line: str, scope_stack: List[str]):
|
| 120 |
+
"""Parse a $var line."""
|
| 121 |
+
# $var wire 8 ! data [7:0] $end
|
| 122 |
+
parts = line.split()
|
| 123 |
+
if len(parts) < 5:
|
| 124 |
+
return
|
| 125 |
+
var_type = parts[1]
|
| 126 |
+
try:
|
| 127 |
+
width = int(parts[2])
|
| 128 |
+
except ValueError:
|
| 129 |
+
width = 1
|
| 130 |
+
sig_id = parts[3]
|
| 131 |
+
name = parts[4]
|
| 132 |
+
|
| 133 |
+
full_scope = ".".join(scope_stack)
|
| 134 |
+
full_name = f"{full_scope}.{name}" if full_scope else name
|
| 135 |
+
|
| 136 |
+
sig = VCDSignal(id=sig_id, name=name, width=width, scope=full_scope)
|
| 137 |
+
self.signals[sig_id] = sig
|
| 138 |
+
self.name_map[full_name] = sig_id
|
| 139 |
+
self.name_map[name] = sig_id # Short name lookup
|
| 140 |
+
|
| 141 |
+
def _record_change(self, sig_id: str, value: str):
|
| 142 |
+
if sig_id in self.signals:
|
| 143 |
+
self.signals[sig_id].changes.append(
|
| 144 |
+
VCDSignalChange(
|
| 145 |
+
time=self.current_time,
|
| 146 |
+
signal_id=sig_id,
|
| 147 |
+
signal_name=self.signals[sig_id].name,
|
| 148 |
+
value=value,
|
| 149 |
+
)
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
def get_signal_value_at(self, signal_name: str, time: int) -> Optional[str]:
|
| 153 |
+
"""Get the value of a signal at a specific time."""
|
| 154 |
+
sig_id = self.name_map.get(signal_name)
|
| 155 |
+
if not sig_id or sig_id not in self.signals:
|
| 156 |
+
return None
|
| 157 |
+
sig = self.signals[sig_id]
|
| 158 |
+
last_val = None
|
| 159 |
+
for ch in sig.changes:
|
| 160 |
+
if ch.time <= time:
|
| 161 |
+
last_val = ch.value
|
| 162 |
+
else:
|
| 163 |
+
break
|
| 164 |
+
return last_val
|
| 165 |
+
|
| 166 |
+
def find_first_mismatch(self, signal_name: str, expected_values: List[Tuple[int, str]]
|
| 167 |
+
) -> Optional[Tuple[int, str, str]]:
|
| 168 |
+
"""Compare signal against expected values; return first mismatch (time, expected, actual)."""
|
| 169 |
+
for time, expected in expected_values:
|
| 170 |
+
actual = self.get_signal_value_at(signal_name, time)
|
| 171 |
+
if actual is None:
|
| 172 |
+
return (time, expected, "UNDEFINED")
|
| 173 |
+
# Normalize for comparison
|
| 174 |
+
if actual.replace("0", "").replace("1", "") == "" and expected.replace("0", "").replace("1", "") == "":
|
| 175 |
+
if int(actual, 2) != int(expected, 2):
|
| 176 |
+
return (time, expected, actual)
|
| 177 |
+
elif actual != expected:
|
| 178 |
+
return (time, expected, actual)
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# βββ AST Back-Tracer (Pyverilog-based) βββββββββββββββββββββββββββββββ
|
| 183 |
+
|
| 184 |
+
@dataclass
|
| 185 |
+
class ASTTraceResult:
|
| 186 |
+
"""Result of back-tracing a signal through the AST."""
|
| 187 |
+
signal_name: str
|
| 188 |
+
source_file: str
|
| 189 |
+
source_line: int
|
| 190 |
+
assignment_type: str # "always_ff", "always_comb", "assign", "unknown"
|
| 191 |
+
rvalue_expression: str # The RHS expression driving this signal
|
| 192 |
+
driving_signals: List[str] # Signals on the RHS (dependencies)
|
| 193 |
+
context_lines: str # Surrounding code context
|
| 194 |
+
fsm_state: str = "" # If signal is state-dependent, which state
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
class ASTBackTracer:
|
| 198 |
+
"""
|
| 199 |
+
Uses Pyverilog to parse RTL and trace signal assignments.
|
| 200 |
+
|
| 201 |
+
When a simulation mismatch is detected on a signal, this tracer
|
| 202 |
+
finds the exact Verilog line(s) that drive it and extracts the
|
| 203 |
+
RVALUE expression for root-cause analysis.
|
| 204 |
+
"""
|
| 205 |
+
|
| 206 |
+
def __init__(self):
|
| 207 |
+
self._ast = None
|
| 208 |
+
self._source_lines: Dict[str, List[str]] = {} # file β lines
|
| 209 |
+
self._assignments: List[Dict[str, Any]] = []
|
| 210 |
+
|
| 211 |
+
def parse_rtl(self, rtl_path: str) -> bool:
|
| 212 |
+
"""Parse an RTL file and build the assignment database."""
|
| 213 |
+
if not os.path.exists(rtl_path):
|
| 214 |
+
logger.error(f"RTL file not found: {rtl_path}")
|
| 215 |
+
return False
|
| 216 |
+
|
| 217 |
+
# Load source for line references
|
| 218 |
+
try:
|
| 219 |
+
with open(rtl_path, "r") as f:
|
| 220 |
+
self._source_lines[rtl_path] = f.readlines()
|
| 221 |
+
except Exception as e:
|
| 222 |
+
logger.error(f"Failed to read RTL: {e}")
|
| 223 |
+
return False
|
| 224 |
+
|
| 225 |
+
# Try Pyverilog AST parse
|
| 226 |
+
try:
|
| 227 |
+
from pyverilog.vparser.parser import parse as pyverilog_parse
|
| 228 |
+
ast, _ = pyverilog_parse([rtl_path])
|
| 229 |
+
self._ast = ast
|
| 230 |
+
self._extract_assignments_from_ast(ast, rtl_path)
|
| 231 |
+
logger.info(f"[AST] Parsed {rtl_path}: {len(self._assignments)} assignments found")
|
| 232 |
+
return True
|
| 233 |
+
except ImportError:
|
| 234 |
+
logger.warning("Pyverilog not available β falling back to regex-based tracing")
|
| 235 |
+
self._extract_assignments_regex(rtl_path)
|
| 236 |
+
return True
|
| 237 |
+
except Exception as e:
|
| 238 |
+
logger.warning(f"Pyverilog parse failed ({e}) β falling back to regex")
|
| 239 |
+
self._extract_assignments_regex(rtl_path)
|
| 240 |
+
return True
|
| 241 |
+
|
| 242 |
+
def _extract_assignments_from_ast(self, ast, source_file: str):
|
| 243 |
+
"""Walk the Pyverilog AST and extract all assignments."""
|
| 244 |
+
try:
|
| 245 |
+
from pyverilog.vparser.ast import (
|
| 246 |
+
Assign, Always, IfStatement, CaseStatement,
|
| 247 |
+
NonblockingSubstitution, BlockingSubstitution,
|
| 248 |
+
Lvalue, Rvalue, Identifier
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def _get_identifiers(node) -> List[str]:
|
| 252 |
+
"""Recursively extract all Identifier names from an AST node."""
|
| 253 |
+
ids = []
|
| 254 |
+
if isinstance(node, Identifier):
|
| 255 |
+
ids.append(node.name)
|
| 256 |
+
if hasattr(node, 'children'):
|
| 257 |
+
for child in node.children():
|
| 258 |
+
ids.extend(_get_identifiers(child))
|
| 259 |
+
return ids
|
| 260 |
+
|
| 261 |
+
def _node_to_str(node) -> str:
|
| 262 |
+
"""Best-effort conversion of AST node to string."""
|
| 263 |
+
if hasattr(node, 'name'):
|
| 264 |
+
return node.name
|
| 265 |
+
try:
|
| 266 |
+
return str(node)
|
| 267 |
+
except Exception:
|
| 268 |
+
return repr(node)
|
| 269 |
+
|
| 270 |
+
def _walk(node, context: str = "unknown"):
|
| 271 |
+
if node is None:
|
| 272 |
+
return
|
| 273 |
+
|
| 274 |
+
if isinstance(node, Assign):
|
| 275 |
+
lv = _node_to_str(node.left) if node.left else "?"
|
| 276 |
+
rv = _node_to_str(node.right) if node.right else "?"
|
| 277 |
+
deps = _get_identifiers(node.right) if node.right else []
|
| 278 |
+
lineno = getattr(node, 'lineno', 0)
|
| 279 |
+
self._assignments.append({
|
| 280 |
+
"signal": lv,
|
| 281 |
+
"rvalue": rv,
|
| 282 |
+
"type": "assign",
|
| 283 |
+
"line": lineno,
|
| 284 |
+
"file": source_file,
|
| 285 |
+
"deps": deps,
|
| 286 |
+
})
|
| 287 |
+
|
| 288 |
+
elif isinstance(node, (NonblockingSubstitution, BlockingSubstitution)):
|
| 289 |
+
atype = "always_ff" if isinstance(node, NonblockingSubstitution) else "always_comb"
|
| 290 |
+
lv = _node_to_str(node.left) if node.left else "?"
|
| 291 |
+
rv = _node_to_str(node.right) if node.right else "?"
|
| 292 |
+
deps = _get_identifiers(node.right) if node.right else []
|
| 293 |
+
lineno = getattr(node, 'lineno', 0)
|
| 294 |
+
self._assignments.append({
|
| 295 |
+
"signal": lv,
|
| 296 |
+
"rvalue": rv,
|
| 297 |
+
"type": context if context != "unknown" else atype,
|
| 298 |
+
"line": lineno,
|
| 299 |
+
"file": source_file,
|
| 300 |
+
"deps": deps,
|
| 301 |
+
})
|
| 302 |
+
|
| 303 |
+
if hasattr(node, 'children'):
|
| 304 |
+
new_ctx = context
|
| 305 |
+
if isinstance(node, Always):
|
| 306 |
+
# Detect always_ff vs always_comb from sensitivity
|
| 307 |
+
sens = _node_to_str(node.sens_list) if hasattr(node, 'sens_list') and node.sens_list else ""
|
| 308 |
+
if "posedge" in sens or "negedge" in sens:
|
| 309 |
+
new_ctx = "always_ff"
|
| 310 |
+
else:
|
| 311 |
+
new_ctx = "always_comb"
|
| 312 |
+
for child in node.children():
|
| 313 |
+
_walk(child, new_ctx)
|
| 314 |
+
|
| 315 |
+
_walk(ast)
|
| 316 |
+
|
| 317 |
+
except Exception as e:
|
| 318 |
+
logger.warning(f"AST walk failed: {e}")
|
| 319 |
+
self._extract_assignments_regex(source_file)
|
| 320 |
+
|
| 321 |
+
def _extract_assignments_regex(self, rtl_path: str):
|
| 322 |
+
"""Fallback: regex-based assignment extraction."""
|
| 323 |
+
lines = self._source_lines.get(rtl_path, [])
|
| 324 |
+
in_always_ff = False
|
| 325 |
+
in_always_comb = False
|
| 326 |
+
|
| 327 |
+
for i, line in enumerate(lines, 1):
|
| 328 |
+
stripped = line.strip()
|
| 329 |
+
|
| 330 |
+
# Track always block context
|
| 331 |
+
if re.search(r'always_ff\b|always\s*@\s*\(\s*posedge', stripped):
|
| 332 |
+
in_always_ff = True
|
| 333 |
+
in_always_comb = False
|
| 334 |
+
elif re.search(r'always_comb\b|always\s*@\s*\(\*\)', stripped):
|
| 335 |
+
in_always_comb = True
|
| 336 |
+
in_always_ff = False
|
| 337 |
+
elif stripped.startswith("end") and (in_always_ff or in_always_comb):
|
| 338 |
+
in_always_ff = False
|
| 339 |
+
in_always_comb = False
|
| 340 |
+
|
| 341 |
+
# Continuous assign
|
| 342 |
+
m = re.match(r'\s*assign\s+(\w+)\s*=\s*(.+?)\s*;', stripped)
|
| 343 |
+
if m:
|
| 344 |
+
sig, rval = m.groups()
|
| 345 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 346 |
+
self._assignments.append({
|
| 347 |
+
"signal": sig, "rvalue": rval, "type": "assign",
|
| 348 |
+
"line": i, "file": rtl_path, "deps": deps,
|
| 349 |
+
})
|
| 350 |
+
continue
|
| 351 |
+
|
| 352 |
+
# Non-blocking (<=)
|
| 353 |
+
m = re.match(r'\s*(\w+)\s*<=\s*(.+?)\s*;', stripped)
|
| 354 |
+
if m:
|
| 355 |
+
sig, rval = m.groups()
|
| 356 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 357 |
+
self._assignments.append({
|
| 358 |
+
"signal": sig, "rvalue": rval,
|
| 359 |
+
"type": "always_ff" if in_always_ff else "always_comb",
|
| 360 |
+
"line": i, "file": rtl_path, "deps": deps,
|
| 361 |
+
})
|
| 362 |
+
continue
|
| 363 |
+
|
| 364 |
+
# Blocking (=) inside always
|
| 365 |
+
if in_always_comb or in_always_ff:
|
| 366 |
+
m = re.match(r'\s*(\w+)\s*=\s*(.+?)\s*;', stripped)
|
| 367 |
+
if m:
|
| 368 |
+
sig, rval = m.groups()
|
| 369 |
+
deps = re.findall(r'\b([a-zA-Z_]\w*)\b', rval)
|
| 370 |
+
self._assignments.append({
|
| 371 |
+
"signal": sig, "rvalue": rval,
|
| 372 |
+
"type": "always_comb" if in_always_comb else "always_ff",
|
| 373 |
+
"line": i, "file": rtl_path, "deps": deps,
|
| 374 |
+
})
|
| 375 |
+
|
| 376 |
+
def trace_signal(self, signal_name: str, max_depth: int = 5) -> List[ASTTraceResult]:
|
| 377 |
+
"""
|
| 378 |
+
Back-trace a signal through the assignment graph.
|
| 379 |
+
|
| 380 |
+
Returns all assignments that drive `signal_name`, plus recursive
|
| 381 |
+
traces of the driving signals (up to max_depth).
|
| 382 |
+
"""
|
| 383 |
+
results: List[ASTTraceResult] = []
|
| 384 |
+
visited: set = set()
|
| 385 |
+
self._trace_recursive(signal_name, results, visited, 0, max_depth)
|
| 386 |
+
return results
|
| 387 |
+
|
| 388 |
+
def _trace_recursive(self, sig: str, results: List[ASTTraceResult],
|
| 389 |
+
visited: set, depth: int, max_depth: int):
|
| 390 |
+
if depth > max_depth or sig in visited:
|
| 391 |
+
return
|
| 392 |
+
visited.add(sig)
|
| 393 |
+
|
| 394 |
+
for asgn in self._assignments:
|
| 395 |
+
if asgn["signal"] == sig:
|
| 396 |
+
# Get context lines
|
| 397 |
+
src_lines = self._source_lines.get(asgn["file"], [])
|
| 398 |
+
line_num = asgn["line"]
|
| 399 |
+
start = max(0, line_num - 4)
|
| 400 |
+
end = min(len(src_lines), line_num + 3)
|
| 401 |
+
context = "".join(src_lines[start:end])
|
| 402 |
+
|
| 403 |
+
results.append(ASTTraceResult(
|
| 404 |
+
signal_name=sig,
|
| 405 |
+
source_file=asgn["file"],
|
| 406 |
+
source_line=line_num,
|
| 407 |
+
assignment_type=asgn["type"],
|
| 408 |
+
rvalue_expression=asgn["rvalue"],
|
| 409 |
+
driving_signals=asgn["deps"],
|
| 410 |
+
context_lines=context,
|
| 411 |
+
))
|
| 412 |
+
|
| 413 |
+
# Recurse into dependencies
|
| 414 |
+
for dep in asgn["deps"]:
|
| 415 |
+
self._trace_recursive(dep, results, visited, depth + 1, max_depth)
|
| 416 |
+
|
| 417 |
+
def get_all_signals(self) -> List[str]:
|
| 418 |
+
"""Return all signal names found in the AST."""
|
| 419 |
+
return list(set(a["signal"] for a in self._assignments))
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
# βββ Waveform Expert Module ββββββββββββββββββββββββββββββββββββββββββ
|
| 423 |
+
|
| 424 |
+
@dataclass
|
| 425 |
+
class WaveformDiagnosis:
|
| 426 |
+
"""Structured diagnosis from waveform + AST analysis."""
|
| 427 |
+
failing_signal: str
|
| 428 |
+
mismatch_time: int
|
| 429 |
+
expected_value: str
|
| 430 |
+
actual_value: str
|
| 431 |
+
root_cause_traces: List[ASTTraceResult]
|
| 432 |
+
suggested_fix_area: str # Human-readable location
|
| 433 |
+
diagnosis_summary: str # Natural language summary for LLM
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
class WaveformExpertModule:
|
| 437 |
+
"""
|
| 438 |
+
VerilogCoder-style AST-based Waveform Tracing Tool.
|
| 439 |
+
|
| 440 |
+
Combines VCD waveform analysis with Pyverilog AST back-tracing
|
| 441 |
+
to produce precise, line-level root-cause diagnosis when
|
| 442 |
+
Icarus Verilog simulations fail.
|
| 443 |
+
|
| 444 |
+
Pipeline:
|
| 445 |
+
1. Parse VCD β find failing signal + mismatch time
|
| 446 |
+
2. Parse RTL AST β build assignment dependency graph
|
| 447 |
+
3. Back-trace failing signal's RVALUE through the graph
|
| 448 |
+
4. Produce structured WaveformDiagnosis for the fixer agent
|
| 449 |
+
"""
|
| 450 |
+
|
| 451 |
+
def __init__(self):
|
| 452 |
+
self.vcd_parser = VCDParser()
|
| 453 |
+
self.ast_tracer = ASTBackTracer()
|
| 454 |
+
|
| 455 |
+
def analyze_failure(
|
| 456 |
+
self,
|
| 457 |
+
rtl_path: str,
|
| 458 |
+
vcd_path: str,
|
| 459 |
+
sim_log: str,
|
| 460 |
+
design_name: str,
|
| 461 |
+
) -> Optional[WaveformDiagnosis]:
|
| 462 |
+
"""
|
| 463 |
+
Full waveform + AST analysis pipeline.
|
| 464 |
+
|
| 465 |
+
Args:
|
| 466 |
+
rtl_path: Path to the RTL .v file
|
| 467 |
+
vcd_path: Path to the simulation .vcd file
|
| 468 |
+
sim_log: Text output from iverilog/vvp simulation
|
| 469 |
+
design_name: Module name
|
| 470 |
+
|
| 471 |
+
Returns:
|
| 472 |
+
WaveformDiagnosis with traces, or None if analysis not possible.
|
| 473 |
+
"""
|
| 474 |
+
logger.info(f"[WaveformExpert] Analyzing failure for {design_name}")
|
| 475 |
+
|
| 476 |
+
# Step 1: Parse VCD
|
| 477 |
+
signals = self.vcd_parser.parse(vcd_path)
|
| 478 |
+
if not signals:
|
| 479 |
+
logger.warning("[WaveformExpert] No signals found in VCD")
|
| 480 |
+
return self._fallback_from_log(sim_log, rtl_path)
|
| 481 |
+
|
| 482 |
+
# Step 2: Parse RTL AST
|
| 483 |
+
self.ast_tracer.parse_rtl(rtl_path)
|
| 484 |
+
|
| 485 |
+
# Step 3: Identify failing signal from simulation log
|
| 486 |
+
failing_sig, mismatch_time, expected, actual = self._extract_failure_from_log(sim_log, signals)
|
| 487 |
+
if not failing_sig:
|
| 488 |
+
logger.warning("[WaveformExpert] Could not identify failing signal from log")
|
| 489 |
+
return self._fallback_from_log(sim_log, rtl_path)
|
| 490 |
+
|
| 491 |
+
# Step 4: Back-trace through AST
|
| 492 |
+
traces = self.ast_tracer.trace_signal(failing_sig)
|
| 493 |
+
|
| 494 |
+
# Step 5: Build diagnosis
|
| 495 |
+
if traces:
|
| 496 |
+
primary = traces[0]
|
| 497 |
+
fix_area = f"{primary.source_file}:{primary.source_line} ({primary.assignment_type})"
|
| 498 |
+
else:
|
| 499 |
+
fix_area = "Could not trace β check module ports and combinational logic"
|
| 500 |
+
|
| 501 |
+
summary = self._build_diagnosis_summary(
|
| 502 |
+
failing_sig, mismatch_time, expected, actual, traces
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
return WaveformDiagnosis(
|
| 506 |
+
failing_signal=failing_sig,
|
| 507 |
+
mismatch_time=mismatch_time,
|
| 508 |
+
expected_value=expected,
|
| 509 |
+
actual_value=actual,
|
| 510 |
+
root_cause_traces=traces,
|
| 511 |
+
suggested_fix_area=fix_area,
|
| 512 |
+
diagnosis_summary=summary,
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
+
def _extract_failure_from_log(
|
| 516 |
+
self, sim_log: str, signals: Dict[str, VCDSignal]
|
| 517 |
+
) -> Tuple[str, int, str, str]:
|
| 518 |
+
"""
|
| 519 |
+
Extract the failing signal, time, expected, and actual values from sim output.
|
| 520 |
+
|
| 521 |
+
Handles common testbench output patterns:
|
| 522 |
+
- "ERROR: signal_name expected X got Y at time T"
|
| 523 |
+
- "MISMATCH at T: expected=X actual=Y"
|
| 524 |
+
- "$display output with expected/got"
|
| 525 |
+
"""
|
| 526 |
+
if not sim_log:
|
| 527 |
+
return "", 0, "", ""
|
| 528 |
+
|
| 529 |
+
# Pattern 1: ERROR: <signal> expected <exp> got <act> at time <t>
|
| 530 |
+
m = re.search(
|
| 531 |
+
r'(?:ERROR|FAIL|MISMATCH)[:\s]+(\w+)\s+expected\s+(\S+)\s+got\s+(\S+)\s+(?:at\s+)?(?:time\s+)?(\d+)',
|
| 532 |
+
sim_log, re.IGNORECASE
|
| 533 |
+
)
|
| 534 |
+
if m:
|
| 535 |
+
return m.group(1), int(m.group(4)), m.group(2), m.group(3)
|
| 536 |
+
|
| 537 |
+
# Pattern 2: "expected <exp> but got <act>" with signal context
|
| 538 |
+
m = re.search(
|
| 539 |
+
r'(\w+)\s*:\s*expected\s+(\S+)\s+(?:but\s+)?got\s+(\S+)',
|
| 540 |
+
sim_log, re.IGNORECASE
|
| 541 |
+
)
|
| 542 |
+
if m:
|
| 543 |
+
return m.group(1), 0, m.group(2), m.group(3)
|
| 544 |
+
|
| 545 |
+
# Pattern 3: "MISMATCH at <time>: expected=<exp> actual=<act>"
|
| 546 |
+
m = re.search(
|
| 547 |
+
r'MISMATCH\s+at\s+(\d+).*expected[=:\s]+(\S+).*actual[=:\s]+(\S+)',
|
| 548 |
+
sim_log, re.IGNORECASE
|
| 549 |
+
)
|
| 550 |
+
if m:
|
| 551 |
+
return "", int(m.group(1)), m.group(2), m.group(3)
|
| 552 |
+
|
| 553 |
+
# Pattern 4: "TEST FAILED" β pick the first non-clk signal with x/z
|
| 554 |
+
for sig_id, sig in signals.items():
|
| 555 |
+
if sig.name in ("clk", "rst_n", "reset"):
|
| 556 |
+
continue
|
| 557 |
+
for ch in sig.changes:
|
| 558 |
+
if 'x' in ch.value.lower() or 'z' in ch.value.lower():
|
| 559 |
+
return sig.name, ch.time, "defined", ch.value
|
| 560 |
+
|
| 561 |
+
return "", 0, "", ""
|
| 562 |
+
|
| 563 |
+
def _fallback_from_log(self, sim_log: str, rtl_path: str) -> Optional[WaveformDiagnosis]:
|
| 564 |
+
"""Fallback diagnosis when VCD isn't available or parseable."""
|
| 565 |
+
if not sim_log:
|
| 566 |
+
return None
|
| 567 |
+
|
| 568 |
+
# Try to at least identify error lines
|
| 569 |
+
error_lines = [l for l in sim_log.split("\n")
|
| 570 |
+
if re.search(r'error|fail|mismatch', l, re.IGNORECASE)]
|
| 571 |
+
|
| 572 |
+
if not error_lines:
|
| 573 |
+
return None
|
| 574 |
+
|
| 575 |
+
return WaveformDiagnosis(
|
| 576 |
+
failing_signal="unknown",
|
| 577 |
+
mismatch_time=0,
|
| 578 |
+
expected_value="unknown",
|
| 579 |
+
actual_value="unknown",
|
| 580 |
+
root_cause_traces=[],
|
| 581 |
+
suggested_fix_area="See simulation log",
|
| 582 |
+
diagnosis_summary=(
|
| 583 |
+
"VCD/AST analysis unavailable. Raw errors from simulation:\n"
|
| 584 |
+
+ "\n".join(error_lines[:10])
|
| 585 |
+
),
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
+
def _build_diagnosis_summary(
|
| 589 |
+
self,
|
| 590 |
+
sig: str,
|
| 591 |
+
time: int,
|
| 592 |
+
expected: str,
|
| 593 |
+
actual: str,
|
| 594 |
+
traces: List[ASTTraceResult],
|
| 595 |
+
) -> str:
|
| 596 |
+
"""Build a human-readable diagnosis for the LLM fixer agent."""
|
| 597 |
+
parts = []
|
| 598 |
+
parts.append(f"SIGNAL MISMATCH: '{sig}' at time {time}ns")
|
| 599 |
+
parts.append(f" Expected: {expected}")
|
| 600 |
+
parts.append(f" Actual: {actual}")
|
| 601 |
+
parts.append("")
|
| 602 |
+
|
| 603 |
+
if traces:
|
| 604 |
+
parts.append("AST BACK-TRACE (root cause chain):")
|
| 605 |
+
for i, tr in enumerate(traces):
|
| 606 |
+
parts.append(
|
| 607 |
+
f" [{i+1}] {tr.signal_name} β {tr.rvalue_expression}"
|
| 608 |
+
)
|
| 609 |
+
parts.append(
|
| 610 |
+
f" Type: {tr.assignment_type} | "
|
| 611 |
+
f"File: {tr.source_file}:{tr.source_line}"
|
| 612 |
+
)
|
| 613 |
+
parts.append(f" Depends on: {', '.join(tr.driving_signals)}")
|
| 614 |
+
if tr.context_lines:
|
| 615 |
+
parts.append(f" Context:\n{tr.context_lines}")
|
| 616 |
+
parts.append("")
|
| 617 |
+
|
| 618 |
+
parts.append("SUGGESTED FIX STRATEGY:")
|
| 619 |
+
primary = traces[0]
|
| 620 |
+
parts.append(
|
| 621 |
+
f" Check the {primary.assignment_type} block at line {primary.source_line} "
|
| 622 |
+
f"of {primary.source_file}."
|
| 623 |
+
)
|
| 624 |
+
parts.append(
|
| 625 |
+
f" The RHS expression '{primary.rvalue_expression}' produces "
|
| 626 |
+
f"'{actual}' but should produce '{expected}'."
|
| 627 |
+
)
|
| 628 |
+
if len(traces) > 1:
|
| 629 |
+
parts.append(
|
| 630 |
+
f" The dependency chain involves {len(traces)} signals β "
|
| 631 |
+
"check upstream logic too."
|
| 632 |
+
)
|
| 633 |
+
else:
|
| 634 |
+
parts.append("No AST traces found β signal may be a port or undeclared.")
|
| 635 |
+
|
| 636 |
+
return "\n".join(parts)
|
| 637 |
+
|
| 638 |
+
def generate_fix_prompt(self, diagnosis: WaveformDiagnosis, rtl_code: str) -> str:
|
| 639 |
+
"""
|
| 640 |
+
Generate a precise LLM prompt from the diagnosis.
|
| 641 |
+
|
| 642 |
+
This replaces vague "fix the simulation error" prompts with exact,
|
| 643 |
+
line-level instructions based on AST + VCD evidence.
|
| 644 |
+
"""
|
| 645 |
+
prompt_parts = [
|
| 646 |
+
"# WAVEFORM-GUIDED RTL FIX REQUEST",
|
| 647 |
+
"",
|
| 648 |
+
"## Diagnosis (from AST + VCD analysis)",
|
| 649 |
+
diagnosis.diagnosis_summary,
|
| 650 |
+
"",
|
| 651 |
+
"## Fix Location",
|
| 652 |
+
f"Primary: {diagnosis.suggested_fix_area}",
|
| 653 |
+
"",
|
| 654 |
+
"## Instructions",
|
| 655 |
+
f"1. The signal '{diagnosis.failing_signal}' produces '{diagnosis.actual_value}' "
|
| 656 |
+
f"but should be '{diagnosis.expected_value}' at time {diagnosis.mismatch_time}ns.",
|
| 657 |
+
]
|
| 658 |
+
|
| 659 |
+
if diagnosis.root_cause_traces:
|
| 660 |
+
tr = diagnosis.root_cause_traces[0]
|
| 661 |
+
prompt_parts.append(
|
| 662 |
+
f"2. Fix the expression: {tr.signal_name} <= {tr.rvalue_expression} "
|
| 663 |
+
f"(line {tr.source_line})"
|
| 664 |
+
)
|
| 665 |
+
if tr.driving_signals:
|
| 666 |
+
prompt_parts.append(
|
| 667 |
+
f"3. Check these upstream signals: {', '.join(tr.driving_signals)}"
|
| 668 |
+
)
|
| 669 |
+
|
| 670 |
+
prompt_parts.extend([
|
| 671 |
+
"",
|
| 672 |
+
"## Current RTL (fix in-place)",
|
| 673 |
+
"```verilog",
|
| 674 |
+
rtl_code,
|
| 675 |
+
"```",
|
| 676 |
+
"",
|
| 677 |
+
"Return ONLY the corrected Verilog inside ```verilog fences.",
|
| 678 |
+
])
|
| 679 |
+
|
| 680 |
+
return "\n".join(prompt_parts)
|
src/agentic/golden_lib/template_matcher.py
CHANGED
|
@@ -92,11 +92,11 @@ class TemplateMatcher:
|
|
| 92 |
# When these appear, the LLM should generate from scratch instead of using a template.
|
| 93 |
COMPLEXITY_INDICATORS = [
|
| 94 |
"tmr", "triple modular", "redundancy", "radiation", "hardened", "hardening",
|
| 95 |
-
"fault tolerant", "majority voting", "lockstep", "
|
| 96 |
-
"
|
| 97 |
"pipeline", "pipelined", "superscalar", "out of order",
|
| 98 |
-
"
|
| 99 |
-
"encryption", "
|
| 100 |
"neural", "accelerator", "tensor", "convolution",
|
| 101 |
"multi.?channel", "multi.?port", "dual.?port",
|
| 102 |
"custom protocol", "proprietary",
|
|
|
|
| 92 |
# When these appear, the LLM should generate from scratch instead of using a template.
|
| 93 |
COMPLEXITY_INDICATORS = [
|
| 94 |
"tmr", "triple modular", "redundancy", "radiation", "hardened", "hardening",
|
| 95 |
+
"fault tolerant", "majority voting", "lockstep", r"\becc\b", "error correct",
|
| 96 |
+
r"\baxi\b", r"\bahb\b", r"\bapb\b", "wishbone", "avalon", # bus protocols
|
| 97 |
"pipeline", "pipelined", "superscalar", "out of order",
|
| 98 |
+
r"\bdma\b", "cache", r"\bmmu\b", "arbiter", "crossbar",
|
| 99 |
+
"encryption", r"\baes\b", r"\bsha\b", r"\brsa\b", "crypto",
|
| 100 |
"neural", "accelerator", "tensor", "convolution",
|
| 101 |
"multi.?channel", "multi.?port", "dual.?port",
|
| 102 |
"custom protocol", "proprietary",
|
src/agentic/golden_lib/templates/spi_master.v
CHANGED
|
@@ -19,6 +19,9 @@ module spi_master #(
|
|
| 19 |
);
|
| 20 |
|
| 21 |
localparam CNT_WIDTH = $clog2(CLK_DIV);
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
localparam [2:0] IDLE = 3'd0,
|
| 24 |
CS_SETUP = 3'd1,
|
|
@@ -29,7 +32,9 @@ module spi_master #(
|
|
| 29 |
reg [2:0] state;
|
| 30 |
reg [CNT_WIDTH-1:0] clk_cnt;
|
| 31 |
reg [2:0] bit_cnt;
|
|
|
|
| 32 |
reg [7:0] shift_out;
|
|
|
|
| 33 |
reg [7:0] shift_in;
|
| 34 |
reg sclk_edge; // 0 = rising, 1 = falling
|
| 35 |
|
|
@@ -67,7 +72,7 @@ module spi_master #(
|
|
| 67 |
CS_SETUP: begin
|
| 68 |
cs_n <= 1'b0;
|
| 69 |
mosi <= mosi_data[7]; // MSB first
|
| 70 |
-
if (clk_cnt ==
|
| 71 |
clk_cnt <= 0;
|
| 72 |
sclk_edge <= 0;
|
| 73 |
state <= TRANSFER;
|
|
@@ -77,7 +82,7 @@ module spi_master #(
|
|
| 77 |
end
|
| 78 |
|
| 79 |
TRANSFER: begin
|
| 80 |
-
if (clk_cnt ==
|
| 81 |
clk_cnt <= 0;
|
| 82 |
if (!sclk_edge) begin
|
| 83 |
// Rising edge: sample MISO
|
|
@@ -103,7 +108,7 @@ module spi_master #(
|
|
| 103 |
|
| 104 |
CS_HOLD: begin
|
| 105 |
sclk <= 1'b0;
|
| 106 |
-
if (clk_cnt ==
|
| 107 |
cs_n <= 1'b1;
|
| 108 |
miso_data <= shift_in;
|
| 109 |
state <= FINISH;
|
|
|
|
| 19 |
);
|
| 20 |
|
| 21 |
localparam CNT_WIDTH = $clog2(CLK_DIV);
|
| 22 |
+
/* verilator lint_off WIDTHTRUNC */
|
| 23 |
+
localparam [CNT_WIDTH-1:0] CLK_DIV_MAX = CLK_DIV - 1;
|
| 24 |
+
/* verilator lint_on WIDTHTRUNC */
|
| 25 |
|
| 26 |
localparam [2:0] IDLE = 3'd0,
|
| 27 |
CS_SETUP = 3'd1,
|
|
|
|
| 32 |
reg [2:0] state;
|
| 33 |
reg [CNT_WIDTH-1:0] clk_cnt;
|
| 34 |
reg [2:0] bit_cnt;
|
| 35 |
+
/* verilator lint_off UNUSEDSIGNAL */
|
| 36 |
reg [7:0] shift_out;
|
| 37 |
+
/* verilator lint_on UNUSEDSIGNAL */
|
| 38 |
reg [7:0] shift_in;
|
| 39 |
reg sclk_edge; // 0 = rising, 1 = falling
|
| 40 |
|
|
|
|
| 72 |
CS_SETUP: begin
|
| 73 |
cs_n <= 1'b0;
|
| 74 |
mosi <= mosi_data[7]; // MSB first
|
| 75 |
+
if (clk_cnt == CLK_DIV_MAX) begin
|
| 76 |
clk_cnt <= 0;
|
| 77 |
sclk_edge <= 0;
|
| 78 |
state <= TRANSFER;
|
|
|
|
| 82 |
end
|
| 83 |
|
| 84 |
TRANSFER: begin
|
| 85 |
+
if (clk_cnt == CLK_DIV_MAX) begin
|
| 86 |
clk_cnt <= 0;
|
| 87 |
if (!sclk_edge) begin
|
| 88 |
// Rising edge: sample MISO
|
|
|
|
| 108 |
|
| 109 |
CS_HOLD: begin
|
| 110 |
sclk <= 1'b0;
|
| 111 |
+
if (clk_cnt == CLK_DIV_MAX) begin
|
| 112 |
cs_n <= 1'b1;
|
| 113 |
miso_data <= shift_in;
|
| 114 |
state <= FINISH;
|
src/agentic/orchestrator.py
CHANGED
|
@@ -6,6 +6,7 @@ import re
|
|
| 6 |
import hashlib
|
| 7 |
import json
|
| 8 |
import signal
|
|
|
|
| 9 |
from dataclasses import dataclass, asdict
|
| 10 |
from typing import Optional, Dict, Any, List
|
| 11 |
from rich.console import Console
|
|
@@ -30,6 +31,7 @@ from .agents.testbench_designer import get_testbench_agent
|
|
| 30 |
from .agents.verifier import get_verification_agent, get_error_analyst_agent, get_regression_agent
|
| 31 |
from .agents.doc_agent import get_doc_agent
|
| 32 |
from .agents.sdc_agent import get_sdc_agent
|
|
|
|
| 33 |
from .tools.vlsi_tools import (
|
| 34 |
write_verilog,
|
| 35 |
run_syntax_check,
|
|
@@ -42,6 +44,7 @@ from .tools.vlsi_tools import (
|
|
| 42 |
run_formal_verification,
|
| 43 |
check_physical_metrics,
|
| 44 |
run_lint_check,
|
|
|
|
| 45 |
run_simulation_with_coverage,
|
| 46 |
parse_coverage_report,
|
| 47 |
parse_drc_lvs_reports,
|
|
@@ -272,6 +275,14 @@ class BuildOrchestrator:
|
|
| 272 |
self.failure_fingerprint_history[fp] = count
|
| 273 |
return count >= 2
|
| 274 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
def _build_llm_context(self, include_rtl: bool = True, max_rtl_chars: int = 15000) -> str:
|
| 276 |
"""Build cumulative context string for LLM calls.
|
| 277 |
|
|
@@ -475,6 +486,30 @@ class BuildOrchestrator:
|
|
| 475 |
# Also store the corrected name so RTL_GEN uses it
|
| 476 |
self.name = safe_name
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
arch_agent = Agent(
|
| 479 |
role='Chief System Architect',
|
| 480 |
goal=f'Define a robust micro-architecture for {self.name}',
|
|
@@ -522,7 +557,7 @@ SPECIFICATION SECTIONS (Markdown):
|
|
| 522 |
result = Crew(agents=[arch_agent], tasks=[spec_task]).kickoff()
|
| 523 |
|
| 524 |
self.artifacts['spec'] = str(result)
|
| 525 |
-
self.log("Architecture Plan Generated", refined=True)
|
| 526 |
self.transition(BuildState.RTL_GEN)
|
| 527 |
|
| 528 |
def _get_strategy_prompt(self) -> str:
|
|
@@ -552,16 +587,39 @@ SPECIFICATION SECTIONS (Markdown):
|
|
| 552 |
|
| 553 |
def _get_tb_strategy_prompt(self) -> str:
|
| 554 |
if self.strategy == BuildStrategy.SV_MODULAR:
|
| 555 |
-
return """Use
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
else:
|
| 566 |
return """Use Verilog-2005 Procedural Verification:
|
| 567 |
- Use `initial` blocks for stimulus.
|
|
@@ -728,171 +786,66 @@ SPECIFICATION SECTIONS (Markdown):
|
|
| 728 |
self.tb_failure_fingerprint_history[fp] = count
|
| 729 |
return count >= 2
|
| 730 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
def generate_uvm_lite_tb_from_rtl_ports(self, design_name: str, rtl_code: str) -> str:
|
| 732 |
-
"""Deterministic
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 733 |
ports = self._extract_module_ports(rtl_code)
|
| 734 |
if not ports:
|
| 735 |
return self._generate_fallback_testbench(rtl_code)
|
| 736 |
|
| 737 |
-
if_name = f"{design_name}_if"
|
| 738 |
clock_name = None
|
| 739 |
reset_name = None
|
| 740 |
input_ports: List[Dict[str, str]] = []
|
| 741 |
output_ports: List[Dict[str, str]] = []
|
| 742 |
-
inout_ports: List[Dict[str, str]] = []
|
| 743 |
|
| 744 |
for p in ports:
|
| 745 |
pname = p["name"]
|
| 746 |
if p["direction"] == "input":
|
| 747 |
input_ports.append(p)
|
| 748 |
-
if clock_name is None and re.search(
|
|
|
|
|
|
|
| 749 |
clock_name = pname
|
| 750 |
-
if reset_name is None and re.search(
|
|
|
|
|
|
|
| 751 |
reset_name = pname
|
| 752 |
elif p["direction"] == "output":
|
| 753 |
output_ports.append(p)
|
| 754 |
-
else:
|
| 755 |
-
inout_ports.append(p)
|
| 756 |
|
| 757 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
lines: List[str] = ["`timescale 1ns/1ps", ""]
|
| 760 |
-
lines.append(f"
|
| 761 |
-
for p in ports:
|
| 762 |
-
width = f"{p['width']} " if p["width"] else ""
|
| 763 |
-
lines.append(f" logic {width}{p['name']};")
|
| 764 |
-
drv_input = clock_name if clock_name else (input_ports[0]["name"] if input_ports else ports[0]["name"])
|
| 765 |
-
drv_outputs = [p["name"] for p in non_clk_inputs + inout_ports if p["name"] != drv_input]
|
| 766 |
-
if drv_outputs:
|
| 767 |
-
lines.append(f" modport drv (input {drv_input}, output {', '.join(drv_outputs)});")
|
| 768 |
-
else:
|
| 769 |
-
lines.append(f" modport drv (input {drv_input});")
|
| 770 |
-
|
| 771 |
-
mon_inputs: List[str] = []
|
| 772 |
-
if clock_name:
|
| 773 |
-
mon_inputs.append(clock_name)
|
| 774 |
-
mon_inputs.extend([p["name"] for p in output_ports + inout_ports])
|
| 775 |
-
mon_inputs = list(dict.fromkeys(mon_inputs))
|
| 776 |
-
if mon_inputs:
|
| 777 |
-
lines.append(f" modport mon (input {', '.join(mon_inputs)});")
|
| 778 |
-
lines.append("endinterface")
|
| 779 |
lines.append("")
|
| 780 |
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
" bit has_x;",
|
| 789 |
-
"endclass",
|
| 790 |
-
"",
|
| 791 |
-
"class Driver;",
|
| 792 |
-
f" virtual {if_name} vif;",
|
| 793 |
-
f" function new(virtual {if_name} vif);",
|
| 794 |
-
" this.vif = vif;",
|
| 795 |
-
" endfunction",
|
| 796 |
-
"",
|
| 797 |
-
" task reset_phase();",
|
| 798 |
-
]
|
| 799 |
-
)
|
| 800 |
-
if reset_name:
|
| 801 |
-
if reset_name.lower().endswith("_n"):
|
| 802 |
-
lines.extend(
|
| 803 |
-
[
|
| 804 |
-
f" vif.{reset_name} = 1'b0;",
|
| 805 |
-
" repeat (5) @(posedge vif." + (clock_name if clock_name else non_clk_inputs[0]["name"]) + ");" if clock_name else " #50;",
|
| 806 |
-
f" vif.{reset_name} = 1'b1;",
|
| 807 |
-
]
|
| 808 |
-
)
|
| 809 |
-
else:
|
| 810 |
-
lines.extend(
|
| 811 |
-
[
|
| 812 |
-
f" vif.{reset_name} = 1'b1;",
|
| 813 |
-
" repeat (5) @(posedge vif." + (clock_name if clock_name else non_clk_inputs[0]["name"]) + ");" if clock_name else " #50;",
|
| 814 |
-
f" vif.{reset_name} = 1'b0;",
|
| 815 |
-
]
|
| 816 |
-
)
|
| 817 |
-
lines.append(" endtask")
|
| 818 |
lines.append("")
|
| 819 |
-
|
| 820 |
-
if clock_name:
|
| 821 |
-
lines.append(f" @(posedge vif.{clock_name});")
|
| 822 |
-
else:
|
| 823 |
-
lines.append(" #10;")
|
| 824 |
-
for p in non_clk_inputs:
|
| 825 |
-
pname = p["name"]
|
| 826 |
-
if pname == reset_name:
|
| 827 |
-
continue
|
| 828 |
-
width = p["width"]
|
| 829 |
-
if width:
|
| 830 |
-
lines.append(f" vif.{pname} = $urandom;")
|
| 831 |
-
else:
|
| 832 |
-
lines.append(f" vif.{pname} = $random % 2;")
|
| 833 |
-
lines.append(" endtask")
|
| 834 |
-
lines.append("endclass")
|
| 835 |
-
lines.append("")
|
| 836 |
-
lines.extend(
|
| 837 |
-
[
|
| 838 |
-
"class Monitor;",
|
| 839 |
-
f" virtual {if_name} vif;",
|
| 840 |
-
f" function new(virtual {if_name} vif);",
|
| 841 |
-
" this.vif = vif;",
|
| 842 |
-
" endfunction",
|
| 843 |
-
" function bit has_unknown_output();",
|
| 844 |
-
" bit bad;",
|
| 845 |
-
" bad = 0;",
|
| 846 |
-
]
|
| 847 |
-
)
|
| 848 |
-
for p in output_ports + inout_ports:
|
| 849 |
-
lines.append(f" if (^(vif.{p['name']}) === 1'bx) bad = 1;")
|
| 850 |
-
lines.extend(
|
| 851 |
-
[
|
| 852 |
-
" return bad;",
|
| 853 |
-
" endfunction",
|
| 854 |
-
"endclass",
|
| 855 |
-
"",
|
| 856 |
-
"class Scoreboard;",
|
| 857 |
-
" int errors;",
|
| 858 |
-
" function new();",
|
| 859 |
-
" errors = 0;",
|
| 860 |
-
" endfunction",
|
| 861 |
-
" function void sample(bit has_x);",
|
| 862 |
-
" if (has_x) begin",
|
| 863 |
-
' $display("TEST FAILED: X/Z detected on DUT output.");',
|
| 864 |
-
" errors++;",
|
| 865 |
-
" end",
|
| 866 |
-
" endfunction",
|
| 867 |
-
"endclass",
|
| 868 |
-
"",
|
| 869 |
-
"class Environment;",
|
| 870 |
-
f" virtual {if_name} vif;",
|
| 871 |
-
" Driver drv;",
|
| 872 |
-
" Monitor mon;",
|
| 873 |
-
" Scoreboard sb;",
|
| 874 |
-
f" function new(virtual {if_name} vif);",
|
| 875 |
-
" this.vif = vif;",
|
| 876 |
-
" drv = new(vif);",
|
| 877 |
-
" mon = new(vif);",
|
| 878 |
-
" sb = new();",
|
| 879 |
-
" endfunction",
|
| 880 |
-
" task run();",
|
| 881 |
-
" drv.reset_phase();",
|
| 882 |
-
" repeat (40) begin",
|
| 883 |
-
" drv.drive_step();",
|
| 884 |
-
" sb.sample(mon.has_unknown_output());",
|
| 885 |
-
" end",
|
| 886 |
-
" if (sb.errors == 0) begin",
|
| 887 |
-
' $display("TEST PASSED");',
|
| 888 |
-
" end else begin",
|
| 889 |
-
' $display("TEST FAILED");',
|
| 890 |
-
" end",
|
| 891 |
-
" endtask",
|
| 892 |
-
"endclass",
|
| 893 |
-
"",
|
| 894 |
-
]
|
| 895 |
-
)
|
| 896 |
# --- DUT instantiation with parameter defaults ---
|
| 897 |
param_pattern = re.compile(
|
| 898 |
r"parameter\s+(?:\w+\s+)?([A-Za-z_]\w*)\s*=\s*([^,;\)\n]+)",
|
|
@@ -904,31 +857,101 @@ SPECIFICATION SECTIONS (Markdown):
|
|
| 904 |
lines.append(f" {design_name} #({param_str}) dut (")
|
| 905 |
else:
|
| 906 |
lines.append(f" {design_name} dut (")
|
| 907 |
-
conn = [f" .{p['name']}(
|
| 908 |
lines.append(",\n".join(conn))
|
| 909 |
-
lines.
|
|
|
|
|
|
|
|
|
|
| 910 |
if clock_name:
|
| 911 |
-
lines.
|
| 912 |
-
|
| 913 |
-
|
| 914 |
-
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 932 |
return "\n".join(lines)
|
| 933 |
|
| 934 |
def _deterministic_tb_fallback(self, rtl_code: str) -> str:
|
|
@@ -1134,21 +1157,38 @@ endclass
|
|
| 1134 |
return "\n".join([line for line in body if line is not None])
|
| 1135 |
|
| 1136 |
def _kickoff_with_timeout(self, agents: List[Agent], tasks: List[Task], timeout_s: int) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1137 |
timeout_s = max(1, int(timeout_s))
|
| 1138 |
-
if not hasattr(signal, "SIGALRM"):
|
| 1139 |
-
return str(Crew(agents=agents, tasks=tasks).kickoff()) # type: ignore
|
| 1140 |
|
| 1141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1142 |
raise TimeoutError(f"Crew kickoff exceeded {timeout_s}s timeout")
|
| 1143 |
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
|
| 1148 |
-
return
|
| 1149 |
-
|
| 1150 |
-
|
| 1151 |
-
signal.signal(signal.SIGALRM, prev_handler)
|
| 1152 |
|
| 1153 |
def _condense_failure_log(self, raw_text: str, kind: str) -> str:
|
| 1154 |
if not raw_text:
|
|
@@ -1394,6 +1434,24 @@ endclass
|
|
| 1394 |
verbose=self.verbose,
|
| 1395 |
strategy=self.strategy.name
|
| 1396 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1397 |
|
| 1398 |
rtl_task = Task(
|
| 1399 |
description=f"""Design module "{self.name}" based on SPEC.
|
|
@@ -1415,12 +1473,35 @@ CRITICAL RULES:
|
|
| 1415 |
5. **MODULAR HIERARCHY**: For complex designs, break them into smaller sub-modules. Output ALL modules in your response.
|
| 1416 |
6. Return code in ```verilog fence.
|
| 1417 |
""",
|
| 1418 |
-
expected_output='Verilog Code',
|
| 1419 |
agent=rtl_agent
|
| 1420 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1421 |
|
| 1422 |
with console.status(f"[bold yellow]Generating RTL ({self.strategy.name})...[/bold yellow]"):
|
| 1423 |
-
result = Crew(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1424 |
|
| 1425 |
rtl_code = str(result)
|
| 1426 |
self.logger.info(f"GENERATED RTL ({self.strategy.name}):\n{rtl_code}")
|
|
@@ -1569,17 +1650,59 @@ You explain what you changed and why.""",
|
|
| 1569 |
new_code = str(result)
|
| 1570 |
self.logger.info(f"FIXED RTL:\n{new_code}")
|
| 1571 |
|
| 1572 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1573 |
if isinstance(new_path, str) and new_path.startswith("Error:"):
|
| 1574 |
-
|
| 1575 |
-
# Don't fail immediately β the LLM returned bad output, retry within budget
|
| 1576 |
-
retry_count = self.state_retry_counts.get(self.state.name, 0)
|
| 1577 |
-
if retry_count >= self.max_retries:
|
| 1578 |
-
self.log("Write error persisted after max retries. Failing.", refined=True)
|
| 1579 |
-
self.state = BuildState.FAIL
|
| 1580 |
-
else:
|
| 1581 |
-
self.log(f"Retrying fix (LLM output was unparsable).", refined=True)
|
| 1582 |
-
return
|
| 1583 |
|
| 1584 |
self.artifacts['rtl_path'] = new_path
|
| 1585 |
# Read back the CLEANED version, not raw LLM output
|
|
@@ -1610,6 +1733,7 @@ You explain what you changed and why.""",
|
|
| 1610 |
self.logger.info(f"GOLDEN TESTBENCH:\n{tb_code}")
|
| 1611 |
tb_path = write_verilog(self.name, tb_code, is_testbench=True)
|
| 1612 |
self.artifacts['tb_path'] = tb_path
|
|
|
|
| 1613 |
else:
|
| 1614 |
self.log("Generating Testbench...", refined=True)
|
| 1615 |
tb_agent = get_testbench_agent(self.llm, f"Verify {self.name}", verbose=self.verbose, strategy=self.strategy.name)
|
|
@@ -1684,6 +1808,7 @@ RULES:
|
|
| 1684 |
self.state = BuildState.FAIL
|
| 1685 |
return
|
| 1686 |
self.artifacts['tb_path'] = tb_path
|
|
|
|
| 1687 |
else:
|
| 1688 |
self.log(f"Verifying with existing Testbench (Attempt {self.retry_count}).", refined=True)
|
| 1689 |
# Verify file exists
|
|
@@ -1797,7 +1922,7 @@ RULES:
|
|
| 1797 |
# --- AUTONOMOUS FIX: Try to fix compilation errors without LLM ---
|
| 1798 |
# Auto-fixes removed (Verilator supports SV natively)
|
| 1799 |
|
| 1800 |
-
# --- LLM ERROR ANALYSIS:
|
| 1801 |
analyst = get_error_analyst_agent(self.llm, verbose=self.verbose)
|
| 1802 |
analysis_task = Task(
|
| 1803 |
description=f'''Analyze this Verification Failure for "{self.name}".
|
|
@@ -1811,6 +1936,8 @@ ERROR LOG:
|
|
| 1811 |
CURRENT TESTBENCH (first 3000 chars):
|
| 1812 |
{tb_code[:3000]}
|
| 1813 |
|
|
|
|
|
|
|
| 1814 |
Classify the failure as ONE of:
|
| 1815 |
A) TESTBENCH_SYNTAX β TB compilation/syntax error (missing semicolons, undeclared signals, class errors)
|
| 1816 |
B) RTL_LOGIC_BUG β Functional error in RTL design (wrong state transitions, bad arithmetic, logic errors)
|
|
@@ -1905,7 +2032,7 @@ FIX_HINT: <specific suggestion for how to fix it>''',
|
|
| 1905 |
port_info = self._extract_module_interface(self.artifacts['rtl_code'])
|
| 1906 |
fix_prompt = f"""Fix the Testbench logic/syntax.
|
| 1907 |
|
| 1908 |
-
DIAGNOSIS:
|
| 1909 |
ROOT CAUSE: {root_cause}
|
| 1910 |
FIX HINT: {fix_hint}
|
| 1911 |
|
|
@@ -1928,10 +2055,13 @@ Ref RTL:
|
|
| 1928 |
PREVIOUS ATTEMPTS:
|
| 1929 |
{self._format_failure_history()}
|
| 1930 |
|
| 1931 |
-
CRITICAL:
|
| 1932 |
- Return ONLY the fixed Testbench code in ```verilog fences.
|
| 1933 |
- Do NOT invent ports that aren't in the MODULE INTERFACE above.
|
| 1934 |
- Module name of DUT is "{self.name}"
|
|
|
|
|
|
|
|
|
|
| 1935 |
"""
|
| 1936 |
else:
|
| 1937 |
self.log("Analyst identified RTL Logic Error. Fixing RTL...", refined=True)
|
|
@@ -1941,7 +2071,7 @@ CRITICAL:
|
|
| 1941 |
|
| 1942 |
fix_prompt = f"""Fix the RTL logic to pass verification.
|
| 1943 |
|
| 1944 |
-
DIAGNOSIS:
|
| 1945 |
ROOT CAUSE: {root_cause}
|
| 1946 |
FIX HINT: {fix_hint}
|
| 1947 |
|
|
@@ -1970,18 +2100,23 @@ PREVIOUS ATTEMPTS:
|
|
| 1970 |
CRITICAL:
|
| 1971 |
- Address the ROOT CAUSE and FIX HINT above directly.
|
| 1972 |
- Maintain design intent from the architecture spec.
|
|
|
|
| 1973 |
- Return ONLY the fixed {self.strategy.name} logic in ```verilog fences.
|
| 1974 |
"""
|
| 1975 |
|
| 1976 |
-
# Execute Fix
|
| 1977 |
fix_task = Task(
|
| 1978 |
description=fix_prompt,
|
| 1979 |
-
expected_output="Fixed Verilog Code",
|
| 1980 |
agent=fixer
|
| 1981 |
)
|
| 1982 |
|
| 1983 |
with console.status("[bold yellow]AI Implementing Fix...[/bold yellow]"):
|
| 1984 |
-
result = Crew(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1985 |
fixed_code = str(result)
|
| 1986 |
self.logger.info(f"FIXED CODE:\n{fixed_code}")
|
| 1987 |
|
|
@@ -2261,7 +2396,7 @@ CRITICAL:
|
|
| 2261 |
|
| 2262 |
coverage_checks = {
|
| 2263 |
"line": line_pct >= float(thresholds["line"]),
|
| 2264 |
-
"branch": branch_pct >=
|
| 2265 |
"toggle": toggle_pct >= float(thresholds["toggle"]),
|
| 2266 |
"functional": functional_pct >= float(thresholds["functional"]),
|
| 2267 |
}
|
|
@@ -2329,8 +2464,9 @@ CRITICAL:
|
|
| 2329 |
|
| 2330 |
tb_agent = get_testbench_agent(self.llm, f"Improve coverage for {self.name}", verbose=self.verbose, strategy=self.strategy.name)
|
| 2331 |
|
|
|
|
| 2332 |
improve_prompt = f"""The current testbench for "{self.name}" does not meet coverage thresholds.
|
| 2333 |
-
TARGET:
|
| 2334 |
Current Coverage Data: {coverage_data}
|
| 2335 |
|
| 2336 |
Current RTL:
|
|
@@ -2344,7 +2480,7 @@ CRITICAL:
|
|
| 2344 |
```
|
| 2345 |
|
| 2346 |
Create an IMPROVED self-checking testbench that:
|
| 2347 |
-
1. Achieves >
|
| 2348 |
2. Tests all FSM states (not just happy path)
|
| 2349 |
3. Exercises all conditional branches (if/else, case)
|
| 2350 |
3. Tests reset behavior mid-operation
|
|
@@ -2989,8 +3125,63 @@ set ::env(MAGIC_DRC_USE_GDS) 1
|
|
| 2989 |
self.log(f"GDSII generated: {result}", refined=True)
|
| 2990 |
self.transition(BuildState.CONVERGENCE_REVIEW)
|
| 2991 |
else:
|
| 2992 |
-
|
| 2993 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2994 |
|
| 2995 |
def do_signoff(self):
|
| 2996 |
"""Performs full fabrication-readiness signoff: DRC/LVS, timing closure, power analysis."""
|
|
|
|
| 6 |
import hashlib
|
| 7 |
import json
|
| 8 |
import signal
|
| 9 |
+
import threading
|
| 10 |
from dataclasses import dataclass, asdict
|
| 11 |
from typing import Optional, Dict, Any, List
|
| 12 |
from rich.console import Console
|
|
|
|
| 31 |
from .agents.verifier import get_verification_agent, get_error_analyst_agent, get_regression_agent
|
| 32 |
from .agents.doc_agent import get_doc_agent
|
| 33 |
from .agents.sdc_agent import get_sdc_agent
|
| 34 |
+
from .core import ArchitectModule, SelfReflectPipeline
|
| 35 |
from .tools.vlsi_tools import (
|
| 36 |
write_verilog,
|
| 37 |
run_syntax_check,
|
|
|
|
| 44 |
run_formal_verification,
|
| 45 |
check_physical_metrics,
|
| 46 |
run_lint_check,
|
| 47 |
+
run_iverilog_lint,
|
| 48 |
run_simulation_with_coverage,
|
| 49 |
parse_coverage_report,
|
| 50 |
parse_drc_lvs_reports,
|
|
|
|
| 275 |
self.failure_fingerprint_history[fp] = count
|
| 276 |
return count >= 2
|
| 277 |
|
| 278 |
+
def _clear_last_fingerprint(self, error_text: str) -> None:
|
| 279 |
+
"""Reset the fingerprint counter for the given error so the next
|
| 280 |
+
loop iteration doesn't see a 'repeated failure' when the code was
|
| 281 |
+
never actually updated (e.g. LLM returned unparsable output)."""
|
| 282 |
+
base = f"{self.state.name}|{error_text[:500]}|{self._artifact_fingerprint()}"
|
| 283 |
+
fp = hashlib.sha256(base.encode("utf-8", errors="ignore")).hexdigest()
|
| 284 |
+
self.failure_fingerprint_history.pop(fp, None)
|
| 285 |
+
|
| 286 |
def _build_llm_context(self, include_rtl: bool = True, max_rtl_chars: int = 15000) -> str:
|
| 287 |
"""Build cumulative context string for LLM calls.
|
| 288 |
|
|
|
|
| 486 |
# Also store the corrected name so RTL_GEN uses it
|
| 487 |
self.name = safe_name
|
| 488 |
|
| 489 |
+
# ββ Phase 1: Structured Spec Decomposition (ArchitectModule) ββ
|
| 490 |
+
# Produces a validated JSON contract (SID) that defines every port,
|
| 491 |
+
# parameter, FSM state, and sub-module BEFORE any Verilog is written.
|
| 492 |
+
try:
|
| 493 |
+
architect = ArchitectModule(llm=self.llm, verbose=self.verbose, max_retries=3)
|
| 494 |
+
sid = architect.decompose(
|
| 495 |
+
design_name=self.name,
|
| 496 |
+
spec_text=self.desc,
|
| 497 |
+
)
|
| 498 |
+
self.artifacts['sid'] = sid.to_json()
|
| 499 |
+
# Convert SID β detailed RTL prompt for the coder agent
|
| 500 |
+
self.artifacts['spec'] = architect.sid_to_rtl_prompt(sid)
|
| 501 |
+
self.log(f"Structured Spec: {len(sid.sub_modules)} sub-modules decomposed", refined=True)
|
| 502 |
+
except Exception as e:
|
| 503 |
+
self.logger.warning(f"ArchitectModule failed ({e}), falling back to Crew-based spec")
|
| 504 |
+
# Fallback: original Crew-based spec generation
|
| 505 |
+
self._do_spec_fallback()
|
| 506 |
+
return
|
| 507 |
+
|
| 508 |
+
self.log("Architecture Plan Generated (SID validated)", refined=True)
|
| 509 |
+
self.transition(BuildState.RTL_GEN)
|
| 510 |
+
|
| 511 |
+
def _do_spec_fallback(self):
|
| 512 |
+
"""Fallback spec generation using a single CrewAI agent."""
|
| 513 |
arch_agent = Agent(
|
| 514 |
role='Chief System Architect',
|
| 515 |
goal=f'Define a robust micro-architecture for {self.name}',
|
|
|
|
| 557 |
result = Crew(agents=[arch_agent], tasks=[spec_task]).kickoff()
|
| 558 |
|
| 559 |
self.artifacts['spec'] = str(result)
|
| 560 |
+
self.log("Architecture Plan Generated (fallback)", refined=True)
|
| 561 |
self.transition(BuildState.RTL_GEN)
|
| 562 |
|
| 563 |
def _get_strategy_prompt(self) -> str:
|
|
|
|
| 587 |
|
| 588 |
def _get_tb_strategy_prompt(self) -> str:
|
| 589 |
if self.strategy == BuildStrategy.SV_MODULAR:
|
| 590 |
+
return """Use FLAT PROCEDURAL SystemVerilog Verification (Verilator-safe):
|
| 591 |
+
|
| 592 |
+
CRITICAL VERILATOR CONSTRAINTS β MUST FOLLOW:
|
| 593 |
+
βββββββββββββββββββββββββββββββββββββββββββββ
|
| 594 |
+
β’ Do NOT use `interface` blocks β Verilator REJECTS them.
|
| 595 |
+
β’ Do NOT use `class` (Transaction, Driver, Monitor, Scoreboard) β Verilator REJECTS classes inside modules.
|
| 596 |
+
β’ Do NOT use `covergroup` / `coverpoint` β Verilator does NOT support them.
|
| 597 |
+
β’ Do NOT use `virtual interface` handles or `vif.signal` β Verilator REJECTS these.
|
| 598 |
+
β’ Do NOT use `program` blocks β Verilator REJECTS them.
|
| 599 |
+
β’ Do NOT use `new()`, `rand`, or any OOP construct.
|
| 600 |
+
|
| 601 |
+
WHAT TO DO INSTEAD:
|
| 602 |
+
βββββββββββββββββββββ
|
| 603 |
+
β’ Declare ALL DUT signals as `reg` (inputs) or `wire` (outputs) in the TB module.
|
| 604 |
+
β’ Instantiate DUT with direct port connections: `.port_name(port_name)`
|
| 605 |
+
β’ Use `initial` blocks for reset, stimulus, and checking.
|
| 606 |
+
β’ Use `$urandom` for randomized stimulus (Verilator-safe).
|
| 607 |
+
β’ Use `always #5 clk = ~clk;` for clock generation.
|
| 608 |
+
β’ Check outputs directly with `if` statements and `$display`.
|
| 609 |
+
β’ Track errors with `integer fail_count;` β print TEST PASSED/FAILED at end.
|
| 610 |
+
β’ Add a timeout watchdog: `initial begin #100000; $display("TEST FAILED: Timeout"); $finish; end`
|
| 611 |
+
β’ Dump waveforms: `$dumpfile("design.vcd"); $dumpvars(0, <tb_name>);`
|
| 612 |
+
|
| 613 |
+
STRUCTURE:
|
| 614 |
+
βββββββββββ
|
| 615 |
+
1. `timescale 1ns/1ps
|
| 616 |
+
2. module <name>_tb;
|
| 617 |
+
3. Signal declarations (reg for inputs, wire for outputs)
|
| 618 |
+
4. DUT instantiation
|
| 619 |
+
5. Clock generation
|
| 620 |
+
6. initial block: reset β stimulus β checks β PASS/FAIL β $finish
|
| 621 |
+
7. Timeout watchdog
|
| 622 |
+
8. endmodule"""
|
| 623 |
else:
|
| 624 |
return """Use Verilog-2005 Procedural Verification:
|
| 625 |
- Use `initial` blocks for stimulus.
|
|
|
|
| 786 |
self.tb_failure_fingerprint_history[fp] = count
|
| 787 |
return count >= 2
|
| 788 |
|
| 789 |
+
def _clear_tb_fingerprints(self) -> None:
|
| 790 |
+
"""Reset all TB failure fingerprints.
|
| 791 |
+
|
| 792 |
+
Called when a fundamentally new TB is generated (LLM or deterministic
|
| 793 |
+
fallback) so that compile/static gates get a fresh set of attempts
|
| 794 |
+
against the new artifact instead of matching old fingerprints.
|
| 795 |
+
"""
|
| 796 |
+
self.tb_failure_fingerprint_history.clear()
|
| 797 |
+
self.tb_recovery_counts.clear()
|
| 798 |
+
|
| 799 |
def generate_uvm_lite_tb_from_rtl_ports(self, design_name: str, rtl_code: str) -> str:
|
| 800 |
+
"""Deterministic Verilator-safe testbench generated from RTL ports.
|
| 801 |
+
|
| 802 |
+
Generates a flat procedural TB β no interfaces, no classes, no virtual
|
| 803 |
+
references. This compiles on Verilator, iverilog, and any IEEE-1800
|
| 804 |
+
simulator without modification.
|
| 805 |
+
"""
|
| 806 |
ports = self._extract_module_ports(rtl_code)
|
| 807 |
if not ports:
|
| 808 |
return self._generate_fallback_testbench(rtl_code)
|
| 809 |
|
|
|
|
| 810 |
clock_name = None
|
| 811 |
reset_name = None
|
| 812 |
input_ports: List[Dict[str, str]] = []
|
| 813 |
output_ports: List[Dict[str, str]] = []
|
|
|
|
| 814 |
|
| 815 |
for p in ports:
|
| 816 |
pname = p["name"]
|
| 817 |
if p["direction"] == "input":
|
| 818 |
input_ports.append(p)
|
| 819 |
+
if clock_name is None and re.search(
|
| 820 |
+
r'(?:^|_)(?:clk|clock|sclk|aclk)(?:_|$)|^i_clk', pname, re.IGNORECASE
|
| 821 |
+
):
|
| 822 |
clock_name = pname
|
| 823 |
+
if reset_name is None and re.search(
|
| 824 |
+
r'(?:^|_)(?:rst|reset|nrst|areset)(?:_|$)|^i_rst', pname, re.IGNORECASE
|
| 825 |
+
):
|
| 826 |
reset_name = pname
|
| 827 |
elif p["direction"] == "output":
|
| 828 |
output_ports.append(p)
|
|
|
|
|
|
|
| 829 |
|
| 830 |
+
non_clk_rst_inputs = [
|
| 831 |
+
p for p in input_ports
|
| 832 |
+
if p["name"] != clock_name and p["name"] != reset_name
|
| 833 |
+
]
|
| 834 |
+
reset_active_low = reset_name and reset_name.lower().endswith("_n") if reset_name else False
|
| 835 |
|
| 836 |
lines: List[str] = ["`timescale 1ns/1ps", ""]
|
| 837 |
+
lines.append(f"module {design_name}_tb;")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
lines.append("")
|
| 839 |
|
| 840 |
+
# --- Signal declarations ---
|
| 841 |
+
for p in input_ports:
|
| 842 |
+
width = f"{p['width']} " if p.get("width") else ""
|
| 843 |
+
lines.append(f" reg {width}{p['name']};")
|
| 844 |
+
for p in output_ports:
|
| 845 |
+
width = f"{p['width']} " if p.get("width") else ""
|
| 846 |
+
lines.append(f" wire {width}{p['name']};")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
lines.append("")
|
| 848 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
# --- DUT instantiation with parameter defaults ---
|
| 850 |
param_pattern = re.compile(
|
| 851 |
r"parameter\s+(?:\w+\s+)?([A-Za-z_]\w*)\s*=\s*([^,;\)\n]+)",
|
|
|
|
| 857 |
lines.append(f" {design_name} #({param_str}) dut (")
|
| 858 |
else:
|
| 859 |
lines.append(f" {design_name} dut (")
|
| 860 |
+
conn = [f" .{p['name']}({p['name']})" for p in ports]
|
| 861 |
lines.append(",\n".join(conn))
|
| 862 |
+
lines.append(" );")
|
| 863 |
+
lines.append("")
|
| 864 |
+
|
| 865 |
+
# --- Clock generation ---
|
| 866 |
if clock_name:
|
| 867 |
+
lines.append(f" // Clock: 100MHz (10ns period)")
|
| 868 |
+
lines.append(f" initial {clock_name} = 1'b0;")
|
| 869 |
+
lines.append(f" always #5 {clock_name} = ~{clock_name};")
|
| 870 |
+
lines.append("")
|
| 871 |
+
|
| 872 |
+
# --- Failure tracker ---
|
| 873 |
+
lines.append(" integer tb_fail;")
|
| 874 |
+
lines.append("")
|
| 875 |
+
|
| 876 |
+
# --- Main test sequence ---
|
| 877 |
+
lines.append(" initial begin")
|
| 878 |
+
lines.append(" tb_fail = 0;")
|
| 879 |
+
lines.append("")
|
| 880 |
+
|
| 881 |
+
# Dump waveforms
|
| 882 |
+
lines.append(f' $dumpfile("{design_name}.vcd");')
|
| 883 |
+
lines.append(f' $dumpvars(0, {design_name}_tb);')
|
| 884 |
+
lines.append("")
|
| 885 |
+
|
| 886 |
+
# Initialize all inputs
|
| 887 |
+
for p in non_clk_rst_inputs:
|
| 888 |
+
width = p.get("width", "")
|
| 889 |
+
if width:
|
| 890 |
+
bits = re.search(r'\[(\d+):', width)
|
| 891 |
+
if bits:
|
| 892 |
+
lines.append(f" {p['name']} = {int(bits.group(1))+1}'d0;")
|
| 893 |
+
else:
|
| 894 |
+
lines.append(f" {p['name']} = 0;")
|
| 895 |
+
else:
|
| 896 |
+
lines.append(f" {p['name']} = 1'b0;")
|
| 897 |
+
|
| 898 |
+
# Reset sequence
|
| 899 |
+
if reset_name:
|
| 900 |
+
if reset_active_low:
|
| 901 |
+
lines.append(f" {reset_name} = 1'b0; // Assert reset (active-low)")
|
| 902 |
+
lines.append(" #50;")
|
| 903 |
+
lines.append(f" {reset_name} = 1'b1; // Deassert reset")
|
| 904 |
+
else:
|
| 905 |
+
lines.append(f" {reset_name} = 1'b1; // Assert reset (active-high)")
|
| 906 |
+
lines.append(" #50;")
|
| 907 |
+
lines.append(f" {reset_name} = 1'b0; // Deassert reset")
|
| 908 |
+
lines.append(" #20;")
|
| 909 |
+
lines.append("")
|
| 910 |
+
|
| 911 |
+
# Stimulus: drive random values on data inputs
|
| 912 |
+
lines.append(" // === Stimulus Phase ===")
|
| 913 |
+
lines.append(" repeat (40) begin")
|
| 914 |
+
if clock_name:
|
| 915 |
+
lines.append(f" @(posedge {clock_name});")
|
| 916 |
+
else:
|
| 917 |
+
lines.append(" #10;")
|
| 918 |
+
for p in non_clk_rst_inputs:
|
| 919 |
+
lines.append(f" {p['name']} = $urandom;")
|
| 920 |
+
lines.append(" end")
|
| 921 |
+
lines.append("")
|
| 922 |
+
|
| 923 |
+
# Output check: verify no X/Z on outputs after stimulus
|
| 924 |
+
lines.append(" // === Output Sanity Check ===")
|
| 925 |
+
if clock_name:
|
| 926 |
+
lines.append(f" @(posedge {clock_name});")
|
| 927 |
+
else:
|
| 928 |
+
lines.append(" #10;")
|
| 929 |
+
for p in output_ports:
|
| 930 |
+
lines.append(f" if (^({p['name']}) === 1'bx) begin")
|
| 931 |
+
lines.append(f' $display("TEST FAILED: X/Z detected on {p["name"]}");')
|
| 932 |
+
lines.append(" tb_fail = tb_fail + 1;")
|
| 933 |
+
lines.append(" end")
|
| 934 |
+
lines.append("")
|
| 935 |
+
|
| 936 |
+
# Result
|
| 937 |
+
lines.append(" if (tb_fail == 0) begin")
|
| 938 |
+
lines.append(' $display("TEST PASSED");')
|
| 939 |
+
lines.append(" end else begin")
|
| 940 |
+
lines.append(' $display("TEST FAILED");')
|
| 941 |
+
lines.append(" end")
|
| 942 |
+
lines.append(" $finish;")
|
| 943 |
+
lines.append(" end")
|
| 944 |
+
lines.append("")
|
| 945 |
+
|
| 946 |
+
# Timeout watchdog
|
| 947 |
+
lines.append(" // Timeout watchdog")
|
| 948 |
+
lines.append(" initial begin")
|
| 949 |
+
lines.append(" #100000;")
|
| 950 |
+
lines.append(' $display("TEST FAILED: Timeout");')
|
| 951 |
+
lines.append(" $finish;")
|
| 952 |
+
lines.append(" end")
|
| 953 |
+
lines.append("")
|
| 954 |
+
lines.append("endmodule")
|
| 955 |
return "\n".join(lines)
|
| 956 |
|
| 957 |
def _deterministic_tb_fallback(self, rtl_code: str) -> str:
|
|
|
|
| 1157 |
return "\n".join([line for line in body if line is not None])
|
| 1158 |
|
| 1159 |
def _kickoff_with_timeout(self, agents: List[Agent], tasks: List[Task], timeout_s: int) -> str:
|
| 1160 |
+
"""Run CrewAI kickoff with a timeout.
|
| 1161 |
+
|
| 1162 |
+
Uses threading instead of signal.SIGALRM so it works from any thread
|
| 1163 |
+
(FastAPI worker threads, background threads, etc.).
|
| 1164 |
+
"""
|
| 1165 |
timeout_s = max(1, int(timeout_s))
|
|
|
|
|
|
|
| 1166 |
|
| 1167 |
+
result_box: List[str] = []
|
| 1168 |
+
error_box: List[Exception] = []
|
| 1169 |
+
|
| 1170 |
+
def _run():
|
| 1171 |
+
try:
|
| 1172 |
+
result_box.append(str(Crew(agents=agents, tasks=tasks).kickoff()))
|
| 1173 |
+
except Exception as exc:
|
| 1174 |
+
error_box.append(exc)
|
| 1175 |
+
|
| 1176 |
+
worker = threading.Thread(target=_run, daemon=True)
|
| 1177 |
+
worker.start()
|
| 1178 |
+
worker.join(timeout=timeout_s)
|
| 1179 |
+
|
| 1180 |
+
if worker.is_alive():
|
| 1181 |
+
# Thread is still running β we can't forcibly kill it, but we
|
| 1182 |
+
# raise so the caller falls back to the deterministic template.
|
| 1183 |
raise TimeoutError(f"Crew kickoff exceeded {timeout_s}s timeout")
|
| 1184 |
|
| 1185 |
+
if error_box:
|
| 1186 |
+
raise error_box[0]
|
| 1187 |
+
|
| 1188 |
+
if result_box:
|
| 1189 |
+
return result_box[0]
|
| 1190 |
+
|
| 1191 |
+
raise RuntimeError("Crew kickoff returned no result")
|
|
|
|
| 1192 |
|
| 1193 |
def _condense_failure_log(self, raw_text: str, kind: str) -> str:
|
| 1194 |
if not raw_text:
|
|
|
|
| 1434 |
verbose=self.verbose,
|
| 1435 |
strategy=self.strategy.name
|
| 1436 |
)
|
| 1437 |
+
|
| 1438 |
+
# Reviewer agent β checks the designer's output for common issues
|
| 1439 |
+
reviewer = Agent(
|
| 1440 |
+
role="RTL Reviewer",
|
| 1441 |
+
goal="Review generated RTL for completeness, lint issues, and Verilator compatibility",
|
| 1442 |
+
backstory="""Senior RTL reviewer who catches missing reset logic, width mismatches,
|
| 1443 |
+
undriven outputs, and Verilator-incompatible constructs. You verify that:
|
| 1444 |
+
1. All outputs are driven in all code paths
|
| 1445 |
+
2. All registers are reset
|
| 1446 |
+
3. Width mismatches are flagged
|
| 1447 |
+
4. Module name matches the design name
|
| 1448 |
+
5. No placeholders or TODO comments remain
|
| 1449 |
+
You return the FINAL corrected code in ```verilog``` fences.""",
|
| 1450 |
+
llm=self.llm,
|
| 1451 |
+
verbose=False,
|
| 1452 |
+
tools=[syntax_check_tool, read_file_tool],
|
| 1453 |
+
allow_delegation=False
|
| 1454 |
+
)
|
| 1455 |
|
| 1456 |
rtl_task = Task(
|
| 1457 |
description=f"""Design module "{self.name}" based on SPEC.
|
|
|
|
| 1473 |
5. **MODULAR HIERARCHY**: For complex designs, break them into smaller sub-modules. Output ALL modules in your response.
|
| 1474 |
6. Return code in ```verilog fence.
|
| 1475 |
""",
|
| 1476 |
+
expected_output='Complete Verilog RTL Code',
|
| 1477 |
agent=rtl_agent
|
| 1478 |
)
|
| 1479 |
+
|
| 1480 |
+
review_task = Task(
|
| 1481 |
+
description=f"""Review the RTL code generated by the designer for module "{self.name}".
|
| 1482 |
+
|
| 1483 |
+
Check for these common issues:
|
| 1484 |
+
1. Module name must be exactly "{self.name}"
|
| 1485 |
+
2. All always_comb blocks must assign ALL variables in ALL branches (no latches)
|
| 1486 |
+
3. Width mismatches (e.g., 2-bit signal assigned to 3-bit variable)
|
| 1487 |
+
4. All outputs must be driven
|
| 1488 |
+
5. All registers must be reset in the reset branch
|
| 1489 |
+
6. No placeholders, TODOs, or simplified logic
|
| 1490 |
+
|
| 1491 |
+
If you find issues, FIX them and output the corrected code.
|
| 1492 |
+
If the code is correct, output it unchanged.
|
| 1493 |
+
ALWAYS return the COMPLETE code in ```verilog``` fences.
|
| 1494 |
+
""",
|
| 1495 |
+
expected_output='Reviewed and corrected Verilog RTL Code in ```verilog``` fences',
|
| 1496 |
+
agent=reviewer
|
| 1497 |
+
)
|
| 1498 |
|
| 1499 |
with console.status(f"[bold yellow]Generating RTL ({self.strategy.name})...[/bold yellow]"):
|
| 1500 |
+
result = Crew(
|
| 1501 |
+
agents=[rtl_agent, reviewer],
|
| 1502 |
+
tasks=[rtl_task, review_task],
|
| 1503 |
+
verbose=self.verbose
|
| 1504 |
+
).kickoff()
|
| 1505 |
|
| 1506 |
rtl_code = str(result)
|
| 1507 |
self.logger.info(f"GENERATED RTL ({self.strategy.name}):\n{rtl_code}")
|
|
|
|
| 1650 |
new_code = str(result)
|
| 1651 |
self.logger.info(f"FIXED RTL:\n{new_code}")
|
| 1652 |
|
| 1653 |
+
# --- Inner retry loop for LLM parse errors ---
|
| 1654 |
+
# If write_verilog fails (LLM didn't output valid code), re-prompt immediately
|
| 1655 |
+
# instead of returning to the main loop (which would re-check the stale file
|
| 1656 |
+
# and trigger the fingerprint detector).
|
| 1657 |
+
_inner_code = new_code
|
| 1658 |
+
for _parse_retry in range(3): # up to 3 immediate retries for parse errors
|
| 1659 |
+
new_path = write_verilog(self.name, _inner_code)
|
| 1660 |
+
if not (isinstance(new_path, str) and new_path.startswith("Error:")):
|
| 1661 |
+
break # write succeeded
|
| 1662 |
+
|
| 1663 |
+
self.log(f"File Write Error in FIX stage (parse retry {_parse_retry + 1}/3): {new_path}", refined=True)
|
| 1664 |
+
|
| 1665 |
+
if _parse_retry >= 2:
|
| 1666 |
+
# Exhausted parse retries β clear fingerprint and fall back to main retry
|
| 1667 |
+
# Clear the fingerprint so the next main-loop iteration gets a real attempt
|
| 1668 |
+
self._clear_last_fingerprint(str(errors))
|
| 1669 |
+
retry_count = self._bump_state_retry()
|
| 1670 |
+
if retry_count >= self.max_retries:
|
| 1671 |
+
self.log("Write error persisted after max retries. Failing.", refined=True)
|
| 1672 |
+
self.state = BuildState.FAIL
|
| 1673 |
+
else:
|
| 1674 |
+
self.log(f"Retrying fix via main loop (attempt {retry_count}).", refined=True)
|
| 1675 |
+
return
|
| 1676 |
+
|
| 1677 |
+
# Re-prompt the LLM immediately with explicit format instructions
|
| 1678 |
+
reformat_prompt = f"""Your previous response could NOT be parsed as Verilog code.
|
| 1679 |
+
The parser said: {new_path}
|
| 1680 |
+
|
| 1681 |
+
You MUST output the COMPLETE Verilog module inside ```verilog``` fences.
|
| 1682 |
+
Do NOT output only a description or explanation β output the FULL code.
|
| 1683 |
+
|
| 1684 |
+
Here is the current code that needs the lint fixes applied:
|
| 1685 |
+
```verilog
|
| 1686 |
+
{self.artifacts['rtl_code']}
|
| 1687 |
+
```
|
| 1688 |
+
|
| 1689 |
+
Original errors to fix:
|
| 1690 |
+
{errors_for_llm}
|
| 1691 |
+
|
| 1692 |
+
OUTPUT FORMAT: You must respond with the complete fixed Verilog code inside ```verilog``` fences.
|
| 1693 |
+
"""
|
| 1694 |
+
reformat_task = Task(
|
| 1695 |
+
description=reformat_prompt,
|
| 1696 |
+
expected_output="Complete fixed Verilog code inside ```verilog``` fences",
|
| 1697 |
+
agent=fixer
|
| 1698 |
+
)
|
| 1699 |
+
with console.status("[bold yellow]Re-prompting LLM for valid Verilog output...[/bold yellow]"):
|
| 1700 |
+
reformat_result = Crew(agents=[fixer], tasks=[reformat_task]).kickoff()
|
| 1701 |
+
_inner_code = str(reformat_result)
|
| 1702 |
+
self.logger.info(f"REFORMATTED RTL (parse retry {_parse_retry + 1}):\n{_inner_code}")
|
| 1703 |
+
|
| 1704 |
if isinstance(new_path, str) and new_path.startswith("Error:"):
|
| 1705 |
+
return # already handled above
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1706 |
|
| 1707 |
self.artifacts['rtl_path'] = new_path
|
| 1708 |
# Read back the CLEANED version, not raw LLM output
|
|
|
|
| 1733 |
self.logger.info(f"GOLDEN TESTBENCH:\n{tb_code}")
|
| 1734 |
tb_path = write_verilog(self.name, tb_code, is_testbench=True)
|
| 1735 |
self.artifacts['tb_path'] = tb_path
|
| 1736 |
+
self._clear_tb_fingerprints() # New TB β fresh gate attempts
|
| 1737 |
else:
|
| 1738 |
self.log("Generating Testbench...", refined=True)
|
| 1739 |
tb_agent = get_testbench_agent(self.llm, f"Verify {self.name}", verbose=self.verbose, strategy=self.strategy.name)
|
|
|
|
| 1808 |
self.state = BuildState.FAIL
|
| 1809 |
return
|
| 1810 |
self.artifacts['tb_path'] = tb_path
|
| 1811 |
+
self._clear_tb_fingerprints() # New TB β fresh gate attempts
|
| 1812 |
else:
|
| 1813 |
self.log(f"Verifying with existing Testbench (Attempt {self.retry_count}).", refined=True)
|
| 1814 |
# Verify file exists
|
|
|
|
| 1922 |
# --- AUTONOMOUS FIX: Try to fix compilation errors without LLM ---
|
| 1923 |
# Auto-fixes removed (Verilator supports SV natively)
|
| 1924 |
|
| 1925 |
+
# --- LLM ERROR ANALYSIS + FIX: Collaborative 2-agent Crew ---
|
| 1926 |
analyst = get_error_analyst_agent(self.llm, verbose=self.verbose)
|
| 1927 |
analysis_task = Task(
|
| 1928 |
description=f'''Analyze this Verification Failure for "{self.name}".
|
|
|
|
| 1936 |
CURRENT TESTBENCH (first 3000 chars):
|
| 1937 |
{tb_code[:3000]}
|
| 1938 |
|
| 1939 |
+
Use your read_file tool to read the full RTL and TB files if needed.
|
| 1940 |
+
|
| 1941 |
Classify the failure as ONE of:
|
| 1942 |
A) TESTBENCH_SYNTAX β TB compilation/syntax error (missing semicolons, undeclared signals, class errors)
|
| 1943 |
B) RTL_LOGIC_BUG β Functional error in RTL design (wrong state transitions, bad arithmetic, logic errors)
|
|
|
|
| 2032 |
port_info = self._extract_module_interface(self.artifacts['rtl_code'])
|
| 2033 |
fix_prompt = f"""Fix the Testbench logic/syntax.
|
| 2034 |
|
| 2035 |
+
DIAGNOSIS FROM ERROR ANALYST:
|
| 2036 |
ROOT CAUSE: {root_cause}
|
| 2037 |
FIX HINT: {fix_hint}
|
| 2038 |
|
|
|
|
| 2055 |
PREVIOUS ATTEMPTS:
|
| 2056 |
{self._format_failure_history()}
|
| 2057 |
|
| 2058 |
+
CRITICAL RULES:
|
| 2059 |
- Return ONLY the fixed Testbench code in ```verilog fences.
|
| 2060 |
- Do NOT invent ports that aren't in the MODULE INTERFACE above.
|
| 2061 |
- Module name of DUT is "{self.name}"
|
| 2062 |
+
- NEVER use: class, interface, covergroup, program, rand, virtual, new()
|
| 2063 |
+
- Use flat procedural style: reg/wire declarations, initial/always blocks
|
| 2064 |
+
- Use your syntax_check tool to verify the fix compiles before returning it
|
| 2065 |
"""
|
| 2066 |
else:
|
| 2067 |
self.log("Analyst identified RTL Logic Error. Fixing RTL...", refined=True)
|
|
|
|
| 2071 |
|
| 2072 |
fix_prompt = f"""Fix the RTL logic to pass verification.
|
| 2073 |
|
| 2074 |
+
DIAGNOSIS FROM ERROR ANALYST:
|
| 2075 |
ROOT CAUSE: {root_cause}
|
| 2076 |
FIX HINT: {fix_hint}
|
| 2077 |
|
|
|
|
| 2100 |
CRITICAL:
|
| 2101 |
- Address the ROOT CAUSE and FIX HINT above directly.
|
| 2102 |
- Maintain design intent from the architecture spec.
|
| 2103 |
+
- Use your syntax_check tool to verify the fix compiles before returning it.
|
| 2104 |
- Return ONLY the fixed {self.strategy.name} logic in ```verilog fences.
|
| 2105 |
"""
|
| 2106 |
|
| 2107 |
+
# Execute Fix β fixer uses analyst's diagnosis as context
|
| 2108 |
fix_task = Task(
|
| 2109 |
description=fix_prompt,
|
| 2110 |
+
expected_output="Fixed Verilog Code in ```verilog fences",
|
| 2111 |
agent=fixer
|
| 2112 |
)
|
| 2113 |
|
| 2114 |
with console.status("[bold yellow]AI Implementing Fix...[/bold yellow]"):
|
| 2115 |
+
result = Crew(
|
| 2116 |
+
agents=[fixer],
|
| 2117 |
+
tasks=[fix_task],
|
| 2118 |
+
verbose=self.verbose
|
| 2119 |
+
).kickoff()
|
| 2120 |
fixed_code = str(result)
|
| 2121 |
self.logger.info(f"FIXED CODE:\n{fixed_code}")
|
| 2122 |
|
|
|
|
| 2396 |
|
| 2397 |
coverage_checks = {
|
| 2398 |
"line": line_pct >= float(thresholds["line"]),
|
| 2399 |
+
"branch": branch_pct >= float(thresholds["branch"]),
|
| 2400 |
"toggle": toggle_pct >= float(thresholds["toggle"]),
|
| 2401 |
"functional": functional_pct >= float(thresholds["functional"]),
|
| 2402 |
}
|
|
|
|
| 2464 |
|
| 2465 |
tb_agent = get_testbench_agent(self.llm, f"Improve coverage for {self.name}", verbose=self.verbose, strategy=self.strategy.name)
|
| 2466 |
|
| 2467 |
+
branch_target = float(thresholds['branch'])
|
| 2468 |
improve_prompt = f"""The current testbench for "{self.name}" does not meet coverage thresholds.
|
| 2469 |
+
TARGET: Branch >={branch_target:.1f}%, Line >={float(thresholds['line']):.1f}%.
|
| 2470 |
Current Coverage Data: {coverage_data}
|
| 2471 |
|
| 2472 |
Current RTL:
|
|
|
|
| 2480 |
```
|
| 2481 |
|
| 2482 |
Create an IMPROVED self-checking testbench that:
|
| 2483 |
+
1. Achieves >={branch_target:.1f}% branch coverage by hitting all missing branches.
|
| 2484 |
2. Tests all FSM states (not just happy path)
|
| 2485 |
3. Exercises all conditional branches (if/else, case)
|
| 2486 |
3. Tests reset behavior mid-operation
|
|
|
|
| 3125 |
self.log(f"GDSII generated: {result}", refined=True)
|
| 3126 |
self.transition(BuildState.CONVERGENCE_REVIEW)
|
| 3127 |
else:
|
| 3128 |
+
# ββ Self-Reflective Retry via SelfReflectPipeline ββ
|
| 3129 |
+
self.log(f"Hardening failed. Activating self-reflection retry...", refined=True)
|
| 3130 |
+
try:
|
| 3131 |
+
reflect_pipeline = SelfReflectPipeline(
|
| 3132 |
+
llm=self.llm,
|
| 3133 |
+
max_retries=3,
|
| 3134 |
+
verbose=self.verbose,
|
| 3135 |
+
on_reflection=lambda evt: self.log(
|
| 3136 |
+
f"[Self-Reflect] {evt.get('category','')}: {evt.get('reflection','')[:120]}",
|
| 3137 |
+
refined=True
|
| 3138 |
+
),
|
| 3139 |
+
)
|
| 3140 |
+
|
| 3141 |
+
def _hardening_action():
|
| 3142 |
+
"""Re-run OpenLane and return (success, error_msg, metrics)."""
|
| 3143 |
+
new_tag = f"agentrun_{self.global_step_count}_{int(time.time()) % 10000}"
|
| 3144 |
+
ok, res = run_openlane(
|
| 3145 |
+
self.name, background=False, run_tag=new_tag,
|
| 3146 |
+
floorplan_tcl=self.artifacts.get("floorplan_tcl", ""),
|
| 3147 |
+
pdk_name=pdk_name,
|
| 3148 |
+
)
|
| 3149 |
+
if ok:
|
| 3150 |
+
self.artifacts['gds'] = res
|
| 3151 |
+
self.artifacts['run_tag'] = new_tag
|
| 3152 |
+
return ok, res if not ok else "", {}
|
| 3153 |
+
|
| 3154 |
+
def _hardening_fix(action):
|
| 3155 |
+
"""Apply a corrective action from self-reflection."""
|
| 3156 |
+
if action.action_type == "adjust_config":
|
| 3157 |
+
# Common fix: increase die area or relax utilisation
|
| 3158 |
+
self.log(f"Applying config fix: {action.description}", refined=True)
|
| 3159 |
+
return True # Mark as applied; the next retry re-generates config
|
| 3160 |
+
elif action.action_type == "modify_rtl":
|
| 3161 |
+
self.log(f"RTL modification suggested: {action.description}", refined=True)
|
| 3162 |
+
return True
|
| 3163 |
+
return False
|
| 3164 |
+
|
| 3165 |
+
rtl_summary = self.artifacts.get("rtl_code", "")[:2000]
|
| 3166 |
+
ok, msg, reflections = reflect_pipeline.run_with_retry(
|
| 3167 |
+
stage_name="OpenLane Hardening",
|
| 3168 |
+
action_fn=_hardening_action,
|
| 3169 |
+
fix_fn=_hardening_fix,
|
| 3170 |
+
rtl_summary=rtl_summary,
|
| 3171 |
+
)
|
| 3172 |
+
|
| 3173 |
+
if ok:
|
| 3174 |
+
self.log(f"Hardening recovered via self-reflection: {msg}", refined=True)
|
| 3175 |
+
self.artifacts['self_reflect_history'] = reflect_pipeline.get_summary()
|
| 3176 |
+
self.transition(BuildState.CONVERGENCE_REVIEW)
|
| 3177 |
+
else:
|
| 3178 |
+
self.log(f"Hardening failed after self-reflection: {msg}", refined=True)
|
| 3179 |
+
self.artifacts['self_reflect_history'] = reflect_pipeline.get_summary()
|
| 3180 |
+
self.state = BuildState.FAIL
|
| 3181 |
+
except Exception as e:
|
| 3182 |
+
self.logger.warning(f"SelfReflectPipeline error: {e}")
|
| 3183 |
+
self.log(f"Hardening Failed: {result}", refined=True)
|
| 3184 |
+
self.state = BuildState.FAIL
|
| 3185 |
|
| 3186 |
def do_signoff(self):
|
| 3187 |
"""Performs full fabrication-readiness signoff: DRC/LVS, timing closure, power analysis."""
|
src/agentic/tools/vlsi_tools.py
CHANGED
|
@@ -247,8 +247,16 @@ def write_verilog(design_name: str, code: str, is_testbench: bool = False, suffi
|
|
| 247 |
# Only strip if the line is before the first 'module'
|
| 248 |
|
| 249 |
# Prevent Verilator syntax errors from normal comments starting with "verilator"
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
module_pos = clean_code.find('module')
|
| 253 |
if module_pos > 0:
|
| 254 |
preamble = clean_code[:module_pos]
|
|
@@ -299,7 +307,7 @@ def write_verilog(design_name: str, code: str, is_testbench: bool = False, suffi
|
|
| 299 |
clean_code += "\n"
|
| 300 |
|
| 301 |
# --- MULTI-FILE RTL HIERARCHY SPLITTING ---
|
| 302 |
-
if not is_testbench and "module" in clean_code:
|
| 303 |
import glob
|
| 304 |
# Remove old RTL files to prevent stale modules from breaking build
|
| 305 |
src_dir = os.path.dirname(path)
|
|
@@ -373,6 +381,8 @@ def run_syntax_check(file_path: str) -> tuple:
|
|
| 373 |
def run_lint_check(file_path: str) -> tuple:
|
| 374 |
"""
|
| 375 |
Runs Verilator --lint-only for stricter static analysis.
|
|
|
|
|
|
|
| 376 |
Returns: (True, "OK") or (False, ErrorLog)
|
| 377 |
"""
|
| 378 |
if not os.path.exists(file_path):
|
|
@@ -384,8 +394,18 @@ def run_lint_check(file_path: str) -> tuple:
|
|
| 384 |
if file_path not in rtl_files and os.path.exists(file_path):
|
| 385 |
rtl_files.append(file_path)
|
| 386 |
|
| 387 |
-
#
|
| 388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
|
| 390 |
try:
|
| 391 |
result = subprocess.run(
|
|
@@ -393,16 +413,35 @@ def run_lint_check(file_path: str) -> tuple:
|
|
| 393 |
capture_output=True, text=True,
|
| 394 |
timeout=30
|
| 395 |
)
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
except FileNotFoundError:
|
| 408 |
return True, "Verilator not found (Skipping Lint)"
|
|
@@ -410,6 +449,49 @@ def run_lint_check(file_path: str) -> tuple:
|
|
| 410 |
return False, "Lint check timed out."
|
| 411 |
|
| 412 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
def run_semantic_rigor_check(file_path: str) -> Tuple[bool, Dict[str, Any]]:
|
| 414 |
"""Deterministic semantic preflight for width-safety and port-shadowing."""
|
| 415 |
report: Dict[str, Any] = {
|
|
@@ -618,6 +700,42 @@ def _extract_disable_iff(condition: str) -> Tuple[str, str]:
|
|
| 618 |
return disable_cond, cond.strip()
|
| 619 |
|
| 620 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 621 |
def _split_sva_implication(condition: str) -> Tuple[str, str, str]:
|
| 622 |
"""Split implication into antecedent/operator/consequent."""
|
| 623 |
match = re.match(r'(.+?)\s*(\|->|\|=>)\s*(.+)', condition.strip(), re.DOTALL)
|
|
@@ -687,8 +805,17 @@ def convert_sva_to_yosys(sva_content: str, module_name: str) -> str:
|
|
| 687 |
|
| 688 |
Supports implication forms `|->` and `|=>` plus bounded `##N` delays by
|
| 689 |
generating per-property trigger shift registers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
"""
|
| 691 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
if not port_match:
|
| 693 |
return ""
|
| 694 |
|
|
@@ -698,7 +825,11 @@ def convert_sva_to_yosys(sva_content: str, module_name: str) -> str:
|
|
| 698 |
line = line.strip()
|
| 699 |
if line and not line.startswith('//'):
|
| 700 |
port_lines.append(line.rstrip(','))
|
|
|
|
|
|
|
|
|
|
| 701 |
|
|
|
|
| 702 |
raw_properties = re.findall(r'property\s+(\w+)\s*;(.*?)endproperty', sva_content, re.DOTALL)
|
| 703 |
properties = []
|
| 704 |
for prop_name, body in raw_properties:
|
|
@@ -708,6 +839,81 @@ def convert_sva_to_yosys(sva_content: str, module_name: str) -> str:
|
|
| 708 |
condition = body_match.group(2).strip()
|
| 709 |
properties.append((prop_name, clk, condition))
|
| 710 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
yosys_code = f'''// AUTO-GENERATED: Yosys-compatible assertions for {module_name}
|
| 712 |
// Original industry-standard SVA is preserved in {module_name}_sva.sv
|
| 713 |
// This file is used ONLY for open-source formal verification (SymbiYosys)
|
|
@@ -759,8 +965,8 @@ module {module_name}_sby_check (
|
|
| 759 |
base_delay = 0 if op == "|->" else 1
|
| 760 |
extra_delay, consequent_expr = _consume_delay_prefix(consequent)
|
| 761 |
total_delay = base_delay + extra_delay
|
| 762 |
-
antecedent_expr = antecedent if antecedent else "1'b1"
|
| 763 |
-
consequent_expr = consequent_expr if consequent_expr else "1'b1"
|
| 764 |
|
| 765 |
if total_delay == 0:
|
| 766 |
if disable_cond:
|
|
@@ -789,9 +995,9 @@ module {module_name}_sby_check (
|
|
| 789 |
else:
|
| 790 |
delayed_match = re.match(r'^\(?\s*(.+?)\s*##\s*(\d+)\s*(.+?)\s*\)?$', cond, re.DOTALL)
|
| 791 |
if delayed_match:
|
| 792 |
-
antecedent_expr = delayed_match.group(1).strip()
|
| 793 |
total_delay = int(delayed_match.group(2))
|
| 794 |
-
consequent_expr = delayed_match.group(3).strip()
|
| 795 |
trig_name = f"p_trig_{idx}"
|
| 796 |
trigger_defs.append(f" reg [{total_delay}:0] {trig_name} = '0;")
|
| 797 |
if disable_cond:
|
|
@@ -809,6 +1015,7 @@ module {module_name}_sby_check (
|
|
| 809 |
block_lines.append(f" {trig_name}[{stage + 1}] <= {trig_name}[{stage}];")
|
| 810 |
block_lines.append(f" if (init_done && {trig_name}[{total_delay}]) assert({consequent_expr});")
|
| 811 |
else:
|
|
|
|
| 812 |
if disable_cond:
|
| 813 |
block_lines.append(f" if (!({disable_cond}) && init_done) assert({cond});")
|
| 814 |
else:
|
|
@@ -820,6 +1027,25 @@ module {module_name}_sby_check (
|
|
| 820 |
if trigger_defs:
|
| 821 |
yosys_code += "\n".join(trigger_defs) + "\n\n"
|
| 822 |
yosys_code += "\n".join(property_blocks)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
yosys_code += f'''endmodule
|
| 824 |
|
| 825 |
// Bind to DUT
|
|
@@ -834,10 +1060,15 @@ def write_sby_config(design_name, use_sby_check: bool = True):
|
|
| 834 |
design_name: Name of the design
|
| 835 |
use_sby_check: If True, use the Yosys-compatible _sby_check.sv file
|
| 836 |
"""
|
| 837 |
-
|
|
|
|
| 838 |
|
| 839 |
sva_file = f"{design_name}_sby_check.sv" if use_sby_check else f"{design_name}_sva.sv"
|
| 840 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
config = f"""[options]
|
| 842 |
mode prove
|
| 843 |
|
|
@@ -850,8 +1081,8 @@ read -formal {sva_file}
|
|
| 850 |
prep -top {design_name}
|
| 851 |
|
| 852 |
[files]
|
| 853 |
-
{
|
| 854 |
-
{
|
| 855 |
"""
|
| 856 |
with open(path, "w") as f:
|
| 857 |
f.write(config)
|
|
@@ -1031,14 +1262,26 @@ def run_tb_static_contract_check(tb_code: str, strategy: str = "SV_MODULAR") ->
|
|
| 1031 |
|
| 1032 |
strategy_norm = str(strategy).upper()
|
| 1033 |
if "SV_MODULAR" in strategy_norm:
|
| 1034 |
-
|
| 1035 |
-
|
| 1036 |
-
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1042 |
|
| 1043 |
# Disallow problematic constructs in this flow.
|
| 1044 |
unsupported = [
|
|
@@ -1188,6 +1431,12 @@ def run_tb_compile_gate(design_name: str, tb_path: str, rtl_path: str) -> Tuple[
|
|
| 1188 |
categories.add("covergroup_scope_error")
|
| 1189 |
if "pin not found" in low or "pinnotfound" in low:
|
| 1190 |
categories.add("pin_mismatch")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1191 |
if not categories:
|
| 1192 |
categories.add("compile_error")
|
| 1193 |
report["issue_categories"] = sorted(categories)
|
|
@@ -1195,15 +1444,324 @@ def run_tb_compile_gate(design_name: str, tb_path: str, rtl_path: str) -> Tuple[
|
|
| 1195 |
fp_base = "|".join(report["issue_categories"]) + "|" + "\n".join(report["diagnostics"][:6])
|
| 1196 |
report["fingerprint"] = hashlib.sha256(fp_base.encode("utf-8", errors="ignore")).hexdigest()[:16]
|
| 1197 |
report["ok"] = result.returncode == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1198 |
return report["ok"], report
|
| 1199 |
|
| 1200 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1201 |
def repair_tb_for_verilator(tb_code: str, compile_report: Dict[str, Any]) -> str:
|
| 1202 |
-
"""Deterministic repair pass for common Verilator TB incompatibilities.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1203 |
fixed = tb_code or ""
|
| 1204 |
if not fixed.strip():
|
| 1205 |
return fixed
|
| 1206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1207 |
interface_names = set(re.findall(r"^\s*interface\s+([A-Za-z_]\w*)\b", fixed, flags=re.MULTILINE))
|
| 1208 |
interface_names.update(re.findall(r"\b([A-Za-z_]\w*_if)\b", fixed))
|
| 1209 |
interface_names = {x for x in interface_names if x}
|
|
@@ -1276,12 +1834,67 @@ def repair_tb_for_verilator(tb_code: str, compile_report: Dict[str, Any]) -> str
|
|
| 1276 |
flags=re.MULTILINE,
|
| 1277 |
)
|
| 1278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1279 |
# Clean excessive blank runs after rewrites.
|
| 1280 |
fixed = re.sub(r"\n{3,}", "\n\n", fixed)
|
| 1281 |
if not fixed.endswith("\n"):
|
| 1282 |
fixed += "\n"
|
| 1283 |
return fixed
|
| 1284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
def run_simulation(design_name: str) -> tuple:
|
| 1286 |
"""
|
| 1287 |
Compiles and runs the testbench simulation using Verilator (Production Mode).
|
|
@@ -1411,7 +2024,13 @@ def run_openlane(
|
|
| 1411 |
"./flow.tcl", "-design", design_name, "-tag", run_tag, "-overwrite", "-ignore_mismatches"
|
| 1412 |
]
|
| 1413 |
if floorplan_tcl:
|
| 1414 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1415 |
|
| 1416 |
if background:
|
| 1417 |
log_file_path = os.path.join(design_dir, "harden.log")
|
|
|
|
| 247 |
# Only strip if the line is before the first 'module'
|
| 248 |
|
| 249 |
# Prevent Verilator syntax errors from normal comments starting with "verilator"
|
| 250 |
+
# BUT preserve legitimate Verilator pragmas (lint_off, lint_on, public, etc.)
|
| 251 |
+
_VERILATOR_PRAGMAS = r'lint_off|lint_on|public|no_inline|split_var|coverage_off|coverage_on|tracing_off|tracing_on'
|
| 252 |
+
clean_code = re.sub(
|
| 253 |
+
r'(?i)(//\s*)(verilator)\b(?!\s*(?:' + _VERILATOR_PRAGMAS + r'))',
|
| 254 |
+
r'\1[\2]', clean_code,
|
| 255 |
+
)
|
| 256 |
+
clean_code = re.sub(
|
| 257 |
+
r'(?i)(/\*\s*)(verilator)\b(?!\s*(?:' + _VERILATOR_PRAGMAS + r'))',
|
| 258 |
+
r'\1[\2]', clean_code,
|
| 259 |
+
)
|
| 260 |
module_pos = clean_code.find('module')
|
| 261 |
if module_pos > 0:
|
| 262 |
preamble = clean_code[:module_pos]
|
|
|
|
| 307 |
clean_code += "\n"
|
| 308 |
|
| 309 |
# --- MULTI-FILE RTL HIERARCHY SPLITTING ---
|
| 310 |
+
if not is_testbench and ext == ".v" and "module" in clean_code:
|
| 311 |
import glob
|
| 312 |
# Remove old RTL files to prevent stale modules from breaking build
|
| 313 |
src_dir = os.path.dirname(path)
|
|
|
|
| 381 |
def run_lint_check(file_path: str) -> tuple:
|
| 382 |
"""
|
| 383 |
Runs Verilator --lint-only for stricter static analysis.
|
| 384 |
+
Uses -Wno-fatal so warnings don't cause non-zero exit.
|
| 385 |
+
Falls back to iverilog if Verilator reports only warnings (no real errors).
|
| 386 |
Returns: (True, "OK") or (False, ErrorLog)
|
| 387 |
"""
|
| 388 |
if not os.path.exists(file_path):
|
|
|
|
| 394 |
if file_path not in rtl_files and os.path.exists(file_path):
|
| 395 |
rtl_files.append(file_path)
|
| 396 |
|
| 397 |
+
# --sv: force SystemVerilog parsing (critical for typedef, logic, always_comb)
|
| 398 |
+
# -Wno-fatal: don't exit on warnings β let us separate real errors from warnings
|
| 399 |
+
# Suppress informational warnings that are not bugs:
|
| 400 |
+
cmd = [
|
| 401 |
+
"verilator", "--lint-only", "--sv", "--timing",
|
| 402 |
+
"-Wno-fatal", # warnings don't cause non-zero exit
|
| 403 |
+
"-Wno-UNUSED", # unused signals (common in AI-generated code)
|
| 404 |
+
"-Wno-PINMISSING", # missing port connections
|
| 405 |
+
"-Wno-CASEINCOMPLETE", # incomplete case (handled by default)
|
| 406 |
+
"-Wno-WIDTHEXPAND", # zero-extension (harmless implicit widening)
|
| 407 |
+
"-Wno-WIDTHTRUNC", # truncation (flag separately in semantic check)
|
| 408 |
+
] + rtl_files
|
| 409 |
|
| 410 |
try:
|
| 411 |
result = subprocess.run(
|
|
|
|
| 413 |
capture_output=True, text=True,
|
| 414 |
timeout=30
|
| 415 |
)
|
| 416 |
+
stderr = result.stderr.strip()
|
| 417 |
+
|
| 418 |
+
if result.returncode == 0:
|
| 419 |
+
# Check for remaining warnings (non-fatal)
|
| 420 |
+
if stderr:
|
| 421 |
+
# Parse for LATCH warnings β these are fixable and important
|
| 422 |
+
has_latch = bool(re.search(r'%Warning-LATCH:', stderr))
|
| 423 |
+
if has_latch:
|
| 424 |
+
# LATCH is a real design issue β fail so the LLM can fix it
|
| 425 |
+
return False, f"Verilator Lint Errors:\n{stderr}"
|
| 426 |
+
# Other warnings are informational, pass with report
|
| 427 |
+
return True, f"Lint OK (with warnings):\n{stderr}"
|
| 428 |
+
return True, "Lint OK"
|
| 429 |
+
|
| 430 |
+
# Non-zero exit: check if there are REAL %Error lines (not just "Exiting due to N warning(s)")
|
| 431 |
+
real_errors = [
|
| 432 |
+
line for line in stderr.splitlines()
|
| 433 |
+
if line.strip().startswith("%Error") and "Exiting due to" not in line
|
| 434 |
+
]
|
| 435 |
+
|
| 436 |
+
if not real_errors:
|
| 437 |
+
# Only warnings caused the exit β try iverilog fallback
|
| 438 |
+
iverilog_ok, iverilog_report = run_iverilog_lint(file_path)
|
| 439 |
+
if iverilog_ok:
|
| 440 |
+
return True, f"Lint OK (Verilator warnings only, iverilog passed):\n{stderr}"
|
| 441 |
+
else:
|
| 442 |
+
return False, f"Verilator Lint Errors:\n{stderr}\n\niverilog also failed:\n{iverilog_report}"
|
| 443 |
+
|
| 444 |
+
return False, f"Verilator Lint Errors:\n{stderr}"
|
| 445 |
|
| 446 |
except FileNotFoundError:
|
| 447 |
return True, "Verilator not found (Skipping Lint)"
|
|
|
|
| 449 |
return False, "Lint check timed out."
|
| 450 |
|
| 451 |
|
| 452 |
+
def run_iverilog_lint(file_path: str) -> tuple:
|
| 453 |
+
"""
|
| 454 |
+
Fallback lint check using Icarus Verilog (iverilog).
|
| 455 |
+
iverilog is an industry-standard open-source simulator used widely in
|
| 456 |
+
academia and production for syntax/semantic validation.
|
| 457 |
+
Returns: (True, "OK") or (False, ErrorLog)
|
| 458 |
+
"""
|
| 459 |
+
if not os.path.exists(file_path):
|
| 460 |
+
return False, f"File not found: {file_path}"
|
| 461 |
+
|
| 462 |
+
import glob
|
| 463 |
+
src_dir = os.path.dirname(file_path)
|
| 464 |
+
rtl_files = [f for f in glob.glob(os.path.join(src_dir, "*.v")) if not f.endswith("_tb.v") and "regression" not in f]
|
| 465 |
+
if file_path not in rtl_files and os.path.exists(file_path):
|
| 466 |
+
rtl_files.append(file_path)
|
| 467 |
+
|
| 468 |
+
# -g2012: IEEE 1800-2012 SystemVerilog standard
|
| 469 |
+
# -Wall: enable all warnings
|
| 470 |
+
# -o /dev/null: don't produce output binary (lint-only mode)
|
| 471 |
+
cmd = ["iverilog", "-g2012", "-Wall", "-o", "/dev/null"] + rtl_files
|
| 472 |
+
|
| 473 |
+
try:
|
| 474 |
+
result = subprocess.run(
|
| 475 |
+
cmd,
|
| 476 |
+
capture_output=True, text=True,
|
| 477 |
+
timeout=30
|
| 478 |
+
)
|
| 479 |
+
combined = (result.stdout + "\n" + result.stderr).strip()
|
| 480 |
+
|
| 481 |
+
# iverilog returns 0 on success, non-zero on errors
|
| 482 |
+
if result.returncode == 0:
|
| 483 |
+
if combined:
|
| 484 |
+
return True, f"iverilog OK (with warnings):\n{combined}"
|
| 485 |
+
return True, "iverilog OK"
|
| 486 |
+
|
| 487 |
+
return False, f"iverilog Lint Errors:\n{combined}"
|
| 488 |
+
|
| 489 |
+
except FileNotFoundError:
|
| 490 |
+
return False, "iverilog not found (install with: apt install iverilog)"
|
| 491 |
+
except subprocess.TimeoutExpired:
|
| 492 |
+
return False, "iverilog lint check timed out."
|
| 493 |
+
|
| 494 |
+
|
| 495 |
def run_semantic_rigor_check(file_path: str) -> Tuple[bool, Dict[str, Any]]:
|
| 496 |
"""Deterministic semantic preflight for width-safety and port-shadowing."""
|
| 497 |
report: Dict[str, Any] = {
|
|
|
|
| 700 |
return disable_cond, cond.strip()
|
| 701 |
|
| 702 |
|
| 703 |
+
def _balance_parens(expr: str) -> str:
|
| 704 |
+
"""Ensure parentheses are balanced, stripping outermost wrapper if unbalanced."""
|
| 705 |
+
expr = expr.strip()
|
| 706 |
+
depth = 0
|
| 707 |
+
for ch in expr:
|
| 708 |
+
if ch == '(':
|
| 709 |
+
depth += 1
|
| 710 |
+
elif ch == ')':
|
| 711 |
+
depth -= 1
|
| 712 |
+
# If more open than close, add closing parens
|
| 713 |
+
if depth > 0:
|
| 714 |
+
expr += ')' * depth
|
| 715 |
+
# If more close than open, strip trailing close parens
|
| 716 |
+
elif depth < 0:
|
| 717 |
+
while depth < 0 and expr.endswith(')'):
|
| 718 |
+
expr = expr[:-1].rstrip()
|
| 719 |
+
depth += 1
|
| 720 |
+
# Strip redundant outer wrapping: ((x)) β (x)
|
| 721 |
+
while len(expr) > 2 and expr.startswith('(') and expr.endswith(')'):
|
| 722 |
+
inner = expr[1:-1]
|
| 723 |
+
# Only strip if inner parens are balanced
|
| 724 |
+
d = 0
|
| 725 |
+
ok = True
|
| 726 |
+
for ch in inner:
|
| 727 |
+
if ch == '(': d += 1
|
| 728 |
+
elif ch == ')': d -= 1
|
| 729 |
+
if d < 0:
|
| 730 |
+
ok = False
|
| 731 |
+
break
|
| 732 |
+
if ok and d == 0:
|
| 733 |
+
expr = inner
|
| 734 |
+
else:
|
| 735 |
+
break
|
| 736 |
+
return expr
|
| 737 |
+
|
| 738 |
+
|
| 739 |
def _split_sva_implication(condition: str) -> Tuple[str, str, str]:
|
| 740 |
"""Split implication into antecedent/operator/consequent."""
|
| 741 |
match = re.match(r'(.+?)\s*(\|->|\|=>)\s*(.+)', condition.strip(), re.DOTALL)
|
|
|
|
| 805 |
|
| 806 |
Supports implication forms `|->` and `|=>` plus bounded `##N` delays by
|
| 807 |
generating per-property trigger shift registers.
|
| 808 |
+
|
| 809 |
+
Handles both:
|
| 810 |
+
- Named properties: ``property foo; ... endproperty``
|
| 811 |
+
- Inline assertions: ``assert property (@(posedge clk) ...);``
|
| 812 |
+
- Parameterized module declarations: ``module foo_sva #(parameter ...) (...)``
|
| 813 |
"""
|
| 814 |
+
# Match module declaration β with or without #(parameter ...)
|
| 815 |
+
port_match = re.search(
|
| 816 |
+
r'module\s+\w+_sva\s*(?:#\s*\([^)]*\)\s*)?\s*\((.*?)\)\s*;',
|
| 817 |
+
sva_content, re.DOTALL,
|
| 818 |
+
)
|
| 819 |
if not port_match:
|
| 820 |
return ""
|
| 821 |
|
|
|
|
| 825 |
line = line.strip()
|
| 826 |
if line and not line.startswith('//'):
|
| 827 |
port_lines.append(line.rstrip(','))
|
| 828 |
+
|
| 829 |
+
if not port_lines:
|
| 830 |
+
return ""
|
| 831 |
|
| 832 |
+
# --- Extract named properties (property ... endproperty) ---
|
| 833 |
raw_properties = re.findall(r'property\s+(\w+)\s*;(.*?)endproperty', sva_content, re.DOTALL)
|
| 834 |
properties = []
|
| 835 |
for prop_name, body in raw_properties:
|
|
|
|
| 839 |
condition = body_match.group(2).strip()
|
| 840 |
properties.append((prop_name, clk, condition))
|
| 841 |
|
| 842 |
+
# --- Extract inline assertions (assert property (...)) ---
|
| 843 |
+
# These don't have property/endproperty wrappers
|
| 844 |
+
inline_asserts = re.findall(
|
| 845 |
+
r'assert\s+property\s*\(\s*@\(posedge\s+(\w+)\)\s*(.*?)\)\s*;',
|
| 846 |
+
sva_content, re.DOTALL,
|
| 847 |
+
)
|
| 848 |
+
for idx, (clk, condition) in enumerate(inline_asserts):
|
| 849 |
+
prop_name = f"inline_assert_{idx}"
|
| 850 |
+
condition = condition.strip().rstrip(')')
|
| 851 |
+
# Handle unbalanced parens from greedy match
|
| 852 |
+
open_p = condition.count('(')
|
| 853 |
+
close_p = condition.count(')')
|
| 854 |
+
while close_p > open_p and condition.endswith(')'):
|
| 855 |
+
condition = condition[:-1].rstrip()
|
| 856 |
+
close_p -= 1
|
| 857 |
+
properties.append((prop_name, clk, condition))
|
| 858 |
+
|
| 859 |
+
# --- Extract inline cover properties ---
|
| 860 |
+
inline_covers = re.findall(
|
| 861 |
+
r'cover\s+property\s*\(\s*@\(posedge\s+(\w+)\)\s*(.*?)\)\s*;',
|
| 862 |
+
sva_content, re.DOTALL,
|
| 863 |
+
)
|
| 864 |
+
# Cover properties are informational β we'll add them as cover statements
|
| 865 |
+
|
| 866 |
+
if not properties and not inline_covers:
|
| 867 |
+
return ""
|
| 868 |
+
|
| 869 |
+
# --- Extract port signal names for filtering ---
|
| 870 |
+
# Properties referencing internal signals (state, shift_in, etc.) must be
|
| 871 |
+
# skipped because they're not accessible from the bind-check module.
|
| 872 |
+
port_signal_names = set()
|
| 873 |
+
for pl in port_lines:
|
| 874 |
+
# Extract the last word (signal name) from port declaration
|
| 875 |
+
m = re.search(r'(\w+)\s*$', pl)
|
| 876 |
+
if m:
|
| 877 |
+
port_signal_names.add(m.group(1))
|
| 878 |
+
|
| 879 |
+
def _uses_only_port_signals(condition: str) -> bool:
|
| 880 |
+
"""Check if a condition only references port signals (not internals)."""
|
| 881 |
+
# Extract all identifiers from the condition
|
| 882 |
+
idents = set(re.findall(r'\b([a-zA-Z_]\w*)\b', condition))
|
| 883 |
+
# Remove known keywords and constants
|
| 884 |
+
keywords = {'posedge', 'negedge', 'disable', 'iff', 'if', 'else',
|
| 885 |
+
'begin', 'end', 'assert', 'property', 'cover', 'bit',
|
| 886 |
+
'reg', 'wire', 'logic', 'init_done', 'past'}
|
| 887 |
+
idents -= keywords
|
| 888 |
+
# Remove numeric-looking identifiers (like b0, h1, etc.)
|
| 889 |
+
idents = {i for i in idents if not re.match(r'^[0-9]|^[bBhHdD]\d', i)}
|
| 890 |
+
if not idents:
|
| 891 |
+
return True
|
| 892 |
+
# Check if all identifiers are port signals or are past_* references
|
| 893 |
+
for ident in idents:
|
| 894 |
+
if ident not in port_signal_names and not ident.startswith('past_'):
|
| 895 |
+
return False
|
| 896 |
+
return True
|
| 897 |
+
|
| 898 |
+
# Filter properties to only those using port signals
|
| 899 |
+
# and only those without range delays ##[N:M] or $-functions which can't
|
| 900 |
+
# be translated to RTL trigger chains
|
| 901 |
+
port_properties = []
|
| 902 |
+
for prop_name, clk, condition in properties:
|
| 903 |
+
if not _uses_only_port_signals(condition):
|
| 904 |
+
continue
|
| 905 |
+
# Skip properties with range delays (##[...]) β can't map to fixed-cycle RTL
|
| 906 |
+
if re.search(r'##\s*\[', condition):
|
| 907 |
+
continue
|
| 908 |
+
# Skip properties with $past, $isunknown, etc.
|
| 909 |
+
if re.search(r'\$\w+', condition):
|
| 910 |
+
continue
|
| 911 |
+
port_properties.append((prop_name, clk, condition))
|
| 912 |
+
properties = port_properties
|
| 913 |
+
|
| 914 |
+
if not properties and not inline_covers:
|
| 915 |
+
return ""
|
| 916 |
+
|
| 917 |
yosys_code = f'''// AUTO-GENERATED: Yosys-compatible assertions for {module_name}
|
| 918 |
// Original industry-standard SVA is preserved in {module_name}_sva.sv
|
| 919 |
// This file is used ONLY for open-source formal verification (SymbiYosys)
|
|
|
|
| 965 |
base_delay = 0 if op == "|->" else 1
|
| 966 |
extra_delay, consequent_expr = _consume_delay_prefix(consequent)
|
| 967 |
total_delay = base_delay + extra_delay
|
| 968 |
+
antecedent_expr = _balance_parens(antecedent) if antecedent else "1'b1"
|
| 969 |
+
consequent_expr = _balance_parens(consequent_expr) if consequent_expr else "1'b1"
|
| 970 |
|
| 971 |
if total_delay == 0:
|
| 972 |
if disable_cond:
|
|
|
|
| 995 |
else:
|
| 996 |
delayed_match = re.match(r'^\(?\s*(.+?)\s*##\s*(\d+)\s*(.+?)\s*\)?$', cond, re.DOTALL)
|
| 997 |
if delayed_match:
|
| 998 |
+
antecedent_expr = _balance_parens(delayed_match.group(1).strip())
|
| 999 |
total_delay = int(delayed_match.group(2))
|
| 1000 |
+
consequent_expr = _balance_parens(delayed_match.group(3).strip())
|
| 1001 |
trig_name = f"p_trig_{idx}"
|
| 1002 |
trigger_defs.append(f" reg [{total_delay}:0] {trig_name} = '0;")
|
| 1003 |
if disable_cond:
|
|
|
|
| 1015 |
block_lines.append(f" {trig_name}[{stage + 1}] <= {trig_name}[{stage}];")
|
| 1016 |
block_lines.append(f" if (init_done && {trig_name}[{total_delay}]) assert({consequent_expr});")
|
| 1017 |
else:
|
| 1018 |
+
cond = _balance_parens(cond)
|
| 1019 |
if disable_cond:
|
| 1020 |
block_lines.append(f" if (!({disable_cond}) && init_done) assert({cond});")
|
| 1021 |
else:
|
|
|
|
| 1027 |
if trigger_defs:
|
| 1028 |
yosys_code += "\n".join(trigger_defs) + "\n\n"
|
| 1029 |
yosys_code += "\n".join(property_blocks)
|
| 1030 |
+
|
| 1031 |
+
# --- Add cover properties ---
|
| 1032 |
+
for idx, (clk, condition) in enumerate(inline_covers):
|
| 1033 |
+
condition = condition.strip().rstrip(')')
|
| 1034 |
+
# Balance and clean up parens
|
| 1035 |
+
condition = _balance_parens(condition)
|
| 1036 |
+
disable_cond, cond = _extract_disable_iff(condition)
|
| 1037 |
+
cond = _balance_parens(cond)
|
| 1038 |
+
if _uses_only_port_signals(cond):
|
| 1039 |
+
yosys_code += f"\n // Cover: inline_cover_{idx}\n"
|
| 1040 |
+
if disable_cond:
|
| 1041 |
+
yosys_code += f" always @(posedge {clk}) begin\n"
|
| 1042 |
+
yosys_code += f" if (!({disable_cond}) && init_done) cover({cond});\n"
|
| 1043 |
+
yosys_code += " end\n"
|
| 1044 |
+
else:
|
| 1045 |
+
yosys_code += f" always @(posedge {clk}) begin\n"
|
| 1046 |
+
yosys_code += f" if (init_done) cover({cond});\n"
|
| 1047 |
+
yosys_code += " end\n"
|
| 1048 |
+
|
| 1049 |
yosys_code += f'''endmodule
|
| 1050 |
|
| 1051 |
// Bind to DUT
|
|
|
|
| 1060 |
design_name: Name of the design
|
| 1061 |
use_sby_check: If True, use the Yosys-compatible _sby_check.sv file
|
| 1062 |
"""
|
| 1063 |
+
design_dir = f"{OPENLANE_ROOT}/designs/{design_name}/src"
|
| 1064 |
+
path = f"{design_dir}/{design_name}.sby"
|
| 1065 |
|
| 1066 |
sva_file = f"{design_name}_sby_check.sv" if use_sby_check else f"{design_name}_sva.sv"
|
| 1067 |
|
| 1068 |
+
# Use absolute paths in [files] to avoid SBY working-directory issues
|
| 1069 |
+
rtl_abs = f"{design_dir}/{design_name}.v"
|
| 1070 |
+
sva_abs = f"{design_dir}/{sva_file}"
|
| 1071 |
+
|
| 1072 |
config = f"""[options]
|
| 1073 |
mode prove
|
| 1074 |
|
|
|
|
| 1081 |
prep -top {design_name}
|
| 1082 |
|
| 1083 |
[files]
|
| 1084 |
+
{rtl_abs}
|
| 1085 |
+
{sva_abs}
|
| 1086 |
"""
|
| 1087 |
with open(path, "w") as f:
|
| 1088 |
f.write(config)
|
|
|
|
| 1262 |
|
| 1263 |
strategy_norm = str(strategy).upper()
|
| 1264 |
if "SV_MODULAR" in strategy_norm:
|
| 1265 |
+
# Verilator does NOT support classes/interfaces inside modules.
|
| 1266 |
+
# Instead of requiring them, we CHECK that the TB has proper stimulus
|
| 1267 |
+
# and checking infrastructure (procedural or class-based).
|
| 1268 |
+
has_dut_inst = re.search(r'\b\w+\s+dut\s*\(', text) is not None
|
| 1269 |
+
has_stimulus = bool(re.search(r'\$urandom|\$random|initial\s+begin', text))
|
| 1270 |
+
has_checking = bool(re.search(r'if\s*\(|assert\s*\(', text))
|
| 1271 |
+
report["checks"]["has_dut_instantiation"] = has_dut_inst
|
| 1272 |
+
report["checks"]["has_stimulus"] = has_stimulus
|
| 1273 |
+
report["checks"]["has_checking"] = has_checking
|
| 1274 |
+
if not has_dut_inst:
|
| 1275 |
+
_add_issue("missing_dut_instantiation", "TB must instantiate the DUT.")
|
| 1276 |
+
if not has_stimulus:
|
| 1277 |
+
_add_issue("missing_stimulus", "TB must contain stimulus logic ($urandom, $random, or initial block).")
|
| 1278 |
+
# Warn about Verilator-incompatible constructs (non-blocking)
|
| 1279 |
+
if "class " in text and re.search(r'^\s*class\b', text, re.MULTILINE):
|
| 1280 |
+
_add_issue("verilator_unsupported_class", "Classes inside modules are rejected by Verilator. Use flat procedural code.", severity="warning")
|
| 1281 |
+
if re.search(r'^\s*interface\b', text, re.MULTILINE):
|
| 1282 |
+
_add_issue("verilator_unsupported_interface", "Interface blocks inside modules are rejected by Verilator.", severity="warning")
|
| 1283 |
+
if re.search(r'\bcovergroup\b', text, re.IGNORECASE):
|
| 1284 |
+
_add_issue("verilator_unsupported_covergroup", "Covergroups are not supported by Verilator.", severity="warning")
|
| 1285 |
|
| 1286 |
# Disallow problematic constructs in this flow.
|
| 1287 |
unsupported = [
|
|
|
|
| 1431 |
categories.add("covergroup_scope_error")
|
| 1432 |
if "pin not found" in low or "pinnotfound" in low:
|
| 1433 |
categories.add("pin_mismatch")
|
| 1434 |
+
# Missing interface definition (e.g. UVM-lite fallback references _if not in design)
|
| 1435 |
+
if "cannot find" in low and "interface" in low:
|
| 1436 |
+
categories.add("missing_interface")
|
| 1437 |
+
# Dotted references to missing interfaces (cascade from above)
|
| 1438 |
+
if "dotted reference" in low and ("missing module" in low or "missing interface" in low):
|
| 1439 |
+
categories.add("dotted_ref_missing_interface")
|
| 1440 |
if not categories:
|
| 1441 |
categories.add("compile_error")
|
| 1442 |
report["issue_categories"] = sorted(categories)
|
|
|
|
| 1444 |
fp_base = "|".join(report["issue_categories"]) + "|" + "\n".join(report["diagnostics"][:6])
|
| 1445 |
report["fingerprint"] = hashlib.sha256(fp_base.encode("utf-8", errors="ignore")).hexdigest()[:16]
|
| 1446 |
report["ok"] = result.returncode == 0
|
| 1447 |
+
|
| 1448 |
+
# --- iverilog fallback ---
|
| 1449 |
+
# If Verilator rejects the TB (especially for interface/class issues),
|
| 1450 |
+
# try compiling with iverilog to determine if the code is fundamentally
|
| 1451 |
+
# broken or just Verilator-incompatible.
|
| 1452 |
+
if not report["ok"]:
|
| 1453 |
+
verilator_only_cats = {
|
| 1454 |
+
"missing_interface", "dotted_ref_missing_interface",
|
| 1455 |
+
"constructor_interface_type_error", "interface_typing_error",
|
| 1456 |
+
"unsupported_class_construct",
|
| 1457 |
+
}
|
| 1458 |
+
if verilator_only_cats & set(report["issue_categories"]):
|
| 1459 |
+
iverilog_ok, iverilog_msg = _iverilog_compile_tb(tb_path, rtl_path, design_name)
|
| 1460 |
+
report["iverilog_fallback_ok"] = iverilog_ok
|
| 1461 |
+
report["iverilog_fallback_msg"] = iverilog_msg
|
| 1462 |
+
if iverilog_ok:
|
| 1463 |
+
report["ok"] = True
|
| 1464 |
+
report["issue_categories"].append("verilator_only_failure_iverilog_ok")
|
| 1465 |
+
|
| 1466 |
return report["ok"], report
|
| 1467 |
|
| 1468 |
|
| 1469 |
+
def _iverilog_compile_tb(tb_path: str, rtl_path: str, design_name: str) -> Tuple[bool, str]:
|
| 1470 |
+
"""Try compiling TB + RTL with iverilog as a Verilator fallback."""
|
| 1471 |
+
cmd = ["iverilog", "-g2012", "-Wall", "-o", "/dev/null", rtl_path, tb_path]
|
| 1472 |
+
try:
|
| 1473 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
| 1474 |
+
combined = (result.stdout + "\n" + result.stderr).strip()
|
| 1475 |
+
if result.returncode == 0:
|
| 1476 |
+
return True, f"iverilog compile OK: {combined[:500]}" if combined else "iverilog compile OK"
|
| 1477 |
+
return False, f"iverilog compile failed:\n{combined[:2000]}"
|
| 1478 |
+
except FileNotFoundError:
|
| 1479 |
+
return False, "iverilog not found"
|
| 1480 |
+
except subprocess.TimeoutExpired:
|
| 1481 |
+
return False, "iverilog compile timed out"
|
| 1482 |
+
|
| 1483 |
+
|
| 1484 |
+
# ---------------------------------------------------------------------------
|
| 1485 |
+
# Error-log classifier β parse Verilator compile output into structured,
|
| 1486 |
+
# actionable error records so the repair pass can apply *targeted* fixes
|
| 1487 |
+
# instead of blind regex guessing.
|
| 1488 |
+
# ---------------------------------------------------------------------------
|
| 1489 |
+
|
| 1490 |
+
def classify_compile_errors(compile_report: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 1491 |
+
"""Parse Verilator compile output and return a list of classified error records.
|
| 1492 |
+
|
| 1493 |
+
Each record has:
|
| 1494 |
+
category β str e.g. 'virtual_interface_module_scope', 'unsupported_class',
|
| 1495 |
+
'rand_constraint', 'syntax_error', 'port_mismatch', β¦
|
| 1496 |
+
line β int source line number (0 if unknown)
|
| 1497 |
+
file β str filename from the error ('' if unknown)
|
| 1498 |
+
message β str raw error/warning text
|
| 1499 |
+
action β str suggested repair action:
|
| 1500 |
+
'remove_line', 'strip_block', 'strip_keyword',
|
| 1501 |
+
'rewrite', 'regenerate', 'unknown'
|
| 1502 |
+
"""
|
| 1503 |
+
raw = compile_report.get("compile_output", "")
|
| 1504 |
+
if not raw:
|
| 1505 |
+
return []
|
| 1506 |
+
|
| 1507 |
+
errors: List[Dict[str, Any]] = []
|
| 1508 |
+
seen_sigs: set = set() # deduplicate identical messages
|
| 1509 |
+
|
| 1510 |
+
# ---- Verilator error/warning line patterns ----
|
| 1511 |
+
# %Error: file.v:17:33: syntax error, unexpected ';'
|
| 1512 |
+
# %Error-<tag>: file.v:10: ...
|
| 1513 |
+
# %Warning-<tag>: file.v:15: ...
|
| 1514 |
+
loc_pat = re.compile(
|
| 1515 |
+
r"^%(?:Error|Warning)(?:-\w+)?:\s*([^:]+):(\d+)(?::\d+)?:\s*(.+)$"
|
| 1516 |
+
)
|
| 1517 |
+
# Some messages lack a file:line prefix
|
| 1518 |
+
generic_pat = re.compile(
|
| 1519 |
+
r"^%(?:Error|Warning)(?:-\w+)?:\s*(.+)$"
|
| 1520 |
+
)
|
| 1521 |
+
|
| 1522 |
+
for line in raw.splitlines():
|
| 1523 |
+
s = line.strip()
|
| 1524 |
+
if not s:
|
| 1525 |
+
continue
|
| 1526 |
+
|
| 1527 |
+
m = loc_pat.match(s)
|
| 1528 |
+
if m:
|
| 1529 |
+
fname, lineno_str, msg = m.group(1), m.group(2), m.group(3)
|
| 1530 |
+
lineno = int(lineno_str)
|
| 1531 |
+
elif generic_pat.match(s):
|
| 1532 |
+
fname, lineno, msg = "", 0, generic_pat.match(s).group(1)
|
| 1533 |
+
else:
|
| 1534 |
+
# Pick up lines that contain 'syntax error' but lack the % prefix
|
| 1535 |
+
if "syntax error" in s.lower() or "error:" in s.lower():
|
| 1536 |
+
fname, lineno, msg = "", 0, s
|
| 1537 |
+
else:
|
| 1538 |
+
continue
|
| 1539 |
+
|
| 1540 |
+
sig = f"{fname}:{lineno}:{msg[:80]}"
|
| 1541 |
+
if sig in seen_sigs:
|
| 1542 |
+
continue
|
| 1543 |
+
seen_sigs.add(sig)
|
| 1544 |
+
|
| 1545 |
+
cat, action = _classify_single_error(msg, fname, lineno)
|
| 1546 |
+
errors.append({
|
| 1547 |
+
"category": cat,
|
| 1548 |
+
"line": lineno,
|
| 1549 |
+
"file": fname,
|
| 1550 |
+
"message": msg,
|
| 1551 |
+
"action": action,
|
| 1552 |
+
})
|
| 1553 |
+
|
| 1554 |
+
return errors
|
| 1555 |
+
|
| 1556 |
+
|
| 1557 |
+
def _classify_single_error(msg: str, fname: str, lineno: int) -> tuple:
|
| 1558 |
+
"""Classify a single error message into (category, action)."""
|
| 1559 |
+
low = msg.lower()
|
| 1560 |
+
|
| 1561 |
+
# ---- virtual interface at module scope ----
|
| 1562 |
+
if "virtual" in low and ("interface" in low or "unexpected" in low):
|
| 1563 |
+
return ("virtual_interface_module_scope", "remove_line")
|
| 1564 |
+
|
| 1565 |
+
# ---- class / endclass / rand / constraint not supported ----
|
| 1566 |
+
if any(kw in low for kw in ("class ", "endclass", "rand ", "constraint ")):
|
| 1567 |
+
return ("unsupported_class_construct", "strip_block")
|
| 1568 |
+
# rand keyword in variable decl
|
| 1569 |
+
if re.search(r"\brand\b", low):
|
| 1570 |
+
return ("rand_constraint", "strip_keyword")
|
| 1571 |
+
|
| 1572 |
+
# ---- missing interface definition (root cause of UVM-lite fallback failures) ----
|
| 1573 |
+
if "cannot find" in low and "interface" in low:
|
| 1574 |
+
return ("missing_interface", "regenerate")
|
| 1575 |
+
|
| 1576 |
+
# ---- dotted reference to missing module/interface (cascade from missing interface) ----
|
| 1577 |
+
if "dotted reference" in low and ("missing module" in low or "missing interface" in low):
|
| 1578 |
+
return ("dotted_ref_missing_interface", "rewrite")
|
| 1579 |
+
|
| 1580 |
+
# ---- can't find definition in dotted variable (e.g. vif.clk) ----
|
| 1581 |
+
if "can't find definition" in low and "dotted variable" in low:
|
| 1582 |
+
return ("dotted_ref_missing_interface", "rewrite")
|
| 1583 |
+
|
| 1584 |
+
# ---- CELL vs variable mismatch (e.g. vif is a cell but used as variable) ----
|
| 1585 |
+
if "found definition" in low and "cell" in low and "expected a variable" in low:
|
| 1586 |
+
return ("cell_variable_mismatch", "regenerate")
|
| 1587 |
+
|
| 1588 |
+
# ---- interface typing errors ----
|
| 1589 |
+
if "unexpected identifier" in low and ("_if" in msg or "interface" in low):
|
| 1590 |
+
return ("interface_typing_error", "rewrite")
|
| 1591 |
+
|
| 1592 |
+
# ---- covergroup / coverpoint ----
|
| 1593 |
+
if "covergroup" in low or "coverpoint" in low:
|
| 1594 |
+
return ("covergroup_unsupported", "strip_block")
|
| 1595 |
+
|
| 1596 |
+
# ---- port/pin mismatch ----
|
| 1597 |
+
if "pin not found" in low or "pinnotfound" in low or ("port" in low and "not found" in low):
|
| 1598 |
+
return ("port_mismatch", "regenerate")
|
| 1599 |
+
|
| 1600 |
+
# ---- undeclared identifier ----
|
| 1601 |
+
if "was not found" in low or "undeclared identifier" in low or ("unknown" in low and "identifier" in low):
|
| 1602 |
+
return ("undeclared_identifier", "rewrite")
|
| 1603 |
+
|
| 1604 |
+
# ---- generic syntax error ----
|
| 1605 |
+
if "syntax error" in low:
|
| 1606 |
+
return ("syntax_error", "rewrite")
|
| 1607 |
+
|
| 1608 |
+
# ---- missing module ----
|
| 1609 |
+
if "cannot find" in low and "module" in low:
|
| 1610 |
+
return ("missing_module", "regenerate")
|
| 1611 |
+
|
| 1612 |
+
# ---- timescale warnings (non-fatal, informational) ----
|
| 1613 |
+
if "timescale" in low:
|
| 1614 |
+
return ("timescale_warning", "ignore")
|
| 1615 |
+
|
| 1616 |
+
# ---- parser internal error ----
|
| 1617 |
+
if "internal error" in low:
|
| 1618 |
+
return ("parser_internal_error", "regenerate")
|
| 1619 |
+
|
| 1620 |
+
return ("compile_error", "unknown")
|
| 1621 |
+
|
| 1622 |
+
|
| 1623 |
def repair_tb_for_verilator(tb_code: str, compile_report: Dict[str, Any]) -> str:
|
| 1624 |
+
"""Deterministic repair pass for common Verilator TB incompatibilities.
|
| 1625 |
+
|
| 1626 |
+
This enhanced version first classifies the error log using
|
| 1627 |
+
``classify_compile_errors`` so it can apply *targeted* fixes instead of
|
| 1628 |
+
blind regex guessing. The original regex-based repairs are kept as a
|
| 1629 |
+
fallback for any errors the classifier cannot handle.
|
| 1630 |
+
"""
|
| 1631 |
fixed = tb_code or ""
|
| 1632 |
if not fixed.strip():
|
| 1633 |
return fixed
|
| 1634 |
|
| 1635 |
+
# ------------------------------------------------------------------
|
| 1636 |
+
# Phase 0 β Classify errors from the compile report
|
| 1637 |
+
# ------------------------------------------------------------------
|
| 1638 |
+
classified = classify_compile_errors(compile_report)
|
| 1639 |
+
categories_seen = {e["category"] for e in classified}
|
| 1640 |
+
error_lines = {e["line"] for e in classified if e["line"] > 0}
|
| 1641 |
+
|
| 1642 |
+
# ------------------------------------------------------------------
|
| 1643 |
+
# Phase 1 β TARGETED fixes driven by classified errors
|
| 1644 |
+
# ------------------------------------------------------------------
|
| 1645 |
+
|
| 1646 |
+
# 1a. Remove ``virtual interface <name>;`` at **module** scope
|
| 1647 |
+
# (Verilator rejects this β the interface type isn't even defined
|
| 1648 |
+
# in the design, so we remove the line entirely.)
|
| 1649 |
+
if "virtual_interface_module_scope" in categories_seen or re.search(
|
| 1650 |
+
r"^\s*virtual\s+interface\s+\w+\s*;", fixed, re.MULTILINE
|
| 1651 |
+
):
|
| 1652 |
+
lines = fixed.splitlines()
|
| 1653 |
+
in_class = False
|
| 1654 |
+
kept: List[str] = []
|
| 1655 |
+
for ln in lines:
|
| 1656 |
+
stripped = ln.strip()
|
| 1657 |
+
if re.match(r"^class\b", stripped):
|
| 1658 |
+
in_class = True
|
| 1659 |
+
if re.match(r"^endclass\b", stripped):
|
| 1660 |
+
in_class = False
|
| 1661 |
+
# Only remove at module scope, not inside classes
|
| 1662 |
+
if not in_class and re.match(r"^\s*virtual\s+interface\s+\w+\s*;", ln):
|
| 1663 |
+
continue # drop the line
|
| 1664 |
+
kept.append(ln)
|
| 1665 |
+
fixed = "\n".join(kept)
|
| 1666 |
+
|
| 1667 |
+
# 1b. Strip class β¦ endclass blocks at module scope
|
| 1668 |
+
# (Verilator doesn't support SV classes at top/module scope.)
|
| 1669 |
+
if "unsupported_class_construct" in categories_seen or re.search(
|
| 1670 |
+
r"^\s*class\b", fixed, re.MULTILINE
|
| 1671 |
+
):
|
| 1672 |
+
fixed = _strip_module_scope_classes(fixed)
|
| 1673 |
+
|
| 1674 |
+
# 1b2. Rewrite missing-interface pattern: ``<name>_if vif()`` + ``vif.X``
|
| 1675 |
+
# β remove interface instantiation, replace ``vif.X`` with direct ``X``
|
| 1676 |
+
# This handles the UVM-lite fallback TB that references a non-existent
|
| 1677 |
+
# interface definition.
|
| 1678 |
+
missing_if_errors = {"missing_interface", "dotted_ref_missing_interface",
|
| 1679 |
+
"cell_variable_mismatch"}
|
| 1680 |
+
if missing_if_errors & categories_seen or re.search(
|
| 1681 |
+
r"^\s*\w+_if\s+\w+\s*\(\s*\)\s*;", fixed, re.MULTILINE
|
| 1682 |
+
):
|
| 1683 |
+
# Find all interface instance names: ``<if_type> <inst_name>();``
|
| 1684 |
+
if_instances = re.findall(
|
| 1685 |
+
r"^\s*(\w+_if)\s+(\w+)\s*\(\s*\)\s*;", fixed, re.MULTILINE
|
| 1686 |
+
)
|
| 1687 |
+
for if_type, inst_name in if_instances:
|
| 1688 |
+
# Remove the interface instantiation line
|
| 1689 |
+
fixed = re.sub(
|
| 1690 |
+
rf"^\s*{re.escape(if_type)}\s+{re.escape(inst_name)}\s*\(\s*\)\s*;\s*$",
|
| 1691 |
+
"",
|
| 1692 |
+
fixed,
|
| 1693 |
+
flags=re.MULTILINE,
|
| 1694 |
+
)
|
| 1695 |
+
# Replace ``inst_name.signal`` with just ``signal`` everywhere
|
| 1696 |
+
fixed = re.sub(
|
| 1697 |
+
rf"\b{re.escape(inst_name)}\.(\w+)",
|
| 1698 |
+
r"\1",
|
| 1699 |
+
fixed,
|
| 1700 |
+
)
|
| 1701 |
+
# Also remove ``virtual <if_type> <var>;`` declarations inside classes
|
| 1702 |
+
for if_type, _ in if_instances:
|
| 1703 |
+
fixed = re.sub(
|
| 1704 |
+
rf"^\s*virtual\s+{re.escape(if_type)}\s+\w+\s*;\s*$",
|
| 1705 |
+
"",
|
| 1706 |
+
fixed,
|
| 1707 |
+
flags=re.MULTILINE,
|
| 1708 |
+
)
|
| 1709 |
+
# Remove function args that reference virtual interface types
|
| 1710 |
+
for if_type, _ in if_instances:
|
| 1711 |
+
fixed = re.sub(
|
| 1712 |
+
rf"\bvirtual\s+{re.escape(if_type)}\s+\w+",
|
| 1713 |
+
"",
|
| 1714 |
+
fixed,
|
| 1715 |
+
)
|
| 1716 |
+
# Clean up empty function argument lists: ``function new();``
|
| 1717 |
+
fixed = re.sub(r"\(\s*,\s*\)", "()", fixed)
|
| 1718 |
+
fixed = re.sub(r"\(\s*\)", "()", fixed)
|
| 1719 |
+
|
| 1720 |
+
# 1c. Strip ``rand`` keyword from any surviving variable declarations
|
| 1721 |
+
if "rand_constraint" in categories_seen or re.search(r"\brand\s+", fixed):
|
| 1722 |
+
fixed = re.sub(r"\brand\s+", "", fixed)
|
| 1723 |
+
|
| 1724 |
+
# 1d. Strip ``constraint`` blocks
|
| 1725 |
+
fixed = re.sub(
|
| 1726 |
+
r"(?ms)^\s*constraint\s+\w+\s*\{.*?\}\s*;?\s*$", "", fixed
|
| 1727 |
+
)
|
| 1728 |
+
|
| 1729 |
+
# 1e. Replace class-based ``new()`` calls with plain procedural code.
|
| 1730 |
+
# e.g. ``Driver driver; driver = new(dut);`` β remove both lines
|
| 1731 |
+
# when the class was already stripped.
|
| 1732 |
+
# After class stripping, type names of stripped classes become undeclared.
|
| 1733 |
+
# Remove ``<TypeName> <var>;`` and ``<var> = new(β¦);`` when TypeName
|
| 1734 |
+
# was among the stripped classes.
|
| 1735 |
+
if hasattr(_strip_module_scope_classes, "_last_stripped_classes"):
|
| 1736 |
+
for cls_name in _strip_module_scope_classes._last_stripped_classes:
|
| 1737 |
+
# declaration: ``ClassName varName;``
|
| 1738 |
+
fixed = re.sub(
|
| 1739 |
+
rf"^\s*{re.escape(cls_name)}\s+\w+\s*;\s*$",
|
| 1740 |
+
"",
|
| 1741 |
+
fixed,
|
| 1742 |
+
flags=re.MULTILINE,
|
| 1743 |
+
)
|
| 1744 |
+
# ``varName = new(β¦);`` or ``varName = new();``
|
| 1745 |
+
# (we already removed the type decl, so we also need to remove the
|
| 1746 |
+
# assignment to ``new`` that references the same variable)
|
| 1747 |
+
fixed = re.sub(
|
| 1748 |
+
r"^\s*\w+\s*=\s*new\s*\(.*?\)\s*;\s*$",
|
| 1749 |
+
"",
|
| 1750 |
+
fixed,
|
| 1751 |
+
flags=re.MULTILINE,
|
| 1752 |
+
)
|
| 1753 |
+
# ``varName.run();`` calls on stripped objects
|
| 1754 |
+
fixed = re.sub(
|
| 1755 |
+
r"^\s*\w+\.\w+\s*\(.*?\)\s*;\s*$",
|
| 1756 |
+
"",
|
| 1757 |
+
fixed,
|
| 1758 |
+
flags=re.MULTILINE,
|
| 1759 |
+
)
|
| 1760 |
+
|
| 1761 |
+
# ------------------------------------------------------------------
|
| 1762 |
+
# Phase 2 β LEGACY regex-based repairs (kept for breadth)
|
| 1763 |
+
# ------------------------------------------------------------------
|
| 1764 |
+
|
| 1765 |
interface_names = set(re.findall(r"^\s*interface\s+([A-Za-z_]\w*)\b", fixed, flags=re.MULTILINE))
|
| 1766 |
interface_names.update(re.findall(r"\b([A-Za-z_]\w*_if)\b", fixed))
|
| 1767 |
interface_names = {x for x in interface_names if x}
|
|
|
|
| 1834 |
flags=re.MULTILINE,
|
| 1835 |
)
|
| 1836 |
|
| 1837 |
+
# ------------------------------------------------------------------
|
| 1838 |
+
# Phase 3 β Safety net: if the TB is now empty or has no module, bail
|
| 1839 |
+
# ------------------------------------------------------------------
|
| 1840 |
+
if "module" not in fixed:
|
| 1841 |
+
# Return original β the orchestrator will escalate to full regen
|
| 1842 |
+
return tb_code
|
| 1843 |
+
|
| 1844 |
# Clean excessive blank runs after rewrites.
|
| 1845 |
fixed = re.sub(r"\n{3,}", "\n\n", fixed)
|
| 1846 |
if not fixed.endswith("\n"):
|
| 1847 |
fixed += "\n"
|
| 1848 |
return fixed
|
| 1849 |
|
| 1850 |
+
|
| 1851 |
+
def _strip_module_scope_classes(code: str) -> str:
|
| 1852 |
+
"""Remove all ``class β¦ endclass`` blocks that appear at module scope.
|
| 1853 |
+
|
| 1854 |
+
Preserves classes that are inside ``package β¦ endpackage`` since those
|
| 1855 |
+
are structurally valid in SystemVerilog. Tracks the *names* of stripped
|
| 1856 |
+
classes on the function attribute ``_last_stripped_classes`` so the caller
|
| 1857 |
+
can clean up dangling references.
|
| 1858 |
+
"""
|
| 1859 |
+
lines = code.splitlines()
|
| 1860 |
+
result: List[str] = []
|
| 1861 |
+
depth = 0 # nesting depth of class blocks being stripped
|
| 1862 |
+
in_package = False
|
| 1863 |
+
stripped_classes: List[str] = []
|
| 1864 |
+
|
| 1865 |
+
for ln in lines:
|
| 1866 |
+
stripped = ln.strip()
|
| 1867 |
+
|
| 1868 |
+
# Track package scope
|
| 1869 |
+
if re.match(r"^package\b", stripped):
|
| 1870 |
+
in_package = True
|
| 1871 |
+
if re.match(r"^endpackage\b", stripped):
|
| 1872 |
+
in_package = False
|
| 1873 |
+
|
| 1874 |
+
# Only strip at module scope (not inside package)
|
| 1875 |
+
if not in_package:
|
| 1876 |
+
if depth == 0 and re.match(r"^class\b", stripped):
|
| 1877 |
+
m = re.match(r"^class\s+([A-Za-z_]\w*)", stripped)
|
| 1878 |
+
if m:
|
| 1879 |
+
stripped_classes.append(m.group(1))
|
| 1880 |
+
depth = 1
|
| 1881 |
+
continue
|
| 1882 |
+
if depth > 0:
|
| 1883 |
+
# Handle nested classes if any
|
| 1884 |
+
if re.match(r"^class\b", stripped):
|
| 1885 |
+
depth += 1
|
| 1886 |
+
if re.match(r"^endclass\b", stripped):
|
| 1887 |
+
depth -= 1
|
| 1888 |
+
continue # skip all lines inside the class block
|
| 1889 |
+
|
| 1890 |
+
result.append(ln)
|
| 1891 |
+
|
| 1892 |
+
_strip_module_scope_classes._last_stripped_classes = stripped_classes
|
| 1893 |
+
return "\n".join(result)
|
| 1894 |
+
|
| 1895 |
+
# Initialize the function attribute
|
| 1896 |
+
_strip_module_scope_classes._last_stripped_classes = []
|
| 1897 |
+
|
| 1898 |
def run_simulation(design_name: str) -> tuple:
|
| 1899 |
"""
|
| 1900 |
Compiles and runs the testbench simulation using Verilator (Production Mode).
|
|
|
|
| 2024 |
"./flow.tcl", "-design", design_name, "-tag", run_tag, "-overwrite", "-ignore_mismatches"
|
| 2025 |
]
|
| 2026 |
if floorplan_tcl:
|
| 2027 |
+
# Convert host absolute path to Docker-relative path
|
| 2028 |
+
# Docker mounts OPENLANE_ROOT at /openlane
|
| 2029 |
+
if floorplan_tcl.startswith(OPENLANE_ROOT):
|
| 2030 |
+
docker_config_path = floorplan_tcl.replace(OPENLANE_ROOT, "/openlane")
|
| 2031 |
+
else:
|
| 2032 |
+
docker_config_path = floorplan_tcl
|
| 2033 |
+
cmd.extend(["-config_file", docker_config_path])
|
| 2034 |
|
| 2035 |
if background:
|
| 2036 |
log_file_path = os.path.join(design_dir, "harden.log")
|
tests/test_tier1_upgrade.py
DELETED
|
@@ -1,581 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import re
|
| 3 |
-
import sys
|
| 4 |
-
import shutil
|
| 5 |
-
import tempfile
|
| 6 |
-
import textwrap
|
| 7 |
-
import unittest
|
| 8 |
-
from unittest.mock import patch
|
| 9 |
-
|
| 10 |
-
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
| 11 |
-
SRC_ROOT = os.path.join(REPO_ROOT, "src")
|
| 12 |
-
if SRC_ROOT not in sys.path:
|
| 13 |
-
sys.path.insert(0, SRC_ROOT)
|
| 14 |
-
|
| 15 |
-
from agentic.tools import vlsi_tools # noqa: E402
|
| 16 |
-
from agentic.orchestrator import BuildOrchestrator # noqa: E402
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
class SyntaxIntegrityTests(unittest.TestCase):
|
| 20 |
-
def test_no_merge_conflict_markers(self):
|
| 21 |
-
base = os.path.join(REPO_ROOT, "src", "agentic")
|
| 22 |
-
bad = []
|
| 23 |
-
for root, _, files in os.walk(base):
|
| 24 |
-
for fname in files:
|
| 25 |
-
if not fname.endswith(".py"):
|
| 26 |
-
continue
|
| 27 |
-
path = os.path.join(root, fname)
|
| 28 |
-
with open(path, "r", errors="ignore") as f:
|
| 29 |
-
for idx, line in enumerate(f, start=1):
|
| 30 |
-
if line.startswith("<<<<<<<") or line.startswith(">>>>>>>"):
|
| 31 |
-
bad.append(f"{path}:{idx}")
|
| 32 |
-
self.assertEqual([], bad, msg=f"Found conflict markers: {bad}")
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
class SemanticGateTests(unittest.TestCase):
|
| 36 |
-
def _write_tmp(self, code: str) -> str:
|
| 37 |
-
tmpdir = tempfile.mkdtemp(prefix="tier1_sem_")
|
| 38 |
-
path = os.path.join(tmpdir, "dut.sv")
|
| 39 |
-
with open(path, "w") as f:
|
| 40 |
-
f.write(code)
|
| 41 |
-
self.addCleanup(lambda: shutil.rmtree(tmpdir, ignore_errors=True))
|
| 42 |
-
return path
|
| 43 |
-
|
| 44 |
-
def test_port_shadowing_rejected(self):
|
| 45 |
-
code = textwrap.dedent(
|
| 46 |
-
"""
|
| 47 |
-
module dut(
|
| 48 |
-
input logic clk,
|
| 49 |
-
input logic a,
|
| 50 |
-
output logic y
|
| 51 |
-
);
|
| 52 |
-
logic a;
|
| 53 |
-
always_comb y = a;
|
| 54 |
-
endmodule
|
| 55 |
-
"""
|
| 56 |
-
)
|
| 57 |
-
path = self._write_tmp(code)
|
| 58 |
-
ok, report = vlsi_tools.run_semantic_rigor_check(path)
|
| 59 |
-
self.assertFalse(ok)
|
| 60 |
-
self.assertIn("a", report.get("port_shadowing", []))
|
| 61 |
-
|
| 62 |
-
def test_clean_semantics_pass(self):
|
| 63 |
-
code = textwrap.dedent(
|
| 64 |
-
"""
|
| 65 |
-
module dut(
|
| 66 |
-
input logic clk,
|
| 67 |
-
input logic [3:0] a,
|
| 68 |
-
output logic [3:0] y
|
| 69 |
-
);
|
| 70 |
-
always_comb y = a;
|
| 71 |
-
endmodule
|
| 72 |
-
"""
|
| 73 |
-
)
|
| 74 |
-
path = self._write_tmp(code)
|
| 75 |
-
ok, report = vlsi_tools.run_semantic_rigor_check(path)
|
| 76 |
-
self.assertTrue(ok, msg=str(report))
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
class ParserTests(unittest.TestCase):
|
| 80 |
-
def test_log_summary_stream_parser(self):
|
| 81 |
-
tmpdir = tempfile.mkdtemp(prefix="tier1_log_")
|
| 82 |
-
self.addCleanup(lambda: shutil.rmtree(tmpdir, ignore_errors=True))
|
| 83 |
-
log = os.path.join(tmpdir, "routing.log")
|
| 84 |
-
with open(log, "w") as f:
|
| 85 |
-
for _ in range(5000):
|
| 86 |
-
f.write("[INFO GRT] overflow on met2 congestion\n")
|
| 87 |
-
for _ in range(200):
|
| 88 |
-
f.write("[WARN] antenna violation\n")
|
| 89 |
-
summary = vlsi_tools.parse_eda_log_summary(log, kind="routing", top_n=10)
|
| 90 |
-
self.assertEqual(summary.get("total_lines"), 5200)
|
| 91 |
-
self.assertTrue(summary.get("top_issues"))
|
| 92 |
-
self.assertIn("routing_congestion", summary.get("counts", {}))
|
| 93 |
-
|
| 94 |
-
def test_multi_corner_sta_parse(self):
|
| 95 |
-
tmp = tempfile.mkdtemp(prefix="tier1_sta_")
|
| 96 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 97 |
-
original = vlsi_tools.OPENLANE_ROOT
|
| 98 |
-
vlsi_tools.OPENLANE_ROOT = tmp
|
| 99 |
-
self.addCleanup(lambda: setattr(vlsi_tools, "OPENLANE_ROOT", original))
|
| 100 |
-
|
| 101 |
-
base = os.path.join(tmp, "designs", "chip", "runs", "run1", "reports", "signoff")
|
| 102 |
-
for corner, setup, hold in [
|
| 103 |
-
("26-mca", "5.20", "0.11"),
|
| 104 |
-
("28-mca", "5.00", "0.09"),
|
| 105 |
-
("30-mca", "4.90", "0.08"),
|
| 106 |
-
]:
|
| 107 |
-
os.makedirs(os.path.join(base, corner), exist_ok=True)
|
| 108 |
-
path = os.path.join(base, corner, f"{corner}_sta.summary.rpt")
|
| 109 |
-
with open(path, "w") as f:
|
| 110 |
-
f.write(
|
| 111 |
-
textwrap.dedent(
|
| 112 |
-
f"""
|
| 113 |
-
report_wns
|
| 114 |
-
wns {setup}
|
| 115 |
-
report_worst_slack -max (Setup)
|
| 116 |
-
worst slack {setup}
|
| 117 |
-
report_worst_slack -min (Hold)
|
| 118 |
-
worst slack {hold}
|
| 119 |
-
"""
|
| 120 |
-
)
|
| 121 |
-
)
|
| 122 |
-
sta = vlsi_tools.parse_sta_signoff("chip")
|
| 123 |
-
self.assertFalse(sta.get("error"))
|
| 124 |
-
self.assertEqual(3, len(sta.get("corners", [])))
|
| 125 |
-
self.assertAlmostEqual(4.90, sta.get("worst_setup"), places=2)
|
| 126 |
-
self.assertAlmostEqual(0.08, sta.get("worst_hold"), places=2)
|
| 127 |
-
|
| 128 |
-
def test_congestion_parser(self):
|
| 129 |
-
tmp = tempfile.mkdtemp(prefix="tier1_cong_")
|
| 130 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 131 |
-
original = vlsi_tools.OPENLANE_ROOT
|
| 132 |
-
vlsi_tools.OPENLANE_ROOT = tmp
|
| 133 |
-
self.addCleanup(lambda: setattr(vlsi_tools, "OPENLANE_ROOT", original))
|
| 134 |
-
|
| 135 |
-
log_dir = os.path.join(tmp, "designs", "chip", "runs", "agentrun", "logs", "routing")
|
| 136 |
-
os.makedirs(log_dir, exist_ok=True)
|
| 137 |
-
log_path = os.path.join(log_dir, "19-global.log")
|
| 138 |
-
with open(log_path, "w") as f:
|
| 139 |
-
f.write("met1 8342 44 0.53% 0 / 0 / 0\n")
|
| 140 |
-
f.write("met2 8036 1580 19.66% 5 / 2 / 7\n")
|
| 141 |
-
f.write("Total 16378 1624 9.91% 5 / 2 / 7\n")
|
| 142 |
-
data = vlsi_tools.parse_congestion_metrics("chip")
|
| 143 |
-
self.assertAlmostEqual(9.91, data.get("total_usage_pct"), places=2)
|
| 144 |
-
self.assertEqual(7, data.get("total_overflow"))
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
class CoverageAdapterTests(unittest.TestCase):
|
| 148 |
-
def test_detect_tb_style(self):
|
| 149 |
-
self.assertEqual("sv_class_based", vlsi_tools.detect_tb_style("class Driver; endclass"))
|
| 150 |
-
self.assertEqual("classic_verilog", vlsi_tools.detect_tb_style("module tb; initial begin end endmodule"))
|
| 151 |
-
|
| 152 |
-
def test_coverage_never_returns_empty_dict_on_missing_files(self):
|
| 153 |
-
tmp = tempfile.mkdtemp(prefix="tier1_cov_missing_")
|
| 154 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 155 |
-
original = vlsi_tools.OPENLANE_ROOT
|
| 156 |
-
vlsi_tools.OPENLANE_ROOT = tmp
|
| 157 |
-
self.addCleanup(lambda: setattr(vlsi_tools, "OPENLANE_ROOT", original))
|
| 158 |
-
|
| 159 |
-
passed, output, cov = vlsi_tools.run_simulation_with_coverage("chip_missing", backend="auto")
|
| 160 |
-
self.assertFalse(passed)
|
| 161 |
-
self.assertIsInstance(cov, dict)
|
| 162 |
-
self.assertNotEqual({}, cov)
|
| 163 |
-
self.assertTrue(cov.get("infra_failure"))
|
| 164 |
-
|
| 165 |
-
def test_iverilog_backend_rejects_class_sv_tb(self):
|
| 166 |
-
tmp = tempfile.mkdtemp(prefix="tier1_cov_ivl_")
|
| 167 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 168 |
-
original = vlsi_tools.OPENLANE_ROOT
|
| 169 |
-
vlsi_tools.OPENLANE_ROOT = tmp
|
| 170 |
-
self.addCleanup(lambda: setattr(vlsi_tools, "OPENLANE_ROOT", original))
|
| 171 |
-
|
| 172 |
-
src = os.path.join(tmp, "designs", "chip", "src")
|
| 173 |
-
os.makedirs(src, exist_ok=True)
|
| 174 |
-
rtl = os.path.join(src, "chip.v")
|
| 175 |
-
tb = os.path.join(src, "chip_tb.v")
|
| 176 |
-
with open(rtl, "w") as f:
|
| 177 |
-
f.write("module chip(input logic clk, output logic y); assign y = clk; endmodule\n")
|
| 178 |
-
with open(tb, "w") as f:
|
| 179 |
-
f.write("interface chip_if; logic clk; endinterface\nclass Driver; virtual chip_if vif; endclass\nmodule chip_tb; endmodule\n")
|
| 180 |
-
|
| 181 |
-
class CompileFail:
|
| 182 |
-
returncode = 1
|
| 183 |
-
stdout = ""
|
| 184 |
-
stderr = "syntax error: unsupported class item"
|
| 185 |
-
|
| 186 |
-
with patch("agentic.tools.vlsi_tools.subprocess.run", return_value=CompileFail()):
|
| 187 |
-
passed, _, cov = vlsi_tools.run_simulation_with_coverage(
|
| 188 |
-
"chip",
|
| 189 |
-
backend="iverilog",
|
| 190 |
-
fallback_policy="fail_closed",
|
| 191 |
-
profile="balanced",
|
| 192 |
-
)
|
| 193 |
-
|
| 194 |
-
self.assertFalse(passed)
|
| 195 |
-
self.assertTrue(cov.get("infra_failure"))
|
| 196 |
-
self.assertEqual("unsupported_tb_style", cov.get("error_kind"))
|
| 197 |
-
self.assertEqual("iverilog", cov.get("backend"))
|
| 198 |
-
|
| 199 |
-
def test_auto_backend_fallback_oss_to_iverilog(self):
|
| 200 |
-
tmp = tempfile.mkdtemp(prefix="tier1_cov_fallback_")
|
| 201 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 202 |
-
original = vlsi_tools.OPENLANE_ROOT
|
| 203 |
-
vlsi_tools.OPENLANE_ROOT = tmp
|
| 204 |
-
self.addCleanup(lambda: setattr(vlsi_tools, "OPENLANE_ROOT", original))
|
| 205 |
-
|
| 206 |
-
src = os.path.join(tmp, "designs", "chip", "src")
|
| 207 |
-
os.makedirs(src, exist_ok=True)
|
| 208 |
-
with open(os.path.join(src, "chip.v"), "w") as f:
|
| 209 |
-
f.write("module chip(input logic clk, output logic y); assign y = clk; endmodule\n")
|
| 210 |
-
with open(os.path.join(src, "chip_tb.v"), "w") as f:
|
| 211 |
-
f.write("class Driver; endclass\nmodule chip_tb; initial $display(\"TEST PASSED\"); endmodule\n")
|
| 212 |
-
|
| 213 |
-
primary_fail = (
|
| 214 |
-
False,
|
| 215 |
-
"primary fail",
|
| 216 |
-
{
|
| 217 |
-
"ok": False,
|
| 218 |
-
"backend": "verilator",
|
| 219 |
-
"coverage_mode": "full_oss",
|
| 220 |
-
"infra_failure": True,
|
| 221 |
-
"error_kind": "compile_error",
|
| 222 |
-
"diagnostics": ["compile fail"],
|
| 223 |
-
"line_pct": 0.0,
|
| 224 |
-
"branch_pct": 0.0,
|
| 225 |
-
"toggle_pct": 0.0,
|
| 226 |
-
"functional_pct": 0.0,
|
| 227 |
-
"assertion_pct": 0.0,
|
| 228 |
-
"signals_toggled": 0,
|
| 229 |
-
"total_signals": 0,
|
| 230 |
-
"report_path": "",
|
| 231 |
-
"raw_diag_path": "",
|
| 232 |
-
},
|
| 233 |
-
)
|
| 234 |
-
fallback_ok = (
|
| 235 |
-
True,
|
| 236 |
-
"fallback ok",
|
| 237 |
-
{
|
| 238 |
-
"ok": True,
|
| 239 |
-
"backend": "iverilog",
|
| 240 |
-
"coverage_mode": "fallback_oss",
|
| 241 |
-
"infra_failure": False,
|
| 242 |
-
"error_kind": "",
|
| 243 |
-
"diagnostics": [],
|
| 244 |
-
"line_pct": 86.0,
|
| 245 |
-
"branch_pct": 81.0,
|
| 246 |
-
"toggle_pct": 76.0,
|
| 247 |
-
"functional_pct": 82.0,
|
| 248 |
-
"assertion_pct": 100.0,
|
| 249 |
-
"signals_toggled": 4,
|
| 250 |
-
"total_signals": 5,
|
| 251 |
-
"report_path": "diag",
|
| 252 |
-
"raw_diag_path": "diag",
|
| 253 |
-
},
|
| 254 |
-
)
|
| 255 |
-
|
| 256 |
-
with patch("agentic.tools.vlsi_tools.run_verilator_coverage", return_value=primary_fail) as ver_mock, patch(
|
| 257 |
-
"agentic.tools.vlsi_tools.run_iverilog_coverage", return_value=fallback_ok
|
| 258 |
-
) as ivl_mock:
|
| 259 |
-
passed, _, cov = vlsi_tools.run_simulation_with_coverage(
|
| 260 |
-
"chip", backend="auto", fallback_policy="fallback_oss", profile="balanced"
|
| 261 |
-
)
|
| 262 |
-
|
| 263 |
-
self.assertTrue(passed)
|
| 264 |
-
self.assertEqual("iverilog", cov.get("backend"))
|
| 265 |
-
self.assertEqual("fallback_oss", cov.get("coverage_mode"))
|
| 266 |
-
self.assertFalse(cov.get("infra_failure"))
|
| 267 |
-
self.assertTrue(ver_mock.called)
|
| 268 |
-
self.assertTrue(ivl_mock.called)
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
class FormalConversionTests(unittest.TestCase):
|
| 272 |
-
def test_sva_converter_removes_temporal_tokens_for_sby(self):
|
| 273 |
-
sva = textwrap.dedent(
|
| 274 |
-
"""
|
| 275 |
-
module my_chip_sva (
|
| 276 |
-
input logic clk,
|
| 277 |
-
input logic rst_n,
|
| 278 |
-
input logic en,
|
| 279 |
-
output logic [7:0] cnt_out
|
| 280 |
-
);
|
| 281 |
-
property p_reset_assert;
|
| 282 |
-
@(posedge clk) !rst_n |-> ##1 cnt_out == 8'd0;
|
| 283 |
-
endproperty
|
| 284 |
-
assert property (p_reset_assert);
|
| 285 |
-
|
| 286 |
-
property p_increment;
|
| 287 |
-
@(posedge clk) disable iff (!rst_n) en |=> cnt_out == $past(cnt_out) + 1;
|
| 288 |
-
endproperty
|
| 289 |
-
assert property (p_increment);
|
| 290 |
-
|
| 291 |
-
property p_toggle_seq;
|
| 292 |
-
@(posedge clk) !en ##1 en;
|
| 293 |
-
endproperty
|
| 294 |
-
assert property (p_toggle_seq);
|
| 295 |
-
endmodule
|
| 296 |
-
"""
|
| 297 |
-
)
|
| 298 |
-
|
| 299 |
-
converted = vlsi_tools.convert_sva_to_yosys(sva, "my_chip")
|
| 300 |
-
self.assertIsNotNone(converted)
|
| 301 |
-
self.assertNotIn("|->", converted)
|
| 302 |
-
self.assertNotIn("|=>", converted)
|
| 303 |
-
self.assertIsNone(re.search(r"##\s*\d+", converted))
|
| 304 |
-
self.assertIn("reg [7:0] past_cnt_out;", converted)
|
| 305 |
-
|
| 306 |
-
ok, report = vlsi_tools.validate_yosys_sby_check(converted)
|
| 307 |
-
self.assertTrue(ok, msg=str(report))
|
| 308 |
-
|
| 309 |
-
def test_sby_preflight_rejects_residual_temporal_syntax(self):
|
| 310 |
-
bad_code = textwrap.dedent(
|
| 311 |
-
"""
|
| 312 |
-
module bad_sby(input logic clk, input logic a, input logic b);
|
| 313 |
-
always @(posedge clk) begin
|
| 314 |
-
assert(a |-> ##1 b);
|
| 315 |
-
end
|
| 316 |
-
endmodule
|
| 317 |
-
"""
|
| 318 |
-
)
|
| 319 |
-
ok, report = vlsi_tools.validate_yosys_sby_check(bad_code)
|
| 320 |
-
self.assertFalse(ok)
|
| 321 |
-
issue_codes = {issue.get("issue_code") for issue in report.get("issues", [])}
|
| 322 |
-
self.assertIn("residual_temporal_implication", issue_codes)
|
| 323 |
-
self.assertIn("residual_temporal_delay", issue_codes)
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
class TestbenchGateTests(unittest.TestCase):
|
| 327 |
-
def test_tb_static_gate_rejects_non_virtual_interface_usage(self):
|
| 328 |
-
tb = textwrap.dedent(
|
| 329 |
-
"""
|
| 330 |
-
interface dut_if;
|
| 331 |
-
logic clk;
|
| 332 |
-
logic rst_n;
|
| 333 |
-
logic en;
|
| 334 |
-
logic [7:0] q;
|
| 335 |
-
endinterface
|
| 336 |
-
|
| 337 |
-
class Transaction; endclass
|
| 338 |
-
class Driver;
|
| 339 |
-
dut_if vif;
|
| 340 |
-
function new(dut_if vif);
|
| 341 |
-
this.vif = vif;
|
| 342 |
-
endfunction
|
| 343 |
-
endclass
|
| 344 |
-
class Monitor; endclass
|
| 345 |
-
class Scoreboard; endclass
|
| 346 |
-
|
| 347 |
-
module dut_tb;
|
| 348 |
-
initial begin
|
| 349 |
-
$display("TEST PASSED");
|
| 350 |
-
$display("TEST FAILED");
|
| 351 |
-
end
|
| 352 |
-
endmodule
|
| 353 |
-
"""
|
| 354 |
-
)
|
| 355 |
-
ok, report = vlsi_tools.run_tb_static_contract_check(tb, "SV_MODULAR")
|
| 356 |
-
self.assertFalse(ok)
|
| 357 |
-
codes = set(report.get("issue_codes", []))
|
| 358 |
-
self.assertIn("non_virtual_interface_handle", codes)
|
| 359 |
-
self.assertIn("constructor_interface_type_error", codes)
|
| 360 |
-
|
| 361 |
-
def test_tb_repair_patches_interface_and_covergroup_patterns(self):
|
| 362 |
-
tb = textwrap.dedent(
|
| 363 |
-
"""
|
| 364 |
-
interface dut_if;
|
| 365 |
-
logic clk;
|
| 366 |
-
logic rst_n;
|
| 367 |
-
logic en;
|
| 368 |
-
logic [7:0] q;
|
| 369 |
-
endinterface
|
| 370 |
-
|
| 371 |
-
class Transaction; endclass
|
| 372 |
-
class Driver;
|
| 373 |
-
dut_if vif;
|
| 374 |
-
function new(dut_if vif);
|
| 375 |
-
this.vif = vif;
|
| 376 |
-
endfunction
|
| 377 |
-
endclass
|
| 378 |
-
|
| 379 |
-
covergroup cv_q;
|
| 380 |
-
coverpoint q { bins all = {[0:255]}; }
|
| 381 |
-
endgroup
|
| 382 |
-
|
| 383 |
-
class Scoreboard;
|
| 384 |
-
cv_q cov;
|
| 385 |
-
function new();
|
| 386 |
-
cov = new;
|
| 387 |
-
endfunction
|
| 388 |
-
function void sample();
|
| 389 |
-
cov.sample();
|
| 390 |
-
endfunction
|
| 391 |
-
endclass
|
| 392 |
-
"""
|
| 393 |
-
)
|
| 394 |
-
repaired = vlsi_tools.repair_tb_for_verilator(tb, {"issue_categories": ["interface_typing_error", "covergroup_scope_error"]})
|
| 395 |
-
self.assertIn("virtual dut_if vif;", repaired)
|
| 396 |
-
self.assertIn("function new(virtual dut_if vif);", repaired)
|
| 397 |
-
self.assertNotIn("covergroup cv_q", repaired)
|
| 398 |
-
self.assertNotIn("cov.sample()", repaired)
|
| 399 |
-
|
| 400 |
-
def test_tb_compile_gate_normalizes_diagnostics(self):
|
| 401 |
-
tmpdir = tempfile.mkdtemp(prefix="tier1_tb_compile_")
|
| 402 |
-
self.addCleanup(lambda: shutil.rmtree(tmpdir, ignore_errors=True))
|
| 403 |
-
rtl_path = os.path.join(tmpdir, "dut.v")
|
| 404 |
-
tb_path = os.path.join(tmpdir, "dut_tb.v")
|
| 405 |
-
with open(rtl_path, "w") as f:
|
| 406 |
-
f.write("module dut(input logic clk, output logic y); assign y = clk; endmodule\n")
|
| 407 |
-
with open(tb_path, "w") as f:
|
| 408 |
-
f.write("module dut_tb; dut_if vif; endmodule\n")
|
| 409 |
-
|
| 410 |
-
fake_stderr = textwrap.dedent(
|
| 411 |
-
"""
|
| 412 |
-
%Error: /tmp/dut_tb.v:34:5: syntax error, unexpected IDENTIFIER
|
| 413 |
-
%Error: /tmp/dut_tb.v:37:30: syntax error, unexpected IDENTIFIER, expecting ')'
|
| 414 |
-
%Error: Internal Error: parser confused in class Driver
|
| 415 |
-
"""
|
| 416 |
-
)
|
| 417 |
-
|
| 418 |
-
class DummyResult:
|
| 419 |
-
returncode = 1
|
| 420 |
-
stdout = ""
|
| 421 |
-
stderr = fake_stderr
|
| 422 |
-
|
| 423 |
-
with patch("agentic.tools.vlsi_tools.subprocess.run", return_value=DummyResult()):
|
| 424 |
-
ok, report = vlsi_tools.run_tb_compile_gate("dut", tb_path, rtl_path)
|
| 425 |
-
|
| 426 |
-
self.assertFalse(ok)
|
| 427 |
-
cats = set(report.get("issue_categories", []))
|
| 428 |
-
self.assertIn("syntax_error", cats)
|
| 429 |
-
self.assertIn("interface_typing_error", cats)
|
| 430 |
-
self.assertIn("parser_internal_state_error", cats)
|
| 431 |
-
self.assertTrue(report.get("fingerprint"))
|
| 432 |
-
|
| 433 |
-
def test_coverpoint_hierarchical_expression_not_flagged(self):
|
| 434 |
-
tb = textwrap.dedent(
|
| 435 |
-
"""
|
| 436 |
-
interface dut_if;
|
| 437 |
-
logic clk;
|
| 438 |
-
logic en;
|
| 439 |
-
endinterface
|
| 440 |
-
class Transaction; endclass
|
| 441 |
-
class Driver; endclass
|
| 442 |
-
class Monitor; endclass
|
| 443 |
-
class Scoreboard; endclass
|
| 444 |
-
covergroup cg;
|
| 445 |
-
coverpoint vif.en { bins all[] = {0,1}; }
|
| 446 |
-
endgroup
|
| 447 |
-
module dut_tb;
|
| 448 |
-
initial begin
|
| 449 |
-
$display("TEST PASSED");
|
| 450 |
-
$display("TEST FAILED");
|
| 451 |
-
end
|
| 452 |
-
endmodule
|
| 453 |
-
"""
|
| 454 |
-
)
|
| 455 |
-
ok, report = vlsi_tools.run_tb_static_contract_check(tb, "SV_MODULAR")
|
| 456 |
-
codes = set(report.get("issue_codes", []))
|
| 457 |
-
self.assertNotIn("covergroup_scope_error", codes)
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
class OrchestratorSafetyTests(unittest.TestCase):
|
| 461 |
-
def test_failure_fingerprint_repetition(self):
|
| 462 |
-
orch = BuildOrchestrator(
|
| 463 |
-
name="fingerprint_demo",
|
| 464 |
-
desc="demo",
|
| 465 |
-
llm=None,
|
| 466 |
-
strict_gates=True,
|
| 467 |
-
)
|
| 468 |
-
first = orch._record_failure_fingerprint("same failure")
|
| 469 |
-
second = orch._record_failure_fingerprint("same failure")
|
| 470 |
-
self.assertFalse(first)
|
| 471 |
-
self.assertTrue(second)
|
| 472 |
-
|
| 473 |
-
def test_hierarchy_auto_threshold(self):
|
| 474 |
-
orch = BuildOrchestrator(
|
| 475 |
-
name="hier_demo",
|
| 476 |
-
desc="demo",
|
| 477 |
-
llm=None,
|
| 478 |
-
hierarchical_mode="auto",
|
| 479 |
-
)
|
| 480 |
-
rtl = "\n".join([
|
| 481 |
-
"module top(input logic clk, output logic y); assign y = 1'b0; endmodule",
|
| 482 |
-
"module blk_a(input logic i, output logic o); assign o = i; endmodule",
|
| 483 |
-
"module blk_b(input logic i, output logic o); assign o = i; endmodule",
|
| 484 |
-
] + ["// filler"] * 650)
|
| 485 |
-
orch._evaluate_hierarchy(rtl)
|
| 486 |
-
plan = orch.artifacts.get("hierarchy_plan", {})
|
| 487 |
-
self.assertTrue(plan.get("enabled"), msg=str(plan))
|
| 488 |
-
|
| 489 |
-
def test_benchmark_metrics_written_to_metircs(self):
|
| 490 |
-
import agentic.orchestrator as orch_mod
|
| 491 |
-
|
| 492 |
-
tmp = tempfile.mkdtemp(prefix="tier1_metircs_")
|
| 493 |
-
self.addCleanup(lambda: shutil.rmtree(tmp, ignore_errors=True))
|
| 494 |
-
old_workspace = orch_mod.WORKSPACE_ROOT
|
| 495 |
-
orch_mod.WORKSPACE_ROOT = tmp
|
| 496 |
-
self.addCleanup(lambda: setattr(orch_mod, "WORKSPACE_ROOT", old_workspace))
|
| 497 |
-
|
| 498 |
-
orch = BuildOrchestrator(name="metric_chip", desc="demo", llm=None)
|
| 499 |
-
orch.state = orch.state.SUCCESS
|
| 500 |
-
orch.artifacts["signoff_result"] = "PASS"
|
| 501 |
-
orch.artifacts["metrics"] = {"chip_area_um2": 1234.5, "area": 321, "utilization": 42.0, "timing_tns": 0.0, "timing_wns": 0.1}
|
| 502 |
-
orch.artifacts["sta_signoff"] = {"worst_setup": 0.1, "worst_hold": 0.05}
|
| 503 |
-
orch.artifacts["power_signoff"] = {"total_power_w": 1e-3, "internal_power_w": 5e-4, "switching_power_w": 4e-4, "leakage_power_w": 1e-5, "irdrop_max_vpwr": 0.01, "irdrop_max_vgnd": 0.02}
|
| 504 |
-
orch.artifacts["signoff"] = {"drc_violations": 0, "lvs_errors": 0, "antenna_violations": 0}
|
| 505 |
-
orch.artifacts["coverage"] = {"line_pct": 90.0}
|
| 506 |
-
orch.artifacts["formal_result"] = "PASS"
|
| 507 |
-
orch.artifacts["lec_result"] = "PASS"
|
| 508 |
-
orch._save_industry_benchmark_metrics()
|
| 509 |
-
|
| 510 |
-
metircs_dir = os.path.join(tmp, "metircs", "metric_chip")
|
| 511 |
-
self.assertTrue(os.path.isdir(metircs_dir))
|
| 512 |
-
self.assertTrue(os.path.isfile(os.path.join(metircs_dir, "latest.json")))
|
| 513 |
-
self.assertTrue(os.path.isfile(os.path.join(metircs_dir, "latest.md")))
|
| 514 |
-
|
| 515 |
-
def test_extract_module_ports_ignores_comments(self):
|
| 516 |
-
orch = BuildOrchestrator(name="ports_demo", desc="demo", llm=None)
|
| 517 |
-
rtl = textwrap.dedent(
|
| 518 |
-
"""
|
| 519 |
-
module ports_demo (
|
| 520 |
-
input logic clk,
|
| 521 |
-
input logic rst_n, // asynchronous reset
|
| 522 |
-
output logic [7:0] count // output assignments are below
|
| 523 |
-
);
|
| 524 |
-
// External output assignments comment should not become a port name.
|
| 525 |
-
assign count = 8'h00;
|
| 526 |
-
endmodule
|
| 527 |
-
"""
|
| 528 |
-
)
|
| 529 |
-
ports = orch._extract_module_ports(rtl)
|
| 530 |
-
names = [p["name"] for p in ports]
|
| 531 |
-
self.assertEqual(["clk", "rst_n", "count"], names)
|
| 532 |
-
self.assertNotIn("assignments", names)
|
| 533 |
-
|
| 534 |
-
def test_coverage_infra_failure_fail_closed_no_tb_regen(self):
|
| 535 |
-
orch = BuildOrchestrator(
|
| 536 |
-
name="cov_fail_demo",
|
| 537 |
-
desc="demo",
|
| 538 |
-
llm=None,
|
| 539 |
-
strict_gates=True,
|
| 540 |
-
coverage_backend="auto",
|
| 541 |
-
coverage_fallback_policy="fail_closed",
|
| 542 |
-
coverage_profile="balanced",
|
| 543 |
-
)
|
| 544 |
-
orch.state = orch.state.COVERAGE_CHECK
|
| 545 |
-
orch.artifacts["root"] = tempfile.mkdtemp(prefix="tier1_cov_fail_")
|
| 546 |
-
self.addCleanup(lambda: shutil.rmtree(orch.artifacts["root"], ignore_errors=True))
|
| 547 |
-
orch.setup_logger()
|
| 548 |
-
orch.artifacts["rtl_code"] = "module cov_fail_demo(input logic clk, output logic y); assign y = clk; endmodule\n"
|
| 549 |
-
tb_path = os.path.join(orch.artifacts["root"], "cov_fail_demo_tb.v")
|
| 550 |
-
with open(tb_path, "w") as f:
|
| 551 |
-
f.write("module cov_fail_demo_tb; initial $display(\"TEST PASSED\"); endmodule\n")
|
| 552 |
-
orch.artifacts["tb_path"] = tb_path
|
| 553 |
-
|
| 554 |
-
cov_result = {
|
| 555 |
-
"ok": False,
|
| 556 |
-
"backend": "verilator",
|
| 557 |
-
"coverage_mode": "full_oss",
|
| 558 |
-
"infra_failure": True,
|
| 559 |
-
"error_kind": "tool_missing",
|
| 560 |
-
"diagnostics": ["verilator missing"],
|
| 561 |
-
"line_pct": 0.0,
|
| 562 |
-
"branch_pct": 0.0,
|
| 563 |
-
"toggle_pct": 0.0,
|
| 564 |
-
"functional_pct": 0.0,
|
| 565 |
-
"assertion_pct": 0.0,
|
| 566 |
-
"signals_toggled": 0,
|
| 567 |
-
"total_signals": 0,
|
| 568 |
-
"report_path": "",
|
| 569 |
-
"raw_diag_path": "",
|
| 570 |
-
}
|
| 571 |
-
|
| 572 |
-
with patch("agentic.orchestrator.run_simulation_with_coverage", return_value=(False, "infra fail", cov_result)):
|
| 573 |
-
orch.do_coverage_check()
|
| 574 |
-
|
| 575 |
-
self.assertEqual("FAIL", orch.state.name)
|
| 576 |
-
self.assertEqual(0, orch.retry_count)
|
| 577 |
-
self.assertEqual(1, orch.artifacts.get("coverage_attempt_count"))
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
if __name__ == "__main__":
|
| 581 |
-
unittest.main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/README.md
DELETED
|
@@ -1,97 +0,0 @@
|
|
| 1 |
-
# VeriReason Training Tools for AgentIC
|
| 2 |
-
|
| 3 |
-
Train VeriReason to generate better Verilog using data from AgentIC builds.
|
| 4 |
-
|
| 5 |
-
## Complete Workflow
|
| 6 |
-
|
| 7 |
-
### Step 1: Run Builds with Cloud LLM
|
| 8 |
-
```bash
|
| 9 |
-
cd ~/AgentIC && source .venv-agentic/bin/activate
|
| 10 |
-
|
| 11 |
-
python main.py build -n "counter" -d "8-bit counter with enable" --skip-openlane
|
| 12 |
-
python main.py build -n "uart_tx" -d "UART transmitter 115200 baud" --skip-openlane
|
| 13 |
-
python main.py build -n "spi_master" -d "SPI master with CPOL/CPHA" --skip-openlane
|
| 14 |
-
python main.py build -n "fifo" -d "sync FIFO depth 16, 8-bit data" --skip-openlane
|
| 15 |
-
python main.py build -n "pwm" -d "PWM controller 8-bit duty cycle" --skip-openlane
|
| 16 |
-
```
|
| 17 |
-
Even failed builds are valuable β they produce errorβfix training pairs.
|
| 18 |
-
|
| 19 |
-
### Step 2: Collect Training Data
|
| 20 |
-
```bash
|
| 21 |
-
python3 training/collect_training_data.py
|
| 22 |
-
# Output: training/agentic_sft_data.jsonl
|
| 23 |
-
```
|
| 24 |
-
|
| 25 |
-
### Step 3: Generate Log-Based Reasoning (Recommended)
|
| 26 |
-
```bash
|
| 27 |
-
ollama serve # in another terminal
|
| 28 |
-
python3 training/generate_reasoning.py
|
| 29 |
-
# Output: training/agentic_sft_data_with_reasoning.jsonl
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
VeriReason **reads the actual build logs** and generates chain-of-thought reasoning about what happened:
|
| 33 |
-
|
| 34 |
-
```
|
| 35 |
-
Build log says:
|
| 36 |
-
ERROR: MULTIDRIVEN on cnt (two always blocks)
|
| 37 |
-
FIX: Merged into single always_ff with async reset
|
| 38 |
-
SIM: Timing race detected, CLASS=D
|
| 39 |
-
|
| 40 |
-
VeriReason generates:
|
| 41 |
-
<think>
|
| 42 |
-
The initial RTL had two always blocks driving cnt β one for
|
| 43 |
-
incrementing and one for reset. This is a MULTIDRIVEN violation.
|
| 44 |
-
The correct approach is a single always_ff with async reset in
|
| 45 |
-
the sensitivity list. The sim timing race happened because the
|
| 46 |
-
TB released reset on the same clock edge...
|
| 47 |
-
</think>
|
| 48 |
-
module counter(...) // cloud's verified code
|
| 49 |
-
```
|
| 50 |
-
|
| 51 |
-
### Step 4: Fine-Tune VeriReason
|
| 52 |
-
```bash
|
| 53 |
-
pip install llamafactory
|
| 54 |
-
llamafactory-cli train training/agentic_sft_config.yaml
|
| 55 |
-
```
|
| 56 |
-
- **GPU**: 24GB+ VRAM (RTX 3090/4090/A100)
|
| 57 |
-
- **Time**: ~4-8 hrs (3B) or ~8-12 hrs (7B)
|
| 58 |
-
- **Output**: `training/checkpoints/agentic-sft/` (LoRA weights, ~200MB)
|
| 59 |
-
|
| 60 |
-
### Step 5: Deploy Fine-Tuned Model
|
| 61 |
-
```bash
|
| 62 |
-
# Merge LoRA into base model
|
| 63 |
-
llamafactory-cli export \
|
| 64 |
-
--model_name_or_path Nellyw888/VeriReason-Qwen2.5-7b-SFT-Reasoning \
|
| 65 |
-
--adapter_name_or_path training/checkpoints/agentic-sft \
|
| 66 |
-
--export_dir training/merged-model --template qwen
|
| 67 |
-
|
| 68 |
-
# Import into Ollama
|
| 69 |
-
cat > training/Modelfile << 'EOF'
|
| 70 |
-
FROM training/merged-model
|
| 71 |
-
PARAMETER temperature 0.2
|
| 72 |
-
PARAMETER num_ctx 4096
|
| 73 |
-
SYSTEM You are a Verilog RTL expert. Generate synthesizable SystemVerilog.
|
| 74 |
-
EOF
|
| 75 |
-
ollama create verireason-agentic -f training/Modelfile
|
| 76 |
-
|
| 77 |
-
# Use with AgentIC
|
| 78 |
-
export LLM_MODEL="ollama/verireason-agentic"
|
| 79 |
-
export LLM_BASE_URL="http://localhost:11434"
|
| 80 |
-
python main.py build -n "my_chip" -d "your design" --skip-openlane
|
| 81 |
-
```
|
| 82 |
-
|
| 83 |
-
## Files
|
| 84 |
-
|
| 85 |
-
| File | Purpose |
|
| 86 |
-
|------|---------|
|
| 87 |
-
| `collect_training_data.py` | Extracts SFT pairs from build logs |
|
| 88 |
-
| `generate_reasoning.py` | VeriReason reads build logs β generates CoT reasoning |
|
| 89 |
-
| `agentic_sft_config.yaml` | LLamaFactory LoRA fine-tuning config |
|
| 90 |
-
| `verilog_rewards_enhanced.py` | GRPO reward function (6 signals) |
|
| 91 |
-
|
| 92 |
-
## Self-Improving Loop
|
| 93 |
-
|
| 94 |
-
```
|
| 95 |
-
Cloud builds β collect data β VeriReason reads logs β generates reasoning
|
| 96 |
-
β fine-tune VeriReason β better local code β more builds β repeat
|
| 97 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/agentic_sft_config.yaml
DELETED
|
@@ -1,74 +0,0 @@
|
|
| 1 |
-
### AgentIC-Specific VeriReason Fine-Tuning Config
|
| 2 |
-
### For use with LLamaFactory: llamafactory-cli train training/agentic_sft_config.yaml
|
| 3 |
-
###
|
| 4 |
-
### Prerequisites:
|
| 5 |
-
### pip install llamafactory
|
| 6 |
-
### python training/collect_training_data.py # generates agentic_sft_data.jsonl
|
| 7 |
-
|
| 8 |
-
### ============================================================
|
| 9 |
-
### Model Configuration
|
| 10 |
-
### ============================================================
|
| 11 |
-
model_name_or_path: Nellyw888/VeriReason-Qwen2.5-7b-SFT-Reasoning
|
| 12 |
-
# Alternatives:
|
| 13 |
-
# Nellyw888/VeriReason-Qwen2.5-3B-Verilog-RTL-GRPO-reasoning-tb (3B, lighter)
|
| 14 |
-
# Nellyw888/VeriReason-Llama-7b-RTLCoder-GRPO-reasoning-tb (LLaMA backbone)
|
| 15 |
-
|
| 16 |
-
### ============================================================
|
| 17 |
-
### Data Configuration
|
| 18 |
-
### ============================================================
|
| 19 |
-
dataset_dir: training
|
| 20 |
-
dataset: agentic_sft_data # will look for agentic_sft_data.jsonl
|
| 21 |
-
|
| 22 |
-
# Template matching the AgentIC prompt format
|
| 23 |
-
template: qwen # or llama3 if using Llama backbone
|
| 24 |
-
|
| 25 |
-
# Custom prompt template that matches AgentIC's format
|
| 26 |
-
# The model learns to parse BUILD CONTEXT and output in CLASS/ROOT_CAUSE format
|
| 27 |
-
|
| 28 |
-
### ============================================================
|
| 29 |
-
### Training Hyperparameters
|
| 30 |
-
### ============================================================
|
| 31 |
-
stage: sft
|
| 32 |
-
do_train: true
|
| 33 |
-
|
| 34 |
-
# LoRA for efficient fine-tuning (fits on 24GB GPU)
|
| 35 |
-
finetuning_type: lora
|
| 36 |
-
lora_rank: 64
|
| 37 |
-
lora_alpha: 128
|
| 38 |
-
lora_dropout: 0.05
|
| 39 |
-
lora_target: all # target all linear layers
|
| 40 |
-
|
| 41 |
-
per_device_train_batch_size: 2
|
| 42 |
-
gradient_accumulation_steps: 8 # effective batch size = 16
|
| 43 |
-
learning_rate: 2.0e-5
|
| 44 |
-
num_train_epochs: 3
|
| 45 |
-
lr_scheduler_type: cosine
|
| 46 |
-
warmup_ratio: 0.1
|
| 47 |
-
|
| 48 |
-
# Sequence length β AgentIC prompts can be long (RTL + context)
|
| 49 |
-
cutoff_len: 4096
|
| 50 |
-
|
| 51 |
-
# Mixed precision for speed
|
| 52 |
-
bf16: true # set to false if GPU doesn't support bf16
|
| 53 |
-
|
| 54 |
-
### ============================================================
|
| 55 |
-
### Logging & Saving
|
| 56 |
-
### ============================================================
|
| 57 |
-
output_dir: training/checkpoints/agentic-sft
|
| 58 |
-
logging_steps: 10
|
| 59 |
-
save_steps: 200
|
| 60 |
-
save_total_limit: 3
|
| 61 |
-
|
| 62 |
-
report_to: none # set to "wandb" if you have W&B
|
| 63 |
-
|
| 64 |
-
### ============================================================
|
| 65 |
-
### Evaluation
|
| 66 |
-
### ============================================================
|
| 67 |
-
do_eval: false
|
| 68 |
-
# Set do_eval: true and add eval_dataset if you split your data
|
| 69 |
-
|
| 70 |
-
### ============================================================
|
| 71 |
-
### Advanced (keep defaults unless you know what you're doing)
|
| 72 |
-
### ============================================================
|
| 73 |
-
overwrite_cache: true
|
| 74 |
-
preprocessing_num_workers: 4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/collect_training_data.py
DELETED
|
@@ -1,266 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
AgentIC β VeriReason Training Data Collector
|
| 4 |
-
=============================================
|
| 5 |
-
Automatically collects training data from AgentIC build logs.
|
| 6 |
-
|
| 7 |
-
Usage:
|
| 8 |
-
python collect_training_data.py # scan all builds
|
| 9 |
-
python collect_training_data.py --design my_chip # scan specific design
|
| 10 |
-
python collect_training_data.py --output data.jsonl # custom output path
|
| 11 |
-
|
| 12 |
-
The output is a JSONL file ready for SFT (Supervised Fine-Tuning) with
|
| 13 |
-
LLamaFactory or OpenR1.
|
| 14 |
-
"""
|
| 15 |
-
|
| 16 |
-
import argparse
|
| 17 |
-
import json
|
| 18 |
-
import os
|
| 19 |
-
import re
|
| 20 |
-
import glob
|
| 21 |
-
from pathlib import Path
|
| 22 |
-
from datetime import datetime
|
| 23 |
-
from typing import List, Dict, Optional, Any
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
OPENLANE_ROOT = os.environ.get("OPENLANE_ROOT", os.path.expanduser("~/OpenLane"))
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def extract_spec(log_text: str) -> str:
|
| 30 |
-
"""Extract the architecture spec from the log."""
|
| 31 |
-
match = re.search(r"\[SPEC\] Architecture Plan Generated", log_text)
|
| 32 |
-
if not match:
|
| 33 |
-
return ""
|
| 34 |
-
# Spec content is typically between SPEC and RTL_GEN transitions
|
| 35 |
-
spec_section = re.search(
|
| 36 |
-
r"Architecture Plan Generated([\s\S]*?)\[(?:SPEC|RTL_GEN)\].*Transitioning",
|
| 37 |
-
log_text,
|
| 38 |
-
)
|
| 39 |
-
return spec_section.group(1).strip() if spec_section else ""
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def extract_rtl_blocks(log_text: str) -> List[Dict[str, str]]:
|
| 43 |
-
"""Extract all RTL code blocks (generated + fixed versions)."""
|
| 44 |
-
blocks = []
|
| 45 |
-
# Match both GENERATED RTL and FIXED RTL
|
| 46 |
-
pattern = re.compile(
|
| 47 |
-
r"(GENERATED RTL|FIXED RTL).*?:\s*\n```(?:verilog|systemverilog)?\n([\s\S]*?)```",
|
| 48 |
-
re.IGNORECASE,
|
| 49 |
-
)
|
| 50 |
-
for match in pattern.finditer(log_text):
|
| 51 |
-
label = match.group(1).strip()
|
| 52 |
-
code = match.group(2).strip()
|
| 53 |
-
blocks.append({"type": label, "code": code})
|
| 54 |
-
return blocks
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
def extract_testbench(log_text: str) -> str:
|
| 58 |
-
"""Extract the generated testbench code."""
|
| 59 |
-
match = re.search(
|
| 60 |
-
r"GENERATED TESTBENCH:\s*\n([\s\S]*?)(?:\n\d{4}-\d{2}-\d{2}|\Z)", log_text
|
| 61 |
-
)
|
| 62 |
-
return match.group(1).strip() if match else ""
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def extract_errors(log_text: str) -> List[Dict[str, str]]:
|
| 66 |
-
"""Extract error logs with their context."""
|
| 67 |
-
errors = []
|
| 68 |
-
# Syntax/lint errors
|
| 69 |
-
for match in re.finditer(
|
| 70 |
-
r"SYNTAX/LINT ERRORS:\s*\n([\s\S]*?)(?:\n\d{4}-\d{2}-\d{2}|\Z)", log_text
|
| 71 |
-
):
|
| 72 |
-
errors.append({"type": "syntax_lint", "content": match.group(1).strip()})
|
| 73 |
-
|
| 74 |
-
# Simulation failures with diagnosis
|
| 75 |
-
for match in re.finditer(
|
| 76 |
-
r"\[VERIFICATION\] Diagnosis: CLASS=(\w)\s*\|\s*ROOT_CAUSE=(.*?)\s*\|\s*FIX_HINT=(.*?)$",
|
| 77 |
-
log_text,
|
| 78 |
-
re.MULTILINE,
|
| 79 |
-
):
|
| 80 |
-
errors.append(
|
| 81 |
-
{
|
| 82 |
-
"type": "sim_diagnosis",
|
| 83 |
-
"class": match.group(1),
|
| 84 |
-
"root_cause": match.group(2).strip(),
|
| 85 |
-
"fix_hint": match.group(3).strip(),
|
| 86 |
-
}
|
| 87 |
-
)
|
| 88 |
-
return errors
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def extract_final_status(log_text: str) -> str:
|
| 92 |
-
"""Extract final build status."""
|
| 93 |
-
if "BUILD FAILED" in log_text:
|
| 94 |
-
return "FAIL"
|
| 95 |
-
if "SIGNOFF PASSED" in log_text or "[SUCCESS]" in log_text:
|
| 96 |
-
return "PASS"
|
| 97 |
-
return "UNKNOWN"
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
def build_sft_pairs(
|
| 101 |
-
design_name: str,
|
| 102 |
-
description: str,
|
| 103 |
-
rtl_blocks: List[Dict[str, str]],
|
| 104 |
-
errors: List[Dict[str, str]],
|
| 105 |
-
testbench: str,
|
| 106 |
-
spec: str,
|
| 107 |
-
) -> List[Dict[str, str]]:
|
| 108 |
-
"""Generate SFT training pairs from extracted data."""
|
| 109 |
-
pairs = []
|
| 110 |
-
|
| 111 |
-
# 1. Spec β RTL generation pair
|
| 112 |
-
if spec and rtl_blocks:
|
| 113 |
-
first_rtl = rtl_blocks[0]["code"]
|
| 114 |
-
pairs.append(
|
| 115 |
-
{
|
| 116 |
-
"instruction": f"Generate synthesizable SystemVerilog RTL for: {description}",
|
| 117 |
-
"input": f"ARCHITECTURE SPEC:\n{spec[:4000]}",
|
| 118 |
-
"output": first_rtl,
|
| 119 |
-
"category": "rtl_generation",
|
| 120 |
-
"design": design_name,
|
| 121 |
-
}
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
# 2. Error β Fix pairs (the gold mine for training)
|
| 125 |
-
for i in range(len(rtl_blocks) - 1):
|
| 126 |
-
if rtl_blocks[i]["type"] == "GENERATED RTL" or rtl_blocks[i]["type"] == "FIXED RTL":
|
| 127 |
-
before = rtl_blocks[i]["code"]
|
| 128 |
-
after = rtl_blocks[i + 1]["code"]
|
| 129 |
-
# Find the error between these two versions
|
| 130 |
-
relevant_error = ""
|
| 131 |
-
if i < len(errors):
|
| 132 |
-
relevant_error = json.dumps(errors[i], indent=2)
|
| 133 |
-
|
| 134 |
-
if before != after:
|
| 135 |
-
pairs.append(
|
| 136 |
-
{
|
| 137 |
-
"instruction": "Fix the following Verilog code based on the error report.",
|
| 138 |
-
"input": f"ERROR:\n{relevant_error}\n\nCODE:\n```verilog\n{before}\n```",
|
| 139 |
-
"output": after,
|
| 140 |
-
"category": "rtl_fix",
|
| 141 |
-
"design": design_name,
|
| 142 |
-
}
|
| 143 |
-
)
|
| 144 |
-
|
| 145 |
-
# 3. Error classification pairs
|
| 146 |
-
for err in errors:
|
| 147 |
-
if err["type"] == "sim_diagnosis":
|
| 148 |
-
pairs.append(
|
| 149 |
-
{
|
| 150 |
-
"instruction": "Classify this simulation failure and provide root cause analysis.",
|
| 151 |
-
"input": f"Simulation failed for design '{design_name}'.\nError details: {err.get('root_cause', '')}",
|
| 152 |
-
"output": f"CLASS: {err['class']}\nROOT_CAUSE: {err['root_cause']}\nFIX_HINT: {err['fix_hint']}",
|
| 153 |
-
"category": "error_classification",
|
| 154 |
-
"design": design_name,
|
| 155 |
-
}
|
| 156 |
-
)
|
| 157 |
-
|
| 158 |
-
# 4. RTL β Testbench pair
|
| 159 |
-
if rtl_blocks and testbench:
|
| 160 |
-
final_rtl = rtl_blocks[-1]["code"]
|
| 161 |
-
pairs.append(
|
| 162 |
-
{
|
| 163 |
-
"instruction": f"Generate a UVM-lite SystemVerilog testbench for the following RTL module.",
|
| 164 |
-
"input": f"```verilog\n{final_rtl}\n```",
|
| 165 |
-
"output": testbench,
|
| 166 |
-
"category": "tb_generation",
|
| 167 |
-
"design": design_name,
|
| 168 |
-
}
|
| 169 |
-
)
|
| 170 |
-
|
| 171 |
-
return pairs
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
def process_design(design_dir: str) -> List[Dict[str, str]]:
|
| 175 |
-
"""Process a single design directory and extract training pairs."""
|
| 176 |
-
design_name = os.path.basename(design_dir)
|
| 177 |
-
log_files = glob.glob(os.path.join(design_dir, "*.log"))
|
| 178 |
-
if not log_files:
|
| 179 |
-
return []
|
| 180 |
-
|
| 181 |
-
all_pairs = []
|
| 182 |
-
for log_file in log_files:
|
| 183 |
-
try:
|
| 184 |
-
with open(log_file, "r") as f:
|
| 185 |
-
log_text = f.read()
|
| 186 |
-
except Exception:
|
| 187 |
-
continue
|
| 188 |
-
|
| 189 |
-
if len(log_text) < 100:
|
| 190 |
-
continue
|
| 191 |
-
|
| 192 |
-
# Extract description from log header
|
| 193 |
-
desc_match = re.search(r"Description:\s*(.+?)$", log_text, re.MULTILINE)
|
| 194 |
-
description = desc_match.group(1).strip() if desc_match else design_name
|
| 195 |
-
|
| 196 |
-
spec = extract_spec(log_text)
|
| 197 |
-
rtl_blocks = extract_rtl_blocks(log_text)
|
| 198 |
-
testbench = extract_testbench(log_text)
|
| 199 |
-
errors = extract_errors(log_text)
|
| 200 |
-
status = extract_final_status(log_text)
|
| 201 |
-
|
| 202 |
-
if not rtl_blocks:
|
| 203 |
-
continue
|
| 204 |
-
|
| 205 |
-
pairs = build_sft_pairs(design_name, description, rtl_blocks, errors, testbench, spec)
|
| 206 |
-
|
| 207 |
-
# Tag with metadata
|
| 208 |
-
for pair in pairs:
|
| 209 |
-
pair["source_log"] = log_file
|
| 210 |
-
pair["build_status"] = status
|
| 211 |
-
pair["timestamp"] = datetime.now().isoformat()
|
| 212 |
-
|
| 213 |
-
all_pairs.extend(pairs)
|
| 214 |
-
|
| 215 |
-
return all_pairs
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
def main():
|
| 219 |
-
parser = argparse.ArgumentParser(description="Collect VeriReason training data from AgentIC builds")
|
| 220 |
-
parser.add_argument("--design", type=str, default=None, help="Process specific design only")
|
| 221 |
-
parser.add_argument("--output", type=str, default="training/agentic_sft_data.jsonl", help="Output JSONL file")
|
| 222 |
-
parser.add_argument("--designs-dir", type=str, default=f"{OPENLANE_ROOT}/designs", help="Designs directory")
|
| 223 |
-
args = parser.parse_args()
|
| 224 |
-
|
| 225 |
-
all_pairs: List[Dict[str, str]] = []
|
| 226 |
-
|
| 227 |
-
if args.design:
|
| 228 |
-
design_dir = os.path.join(args.designs_dir, args.design)
|
| 229 |
-
if os.path.isdir(design_dir):
|
| 230 |
-
all_pairs = process_design(design_dir)
|
| 231 |
-
else:
|
| 232 |
-
print(f"Design directory not found: {design_dir}")
|
| 233 |
-
return
|
| 234 |
-
else:
|
| 235 |
-
# Process all designs
|
| 236 |
-
for entry in sorted(os.listdir(args.designs_dir)):
|
| 237 |
-
design_dir = os.path.join(args.designs_dir, entry)
|
| 238 |
-
if os.path.isdir(design_dir):
|
| 239 |
-
pairs = process_design(design_dir)
|
| 240 |
-
all_pairs.extend(pairs)
|
| 241 |
-
if pairs:
|
| 242 |
-
print(f" {entry}: {len(pairs)} training pairs")
|
| 243 |
-
|
| 244 |
-
# Write output
|
| 245 |
-
os.makedirs(os.path.dirname(args.output) if os.path.dirname(args.output) else ".", exist_ok=True)
|
| 246 |
-
with open(args.output, "w") as f:
|
| 247 |
-
for pair in all_pairs:
|
| 248 |
-
f.write(json.dumps(pair, ensure_ascii=False) + "\n")
|
| 249 |
-
|
| 250 |
-
# Summary
|
| 251 |
-
categories = {}
|
| 252 |
-
for p in all_pairs:
|
| 253 |
-
cat = p.get("category", "unknown")
|
| 254 |
-
categories[cat] = categories.get(cat, 0) + 1
|
| 255 |
-
|
| 256 |
-
print(f"\n{'='*50}")
|
| 257 |
-
print(f"Total training pairs: {len(all_pairs)}")
|
| 258 |
-
print(f"Output: {args.output}")
|
| 259 |
-
print(f"Categories:")
|
| 260 |
-
for cat, count in sorted(categories.items()):
|
| 261 |
-
print(f" {cat}: {count}")
|
| 262 |
-
print(f"{'='*50}")
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
if __name__ == "__main__":
|
| 266 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/generate_reasoning.py
DELETED
|
@@ -1,294 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
VeriReason Reasoning Generator (Log-Based)
|
| 4 |
-
============================================
|
| 5 |
-
Feeds actual AgentIC build logs to VeriReason and asks it to generate
|
| 6 |
-
chain-of-thought (CoT) reasoning about what happened β what went wrong,
|
| 7 |
-
why fixes worked, what the correct approach should have been.
|
| 8 |
-
|
| 9 |
-
This creates training data where VeriReason learns from REAL build
|
| 10 |
-
experiences, not generic reasoning.
|
| 11 |
-
|
| 12 |
-
Usage:
|
| 13 |
-
# Step 1: Collect data from cloud builds
|
| 14 |
-
python3 training/collect_training_data.py
|
| 15 |
-
|
| 16 |
-
# Step 2: Generate log-based reasoning
|
| 17 |
-
ollama serve # make sure Ollama is running
|
| 18 |
-
python3 training/generate_reasoning.py
|
| 19 |
-
|
| 20 |
-
# Step 3: Fine-tune
|
| 21 |
-
llamafactory-cli train training/agentic_sft_config.yaml
|
| 22 |
-
"""
|
| 23 |
-
|
| 24 |
-
import argparse
|
| 25 |
-
import glob
|
| 26 |
-
import json
|
| 27 |
-
import os
|
| 28 |
-
import re
|
| 29 |
-
import requests
|
| 30 |
-
import time
|
| 31 |
-
from typing import Optional, List, Dict
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
|
| 35 |
-
VERIREASON_MODEL = os.environ.get(
|
| 36 |
-
"VERIREASON_MODEL",
|
| 37 |
-
"hf.co/mradermacher/VeriReason-Qwen2.5-3b-RTLCoder-Verilog-GRPO-reasoning-tb-GGUF:Q4_K_M",
|
| 38 |
-
)
|
| 39 |
-
OPENLANE_ROOT = os.environ.get("OPENLANE_ROOT", os.path.expanduser("~/OpenLane"))
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def check_ollama() -> bool:
|
| 43 |
-
"""Check if Ollama is running and VeriReason is available."""
|
| 44 |
-
try:
|
| 45 |
-
r = requests.get(f"{OLLAMA_URL}/api/tags", timeout=5)
|
| 46 |
-
return r.status_code == 200
|
| 47 |
-
except Exception:
|
| 48 |
-
return False
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
def ask_verireason(prompt: str, timeout: int = 180) -> Optional[str]:
|
| 52 |
-
"""Send a prompt to VeriReason via Ollama and get the response."""
|
| 53 |
-
try:
|
| 54 |
-
r = requests.post(
|
| 55 |
-
f"{OLLAMA_URL}/api/generate",
|
| 56 |
-
json={
|
| 57 |
-
"model": VERIREASON_MODEL,
|
| 58 |
-
"prompt": prompt,
|
| 59 |
-
"stream": False,
|
| 60 |
-
"options": {"temperature": 0.3, "num_predict": 2048},
|
| 61 |
-
},
|
| 62 |
-
timeout=timeout,
|
| 63 |
-
)
|
| 64 |
-
if r.status_code == 200:
|
| 65 |
-
return r.json().get("response", "").strip()
|
| 66 |
-
return None
|
| 67 |
-
except Exception as e:
|
| 68 |
-
print(f" Ollama error: {e}")
|
| 69 |
-
return None
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
def extract_log_summary(log_path: str, max_chars: int = 4000) -> str:
|
| 73 |
-
"""Extract a condensed summary of key events from a build log."""
|
| 74 |
-
with open(log_path, "r") as f:
|
| 75 |
-
log_text = f.read()
|
| 76 |
-
|
| 77 |
-
events = []
|
| 78 |
-
|
| 79 |
-
# Extract state transitions
|
| 80 |
-
for m in re.finditer(r"\[(\w+)\] Transitioning: (\w+) -> (\w+)", log_text):
|
| 81 |
-
events.append(f"STATE: {m.group(2)} β {m.group(3)}")
|
| 82 |
-
|
| 83 |
-
# Extract errors
|
| 84 |
-
for m in re.finditer(r"(SYNTAX/LINT ERRORS|LINT REPORT):\s*\n([\s\S]*?)(?:\n\d{4}-\d{2}|\Z)", log_text):
|
| 85 |
-
error_text = m.group(2).strip()[:500]
|
| 86 |
-
events.append(f"ERROR: {error_text}")
|
| 87 |
-
|
| 88 |
-
# Extract RTL code blocks (first and last only)
|
| 89 |
-
rtl_blocks = re.findall(r"(GENERATED RTL|FIXED RTL).*?:\s*\n```(?:verilog)?\n([\s\S]*?)```", log_text)
|
| 90 |
-
if rtl_blocks:
|
| 91 |
-
events.append(f"INITIAL RTL:\n```verilog\n{rtl_blocks[0][1][:1500]}\n```")
|
| 92 |
-
if len(rtl_blocks) > 1:
|
| 93 |
-
events.append(f"FINAL RTL:\n```verilog\n{rtl_blocks[-1][1][:1500]}\n```")
|
| 94 |
-
|
| 95 |
-
# Extract simulation results
|
| 96 |
-
for m in re.finditer(r"\[VERIFICATION\] (Sim Failed|Simulation Passed|Diagnosis:.*?)$", log_text, re.MULTILINE):
|
| 97 |
-
events.append(f"SIM: {m.group(1)[:200]}")
|
| 98 |
-
|
| 99 |
-
# Extract TB gate results
|
| 100 |
-
for m in re.finditer(r"TB (COMPILE|STATIC) GATE \((PASS|FAIL)\)", log_text):
|
| 101 |
-
events.append(f"TB GATE: {m.group(1)} {m.group(2)}")
|
| 102 |
-
|
| 103 |
-
# Extract final status
|
| 104 |
-
if "BUILD FAILED" in log_text:
|
| 105 |
-
events.append("RESULT: BUILD FAILED")
|
| 106 |
-
elif "SIGNOFF PASSED" in log_text or "Simulation Passed" in log_text:
|
| 107 |
-
events.append("RESULT: BUILD PASSED")
|
| 108 |
-
|
| 109 |
-
summary = "\n".join(events)
|
| 110 |
-
return summary[:max_chars]
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
def generate_reasoning_from_log(design_name: str, log_summary: str, category: str) -> Optional[str]:
|
| 114 |
-
"""
|
| 115 |
-
Ask VeriReason to read a build log and generate chain-of-thought
|
| 116 |
-
reasoning about what happened and what should have been done.
|
| 117 |
-
"""
|
| 118 |
-
if category == "rtl_generation":
|
| 119 |
-
prompt = f"""You are a Verilog RTL expert reviewing a build log for "{design_name}".
|
| 120 |
-
|
| 121 |
-
BUILD LOG:
|
| 122 |
-
{log_summary}
|
| 123 |
-
|
| 124 |
-
Based on this build log, write a detailed chain-of-thought reasoning that explains:
|
| 125 |
-
1. What the design requirements were
|
| 126 |
-
2. What approach the RTL generator took
|
| 127 |
-
3. What errors occurred (if any) and why
|
| 128 |
-
4. What the correct implementation strategy should be
|
| 129 |
-
5. Key lessons for generating this type of design
|
| 130 |
-
|
| 131 |
-
Write your reasoning inside <think> tags. Be specific about Verilog/SystemVerilog best practices.
|
| 132 |
-
Focus on the WHY, not just the WHAT."""
|
| 133 |
-
|
| 134 |
-
elif category == "rtl_fix":
|
| 135 |
-
prompt = f"""You are a Verilog RTL expert reviewing an error-fix cycle for "{design_name}".
|
| 136 |
-
|
| 137 |
-
BUILD LOG:
|
| 138 |
-
{log_summary}
|
| 139 |
-
|
| 140 |
-
Based on this build log, write a chain-of-thought reasoning that explains:
|
| 141 |
-
1. What the original error was and its root cause
|
| 142 |
-
2. Why the initial code was wrong (specific Verilog/synthesis reason)
|
| 143 |
-
3. How the fix addresses the root cause
|
| 144 |
-
4. What pattern to recognize to avoid this error in future
|
| 145 |
-
5. Any remaining risks or edge cases
|
| 146 |
-
|
| 147 |
-
Write your reasoning inside <think> tags. Be very specific about the Verilog error patterns."""
|
| 148 |
-
|
| 149 |
-
elif category == "error_classification":
|
| 150 |
-
prompt = f"""You are a Verilog verification expert reviewing a simulation failure for "{design_name}".
|
| 151 |
-
|
| 152 |
-
BUILD LOG:
|
| 153 |
-
{log_summary}
|
| 154 |
-
|
| 155 |
-
Based on this build log, write a chain-of-thought reasoning that explains:
|
| 156 |
-
1. What type of failure this is (syntax, logic, timing, architectural)
|
| 157 |
-
2. How to diagnose this class of error systematically
|
| 158 |
-
3. What the root cause was
|
| 159 |
-
4. What the fix strategy should be
|
| 160 |
-
5. How to prevent this type of failure in future designs
|
| 161 |
-
|
| 162 |
-
Write your reasoning inside <think> tags."""
|
| 163 |
-
|
| 164 |
-
else:
|
| 165 |
-
prompt = f"""You are a Verilog expert reviewing a build process for "{design_name}".
|
| 166 |
-
|
| 167 |
-
BUILD LOG:
|
| 168 |
-
{log_summary}
|
| 169 |
-
|
| 170 |
-
Write a chain-of-thought reasoning about:
|
| 171 |
-
1. What happened in this build
|
| 172 |
-
2. What went well and what went wrong
|
| 173 |
-
3. What the correct approach should have been
|
| 174 |
-
4. Key lessons learned
|
| 175 |
-
|
| 176 |
-
Write your reasoning inside <think> tags."""
|
| 177 |
-
|
| 178 |
-
response = ask_verireason(prompt)
|
| 179 |
-
if not response:
|
| 180 |
-
return None
|
| 181 |
-
|
| 182 |
-
# Extract <think> content
|
| 183 |
-
think_match = re.search(r"<think>([\s\S]*?)</think>", response)
|
| 184 |
-
if think_match:
|
| 185 |
-
return f"<think>\n{think_match.group(1).strip()}\n</think>"
|
| 186 |
-
|
| 187 |
-
# If no <think> tags, wrap the whole response
|
| 188 |
-
if len(response) > 50:
|
| 189 |
-
return f"<think>\n{response[:1500].strip()}\n</think>"
|
| 190 |
-
|
| 191 |
-
return None
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
def process_designs(input_file: str, output_file: str, designs_dir: str, max_pairs: int):
|
| 195 |
-
"""Main processing loop."""
|
| 196 |
-
# Load existing training pairs
|
| 197 |
-
pairs = []
|
| 198 |
-
if os.path.exists(input_file):
|
| 199 |
-
with open(input_file, "r") as f:
|
| 200 |
-
for line in f:
|
| 201 |
-
line = line.strip()
|
| 202 |
-
if line:
|
| 203 |
-
pairs.append(json.loads(line))
|
| 204 |
-
print(f"Loaded {len(pairs)} training pairs from {input_file}")
|
| 205 |
-
else:
|
| 206 |
-
print(f"No training data found at {input_file}")
|
| 207 |
-
print("Run 'python3 training/collect_training_data.py' first!")
|
| 208 |
-
return
|
| 209 |
-
|
| 210 |
-
# Build a map of design β log files
|
| 211 |
-
design_logs: Dict[str, str] = {}
|
| 212 |
-
for entry in os.listdir(designs_dir):
|
| 213 |
-
design_dir = os.path.join(designs_dir, entry)
|
| 214 |
-
if os.path.isdir(design_dir):
|
| 215 |
-
logs = glob.glob(os.path.join(design_dir, "*.log"))
|
| 216 |
-
if logs:
|
| 217 |
-
design_logs[entry] = logs[0] # Use first log
|
| 218 |
-
|
| 219 |
-
print(f"Found logs for {len(design_logs)} designs: {', '.join(design_logs.keys())}")
|
| 220 |
-
|
| 221 |
-
enriched = []
|
| 222 |
-
reasoning_count = 0
|
| 223 |
-
|
| 224 |
-
for i, pair in enumerate(pairs[:max_pairs]):
|
| 225 |
-
design = pair.get("design", "")
|
| 226 |
-
category = pair.get("category", "")
|
| 227 |
-
|
| 228 |
-
# Check if we have a log for this design
|
| 229 |
-
if design in design_logs:
|
| 230 |
-
print(f" [{i+1}/{min(len(pairs), max_pairs)}] {design} ({category})...", end=" ", flush=True)
|
| 231 |
-
|
| 232 |
-
log_summary = extract_log_summary(design_logs[design])
|
| 233 |
-
reasoning = generate_reasoning_from_log(design, log_summary, category)
|
| 234 |
-
|
| 235 |
-
if reasoning:
|
| 236 |
-
enriched_pair = pair.copy()
|
| 237 |
-
enriched_pair["output"] = reasoning + "\n" + pair["output"]
|
| 238 |
-
enriched_pair["has_reasoning"] = True
|
| 239 |
-
enriched_pair["reasoning_source"] = "build_log"
|
| 240 |
-
enriched.append(enriched_pair)
|
| 241 |
-
reasoning_count += 1
|
| 242 |
-
print("β
")
|
| 243 |
-
else:
|
| 244 |
-
enriched.append(pair)
|
| 245 |
-
print("β οΈ (kept without reasoning)")
|
| 246 |
-
else:
|
| 247 |
-
enriched.append(pair)
|
| 248 |
-
|
| 249 |
-
time.sleep(0.5)
|
| 250 |
-
|
| 251 |
-
# Write output
|
| 252 |
-
os.makedirs(os.path.dirname(output_file) if os.path.dirname(output_file) else ".", exist_ok=True)
|
| 253 |
-
with open(output_file, "w") as f:
|
| 254 |
-
for pair in enriched:
|
| 255 |
-
f.write(json.dumps(pair, ensure_ascii=False) + "\n")
|
| 256 |
-
|
| 257 |
-
print(f"\n{'='*50}")
|
| 258 |
-
print(f"Total pairs: {len(enriched)}")
|
| 259 |
-
print(f" With log-based reasoning: {reasoning_count}")
|
| 260 |
-
print(f" Without reasoning: {len(enriched) - reasoning_count}")
|
| 261 |
-
print(f"Output: {output_file}")
|
| 262 |
-
print(f"{'='*50}")
|
| 263 |
-
print(f"\nNext: llamafactory-cli train training/agentic_sft_config.yaml")
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
def main():
|
| 267 |
-
parser = argparse.ArgumentParser(description="Generate reasoning from build logs")
|
| 268 |
-
parser.add_argument("--input", default="training/agentic_sft_data.jsonl")
|
| 269 |
-
parser.add_argument("--output", default="training/agentic_sft_data_with_reasoning.jsonl")
|
| 270 |
-
parser.add_argument("--designs-dir", default=f"{OPENLANE_ROOT}/designs")
|
| 271 |
-
parser.add_argument("--max", type=int, default=100)
|
| 272 |
-
parser.add_argument("--model", type=str, default=None)
|
| 273 |
-
args = parser.parse_args()
|
| 274 |
-
|
| 275 |
-
global VERIREASON_MODEL
|
| 276 |
-
if args.model:
|
| 277 |
-
VERIREASON_MODEL = args.model
|
| 278 |
-
|
| 279 |
-
print("VeriReason Log-Based Reasoning Generator")
|
| 280 |
-
print(f" Model: {VERIREASON_MODEL}")
|
| 281 |
-
print(f" Ollama: {OLLAMA_URL}")
|
| 282 |
-
print(f" Designs: {args.designs_dir}")
|
| 283 |
-
print()
|
| 284 |
-
|
| 285 |
-
if not check_ollama():
|
| 286 |
-
print("Error: Ollama is not running!")
|
| 287 |
-
print(" Start it: ollama serve")
|
| 288 |
-
return
|
| 289 |
-
|
| 290 |
-
process_designs(args.input, args.output, args.designs_dir, args.max)
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
if __name__ == "__main__":
|
| 294 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
training/verilog_rewards_enhanced.py
DELETED
|
@@ -1,290 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Enhanced GRPO Reward Function for VeriReason
|
| 4 |
-
=============================================
|
| 5 |
-
This replaces the original verilog_rewards_tb.py with additional
|
| 6 |
-
reward signals from Verilator lint checking.
|
| 7 |
-
|
| 8 |
-
Original VeriReason reward: testbench pass/fail only (binary)
|
| 9 |
-
Enhanced reward: testbench + lint + structural quality checks
|
| 10 |
-
|
| 11 |
-
Usage with GRPO training:
|
| 12 |
-
Copy this file to src/open-r1/ in the OpenR1 directory, then
|
| 13 |
-
reference it in your GRPO training config.
|
| 14 |
-
|
| 15 |
-
Reward breakdown:
|
| 16 |
-
+0.4 Testbench simulation passes
|
| 17 |
-
+0.2 Verilator lint clean (no warnings)
|
| 18 |
-
+0.1 Syntax compiles without errors
|
| 19 |
-
+0.1 No multi-driven signals (MULTIDRIVEN)
|
| 20 |
-
+0.1 Proper reset initialization (all registers reset)
|
| 21 |
-
+0.05 Uses always_ff/always_comb (not legacy always)
|
| 22 |
-
+0.05 No width mismatches
|
| 23 |
-
-0.5 Testbench fails
|
| 24 |
-
-0.3 Syntax error (doesn't compile)
|
| 25 |
-
-0.2 Contains X/Z propagation issues
|
| 26 |
-
"""
|
| 27 |
-
|
| 28 |
-
import subprocess
|
| 29 |
-
import tempfile
|
| 30 |
-
import os
|
| 31 |
-
import re
|
| 32 |
-
from typing import Dict, Tuple
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
def extract_verilog_from_response(response: str) -> str:
|
| 36 |
-
"""Extract Verilog code from LLM response, handling markdown blocks."""
|
| 37 |
-
# Try to extract from code blocks
|
| 38 |
-
match = re.search(
|
| 39 |
-
r"```(?:verilog|systemverilog|sv)?\s*\n([\s\S]*?)```", response
|
| 40 |
-
)
|
| 41 |
-
if match:
|
| 42 |
-
return match.group(1).strip()
|
| 43 |
-
|
| 44 |
-
# If no code block, try to find module..endmodule
|
| 45 |
-
match = re.search(r"(module\s+\w+[\s\S]*?endmodule)", response)
|
| 46 |
-
if match:
|
| 47 |
-
return match.group(1).strip()
|
| 48 |
-
|
| 49 |
-
return response.strip()
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
def run_verilator_syntax(verilog_code: str, work_dir: str) -> Tuple[bool, str]:
|
| 53 |
-
"""Run Verilator syntax check. Returns (passed, output)."""
|
| 54 |
-
src_path = os.path.join(work_dir, "dut.sv")
|
| 55 |
-
with open(src_path, "w") as f:
|
| 56 |
-
f.write(verilog_code)
|
| 57 |
-
|
| 58 |
-
try:
|
| 59 |
-
result = subprocess.run(
|
| 60 |
-
["verilator", "--lint-only", "--sv", "--timing", src_path],
|
| 61 |
-
capture_output=True, text=True, timeout=30,
|
| 62 |
-
)
|
| 63 |
-
output = result.stdout + result.stderr
|
| 64 |
-
passed = result.returncode == 0
|
| 65 |
-
return passed, output
|
| 66 |
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
| 67 |
-
return False, "verilator not available or timeout"
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
def run_verilator_lint(verilog_code: str, work_dir: str) -> Tuple[bool, str, Dict[str, int]]:
|
| 71 |
-
"""Run Verilator lint check with detailed warning analysis."""
|
| 72 |
-
src_path = os.path.join(work_dir, "dut.sv")
|
| 73 |
-
with open(src_path, "w") as f:
|
| 74 |
-
f.write(verilog_code)
|
| 75 |
-
|
| 76 |
-
try:
|
| 77 |
-
result = subprocess.run(
|
| 78 |
-
["verilator", "--lint-only", "--sv", "--timing", "-Wall", src_path],
|
| 79 |
-
capture_output=True, text=True, timeout=30,
|
| 80 |
-
)
|
| 81 |
-
output = result.stdout + result.stderr
|
| 82 |
-
|
| 83 |
-
# Count warnings by type
|
| 84 |
-
warnings: Dict[str, int] = {}
|
| 85 |
-
for match in re.finditer(r"%Warning-(\w+):", output):
|
| 86 |
-
wtype = match.group(1)
|
| 87 |
-
warnings[wtype] = warnings.get(wtype, 0) + 1
|
| 88 |
-
|
| 89 |
-
lint_clean = len(warnings) == 0 and result.returncode == 0
|
| 90 |
-
return lint_clean, output, warnings
|
| 91 |
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
| 92 |
-
return False, "verilator not available or timeout", {}
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
def check_structural_quality(verilog_code: str) -> Dict[str, float]:
|
| 96 |
-
"""Check structural code quality. Returns individual reward components."""
|
| 97 |
-
rewards = {}
|
| 98 |
-
|
| 99 |
-
# 1. Uses modern always_ff/always_comb instead of legacy always
|
| 100 |
-
has_modern = bool(re.search(r'\balways_ff\b|\balways_comb\b', verilog_code))
|
| 101 |
-
has_legacy = bool(re.search(r'\balways\s*@', verilog_code))
|
| 102 |
-
if has_modern and not has_legacy:
|
| 103 |
-
rewards["modern_sv"] = 0.05
|
| 104 |
-
elif has_modern:
|
| 105 |
-
rewards["modern_sv"] = 0.025 # partial credit
|
| 106 |
-
else:
|
| 107 |
-
rewards["modern_sv"] = 0.0
|
| 108 |
-
|
| 109 |
-
# 2. All registers have reset initialization
|
| 110 |
-
ff_blocks = re.findall(r'always_ff\s*@\s*\(.*?\)\s*begin([\s\S]*?)end', verilog_code)
|
| 111 |
-
if ff_blocks:
|
| 112 |
-
all_have_reset = all("rst" in block.lower() or "reset" in block.lower() for block in ff_blocks)
|
| 113 |
-
rewards["reset_init"] = 0.1 if all_have_reset else 0.0
|
| 114 |
-
else:
|
| 115 |
-
# Check legacy always blocks for reset
|
| 116 |
-
always_blocks = re.findall(r'always\s*@\s*\(.*?\)\s*begin([\s\S]*?)end', verilog_code)
|
| 117 |
-
if always_blocks:
|
| 118 |
-
all_have_reset = all("rst" in block.lower() or "reset" in block.lower() for block in always_blocks)
|
| 119 |
-
rewards["reset_init"] = 0.1 if all_have_reset else 0.0
|
| 120 |
-
else:
|
| 121 |
-
rewards["reset_init"] = 0.0
|
| 122 |
-
|
| 123 |
-
# 3. No X/Z literal usage (2-state safe for Verilator)
|
| 124 |
-
xz_usage = len(re.findall(r"\b[0-9]+'[bBoOhHdD].*[xXzZ]", verilog_code))
|
| 125 |
-
rewards["no_xz"] = 0.05 if xz_usage == 0 else -0.1
|
| 126 |
-
|
| 127 |
-
# 4. Has proper module header with types
|
| 128 |
-
has_typed_ports = bool(re.search(r'\b(input|output)\s+(logic|wire|reg)\b', verilog_code))
|
| 129 |
-
rewards["typed_ports"] = 0.025 if has_typed_ports else 0.0
|
| 130 |
-
|
| 131 |
-
return rewards
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
def run_simulation(
|
| 135 |
-
verilog_code: str, testbench_code: str, work_dir: str
|
| 136 |
-
) -> Tuple[bool, str]:
|
| 137 |
-
"""Compile and simulate with Verilator or iverilog. Returns (passed, output)."""
|
| 138 |
-
rtl_path = os.path.join(work_dir, "dut.sv")
|
| 139 |
-
tb_path = os.path.join(work_dir, "tb.sv")
|
| 140 |
-
|
| 141 |
-
with open(rtl_path, "w") as f:
|
| 142 |
-
f.write(verilog_code)
|
| 143 |
-
with open(tb_path, "w") as f:
|
| 144 |
-
f.write(testbench_code)
|
| 145 |
-
|
| 146 |
-
# Try iverilog first (more lenient with SV features)
|
| 147 |
-
try:
|
| 148 |
-
compile_result = subprocess.run(
|
| 149 |
-
["iverilog", "-g2012", "-o", os.path.join(work_dir, "sim"), rtl_path, tb_path],
|
| 150 |
-
capture_output=True, text=True, timeout=30,
|
| 151 |
-
)
|
| 152 |
-
if compile_result.returncode != 0:
|
| 153 |
-
return False, f"Compile error: {compile_result.stderr}"
|
| 154 |
-
|
| 155 |
-
sim_result = subprocess.run(
|
| 156 |
-
["vvp", os.path.join(work_dir, "sim")],
|
| 157 |
-
capture_output=True, text=True, timeout=60,
|
| 158 |
-
)
|
| 159 |
-
output = sim_result.stdout + sim_result.stderr
|
| 160 |
-
passed = "TEST PASSED" in output and "TEST FAILED" not in output
|
| 161 |
-
return passed, output
|
| 162 |
-
except (subprocess.TimeoutExpired, FileNotFoundError):
|
| 163 |
-
return False, "simulation timeout or tools not found"
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
def compute_reward(
|
| 167 |
-
response: str,
|
| 168 |
-
testbench_code: str = "",
|
| 169 |
-
reference_code: str = "",
|
| 170 |
-
) -> float:
|
| 171 |
-
"""
|
| 172 |
-
Compute the total reward for a generated Verilog response.
|
| 173 |
-
|
| 174 |
-
This is the main function called by the GRPO training loop.
|
| 175 |
-
|
| 176 |
-
Args:
|
| 177 |
-
response: The LLM's generated response (may contain markdown)
|
| 178 |
-
testbench_code: Optional testbench for simulation testing
|
| 179 |
-
reference_code: Optional reference implementation for comparison
|
| 180 |
-
|
| 181 |
-
Returns:
|
| 182 |
-
float: Reward score between -1.0 and 1.0
|
| 183 |
-
"""
|
| 184 |
-
verilog_code = extract_verilog_from_response(response)
|
| 185 |
-
|
| 186 |
-
if not verilog_code or "module" not in verilog_code:
|
| 187 |
-
return -0.5 # No valid Verilog generated
|
| 188 |
-
|
| 189 |
-
total_reward = 0.0
|
| 190 |
-
|
| 191 |
-
with tempfile.TemporaryDirectory(prefix="verireason_") as work_dir:
|
| 192 |
-
# 1. Syntax check (+0.1)
|
| 193 |
-
syntax_ok, syntax_output = run_verilator_syntax(verilog_code, work_dir)
|
| 194 |
-
if syntax_ok:
|
| 195 |
-
total_reward += 0.1
|
| 196 |
-
else:
|
| 197 |
-
total_reward -= 0.3
|
| 198 |
-
return total_reward # No point continuing if syntax fails
|
| 199 |
-
|
| 200 |
-
# 2. Lint check (+0.2)
|
| 201 |
-
lint_ok, lint_output, warnings = run_verilator_lint(verilog_code, work_dir)
|
| 202 |
-
if lint_ok:
|
| 203 |
-
total_reward += 0.2
|
| 204 |
-
else:
|
| 205 |
-
# Partial penalties for specific warning types
|
| 206 |
-
if "MULTIDRIVEN" in warnings:
|
| 207 |
-
total_reward -= 0.1 # Severe: multi-driven signals
|
| 208 |
-
if "WIDTH" in warnings:
|
| 209 |
-
total_reward -= 0.05 # Width mismatch
|
| 210 |
-
if warnings and "MULTIDRIVEN" not in warnings and "WIDTH" not in warnings:
|
| 211 |
-
total_reward += 0.1 # Minor warnings = partial credit
|
| 212 |
-
|
| 213 |
-
# 3. Structural quality checks (+0.2 max)
|
| 214 |
-
quality_rewards = check_structural_quality(verilog_code)
|
| 215 |
-
total_reward += sum(quality_rewards.values())
|
| 216 |
-
|
| 217 |
-
# 4. Simulation test (+0.4 / -0.5)
|
| 218 |
-
if testbench_code:
|
| 219 |
-
sim_passed, sim_output = run_simulation(verilog_code, testbench_code, work_dir)
|
| 220 |
-
if sim_passed:
|
| 221 |
-
total_reward += 0.4
|
| 222 |
-
else:
|
| 223 |
-
total_reward -= 0.5
|
| 224 |
-
# Check for X/Z issues specifically
|
| 225 |
-
if "X/Z detected" in sim_output:
|
| 226 |
-
total_reward -= 0.2
|
| 227 |
-
|
| 228 |
-
# Clamp to [-1.0, 1.0]
|
| 229 |
-
return max(-1.0, min(1.0, total_reward))
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
# βββ Batch interface for GRPO training ββββββββββββββββββββββββββββββ
|
| 233 |
-
|
| 234 |
-
def compute_rewards_batch(
|
| 235 |
-
responses: list,
|
| 236 |
-
testbenches: list = None,
|
| 237 |
-
references: list = None,
|
| 238 |
-
) -> list:
|
| 239 |
-
"""
|
| 240 |
-
Batch reward computation for GRPO training.
|
| 241 |
-
|
| 242 |
-
Args:
|
| 243 |
-
responses: List of LLM responses
|
| 244 |
-
testbenches: Optional list of testbench codes (parallel to responses)
|
| 245 |
-
references: Optional list of reference codes
|
| 246 |
-
|
| 247 |
-
Returns:
|
| 248 |
-
List of reward floats
|
| 249 |
-
"""
|
| 250 |
-
if testbenches is None:
|
| 251 |
-
testbenches = [""] * len(responses)
|
| 252 |
-
if references is None:
|
| 253 |
-
references = [""] * len(responses)
|
| 254 |
-
|
| 255 |
-
rewards = []
|
| 256 |
-
for resp, tb, ref in zip(responses, testbenches, references):
|
| 257 |
-
try:
|
| 258 |
-
r = compute_reward(resp, tb, ref)
|
| 259 |
-
except Exception:
|
| 260 |
-
r = -0.5 # Fail-safe
|
| 261 |
-
rewards.append(r)
|
| 262 |
-
return rewards
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
if __name__ == "__main__":
|
| 266 |
-
# Quick test
|
| 267 |
-
test_code = """
|
| 268 |
-
module counter #(parameter WIDTH = 8) (
|
| 269 |
-
input logic clk,
|
| 270 |
-
input logic rst_n,
|
| 271 |
-
input logic en,
|
| 272 |
-
output logic [WIDTH-1:0] cnt
|
| 273 |
-
);
|
| 274 |
-
always_ff @(posedge clk or negedge rst_n) begin
|
| 275 |
-
if (!rst_n)
|
| 276 |
-
cnt <= '0;
|
| 277 |
-
else if (en)
|
| 278 |
-
cnt <= cnt + 1'b1;
|
| 279 |
-
end
|
| 280 |
-
endmodule
|
| 281 |
-
"""
|
| 282 |
-
print(f"Reward for clean counter: {compute_reward(test_code):.3f}")
|
| 283 |
-
|
| 284 |
-
bad_code = """
|
| 285 |
-
module counter(input clk, output reg [7:0] cnt);
|
| 286 |
-
always @(posedge clk) cnt <= cnt + 1;
|
| 287 |
-
always @(negedge clk) cnt <= 0; // MULTIDRIVEN!
|
| 288 |
-
endmodule
|
| 289 |
-
"""
|
| 290 |
-
print(f"Reward for bad counter: {compute_reward(bad_code):.3f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
web/package-lock.json
CHANGED
|
@@ -16,6 +16,8 @@
|
|
| 16 |
"lucide-react": "^0.575.0",
|
| 17 |
"react": "^19.2.0",
|
| 18 |
"react-dom": "^19.2.0",
|
|
|
|
|
|
|
| 19 |
"three": "^0.183.1"
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
|
@@ -1551,6 +1553,15 @@
|
|
| 1551 |
"@babel/types": "^7.28.2"
|
| 1552 |
}
|
| 1553 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1554 |
"node_modules/@types/draco3d": {
|
| 1555 |
"version": "1.4.10",
|
| 1556 |
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
|
@@ -1561,9 +1572,26 @@
|
|
| 1561 |
"version": "1.0.8",
|
| 1562 |
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 1563 |
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 1564 |
-
"dev": true,
|
| 1565 |
"license": "MIT"
|
| 1566 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1567 |
"node_modules/@types/json-schema": {
|
| 1568 |
"version": "7.0.15",
|
| 1569 |
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
|
@@ -1571,6 +1599,21 @@
|
|
| 1571 |
"dev": true,
|
| 1572 |
"license": "MIT"
|
| 1573 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1574 |
"node_modules/@types/node": {
|
| 1575 |
"version": "24.10.13",
|
| 1576 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
|
|
@@ -1636,6 +1679,12 @@
|
|
| 1636 |
"meshoptimizer": "~1.0.1"
|
| 1637 |
}
|
| 1638 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1639 |
"node_modules/@types/webxr": {
|
| 1640 |
"version": "0.5.24",
|
| 1641 |
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
|
@@ -1924,6 +1973,12 @@
|
|
| 1924 |
"url": "https://opencollective.com/eslint"
|
| 1925 |
}
|
| 1926 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1927 |
"node_modules/@use-gesture/core": {
|
| 1928 |
"version": "10.3.1",
|
| 1929 |
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
|
@@ -2049,6 +2104,16 @@
|
|
| 2049 |
"proxy-from-env": "^1.1.0"
|
| 2050 |
}
|
| 2051 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2052 |
"node_modules/balanced-match": {
|
| 2053 |
"version": "1.0.2",
|
| 2054 |
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
@@ -2224,6 +2289,16 @@
|
|
| 2224 |
],
|
| 2225 |
"license": "CC-BY-4.0"
|
| 2226 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2227 |
"node_modules/chalk": {
|
| 2228 |
"version": "4.1.2",
|
| 2229 |
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
|
@@ -2241,6 +2316,46 @@
|
|
| 2241 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 2242 |
}
|
| 2243 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2244 |
"node_modules/color-convert": {
|
| 2245 |
"version": "2.0.1",
|
| 2246 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
@@ -2273,6 +2388,16 @@
|
|
| 2273 |
"node": ">= 0.8"
|
| 2274 |
}
|
| 2275 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2276 |
"node_modules/concat-map": {
|
| 2277 |
"version": "0.0.1",
|
| 2278 |
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
@@ -2329,7 +2454,6 @@
|
|
| 2329 |
"version": "4.4.3",
|
| 2330 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 2331 |
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 2332 |
-
"dev": true,
|
| 2333 |
"license": "MIT",
|
| 2334 |
"dependencies": {
|
| 2335 |
"ms": "^2.1.3"
|
|
@@ -2343,6 +2467,19 @@
|
|
| 2343 |
}
|
| 2344 |
}
|
| 2345 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2346 |
"node_modules/deep-is": {
|
| 2347 |
"version": "0.1.4",
|
| 2348 |
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
|
@@ -2359,6 +2496,15 @@
|
|
| 2359 |
"node": ">=0.4.0"
|
| 2360 |
}
|
| 2361 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2362 |
"node_modules/detect-gpu": {
|
| 2363 |
"version": "5.0.70",
|
| 2364 |
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
|
@@ -2368,6 +2514,19 @@
|
|
| 2368 |
"webgl-constants": "^1.1.1"
|
| 2369 |
}
|
| 2370 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2371 |
"node_modules/draco3d": {
|
| 2372 |
"version": "1.5.7",
|
| 2373 |
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
|
@@ -2679,6 +2838,16 @@
|
|
| 2679 |
"node": ">=4.0"
|
| 2680 |
}
|
| 2681 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2682 |
"node_modules/esutils": {
|
| 2683 |
"version": "2.0.3",
|
| 2684 |
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
|
@@ -2689,6 +2858,12 @@
|
|
| 2689 |
"node": ">=0.10.0"
|
| 2690 |
}
|
| 2691 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2692 |
"node_modules/fast-deep-equal": {
|
| 2693 |
"version": "3.1.3",
|
| 2694 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
@@ -3012,6 +3187,46 @@
|
|
| 3012 |
"node": ">= 0.4"
|
| 3013 |
}
|
| 3014 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3015 |
"node_modules/hermes-estree": {
|
| 3016 |
"version": "0.25.1",
|
| 3017 |
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
|
@@ -3035,6 +3250,16 @@
|
|
| 3035 |
"integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
|
| 3036 |
"license": "Apache-2.0"
|
| 3037 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3038 |
"node_modules/ieee754": {
|
| 3039 |
"version": "1.2.1",
|
| 3040 |
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
|
@@ -3098,6 +3323,46 @@
|
|
| 3098 |
"node": ">=0.8.19"
|
| 3099 |
}
|
| 3100 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3101 |
"node_modules/is-extglob": {
|
| 3102 |
"version": "2.1.1",
|
| 3103 |
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
|
@@ -3121,6 +3386,28 @@
|
|
| 3121 |
"node": ">=0.10.0"
|
| 3122 |
}
|
| 3123 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3124 |
"node_modules/is-promise": {
|
| 3125 |
"version": "2.2.2",
|
| 3126 |
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
|
@@ -3268,6 +3555,16 @@
|
|
| 3268 |
"dev": true,
|
| 3269 |
"license": "MIT"
|
| 3270 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3271 |
"node_modules/lru-cache": {
|
| 3272 |
"version": "5.1.1",
|
| 3273 |
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
@@ -3297,6 +3594,16 @@
|
|
| 3297 |
"three": ">=0.134.0"
|
| 3298 |
}
|
| 3299 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3300 |
"node_modules/math-intrinsics": {
|
| 3301 |
"version": "1.1.0",
|
| 3302 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
@@ -3306,94 +3613,938 @@
|
|
| 3306 |
"node": ">= 0.4"
|
| 3307 |
}
|
| 3308 |
},
|
| 3309 |
-
"node_modules/
|
| 3310 |
-
"version": "3.
|
| 3311 |
-
"resolved": "https://registry.npmjs.org/
|
| 3312 |
-
"integrity": "sha512-/
|
| 3313 |
"license": "MIT",
|
| 3314 |
-
"
|
| 3315 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3316 |
}
|
| 3317 |
},
|
| 3318 |
-
"node_modules/
|
| 3319 |
-
"version": "
|
| 3320 |
-
"resolved": "https://registry.npmjs.org/
|
| 3321 |
-
"integrity": "sha512-
|
| 3322 |
-
"license": "MIT"
|
| 3323 |
-
},
|
| 3324 |
-
"node_modules/mime-db": {
|
| 3325 |
-
"version": "1.52.0",
|
| 3326 |
-
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 3327 |
-
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 3328 |
"license": "MIT",
|
| 3329 |
"engines": {
|
| 3330 |
-
"node": ">=
|
|
|
|
|
|
|
|
|
|
| 3331 |
}
|
| 3332 |
},
|
| 3333 |
-
"node_modules/
|
| 3334 |
-
"version": "2.
|
| 3335 |
-
"resolved": "https://registry.npmjs.org/
|
| 3336 |
-
"integrity": "sha512-
|
| 3337 |
"license": "MIT",
|
| 3338 |
"dependencies": {
|
| 3339 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3340 |
},
|
| 3341 |
-
"
|
| 3342 |
-
"
|
|
|
|
| 3343 |
}
|
| 3344 |
},
|
| 3345 |
-
"node_modules/
|
| 3346 |
-
"version": "3.1.
|
| 3347 |
-
"resolved": "https://registry.npmjs.org/
|
| 3348 |
-
"integrity": "sha512-
|
| 3349 |
-
"
|
| 3350 |
-
"license": "ISC",
|
| 3351 |
"dependencies": {
|
| 3352 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3353 |
},
|
| 3354 |
-
"
|
| 3355 |
-
"
|
|
|
|
| 3356 |
}
|
| 3357 |
},
|
| 3358 |
-
"node_modules/
|
| 3359 |
-
"version": "
|
| 3360 |
-
"resolved": "https://registry.npmjs.org/
|
| 3361 |
-
"integrity": "sha512-
|
| 3362 |
"license": "MIT",
|
| 3363 |
"dependencies": {
|
| 3364 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3365 |
}
|
| 3366 |
},
|
| 3367 |
-
"node_modules/
|
| 3368 |
-
"version": "
|
| 3369 |
-
"resolved": "https://registry.npmjs.org/
|
| 3370 |
-
"integrity": "sha512-
|
| 3371 |
-
"license": "MIT"
|
| 3372 |
-
},
|
| 3373 |
-
"node_modules/ms": {
|
| 3374 |
-
"version": "2.1.3",
|
| 3375 |
-
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 3376 |
-
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 3377 |
-
"dev": true,
|
| 3378 |
-
"license": "MIT"
|
| 3379 |
-
},
|
| 3380 |
-
"node_modules/nanoid": {
|
| 3381 |
-
"version": "3.3.11",
|
| 3382 |
-
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 3383 |
-
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 3384 |
-
"dev": true,
|
| 3385 |
-
"funding": [
|
| 3386 |
-
{
|
| 3387 |
-
"type": "github",
|
| 3388 |
-
"url": "https://github.com/sponsors/ai"
|
| 3389 |
-
}
|
| 3390 |
-
],
|
| 3391 |
"license": "MIT",
|
| 3392 |
-
"
|
| 3393 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3394 |
},
|
| 3395 |
-
"
|
| 3396 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3397 |
}
|
| 3398 |
},
|
| 3399 |
"node_modules/natural-compare": {
|
|
@@ -3473,6 +4624,31 @@
|
|
| 3473 |
"node": ">=6"
|
| 3474 |
}
|
| 3475 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3476 |
"node_modules/path-exists": {
|
| 3477 |
"version": "4.0.0",
|
| 3478 |
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
@@ -3567,6 +4743,16 @@
|
|
| 3567 |
"lie": "^3.0.2"
|
| 3568 |
}
|
| 3569 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3570 |
"node_modules/proxy-from-env": {
|
| 3571 |
"version": "1.1.0",
|
| 3572 |
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
@@ -3604,6 +4790,33 @@
|
|
| 3604 |
"react": "^19.2.4"
|
| 3605 |
}
|
| 3606 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3607 |
"node_modules/react-refresh": {
|
| 3608 |
"version": "0.18.0",
|
| 3609 |
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
|
@@ -3629,6 +4842,72 @@
|
|
| 3629 |
}
|
| 3630 |
}
|
| 3631 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3632 |
"node_modules/require-from-string": {
|
| 3633 |
"version": "2.0.2",
|
| 3634 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
@@ -3740,6 +5019,16 @@
|
|
| 3740 |
"node": ">=0.10.0"
|
| 3741 |
}
|
| 3742 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3743 |
"node_modules/stats-gl": {
|
| 3744 |
"version": "2.4.2",
|
| 3745 |
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
|
@@ -3766,6 +5055,20 @@
|
|
| 3766 |
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
| 3767 |
"license": "MIT"
|
| 3768 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3769 |
"node_modules/strip-json-comments": {
|
| 3770 |
"version": "3.1.1",
|
| 3771 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
@@ -3779,6 +5082,24 @@
|
|
| 3779 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 3780 |
}
|
| 3781 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3782 |
"node_modules/supports-color": {
|
| 3783 |
"version": "7.2.0",
|
| 3784 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
@@ -3856,6 +5177,16 @@
|
|
| 3856 |
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 3857 |
}
|
| 3858 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3859 |
"node_modules/troika-three-text": {
|
| 3860 |
"version": "0.52.4",
|
| 3861 |
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
|
@@ -3886,6 +5217,16 @@
|
|
| 3886 |
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
| 3887 |
"license": "MIT"
|
| 3888 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3889 |
"node_modules/ts-api-utils": {
|
| 3890 |
"version": "2.4.0",
|
| 3891 |
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
|
|
@@ -4000,6 +5341,93 @@
|
|
| 4000 |
"dev": true,
|
| 4001 |
"license": "MIT"
|
| 4002 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4003 |
"node_modules/update-browserslist-db": {
|
| 4004 |
"version": "1.2.3",
|
| 4005 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
|
@@ -4059,6 +5487,34 @@
|
|
| 4059 |
"node": ">= 4"
|
| 4060 |
}
|
| 4061 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4062 |
"node_modules/vite": {
|
| 4063 |
"version": "7.3.1",
|
| 4064 |
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
|
@@ -4241,6 +5697,16 @@
|
|
| 4241 |
"optional": true
|
| 4242 |
}
|
| 4243 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4244 |
}
|
| 4245 |
}
|
| 4246 |
}
|
|
|
|
| 16 |
"lucide-react": "^0.575.0",
|
| 17 |
"react": "^19.2.0",
|
| 18 |
"react-dom": "^19.2.0",
|
| 19 |
+
"react-markdown": "^10.1.0",
|
| 20 |
+
"remark-gfm": "^4.0.1",
|
| 21 |
"three": "^0.183.1"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
|
|
|
| 1553 |
"@babel/types": "^7.28.2"
|
| 1554 |
}
|
| 1555 |
},
|
| 1556 |
+
"node_modules/@types/debug": {
|
| 1557 |
+
"version": "4.1.12",
|
| 1558 |
+
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
| 1559 |
+
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
| 1560 |
+
"license": "MIT",
|
| 1561 |
+
"dependencies": {
|
| 1562 |
+
"@types/ms": "*"
|
| 1563 |
+
}
|
| 1564 |
+
},
|
| 1565 |
"node_modules/@types/draco3d": {
|
| 1566 |
"version": "1.4.10",
|
| 1567 |
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
|
|
|
| 1572 |
"version": "1.0.8",
|
| 1573 |
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 1574 |
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
|
|
|
| 1575 |
"license": "MIT"
|
| 1576 |
},
|
| 1577 |
+
"node_modules/@types/estree-jsx": {
|
| 1578 |
+
"version": "1.0.5",
|
| 1579 |
+
"resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
|
| 1580 |
+
"integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
|
| 1581 |
+
"license": "MIT",
|
| 1582 |
+
"dependencies": {
|
| 1583 |
+
"@types/estree": "*"
|
| 1584 |
+
}
|
| 1585 |
+
},
|
| 1586 |
+
"node_modules/@types/hast": {
|
| 1587 |
+
"version": "3.0.4",
|
| 1588 |
+
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
| 1589 |
+
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
| 1590 |
+
"license": "MIT",
|
| 1591 |
+
"dependencies": {
|
| 1592 |
+
"@types/unist": "*"
|
| 1593 |
+
}
|
| 1594 |
+
},
|
| 1595 |
"node_modules/@types/json-schema": {
|
| 1596 |
"version": "7.0.15",
|
| 1597 |
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
|
|
|
| 1599 |
"dev": true,
|
| 1600 |
"license": "MIT"
|
| 1601 |
},
|
| 1602 |
+
"node_modules/@types/mdast": {
|
| 1603 |
+
"version": "4.0.4",
|
| 1604 |
+
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
| 1605 |
+
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
|
| 1606 |
+
"license": "MIT",
|
| 1607 |
+
"dependencies": {
|
| 1608 |
+
"@types/unist": "*"
|
| 1609 |
+
}
|
| 1610 |
+
},
|
| 1611 |
+
"node_modules/@types/ms": {
|
| 1612 |
+
"version": "2.1.0",
|
| 1613 |
+
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
| 1614 |
+
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
| 1615 |
+
"license": "MIT"
|
| 1616 |
+
},
|
| 1617 |
"node_modules/@types/node": {
|
| 1618 |
"version": "24.10.13",
|
| 1619 |
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz",
|
|
|
|
| 1679 |
"meshoptimizer": "~1.0.1"
|
| 1680 |
}
|
| 1681 |
},
|
| 1682 |
+
"node_modules/@types/unist": {
|
| 1683 |
+
"version": "3.0.3",
|
| 1684 |
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
|
| 1685 |
+
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
|
| 1686 |
+
"license": "MIT"
|
| 1687 |
+
},
|
| 1688 |
"node_modules/@types/webxr": {
|
| 1689 |
"version": "0.5.24",
|
| 1690 |
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
|
|
|
| 1973 |
"url": "https://opencollective.com/eslint"
|
| 1974 |
}
|
| 1975 |
},
|
| 1976 |
+
"node_modules/@ungap/structured-clone": {
|
| 1977 |
+
"version": "1.3.0",
|
| 1978 |
+
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
| 1979 |
+
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
| 1980 |
+
"license": "ISC"
|
| 1981 |
+
},
|
| 1982 |
"node_modules/@use-gesture/core": {
|
| 1983 |
"version": "10.3.1",
|
| 1984 |
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
|
|
|
| 2104 |
"proxy-from-env": "^1.1.0"
|
| 2105 |
}
|
| 2106 |
},
|
| 2107 |
+
"node_modules/bail": {
|
| 2108 |
+
"version": "2.0.2",
|
| 2109 |
+
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
| 2110 |
+
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
|
| 2111 |
+
"license": "MIT",
|
| 2112 |
+
"funding": {
|
| 2113 |
+
"type": "github",
|
| 2114 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2115 |
+
}
|
| 2116 |
+
},
|
| 2117 |
"node_modules/balanced-match": {
|
| 2118 |
"version": "1.0.2",
|
| 2119 |
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
|
|
| 2289 |
],
|
| 2290 |
"license": "CC-BY-4.0"
|
| 2291 |
},
|
| 2292 |
+
"node_modules/ccount": {
|
| 2293 |
+
"version": "2.0.1",
|
| 2294 |
+
"resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
|
| 2295 |
+
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
|
| 2296 |
+
"license": "MIT",
|
| 2297 |
+
"funding": {
|
| 2298 |
+
"type": "github",
|
| 2299 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2300 |
+
}
|
| 2301 |
+
},
|
| 2302 |
"node_modules/chalk": {
|
| 2303 |
"version": "4.1.2",
|
| 2304 |
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
|
|
|
| 2316 |
"url": "https://github.com/chalk/chalk?sponsor=1"
|
| 2317 |
}
|
| 2318 |
},
|
| 2319 |
+
"node_modules/character-entities": {
|
| 2320 |
+
"version": "2.0.2",
|
| 2321 |
+
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
|
| 2322 |
+
"integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
|
| 2323 |
+
"license": "MIT",
|
| 2324 |
+
"funding": {
|
| 2325 |
+
"type": "github",
|
| 2326 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2327 |
+
}
|
| 2328 |
+
},
|
| 2329 |
+
"node_modules/character-entities-html4": {
|
| 2330 |
+
"version": "2.1.0",
|
| 2331 |
+
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
|
| 2332 |
+
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
|
| 2333 |
+
"license": "MIT",
|
| 2334 |
+
"funding": {
|
| 2335 |
+
"type": "github",
|
| 2336 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2337 |
+
}
|
| 2338 |
+
},
|
| 2339 |
+
"node_modules/character-entities-legacy": {
|
| 2340 |
+
"version": "3.0.0",
|
| 2341 |
+
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
|
| 2342 |
+
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
|
| 2343 |
+
"license": "MIT",
|
| 2344 |
+
"funding": {
|
| 2345 |
+
"type": "github",
|
| 2346 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2347 |
+
}
|
| 2348 |
+
},
|
| 2349 |
+
"node_modules/character-reference-invalid": {
|
| 2350 |
+
"version": "2.0.1",
|
| 2351 |
+
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
|
| 2352 |
+
"integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
|
| 2353 |
+
"license": "MIT",
|
| 2354 |
+
"funding": {
|
| 2355 |
+
"type": "github",
|
| 2356 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2357 |
+
}
|
| 2358 |
+
},
|
| 2359 |
"node_modules/color-convert": {
|
| 2360 |
"version": "2.0.1",
|
| 2361 |
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
|
|
|
| 2388 |
"node": ">= 0.8"
|
| 2389 |
}
|
| 2390 |
},
|
| 2391 |
+
"node_modules/comma-separated-tokens": {
|
| 2392 |
+
"version": "2.0.3",
|
| 2393 |
+
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
| 2394 |
+
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
|
| 2395 |
+
"license": "MIT",
|
| 2396 |
+
"funding": {
|
| 2397 |
+
"type": "github",
|
| 2398 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2399 |
+
}
|
| 2400 |
+
},
|
| 2401 |
"node_modules/concat-map": {
|
| 2402 |
"version": "0.0.1",
|
| 2403 |
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
|
|
| 2454 |
"version": "4.4.3",
|
| 2455 |
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 2456 |
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
|
|
|
| 2457 |
"license": "MIT",
|
| 2458 |
"dependencies": {
|
| 2459 |
"ms": "^2.1.3"
|
|
|
|
| 2467 |
}
|
| 2468 |
}
|
| 2469 |
},
|
| 2470 |
+
"node_modules/decode-named-character-reference": {
|
| 2471 |
+
"version": "1.3.0",
|
| 2472 |
+
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
|
| 2473 |
+
"integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
|
| 2474 |
+
"license": "MIT",
|
| 2475 |
+
"dependencies": {
|
| 2476 |
+
"character-entities": "^2.0.0"
|
| 2477 |
+
},
|
| 2478 |
+
"funding": {
|
| 2479 |
+
"type": "github",
|
| 2480 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2481 |
+
}
|
| 2482 |
+
},
|
| 2483 |
"node_modules/deep-is": {
|
| 2484 |
"version": "0.1.4",
|
| 2485 |
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
|
|
|
| 2496 |
"node": ">=0.4.0"
|
| 2497 |
}
|
| 2498 |
},
|
| 2499 |
+
"node_modules/dequal": {
|
| 2500 |
+
"version": "2.0.3",
|
| 2501 |
+
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
| 2502 |
+
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
| 2503 |
+
"license": "MIT",
|
| 2504 |
+
"engines": {
|
| 2505 |
+
"node": ">=6"
|
| 2506 |
+
}
|
| 2507 |
+
},
|
| 2508 |
"node_modules/detect-gpu": {
|
| 2509 |
"version": "5.0.70",
|
| 2510 |
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
|
|
|
| 2514 |
"webgl-constants": "^1.1.1"
|
| 2515 |
}
|
| 2516 |
},
|
| 2517 |
+
"node_modules/devlop": {
|
| 2518 |
+
"version": "1.1.0",
|
| 2519 |
+
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
| 2520 |
+
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
|
| 2521 |
+
"license": "MIT",
|
| 2522 |
+
"dependencies": {
|
| 2523 |
+
"dequal": "^2.0.0"
|
| 2524 |
+
},
|
| 2525 |
+
"funding": {
|
| 2526 |
+
"type": "github",
|
| 2527 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 2528 |
+
}
|
| 2529 |
+
},
|
| 2530 |
"node_modules/draco3d": {
|
| 2531 |
"version": "1.5.7",
|
| 2532 |
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
|
|
|
| 2838 |
"node": ">=4.0"
|
| 2839 |
}
|
| 2840 |
},
|
| 2841 |
+
"node_modules/estree-util-is-identifier-name": {
|
| 2842 |
+
"version": "3.0.0",
|
| 2843 |
+
"resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
|
| 2844 |
+
"integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
|
| 2845 |
+
"license": "MIT",
|
| 2846 |
+
"funding": {
|
| 2847 |
+
"type": "opencollective",
|
| 2848 |
+
"url": "https://opencollective.com/unified"
|
| 2849 |
+
}
|
| 2850 |
+
},
|
| 2851 |
"node_modules/esutils": {
|
| 2852 |
"version": "2.0.3",
|
| 2853 |
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
|
|
|
| 2858 |
"node": ">=0.10.0"
|
| 2859 |
}
|
| 2860 |
},
|
| 2861 |
+
"node_modules/extend": {
|
| 2862 |
+
"version": "3.0.2",
|
| 2863 |
+
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
| 2864 |
+
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
| 2865 |
+
"license": "MIT"
|
| 2866 |
+
},
|
| 2867 |
"node_modules/fast-deep-equal": {
|
| 2868 |
"version": "3.1.3",
|
| 2869 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
|
|
| 3187 |
"node": ">= 0.4"
|
| 3188 |
}
|
| 3189 |
},
|
| 3190 |
+
"node_modules/hast-util-to-jsx-runtime": {
|
| 3191 |
+
"version": "2.3.6",
|
| 3192 |
+
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
| 3193 |
+
"integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
|
| 3194 |
+
"license": "MIT",
|
| 3195 |
+
"dependencies": {
|
| 3196 |
+
"@types/estree": "^1.0.0",
|
| 3197 |
+
"@types/hast": "^3.0.0",
|
| 3198 |
+
"@types/unist": "^3.0.0",
|
| 3199 |
+
"comma-separated-tokens": "^2.0.0",
|
| 3200 |
+
"devlop": "^1.0.0",
|
| 3201 |
+
"estree-util-is-identifier-name": "^3.0.0",
|
| 3202 |
+
"hast-util-whitespace": "^3.0.0",
|
| 3203 |
+
"mdast-util-mdx-expression": "^2.0.0",
|
| 3204 |
+
"mdast-util-mdx-jsx": "^3.0.0",
|
| 3205 |
+
"mdast-util-mdxjs-esm": "^2.0.0",
|
| 3206 |
+
"property-information": "^7.0.0",
|
| 3207 |
+
"space-separated-tokens": "^2.0.0",
|
| 3208 |
+
"style-to-js": "^1.0.0",
|
| 3209 |
+
"unist-util-position": "^5.0.0",
|
| 3210 |
+
"vfile-message": "^4.0.0"
|
| 3211 |
+
},
|
| 3212 |
+
"funding": {
|
| 3213 |
+
"type": "opencollective",
|
| 3214 |
+
"url": "https://opencollective.com/unified"
|
| 3215 |
+
}
|
| 3216 |
+
},
|
| 3217 |
+
"node_modules/hast-util-whitespace": {
|
| 3218 |
+
"version": "3.0.0",
|
| 3219 |
+
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
| 3220 |
+
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
|
| 3221 |
+
"license": "MIT",
|
| 3222 |
+
"dependencies": {
|
| 3223 |
+
"@types/hast": "^3.0.0"
|
| 3224 |
+
},
|
| 3225 |
+
"funding": {
|
| 3226 |
+
"type": "opencollective",
|
| 3227 |
+
"url": "https://opencollective.com/unified"
|
| 3228 |
+
}
|
| 3229 |
+
},
|
| 3230 |
"node_modules/hermes-estree": {
|
| 3231 |
"version": "0.25.1",
|
| 3232 |
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
|
|
|
| 3250 |
"integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==",
|
| 3251 |
"license": "Apache-2.0"
|
| 3252 |
},
|
| 3253 |
+
"node_modules/html-url-attributes": {
|
| 3254 |
+
"version": "3.0.1",
|
| 3255 |
+
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
| 3256 |
+
"integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
|
| 3257 |
+
"license": "MIT",
|
| 3258 |
+
"funding": {
|
| 3259 |
+
"type": "opencollective",
|
| 3260 |
+
"url": "https://opencollective.com/unified"
|
| 3261 |
+
}
|
| 3262 |
+
},
|
| 3263 |
"node_modules/ieee754": {
|
| 3264 |
"version": "1.2.1",
|
| 3265 |
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
|
|
|
| 3323 |
"node": ">=0.8.19"
|
| 3324 |
}
|
| 3325 |
},
|
| 3326 |
+
"node_modules/inline-style-parser": {
|
| 3327 |
+
"version": "0.2.7",
|
| 3328 |
+
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
|
| 3329 |
+
"integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
|
| 3330 |
+
"license": "MIT"
|
| 3331 |
+
},
|
| 3332 |
+
"node_modules/is-alphabetical": {
|
| 3333 |
+
"version": "2.0.1",
|
| 3334 |
+
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
|
| 3335 |
+
"integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
|
| 3336 |
+
"license": "MIT",
|
| 3337 |
+
"funding": {
|
| 3338 |
+
"type": "github",
|
| 3339 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3340 |
+
}
|
| 3341 |
+
},
|
| 3342 |
+
"node_modules/is-alphanumerical": {
|
| 3343 |
+
"version": "2.0.1",
|
| 3344 |
+
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
|
| 3345 |
+
"integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
|
| 3346 |
+
"license": "MIT",
|
| 3347 |
+
"dependencies": {
|
| 3348 |
+
"is-alphabetical": "^2.0.0",
|
| 3349 |
+
"is-decimal": "^2.0.0"
|
| 3350 |
+
},
|
| 3351 |
+
"funding": {
|
| 3352 |
+
"type": "github",
|
| 3353 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3354 |
+
}
|
| 3355 |
+
},
|
| 3356 |
+
"node_modules/is-decimal": {
|
| 3357 |
+
"version": "2.0.1",
|
| 3358 |
+
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
|
| 3359 |
+
"integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
|
| 3360 |
+
"license": "MIT",
|
| 3361 |
+
"funding": {
|
| 3362 |
+
"type": "github",
|
| 3363 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3364 |
+
}
|
| 3365 |
+
},
|
| 3366 |
"node_modules/is-extglob": {
|
| 3367 |
"version": "2.1.1",
|
| 3368 |
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
|
|
|
| 3386 |
"node": ">=0.10.0"
|
| 3387 |
}
|
| 3388 |
},
|
| 3389 |
+
"node_modules/is-hexadecimal": {
|
| 3390 |
+
"version": "2.0.1",
|
| 3391 |
+
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
|
| 3392 |
+
"integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
|
| 3393 |
+
"license": "MIT",
|
| 3394 |
+
"funding": {
|
| 3395 |
+
"type": "github",
|
| 3396 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3397 |
+
}
|
| 3398 |
+
},
|
| 3399 |
+
"node_modules/is-plain-obj": {
|
| 3400 |
+
"version": "4.1.0",
|
| 3401 |
+
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
|
| 3402 |
+
"integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
|
| 3403 |
+
"license": "MIT",
|
| 3404 |
+
"engines": {
|
| 3405 |
+
"node": ">=12"
|
| 3406 |
+
},
|
| 3407 |
+
"funding": {
|
| 3408 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 3409 |
+
}
|
| 3410 |
+
},
|
| 3411 |
"node_modules/is-promise": {
|
| 3412 |
"version": "2.2.2",
|
| 3413 |
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
|
|
|
| 3555 |
"dev": true,
|
| 3556 |
"license": "MIT"
|
| 3557 |
},
|
| 3558 |
+
"node_modules/longest-streak": {
|
| 3559 |
+
"version": "3.1.0",
|
| 3560 |
+
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
|
| 3561 |
+
"integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
|
| 3562 |
+
"license": "MIT",
|
| 3563 |
+
"funding": {
|
| 3564 |
+
"type": "github",
|
| 3565 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3566 |
+
}
|
| 3567 |
+
},
|
| 3568 |
"node_modules/lru-cache": {
|
| 3569 |
"version": "5.1.1",
|
| 3570 |
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
|
|
|
| 3594 |
"three": ">=0.134.0"
|
| 3595 |
}
|
| 3596 |
},
|
| 3597 |
+
"node_modules/markdown-table": {
|
| 3598 |
+
"version": "3.0.4",
|
| 3599 |
+
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
| 3600 |
+
"integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
|
| 3601 |
+
"license": "MIT",
|
| 3602 |
+
"funding": {
|
| 3603 |
+
"type": "github",
|
| 3604 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 3605 |
+
}
|
| 3606 |
+
},
|
| 3607 |
"node_modules/math-intrinsics": {
|
| 3608 |
"version": "1.1.0",
|
| 3609 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
|
|
|
| 3613 |
"node": ">= 0.4"
|
| 3614 |
}
|
| 3615 |
},
|
| 3616 |
+
"node_modules/mdast-util-find-and-replace": {
|
| 3617 |
+
"version": "3.0.2",
|
| 3618 |
+
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
|
| 3619 |
+
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
|
| 3620 |
"license": "MIT",
|
| 3621 |
+
"dependencies": {
|
| 3622 |
+
"@types/mdast": "^4.0.0",
|
| 3623 |
+
"escape-string-regexp": "^5.0.0",
|
| 3624 |
+
"unist-util-is": "^6.0.0",
|
| 3625 |
+
"unist-util-visit-parents": "^6.0.0"
|
| 3626 |
+
},
|
| 3627 |
+
"funding": {
|
| 3628 |
+
"type": "opencollective",
|
| 3629 |
+
"url": "https://opencollective.com/unified"
|
| 3630 |
}
|
| 3631 |
},
|
| 3632 |
+
"node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
|
| 3633 |
+
"version": "5.0.0",
|
| 3634 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
| 3635 |
+
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3636 |
"license": "MIT",
|
| 3637 |
"engines": {
|
| 3638 |
+
"node": ">=12"
|
| 3639 |
+
},
|
| 3640 |
+
"funding": {
|
| 3641 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 3642 |
}
|
| 3643 |
},
|
| 3644 |
+
"node_modules/mdast-util-from-markdown": {
|
| 3645 |
+
"version": "2.0.3",
|
| 3646 |
+
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
|
| 3647 |
+
"integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
|
| 3648 |
"license": "MIT",
|
| 3649 |
"dependencies": {
|
| 3650 |
+
"@types/mdast": "^4.0.0",
|
| 3651 |
+
"@types/unist": "^3.0.0",
|
| 3652 |
+
"decode-named-character-reference": "^1.0.0",
|
| 3653 |
+
"devlop": "^1.0.0",
|
| 3654 |
+
"mdast-util-to-string": "^4.0.0",
|
| 3655 |
+
"micromark": "^4.0.0",
|
| 3656 |
+
"micromark-util-decode-numeric-character-reference": "^2.0.0",
|
| 3657 |
+
"micromark-util-decode-string": "^2.0.0",
|
| 3658 |
+
"micromark-util-normalize-identifier": "^2.0.0",
|
| 3659 |
+
"micromark-util-symbol": "^2.0.0",
|
| 3660 |
+
"micromark-util-types": "^2.0.0",
|
| 3661 |
+
"unist-util-stringify-position": "^4.0.0"
|
| 3662 |
},
|
| 3663 |
+
"funding": {
|
| 3664 |
+
"type": "opencollective",
|
| 3665 |
+
"url": "https://opencollective.com/unified"
|
| 3666 |
}
|
| 3667 |
},
|
| 3668 |
+
"node_modules/mdast-util-gfm": {
|
| 3669 |
+
"version": "3.1.0",
|
| 3670 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
|
| 3671 |
+
"integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
|
| 3672 |
+
"license": "MIT",
|
|
|
|
| 3673 |
"dependencies": {
|
| 3674 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3675 |
+
"mdast-util-gfm-autolink-literal": "^2.0.0",
|
| 3676 |
+
"mdast-util-gfm-footnote": "^2.0.0",
|
| 3677 |
+
"mdast-util-gfm-strikethrough": "^2.0.0",
|
| 3678 |
+
"mdast-util-gfm-table": "^2.0.0",
|
| 3679 |
+
"mdast-util-gfm-task-list-item": "^2.0.0",
|
| 3680 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3681 |
},
|
| 3682 |
+
"funding": {
|
| 3683 |
+
"type": "opencollective",
|
| 3684 |
+
"url": "https://opencollective.com/unified"
|
| 3685 |
}
|
| 3686 |
},
|
| 3687 |
+
"node_modules/mdast-util-gfm-autolink-literal": {
|
| 3688 |
+
"version": "2.0.1",
|
| 3689 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
|
| 3690 |
+
"integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
|
| 3691 |
"license": "MIT",
|
| 3692 |
"dependencies": {
|
| 3693 |
+
"@types/mdast": "^4.0.0",
|
| 3694 |
+
"ccount": "^2.0.0",
|
| 3695 |
+
"devlop": "^1.0.0",
|
| 3696 |
+
"mdast-util-find-and-replace": "^3.0.0",
|
| 3697 |
+
"micromark-util-character": "^2.0.0"
|
| 3698 |
+
},
|
| 3699 |
+
"funding": {
|
| 3700 |
+
"type": "opencollective",
|
| 3701 |
+
"url": "https://opencollective.com/unified"
|
| 3702 |
}
|
| 3703 |
},
|
| 3704 |
+
"node_modules/mdast-util-gfm-footnote": {
|
| 3705 |
+
"version": "2.1.0",
|
| 3706 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
|
| 3707 |
+
"integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3708 |
"license": "MIT",
|
| 3709 |
+
"dependencies": {
|
| 3710 |
+
"@types/mdast": "^4.0.0",
|
| 3711 |
+
"devlop": "^1.1.0",
|
| 3712 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3713 |
+
"mdast-util-to-markdown": "^2.0.0",
|
| 3714 |
+
"micromark-util-normalize-identifier": "^2.0.0"
|
| 3715 |
},
|
| 3716 |
+
"funding": {
|
| 3717 |
+
"type": "opencollective",
|
| 3718 |
+
"url": "https://opencollective.com/unified"
|
| 3719 |
+
}
|
| 3720 |
+
},
|
| 3721 |
+
"node_modules/mdast-util-gfm-strikethrough": {
|
| 3722 |
+
"version": "2.0.0",
|
| 3723 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
|
| 3724 |
+
"integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
|
| 3725 |
+
"license": "MIT",
|
| 3726 |
+
"dependencies": {
|
| 3727 |
+
"@types/mdast": "^4.0.0",
|
| 3728 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3729 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3730 |
+
},
|
| 3731 |
+
"funding": {
|
| 3732 |
+
"type": "opencollective",
|
| 3733 |
+
"url": "https://opencollective.com/unified"
|
| 3734 |
+
}
|
| 3735 |
+
},
|
| 3736 |
+
"node_modules/mdast-util-gfm-table": {
|
| 3737 |
+
"version": "2.0.0",
|
| 3738 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
|
| 3739 |
+
"integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
|
| 3740 |
+
"license": "MIT",
|
| 3741 |
+
"dependencies": {
|
| 3742 |
+
"@types/mdast": "^4.0.0",
|
| 3743 |
+
"devlop": "^1.0.0",
|
| 3744 |
+
"markdown-table": "^3.0.0",
|
| 3745 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3746 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3747 |
+
},
|
| 3748 |
+
"funding": {
|
| 3749 |
+
"type": "opencollective",
|
| 3750 |
+
"url": "https://opencollective.com/unified"
|
| 3751 |
+
}
|
| 3752 |
+
},
|
| 3753 |
+
"node_modules/mdast-util-gfm-task-list-item": {
|
| 3754 |
+
"version": "2.0.0",
|
| 3755 |
+
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
|
| 3756 |
+
"integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
|
| 3757 |
+
"license": "MIT",
|
| 3758 |
+
"dependencies": {
|
| 3759 |
+
"@types/mdast": "^4.0.0",
|
| 3760 |
+
"devlop": "^1.0.0",
|
| 3761 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3762 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3763 |
+
},
|
| 3764 |
+
"funding": {
|
| 3765 |
+
"type": "opencollective",
|
| 3766 |
+
"url": "https://opencollective.com/unified"
|
| 3767 |
+
}
|
| 3768 |
+
},
|
| 3769 |
+
"node_modules/mdast-util-mdx-expression": {
|
| 3770 |
+
"version": "2.0.1",
|
| 3771 |
+
"resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
|
| 3772 |
+
"integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
|
| 3773 |
+
"license": "MIT",
|
| 3774 |
+
"dependencies": {
|
| 3775 |
+
"@types/estree-jsx": "^1.0.0",
|
| 3776 |
+
"@types/hast": "^3.0.0",
|
| 3777 |
+
"@types/mdast": "^4.0.0",
|
| 3778 |
+
"devlop": "^1.0.0",
|
| 3779 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3780 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3781 |
+
},
|
| 3782 |
+
"funding": {
|
| 3783 |
+
"type": "opencollective",
|
| 3784 |
+
"url": "https://opencollective.com/unified"
|
| 3785 |
+
}
|
| 3786 |
+
},
|
| 3787 |
+
"node_modules/mdast-util-mdx-jsx": {
|
| 3788 |
+
"version": "3.2.0",
|
| 3789 |
+
"resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
|
| 3790 |
+
"integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
|
| 3791 |
+
"license": "MIT",
|
| 3792 |
+
"dependencies": {
|
| 3793 |
+
"@types/estree-jsx": "^1.0.0",
|
| 3794 |
+
"@types/hast": "^3.0.0",
|
| 3795 |
+
"@types/mdast": "^4.0.0",
|
| 3796 |
+
"@types/unist": "^3.0.0",
|
| 3797 |
+
"ccount": "^2.0.0",
|
| 3798 |
+
"devlop": "^1.1.0",
|
| 3799 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3800 |
+
"mdast-util-to-markdown": "^2.0.0",
|
| 3801 |
+
"parse-entities": "^4.0.0",
|
| 3802 |
+
"stringify-entities": "^4.0.0",
|
| 3803 |
+
"unist-util-stringify-position": "^4.0.0",
|
| 3804 |
+
"vfile-message": "^4.0.0"
|
| 3805 |
+
},
|
| 3806 |
+
"funding": {
|
| 3807 |
+
"type": "opencollective",
|
| 3808 |
+
"url": "https://opencollective.com/unified"
|
| 3809 |
+
}
|
| 3810 |
+
},
|
| 3811 |
+
"node_modules/mdast-util-mdxjs-esm": {
|
| 3812 |
+
"version": "2.0.1",
|
| 3813 |
+
"resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
|
| 3814 |
+
"integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
|
| 3815 |
+
"license": "MIT",
|
| 3816 |
+
"dependencies": {
|
| 3817 |
+
"@types/estree-jsx": "^1.0.0",
|
| 3818 |
+
"@types/hast": "^3.0.0",
|
| 3819 |
+
"@types/mdast": "^4.0.0",
|
| 3820 |
+
"devlop": "^1.0.0",
|
| 3821 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 3822 |
+
"mdast-util-to-markdown": "^2.0.0"
|
| 3823 |
+
},
|
| 3824 |
+
"funding": {
|
| 3825 |
+
"type": "opencollective",
|
| 3826 |
+
"url": "https://opencollective.com/unified"
|
| 3827 |
+
}
|
| 3828 |
+
},
|
| 3829 |
+
"node_modules/mdast-util-phrasing": {
|
| 3830 |
+
"version": "4.1.0",
|
| 3831 |
+
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
|
| 3832 |
+
"integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
|
| 3833 |
+
"license": "MIT",
|
| 3834 |
+
"dependencies": {
|
| 3835 |
+
"@types/mdast": "^4.0.0",
|
| 3836 |
+
"unist-util-is": "^6.0.0"
|
| 3837 |
+
},
|
| 3838 |
+
"funding": {
|
| 3839 |
+
"type": "opencollective",
|
| 3840 |
+
"url": "https://opencollective.com/unified"
|
| 3841 |
+
}
|
| 3842 |
+
},
|
| 3843 |
+
"node_modules/mdast-util-to-hast": {
|
| 3844 |
+
"version": "13.2.1",
|
| 3845 |
+
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
|
| 3846 |
+
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
|
| 3847 |
+
"license": "MIT",
|
| 3848 |
+
"dependencies": {
|
| 3849 |
+
"@types/hast": "^3.0.0",
|
| 3850 |
+
"@types/mdast": "^4.0.0",
|
| 3851 |
+
"@ungap/structured-clone": "^1.0.0",
|
| 3852 |
+
"devlop": "^1.0.0",
|
| 3853 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 3854 |
+
"trim-lines": "^3.0.0",
|
| 3855 |
+
"unist-util-position": "^5.0.0",
|
| 3856 |
+
"unist-util-visit": "^5.0.0",
|
| 3857 |
+
"vfile": "^6.0.0"
|
| 3858 |
+
},
|
| 3859 |
+
"funding": {
|
| 3860 |
+
"type": "opencollective",
|
| 3861 |
+
"url": "https://opencollective.com/unified"
|
| 3862 |
+
}
|
| 3863 |
+
},
|
| 3864 |
+
"node_modules/mdast-util-to-markdown": {
|
| 3865 |
+
"version": "2.1.2",
|
| 3866 |
+
"resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
|
| 3867 |
+
"integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
|
| 3868 |
+
"license": "MIT",
|
| 3869 |
+
"dependencies": {
|
| 3870 |
+
"@types/mdast": "^4.0.0",
|
| 3871 |
+
"@types/unist": "^3.0.0",
|
| 3872 |
+
"longest-streak": "^3.0.0",
|
| 3873 |
+
"mdast-util-phrasing": "^4.0.0",
|
| 3874 |
+
"mdast-util-to-string": "^4.0.0",
|
| 3875 |
+
"micromark-util-classify-character": "^2.0.0",
|
| 3876 |
+
"micromark-util-decode-string": "^2.0.0",
|
| 3877 |
+
"unist-util-visit": "^5.0.0",
|
| 3878 |
+
"zwitch": "^2.0.0"
|
| 3879 |
+
},
|
| 3880 |
+
"funding": {
|
| 3881 |
+
"type": "opencollective",
|
| 3882 |
+
"url": "https://opencollective.com/unified"
|
| 3883 |
+
}
|
| 3884 |
+
},
|
| 3885 |
+
"node_modules/mdast-util-to-string": {
|
| 3886 |
+
"version": "4.0.0",
|
| 3887 |
+
"resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
|
| 3888 |
+
"integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
|
| 3889 |
+
"license": "MIT",
|
| 3890 |
+
"dependencies": {
|
| 3891 |
+
"@types/mdast": "^4.0.0"
|
| 3892 |
+
},
|
| 3893 |
+
"funding": {
|
| 3894 |
+
"type": "opencollective",
|
| 3895 |
+
"url": "https://opencollective.com/unified"
|
| 3896 |
+
}
|
| 3897 |
+
},
|
| 3898 |
+
"node_modules/meshline": {
|
| 3899 |
+
"version": "3.3.1",
|
| 3900 |
+
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
|
| 3901 |
+
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
|
| 3902 |
+
"license": "MIT",
|
| 3903 |
+
"peerDependencies": {
|
| 3904 |
+
"three": ">=0.137"
|
| 3905 |
+
}
|
| 3906 |
+
},
|
| 3907 |
+
"node_modules/meshoptimizer": {
|
| 3908 |
+
"version": "1.0.1",
|
| 3909 |
+
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz",
|
| 3910 |
+
"integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==",
|
| 3911 |
+
"license": "MIT"
|
| 3912 |
+
},
|
| 3913 |
+
"node_modules/micromark": {
|
| 3914 |
+
"version": "4.0.2",
|
| 3915 |
+
"resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
|
| 3916 |
+
"integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
|
| 3917 |
+
"funding": [
|
| 3918 |
+
{
|
| 3919 |
+
"type": "GitHub Sponsors",
|
| 3920 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 3921 |
+
},
|
| 3922 |
+
{
|
| 3923 |
+
"type": "OpenCollective",
|
| 3924 |
+
"url": "https://opencollective.com/unified"
|
| 3925 |
+
}
|
| 3926 |
+
],
|
| 3927 |
+
"license": "MIT",
|
| 3928 |
+
"dependencies": {
|
| 3929 |
+
"@types/debug": "^4.0.0",
|
| 3930 |
+
"debug": "^4.0.0",
|
| 3931 |
+
"decode-named-character-reference": "^1.0.0",
|
| 3932 |
+
"devlop": "^1.0.0",
|
| 3933 |
+
"micromark-core-commonmark": "^2.0.0",
|
| 3934 |
+
"micromark-factory-space": "^2.0.0",
|
| 3935 |
+
"micromark-util-character": "^2.0.0",
|
| 3936 |
+
"micromark-util-chunked": "^2.0.0",
|
| 3937 |
+
"micromark-util-combine-extensions": "^2.0.0",
|
| 3938 |
+
"micromark-util-decode-numeric-character-reference": "^2.0.0",
|
| 3939 |
+
"micromark-util-encode": "^2.0.0",
|
| 3940 |
+
"micromark-util-normalize-identifier": "^2.0.0",
|
| 3941 |
+
"micromark-util-resolve-all": "^2.0.0",
|
| 3942 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 3943 |
+
"micromark-util-subtokenize": "^2.0.0",
|
| 3944 |
+
"micromark-util-symbol": "^2.0.0",
|
| 3945 |
+
"micromark-util-types": "^2.0.0"
|
| 3946 |
+
}
|
| 3947 |
+
},
|
| 3948 |
+
"node_modules/micromark-core-commonmark": {
|
| 3949 |
+
"version": "2.0.3",
|
| 3950 |
+
"resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
|
| 3951 |
+
"integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
|
| 3952 |
+
"funding": [
|
| 3953 |
+
{
|
| 3954 |
+
"type": "GitHub Sponsors",
|
| 3955 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 3956 |
+
},
|
| 3957 |
+
{
|
| 3958 |
+
"type": "OpenCollective",
|
| 3959 |
+
"url": "https://opencollective.com/unified"
|
| 3960 |
+
}
|
| 3961 |
+
],
|
| 3962 |
+
"license": "MIT",
|
| 3963 |
+
"dependencies": {
|
| 3964 |
+
"decode-named-character-reference": "^1.0.0",
|
| 3965 |
+
"devlop": "^1.0.0",
|
| 3966 |
+
"micromark-factory-destination": "^2.0.0",
|
| 3967 |
+
"micromark-factory-label": "^2.0.0",
|
| 3968 |
+
"micromark-factory-space": "^2.0.0",
|
| 3969 |
+
"micromark-factory-title": "^2.0.0",
|
| 3970 |
+
"micromark-factory-whitespace": "^2.0.0",
|
| 3971 |
+
"micromark-util-character": "^2.0.0",
|
| 3972 |
+
"micromark-util-chunked": "^2.0.0",
|
| 3973 |
+
"micromark-util-classify-character": "^2.0.0",
|
| 3974 |
+
"micromark-util-html-tag-name": "^2.0.0",
|
| 3975 |
+
"micromark-util-normalize-identifier": "^2.0.0",
|
| 3976 |
+
"micromark-util-resolve-all": "^2.0.0",
|
| 3977 |
+
"micromark-util-subtokenize": "^2.0.0",
|
| 3978 |
+
"micromark-util-symbol": "^2.0.0",
|
| 3979 |
+
"micromark-util-types": "^2.0.0"
|
| 3980 |
+
}
|
| 3981 |
+
},
|
| 3982 |
+
"node_modules/micromark-extension-gfm": {
|
| 3983 |
+
"version": "3.0.0",
|
| 3984 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
|
| 3985 |
+
"integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
|
| 3986 |
+
"license": "MIT",
|
| 3987 |
+
"dependencies": {
|
| 3988 |
+
"micromark-extension-gfm-autolink-literal": "^2.0.0",
|
| 3989 |
+
"micromark-extension-gfm-footnote": "^2.0.0",
|
| 3990 |
+
"micromark-extension-gfm-strikethrough": "^2.0.0",
|
| 3991 |
+
"micromark-extension-gfm-table": "^2.0.0",
|
| 3992 |
+
"micromark-extension-gfm-tagfilter": "^2.0.0",
|
| 3993 |
+
"micromark-extension-gfm-task-list-item": "^2.0.0",
|
| 3994 |
+
"micromark-util-combine-extensions": "^2.0.0",
|
| 3995 |
+
"micromark-util-types": "^2.0.0"
|
| 3996 |
+
},
|
| 3997 |
+
"funding": {
|
| 3998 |
+
"type": "opencollective",
|
| 3999 |
+
"url": "https://opencollective.com/unified"
|
| 4000 |
+
}
|
| 4001 |
+
},
|
| 4002 |
+
"node_modules/micromark-extension-gfm-autolink-literal": {
|
| 4003 |
+
"version": "2.1.0",
|
| 4004 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
|
| 4005 |
+
"integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
|
| 4006 |
+
"license": "MIT",
|
| 4007 |
+
"dependencies": {
|
| 4008 |
+
"micromark-util-character": "^2.0.0",
|
| 4009 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 4010 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4011 |
+
"micromark-util-types": "^2.0.0"
|
| 4012 |
+
},
|
| 4013 |
+
"funding": {
|
| 4014 |
+
"type": "opencollective",
|
| 4015 |
+
"url": "https://opencollective.com/unified"
|
| 4016 |
+
}
|
| 4017 |
+
},
|
| 4018 |
+
"node_modules/micromark-extension-gfm-footnote": {
|
| 4019 |
+
"version": "2.1.0",
|
| 4020 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
|
| 4021 |
+
"integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
|
| 4022 |
+
"license": "MIT",
|
| 4023 |
+
"dependencies": {
|
| 4024 |
+
"devlop": "^1.0.0",
|
| 4025 |
+
"micromark-core-commonmark": "^2.0.0",
|
| 4026 |
+
"micromark-factory-space": "^2.0.0",
|
| 4027 |
+
"micromark-util-character": "^2.0.0",
|
| 4028 |
+
"micromark-util-normalize-identifier": "^2.0.0",
|
| 4029 |
+
"micromark-util-sanitize-uri": "^2.0.0",
|
| 4030 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4031 |
+
"micromark-util-types": "^2.0.0"
|
| 4032 |
+
},
|
| 4033 |
+
"funding": {
|
| 4034 |
+
"type": "opencollective",
|
| 4035 |
+
"url": "https://opencollective.com/unified"
|
| 4036 |
+
}
|
| 4037 |
+
},
|
| 4038 |
+
"node_modules/micromark-extension-gfm-strikethrough": {
|
| 4039 |
+
"version": "2.1.0",
|
| 4040 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
|
| 4041 |
+
"integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
|
| 4042 |
+
"license": "MIT",
|
| 4043 |
+
"dependencies": {
|
| 4044 |
+
"devlop": "^1.0.0",
|
| 4045 |
+
"micromark-util-chunked": "^2.0.0",
|
| 4046 |
+
"micromark-util-classify-character": "^2.0.0",
|
| 4047 |
+
"micromark-util-resolve-all": "^2.0.0",
|
| 4048 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4049 |
+
"micromark-util-types": "^2.0.0"
|
| 4050 |
+
},
|
| 4051 |
+
"funding": {
|
| 4052 |
+
"type": "opencollective",
|
| 4053 |
+
"url": "https://opencollective.com/unified"
|
| 4054 |
+
}
|
| 4055 |
+
},
|
| 4056 |
+
"node_modules/micromark-extension-gfm-table": {
|
| 4057 |
+
"version": "2.1.1",
|
| 4058 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
|
| 4059 |
+
"integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
|
| 4060 |
+
"license": "MIT",
|
| 4061 |
+
"dependencies": {
|
| 4062 |
+
"devlop": "^1.0.0",
|
| 4063 |
+
"micromark-factory-space": "^2.0.0",
|
| 4064 |
+
"micromark-util-character": "^2.0.0",
|
| 4065 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4066 |
+
"micromark-util-types": "^2.0.0"
|
| 4067 |
+
},
|
| 4068 |
+
"funding": {
|
| 4069 |
+
"type": "opencollective",
|
| 4070 |
+
"url": "https://opencollective.com/unified"
|
| 4071 |
+
}
|
| 4072 |
+
},
|
| 4073 |
+
"node_modules/micromark-extension-gfm-tagfilter": {
|
| 4074 |
+
"version": "2.0.0",
|
| 4075 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
|
| 4076 |
+
"integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
|
| 4077 |
+
"license": "MIT",
|
| 4078 |
+
"dependencies": {
|
| 4079 |
+
"micromark-util-types": "^2.0.0"
|
| 4080 |
+
},
|
| 4081 |
+
"funding": {
|
| 4082 |
+
"type": "opencollective",
|
| 4083 |
+
"url": "https://opencollective.com/unified"
|
| 4084 |
+
}
|
| 4085 |
+
},
|
| 4086 |
+
"node_modules/micromark-extension-gfm-task-list-item": {
|
| 4087 |
+
"version": "2.1.0",
|
| 4088 |
+
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
|
| 4089 |
+
"integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
|
| 4090 |
+
"license": "MIT",
|
| 4091 |
+
"dependencies": {
|
| 4092 |
+
"devlop": "^1.0.0",
|
| 4093 |
+
"micromark-factory-space": "^2.0.0",
|
| 4094 |
+
"micromark-util-character": "^2.0.0",
|
| 4095 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4096 |
+
"micromark-util-types": "^2.0.0"
|
| 4097 |
+
},
|
| 4098 |
+
"funding": {
|
| 4099 |
+
"type": "opencollective",
|
| 4100 |
+
"url": "https://opencollective.com/unified"
|
| 4101 |
+
}
|
| 4102 |
+
},
|
| 4103 |
+
"node_modules/micromark-factory-destination": {
|
| 4104 |
+
"version": "2.0.1",
|
| 4105 |
+
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
|
| 4106 |
+
"integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
|
| 4107 |
+
"funding": [
|
| 4108 |
+
{
|
| 4109 |
+
"type": "GitHub Sponsors",
|
| 4110 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4111 |
+
},
|
| 4112 |
+
{
|
| 4113 |
+
"type": "OpenCollective",
|
| 4114 |
+
"url": "https://opencollective.com/unified"
|
| 4115 |
+
}
|
| 4116 |
+
],
|
| 4117 |
+
"license": "MIT",
|
| 4118 |
+
"dependencies": {
|
| 4119 |
+
"micromark-util-character": "^2.0.0",
|
| 4120 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4121 |
+
"micromark-util-types": "^2.0.0"
|
| 4122 |
+
}
|
| 4123 |
+
},
|
| 4124 |
+
"node_modules/micromark-factory-label": {
|
| 4125 |
+
"version": "2.0.1",
|
| 4126 |
+
"resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
|
| 4127 |
+
"integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
|
| 4128 |
+
"funding": [
|
| 4129 |
+
{
|
| 4130 |
+
"type": "GitHub Sponsors",
|
| 4131 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4132 |
+
},
|
| 4133 |
+
{
|
| 4134 |
+
"type": "OpenCollective",
|
| 4135 |
+
"url": "https://opencollective.com/unified"
|
| 4136 |
+
}
|
| 4137 |
+
],
|
| 4138 |
+
"license": "MIT",
|
| 4139 |
+
"dependencies": {
|
| 4140 |
+
"devlop": "^1.0.0",
|
| 4141 |
+
"micromark-util-character": "^2.0.0",
|
| 4142 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4143 |
+
"micromark-util-types": "^2.0.0"
|
| 4144 |
+
}
|
| 4145 |
+
},
|
| 4146 |
+
"node_modules/micromark-factory-space": {
|
| 4147 |
+
"version": "2.0.1",
|
| 4148 |
+
"resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
|
| 4149 |
+
"integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
|
| 4150 |
+
"funding": [
|
| 4151 |
+
{
|
| 4152 |
+
"type": "GitHub Sponsors",
|
| 4153 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4154 |
+
},
|
| 4155 |
+
{
|
| 4156 |
+
"type": "OpenCollective",
|
| 4157 |
+
"url": "https://opencollective.com/unified"
|
| 4158 |
+
}
|
| 4159 |
+
],
|
| 4160 |
+
"license": "MIT",
|
| 4161 |
+
"dependencies": {
|
| 4162 |
+
"micromark-util-character": "^2.0.0",
|
| 4163 |
+
"micromark-util-types": "^2.0.0"
|
| 4164 |
+
}
|
| 4165 |
+
},
|
| 4166 |
+
"node_modules/micromark-factory-title": {
|
| 4167 |
+
"version": "2.0.1",
|
| 4168 |
+
"resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
|
| 4169 |
+
"integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
|
| 4170 |
+
"funding": [
|
| 4171 |
+
{
|
| 4172 |
+
"type": "GitHub Sponsors",
|
| 4173 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4174 |
+
},
|
| 4175 |
+
{
|
| 4176 |
+
"type": "OpenCollective",
|
| 4177 |
+
"url": "https://opencollective.com/unified"
|
| 4178 |
+
}
|
| 4179 |
+
],
|
| 4180 |
+
"license": "MIT",
|
| 4181 |
+
"dependencies": {
|
| 4182 |
+
"micromark-factory-space": "^2.0.0",
|
| 4183 |
+
"micromark-util-character": "^2.0.0",
|
| 4184 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4185 |
+
"micromark-util-types": "^2.0.0"
|
| 4186 |
+
}
|
| 4187 |
+
},
|
| 4188 |
+
"node_modules/micromark-factory-whitespace": {
|
| 4189 |
+
"version": "2.0.1",
|
| 4190 |
+
"resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
|
| 4191 |
+
"integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
|
| 4192 |
+
"funding": [
|
| 4193 |
+
{
|
| 4194 |
+
"type": "GitHub Sponsors",
|
| 4195 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4196 |
+
},
|
| 4197 |
+
{
|
| 4198 |
+
"type": "OpenCollective",
|
| 4199 |
+
"url": "https://opencollective.com/unified"
|
| 4200 |
+
}
|
| 4201 |
+
],
|
| 4202 |
+
"license": "MIT",
|
| 4203 |
+
"dependencies": {
|
| 4204 |
+
"micromark-factory-space": "^2.0.0",
|
| 4205 |
+
"micromark-util-character": "^2.0.0",
|
| 4206 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4207 |
+
"micromark-util-types": "^2.0.0"
|
| 4208 |
+
}
|
| 4209 |
+
},
|
| 4210 |
+
"node_modules/micromark-util-character": {
|
| 4211 |
+
"version": "2.1.1",
|
| 4212 |
+
"resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
|
| 4213 |
+
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
|
| 4214 |
+
"funding": [
|
| 4215 |
+
{
|
| 4216 |
+
"type": "GitHub Sponsors",
|
| 4217 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4218 |
+
},
|
| 4219 |
+
{
|
| 4220 |
+
"type": "OpenCollective",
|
| 4221 |
+
"url": "https://opencollective.com/unified"
|
| 4222 |
+
}
|
| 4223 |
+
],
|
| 4224 |
+
"license": "MIT",
|
| 4225 |
+
"dependencies": {
|
| 4226 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4227 |
+
"micromark-util-types": "^2.0.0"
|
| 4228 |
+
}
|
| 4229 |
+
},
|
| 4230 |
+
"node_modules/micromark-util-chunked": {
|
| 4231 |
+
"version": "2.0.1",
|
| 4232 |
+
"resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
|
| 4233 |
+
"integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
|
| 4234 |
+
"funding": [
|
| 4235 |
+
{
|
| 4236 |
+
"type": "GitHub Sponsors",
|
| 4237 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4238 |
+
},
|
| 4239 |
+
{
|
| 4240 |
+
"type": "OpenCollective",
|
| 4241 |
+
"url": "https://opencollective.com/unified"
|
| 4242 |
+
}
|
| 4243 |
+
],
|
| 4244 |
+
"license": "MIT",
|
| 4245 |
+
"dependencies": {
|
| 4246 |
+
"micromark-util-symbol": "^2.0.0"
|
| 4247 |
+
}
|
| 4248 |
+
},
|
| 4249 |
+
"node_modules/micromark-util-classify-character": {
|
| 4250 |
+
"version": "2.0.1",
|
| 4251 |
+
"resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
|
| 4252 |
+
"integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
|
| 4253 |
+
"funding": [
|
| 4254 |
+
{
|
| 4255 |
+
"type": "GitHub Sponsors",
|
| 4256 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4257 |
+
},
|
| 4258 |
+
{
|
| 4259 |
+
"type": "OpenCollective",
|
| 4260 |
+
"url": "https://opencollective.com/unified"
|
| 4261 |
+
}
|
| 4262 |
+
],
|
| 4263 |
+
"license": "MIT",
|
| 4264 |
+
"dependencies": {
|
| 4265 |
+
"micromark-util-character": "^2.0.0",
|
| 4266 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4267 |
+
"micromark-util-types": "^2.0.0"
|
| 4268 |
+
}
|
| 4269 |
+
},
|
| 4270 |
+
"node_modules/micromark-util-combine-extensions": {
|
| 4271 |
+
"version": "2.0.1",
|
| 4272 |
+
"resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
|
| 4273 |
+
"integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
|
| 4274 |
+
"funding": [
|
| 4275 |
+
{
|
| 4276 |
+
"type": "GitHub Sponsors",
|
| 4277 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4278 |
+
},
|
| 4279 |
+
{
|
| 4280 |
+
"type": "OpenCollective",
|
| 4281 |
+
"url": "https://opencollective.com/unified"
|
| 4282 |
+
}
|
| 4283 |
+
],
|
| 4284 |
+
"license": "MIT",
|
| 4285 |
+
"dependencies": {
|
| 4286 |
+
"micromark-util-chunked": "^2.0.0",
|
| 4287 |
+
"micromark-util-types": "^2.0.0"
|
| 4288 |
+
}
|
| 4289 |
+
},
|
| 4290 |
+
"node_modules/micromark-util-decode-numeric-character-reference": {
|
| 4291 |
+
"version": "2.0.2",
|
| 4292 |
+
"resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
|
| 4293 |
+
"integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
|
| 4294 |
+
"funding": [
|
| 4295 |
+
{
|
| 4296 |
+
"type": "GitHub Sponsors",
|
| 4297 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4298 |
+
},
|
| 4299 |
+
{
|
| 4300 |
+
"type": "OpenCollective",
|
| 4301 |
+
"url": "https://opencollective.com/unified"
|
| 4302 |
+
}
|
| 4303 |
+
],
|
| 4304 |
+
"license": "MIT",
|
| 4305 |
+
"dependencies": {
|
| 4306 |
+
"micromark-util-symbol": "^2.0.0"
|
| 4307 |
+
}
|
| 4308 |
+
},
|
| 4309 |
+
"node_modules/micromark-util-decode-string": {
|
| 4310 |
+
"version": "2.0.1",
|
| 4311 |
+
"resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
|
| 4312 |
+
"integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
|
| 4313 |
+
"funding": [
|
| 4314 |
+
{
|
| 4315 |
+
"type": "GitHub Sponsors",
|
| 4316 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4317 |
+
},
|
| 4318 |
+
{
|
| 4319 |
+
"type": "OpenCollective",
|
| 4320 |
+
"url": "https://opencollective.com/unified"
|
| 4321 |
+
}
|
| 4322 |
+
],
|
| 4323 |
+
"license": "MIT",
|
| 4324 |
+
"dependencies": {
|
| 4325 |
+
"decode-named-character-reference": "^1.0.0",
|
| 4326 |
+
"micromark-util-character": "^2.0.0",
|
| 4327 |
+
"micromark-util-decode-numeric-character-reference": "^2.0.0",
|
| 4328 |
+
"micromark-util-symbol": "^2.0.0"
|
| 4329 |
+
}
|
| 4330 |
+
},
|
| 4331 |
+
"node_modules/micromark-util-encode": {
|
| 4332 |
+
"version": "2.0.1",
|
| 4333 |
+
"resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
|
| 4334 |
+
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
|
| 4335 |
+
"funding": [
|
| 4336 |
+
{
|
| 4337 |
+
"type": "GitHub Sponsors",
|
| 4338 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4339 |
+
},
|
| 4340 |
+
{
|
| 4341 |
+
"type": "OpenCollective",
|
| 4342 |
+
"url": "https://opencollective.com/unified"
|
| 4343 |
+
}
|
| 4344 |
+
],
|
| 4345 |
+
"license": "MIT"
|
| 4346 |
+
},
|
| 4347 |
+
"node_modules/micromark-util-html-tag-name": {
|
| 4348 |
+
"version": "2.0.1",
|
| 4349 |
+
"resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
|
| 4350 |
+
"integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
|
| 4351 |
+
"funding": [
|
| 4352 |
+
{
|
| 4353 |
+
"type": "GitHub Sponsors",
|
| 4354 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4355 |
+
},
|
| 4356 |
+
{
|
| 4357 |
+
"type": "OpenCollective",
|
| 4358 |
+
"url": "https://opencollective.com/unified"
|
| 4359 |
+
}
|
| 4360 |
+
],
|
| 4361 |
+
"license": "MIT"
|
| 4362 |
+
},
|
| 4363 |
+
"node_modules/micromark-util-normalize-identifier": {
|
| 4364 |
+
"version": "2.0.1",
|
| 4365 |
+
"resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
|
| 4366 |
+
"integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
|
| 4367 |
+
"funding": [
|
| 4368 |
+
{
|
| 4369 |
+
"type": "GitHub Sponsors",
|
| 4370 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4371 |
+
},
|
| 4372 |
+
{
|
| 4373 |
+
"type": "OpenCollective",
|
| 4374 |
+
"url": "https://opencollective.com/unified"
|
| 4375 |
+
}
|
| 4376 |
+
],
|
| 4377 |
+
"license": "MIT",
|
| 4378 |
+
"dependencies": {
|
| 4379 |
+
"micromark-util-symbol": "^2.0.0"
|
| 4380 |
+
}
|
| 4381 |
+
},
|
| 4382 |
+
"node_modules/micromark-util-resolve-all": {
|
| 4383 |
+
"version": "2.0.1",
|
| 4384 |
+
"resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
|
| 4385 |
+
"integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
|
| 4386 |
+
"funding": [
|
| 4387 |
+
{
|
| 4388 |
+
"type": "GitHub Sponsors",
|
| 4389 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4390 |
+
},
|
| 4391 |
+
{
|
| 4392 |
+
"type": "OpenCollective",
|
| 4393 |
+
"url": "https://opencollective.com/unified"
|
| 4394 |
+
}
|
| 4395 |
+
],
|
| 4396 |
+
"license": "MIT",
|
| 4397 |
+
"dependencies": {
|
| 4398 |
+
"micromark-util-types": "^2.0.0"
|
| 4399 |
+
}
|
| 4400 |
+
},
|
| 4401 |
+
"node_modules/micromark-util-sanitize-uri": {
|
| 4402 |
+
"version": "2.0.1",
|
| 4403 |
+
"resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
|
| 4404 |
+
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
|
| 4405 |
+
"funding": [
|
| 4406 |
+
{
|
| 4407 |
+
"type": "GitHub Sponsors",
|
| 4408 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4409 |
+
},
|
| 4410 |
+
{
|
| 4411 |
+
"type": "OpenCollective",
|
| 4412 |
+
"url": "https://opencollective.com/unified"
|
| 4413 |
+
}
|
| 4414 |
+
],
|
| 4415 |
+
"license": "MIT",
|
| 4416 |
+
"dependencies": {
|
| 4417 |
+
"micromark-util-character": "^2.0.0",
|
| 4418 |
+
"micromark-util-encode": "^2.0.0",
|
| 4419 |
+
"micromark-util-symbol": "^2.0.0"
|
| 4420 |
+
}
|
| 4421 |
+
},
|
| 4422 |
+
"node_modules/micromark-util-subtokenize": {
|
| 4423 |
+
"version": "2.1.0",
|
| 4424 |
+
"resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
|
| 4425 |
+
"integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
|
| 4426 |
+
"funding": [
|
| 4427 |
+
{
|
| 4428 |
+
"type": "GitHub Sponsors",
|
| 4429 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4430 |
+
},
|
| 4431 |
+
{
|
| 4432 |
+
"type": "OpenCollective",
|
| 4433 |
+
"url": "https://opencollective.com/unified"
|
| 4434 |
+
}
|
| 4435 |
+
],
|
| 4436 |
+
"license": "MIT",
|
| 4437 |
+
"dependencies": {
|
| 4438 |
+
"devlop": "^1.0.0",
|
| 4439 |
+
"micromark-util-chunked": "^2.0.0",
|
| 4440 |
+
"micromark-util-symbol": "^2.0.0",
|
| 4441 |
+
"micromark-util-types": "^2.0.0"
|
| 4442 |
+
}
|
| 4443 |
+
},
|
| 4444 |
+
"node_modules/micromark-util-symbol": {
|
| 4445 |
+
"version": "2.0.1",
|
| 4446 |
+
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
|
| 4447 |
+
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
|
| 4448 |
+
"funding": [
|
| 4449 |
+
{
|
| 4450 |
+
"type": "GitHub Sponsors",
|
| 4451 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4452 |
+
},
|
| 4453 |
+
{
|
| 4454 |
+
"type": "OpenCollective",
|
| 4455 |
+
"url": "https://opencollective.com/unified"
|
| 4456 |
+
}
|
| 4457 |
+
],
|
| 4458 |
+
"license": "MIT"
|
| 4459 |
+
},
|
| 4460 |
+
"node_modules/micromark-util-types": {
|
| 4461 |
+
"version": "2.0.2",
|
| 4462 |
+
"resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
|
| 4463 |
+
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
|
| 4464 |
+
"funding": [
|
| 4465 |
+
{
|
| 4466 |
+
"type": "GitHub Sponsors",
|
| 4467 |
+
"url": "https://github.com/sponsors/unifiedjs"
|
| 4468 |
+
},
|
| 4469 |
+
{
|
| 4470 |
+
"type": "OpenCollective",
|
| 4471 |
+
"url": "https://opencollective.com/unified"
|
| 4472 |
+
}
|
| 4473 |
+
],
|
| 4474 |
+
"license": "MIT"
|
| 4475 |
+
},
|
| 4476 |
+
"node_modules/mime-db": {
|
| 4477 |
+
"version": "1.52.0",
|
| 4478 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 4479 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 4480 |
+
"license": "MIT",
|
| 4481 |
+
"engines": {
|
| 4482 |
+
"node": ">= 0.6"
|
| 4483 |
+
}
|
| 4484 |
+
},
|
| 4485 |
+
"node_modules/mime-types": {
|
| 4486 |
+
"version": "2.1.35",
|
| 4487 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 4488 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 4489 |
+
"license": "MIT",
|
| 4490 |
+
"dependencies": {
|
| 4491 |
+
"mime-db": "1.52.0"
|
| 4492 |
+
},
|
| 4493 |
+
"engines": {
|
| 4494 |
+
"node": ">= 0.6"
|
| 4495 |
+
}
|
| 4496 |
+
},
|
| 4497 |
+
"node_modules/minimatch": {
|
| 4498 |
+
"version": "3.1.2",
|
| 4499 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
| 4500 |
+
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
| 4501 |
+
"dev": true,
|
| 4502 |
+
"license": "ISC",
|
| 4503 |
+
"dependencies": {
|
| 4504 |
+
"brace-expansion": "^1.1.7"
|
| 4505 |
+
},
|
| 4506 |
+
"engines": {
|
| 4507 |
+
"node": "*"
|
| 4508 |
+
}
|
| 4509 |
+
},
|
| 4510 |
+
"node_modules/motion-dom": {
|
| 4511 |
+
"version": "12.34.3",
|
| 4512 |
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.3.tgz",
|
| 4513 |
+
"integrity": "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==",
|
| 4514 |
+
"license": "MIT",
|
| 4515 |
+
"dependencies": {
|
| 4516 |
+
"motion-utils": "^12.29.2"
|
| 4517 |
+
}
|
| 4518 |
+
},
|
| 4519 |
+
"node_modules/motion-utils": {
|
| 4520 |
+
"version": "12.29.2",
|
| 4521 |
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
|
| 4522 |
+
"integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
|
| 4523 |
+
"license": "MIT"
|
| 4524 |
+
},
|
| 4525 |
+
"node_modules/ms": {
|
| 4526 |
+
"version": "2.1.3",
|
| 4527 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 4528 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 4529 |
+
"license": "MIT"
|
| 4530 |
+
},
|
| 4531 |
+
"node_modules/nanoid": {
|
| 4532 |
+
"version": "3.3.11",
|
| 4533 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 4534 |
+
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 4535 |
+
"dev": true,
|
| 4536 |
+
"funding": [
|
| 4537 |
+
{
|
| 4538 |
+
"type": "github",
|
| 4539 |
+
"url": "https://github.com/sponsors/ai"
|
| 4540 |
+
}
|
| 4541 |
+
],
|
| 4542 |
+
"license": "MIT",
|
| 4543 |
+
"bin": {
|
| 4544 |
+
"nanoid": "bin/nanoid.cjs"
|
| 4545 |
+
},
|
| 4546 |
+
"engines": {
|
| 4547 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 4548 |
}
|
| 4549 |
},
|
| 4550 |
"node_modules/natural-compare": {
|
|
|
|
| 4624 |
"node": ">=6"
|
| 4625 |
}
|
| 4626 |
},
|
| 4627 |
+
"node_modules/parse-entities": {
|
| 4628 |
+
"version": "4.0.2",
|
| 4629 |
+
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
|
| 4630 |
+
"integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
|
| 4631 |
+
"license": "MIT",
|
| 4632 |
+
"dependencies": {
|
| 4633 |
+
"@types/unist": "^2.0.0",
|
| 4634 |
+
"character-entities-legacy": "^3.0.0",
|
| 4635 |
+
"character-reference-invalid": "^2.0.0",
|
| 4636 |
+
"decode-named-character-reference": "^1.0.0",
|
| 4637 |
+
"is-alphanumerical": "^2.0.0",
|
| 4638 |
+
"is-decimal": "^2.0.0",
|
| 4639 |
+
"is-hexadecimal": "^2.0.0"
|
| 4640 |
+
},
|
| 4641 |
+
"funding": {
|
| 4642 |
+
"type": "github",
|
| 4643 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 4644 |
+
}
|
| 4645 |
+
},
|
| 4646 |
+
"node_modules/parse-entities/node_modules/@types/unist": {
|
| 4647 |
+
"version": "2.0.11",
|
| 4648 |
+
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
|
| 4649 |
+
"integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
|
| 4650 |
+
"license": "MIT"
|
| 4651 |
+
},
|
| 4652 |
"node_modules/path-exists": {
|
| 4653 |
"version": "4.0.0",
|
| 4654 |
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
|
|
| 4743 |
"lie": "^3.0.2"
|
| 4744 |
}
|
| 4745 |
},
|
| 4746 |
+
"node_modules/property-information": {
|
| 4747 |
+
"version": "7.1.0",
|
| 4748 |
+
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
|
| 4749 |
+
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
|
| 4750 |
+
"license": "MIT",
|
| 4751 |
+
"funding": {
|
| 4752 |
+
"type": "github",
|
| 4753 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 4754 |
+
}
|
| 4755 |
+
},
|
| 4756 |
"node_modules/proxy-from-env": {
|
| 4757 |
"version": "1.1.0",
|
| 4758 |
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
|
|
|
| 4790 |
"react": "^19.2.4"
|
| 4791 |
}
|
| 4792 |
},
|
| 4793 |
+
"node_modules/react-markdown": {
|
| 4794 |
+
"version": "10.1.0",
|
| 4795 |
+
"resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
|
| 4796 |
+
"integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
|
| 4797 |
+
"license": "MIT",
|
| 4798 |
+
"dependencies": {
|
| 4799 |
+
"@types/hast": "^3.0.0",
|
| 4800 |
+
"@types/mdast": "^4.0.0",
|
| 4801 |
+
"devlop": "^1.0.0",
|
| 4802 |
+
"hast-util-to-jsx-runtime": "^2.0.0",
|
| 4803 |
+
"html-url-attributes": "^3.0.0",
|
| 4804 |
+
"mdast-util-to-hast": "^13.0.0",
|
| 4805 |
+
"remark-parse": "^11.0.0",
|
| 4806 |
+
"remark-rehype": "^11.0.0",
|
| 4807 |
+
"unified": "^11.0.0",
|
| 4808 |
+
"unist-util-visit": "^5.0.0",
|
| 4809 |
+
"vfile": "^6.0.0"
|
| 4810 |
+
},
|
| 4811 |
+
"funding": {
|
| 4812 |
+
"type": "opencollective",
|
| 4813 |
+
"url": "https://opencollective.com/unified"
|
| 4814 |
+
},
|
| 4815 |
+
"peerDependencies": {
|
| 4816 |
+
"@types/react": ">=18",
|
| 4817 |
+
"react": ">=18"
|
| 4818 |
+
}
|
| 4819 |
+
},
|
| 4820 |
"node_modules/react-refresh": {
|
| 4821 |
"version": "0.18.0",
|
| 4822 |
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
|
|
|
|
| 4842 |
}
|
| 4843 |
}
|
| 4844 |
},
|
| 4845 |
+
"node_modules/remark-gfm": {
|
| 4846 |
+
"version": "4.0.1",
|
| 4847 |
+
"resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
| 4848 |
+
"integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
|
| 4849 |
+
"license": "MIT",
|
| 4850 |
+
"dependencies": {
|
| 4851 |
+
"@types/mdast": "^4.0.0",
|
| 4852 |
+
"mdast-util-gfm": "^3.0.0",
|
| 4853 |
+
"micromark-extension-gfm": "^3.0.0",
|
| 4854 |
+
"remark-parse": "^11.0.0",
|
| 4855 |
+
"remark-stringify": "^11.0.0",
|
| 4856 |
+
"unified": "^11.0.0"
|
| 4857 |
+
},
|
| 4858 |
+
"funding": {
|
| 4859 |
+
"type": "opencollective",
|
| 4860 |
+
"url": "https://opencollective.com/unified"
|
| 4861 |
+
}
|
| 4862 |
+
},
|
| 4863 |
+
"node_modules/remark-parse": {
|
| 4864 |
+
"version": "11.0.0",
|
| 4865 |
+
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
| 4866 |
+
"integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
|
| 4867 |
+
"license": "MIT",
|
| 4868 |
+
"dependencies": {
|
| 4869 |
+
"@types/mdast": "^4.0.0",
|
| 4870 |
+
"mdast-util-from-markdown": "^2.0.0",
|
| 4871 |
+
"micromark-util-types": "^2.0.0",
|
| 4872 |
+
"unified": "^11.0.0"
|
| 4873 |
+
},
|
| 4874 |
+
"funding": {
|
| 4875 |
+
"type": "opencollective",
|
| 4876 |
+
"url": "https://opencollective.com/unified"
|
| 4877 |
+
}
|
| 4878 |
+
},
|
| 4879 |
+
"node_modules/remark-rehype": {
|
| 4880 |
+
"version": "11.1.2",
|
| 4881 |
+
"resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
|
| 4882 |
+
"integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
|
| 4883 |
+
"license": "MIT",
|
| 4884 |
+
"dependencies": {
|
| 4885 |
+
"@types/hast": "^3.0.0",
|
| 4886 |
+
"@types/mdast": "^4.0.0",
|
| 4887 |
+
"mdast-util-to-hast": "^13.0.0",
|
| 4888 |
+
"unified": "^11.0.0",
|
| 4889 |
+
"vfile": "^6.0.0"
|
| 4890 |
+
},
|
| 4891 |
+
"funding": {
|
| 4892 |
+
"type": "opencollective",
|
| 4893 |
+
"url": "https://opencollective.com/unified"
|
| 4894 |
+
}
|
| 4895 |
+
},
|
| 4896 |
+
"node_modules/remark-stringify": {
|
| 4897 |
+
"version": "11.0.0",
|
| 4898 |
+
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
|
| 4899 |
+
"integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
|
| 4900 |
+
"license": "MIT",
|
| 4901 |
+
"dependencies": {
|
| 4902 |
+
"@types/mdast": "^4.0.0",
|
| 4903 |
+
"mdast-util-to-markdown": "^2.0.0",
|
| 4904 |
+
"unified": "^11.0.0"
|
| 4905 |
+
},
|
| 4906 |
+
"funding": {
|
| 4907 |
+
"type": "opencollective",
|
| 4908 |
+
"url": "https://opencollective.com/unified"
|
| 4909 |
+
}
|
| 4910 |
+
},
|
| 4911 |
"node_modules/require-from-string": {
|
| 4912 |
"version": "2.0.2",
|
| 4913 |
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
|
|
|
| 5019 |
"node": ">=0.10.0"
|
| 5020 |
}
|
| 5021 |
},
|
| 5022 |
+
"node_modules/space-separated-tokens": {
|
| 5023 |
+
"version": "2.0.2",
|
| 5024 |
+
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
|
| 5025 |
+
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
|
| 5026 |
+
"license": "MIT",
|
| 5027 |
+
"funding": {
|
| 5028 |
+
"type": "github",
|
| 5029 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 5030 |
+
}
|
| 5031 |
+
},
|
| 5032 |
"node_modules/stats-gl": {
|
| 5033 |
"version": "2.4.2",
|
| 5034 |
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
|
|
|
| 5055 |
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
| 5056 |
"license": "MIT"
|
| 5057 |
},
|
| 5058 |
+
"node_modules/stringify-entities": {
|
| 5059 |
+
"version": "4.0.4",
|
| 5060 |
+
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
|
| 5061 |
+
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
|
| 5062 |
+
"license": "MIT",
|
| 5063 |
+
"dependencies": {
|
| 5064 |
+
"character-entities-html4": "^2.0.0",
|
| 5065 |
+
"character-entities-legacy": "^3.0.0"
|
| 5066 |
+
},
|
| 5067 |
+
"funding": {
|
| 5068 |
+
"type": "github",
|
| 5069 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 5070 |
+
}
|
| 5071 |
+
},
|
| 5072 |
"node_modules/strip-json-comments": {
|
| 5073 |
"version": "3.1.1",
|
| 5074 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
|
|
| 5082 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 5083 |
}
|
| 5084 |
},
|
| 5085 |
+
"node_modules/style-to-js": {
|
| 5086 |
+
"version": "1.1.21",
|
| 5087 |
+
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
|
| 5088 |
+
"integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
|
| 5089 |
+
"license": "MIT",
|
| 5090 |
+
"dependencies": {
|
| 5091 |
+
"style-to-object": "1.0.14"
|
| 5092 |
+
}
|
| 5093 |
+
},
|
| 5094 |
+
"node_modules/style-to-object": {
|
| 5095 |
+
"version": "1.0.14",
|
| 5096 |
+
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
|
| 5097 |
+
"integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
|
| 5098 |
+
"license": "MIT",
|
| 5099 |
+
"dependencies": {
|
| 5100 |
+
"inline-style-parser": "0.2.7"
|
| 5101 |
+
}
|
| 5102 |
+
},
|
| 5103 |
"node_modules/supports-color": {
|
| 5104 |
"version": "7.2.0",
|
| 5105 |
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
|
|
|
| 5177 |
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 5178 |
}
|
| 5179 |
},
|
| 5180 |
+
"node_modules/trim-lines": {
|
| 5181 |
+
"version": "3.0.1",
|
| 5182 |
+
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
|
| 5183 |
+
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
|
| 5184 |
+
"license": "MIT",
|
| 5185 |
+
"funding": {
|
| 5186 |
+
"type": "github",
|
| 5187 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 5188 |
+
}
|
| 5189 |
+
},
|
| 5190 |
"node_modules/troika-three-text": {
|
| 5191 |
"version": "0.52.4",
|
| 5192 |
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
|
|
|
| 5217 |
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
| 5218 |
"license": "MIT"
|
| 5219 |
},
|
| 5220 |
+
"node_modules/trough": {
|
| 5221 |
+
"version": "2.2.0",
|
| 5222 |
+
"resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
|
| 5223 |
+
"integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
|
| 5224 |
+
"license": "MIT",
|
| 5225 |
+
"funding": {
|
| 5226 |
+
"type": "github",
|
| 5227 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 5228 |
+
}
|
| 5229 |
+
},
|
| 5230 |
"node_modules/ts-api-utils": {
|
| 5231 |
"version": "2.4.0",
|
| 5232 |
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
|
|
|
|
| 5341 |
"dev": true,
|
| 5342 |
"license": "MIT"
|
| 5343 |
},
|
| 5344 |
+
"node_modules/unified": {
|
| 5345 |
+
"version": "11.0.5",
|
| 5346 |
+
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
|
| 5347 |
+
"integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
|
| 5348 |
+
"license": "MIT",
|
| 5349 |
+
"dependencies": {
|
| 5350 |
+
"@types/unist": "^3.0.0",
|
| 5351 |
+
"bail": "^2.0.0",
|
| 5352 |
+
"devlop": "^1.0.0",
|
| 5353 |
+
"extend": "^3.0.0",
|
| 5354 |
+
"is-plain-obj": "^4.0.0",
|
| 5355 |
+
"trough": "^2.0.0",
|
| 5356 |
+
"vfile": "^6.0.0"
|
| 5357 |
+
},
|
| 5358 |
+
"funding": {
|
| 5359 |
+
"type": "opencollective",
|
| 5360 |
+
"url": "https://opencollective.com/unified"
|
| 5361 |
+
}
|
| 5362 |
+
},
|
| 5363 |
+
"node_modules/unist-util-is": {
|
| 5364 |
+
"version": "6.0.1",
|
| 5365 |
+
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
| 5366 |
+
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
|
| 5367 |
+
"license": "MIT",
|
| 5368 |
+
"dependencies": {
|
| 5369 |
+
"@types/unist": "^3.0.0"
|
| 5370 |
+
},
|
| 5371 |
+
"funding": {
|
| 5372 |
+
"type": "opencollective",
|
| 5373 |
+
"url": "https://opencollective.com/unified"
|
| 5374 |
+
}
|
| 5375 |
+
},
|
| 5376 |
+
"node_modules/unist-util-position": {
|
| 5377 |
+
"version": "5.0.0",
|
| 5378 |
+
"resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
|
| 5379 |
+
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
|
| 5380 |
+
"license": "MIT",
|
| 5381 |
+
"dependencies": {
|
| 5382 |
+
"@types/unist": "^3.0.0"
|
| 5383 |
+
},
|
| 5384 |
+
"funding": {
|
| 5385 |
+
"type": "opencollective",
|
| 5386 |
+
"url": "https://opencollective.com/unified"
|
| 5387 |
+
}
|
| 5388 |
+
},
|
| 5389 |
+
"node_modules/unist-util-stringify-position": {
|
| 5390 |
+
"version": "4.0.0",
|
| 5391 |
+
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
|
| 5392 |
+
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
|
| 5393 |
+
"license": "MIT",
|
| 5394 |
+
"dependencies": {
|
| 5395 |
+
"@types/unist": "^3.0.0"
|
| 5396 |
+
},
|
| 5397 |
+
"funding": {
|
| 5398 |
+
"type": "opencollective",
|
| 5399 |
+
"url": "https://opencollective.com/unified"
|
| 5400 |
+
}
|
| 5401 |
+
},
|
| 5402 |
+
"node_modules/unist-util-visit": {
|
| 5403 |
+
"version": "5.1.0",
|
| 5404 |
+
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
|
| 5405 |
+
"integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
|
| 5406 |
+
"license": "MIT",
|
| 5407 |
+
"dependencies": {
|
| 5408 |
+
"@types/unist": "^3.0.0",
|
| 5409 |
+
"unist-util-is": "^6.0.0",
|
| 5410 |
+
"unist-util-visit-parents": "^6.0.0"
|
| 5411 |
+
},
|
| 5412 |
+
"funding": {
|
| 5413 |
+
"type": "opencollective",
|
| 5414 |
+
"url": "https://opencollective.com/unified"
|
| 5415 |
+
}
|
| 5416 |
+
},
|
| 5417 |
+
"node_modules/unist-util-visit-parents": {
|
| 5418 |
+
"version": "6.0.2",
|
| 5419 |
+
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
|
| 5420 |
+
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
|
| 5421 |
+
"license": "MIT",
|
| 5422 |
+
"dependencies": {
|
| 5423 |
+
"@types/unist": "^3.0.0",
|
| 5424 |
+
"unist-util-is": "^6.0.0"
|
| 5425 |
+
},
|
| 5426 |
+
"funding": {
|
| 5427 |
+
"type": "opencollective",
|
| 5428 |
+
"url": "https://opencollective.com/unified"
|
| 5429 |
+
}
|
| 5430 |
+
},
|
| 5431 |
"node_modules/update-browserslist-db": {
|
| 5432 |
"version": "1.2.3",
|
| 5433 |
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
|
|
|
| 5487 |
"node": ">= 4"
|
| 5488 |
}
|
| 5489 |
},
|
| 5490 |
+
"node_modules/vfile": {
|
| 5491 |
+
"version": "6.0.3",
|
| 5492 |
+
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
|
| 5493 |
+
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
|
| 5494 |
+
"license": "MIT",
|
| 5495 |
+
"dependencies": {
|
| 5496 |
+
"@types/unist": "^3.0.0",
|
| 5497 |
+
"vfile-message": "^4.0.0"
|
| 5498 |
+
},
|
| 5499 |
+
"funding": {
|
| 5500 |
+
"type": "opencollective",
|
| 5501 |
+
"url": "https://opencollective.com/unified"
|
| 5502 |
+
}
|
| 5503 |
+
},
|
| 5504 |
+
"node_modules/vfile-message": {
|
| 5505 |
+
"version": "4.0.3",
|
| 5506 |
+
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
|
| 5507 |
+
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
|
| 5508 |
+
"license": "MIT",
|
| 5509 |
+
"dependencies": {
|
| 5510 |
+
"@types/unist": "^3.0.0",
|
| 5511 |
+
"unist-util-stringify-position": "^4.0.0"
|
| 5512 |
+
},
|
| 5513 |
+
"funding": {
|
| 5514 |
+
"type": "opencollective",
|
| 5515 |
+
"url": "https://opencollective.com/unified"
|
| 5516 |
+
}
|
| 5517 |
+
},
|
| 5518 |
"node_modules/vite": {
|
| 5519 |
"version": "7.3.1",
|
| 5520 |
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
|
|
|
| 5697 |
"optional": true
|
| 5698 |
}
|
| 5699 |
}
|
| 5700 |
+
},
|
| 5701 |
+
"node_modules/zwitch": {
|
| 5702 |
+
"version": "2.0.4",
|
| 5703 |
+
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
| 5704 |
+
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
|
| 5705 |
+
"license": "MIT",
|
| 5706 |
+
"funding": {
|
| 5707 |
+
"type": "github",
|
| 5708 |
+
"url": "https://github.com/sponsors/wooorm"
|
| 5709 |
+
}
|
| 5710 |
}
|
| 5711 |
}
|
| 5712 |
}
|
web/package.json
CHANGED
|
@@ -18,6 +18,8 @@
|
|
| 18 |
"lucide-react": "^0.575.0",
|
| 19 |
"react": "^19.2.0",
|
| 20 |
"react-dom": "^19.2.0",
|
|
|
|
|
|
|
| 21 |
"three": "^0.183.1"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
|
|
|
| 18 |
"lucide-react": "^0.575.0",
|
| 19 |
"react": "^19.2.0",
|
| 20 |
"react-dom": "^19.2.0",
|
| 21 |
+
"react-markdown": "^10.1.0",
|
| 22 |
+
"remark-gfm": "^4.0.1",
|
| 23 |
"three": "^0.183.1"
|
| 24 |
},
|
| 25 |
"devDependencies": {
|
web/src/App.tsx
CHANGED
|
@@ -1,21 +1,29 @@
|
|
| 1 |
-
import {
|
| 2 |
import axios from 'axios';
|
| 3 |
-
import { Canvas } from '@react-three/fiber';
|
| 4 |
-
import { Chip3D } from './components/Chip3D';
|
| 5 |
import { Dashboard } from './pages/Dashboard';
|
| 6 |
import { DesignStudio } from './pages/DesignStudio';
|
| 7 |
import { Benchmarking } from './pages/Benchmarking';
|
| 8 |
import { Fabrication } from './pages/Fabrication';
|
|
|
|
| 9 |
import './index.css';
|
| 10 |
|
| 11 |
const App = () => {
|
| 12 |
const [selectedPage, setSelectedPage] = useState('Design Studio');
|
| 13 |
const [designs, setDesigns] = useState<{ name: string, has_gds: boolean }[]>([]);
|
| 14 |
const [selectedDesign, setSelectedDesign] = useState<string>('');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
// Bypass Ngrok browser warning for all Axios requests
|
| 17 |
axios.defaults.headers.common['ngrok-skip-browser-warning'] = 'true';
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
useEffect(() => {
|
| 20 |
const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000').replace(/\/$/, '');
|
| 21 |
axios.get(`${API_BASE_URL}/designs`)
|
|
@@ -30,84 +38,185 @@ const App = () => {
|
|
| 30 |
.catch(err => console.error("Failed to fetch designs", err));
|
| 31 |
}, []);
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
return (
|
| 34 |
-
<div className="app-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
<select
|
|
|
|
| 43 |
value={selectedDesign}
|
| 44 |
onChange={(e) => setSelectedDesign(e.target.value)}
|
| 45 |
-
style={{ width: '100%', background: '#111', color: '#00FF88', border: '1px solid #333', padding: '8px', borderRadius: '4px', fontFamily: 'Fira Code', outline: 'none' }}
|
| 46 |
>
|
| 47 |
-
{designs.map(d => (
|
| 48 |
<option key={d.name} value={d.name}>
|
| 49 |
-
{d.name} {d.has_gds ? '
|
| 50 |
</option>
|
| 51 |
))}
|
| 52 |
</select>
|
| 53 |
</div>
|
| 54 |
|
| 55 |
-
<
|
| 56 |
-
{
|
| 57 |
<button
|
| 58 |
-
key={
|
| 59 |
-
className={selectedPage ===
|
| 60 |
-
onClick={() => setSelectedPage(
|
| 61 |
>
|
| 62 |
-
<span
|
| 63 |
-
|
| 64 |
-
{page === 'Dashboard' && 'π'}
|
| 65 |
-
{page === 'Design Studio' && 'β‘'}
|
| 66 |
-
{page === 'Benchmarking' && 'π'}
|
| 67 |
-
{page === 'Fabrication' && 'ποΈ'}
|
| 68 |
-
</span>
|
| 69 |
-
{page}
|
| 70 |
</button>
|
| 71 |
))}
|
| 72 |
-
</
|
| 73 |
|
| 74 |
-
<div
|
| 75 |
-
<
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
</div>
|
| 78 |
-
</
|
| 79 |
-
|
| 80 |
-
{/* Main Content Area */}
|
| 81 |
-
<main className="main-content">
|
| 82 |
-
{selectedPage === 'Home' && (
|
| 83 |
-
<div className="landing-container">
|
| 84 |
-
<h1 className="landing-title">AgentIC</h1>
|
| 85 |
-
<p className="landing-subtitle">Autonomous Silicon Design Framework</p>
|
| 86 |
-
<div className="chip-canvas-container">
|
| 87 |
-
<Suspense fallback={<div style={{ color: '#00FF88' }}>Loading 3D Engine...</div>}>
|
| 88 |
-
<Canvas camera={{ position: [0, 4, 6], fov: 45 }}>
|
| 89 |
-
{/* Note: ambientLight intrinsic elements exist natively in R3F */}
|
| 90 |
-
<ambientLight intensity={0.5} />
|
| 91 |
-
<pointLight position={[10, 10, 10]} intensity={1} />
|
| 92 |
-
<Chip3D />
|
| 93 |
-
</Canvas>
|
| 94 |
-
</Suspense>
|
| 95 |
-
</div>
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
</main>
|
| 112 |
</div>
|
| 113 |
);
|
|
|
|
| 1 |
+
import { useEffect, useMemo, useState } from 'react';
|
| 2 |
import axios from 'axios';
|
|
|
|
|
|
|
| 3 |
import { Dashboard } from './pages/Dashboard';
|
| 4 |
import { DesignStudio } from './pages/DesignStudio';
|
| 5 |
import { Benchmarking } from './pages/Benchmarking';
|
| 6 |
import { Fabrication } from './pages/Fabrication';
|
| 7 |
+
import { Documentation } from './pages/Documentation';
|
| 8 |
import './index.css';
|
| 9 |
|
| 10 |
const App = () => {
|
| 11 |
const [selectedPage, setSelectedPage] = useState('Design Studio');
|
| 12 |
const [designs, setDesigns] = useState<{ name: string, has_gds: boolean }[]>([]);
|
| 13 |
const [selectedDesign, setSelectedDesign] = useState<string>('');
|
| 14 |
+
const [theme, setTheme] = useState<'light' | 'dark'>(() => {
|
| 15 |
+
const saved = localStorage.getItem('agentic-theme');
|
| 16 |
+
return saved === 'dark' ? 'dark' : 'light';
|
| 17 |
+
});
|
| 18 |
|
| 19 |
// Bypass Ngrok browser warning for all Axios requests
|
| 20 |
axios.defaults.headers.common['ngrok-skip-browser-warning'] = 'true';
|
| 21 |
|
| 22 |
+
useEffect(() => {
|
| 23 |
+
document.documentElement.setAttribute('data-theme', theme);
|
| 24 |
+
localStorage.setItem('agentic-theme', theme);
|
| 25 |
+
}, [theme]);
|
| 26 |
+
|
| 27 |
useEffect(() => {
|
| 28 |
const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000').replace(/\/$/, '');
|
| 29 |
axios.get(`${API_BASE_URL}/designs`)
|
|
|
|
| 38 |
.catch(err => console.error("Failed to fetch designs", err));
|
| 39 |
}, []);
|
| 40 |
|
| 41 |
+
const navItems = useMemo(
|
| 42 |
+
() => [
|
| 43 |
+
{ name: 'Home', icon: 'π ' },
|
| 44 |
+
{ name: 'Design Studio', icon: 'β‘' },
|
| 45 |
+
{ name: 'Dashboard', icon: 'π' },
|
| 46 |
+
{ name: 'Documentation', icon: 'π' },
|
| 47 |
+
{ name: 'Benchmarking', icon: 'π' },
|
| 48 |
+
{ name: 'Fabrication', icon: 'ποΈ' },
|
| 49 |
+
],
|
| 50 |
+
[]
|
| 51 |
+
);
|
| 52 |
+
|
| 53 |
return (
|
| 54 |
+
<div className="app-shell">
|
| 55 |
+
<aside className="app-sidebar">
|
| 56 |
+
<div className="app-brand">
|
| 57 |
+
<div className="app-brand-logo">A</div>
|
| 58 |
+
<div>
|
| 59 |
+
<div className="app-brand-title">AgentIC</div>
|
| 60 |
+
<div className="app-brand-sub">Autonomous Silicon Studio</div>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div className="app-sidebar-group">
|
| 65 |
+
<div className="app-sidebar-label">Active Design</div>
|
| 66 |
<select
|
| 67 |
+
className="app-design-select"
|
| 68 |
value={selectedDesign}
|
| 69 |
onChange={(e) => setSelectedDesign(e.target.value)}
|
|
|
|
| 70 |
>
|
| 71 |
+
{designs.map((d) => (
|
| 72 |
<option key={d.name} value={d.name}>
|
| 73 |
+
{d.name} {d.has_gds ? 'β’ GDS' : ''}
|
| 74 |
</option>
|
| 75 |
))}
|
| 76 |
</select>
|
| 77 |
</div>
|
| 78 |
|
| 79 |
+
<nav className="app-nav">
|
| 80 |
+
{navItems.map((item) => (
|
| 81 |
<button
|
| 82 |
+
key={item.name}
|
| 83 |
+
className={`app-nav-btn ${selectedPage === item.name ? 'active' : ''}`}
|
| 84 |
+
onClick={() => setSelectedPage(item.name)}
|
| 85 |
>
|
| 86 |
+
<span>{item.icon}</span>
|
| 87 |
+
<span>{item.name}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
</button>
|
| 89 |
))}
|
| 90 |
+
</nav>
|
| 91 |
|
| 92 |
+
<div className="app-sidebar-footer">
|
| 93 |
+
<button
|
| 94 |
+
className="theme-toggle"
|
| 95 |
+
onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}
|
| 96 |
+
>
|
| 97 |
+
{theme === 'light' ? 'π Dark' : 'βοΈ Light'}
|
| 98 |
+
</button>
|
| 99 |
+
<div className="app-version">v4.0 Β· Multi-Agent Β· 2026</div>
|
| 100 |
</div>
|
| 101 |
+
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
+
<main className="app-main">
|
| 104 |
+
<header className="app-topbar">
|
| 105 |
+
<h1>{selectedPage}</h1>
|
| 106 |
+
<div className="app-topbar-meta">Multi-Agent Autonomous Silicon</div>
|
| 107 |
+
</header>
|
| 108 |
+
|
| 109 |
+
<section className="app-content">
|
| 110 |
+
{selectedPage === 'Home' && (
|
| 111 |
+
<div className="home-overview">
|
| 112 |
+
<div className="home-hero">
|
| 113 |
+
<div className="home-hero-badge">Text β Silicon</div>
|
| 114 |
+
<h2 className="home-hero-title">Autonomous Chip Design Studio</h2>
|
| 115 |
+
<p className="home-hero-desc">
|
| 116 |
+
From natural language to fabrication-ready GDSII β powered by multi-agent
|
| 117 |
+
collaboration, structured spec decomposition, self-healing loops, and
|
| 118 |
+
15-stage autonomous pipeline.
|
| 119 |
+
</p>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<div className="home-card-grid">
|
| 123 |
+
<div className="home-kpi">{designs.length}<span>Designs</span></div>
|
| 124 |
+
<div className="home-kpi">15<span>Pipeline Stages</span></div>
|
| 125 |
+
<div className="home-kpi">5<span>Core Modules</span></div>
|
| 126 |
+
<div className="home-kpi">12<span>AI Agents</span></div>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<div className="home-section">
|
| 130 |
+
<h3 className="home-section-title">Multi-Agent Architecture</h3>
|
| 131 |
+
<div className="home-agent-grid">
|
| 132 |
+
<div className="agent-card">
|
| 133 |
+
<div className="agent-icon">ποΈ</div>
|
| 134 |
+
<div className="agent-name">ArchitectModule</div>
|
| 135 |
+
<div className="agent-desc">Spec β Structured JSON (SID) contract</div>
|
| 136 |
+
</div>
|
| 137 |
+
<div className="agent-card">
|
| 138 |
+
<div className="agent-icon">π»</div>
|
| 139 |
+
<div className="agent-name">RTL Designer + Reviewer</div>
|
| 140 |
+
<div className="agent-desc">Collaborative 2-agent Crew with tools</div>
|
| 141 |
+
</div>
|
| 142 |
+
<div className="agent-card">
|
| 143 |
+
<div className="agent-icon">π§ͺ</div>
|
| 144 |
+
<div className="agent-name">TB Designer</div>
|
| 145 |
+
<div className="agent-desc">Verilator-safe flat procedural TBs</div>
|
| 146 |
+
</div>
|
| 147 |
+
<div className="agent-card">
|
| 148 |
+
<div className="agent-icon">π</div>
|
| 149 |
+
<div className="agent-name">Error Analyst</div>
|
| 150 |
+
<div className="agent-desc">Multi-class failure diagnosis (AβE)</div>
|
| 151 |
+
</div>
|
| 152 |
+
<div className="agent-card">
|
| 153 |
+
<div className="agent-icon">π</div>
|
| 154 |
+
<div className="agent-name">SelfReflectPipeline</div>
|
| 155 |
+
<div className="agent-desc">Convergence-aware hardening retry</div>
|
| 156 |
+
</div>
|
| 157 |
+
<div className="agent-card">
|
| 158 |
+
<div className="agent-icon">π§ </div>
|
| 159 |
+
<div className="agent-name">DeepDebugger</div>
|
| 160 |
+
<div className="agent-desc">FVDebug causal graphs + for-and-against</div>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
+
<div className="home-section">
|
| 166 |
+
<h3 className="home-section-title">Pipeline Flow</h3>
|
| 167 |
+
<div className="pipeline-flow">
|
| 168 |
+
{[
|
| 169 |
+
{ icon: 'π', label: 'SPEC', sub: 'SID Decompose' },
|
| 170 |
+
{ icon: 'π»', label: 'RTL_GEN', sub: '2-Agent Crew' },
|
| 171 |
+
{ icon: 'π¨', label: 'RTL_FIX', sub: 'Lint + Rigor' },
|
| 172 |
+
{ icon: 'π§ͺ', label: 'VERIFY', sub: 'Sim + TB Gate' },
|
| 173 |
+
{ icon: 'π', label: 'FORMAL', sub: 'SVA + SBY' },
|
| 174 |
+
{ icon: 'π', label: 'COVERAGE', sub: 'Anti-regress' },
|
| 175 |
+
{ icon: 'πΊοΈ', label: 'FLOOR', sub: 'Floorplan' },
|
| 176 |
+
{ icon: 'ποΈ', label: 'HARDEN', sub: 'Self-Reflect' },
|
| 177 |
+
{ icon: 'β
', label: 'SIGNOFF', sub: 'DRC/LVS/STA' },
|
| 178 |
+
].map((s, i) => (
|
| 179 |
+
<div className="pipeline-stage" key={s.label}>
|
| 180 |
+
<div className="pipeline-stage-icon">{s.icon}</div>
|
| 181 |
+
<div className="pipeline-stage-label">{s.label}</div>
|
| 182 |
+
<div className="pipeline-stage-sub">{s.sub}</div>
|
| 183 |
+
{i < 8 && <div className="pipeline-arrow">β</div>}
|
| 184 |
+
</div>
|
| 185 |
+
))}
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
<div className="home-section">
|
| 190 |
+
<h3 className="home-section-title">Quick Start</h3>
|
| 191 |
+
<div className="home-quickstart">
|
| 192 |
+
<div className="quickstart-step">
|
| 193 |
+
<div className="quickstart-num">1</div>
|
| 194 |
+
<div>Go to <strong>Design Studio</strong> and describe any chip</div>
|
| 195 |
+
</div>
|
| 196 |
+
<div className="quickstart-step">
|
| 197 |
+
<div className="quickstart-num">2</div>
|
| 198 |
+
<div>Watch 12 AI agents build it through 15 stages</div>
|
| 199 |
+
</div>
|
| 200 |
+
<div className="quickstart-step">
|
| 201 |
+
<div className="quickstart-num">3</div>
|
| 202 |
+
<div>Check <strong>Dashboard</strong> for silicon metrics and signoff</div>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
<button className="btn-primary home-cta" onClick={() => setSelectedPage('Design Studio')}>
|
| 206 |
+
Start New Build β
|
| 207 |
+
</button>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
)}
|
| 211 |
|
| 212 |
+
{selectedPage === 'Dashboard' && <Dashboard selectedDesign={selectedDesign} />}
|
| 213 |
+
{selectedPage === 'Design Studio' && <DesignStudio />}
|
| 214 |
+
{selectedPage === 'Documentation' && <Documentation />}
|
| 215 |
+
{selectedPage === 'Benchmarking' && <Benchmarking selectedDesign={selectedDesign} />}
|
| 216 |
+
{selectedPage === 'Fabrication' && (
|
| 217 |
+
<Fabrication selectedDesign={selectedDesign} hasGds={designs.find((d) => d.name === selectedDesign)?.has_gds} />
|
| 218 |
+
)}
|
| 219 |
+
</section>
|
| 220 |
</main>
|
| 221 |
</div>
|
| 222 |
);
|
web/src/components/BuildMonitor.tsx
CHANGED
|
@@ -13,6 +13,7 @@ const STATES_DISPLAY: Record<string, { label: string; icon: string }> = {
|
|
| 13 |
FORMAL_VERIFY: { label: 'Formal Verification', icon: 'π' },
|
| 14 |
COVERAGE_CHECK: { label: 'Coverage Analysis', icon: 'π' },
|
| 15 |
REGRESSION: { label: 'Regression Testing', icon: 'π' },
|
|
|
|
| 16 |
FLOORPLAN: { label: 'Floorplanning', icon: 'πΊοΈ' },
|
| 17 |
HARDENING: { label: 'GDSII Hardening', icon: 'ποΈ' },
|
| 18 |
CONVERGENCE_REVIEW: { label: 'Convergence Review', icon: 'π―' },
|
|
@@ -21,8 +22,6 @@ const STATES_DISPLAY: Record<string, { label: string; icon: string }> = {
|
|
| 21 |
SUCCESS: { label: 'Build Complete', icon: 'π' },
|
| 22 |
};
|
| 23 |
|
| 24 |
-
const STATE_ORDER = Object.keys(STATES_DISPLAY);
|
| 25 |
-
|
| 26 |
interface BuildEvent {
|
| 27 |
type: string;
|
| 28 |
state: string;
|
|
@@ -32,23 +31,57 @@ interface BuildEvent {
|
|
| 32 |
timestamp: number;
|
| 33 |
}
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
interface Props {
|
| 36 |
designName: string;
|
| 37 |
jobId: string;
|
| 38 |
events: BuildEvent[];
|
| 39 |
jobStatus: string;
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
-
export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobStatus }) => {
|
| 43 |
const logsRef = useRef<HTMLDivElement>(null);
|
| 44 |
const [cancelling, setCancelling] = React.useState(false);
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
const reachedStates = new Set(events.map(e => e.state));
|
| 47 |
const currentState = events.length > 0 ? events[events.length - 1].state : 'INIT';
|
| 48 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
const logEvents = events.filter(e => e.message && e.message.trim().length > 0);
|
| 50 |
const isDone = ['done', 'failed', 'cancelled', 'cancelling'].includes(jobStatus);
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
useEffect(() => {
|
| 53 |
if (logsRef.current) {
|
| 54 |
logsRef.current.scrollTop = logsRef.current.scrollHeight;
|
|
@@ -77,7 +110,7 @@ export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobSt
|
|
| 77 |
{!isDone ? (
|
| 78 |
<>
|
| 79 |
<span className="spinner" />
|
| 80 |
-
<span>Step {currentStep} / {
|
| 81 |
<button
|
| 82 |
className="cancel-btn"
|
| 83 |
onClick={handleCancel}
|
|
@@ -100,9 +133,9 @@ export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobSt
|
|
| 100 |
<div className="checkpoint-column">
|
| 101 |
<div className="section-heading">Build Pipeline</div>
|
| 102 |
<div className="checkpoint-list">
|
| 103 |
-
{
|
| 104 |
-
const info =
|
| 105 |
-
const isPassed =
|
| 106 |
const isCurrent = currentState === stateKey && !isDone;
|
| 107 |
const isSuccess = stateKey === 'SUCCESS' && jobStatus === 'done';
|
| 108 |
|
|
@@ -122,7 +155,7 @@ export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobSt
|
|
| 122 |
) : (
|
| 123 |
<span className="check-todo" />
|
| 124 |
)}
|
| 125 |
-
{idx <
|
| 126 |
<div className={`checkpoint-line ${isPassed ? 'line-done' : ''}`} />
|
| 127 |
)}
|
| 128 |
</div>
|
|
@@ -141,6 +174,25 @@ export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobSt
|
|
| 141 |
{/* Live Terminal */}
|
| 142 |
<div className="terminal-column">
|
| 143 |
<div className="section-heading">Live Log</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
<div className="live-terminal" ref={logsRef}>
|
| 145 |
{logEvents.length === 0 ? (
|
| 146 |
<span className="terminal-waiting">Waiting for AgentIC to startβ¦</span>
|
|
@@ -167,11 +219,11 @@ export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobSt
|
|
| 167 |
<div className="progress-bar-wrap">
|
| 168 |
<div
|
| 169 |
className="progress-bar-fill"
|
| 170 |
-
style={{ width: `${(currentStep /
|
| 171 |
/>
|
| 172 |
</div>
|
| 173 |
<div className="progress-label">
|
| 174 |
-
{Math.round((currentStep /
|
| 175 |
</div>
|
| 176 |
</div>
|
| 177 |
</div>
|
|
|
|
| 13 |
FORMAL_VERIFY: { label: 'Formal Verification', icon: 'π' },
|
| 14 |
COVERAGE_CHECK: { label: 'Coverage Analysis', icon: 'π' },
|
| 15 |
REGRESSION: { label: 'Regression Testing', icon: 'π' },
|
| 16 |
+
SDC_GEN: { label: 'SDC Generation', icon: 'π' },
|
| 17 |
FLOORPLAN: { label: 'Floorplanning', icon: 'πΊοΈ' },
|
| 18 |
HARDENING: { label: 'GDSII Hardening', icon: 'ποΈ' },
|
| 19 |
CONVERGENCE_REVIEW: { label: 'Convergence Review', icon: 'π―' },
|
|
|
|
| 22 |
SUCCESS: { label: 'Build Complete', icon: 'π' },
|
| 23 |
};
|
| 24 |
|
|
|
|
|
|
|
| 25 |
interface BuildEvent {
|
| 26 |
type: string;
|
| 27 |
state: string;
|
|
|
|
| 31 |
timestamp: number;
|
| 32 |
}
|
| 33 |
|
| 34 |
+
interface StageSchemaItem {
|
| 35 |
+
state: string;
|
| 36 |
+
label: string;
|
| 37 |
+
icon: string;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
interface Props {
|
| 41 |
designName: string;
|
| 42 |
jobId: string;
|
| 43 |
events: BuildEvent[];
|
| 44 |
jobStatus: string;
|
| 45 |
+
stageSchema?: StageSchemaItem[];
|
| 46 |
}
|
| 47 |
|
| 48 |
+
export const BuildMonitor: React.FC<Props> = ({ designName, jobId, events, jobStatus, stageSchema }) => {
|
| 49 |
const logsRef = useRef<HTMLDivElement>(null);
|
| 50 |
const [cancelling, setCancelling] = React.useState(false);
|
| 51 |
|
| 52 |
+
const mergedDisplay: Record<string, { label: string; icon: string }> = React.useMemo(() => {
|
| 53 |
+
if (!stageSchema || stageSchema.length === 0) return STATES_DISPLAY;
|
| 54 |
+
const map: Record<string, { label: string; icon: string }> = {};
|
| 55 |
+
for (const stage of stageSchema) {
|
| 56 |
+
map[stage.state] = { label: stage.label, icon: stage.icon };
|
| 57 |
+
}
|
| 58 |
+
if (!map.SUCCESS) map.SUCCESS = STATES_DISPLAY.SUCCESS;
|
| 59 |
+
return map;
|
| 60 |
+
}, [stageSchema]);
|
| 61 |
+
|
| 62 |
+
const stateOrder = React.useMemo(() => Object.keys(mergedDisplay), [mergedDisplay]);
|
| 63 |
+
|
| 64 |
const reachedStates = new Set(events.map(e => e.state));
|
| 65 |
const currentState = events.length > 0 ? events[events.length - 1].state : 'INIT';
|
| 66 |
+
const currentStateIndex = stateOrder.indexOf(currentState);
|
| 67 |
+
const furthestReachedIndex = Math.max(
|
| 68 |
+
0,
|
| 69 |
+
...events
|
| 70 |
+
.map(e => stateOrder.indexOf(e.state))
|
| 71 |
+
.filter(idx => idx >= 0)
|
| 72 |
+
);
|
| 73 |
+
const currentStep = Math.max(1, (currentStateIndex >= 0 ? currentStateIndex : furthestReachedIndex) + 1);
|
| 74 |
const logEvents = events.filter(e => e.message && e.message.trim().length > 0);
|
| 75 |
const isDone = ['done', 'failed', 'cancelled', 'cancelling'].includes(jobStatus);
|
| 76 |
|
| 77 |
+
const selfHeal = {
|
| 78 |
+
stageExceptions: events.filter(e => /stage .* exception/i.test(e.message || '')).length,
|
| 79 |
+
formalRegens: events.filter(e => /regenerating sva/i.test(e.message || '')).length,
|
| 80 |
+
coverageRestores: events.filter(e => /restoring best testbench/i.test(e.message || '')).length,
|
| 81 |
+
coverageRejects: events.filter(e => /regressed coverage/i.test(e.message || '')).length,
|
| 82 |
+
deterministicFallbacks: events.filter(e => /deterministic tb fallback/i.test(e.message || '')).length,
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
useEffect(() => {
|
| 86 |
if (logsRef.current) {
|
| 87 |
logsRef.current.scrollTop = logsRef.current.scrollHeight;
|
|
|
|
| 110 |
{!isDone ? (
|
| 111 |
<>
|
| 112 |
<span className="spinner" />
|
| 113 |
+
<span>Step {currentStep} / {stateOrder.length}</span>
|
| 114 |
<button
|
| 115 |
className="cancel-btn"
|
| 116 |
onClick={handleCancel}
|
|
|
|
| 133 |
<div className="checkpoint-column">
|
| 134 |
<div className="section-heading">Build Pipeline</div>
|
| 135 |
<div className="checkpoint-list">
|
| 136 |
+
{stateOrder.map((stateKey, idx) => {
|
| 137 |
+
const info = mergedDisplay[stateKey] || { label: stateKey, icon: 'β’' };
|
| 138 |
+
const isPassed = stateOrder.indexOf(stateKey) < stateOrder.indexOf(currentState);
|
| 139 |
const isCurrent = currentState === stateKey && !isDone;
|
| 140 |
const isSuccess = stateKey === 'SUCCESS' && jobStatus === 'done';
|
| 141 |
|
|
|
|
| 155 |
) : (
|
| 156 |
<span className="check-todo" />
|
| 157 |
)}
|
| 158 |
+
{idx < stateOrder.length - 1 && (
|
| 159 |
<div className={`checkpoint-line ${isPassed ? 'line-done' : ''}`} />
|
| 160 |
)}
|
| 161 |
</div>
|
|
|
|
| 174 |
{/* Live Terminal */}
|
| 175 |
<div className="terminal-column">
|
| 176 |
<div className="section-heading">Live Log</div>
|
| 177 |
+
<div style={{
|
| 178 |
+
border: '1px solid var(--border)',
|
| 179 |
+
borderRadius: 'var(--radius)',
|
| 180 |
+
background: 'var(--bg-card)',
|
| 181 |
+
padding: '0.65rem 0.75rem',
|
| 182 |
+
marginBottom: '0.65rem',
|
| 183 |
+
display: 'flex',
|
| 184 |
+
flexWrap: 'wrap',
|
| 185 |
+
gap: '0.5rem',
|
| 186 |
+
color: 'var(--text-mid)',
|
| 187 |
+
fontSize: '0.78rem',
|
| 188 |
+
}}>
|
| 189 |
+
<span style={{ color: 'var(--text)', fontWeight: 600 }}>Self-Healing</span>
|
| 190 |
+
<span>Stage guards: {selfHeal.stageExceptions}</span>
|
| 191 |
+
<span>Formal regens: {selfHeal.formalRegens}</span>
|
| 192 |
+
<span>TB regressions blocked: {selfHeal.coverageRejects}</span>
|
| 193 |
+
<span>Best TB restores: {selfHeal.coverageRestores}</span>
|
| 194 |
+
<span>TB fallbacks: {selfHeal.deterministicFallbacks}</span>
|
| 195 |
+
</div>
|
| 196 |
<div className="live-terminal" ref={logsRef}>
|
| 197 |
{logEvents.length === 0 ? (
|
| 198 |
<span className="terminal-waiting">Waiting for AgentIC to startβ¦</span>
|
|
|
|
| 219 |
<div className="progress-bar-wrap">
|
| 220 |
<div
|
| 221 |
className="progress-bar-fill"
|
| 222 |
+
style={{ width: `${(currentStep / stateOrder.length) * 100}%` }}
|
| 223 |
/>
|
| 224 |
</div>
|
| 225 |
<div className="progress-label">
|
| 226 |
+
{Math.round((currentStep / stateOrder.length) * 100)}% complete
|
| 227 |
</div>
|
| 228 |
</div>
|
| 229 |
</div>
|
web/src/components/ChipSummary.tsx
CHANGED
|
@@ -25,6 +25,20 @@ function MetricCard({ label, value, icon, color }: { label: string; value: any;
|
|
| 25 |
);
|
| 26 |
}
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, events, onReset }) => {
|
| 29 |
const success = jobStatus === 'done';
|
| 30 |
const metrics = result?.metrics || {};
|
|
@@ -34,10 +48,36 @@ export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, ev
|
|
| 34 |
const strategy = result?.strategy || '';
|
| 35 |
const buildTimeSec = result?.build_time_s || 0;
|
| 36 |
const buildTimeMin = Math.round(buildTimeSec / 60);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
// Count checkpoints passed
|
| 39 |
const checkpointCount = events.filter(e => e.type === 'transition' || e.type === 'checkpoint').length;
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
return (
|
| 42 |
<div className="summary-root">
|
| 43 |
{/* ββ Banner βββββββββββββββββββββββββββββββββββββββ */}
|
|
@@ -61,19 +101,35 @@ export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, ev
|
|
| 61 |
</div>
|
| 62 |
</motion.div>
|
| 63 |
|
| 64 |
-
{/* ββ Metrics βββββββββββββββββββββββββββββββ
|
| 65 |
{success && (
|
| 66 |
<div className="summary-section">
|
| 67 |
<h2 className="section-heading">π Silicon Metrics</h2>
|
| 68 |
<div className="metrics-grid">
|
| 69 |
-
<MetricCard label="Worst Negative Slack" value={metrics.wns !== undefined ? `${metrics.wns} ns` : 'N/A'} icon="β±οΈ" color="
|
| 70 |
-
<MetricCard label="Die Area" value={metrics.area} icon="π" color="
|
| 71 |
-
<MetricCard label="Total Power" value={metrics.power} icon="β‘" color="
|
| 72 |
-
<MetricCard label="Gate Count" value={metrics.gate_count} icon="π²" color="
|
| 73 |
</div>
|
| 74 |
</div>
|
| 75 |
)}
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
{/* ββ Strategy & Build Info ββββββββββββββββββββββββ */}
|
| 78 |
{(strategy || buildTimeSec > 0) && (
|
| 79 |
<div className="summary-section">
|
|
@@ -86,6 +142,24 @@ export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, ev
|
|
| 86 |
</div>
|
| 87 |
)}
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
{/* ββ Architecture Spec ββββββββββββββββββββββββββββ */}
|
| 90 |
{spec && (
|
| 91 |
<div className="summary-section">
|
|
@@ -122,7 +196,7 @@ export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, ev
|
|
| 122 |
{convergence.slice(-5).map((s: any, i: number) => (
|
| 123 |
<tr key={i}>
|
| 124 |
<td>{s.iteration}</td>
|
| 125 |
-
<td style={{ color: s.wns >= 0 ? '
|
| 126 |
<td>{s.tns?.toFixed(3)}</td>
|
| 127 |
<td>{s.congestion?.toFixed(2)}</td>
|
| 128 |
<td>{s.area_um2?.toFixed(0)}</td>
|
|
|
|
| 25 |
);
|
| 26 |
}
|
| 27 |
|
| 28 |
+
function CheckItem({ label, passed, detail }: { label: string; passed: boolean | null; detail?: string }) {
|
| 29 |
+
return (
|
| 30 |
+
<div className="check-report-item">
|
| 31 |
+
<span className="check-report-status" data-status={passed === true ? 'pass' : passed === false ? 'fail' : 'skip'}>
|
| 32 |
+
{passed === true ? 'β' : passed === false ? 'β' : 'β'}
|
| 33 |
+
</span>
|
| 34 |
+
<div>
|
| 35 |
+
<span className="check-report-label">{label}</span>
|
| 36 |
+
{detail && <span className="check-report-detail">{detail}</span>}
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, events, onReset }) => {
|
| 43 |
const success = jobStatus === 'done';
|
| 44 |
const metrics = result?.metrics || {};
|
|
|
|
| 48 |
const strategy = result?.strategy || '';
|
| 49 |
const buildTimeSec = result?.build_time_s || 0;
|
| 50 |
const buildTimeMin = Math.round(buildTimeSec / 60);
|
| 51 |
+
const coverage = result?.coverage || {};
|
| 52 |
+
const formalResult = result?.formal_result || '';
|
| 53 |
+
const signoffResult = result?.signoff_result || '';
|
| 54 |
+
|
| 55 |
+
const selfHeal = result?.self_heal || {
|
| 56 |
+
stage_exception_count: events.filter(e => /stage .* exception/i.test(e?.message || '')).length,
|
| 57 |
+
formal_regen_count: events.filter(e => /regenerating sva/i.test(e?.message || '')).length,
|
| 58 |
+
coverage_best_restore_count: events.filter(e => /restoring best testbench/i.test(e?.message || '')).length,
|
| 59 |
+
coverage_regression_reject_count: events.filter(e => /regressed coverage/i.test(e?.message || '')).length,
|
| 60 |
+
deterministic_tb_fallback_count: events.filter(e => /deterministic tb fallback/i.test(e?.message || '')).length,
|
| 61 |
+
};
|
| 62 |
|
|
|
|
| 63 |
const checkpointCount = events.filter(e => e.type === 'transition' || e.type === 'checkpoint').length;
|
| 64 |
|
| 65 |
+
// Derive check results from events and result
|
| 66 |
+
const syntaxPassed = events.some(e => /syntax.*pass|lint.*clean/i.test(e?.message || ''));
|
| 67 |
+
const simPassed = events.some(e => /test passed|simulation.*pass/i.test(e?.message || ''));
|
| 68 |
+
const formalPassed = formalResult ? /pass|success/i.test(formalResult) : events.some(e => /formal.*pass/i.test(e?.message || ''));
|
| 69 |
+
const coveragePassed = events.some(e => /coverage passed/i.test(e?.message || ''));
|
| 70 |
+
const signoffPassed = signoffResult ? /pass|success/i.test(signoffResult) : null;
|
| 71 |
+
const lineCov = coverage?.line || coverage?.line_pct;
|
| 72 |
+
const branchCov = coverage?.branch || coverage?.branch_pct;
|
| 73 |
+
|
| 74 |
+
const totalHealActions =
|
| 75 |
+
(selfHeal.stage_exception_count || 0) +
|
| 76 |
+
(selfHeal.formal_regen_count || 0) +
|
| 77 |
+
(selfHeal.coverage_best_restore_count || 0) +
|
| 78 |
+
(selfHeal.coverage_regression_reject_count || 0) +
|
| 79 |
+
(selfHeal.deterministic_tb_fallback_count || 0);
|
| 80 |
+
|
| 81 |
return (
|
| 82 |
<div className="summary-root">
|
| 83 |
{/* ββ Banner βββββββββββββββββββββββββββββββββββββββ */}
|
|
|
|
| 101 |
</div>
|
| 102 |
</motion.div>
|
| 103 |
|
| 104 |
+
{/* ββ Silicon Metrics βββββββββββββββββββββββββββββββ */}
|
| 105 |
{success && (
|
| 106 |
<div className="summary-section">
|
| 107 |
<h2 className="section-heading">π Silicon Metrics</h2>
|
| 108 |
<div className="metrics-grid">
|
| 109 |
+
<MetricCard label="Worst Negative Slack" value={metrics.wns !== undefined ? `${metrics.wns} ns` : 'N/A'} icon="β±οΈ" color="var(--accent)" />
|
| 110 |
+
<MetricCard label="Die Area" value={metrics.area} icon="π" color="var(--success)" />
|
| 111 |
+
<MetricCard label="Total Power" value={metrics.power} icon="β‘" color="var(--warn)" />
|
| 112 |
+
<MetricCard label="Gate Count" value={metrics.gate_count} icon="π²" color="var(--text-mid)" />
|
| 113 |
</div>
|
| 114 |
</div>
|
| 115 |
)}
|
| 116 |
|
| 117 |
+
{/* ββ Verification Report Card βββββββββββββββββββββ */}
|
| 118 |
+
<div className="summary-section">
|
| 119 |
+
<h2 className="section-heading">π Verification Report Card</h2>
|
| 120 |
+
<div className="check-report-card">
|
| 121 |
+
<CheckItem label="RTL Syntax & Lint" passed={syntaxPassed} detail={syntaxPassed ? 'Verilator lint-only clean' : undefined} />
|
| 122 |
+
<CheckItem label="Functional Simulation" passed={simPassed} detail={simPassed ? 'TEST PASSED detected' : undefined} />
|
| 123 |
+
<CheckItem label="Formal Property Verification" passed={formalPassed} detail={formalResult ? formalResult.substring(0, 60) : undefined} />
|
| 124 |
+
<CheckItem label="Coverage Closure" passed={coveragePassed}
|
| 125 |
+
detail={lineCov ? `Line: ${lineCov}%${branchCov ? ` Β· Branch: ${branchCov}%` : ''}` : undefined}
|
| 126 |
+
/>
|
| 127 |
+
<CheckItem label="DRC / LVS Signoff" passed={signoffPassed}
|
| 128 |
+
detail={signoffResult ? signoffResult.substring(0, 60) : (success ? 'Physical checks completed' : undefined)}
|
| 129 |
+
/>
|
| 130 |
+
</div>
|
| 131 |
+
</div>
|
| 132 |
+
|
| 133 |
{/* ββ Strategy & Build Info ββββββββββββββββββββββββ */}
|
| 134 |
{(strategy || buildTimeSec > 0) && (
|
| 135 |
<div className="summary-section">
|
|
|
|
| 142 |
</div>
|
| 143 |
)}
|
| 144 |
|
| 145 |
+
{/* ββ Self-Healing Insights βββββββββββββββββββββββ */}
|
| 146 |
+
<div className="summary-section">
|
| 147 |
+
<h2 className="section-heading">π§ Self-Healing Insights</h2>
|
| 148 |
+
{totalHealActions === 0 ? (
|
| 149 |
+
<p style={{ color: 'var(--text-dim)', fontSize: '0.82rem' }}>
|
| 150 |
+
Clean run β no self-healing interventions were needed.
|
| 151 |
+
</p>
|
| 152 |
+
) : (
|
| 153 |
+
<div className="info-pills">
|
| 154 |
+
{selfHeal.stage_exception_count > 0 && <span className="info-pill">Stage guards: {selfHeal.stage_exception_count}</span>}
|
| 155 |
+
{selfHeal.formal_regen_count > 0 && <span className="info-pill">Formal regens: {selfHeal.formal_regen_count}</span>}
|
| 156 |
+
{selfHeal.coverage_regression_reject_count > 0 && <span className="info-pill">TB regressions blocked: {selfHeal.coverage_regression_reject_count}</span>}
|
| 157 |
+
{selfHeal.coverage_best_restore_count > 0 && <span className="info-pill">Best TB restores: {selfHeal.coverage_best_restore_count}</span>}
|
| 158 |
+
{selfHeal.deterministic_tb_fallback_count > 0 && <span className="info-pill">TB fallbacks: {selfHeal.deterministic_tb_fallback_count}</span>}
|
| 159 |
+
</div>
|
| 160 |
+
)}
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
{/* ββ Architecture Spec ββββββββββββββββββββββββββββ */}
|
| 164 |
{spec && (
|
| 165 |
<div className="summary-section">
|
|
|
|
| 196 |
{convergence.slice(-5).map((s: any, i: number) => (
|
| 197 |
<tr key={i}>
|
| 198 |
<td>{s.iteration}</td>
|
| 199 |
+
<td style={{ color: s.wns >= 0 ? 'var(--success)' : 'var(--fail)' }}>{s.wns?.toFixed(3)}</td>
|
| 200 |
<td>{s.tns?.toFixed(3)}</td>
|
| 201 |
<td>{s.congestion?.toFixed(2)}</td>
|
| 202 |
<td>{s.area_um2?.toFixed(0)}</td>
|
web/src/index.css
CHANGED
|
@@ -56,6 +56,35 @@
|
|
| 56 |
--slow: 380ms;
|
| 57 |
}
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
/* ββ Reset βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 60 |
*,
|
| 61 |
*::before,
|
|
@@ -65,44 +94,1319 @@
|
|
| 65 |
padding: 0;
|
| 66 |
}
|
| 67 |
|
| 68 |
-
html {
|
| 69 |
-
font-size: 15px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
-
|
| 73 |
-
font-
|
| 74 |
-
background: var(--bg);
|
| 75 |
color: var(--text);
|
| 76 |
-
line-height: 1.6;
|
| 77 |
-
-webkit-font-smoothing: antialiased;
|
| 78 |
-
-moz-osx-font-smoothing: grayscale;
|
| 79 |
}
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
width: 5px;
|
| 84 |
-
height: 5px;
|
| 85 |
}
|
| 86 |
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
| 89 |
}
|
| 90 |
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
border-radius: 3px;
|
| 94 |
-
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
display: flex;
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
}
|
| 107 |
|
| 108 |
/* ββ Sidebar βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
@@ -189,6 +1493,45 @@ body {
|
|
| 189 |
margin-bottom: 0.8rem;
|
| 190 |
}
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
/* ββ Grids βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 193 |
.grid-4 {
|
| 194 |
display: grid;
|
|
@@ -1073,6 +2416,78 @@ body {
|
|
| 1073 |
padding: 0.25rem 0 2rem;
|
| 1074 |
}
|
| 1075 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
.action-btn {
|
| 1077 |
padding: 0.6rem 1.4rem;
|
| 1078 |
border-radius: var(--radius);
|
|
@@ -1097,6 +2512,80 @@ body {
|
|
| 1097 |
}
|
| 1098 |
|
| 1099 |
/* ββ Dashboard βββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1100 |
.metric-value.dashboard {
|
| 1101 |
font-size: 1.7rem;
|
| 1102 |
font-weight: 600;
|
|
|
|
| 56 |
--slow: 380ms;
|
| 57 |
}
|
| 58 |
|
| 59 |
+
[data-theme="dark"] {
|
| 60 |
+
--bg: #121212;
|
| 61 |
+
--bg-card: #1A1A1A;
|
| 62 |
+
--bg-hover: #232323;
|
| 63 |
+
--bg-sidebar: #161616;
|
| 64 |
+
--bg-dark: #0B0B0B;
|
| 65 |
+
|
| 66 |
+
--border: #2B2B2B;
|
| 67 |
+
--border-mid: #3A3A3A;
|
| 68 |
+
--border-strong: #505050;
|
| 69 |
+
|
| 70 |
+
--accent: #c18a73;
|
| 71 |
+
--accent-light: #d4a18b;
|
| 72 |
+
--accent-soft: rgba(193, 138, 115, 0.15);
|
| 73 |
+
--accent-glow: rgba(193, 138, 115, 0.25);
|
| 74 |
+
|
| 75 |
+
--text: #ECE9E4;
|
| 76 |
+
--text-mid: #B4ADA4;
|
| 77 |
+
--text-dim: #8A847D;
|
| 78 |
+
--text-inverse: #121212;
|
| 79 |
+
|
| 80 |
+
--success: #61b88b;
|
| 81 |
+
--success-bg: #133120;
|
| 82 |
+
--success-bdr: #2f6e4f;
|
| 83 |
+
--fail: #d57777;
|
| 84 |
+
--fail-bg: #3f1b1b;
|
| 85 |
+
--fail-bdr: #754040;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
/* ββ Reset βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 89 |
*,
|
| 90 |
*::before,
|
|
|
|
| 94 |
padding: 0;
|
| 95 |
}
|
| 96 |
|
| 97 |
+
html {
|
| 98 |
+
font-size: 15px;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
body {
|
| 102 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 103 |
+
background: var(--bg);
|
| 104 |
+
color: var(--text);
|
| 105 |
+
line-height: 1.6;
|
| 106 |
+
-webkit-font-smoothing: antialiased;
|
| 107 |
+
-moz-osx-font-smoothing: grayscale;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/* ββ Scrollbar ββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 111 |
+
::-webkit-scrollbar {
|
| 112 |
+
width: 5px;
|
| 113 |
+
height: 5px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
::-webkit-scrollbar-track {
|
| 117 |
+
background: transparent;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
::-webkit-scrollbar-thumb {
|
| 121 |
+
background: var(--border-mid);
|
| 122 |
+
border-radius: 3px;
|
| 123 |
+
transition: background var(--fast);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
::-webkit-scrollbar-thumb:hover {
|
| 127 |
+
background: var(--border-strong);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
/* ββ Layout βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 131 |
+
.app-container {
|
| 132 |
+
display: flex;
|
| 133 |
+
height: 100vh;
|
| 134 |
+
overflow: hidden;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
/* ββ New App Shell (Notion-style) βββββββββββββββββββββββββββββββββ */
|
| 138 |
+
.app-shell {
|
| 139 |
+
display: grid;
|
| 140 |
+
grid-template-columns: 260px 1fr;
|
| 141 |
+
min-height: 100vh;
|
| 142 |
+
background: var(--bg);
|
| 143 |
+
color: var(--text);
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.app-sidebar {
|
| 147 |
+
background: var(--bg-sidebar);
|
| 148 |
+
border-right: 1px solid var(--border);
|
| 149 |
+
padding: 0.85rem;
|
| 150 |
+
display: flex;
|
| 151 |
+
flex-direction: column;
|
| 152 |
+
gap: 0.9rem;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.app-brand {
|
| 156 |
+
display: flex;
|
| 157 |
+
align-items: center;
|
| 158 |
+
gap: 0.65rem;
|
| 159 |
+
padding: 0.5rem;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.app-brand-logo {
|
| 163 |
+
width: 30px;
|
| 164 |
+
height: 30px;
|
| 165 |
+
border-radius: 8px;
|
| 166 |
+
background: var(--accent-soft);
|
| 167 |
+
color: var(--accent);
|
| 168 |
+
display: grid;
|
| 169 |
+
place-items: center;
|
| 170 |
+
font-weight: 700;
|
| 171 |
+
border: 1px solid var(--border);
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
.app-brand-title {
|
| 175 |
+
font-size: 0.95rem;
|
| 176 |
+
font-weight: 700;
|
| 177 |
+
line-height: 1.2;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.app-brand-sub {
|
| 181 |
+
font-size: 0.75rem;
|
| 182 |
+
color: var(--text-dim);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.app-sidebar-group {
|
| 186 |
+
padding: 0 0.45rem;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.app-sidebar-label {
|
| 190 |
+
font-size: 0.72rem;
|
| 191 |
+
letter-spacing: 0.08em;
|
| 192 |
+
text-transform: uppercase;
|
| 193 |
+
color: var(--text-dim);
|
| 194 |
+
margin-bottom: 0.35rem;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.app-design-select {
|
| 198 |
+
width: 100%;
|
| 199 |
+
border: 1px solid var(--border);
|
| 200 |
+
border-radius: var(--radius);
|
| 201 |
+
background: var(--bg-card);
|
| 202 |
+
color: var(--text);
|
| 203 |
+
padding: 0.5rem 0.6rem;
|
| 204 |
+
font-family: inherit;
|
| 205 |
+
font-size: 0.84rem;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.app-nav {
|
| 209 |
+
display: flex;
|
| 210 |
+
flex-direction: column;
|
| 211 |
+
gap: 0.2rem;
|
| 212 |
+
margin-top: 0.3rem;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.app-nav-btn {
|
| 216 |
+
width: 100%;
|
| 217 |
+
border: none;
|
| 218 |
+
border-radius: var(--radius);
|
| 219 |
+
background: transparent;
|
| 220 |
+
color: var(--text-mid);
|
| 221 |
+
display: flex;
|
| 222 |
+
align-items: center;
|
| 223 |
+
gap: 0.55rem;
|
| 224 |
+
padding: 0.5rem 0.6rem;
|
| 225 |
+
font-size: 0.85rem;
|
| 226 |
+
cursor: pointer;
|
| 227 |
+
text-align: left;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.app-nav-btn:hover {
|
| 231 |
+
background: var(--bg-hover);
|
| 232 |
+
color: var(--text);
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.app-nav-btn.active {
|
| 236 |
+
background: var(--accent-soft);
|
| 237 |
+
color: var(--accent);
|
| 238 |
+
font-weight: 600;
|
| 239 |
+
border: 1px solid var(--border);
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.app-sidebar-footer {
|
| 243 |
+
margin-top: auto;
|
| 244 |
+
padding: 0.5rem;
|
| 245 |
+
display: flex;
|
| 246 |
+
flex-direction: column;
|
| 247 |
+
gap: 0.5rem;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.theme-toggle {
|
| 251 |
+
border: 1px solid var(--border);
|
| 252 |
+
border-radius: var(--radius);
|
| 253 |
+
background: var(--bg-card);
|
| 254 |
+
color: var(--text-mid);
|
| 255 |
+
padding: 0.45rem 0.55rem;
|
| 256 |
+
font-size: 0.8rem;
|
| 257 |
+
cursor: pointer;
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
.theme-toggle:hover {
|
| 261 |
+
color: var(--text);
|
| 262 |
+
border-color: var(--border-strong);
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.app-version {
|
| 266 |
+
font-size: 0.72rem;
|
| 267 |
+
color: var(--text-dim);
|
| 268 |
+
text-align: center;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.app-main {
|
| 272 |
+
min-width: 0;
|
| 273 |
+
display: flex;
|
| 274 |
+
flex-direction: column;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.app-topbar {
|
| 278 |
+
height: 56px;
|
| 279 |
+
border-bottom: 1px solid var(--border);
|
| 280 |
+
background: color-mix(in srgb, var(--bg-card) 90%, transparent);
|
| 281 |
+
display: flex;
|
| 282 |
+
align-items: center;
|
| 283 |
+
justify-content: space-between;
|
| 284 |
+
padding: 0 1rem 0 1.25rem;
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
.app-topbar h1 {
|
| 288 |
+
font-size: 1rem;
|
| 289 |
+
font-weight: 650;
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
.app-topbar-meta {
|
| 293 |
+
color: var(--text-dim);
|
| 294 |
+
font-size: 0.8rem;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.app-content {
|
| 298 |
+
padding: 0;
|
| 299 |
+
min-height: calc(100vh - 56px);
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
.home-overview {
|
| 303 |
+
padding: 1.5rem;
|
| 304 |
+
max-width: 1080px;
|
| 305 |
+
display: flex;
|
| 306 |
+
flex-direction: column;
|
| 307 |
+
gap: 1.5rem;
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
.home-hero {
|
| 311 |
+
background: linear-gradient(135deg, var(--accent-soft) 0%, var(--bg-card) 50%, var(--success-bg) 100%);
|
| 312 |
+
border: 1px solid var(--border);
|
| 313 |
+
border-radius: var(--radius-lg);
|
| 314 |
+
padding: 2.5rem 2rem 2rem;
|
| 315 |
+
text-align: center;
|
| 316 |
+
position: relative;
|
| 317 |
+
overflow: hidden;
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.home-hero::before {
|
| 321 |
+
content: '';
|
| 322 |
+
position: absolute;
|
| 323 |
+
top: -50%;
|
| 324 |
+
right: -30%;
|
| 325 |
+
width: 300px;
|
| 326 |
+
height: 300px;
|
| 327 |
+
border-radius: 50%;
|
| 328 |
+
background: var(--accent-glow);
|
| 329 |
+
filter: blur(80px);
|
| 330 |
+
pointer-events: none;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
.home-hero-badge {
|
| 334 |
+
display: inline-block;
|
| 335 |
+
background: var(--accent);
|
| 336 |
+
color: var(--text-inverse);
|
| 337 |
+
font-size: 0.72rem;
|
| 338 |
+
font-weight: 600;
|
| 339 |
+
letter-spacing: 0.1em;
|
| 340 |
+
text-transform: uppercase;
|
| 341 |
+
padding: 0.25rem 0.75rem;
|
| 342 |
+
border-radius: 100px;
|
| 343 |
+
margin-bottom: 0.75rem;
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
.home-hero-title {
|
| 347 |
+
font-size: 1.6rem;
|
| 348 |
+
font-weight: 700;
|
| 349 |
+
line-height: 1.25;
|
| 350 |
+
margin-bottom: 0.6rem;
|
| 351 |
+
position: relative;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.home-hero-desc {
|
| 355 |
+
color: var(--text-mid);
|
| 356 |
+
font-size: 0.88rem;
|
| 357 |
+
line-height: 1.65;
|
| 358 |
+
max-width: 600px;
|
| 359 |
+
margin: 0 auto;
|
| 360 |
+
position: relative;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.home-card {
|
| 364 |
+
border: 1px solid var(--border);
|
| 365 |
+
background: var(--bg-card);
|
| 366 |
+
border-radius: var(--radius-md);
|
| 367 |
+
padding: 1rem;
|
| 368 |
+
box-shadow: var(--shadow-xs);
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.home-card h3 {
|
| 372 |
+
margin-bottom: 0.35rem;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
.home-card p {
|
| 376 |
+
color: var(--text-mid);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.home-card-grid {
|
| 380 |
+
display: grid;
|
| 381 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 382 |
+
gap: 0.8rem;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
.home-kpi {
|
| 386 |
+
border: 1px solid var(--border);
|
| 387 |
+
border-radius: var(--radius);
|
| 388 |
+
background: var(--bg-card);
|
| 389 |
+
padding: 1rem 0.8rem;
|
| 390 |
+
font-size: 1.35rem;
|
| 391 |
+
font-weight: 700;
|
| 392 |
+
text-align: center;
|
| 393 |
+
color: var(--accent);
|
| 394 |
+
transition: transform var(--fast), box-shadow var(--fast);
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
.home-kpi:hover {
|
| 398 |
+
transform: translateY(-2px);
|
| 399 |
+
box-shadow: var(--shadow-sm);
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.home-kpi span {
|
| 403 |
+
display: block;
|
| 404 |
+
font-size: 0.72rem;
|
| 405 |
+
color: var(--text-dim);
|
| 406 |
+
margin-top: 0.3rem;
|
| 407 |
+
font-weight: 500;
|
| 408 |
+
letter-spacing: 0.03em;
|
| 409 |
+
text-transform: uppercase;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
.home-section {
|
| 413 |
+
border: 1px solid var(--border);
|
| 414 |
+
background: var(--bg-card);
|
| 415 |
+
border-radius: var(--radius-md);
|
| 416 |
+
padding: 1.25rem;
|
| 417 |
+
box-shadow: var(--shadow-xs);
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
.home-section-title {
|
| 421 |
+
font-size: 0.92rem;
|
| 422 |
+
font-weight: 650;
|
| 423 |
+
margin-bottom: 1rem;
|
| 424 |
+
color: var(--text);
|
| 425 |
+
letter-spacing: -0.01em;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
/* Agent grid */
|
| 429 |
+
.home-agent-grid {
|
| 430 |
+
display: grid;
|
| 431 |
+
grid-template-columns: repeat(3, 1fr);
|
| 432 |
+
gap: 0.65rem;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.agent-card {
|
| 436 |
+
border: 1px solid var(--border);
|
| 437 |
+
border-radius: var(--radius);
|
| 438 |
+
padding: 0.85rem;
|
| 439 |
+
background: var(--bg);
|
| 440 |
+
transition: all var(--fast);
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.agent-card:hover {
|
| 444 |
+
border-color: var(--accent);
|
| 445 |
+
background: var(--accent-soft);
|
| 446 |
+
transform: translateY(-1px);
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.agent-icon {
|
| 450 |
+
font-size: 1.3rem;
|
| 451 |
+
margin-bottom: 0.3rem;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.agent-name {
|
| 455 |
+
font-size: 0.8rem;
|
| 456 |
+
font-weight: 600;
|
| 457 |
+
margin-bottom: 0.15rem;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.agent-desc {
|
| 461 |
+
font-size: 0.72rem;
|
| 462 |
+
color: var(--text-dim);
|
| 463 |
+
line-height: 1.4;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
/* Pipeline flow */
|
| 467 |
+
.pipeline-flow {
|
| 468 |
+
display: flex;
|
| 469 |
+
flex-wrap: wrap;
|
| 470 |
+
gap: 0;
|
| 471 |
+
align-items: flex-start;
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
.pipeline-stage {
|
| 475 |
+
display: flex;
|
| 476 |
+
flex-direction: column;
|
| 477 |
+
align-items: center;
|
| 478 |
+
padding: 0.4rem 0.2rem;
|
| 479 |
+
flex: 1;
|
| 480 |
+
min-width: 80px;
|
| 481 |
+
position: relative;
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
.pipeline-stage-icon {
|
| 485 |
+
font-size: 1.2rem;
|
| 486 |
+
margin-bottom: 0.25rem;
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
.pipeline-stage-label {
|
| 490 |
+
font-size: 0.68rem;
|
| 491 |
+
font-weight: 650;
|
| 492 |
+
letter-spacing: 0.02em;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
.pipeline-stage-sub {
|
| 496 |
+
font-size: 0.62rem;
|
| 497 |
+
color: var(--text-dim);
|
| 498 |
+
margin-top: 0.1rem;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.pipeline-arrow {
|
| 502 |
+
position: absolute;
|
| 503 |
+
right: -8px;
|
| 504 |
+
top: 50%;
|
| 505 |
+
transform: translateY(-50%);
|
| 506 |
+
color: var(--border-mid);
|
| 507 |
+
font-size: 0.85rem;
|
| 508 |
+
font-weight: 300;
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
/* Quick start */
|
| 512 |
+
.home-quickstart {
|
| 513 |
+
display: flex;
|
| 514 |
+
flex-direction: column;
|
| 515 |
+
gap: 0.6rem;
|
| 516 |
+
margin-bottom: 1rem;
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
.quickstart-step {
|
| 520 |
+
display: flex;
|
| 521 |
+
align-items: center;
|
| 522 |
+
gap: 0.75rem;
|
| 523 |
+
font-size: 0.85rem;
|
| 524 |
+
color: var(--text-mid);
|
| 525 |
+
}
|
| 526 |
+
|
| 527 |
+
.quickstart-num {
|
| 528 |
+
width: 24px;
|
| 529 |
+
height: 24px;
|
| 530 |
+
border-radius: 50%;
|
| 531 |
+
background: var(--accent-soft);
|
| 532 |
+
color: var(--accent);
|
| 533 |
+
font-weight: 700;
|
| 534 |
+
font-size: 0.75rem;
|
| 535 |
+
display: grid;
|
| 536 |
+
place-items: center;
|
| 537 |
+
flex-shrink: 0;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.home-cta {
|
| 541 |
+
margin-top: 0.5rem;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
/* ββ Academic Documentation System ββββββββββββββββββββββββββββββββ */
|
| 545 |
+
.adoc-root {
|
| 546 |
+
min-height: calc(100vh - 56px);
|
| 547 |
+
background: var(--bg);
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
/* Hero */
|
| 551 |
+
.adoc-hero {
|
| 552 |
+
background: linear-gradient(135deg, var(--bg-card) 0%, var(--bg) 100%);
|
| 553 |
+
border-bottom: 1px solid var(--border);
|
| 554 |
+
padding: 2.5rem 2rem 2rem;
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
.adoc-hero-inner {
|
| 558 |
+
max-width: 900px;
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
.adoc-hero-badge {
|
| 562 |
+
display: inline-block;
|
| 563 |
+
font-size: 0.68rem;
|
| 564 |
+
font-weight: 700;
|
| 565 |
+
letter-spacing: 0.12em;
|
| 566 |
+
text-transform: uppercase;
|
| 567 |
+
color: var(--accent);
|
| 568 |
+
background: var(--accent-soft);
|
| 569 |
+
border: 1px solid var(--accent);
|
| 570 |
+
border-radius: 4px;
|
| 571 |
+
padding: 0.18rem 0.55rem;
|
| 572 |
+
margin-bottom: 0.65rem;
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
.adoc-hero-title {
|
| 576 |
+
font-size: 1.75rem;
|
| 577 |
+
font-weight: 700;
|
| 578 |
+
letter-spacing: -0.025em;
|
| 579 |
+
color: var(--text);
|
| 580 |
+
margin-bottom: 0.35rem;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.adoc-hero-sub {
|
| 584 |
+
color: var(--text-mid);
|
| 585 |
+
font-size: 0.9rem;
|
| 586 |
+
line-height: 1.6;
|
| 587 |
+
max-width: 680px;
|
| 588 |
+
margin-bottom: 0.65rem;
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
.adoc-hero-stats {
|
| 592 |
+
font-size: 0.78rem;
|
| 593 |
+
color: var(--text-dim);
|
| 594 |
+
display: flex;
|
| 595 |
+
align-items: center;
|
| 596 |
+
gap: 0.5rem;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
.adoc-hero-dot {
|
| 600 |
+
color: var(--border-strong);
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
/* Tab bar */
|
| 604 |
+
.adoc-tabs {
|
| 605 |
+
display: flex;
|
| 606 |
+
gap: 0;
|
| 607 |
+
border-bottom: 1px solid var(--border);
|
| 608 |
+
background: var(--bg-card);
|
| 609 |
+
padding: 0 1.5rem;
|
| 610 |
+
position: sticky;
|
| 611 |
+
top: 0;
|
| 612 |
+
z-index: 10;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
.adoc-tab {
|
| 616 |
+
border: none;
|
| 617 |
+
background: transparent;
|
| 618 |
+
color: var(--text-mid);
|
| 619 |
+
font-family: inherit;
|
| 620 |
+
font-size: 0.82rem;
|
| 621 |
+
font-weight: 500;
|
| 622 |
+
padding: 0.7rem 1.1rem;
|
| 623 |
+
cursor: pointer;
|
| 624 |
+
display: flex;
|
| 625 |
+
align-items: center;
|
| 626 |
+
gap: 0.4rem;
|
| 627 |
+
border-bottom: 2px solid transparent;
|
| 628 |
+
margin-bottom: -1px;
|
| 629 |
+
transition: color var(--fast), border-color var(--fast);
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.adoc-tab:hover {
|
| 633 |
+
color: var(--text);
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
.adoc-tab.active {
|
| 637 |
+
color: var(--accent);
|
| 638 |
+
border-bottom-color: var(--accent);
|
| 639 |
+
font-weight: 600;
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
.adoc-tab-icon {
|
| 643 |
+
font-size: 0.88rem;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
/* Section wrapper */
|
| 647 |
+
.adoc-section {
|
| 648 |
+
padding: 1.5rem 2rem;
|
| 649 |
+
max-width: 1100px;
|
| 650 |
+
display: flex;
|
| 651 |
+
flex-direction: column;
|
| 652 |
+
gap: 1.25rem;
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
/* Paper card */
|
| 656 |
+
.adoc-paper-card {
|
| 657 |
+
background: var(--bg-card);
|
| 658 |
+
border: 1px solid var(--border);
|
| 659 |
+
border-radius: var(--radius-md);
|
| 660 |
+
padding: 1.5rem 1.75rem;
|
| 661 |
+
box-shadow: var(--shadow-sm);
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.adoc-paper-card h2 {
|
| 665 |
+
font-size: 1.08rem;
|
| 666 |
+
font-weight: 700;
|
| 667 |
+
color: var(--text);
|
| 668 |
+
margin-bottom: 0.5rem;
|
| 669 |
+
padding-bottom: 0.4rem;
|
| 670 |
+
border-bottom: 1px solid var(--border);
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
.adoc-paper-card h3 {
|
| 674 |
+
font-size: 0.92rem;
|
| 675 |
+
font-weight: 650;
|
| 676 |
+
color: var(--text);
|
| 677 |
+
margin-bottom: 0.4rem;
|
| 678 |
+
}
|
| 679 |
+
|
| 680 |
+
.adoc-paper-card p {
|
| 681 |
+
color: var(--text-mid);
|
| 682 |
+
font-size: 0.86rem;
|
| 683 |
+
line-height: 1.7;
|
| 684 |
+
margin-bottom: 0.5rem;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
.adoc-full-width {
|
| 688 |
+
max-width: 100%;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
.adoc-meta-text {
|
| 692 |
+
color: var(--text-dim);
|
| 693 |
+
font-size: 0.82rem;
|
| 694 |
+
line-height: 1.6;
|
| 695 |
+
margin-bottom: 0.7rem;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
/* Abstract */
|
| 699 |
+
.adoc-abstract {
|
| 700 |
+
border-left: 3px solid var(--accent);
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
/* Capabilities grid */
|
| 704 |
+
.adoc-cap-grid {
|
| 705 |
+
display: grid;
|
| 706 |
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
| 707 |
+
gap: 0.8rem;
|
| 708 |
+
margin-top: 0.4rem;
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
.adoc-cap-item {
|
| 712 |
+
display: flex;
|
| 713 |
+
gap: 0.75rem;
|
| 714 |
+
align-items: flex-start;
|
| 715 |
+
padding: 0.65rem;
|
| 716 |
+
border: 1px solid var(--border);
|
| 717 |
+
border-radius: var(--radius);
|
| 718 |
+
background: var(--bg);
|
| 719 |
+
transition: border-color var(--fast);
|
| 720 |
+
}
|
| 721 |
+
|
| 722 |
+
.adoc-cap-item:hover {
|
| 723 |
+
border-color: var(--border-strong);
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
.adoc-cap-icon {
|
| 727 |
+
font-size: 1.15rem;
|
| 728 |
+
flex-shrink: 0;
|
| 729 |
+
margin-top: 0.15rem;
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
.adoc-cap-item strong {
|
| 733 |
+
display: block;
|
| 734 |
+
font-size: 0.82rem;
|
| 735 |
+
color: var(--text);
|
| 736 |
+
margin-bottom: 0.15rem;
|
| 737 |
+
}
|
| 738 |
+
|
| 739 |
+
.adoc-cap-item p {
|
| 740 |
+
font-size: 0.78rem;
|
| 741 |
+
color: var(--text-dim);
|
| 742 |
+
margin: 0;
|
| 743 |
+
line-height: 1.55;
|
| 744 |
+
}
|
| 745 |
+
|
| 746 |
+
/* Academic table */
|
| 747 |
+
.adoc-table-wrap {
|
| 748 |
+
overflow-x: auto;
|
| 749 |
+
border-radius: var(--radius);
|
| 750 |
+
border: 1px solid var(--border);
|
| 751 |
+
margin-top: 0.5rem;
|
| 752 |
+
}
|
| 753 |
+
|
| 754 |
+
.adoc-table {
|
| 755 |
+
width: 100%;
|
| 756 |
+
border-collapse: collapse;
|
| 757 |
+
font-size: 0.82rem;
|
| 758 |
+
}
|
| 759 |
+
|
| 760 |
+
.adoc-table thead {
|
| 761 |
+
background: var(--bg);
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
.adoc-table th {
|
| 765 |
+
padding: 0.55rem 0.75rem;
|
| 766 |
+
text-align: left;
|
| 767 |
+
font-weight: 650;
|
| 768 |
+
color: var(--text-mid);
|
| 769 |
+
font-size: 0.74rem;
|
| 770 |
+
letter-spacing: 0.04em;
|
| 771 |
+
text-transform: uppercase;
|
| 772 |
+
border-bottom: 1px solid var(--border);
|
| 773 |
+
white-space: nowrap;
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
.adoc-table td {
|
| 777 |
+
padding: 0.5rem 0.75rem;
|
| 778 |
+
border-bottom: 1px solid var(--border);
|
| 779 |
+
color: var(--text);
|
| 780 |
+
vertical-align: top;
|
| 781 |
+
line-height: 1.55;
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
.adoc-table tr:last-child td {
|
| 785 |
+
border-bottom: none;
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
.adoc-table tr:hover td {
|
| 789 |
+
background: var(--bg-hover);
|
| 790 |
+
}
|
| 791 |
+
|
| 792 |
+
.adoc-table code {
|
| 793 |
+
font-family: 'Fira Code', monospace;
|
| 794 |
+
font-size: 0.78rem;
|
| 795 |
+
background: var(--bg);
|
| 796 |
+
padding: 0.1rem 0.35rem;
|
| 797 |
+
border-radius: 3px;
|
| 798 |
+
border: 1px solid var(--border);
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
/* Pipeline stage list */
|
| 802 |
+
.adoc-pipeline-list {
|
| 803 |
+
display: flex;
|
| 804 |
+
flex-direction: column;
|
| 805 |
+
gap: 0;
|
| 806 |
+
margin-top: 0.5rem;
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
.adoc-pipeline-stage {
|
| 810 |
+
display: grid;
|
| 811 |
+
grid-template-columns: 28px 12px 1fr;
|
| 812 |
+
gap: 0.5rem;
|
| 813 |
+
align-items: start;
|
| 814 |
+
padding: 0.65rem 0;
|
| 815 |
+
}
|
| 816 |
+
|
| 817 |
+
.adoc-stage-num {
|
| 818 |
+
font-family: 'Fira Code', monospace;
|
| 819 |
+
font-size: 0.72rem;
|
| 820 |
+
font-weight: 700;
|
| 821 |
+
color: var(--accent);
|
| 822 |
+
text-align: right;
|
| 823 |
+
padding-top: 0.15rem;
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
.adoc-stage-connector {
|
| 827 |
+
width: 2px;
|
| 828 |
+
height: 100%;
|
| 829 |
+
min-height: 32px;
|
| 830 |
+
background: var(--border);
|
| 831 |
+
margin: 0 auto;
|
| 832 |
+
border-radius: 1px;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
.adoc-stage-body {
|
| 836 |
+
border: 1px solid var(--border);
|
| 837 |
+
border-radius: var(--radius);
|
| 838 |
+
padding: 0.6rem 0.85rem;
|
| 839 |
+
background: var(--bg);
|
| 840 |
+
transition: border-color var(--fast), box-shadow var(--fast);
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
.adoc-stage-body:hover {
|
| 844 |
+
border-color: var(--border-strong);
|
| 845 |
+
box-shadow: var(--shadow-xs);
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
.adoc-stage-header {
|
| 849 |
+
display: flex;
|
| 850 |
+
align-items: center;
|
| 851 |
+
gap: 0.45rem;
|
| 852 |
+
margin-bottom: 0.25rem;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
.adoc-stage-header strong {
|
| 856 |
+
font-size: 0.84rem;
|
| 857 |
+
color: var(--text);
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
.adoc-stage-icon {
|
| 861 |
+
font-size: 0.9rem;
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
.adoc-stage-key {
|
| 865 |
+
font-family: 'Fira Code', monospace;
|
| 866 |
+
font-size: 0.68rem;
|
| 867 |
+
background: var(--accent-soft);
|
| 868 |
+
color: var(--accent);
|
| 869 |
+
padding: 0.1rem 0.4rem;
|
| 870 |
+
border-radius: 3px;
|
| 871 |
+
border: 1px solid rgba(201, 100, 62, 0.2);
|
| 872 |
+
margin-left: auto;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
.adoc-stage-desc {
|
| 876 |
+
font-size: 0.78rem;
|
| 877 |
+
color: var(--text-dim);
|
| 878 |
+
line-height: 1.55;
|
| 879 |
+
margin: 0;
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
/* Flow diagram */
|
| 883 |
+
.adoc-flow-diagram {
|
| 884 |
+
display: flex;
|
| 885 |
+
flex-wrap: wrap;
|
| 886 |
+
gap: 0.2rem;
|
| 887 |
+
align-items: center;
|
| 888 |
+
padding: 1rem 0;
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
.adoc-flow-node {
|
| 892 |
+
display: inline-flex;
|
| 893 |
+
align-items: center;
|
| 894 |
+
gap: 0.3rem;
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
.adoc-flow-badge {
|
| 898 |
+
font-size: 0.82rem;
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
.adoc-flow-label {
|
| 902 |
+
font-family: 'Fira Code', monospace;
|
| 903 |
+
font-size: 0.68rem;
|
| 904 |
+
color: var(--text);
|
| 905 |
+
background: var(--bg);
|
| 906 |
+
border: 1px solid var(--border);
|
| 907 |
+
border-radius: 4px;
|
| 908 |
+
padding: 0.15rem 0.4rem;
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
.adoc-flow-arrow {
|
| 912 |
+
color: var(--text-dim);
|
| 913 |
+
font-size: 0.78rem;
|
| 914 |
+
margin: 0 0.15rem;
|
| 915 |
+
}
|
| 916 |
+
|
| 917 |
+
/* Config tab */
|
| 918 |
+
.adoc-config-header {
|
| 919 |
+
display: flex;
|
| 920 |
+
align-items: center;
|
| 921 |
+
justify-content: space-between;
|
| 922 |
+
gap: 1rem;
|
| 923 |
+
padding: 1.5rem 2rem 0.5rem;
|
| 924 |
+
max-width: 1100px;
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
.adoc-config-header h2 {
|
| 928 |
+
font-size: 1.1rem;
|
| 929 |
+
font-weight: 700;
|
| 930 |
+
margin: 0;
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
.adoc-search {
|
| 934 |
+
border: 1px solid var(--border);
|
| 935 |
+
border-radius: var(--radius);
|
| 936 |
+
background: var(--bg-card);
|
| 937 |
+
color: var(--text);
|
| 938 |
+
padding: 0.45rem 0.75rem;
|
| 939 |
+
font-family: inherit;
|
| 940 |
+
font-size: 0.82rem;
|
| 941 |
+
width: 260px;
|
| 942 |
+
outline: none;
|
| 943 |
+
transition: border-color var(--fast), box-shadow var(--fast);
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
.adoc-search:focus {
|
| 947 |
+
border-color: var(--accent);
|
| 948 |
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
.adoc-config-group {
|
| 952 |
+
margin: 0 2rem;
|
| 953 |
+
max-width: calc(1100px - 4rem);
|
| 954 |
+
}
|
| 955 |
+
|
| 956 |
+
.adoc-config-group + .adoc-config-group {
|
| 957 |
+
margin-top: 1rem;
|
| 958 |
+
}
|
| 959 |
+
|
| 960 |
+
.adoc-config-group:last-child {
|
| 961 |
+
margin-bottom: 2rem;
|
| 962 |
+
}
|
| 963 |
+
|
| 964 |
+
.adoc-config-group-title {
|
| 965 |
+
font-size: 0.9rem;
|
| 966 |
+
font-weight: 700;
|
| 967 |
+
color: var(--accent);
|
| 968 |
+
text-transform: uppercase;
|
| 969 |
+
letter-spacing: 0.08em;
|
| 970 |
+
margin-bottom: 0.6rem;
|
| 971 |
+
padding-bottom: 0.3rem;
|
| 972 |
+
border-bottom: 1px solid var(--border);
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
.adoc-config-table td {
|
| 976 |
+
font-size: 0.8rem;
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
.adoc-param-key {
|
| 980 |
+
font-family: 'Fira Code', monospace;
|
| 981 |
+
font-weight: 600;
|
| 982 |
+
color: var(--text) !important;
|
| 983 |
+
background: transparent !important;
|
| 984 |
+
border: none !important;
|
| 985 |
+
padding: 0 !important;
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
.adoc-type-badge {
|
| 989 |
+
font-size: 0.7rem;
|
| 990 |
+
font-weight: 600;
|
| 991 |
+
color: var(--accent);
|
| 992 |
+
background: var(--accent-soft);
|
| 993 |
+
padding: 0.12rem 0.4rem;
|
| 994 |
+
border-radius: 3px;
|
| 995 |
+
white-space: nowrap;
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
.adoc-enum-val {
|
| 999 |
+
display: inline-block;
|
| 1000 |
+
font-family: 'Fira Code', monospace;
|
| 1001 |
+
font-size: 0.72rem;
|
| 1002 |
+
background: var(--bg);
|
| 1003 |
+
border: 1px solid var(--border);
|
| 1004 |
+
border-radius: 3px;
|
| 1005 |
+
padding: 0.08rem 0.3rem;
|
| 1006 |
+
margin-right: 0.25rem;
|
| 1007 |
+
margin-bottom: 0.15rem;
|
| 1008 |
+
}
|
| 1009 |
+
|
| 1010 |
+
/* Docs reader layout (3-column) */
|
| 1011 |
+
.adoc-docs-layout {
|
| 1012 |
+
display: grid;
|
| 1013 |
+
grid-template-columns: 240px 1fr 200px;
|
| 1014 |
+
min-height: calc(100vh - 200px);
|
| 1015 |
+
gap: 0;
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
.adoc-docs-nav {
|
| 1019 |
+
border-right: 1px solid var(--border);
|
| 1020 |
+
background: var(--bg-card);
|
| 1021 |
+
padding: 1rem 0.75rem;
|
| 1022 |
+
overflow-y: auto;
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
.adoc-docs-nav-title {
|
| 1026 |
+
font-size: 0.78rem;
|
| 1027 |
+
font-weight: 700;
|
| 1028 |
+
text-transform: uppercase;
|
| 1029 |
+
letter-spacing: 0.1em;
|
| 1030 |
+
color: var(--text-dim);
|
| 1031 |
+
margin-bottom: 0.8rem;
|
| 1032 |
+
padding-left: 0.35rem;
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
.adoc-docs-group {
|
| 1036 |
+
margin-bottom: 1rem;
|
| 1037 |
+
}
|
| 1038 |
+
|
| 1039 |
+
.adoc-docs-section-label {
|
| 1040 |
+
font-size: 0.7rem;
|
| 1041 |
+
font-weight: 700;
|
| 1042 |
+
color: var(--accent);
|
| 1043 |
+
text-transform: uppercase;
|
| 1044 |
+
letter-spacing: 0.08em;
|
| 1045 |
+
padding: 0.2rem 0.35rem;
|
| 1046 |
+
margin-bottom: 0.25rem;
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
.adoc-docs-link {
|
| 1050 |
+
display: block;
|
| 1051 |
+
width: 100%;
|
| 1052 |
+
text-align: left;
|
| 1053 |
+
border: 1px solid transparent;
|
| 1054 |
+
border-radius: var(--radius);
|
| 1055 |
+
background: transparent;
|
| 1056 |
+
padding: 0.45rem 0.5rem;
|
| 1057 |
+
cursor: pointer;
|
| 1058 |
+
transition: all var(--fast);
|
| 1059 |
+
margin-bottom: 0.15rem;
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
.adoc-docs-link:hover {
|
| 1063 |
+
background: var(--bg-hover);
|
| 1064 |
+
border-color: var(--border);
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
.adoc-docs-link.active {
|
| 1068 |
+
background: var(--accent-soft);
|
| 1069 |
+
border-color: var(--accent);
|
| 1070 |
+
}
|
| 1071 |
+
|
| 1072 |
+
.adoc-docs-link-title {
|
| 1073 |
+
font-size: 0.8rem;
|
| 1074 |
+
font-weight: 600;
|
| 1075 |
+
color: var(--text);
|
| 1076 |
+
}
|
| 1077 |
+
|
| 1078 |
+
.adoc-docs-link.active .adoc-docs-link-title {
|
| 1079 |
+
color: var(--accent);
|
| 1080 |
+
}
|
| 1081 |
+
|
| 1082 |
+
.adoc-docs-link-sub {
|
| 1083 |
+
display: block;
|
| 1084 |
+
font-size: 0.7rem;
|
| 1085 |
+
color: var(--text-dim);
|
| 1086 |
+
margin-top: 0.1rem;
|
| 1087 |
+
line-height: 1.4;
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
.adoc-docs-content {
|
| 1091 |
+
padding: 1.75rem 2.5rem 3rem;
|
| 1092 |
+
overflow-y: auto;
|
| 1093 |
+
background: var(--bg);
|
| 1094 |
+
}
|
| 1095 |
+
|
| 1096 |
+
.adoc-loading {
|
| 1097 |
+
display: flex;
|
| 1098 |
+
align-items: center;
|
| 1099 |
+
gap: 0.6rem;
|
| 1100 |
+
color: var(--text-mid);
|
| 1101 |
+
padding: 2rem 0;
|
| 1102 |
+
font-size: 0.85rem;
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
/* TOC sidebar */
|
| 1106 |
+
.adoc-toc {
|
| 1107 |
+
border-left: 1px solid var(--border);
|
| 1108 |
+
background: var(--bg-card);
|
| 1109 |
+
padding: 1rem 0.5rem;
|
| 1110 |
+
overflow-y: auto;
|
| 1111 |
+
position: sticky;
|
| 1112 |
+
top: 44px;
|
| 1113 |
+
max-height: calc(100vh - 200px);
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
.adoc-toc-title {
|
| 1117 |
+
font-size: 0.7rem;
|
| 1118 |
+
font-weight: 700;
|
| 1119 |
+
text-transform: uppercase;
|
| 1120 |
+
letter-spacing: 0.1em;
|
| 1121 |
+
color: var(--text-dim);
|
| 1122 |
+
margin-bottom: 0.7rem;
|
| 1123 |
+
padding-left: 0.5rem;
|
| 1124 |
+
}
|
| 1125 |
+
|
| 1126 |
+
.adoc-toc-link {
|
| 1127 |
+
display: block;
|
| 1128 |
+
width: 100%;
|
| 1129 |
+
text-align: left;
|
| 1130 |
+
border: none;
|
| 1131 |
+
background: transparent;
|
| 1132 |
+
font-family: inherit;
|
| 1133 |
+
font-size: 0.72rem;
|
| 1134 |
+
color: var(--text-dim);
|
| 1135 |
+
padding: 0.22rem 0.5rem;
|
| 1136 |
+
cursor: pointer;
|
| 1137 |
+
border-radius: 3px;
|
| 1138 |
+
transition: color var(--fast), background var(--fast);
|
| 1139 |
+
line-height: 1.4;
|
| 1140 |
+
}
|
| 1141 |
+
|
| 1142 |
+
.adoc-toc-link:hover {
|
| 1143 |
+
color: var(--text);
|
| 1144 |
+
background: var(--bg-hover);
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
/* ββ Academic Prose (Markdown Renderer) βββββββββββββββββββββββββββ */
|
| 1148 |
+
.adoc-prose {
|
| 1149 |
+
font-size: 0.88rem;
|
| 1150 |
+
line-height: 1.75;
|
| 1151 |
+
color: var(--text);
|
| 1152 |
+
max-width: 760px;
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
.adoc-prose h1 {
|
| 1156 |
+
font-size: 1.6rem;
|
| 1157 |
+
font-weight: 700;
|
| 1158 |
+
letter-spacing: -0.02em;
|
| 1159 |
+
margin-top: 2rem;
|
| 1160 |
+
margin-bottom: 0.6rem;
|
| 1161 |
+
padding-bottom: 0.4rem;
|
| 1162 |
+
border-bottom: 2px solid var(--border);
|
| 1163 |
+
color: var(--text);
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
.adoc-prose h2 {
|
| 1167 |
+
font-size: 1.2rem;
|
| 1168 |
+
font-weight: 700;
|
| 1169 |
+
color: var(--text);
|
| 1170 |
+
margin-top: 1.75rem;
|
| 1171 |
+
margin-bottom: 0.5rem;
|
| 1172 |
+
padding-bottom: 0.3rem;
|
| 1173 |
+
border-bottom: 1px solid var(--border);
|
| 1174 |
+
}
|
| 1175 |
+
|
| 1176 |
+
.adoc-prose h3 {
|
| 1177 |
+
font-size: 1rem;
|
| 1178 |
+
font-weight: 650;
|
| 1179 |
+
color: var(--text);
|
| 1180 |
+
margin-top: 1.35rem;
|
| 1181 |
+
margin-bottom: 0.35rem;
|
| 1182 |
+
}
|
| 1183 |
+
|
| 1184 |
+
.adoc-prose h4 {
|
| 1185 |
+
font-size: 0.88rem;
|
| 1186 |
+
font-weight: 650;
|
| 1187 |
+
color: var(--text-mid);
|
| 1188 |
+
margin-top: 1rem;
|
| 1189 |
+
margin-bottom: 0.3rem;
|
| 1190 |
+
}
|
| 1191 |
+
|
| 1192 |
+
.adoc-prose p {
|
| 1193 |
+
margin-bottom: 0.75rem;
|
| 1194 |
+
color: var(--text);
|
| 1195 |
+
}
|
| 1196 |
+
|
| 1197 |
+
.adoc-prose ul,
|
| 1198 |
+
.adoc-prose ol {
|
| 1199 |
+
margin-bottom: 0.75rem;
|
| 1200 |
+
padding-left: 1.6rem;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
.adoc-prose li {
|
| 1204 |
+
margin-bottom: 0.3rem;
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
.adoc-prose li::marker {
|
| 1208 |
+
color: var(--text-dim);
|
| 1209 |
}
|
| 1210 |
|
| 1211 |
+
.adoc-prose strong {
|
| 1212 |
+
font-weight: 650;
|
|
|
|
| 1213 |
color: var(--text);
|
|
|
|
|
|
|
|
|
|
| 1214 |
}
|
| 1215 |
|
| 1216 |
+
.adoc-prose em {
|
| 1217 |
+
font-style: italic;
|
|
|
|
|
|
|
| 1218 |
}
|
| 1219 |
|
| 1220 |
+
.adoc-prose a {
|
| 1221 |
+
color: var(--accent);
|
| 1222 |
+
text-decoration: none;
|
| 1223 |
+
border-bottom: 1px solid var(--accent-soft);
|
| 1224 |
+
transition: border-color var(--fast);
|
| 1225 |
}
|
| 1226 |
|
| 1227 |
+
.adoc-prose a:hover {
|
| 1228 |
+
border-color: var(--accent);
|
| 1229 |
+
}
|
| 1230 |
+
|
| 1231 |
+
.adoc-prose blockquote {
|
| 1232 |
+
border-left: 3px solid var(--accent);
|
| 1233 |
+
margin: 0.75rem 0;
|
| 1234 |
+
padding: 0.5rem 1rem;
|
| 1235 |
+
background: var(--accent-soft);
|
| 1236 |
+
border-radius: 0 var(--radius) var(--radius) 0;
|
| 1237 |
+
color: var(--text);
|
| 1238 |
+
}
|
| 1239 |
+
|
| 1240 |
+
.adoc-prose blockquote p {
|
| 1241 |
+
margin-bottom: 0;
|
| 1242 |
+
}
|
| 1243 |
+
|
| 1244 |
+
.adoc-prose pre {
|
| 1245 |
+
background: var(--bg-dark);
|
| 1246 |
+
color: #d0ccc6;
|
| 1247 |
+
border: 1px solid var(--border);
|
| 1248 |
+
border-radius: var(--radius);
|
| 1249 |
+
padding: 0.85rem 1rem;
|
| 1250 |
+
overflow-x: auto;
|
| 1251 |
+
margin-bottom: 0.85rem;
|
| 1252 |
+
font-family: 'Fira Code', monospace;
|
| 1253 |
+
font-size: 0.8rem;
|
| 1254 |
+
line-height: 1.55;
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
.adoc-prose code {
|
| 1258 |
+
font-family: 'Fira Code', monospace;
|
| 1259 |
+
font-size: 0.82em;
|
| 1260 |
+
}
|
| 1261 |
+
|
| 1262 |
+
.adoc-prose :not(pre) > code {
|
| 1263 |
+
background: var(--bg);
|
| 1264 |
+
border: 1px solid var(--border);
|
| 1265 |
border-radius: 3px;
|
| 1266 |
+
padding: 0.12rem 0.3rem;
|
| 1267 |
+
color: var(--accent);
|
| 1268 |
}
|
| 1269 |
|
| 1270 |
+
.adoc-prose table {
|
| 1271 |
+
width: 100%;
|
| 1272 |
+
border-collapse: collapse;
|
| 1273 |
+
margin-bottom: 0.85rem;
|
| 1274 |
+
font-size: 0.82rem;
|
| 1275 |
+
border: 1px solid var(--border);
|
| 1276 |
+
border-radius: var(--radius);
|
| 1277 |
+
overflow: hidden;
|
| 1278 |
}
|
| 1279 |
|
| 1280 |
+
.adoc-prose thead {
|
| 1281 |
+
background: var(--bg);
|
| 1282 |
+
}
|
| 1283 |
+
|
| 1284 |
+
.adoc-prose th {
|
| 1285 |
+
padding: 0.5rem 0.65rem;
|
| 1286 |
+
text-align: left;
|
| 1287 |
+
font-weight: 650;
|
| 1288 |
+
color: var(--text-mid);
|
| 1289 |
+
border-bottom: 1px solid var(--border);
|
| 1290 |
+
font-size: 0.78rem;
|
| 1291 |
+
}
|
| 1292 |
+
|
| 1293 |
+
.adoc-prose td {
|
| 1294 |
+
padding: 0.45rem 0.65rem;
|
| 1295 |
+
border-bottom: 1px solid var(--border);
|
| 1296 |
+
color: var(--text);
|
| 1297 |
+
}
|
| 1298 |
+
|
| 1299 |
+
.adoc-prose tr:last-child td {
|
| 1300 |
+
border-bottom: none;
|
| 1301 |
+
}
|
| 1302 |
+
|
| 1303 |
+
.adoc-prose tr:hover td {
|
| 1304 |
+
background: var(--bg-hover);
|
| 1305 |
+
}
|
| 1306 |
+
|
| 1307 |
+
.adoc-prose hr {
|
| 1308 |
+
border: none;
|
| 1309 |
+
border-top: 1px solid var(--border);
|
| 1310 |
+
margin: 1.5rem 0;
|
| 1311 |
+
}
|
| 1312 |
+
|
| 1313 |
+
.adoc-prose img {
|
| 1314 |
+
max-width: 100%;
|
| 1315 |
+
border-radius: var(--radius);
|
| 1316 |
+
border: 1px solid var(--border);
|
| 1317 |
+
}
|
| 1318 |
+
|
| 1319 |
+
/* ββ Responsive βββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββ */
|
| 1320 |
+
@media (max-width: 1280px) {
|
| 1321 |
+
.adoc-toc {
|
| 1322 |
+
display: none;
|
| 1323 |
+
}
|
| 1324 |
+
|
| 1325 |
+
.adoc-docs-layout {
|
| 1326 |
+
grid-template-columns: 220px 1fr;
|
| 1327 |
+
}
|
| 1328 |
+
}
|
| 1329 |
+
|
| 1330 |
+
@media (max-width: 960px) {
|
| 1331 |
+
.adoc-docs-layout {
|
| 1332 |
+
grid-template-columns: 1fr;
|
| 1333 |
+
}
|
| 1334 |
+
|
| 1335 |
+
.adoc-docs-nav {
|
| 1336 |
+
border-right: none;
|
| 1337 |
+
border-bottom: 1px solid var(--border);
|
| 1338 |
+
max-height: 200px;
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
.adoc-hero {
|
| 1342 |
+
padding: 1.5rem 1rem;
|
| 1343 |
+
}
|
| 1344 |
+
|
| 1345 |
+
.adoc-section {
|
| 1346 |
+
padding: 1rem;
|
| 1347 |
+
}
|
| 1348 |
+
|
| 1349 |
+
.adoc-cap-grid {
|
| 1350 |
+
grid-template-columns: 1fr;
|
| 1351 |
+
}
|
| 1352 |
+
|
| 1353 |
+
.adoc-config-header {
|
| 1354 |
+
flex-direction: column;
|
| 1355 |
+
align-items: flex-start;
|
| 1356 |
+
padding: 1rem 1rem 0.5rem;
|
| 1357 |
+
}
|
| 1358 |
+
|
| 1359 |
+
.adoc-search {
|
| 1360 |
+
width: 100%;
|
| 1361 |
+
}
|
| 1362 |
+
|
| 1363 |
+
.adoc-config-group {
|
| 1364 |
+
margin: 0 1rem;
|
| 1365 |
+
}
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
+
/* ββ Old docs classes (kept for backward compat) ββββββββ */
|
| 1369 |
+
|
| 1370 |
+
/* Overview grid for docs */
|
| 1371 |
+
.adoc-overview-grid {
|
| 1372 |
display: flex;
|
| 1373 |
+
flex-direction: column;
|
| 1374 |
+
gap: 1.25rem;
|
| 1375 |
+
}
|
| 1376 |
+
|
| 1377 |
+
@media (max-width: 960px) {
|
| 1378 |
+
.app-shell {
|
| 1379 |
+
grid-template-columns: 1fr;
|
| 1380 |
+
}
|
| 1381 |
+
|
| 1382 |
+
.app-sidebar {
|
| 1383 |
+
border-right: none;
|
| 1384 |
+
border-bottom: 1px solid var(--border);
|
| 1385 |
+
}
|
| 1386 |
+
|
| 1387 |
+
.docs-layout {
|
| 1388 |
+
grid-template-columns: 1fr;
|
| 1389 |
+
}
|
| 1390 |
+
|
| 1391 |
+
.docs-sidebar {
|
| 1392 |
+
max-height: 220px;
|
| 1393 |
+
}
|
| 1394 |
+
|
| 1395 |
+
.home-card-grid {
|
| 1396 |
+
grid-template-columns: repeat(2, 1fr);
|
| 1397 |
+
}
|
| 1398 |
+
|
| 1399 |
+
.home-agent-grid {
|
| 1400 |
+
grid-template-columns: repeat(2, 1fr);
|
| 1401 |
+
}
|
| 1402 |
+
|
| 1403 |
+
.pipeline-flow {
|
| 1404 |
+
justify-content: center;
|
| 1405 |
+
}
|
| 1406 |
+
|
| 1407 |
+
.pipeline-arrow {
|
| 1408 |
+
display: none;
|
| 1409 |
+
}
|
| 1410 |
}
|
| 1411 |
|
| 1412 |
/* ββ Sidebar βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
|
|
|
| 1493 |
margin-bottom: 0.8rem;
|
| 1494 |
}
|
| 1495 |
|
| 1496 |
+
.app-title {
|
| 1497 |
+
font-size: 1.35rem;
|
| 1498 |
+
font-weight: 650;
|
| 1499 |
+
color: var(--text);
|
| 1500 |
+
margin-bottom: 0.3rem;
|
| 1501 |
+
}
|
| 1502 |
+
|
| 1503 |
+
.app-subtitle {
|
| 1504 |
+
color: var(--text-mid);
|
| 1505 |
+
font-size: 0.88rem;
|
| 1506 |
+
margin-bottom: 0.7rem;
|
| 1507 |
+
}
|
| 1508 |
+
|
| 1509 |
+
.enterprise-table {
|
| 1510 |
+
width: 100%;
|
| 1511 |
+
border-collapse: collapse;
|
| 1512 |
+
text-align: left;
|
| 1513 |
+
}
|
| 1514 |
+
|
| 1515 |
+
.enterprise-table thead tr {
|
| 1516 |
+
border-bottom: 1px solid var(--border);
|
| 1517 |
+
}
|
| 1518 |
+
|
| 1519 |
+
.enterprise-table th,
|
| 1520 |
+
.enterprise-table td {
|
| 1521 |
+
padding: 0.62rem;
|
| 1522 |
+
border-bottom: 1px solid var(--border);
|
| 1523 |
+
font-size: 0.86rem;
|
| 1524 |
+
}
|
| 1525 |
+
|
| 1526 |
+
.enterprise-table th {
|
| 1527 |
+
color: var(--text-mid);
|
| 1528 |
+
font-weight: 600;
|
| 1529 |
+
}
|
| 1530 |
+
|
| 1531 |
+
.enterprise-table td {
|
| 1532 |
+
color: var(--text);
|
| 1533 |
+
}
|
| 1534 |
+
|
| 1535 |
/* ββ Grids βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 1536 |
.grid-4 {
|
| 1537 |
display: grid;
|
|
|
|
| 2416 |
padding: 0.25rem 0 2rem;
|
| 2417 |
}
|
| 2418 |
|
| 2419 |
+
/* Verification Report Card */
|
| 2420 |
+
.check-report-card {
|
| 2421 |
+
display: flex;
|
| 2422 |
+
flex-direction: column;
|
| 2423 |
+
gap: 0;
|
| 2424 |
+
border: 1px solid var(--border);
|
| 2425 |
+
border-radius: var(--radius-md);
|
| 2426 |
+
background: var(--bg-card);
|
| 2427 |
+
overflow: hidden;
|
| 2428 |
+
}
|
| 2429 |
+
|
| 2430 |
+
.check-report-item {
|
| 2431 |
+
display: flex;
|
| 2432 |
+
align-items: center;
|
| 2433 |
+
gap: 0.75rem;
|
| 2434 |
+
padding: 0.65rem 1rem;
|
| 2435 |
+
border-bottom: 1px solid var(--border);
|
| 2436 |
+
transition: background var(--fast);
|
| 2437 |
+
}
|
| 2438 |
+
|
| 2439 |
+
.check-report-item:last-child {
|
| 2440 |
+
border-bottom: none;
|
| 2441 |
+
}
|
| 2442 |
+
|
| 2443 |
+
.check-report-item:hover {
|
| 2444 |
+
background: var(--bg-hover);
|
| 2445 |
+
}
|
| 2446 |
+
|
| 2447 |
+
.check-report-status {
|
| 2448 |
+
width: 24px;
|
| 2449 |
+
height: 24px;
|
| 2450 |
+
border-radius: 50%;
|
| 2451 |
+
display: flex;
|
| 2452 |
+
align-items: center;
|
| 2453 |
+
justify-content: center;
|
| 2454 |
+
font-size: 0.7rem;
|
| 2455 |
+
font-weight: 800;
|
| 2456 |
+
flex-shrink: 0;
|
| 2457 |
+
}
|
| 2458 |
+
|
| 2459 |
+
.check-report-status[data-status='pass'] {
|
| 2460 |
+
background: var(--success-bg);
|
| 2461 |
+
color: var(--success);
|
| 2462 |
+
border: 1px solid var(--success-bdr);
|
| 2463 |
+
}
|
| 2464 |
+
|
| 2465 |
+
.check-report-status[data-status='fail'] {
|
| 2466 |
+
background: var(--fail-bg);
|
| 2467 |
+
color: var(--fail);
|
| 2468 |
+
border: 1px solid var(--fail-bdr);
|
| 2469 |
+
}
|
| 2470 |
+
|
| 2471 |
+
.check-report-status[data-status='skip'] {
|
| 2472 |
+
background: var(--bg);
|
| 2473 |
+
color: var(--text-dim);
|
| 2474 |
+
border: 1px solid var(--border);
|
| 2475 |
+
}
|
| 2476 |
+
|
| 2477 |
+
.check-report-label {
|
| 2478 |
+
font-size: 0.84rem;
|
| 2479 |
+
font-weight: 600;
|
| 2480 |
+
color: var(--text);
|
| 2481 |
+
display: block;
|
| 2482 |
+
}
|
| 2483 |
+
|
| 2484 |
+
.check-report-detail {
|
| 2485 |
+
font-size: 0.74rem;
|
| 2486 |
+
color: var(--text-dim);
|
| 2487 |
+
display: block;
|
| 2488 |
+
margin-top: 0.1rem;
|
| 2489 |
+
}
|
| 2490 |
+
|
| 2491 |
.action-btn {
|
| 2492 |
padding: 0.6rem 1.4rem;
|
| 2493 |
border-radius: var(--radius);
|
|
|
|
| 2512 |
}
|
| 2513 |
|
| 2514 |
/* ββ Dashboard βββββββββββββββββββββββββββββββββββββββββββββββββββββ */
|
| 2515 |
+
.dash-signoff-report {
|
| 2516 |
+
background: var(--bg-dark);
|
| 2517 |
+
padding: 1rem;
|
| 2518 |
+
border: 1px solid var(--border);
|
| 2519 |
+
border-radius: var(--radius);
|
| 2520 |
+
color: var(--success);
|
| 2521 |
+
font-family: 'Fira Code', monospace;
|
| 2522 |
+
font-size: 0.78rem;
|
| 2523 |
+
white-space: pre-wrap;
|
| 2524 |
+
max-height: 400px;
|
| 2525 |
+
overflow-y: auto;
|
| 2526 |
+
line-height: 1.55;
|
| 2527 |
+
}
|
| 2528 |
+
|
| 2529 |
+
.metric-highlight {
|
| 2530 |
+
position: relative;
|
| 2531 |
+
overflow: hidden;
|
| 2532 |
+
transition: transform var(--fast), box-shadow var(--fast);
|
| 2533 |
+
}
|
| 2534 |
+
|
| 2535 |
+
.metric-highlight:hover {
|
| 2536 |
+
transform: translateY(-2px);
|
| 2537 |
+
box-shadow: var(--shadow-md);
|
| 2538 |
+
}
|
| 2539 |
+
|
| 2540 |
+
.metric-tag {
|
| 2541 |
+
color: var(--text-dim);
|
| 2542 |
+
font-size: 0.68rem;
|
| 2543 |
+
margin-top: 0.5rem;
|
| 2544 |
+
text-transform: uppercase;
|
| 2545 |
+
letter-spacing: 0.06em;
|
| 2546 |
+
font-weight: 500;
|
| 2547 |
+
}
|
| 2548 |
+
|
| 2549 |
+
.dash-insight-card {
|
| 2550 |
+
border: 1px solid var(--border);
|
| 2551 |
+
border-radius: var(--radius);
|
| 2552 |
+
padding: 0.85rem;
|
| 2553 |
+
background: var(--bg);
|
| 2554 |
+
transition: all var(--fast);
|
| 2555 |
+
}
|
| 2556 |
+
|
| 2557 |
+
.dash-insight-card:hover {
|
| 2558 |
+
border-color: var(--accent);
|
| 2559 |
+
background: var(--accent-soft);
|
| 2560 |
+
}
|
| 2561 |
+
|
| 2562 |
+
.dash-insight-icon {
|
| 2563 |
+
font-size: 1.2rem;
|
| 2564 |
+
margin-bottom: 0.35rem;
|
| 2565 |
+
}
|
| 2566 |
+
|
| 2567 |
+
.dash-insight-title {
|
| 2568 |
+
font-size: 0.72rem;
|
| 2569 |
+
font-weight: 600;
|
| 2570 |
+
text-transform: uppercase;
|
| 2571 |
+
letter-spacing: 0.04em;
|
| 2572 |
+
color: var(--text-dim);
|
| 2573 |
+
margin-bottom: 0.2rem;
|
| 2574 |
+
}
|
| 2575 |
+
|
| 2576 |
+
.dash-insight-value {
|
| 2577 |
+
font-size: 0.88rem;
|
| 2578 |
+
font-weight: 650;
|
| 2579 |
+
color: var(--text);
|
| 2580 |
+
margin-bottom: 0.2rem;
|
| 2581 |
+
}
|
| 2582 |
+
|
| 2583 |
+
.dash-insight-detail {
|
| 2584 |
+
font-size: 0.72rem;
|
| 2585 |
+
color: var(--text-dim);
|
| 2586 |
+
line-height: 1.4;
|
| 2587 |
+
}
|
| 2588 |
+
|
| 2589 |
.metric-value.dashboard {
|
| 2590 |
font-size: 1.7rem;
|
| 2591 |
font-weight: 600;
|
web/src/pages/Benchmarking.tsx
CHANGED
|
@@ -7,49 +7,99 @@ interface BenchmarkingProps {
|
|
| 7 |
export const Benchmarking: React.FC<BenchmarkingProps> = ({ selectedDesign }) => {
|
| 8 |
return (
|
| 9 |
<div className="page-container">
|
| 10 |
-
<h2
|
| 11 |
-
<p
|
| 12 |
|
| 13 |
-
<div className="sci-fi-card">
|
| 14 |
-
<h3
|
| 15 |
-
<table style={{
|
| 16 |
<thead>
|
| 17 |
-
<tr
|
| 18 |
-
<th
|
| 19 |
-
<th
|
| 20 |
-
<th
|
| 21 |
</tr>
|
| 22 |
</thead>
|
| 23 |
<tbody>
|
| 24 |
<tr>
|
| 25 |
-
<td
|
| 26 |
-
<td style={{
|
| 27 |
-
<td
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</tr>
|
| 29 |
-
<tr
|
| 30 |
-
<td
|
| 31 |
-
<td style={{
|
| 32 |
-
<td
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
</tr>
|
|
|
|
|
|
|
| 34 |
<tr>
|
| 35 |
-
<td style={{
|
| 36 |
-
<td
|
| 37 |
-
<td
|
| 38 |
</tr>
|
| 39 |
-
<tr
|
| 40 |
-
<td style={{
|
| 41 |
-
<td
|
| 42 |
-
<td
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
</tr>
|
| 44 |
<tr>
|
| 45 |
-
<td style={{
|
| 46 |
-
<td
|
| 47 |
-
<td
|
| 48 |
</tr>
|
| 49 |
<tr>
|
| 50 |
-
<td style={{
|
| 51 |
-
<td
|
| 52 |
-
<td
|
| 53 |
</tr>
|
| 54 |
</tbody>
|
| 55 |
</table>
|
|
|
|
| 7 |
export const Benchmarking: React.FC<BenchmarkingProps> = ({ selectedDesign }) => {
|
| 8 |
return (
|
| 9 |
<div className="page-container">
|
| 10 |
+
<h2 className="app-title">π Market Benchmarking: {selectedDesign || 'No Design'}</h2>
|
| 11 |
+
<p className="app-subtitle">Compare AgentIC-generated flows against conventional enterprise chip flows.</p>
|
| 12 |
|
| 13 |
+
<div className="sci-fi-card" style={{ marginBottom: '1.5rem' }}>
|
| 14 |
+
<h3>Cost & Efficiency Analysis</h3>
|
| 15 |
+
<table className="enterprise-table" style={{ marginTop: '10px' }}>
|
| 16 |
<thead>
|
| 17 |
+
<tr>
|
| 18 |
+
<th>Metric</th>
|
| 19 |
+
<th>AgentIC</th>
|
| 20 |
+
<th>Traditional Flow</th>
|
| 21 |
</tr>
|
| 22 |
</thead>
|
| 23 |
<tbody>
|
| 24 |
<tr>
|
| 25 |
+
<td>RTL to GDSII Time</td>
|
| 26 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>~15 Minutes</td>
|
| 27 |
+
<td>Days/Weeks</td>
|
| 28 |
+
</tr>
|
| 29 |
+
<tr>
|
| 30 |
+
<td>Spec Decomposition</td>
|
| 31 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>ArchitectModule SID (automated)</td>
|
| 32 |
+
<td>Manual architecture review (weeks)</td>
|
| 33 |
+
</tr>
|
| 34 |
+
<tr>
|
| 35 |
+
<td>Verification Methodology</td>
|
| 36 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>Multi-agent diagnosis (5-class)</td>
|
| 37 |
+
<td>Manual waveform debugging</td>
|
| 38 |
+
</tr>
|
| 39 |
+
<tr>
|
| 40 |
+
<td>Agent Collaboration</td>
|
| 41 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>12 agents with tools + Crews</td>
|
| 42 |
+
<td>Siloed engineer teams</td>
|
| 43 |
+
</tr>
|
| 44 |
+
<tr>
|
| 45 |
+
<td>Self-Healing</td>
|
| 46 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>SelfReflectPipeline + convergence</td>
|
| 47 |
+
<td>Manual iteration</td>
|
| 48 |
+
</tr>
|
| 49 |
+
<tr>
|
| 50 |
+
<td>Log Triage</td>
|
| 51 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>Automated LLM Parsing</td>
|
| 52 |
+
<td>Manual Grepping</td>
|
| 53 |
+
</tr>
|
| 54 |
+
<tr>
|
| 55 |
+
<td>Licensing Cost</td>
|
| 56 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>Open Source + API</td>
|
| 57 |
+
<td>$1M+ / seat</td>
|
| 58 |
</tr>
|
| 59 |
+
<tr>
|
| 60 |
+
<td>DRC / LVS Violations</td>
|
| 61 |
+
<td style={{ color: 'var(--success)', fontWeight: 600 }}>Auto-heal assisted</td>
|
| 62 |
+
<td>Manual closure process</td>
|
| 63 |
+
</tr>
|
| 64 |
+
</tbody>
|
| 65 |
+
</table>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<div className="sci-fi-card">
|
| 69 |
+
<h3>Core Module Architecture</h3>
|
| 70 |
+
<table className="enterprise-table" style={{ marginTop: '10px' }}>
|
| 71 |
+
<thead>
|
| 72 |
+
<tr>
|
| 73 |
+
<th>Module</th>
|
| 74 |
+
<th>Based On</th>
|
| 75 |
+
<th>Stage</th>
|
| 76 |
</tr>
|
| 77 |
+
</thead>
|
| 78 |
+
<tbody>
|
| 79 |
<tr>
|
| 80 |
+
<td style={{ fontWeight: 600 }}>ArchitectModule</td>
|
| 81 |
+
<td>Spec2RTL-Agent</td>
|
| 82 |
+
<td>SPEC β SID JSON decomposition</td>
|
| 83 |
</tr>
|
| 84 |
+
<tr>
|
| 85 |
+
<td style={{ fontWeight: 600 }}>ReActAgent</td>
|
| 86 |
+
<td>Yao et al., 2023</td>
|
| 87 |
+
<td>ThoughtβActionβObservation loops</td>
|
| 88 |
+
</tr>
|
| 89 |
+
<tr>
|
| 90 |
+
<td style={{ fontWeight: 600 }}>SelfReflectPipeline</td>
|
| 91 |
+
<td>Self-Reflection Retry</td>
|
| 92 |
+
<td>HARDENING with convergence tracking</td>
|
| 93 |
</tr>
|
| 94 |
<tr>
|
| 95 |
+
<td style={{ fontWeight: 600 }}>DeepDebuggerModule</td>
|
| 96 |
+
<td>FVDebug</td>
|
| 97 |
+
<td>Formal β causal graphs + For-and-Against</td>
|
| 98 |
</tr>
|
| 99 |
<tr>
|
| 100 |
+
<td style={{ fontWeight: 600 }}>WaveformExpertModule</td>
|
| 101 |
+
<td>VerilogCoder</td>
|
| 102 |
+
<td>VCD + AST back-trace diagnosis</td>
|
| 103 |
</tr>
|
| 104 |
</tbody>
|
| 105 |
</table>
|
web/src/pages/Dashboard.tsx
CHANGED
|
@@ -11,13 +11,15 @@ export const Dashboard: React.FC<DashboardProps> = ({ selectedDesign }) => {
|
|
| 11 |
});
|
| 12 |
const [signoffData, setSignoffData] = useState<{ report: string, pass: boolean | null }>({ report: 'Fetching full sign-off analysis...', pass: null });
|
| 13 |
const [loading, setLoading] = useState(false);
|
|
|
|
| 14 |
|
| 15 |
useEffect(() => {
|
| 16 |
if (!selectedDesign) return;
|
| 17 |
setLoading(true);
|
| 18 |
|
| 19 |
-
// Fetch Quick Metrics
|
| 20 |
const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000').replace(/\/$/, '');
|
|
|
|
|
|
|
| 21 |
axios.get(`${API_BASE_URL}/metrics/${selectedDesign}`)
|
| 22 |
.then(res => {
|
| 23 |
if (res.data.metrics) setMetrics(res.data.metrics);
|
|
@@ -36,52 +38,128 @@ export const Dashboard: React.FC<DashboardProps> = ({ selectedDesign }) => {
|
|
| 36 |
})
|
| 37 |
.finally(() => setLoading(false));
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}, [selectedDesign]);
|
| 40 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
return (
|
| 42 |
<div className="page-container">
|
| 43 |
<div className="header-container">
|
| 44 |
<h2 className="app-title">π‘ Mission Control: {selectedDesign || 'No Design'}</h2>
|
|
|
|
| 45 |
</div>
|
| 46 |
|
| 47 |
-
{loading ? <div style={{ color: '
|
| 48 |
-
<div className="grid-4">
|
| 49 |
-
<div className="sci-fi-card">
|
| 50 |
<div className="metric-label">Worst Negative Slack</div>
|
| 51 |
-
<div className="metric-value" style={{ color: '
|
| 52 |
-
<div
|
| 53 |
</div>
|
| 54 |
|
| 55 |
-
<div className="sci-fi-card">
|
| 56 |
<div className="metric-label">Total Power</div>
|
| 57 |
-
<div className="metric-value" style={{ color: '
|
| 58 |
-
<div
|
| 59 |
</div>
|
| 60 |
|
| 61 |
-
<div className="sci-fi-card">
|
| 62 |
<div className="metric-label">Die Area</div>
|
| 63 |
-
<div className="metric-value" style={{ color: '
|
| 64 |
-
<div
|
| 65 |
</div>
|
| 66 |
|
| 67 |
-
<div className="sci-fi-card">
|
| 68 |
<div className="metric-label">Gate Count</div>
|
| 69 |
-
<div className="metric-value" style={{ color: '
|
| 70 |
-
<div
|
| 71 |
</div>
|
| 72 |
</div>
|
| 73 |
)}
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
<
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
{signoffData.report}
|
| 83 |
</pre>
|
| 84 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
</div>
|
| 86 |
);
|
| 87 |
};
|
|
|
|
| 11 |
});
|
| 12 |
const [signoffData, setSignoffData] = useState<{ report: string, pass: boolean | null }>({ report: 'Fetching full sign-off analysis...', pass: null });
|
| 13 |
const [loading, setLoading] = useState(false);
|
| 14 |
+
const [recentJobs, setRecentJobs] = useState<any[]>([]);
|
| 15 |
|
| 16 |
useEffect(() => {
|
| 17 |
if (!selectedDesign) return;
|
| 18 |
setLoading(true);
|
| 19 |
|
|
|
|
| 20 |
const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000').replace(/\/$/, '');
|
| 21 |
+
|
| 22 |
+
// Fetch Quick Metrics
|
| 23 |
axios.get(`${API_BASE_URL}/metrics/${selectedDesign}`)
|
| 24 |
.then(res => {
|
| 25 |
if (res.data.metrics) setMetrics(res.data.metrics);
|
|
|
|
| 38 |
})
|
| 39 |
.finally(() => setLoading(false));
|
| 40 |
|
| 41 |
+
// Fetch recent jobs
|
| 42 |
+
axios.get(`${API_BASE_URL}/jobs`)
|
| 43 |
+
.then(res => {
|
| 44 |
+
const jobs = (res.data?.jobs || [])
|
| 45 |
+
.filter((j: any) => j.design_name === selectedDesign)
|
| 46 |
+
.slice(0, 5);
|
| 47 |
+
setRecentJobs(jobs);
|
| 48 |
+
})
|
| 49 |
+
.catch(() => setRecentJobs([]));
|
| 50 |
+
|
| 51 |
}, [selectedDesign]);
|
| 52 |
|
| 53 |
+
const statusColor = (status: string) => {
|
| 54 |
+
if (status === 'done') return 'var(--success)';
|
| 55 |
+
if (status === 'failed') return 'var(--fail)';
|
| 56 |
+
if (status === 'running') return 'var(--accent)';
|
| 57 |
+
return 'var(--text-dim)';
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
return (
|
| 61 |
<div className="page-container">
|
| 62 |
<div className="header-container">
|
| 63 |
<h2 className="app-title">π‘ Mission Control: {selectedDesign || 'No Design'}</h2>
|
| 64 |
+
<p className="app-subtitle">Silicon metrics, signoff analysis, and agent intelligence for this design.</p>
|
| 65 |
</div>
|
| 66 |
|
| 67 |
+
{loading ? <div style={{ color: 'var(--text-mid)', margin: '20px 0' }}>Loading metrics...</div> : (
|
| 68 |
+
<div className="grid-4" style={{ marginBottom: '1.5rem' }}>
|
| 69 |
+
<div className="sci-fi-card metric-highlight">
|
| 70 |
<div className="metric-label">Worst Negative Slack</div>
|
| 71 |
+
<div className="metric-value" style={{ color: 'var(--success)' }}>{metrics.wns}</div>
|
| 72 |
+
<div className="metric-tag">Timing</div>
|
| 73 |
</div>
|
| 74 |
|
| 75 |
+
<div className="sci-fi-card metric-highlight">
|
| 76 |
<div className="metric-label">Total Power</div>
|
| 77 |
+
<div className="metric-value" style={{ color: 'var(--accent)' }}>{metrics.power}</div>
|
| 78 |
+
<div className="metric-tag">Energy</div>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
+
<div className="sci-fi-card metric-highlight">
|
| 82 |
<div className="metric-label">Die Area</div>
|
| 83 |
+
<div className="metric-value" style={{ color: 'var(--text)' }}>{metrics.area}</div>
|
| 84 |
+
<div className="metric-tag">Silicon Footprint</div>
|
| 85 |
</div>
|
| 86 |
|
| 87 |
+
<div className="sci-fi-card metric-highlight">
|
| 88 |
<div className="metric-label">Gate Count</div>
|
| 89 |
+
<div className="metric-value" style={{ color: 'var(--text)' }}>{metrics.gate_count}</div>
|
| 90 |
+
<div className="metric-tag">Logic Cells</div>
|
| 91 |
</div>
|
| 92 |
</div>
|
| 93 |
)}
|
| 94 |
|
| 95 |
+
{/* Agent Intelligence Card */}
|
| 96 |
+
<div className="sci-fi-card" style={{ marginBottom: '1.5rem' }}>
|
| 97 |
+
<h3 style={{ marginBottom: '0.75rem' }}>π§ Agent Architecture</h3>
|
| 98 |
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '0.75rem' }}>
|
| 99 |
+
<div className="dash-insight-card">
|
| 100 |
+
<div className="dash-insight-icon">π</div>
|
| 101 |
+
<div className="dash-insight-title">Spec Decomposition</div>
|
| 102 |
+
<div className="dash-insight-value">SID/JSON Contract</div>
|
| 103 |
+
<div className="dash-insight-detail">ArchitectModule β validated ports, FSMs, sub-modules</div>
|
| 104 |
+
</div>
|
| 105 |
+
<div className="dash-insight-card">
|
| 106 |
+
<div className="dash-insight-icon">π₯</div>
|
| 107 |
+
<div className="dash-insight-title">Collaborative RTL</div>
|
| 108 |
+
<div className="dash-insight-value">Designer + Reviewer</div>
|
| 109 |
+
<div className="dash-insight-detail">2-agent Crew with syntax_check and read_file tools</div>
|
| 110 |
+
</div>
|
| 111 |
+
<div className="dash-insight-card">
|
| 112 |
+
<div className="dash-insight-icon">π</div>
|
| 113 |
+
<div className="dash-insight-title">Self-Healing</div>
|
| 114 |
+
<div className="dash-insight-value">Convergence-Aware</div>
|
| 115 |
+
<div className="dash-insight-detail">SelfReflectPipeline with fingerprinting + stagnation detection</div>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
|
| 120 |
+
<div className="sci-fi-card" style={{ marginBottom: '1.5rem' }}>
|
| 121 |
+
<h3 style={{ marginBottom: '0.75rem' }}>
|
| 122 |
+
AgentIC Signoff Report
|
| 123 |
+
{signoffData.pass === true && <span style={{ color: 'var(--success)', marginLeft: '0.5rem', fontSize: '0.85rem' }}>β
PASSED</span>}
|
| 124 |
+
{signoffData.pass === false && <span style={{ color: 'var(--fail)', marginLeft: '0.5rem', fontSize: '0.85rem' }}>β FAILED</span>}
|
| 125 |
+
</h3>
|
| 126 |
+
<pre className="dash-signoff-report">
|
| 127 |
{signoffData.report}
|
| 128 |
</pre>
|
| 129 |
</div>
|
| 130 |
+
|
| 131 |
+
{/* Recent Build History */}
|
| 132 |
+
{recentJobs.length > 0 && (
|
| 133 |
+
<div className="sci-fi-card">
|
| 134 |
+
<h3 style={{ marginBottom: '0.75rem' }}>Recent Builds</h3>
|
| 135 |
+
<table className="enterprise-table">
|
| 136 |
+
<thead>
|
| 137 |
+
<tr>
|
| 138 |
+
<th>Job ID</th>
|
| 139 |
+
<th>Status</th>
|
| 140 |
+
<th>Current Stage</th>
|
| 141 |
+
<th>Events</th>
|
| 142 |
+
</tr>
|
| 143 |
+
</thead>
|
| 144 |
+
<tbody>
|
| 145 |
+
{recentJobs.map((job: any) => (
|
| 146 |
+
<tr key={job.job_id}>
|
| 147 |
+
<td style={{ fontFamily: 'Fira Code, monospace', fontSize: '0.78rem' }}>
|
| 148 |
+
{job.job_id.substring(0, 8)}β¦
|
| 149 |
+
</td>
|
| 150 |
+
<td>
|
| 151 |
+
<span style={{ color: statusColor(job.status), fontWeight: 600 }}>
|
| 152 |
+
{job.status}
|
| 153 |
+
</span>
|
| 154 |
+
</td>
|
| 155 |
+
<td>{job.current_state}</td>
|
| 156 |
+
<td>{job.event_count}</td>
|
| 157 |
+
</tr>
|
| 158 |
+
))}
|
| 159 |
+
</tbody>
|
| 160 |
+
</table>
|
| 161 |
+
</div>
|
| 162 |
+
)}
|
| 163 |
</div>
|
| 164 |
);
|
| 165 |
};
|
web/src/pages/DesignStudio.tsx
CHANGED
|
@@ -19,6 +19,12 @@ interface BuildEvent {
|
|
| 19 |
status?: string; // present on stream_end events
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
function slugify(text: string): string {
|
| 23 |
return text
|
| 24 |
.toLowerCase()
|
|
@@ -39,7 +45,27 @@ export const DesignStudio = () => {
|
|
| 39 |
const [jobStatus, setJobStatus] = useState<'queued' | 'running' | 'done' | 'failed' | 'cancelled' | 'cancelling'>('queued');
|
| 40 |
const [result, setResult] = useState<any>(null);
|
| 41 |
const [error, setError] = useState('');
|
|
|
|
|
|
|
| 42 |
const [skipOpenlane, setSkipOpenlane] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
const abortCtrlRef = useRef<AbortController | null>(null);
|
| 44 |
|
| 45 |
// Auto-generate design name from prompt
|
|
@@ -57,7 +83,21 @@ export const DesignStudio = () => {
|
|
| 57 |
design_name: designName || slugify(prompt),
|
| 58 |
description: prompt,
|
| 59 |
skip_openlane: skipOpenlane,
|
| 60 |
-
full_signoff:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
});
|
| 62 |
const { job_id } = res.data;
|
| 63 |
setJobId(job_id);
|
|
@@ -73,6 +113,10 @@ export const DesignStudio = () => {
|
|
| 73 |
const ctrl = new AbortController();
|
| 74 |
abortCtrlRef.current = ctrl;
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
fetchEventSource(`${API}/build/stream/${jid}`, {
|
| 77 |
method: 'GET',
|
| 78 |
headers: {
|
|
@@ -89,7 +133,14 @@ export const DesignStudio = () => {
|
|
| 89 |
fetchResult(jid, data.status as any);
|
| 90 |
return;
|
| 91 |
}
|
| 92 |
-
setEvents(prev =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
setJobStatus(data.type === 'error' ? 'failed' : 'running');
|
| 94 |
} catch { /* ignore parse errors */ }
|
| 95 |
},
|
|
@@ -133,6 +184,9 @@ export const DesignStudio = () => {
|
|
| 133 |
if ('Notification' in window && Notification.permission === 'default') {
|
| 134 |
Notification.requestPermission();
|
| 135 |
}
|
|
|
|
|
|
|
|
|
|
| 136 |
return () => abortCtrlRef.current?.abort();
|
| 137 |
}, []);
|
| 138 |
|
|
@@ -189,7 +243,7 @@ export const DesignStudio = () => {
|
|
| 189 |
)}
|
| 190 |
|
| 191 |
<div className="prompt-options">
|
| 192 |
-
<label className="toggle-label">
|
| 193 |
<input
|
| 194 |
type="checkbox"
|
| 195 |
checked={skipOpenlane}
|
|
@@ -197,6 +251,127 @@ export const DesignStudio = () => {
|
|
| 197 |
/>
|
| 198 |
<span>Skip OpenLane (RTL + Verify only, faster)</span>
|
| 199 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
</div>
|
| 201 |
|
| 202 |
{error && <div className="error-banner">β οΈ {error}</div>}
|
|
@@ -229,6 +404,7 @@ export const DesignStudio = () => {
|
|
| 229 |
jobId={jobId}
|
| 230 |
events={events}
|
| 231 |
jobStatus={jobStatus}
|
|
|
|
| 232 |
/>
|
| 233 |
</motion.div>
|
| 234 |
)}
|
|
|
|
| 19 |
status?: string; // present on stream_end events
|
| 20 |
}
|
| 21 |
|
| 22 |
+
interface StageSchemaItem {
|
| 23 |
+
state: string;
|
| 24 |
+
label: string;
|
| 25 |
+
icon: string;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
function slugify(text: string): string {
|
| 29 |
return text
|
| 30 |
.toLowerCase()
|
|
|
|
| 45 |
const [jobStatus, setJobStatus] = useState<'queued' | 'running' | 'done' | 'failed' | 'cancelled' | 'cancelling'>('queued');
|
| 46 |
const [result, setResult] = useState<any>(null);
|
| 47 |
const [error, setError] = useState('');
|
| 48 |
+
|
| 49 |
+
// Build Options
|
| 50 |
const [skipOpenlane, setSkipOpenlane] = useState(false);
|
| 51 |
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
| 52 |
+
const [fullSignoff, setFullSignoff] = useState(false);
|
| 53 |
+
const [maxRetries, setMaxRetries] = useState(5);
|
| 54 |
+
const [showThinking, setShowThinking] = useState(false);
|
| 55 |
+
const [minCoverage, setMinCoverage] = useState(80.0);
|
| 56 |
+
const [strictGates, setStrictGates] = useState(true);
|
| 57 |
+
const [pdkProfile, setPdkProfile] = useState("sky130");
|
| 58 |
+
const [maxPivots, setMaxPivots] = useState(2);
|
| 59 |
+
const [congestionThreshold, setCongestionThreshold] = useState(10.0);
|
| 60 |
+
const [hierarchical, setHierarchical] = useState("auto");
|
| 61 |
+
const [tbGateMode, setTbGateMode] = useState("strict");
|
| 62 |
+
const [tbMaxRetries, setTbMaxRetries] = useState(3);
|
| 63 |
+
const [tbFallbackTemplate, setTbFallbackTemplate] = useState("uvm_lite");
|
| 64 |
+
const [coverageBackend, setCoverageBackend] = useState("auto");
|
| 65 |
+
const [coverageFallbackPolicy, setCoverageFallbackPolicy] = useState("fail_closed");
|
| 66 |
+
const [coverageProfile, setCoverageProfile] = useState("balanced");
|
| 67 |
+
const [stageSchema, setStageSchema] = useState<StageSchemaItem[]>([]);
|
| 68 |
+
|
| 69 |
const abortCtrlRef = useRef<AbortController | null>(null);
|
| 70 |
|
| 71 |
// Auto-generate design name from prompt
|
|
|
|
| 83 |
design_name: designName || slugify(prompt),
|
| 84 |
description: prompt,
|
| 85 |
skip_openlane: skipOpenlane,
|
| 86 |
+
full_signoff: fullSignoff,
|
| 87 |
+
max_retries: maxRetries,
|
| 88 |
+
show_thinking: showThinking,
|
| 89 |
+
min_coverage: minCoverage,
|
| 90 |
+
strict_gates: strictGates,
|
| 91 |
+
pdk_profile: pdkProfile,
|
| 92 |
+
max_pivots: maxPivots,
|
| 93 |
+
congestion_threshold: congestionThreshold,
|
| 94 |
+
hierarchical: hierarchical,
|
| 95 |
+
tb_gate_mode: tbGateMode,
|
| 96 |
+
tb_max_retries: tbMaxRetries,
|
| 97 |
+
tb_fallback_template: tbFallbackTemplate,
|
| 98 |
+
coverage_backend: coverageBackend,
|
| 99 |
+
coverage_fallback_policy: coverageFallbackPolicy,
|
| 100 |
+
coverage_profile: coverageProfile
|
| 101 |
});
|
| 102 |
const { job_id } = res.data;
|
| 103 |
setJobId(job_id);
|
|
|
|
| 113 |
const ctrl = new AbortController();
|
| 114 |
abortCtrlRef.current = ctrl;
|
| 115 |
|
| 116 |
+
// Clear previous events on reconnect to prevent duplicates
|
| 117 |
+
// (server replays all events from the beginning on each connection)
|
| 118 |
+
setEvents([]);
|
| 119 |
+
|
| 120 |
fetchEventSource(`${API}/build/stream/${jid}`, {
|
| 121 |
method: 'GET',
|
| 122 |
headers: {
|
|
|
|
| 133 |
fetchResult(jid, data.status as any);
|
| 134 |
return;
|
| 135 |
}
|
| 136 |
+
setEvents(prev => {
|
| 137 |
+
// Deduplicate: skip if last event has same message + type
|
| 138 |
+
const last = prev[prev.length - 1];
|
| 139 |
+
if (last && last.message === data.message && last.type === data.type) {
|
| 140 |
+
return prev;
|
| 141 |
+
}
|
| 142 |
+
return [...prev, data];
|
| 143 |
+
});
|
| 144 |
setJobStatus(data.type === 'error' ? 'failed' : 'running');
|
| 145 |
} catch { /* ignore parse errors */ }
|
| 146 |
},
|
|
|
|
| 184 |
if ('Notification' in window && Notification.permission === 'default') {
|
| 185 |
Notification.requestPermission();
|
| 186 |
}
|
| 187 |
+
axios.get(`${API}/pipeline/schema`)
|
| 188 |
+
.then(res => setStageSchema(res.data?.stages || []))
|
| 189 |
+
.catch(() => setStageSchema([]));
|
| 190 |
return () => abortCtrlRef.current?.abort();
|
| 191 |
}, []);
|
| 192 |
|
|
|
|
| 243 |
)}
|
| 244 |
|
| 245 |
<div className="prompt-options">
|
| 246 |
+
<label className="toggle-label" style={{ marginBottom: '1rem', display: 'flex' }}>
|
| 247 |
<input
|
| 248 |
type="checkbox"
|
| 249 |
checked={skipOpenlane}
|
|
|
|
| 251 |
/>
|
| 252 |
<span>Skip OpenLane (RTL + Verify only, faster)</span>
|
| 253 |
</label>
|
| 254 |
+
|
| 255 |
+
<button
|
| 256 |
+
className="advanced-toggle-btn"
|
| 257 |
+
onClick={() => setShowAdvanced(!showAdvanced)}
|
| 258 |
+
style={{ background: 'transparent', border: '1px solid var(--border-mid)', color: 'var(--text-mid)', padding: '0.5rem 0.8rem', borderRadius: 'var(--radius)', cursor: 'pointer', fontSize: '0.9rem', width: '100%', textAlign: 'left', marginBottom: '1rem', transition: 'all var(--fast)', fontWeight: 500 }}
|
| 259 |
+
onMouseOver={e => { e.currentTarget.style.borderColor = 'var(--text-dim)'; e.currentTarget.style.color = 'var(--text)'; }}
|
| 260 |
+
onMouseOut={e => { e.currentTarget.style.borderColor = 'var(--border-mid)'; e.currentTarget.style.color = 'var(--text-mid)'; }}
|
| 261 |
+
>
|
| 262 |
+
{showAdvanced ? 'βΌ Hide Advanced Options' : 'βΆ Show Advanced Options'}
|
| 263 |
+
</button>
|
| 264 |
+
|
| 265 |
+
{showAdvanced && (
|
| 266 |
+
<div className="advanced-options-panel" style={{ background: 'var(--bg-card)', padding: '1.25rem', borderRadius: 'var(--radius-md)', fontSize: '0.9rem', display: 'flex', flexDirection: 'column', gap: '1rem', marginBottom: '1.5rem', border: '1px solid var(--border)', boxShadow: 'var(--shadow-xs)' }}>
|
| 267 |
+
|
| 268 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 269 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 270 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Max Retries</span>
|
| 271 |
+
<input type="number" value={maxRetries} onChange={e => setMaxRetries(Number(e.target.value))} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none', transition: 'border-color var(--fast)' }} />
|
| 272 |
+
</label>
|
| 273 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 274 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Min Coverage (%)</span>
|
| 275 |
+
<input type="number" step="0.1" value={minCoverage} onChange={e => setMinCoverage(Number(e.target.value))} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none', transition: 'border-color var(--fast)' }} />
|
| 276 |
+
</label>
|
| 277 |
+
</div>
|
| 278 |
+
|
| 279 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 280 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 281 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>PDK Profile</span>
|
| 282 |
+
<select value={pdkProfile} onChange={e => setPdkProfile(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 283 |
+
<option value="sky130">sky130</option>
|
| 284 |
+
<option value="gf180">gf180</option>
|
| 285 |
+
</select>
|
| 286 |
+
</label>
|
| 287 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 288 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Coverage Profile</span>
|
| 289 |
+
<select value={coverageProfile} onChange={e => setCoverageProfile(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 290 |
+
<option value="balanced">Balanced</option>
|
| 291 |
+
<option value="aggressive">Aggressive</option>
|
| 292 |
+
<option value="relaxed">Relaxed</option>
|
| 293 |
+
</select>
|
| 294 |
+
</label>
|
| 295 |
+
</div>
|
| 296 |
+
|
| 297 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 298 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 299 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>TB Gate Mode</span>
|
| 300 |
+
<select value={tbGateMode} onChange={e => setTbGateMode(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 301 |
+
<option value="strict">Strict</option>
|
| 302 |
+
<option value="relaxed">Relaxed</option>
|
| 303 |
+
</select>
|
| 304 |
+
</label>
|
| 305 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 306 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>TB Fallback Template</span>
|
| 307 |
+
<select value={tbFallbackTemplate} onChange={e => setTbFallbackTemplate(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 308 |
+
<option value="uvm_lite">UVM Lite</option>
|
| 309 |
+
<option value="classic">Classic</option>
|
| 310 |
+
</select>
|
| 311 |
+
</label>
|
| 312 |
+
</div>
|
| 313 |
+
|
| 314 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 315 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 316 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Coverage Backend</span>
|
| 317 |
+
<select value={coverageBackend} onChange={e => setCoverageBackend(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 318 |
+
<option value="auto">Auto</option>
|
| 319 |
+
<option value="verilator">Verilator</option>
|
| 320 |
+
<option value="iverilog">Icarus Verilog</option>
|
| 321 |
+
</select>
|
| 322 |
+
</label>
|
| 323 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 324 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Fallback Policy</span>
|
| 325 |
+
<select value={coverageFallbackPolicy} onChange={e => setCoverageFallbackPolicy(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 326 |
+
<option value="fail_closed">Fail Closed</option>
|
| 327 |
+
<option value="fallback_oss">Fallback OSS</option>
|
| 328 |
+
<option value="skip">Skip</option>
|
| 329 |
+
</select>
|
| 330 |
+
</label>
|
| 331 |
+
</div>
|
| 332 |
+
|
| 333 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 334 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 335 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Hierarchical</span>
|
| 336 |
+
<select value={hierarchical} onChange={e => setHierarchical(e.target.value)} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none' }}>
|
| 337 |
+
<option value="auto">Auto</option>
|
| 338 |
+
<option value="on">On</option>
|
| 339 |
+
<option value="off">Off</option>
|
| 340 |
+
</select>
|
| 341 |
+
</label>
|
| 342 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 343 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Congestion Threshold (%)</span>
|
| 344 |
+
<input type="number" step="0.1" value={congestionThreshold} onChange={e => setCongestionThreshold(Number(e.target.value))} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none', transition: 'border-color var(--fast)' }} />
|
| 345 |
+
</label>
|
| 346 |
+
</div>
|
| 347 |
+
|
| 348 |
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.25rem' }}>
|
| 349 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 350 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>Max Pivots</span>
|
| 351 |
+
<input type="number" value={maxPivots} onChange={e => setMaxPivots(Number(e.target.value))} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none', transition: 'border-color var(--fast)' }} />
|
| 352 |
+
</label>
|
| 353 |
+
<label style={{ display: 'flex', flexDirection: 'column' }}>
|
| 354 |
+
<span style={{ color: 'var(--text-mid)', marginBottom: '0.4rem', fontWeight: 500, fontSize: '0.8rem' }}>TB Max Retries</span>
|
| 355 |
+
<input type="number" value={tbMaxRetries} onChange={e => setTbMaxRetries(Number(e.target.value))} style={{ padding: '0.5rem 0.75rem', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', borderRadius: 'var(--radius-xs)', fontSize: '0.9rem', outline: 'none', transition: 'border-color var(--fast)' }} />
|
| 356 |
+
</label>
|
| 357 |
+
</div>
|
| 358 |
+
|
| 359 |
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1.5rem', marginTop: '0.5rem', background: 'var(--bg)', padding: '1rem', borderRadius: 'var(--radius)', border: '1px solid var(--border-mid)' }}>
|
| 360 |
+
<label className="toggle-label" style={{ display: 'flex', alignItems: 'center' }}>
|
| 361 |
+
<input type="checkbox" checked={fullSignoff} onChange={e => setFullSignoff(e.target.checked)} />
|
| 362 |
+
<span style={{ marginLeft: '0.5rem', color: 'var(--text)', fontWeight: 500 }}>Full Signoff</span>
|
| 363 |
+
</label>
|
| 364 |
+
<label className="toggle-label" style={{ display: 'flex', alignItems: 'center' }}>
|
| 365 |
+
<input type="checkbox" checked={strictGates} onChange={e => setStrictGates(e.target.checked)} />
|
| 366 |
+
<span style={{ marginLeft: '0.5rem', color: 'var(--text)', fontWeight: 500 }}>Strict Gates</span>
|
| 367 |
+
</label>
|
| 368 |
+
<label className="toggle-label" style={{ display: 'flex', alignItems: 'center' }}>
|
| 369 |
+
<input type="checkbox" checked={showThinking} onChange={e => setShowThinking(e.target.checked)} />
|
| 370 |
+
<span style={{ marginLeft: '0.5rem', color: 'var(--text)', fontWeight: 500 }}>Show Thinking</span>
|
| 371 |
+
</label>
|
| 372 |
+
</div>
|
| 373 |
+
</div>
|
| 374 |
+
)}
|
| 375 |
</div>
|
| 376 |
|
| 377 |
{error && <div className="error-banner">β οΈ {error}</div>}
|
|
|
|
| 404 |
jobId={jobId}
|
| 405 |
events={events}
|
| 406 |
jobStatus={jobStatus}
|
| 407 |
+
stageSchema={stageSchema}
|
| 408 |
/>
|
| 409 |
</motion.div>
|
| 410 |
)}
|
web/src/pages/Documentation.tsx
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useMemo, useState, useRef, useCallback } from 'react';
|
| 2 |
+
import axios from 'axios';
|
| 3 |
+
import ReactMarkdown from 'react-markdown';
|
| 4 |
+
import remarkGfm from 'remark-gfm';
|
| 5 |
+
|
| 6 |
+
const API = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000').replace(/\/$/, '');
|
| 7 |
+
|
| 8 |
+
interface DocItem {
|
| 9 |
+
id: string;
|
| 10 |
+
title: string;
|
| 11 |
+
section: string;
|
| 12 |
+
summary: string;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
interface BuildOption {
|
| 16 |
+
key: string;
|
| 17 |
+
type: string;
|
| 18 |
+
default: string | number | boolean;
|
| 19 |
+
description: string;
|
| 20 |
+
values?: string[];
|
| 21 |
+
min?: number;
|
| 22 |
+
max?: number;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
interface BuildOptionGroup {
|
| 26 |
+
name: string;
|
| 27 |
+
options: BuildOption[];
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
interface StageItem {
|
| 31 |
+
state: string;
|
| 32 |
+
label: string;
|
| 33 |
+
icon: string;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
type Tab = 'overview' | 'pipeline' | 'config' | 'docs';
|
| 37 |
+
|
| 38 |
+
/* ββ Table of contents extractor βββββββββββββββββββββββββββ */
|
| 39 |
+
function extractTOC(md: string): { level: number; text: string; id: string }[] {
|
| 40 |
+
const lines = md.split('\n');
|
| 41 |
+
const toc: { level: number; text: string; id: string }[] = [];
|
| 42 |
+
for (const line of lines) {
|
| 43 |
+
const match = line.match(/^(#{1,4})\s+(.+)/);
|
| 44 |
+
if (match) {
|
| 45 |
+
const text = match[2].replace(/[`*_~\[\]]/g, '').trim();
|
| 46 |
+
const id = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
| 47 |
+
toc.push({ level: match[1].length, text, id });
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
return toc;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
export const Documentation = () => {
|
| 54 |
+
const [tab, setTab] = useState<Tab>('overview');
|
| 55 |
+
const [docs, setDocs] = useState<DocItem[]>([]);
|
| 56 |
+
const [selectedDoc, setSelectedDoc] = useState<string>('readme');
|
| 57 |
+
const [, setDocTitle] = useState<string>('Documentation');
|
| 58 |
+
const [content, setContent] = useState<string>('');
|
| 59 |
+
const [loading, setLoading] = useState<boolean>(true);
|
| 60 |
+
const [searchQuery, setSearchQuery] = useState('');
|
| 61 |
+
const [optionGroups, setOptionGroups] = useState<BuildOptionGroup[]>([]);
|
| 62 |
+
const [stages, setStages] = useState<StageItem[]>([]);
|
| 63 |
+
const contentRef = useRef<HTMLDivElement>(null);
|
| 64 |
+
|
| 65 |
+
useEffect(() => {
|
| 66 |
+
const loadIndex = async () => {
|
| 67 |
+
try {
|
| 68 |
+
const [docsRes, optionsRes, schemaRes] = await Promise.all([
|
| 69 |
+
axios.get(`${API}/docs/index`),
|
| 70 |
+
axios.get(`${API}/build/options`),
|
| 71 |
+
axios.get(`${API}/pipeline/schema`),
|
| 72 |
+
]);
|
| 73 |
+
const docsData: DocItem[] = docsRes.data?.docs || [];
|
| 74 |
+
setDocs(docsData);
|
| 75 |
+
setOptionGroups(optionsRes.data?.groups || []);
|
| 76 |
+
setStages(schemaRes.data?.stages || []);
|
| 77 |
+
if (docsData.length > 0) {
|
| 78 |
+
setSelectedDoc(docsData.find((d) => d.id === 'readme')?.id || docsData[0].id);
|
| 79 |
+
}
|
| 80 |
+
} catch {
|
| 81 |
+
setContent('Failed to load documentation index.');
|
| 82 |
+
setLoading(false);
|
| 83 |
+
}
|
| 84 |
+
};
|
| 85 |
+
loadIndex();
|
| 86 |
+
}, []);
|
| 87 |
+
|
| 88 |
+
useEffect(() => {
|
| 89 |
+
if (!selectedDoc) return;
|
| 90 |
+
setLoading(true);
|
| 91 |
+
axios.get(`${API}/docs/content/${selectedDoc}`)
|
| 92 |
+
.then((res) => {
|
| 93 |
+
setDocTitle(res.data?.title || selectedDoc);
|
| 94 |
+
setContent(res.data?.content || 'No content available.');
|
| 95 |
+
})
|
| 96 |
+
.catch(() => {
|
| 97 |
+
setDocTitle('Documentation');
|
| 98 |
+
setContent('Failed to load document content.');
|
| 99 |
+
})
|
| 100 |
+
.finally(() => setLoading(false));
|
| 101 |
+
}, [selectedDoc]);
|
| 102 |
+
|
| 103 |
+
const toc = useMemo(() => extractTOC(content), [content]);
|
| 104 |
+
|
| 105 |
+
const sections = useMemo(() => {
|
| 106 |
+
const grouped = new Map<string, DocItem[]>();
|
| 107 |
+
for (const d of docs) {
|
| 108 |
+
const key = d.section || 'General';
|
| 109 |
+
if (!grouped.has(key)) grouped.set(key, []);
|
| 110 |
+
grouped.get(key)!.push(d);
|
| 111 |
+
}
|
| 112 |
+
return Array.from(grouped.entries());
|
| 113 |
+
}, [docs]);
|
| 114 |
+
|
| 115 |
+
const filteredOptions = useMemo(() => {
|
| 116 |
+
if (!searchQuery.trim()) return optionGroups;
|
| 117 |
+
const q = searchQuery.toLowerCase();
|
| 118 |
+
return optionGroups
|
| 119 |
+
.map((g) => ({
|
| 120 |
+
...g,
|
| 121 |
+
options: g.options.filter(
|
| 122 |
+
(o) =>
|
| 123 |
+
o.key.toLowerCase().includes(q) ||
|
| 124 |
+
o.description.toLowerCase().includes(q)
|
| 125 |
+
),
|
| 126 |
+
}))
|
| 127 |
+
.filter((g) => g.options.length > 0);
|
| 128 |
+
}, [optionGroups, searchQuery]);
|
| 129 |
+
|
| 130 |
+
const scrollToHeading = useCallback((id: string) => {
|
| 131 |
+
const el = contentRef.current?.querySelector(`#${CSS.escape(id)}`);
|
| 132 |
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| 133 |
+
}, []);
|
| 134 |
+
|
| 135 |
+
const tabs: { key: Tab; label: string; icon: string }[] = [
|
| 136 |
+
{ key: 'overview', label: 'Overview', icon: 'π' },
|
| 137 |
+
{ key: 'pipeline', label: 'Pipeline', icon: 'π¬' },
|
| 138 |
+
{ key: 'config', label: 'Configuration', icon: 'βοΈ' },
|
| 139 |
+
{ key: 'docs', label: 'Documents', icon: 'π' },
|
| 140 |
+
];
|
| 141 |
+
|
| 142 |
+
return (
|
| 143 |
+
<div className="adoc-root">
|
| 144 |
+
{/* ββ Header ββββββββββββββββββββββββββββββββββββββ */}
|
| 145 |
+
<header className="adoc-hero">
|
| 146 |
+
<div className="adoc-hero-inner">
|
| 147 |
+
<div className="adoc-hero-badge">Technical Reference Manual</div>
|
| 148 |
+
<h1 className="adoc-hero-title">AgentIC Documentation</h1>
|
| 149 |
+
<p className="adoc-hero-sub">
|
| 150 |
+
Autonomous silicon design platform β architecture, pipeline specification,
|
| 151 |
+
configuration reference, and operational guides.
|
| 152 |
+
</p>
|
| 153 |
+
<div className="adoc-hero-stats">
|
| 154 |
+
<span>{stages.length} pipeline stages</span>
|
| 155 |
+
<span className="adoc-hero-dot">Β·</span>
|
| 156 |
+
<span>{optionGroups.reduce((a, g) => a + g.options.length, 0)} configurable parameters</span>
|
| 157 |
+
<span className="adoc-hero-dot">Β·</span>
|
| 158 |
+
<span>{docs.length} reference documents</span>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</header>
|
| 162 |
+
|
| 163 |
+
{/* ββ Tab bar βββββββββββββββββββββββββββββββββββββ */}
|
| 164 |
+
<nav className="adoc-tabs">
|
| 165 |
+
{tabs.map((t) => (
|
| 166 |
+
<button
|
| 167 |
+
key={t.key}
|
| 168 |
+
className={`adoc-tab ${tab === t.key ? 'active' : ''}`}
|
| 169 |
+
onClick={() => setTab(t.key)}
|
| 170 |
+
>
|
| 171 |
+
<span className="adoc-tab-icon">{t.icon}</span>
|
| 172 |
+
{t.label}
|
| 173 |
+
</button>
|
| 174 |
+
))}
|
| 175 |
+
</nav>
|
| 176 |
+
|
| 177 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 178 |
+
{/* TAB: OVERVIEW */}
|
| 179 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 180 |
+
{tab === 'overview' && (
|
| 181 |
+
<div className="adoc-section">
|
| 182 |
+
<div className="adoc-overview-grid">
|
| 183 |
+
{/* Abstract */}
|
| 184 |
+
<div className="adoc-paper-card adoc-abstract">
|
| 185 |
+
<h2>Abstract</h2>
|
| 186 |
+
<p>
|
| 187 |
+
<strong>AgentIC</strong> is an agentic, LLM-driven autonomous chip design platform.
|
| 188 |
+
Given a naturalβlanguage specification, it generates synthesisable RTL, autoβheals
|
| 189 |
+
syntax and lint errors, runs formal property verification, closes coverage to
|
| 190 |
+
profileβbased thresholds, generates timing constraints, and drives physical
|
| 191 |
+
implementation through OpenLane to produce GDSII β all without human intervention.
|
| 192 |
+
</p>
|
| 193 |
+
<p>
|
| 194 |
+
The platform features a <em>selfβhealing orchestrator</em> that tracks perβstage
|
| 195 |
+
exception budgets, backs up the best testbench for antiβregression, and applies
|
| 196 |
+
bounded retry loops with deterministic fallbacks at every critical gate.
|
| 197 |
+
</p>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
{/* Key Capabilities */}
|
| 201 |
+
<div className="adoc-paper-card">
|
| 202 |
+
<h2>Key Capabilities</h2>
|
| 203 |
+
<div className="adoc-cap-grid">
|
| 204 |
+
{[
|
| 205 |
+
{ icon: 'π§ ', title: 'AIβDriven RTL Generation', desc: 'SystemVerilog or Verilogβ2005 from natural language via CrewAI agent chains.' },
|
| 206 |
+
{ icon: 'π', title: 'SelfβHealing Pipeline', desc: 'Perβstage guards, fingerprint dedup, bounded retries, and deterministic fallbacks.' },
|
| 207 |
+
{ icon: 'π', title: 'Formal Verification', desc: 'SVA generation, Yosys SBY integration, CDC heuristic checks.' },
|
| 208 |
+
{ icon: 'π', title: 'Coverage Closure', desc: 'Profileβbased (balanced / aggressive / relaxed) with antiβregression backup.' },
|
| 209 |
+
{ icon: 'ποΈ', title: 'Physical Implementation', desc: 'OpenLane integration with convergence review and ECO patch loops.' },
|
| 210 |
+
{ icon: 'β
', title: 'Silicon Signoff', desc: 'DRC/LVS, STA, power/IRβdrop, LEC checks before tapeout.' },
|
| 211 |
+
].map((cap) => (
|
| 212 |
+
<div className="adoc-cap-item" key={cap.title}>
|
| 213 |
+
<span className="adoc-cap-icon">{cap.icon}</span>
|
| 214 |
+
<div>
|
| 215 |
+
<strong>{cap.title}</strong>
|
| 216 |
+
<p>{cap.desc}</p>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
))}
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
{/* Quality Gates */}
|
| 224 |
+
<div className="adoc-paper-card adoc-full-width">
|
| 225 |
+
<h2>Quality Gates</h2>
|
| 226 |
+
<div className="adoc-table-wrap">
|
| 227 |
+
<table className="adoc-table">
|
| 228 |
+
<thead>
|
| 229 |
+
<tr>
|
| 230 |
+
<th>Gate</th>
|
| 231 |
+
<th>Check</th>
|
| 232 |
+
<th>SelfβHealing Response</th>
|
| 233 |
+
<th>Fallback</th>
|
| 234 |
+
</tr>
|
| 235 |
+
</thead>
|
| 236 |
+
<tbody>
|
| 237 |
+
{[
|
| 238 |
+
['Syntax', 'Verilator --lint-only', 'LLM auto-repair loop (bounded)', 'SVβVerilog strategy pivot'],
|
| 239 |
+
['TB Compile', 'Icarus iverilog compile', '3-cycle recovery (repair β regen β fallback)', 'Deterministic template TB'],
|
| 240 |
+
['Simulation', 'Runtime: TEST PASSED', 'Re-generate testbench with error analysis', 'Fingerprint dedup + skip'],
|
| 241 |
+
['Formal', 'SVA via Yosys SBY', 'Bounded SVA regeneration', 'Graceful degrade to coverage'],
|
| 242 |
+
['Coverage', 'Profile-based thresholds', 'LLM TB improvement + anti-regression', 'Restore best TB snapshot'],
|
| 243 |
+
['DRC/LVS', 'OpenLane signoff reports', 'ECO patch (gate β RTL fallback)', 'Fail-closed or pivot'],
|
| 244 |
+
].map(([gate, check, heal, fallback]) => (
|
| 245 |
+
<tr key={gate}>
|
| 246 |
+
<td><strong>{gate}</strong></td>
|
| 247 |
+
<td><code>{check}</code></td>
|
| 248 |
+
<td>{heal}</td>
|
| 249 |
+
<td>{fallback}</td>
|
| 250 |
+
</tr>
|
| 251 |
+
))}
|
| 252 |
+
</tbody>
|
| 253 |
+
</table>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
)}
|
| 259 |
+
|
| 260 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 261 |
+
{/* TAB: PIPELINE */}
|
| 262 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 263 |
+
{tab === 'pipeline' && (
|
| 264 |
+
<div className="adoc-section">
|
| 265 |
+
<div className="adoc-paper-card">
|
| 266 |
+
<h2>Build Pipeline β Stage Reference</h2>
|
| 267 |
+
<p className="adoc-meta-text">
|
| 268 |
+
The AgentIC orchestrator executes a deterministic stateβmachine pipeline.
|
| 269 |
+
Each stage has bounded retries, perβstage exception isolation, and configurable quality gates.
|
| 270 |
+
</p>
|
| 271 |
+
<div className="adoc-pipeline-list">
|
| 272 |
+
{stages.map((stage, idx) => (
|
| 273 |
+
<div className="adoc-pipeline-stage" key={stage.state}>
|
| 274 |
+
<div className="adoc-stage-num">{String(idx + 1).padStart(2, '0')}</div>
|
| 275 |
+
<div className="adoc-stage-connector" />
|
| 276 |
+
<div className="adoc-stage-body">
|
| 277 |
+
<div className="adoc-stage-header">
|
| 278 |
+
<span className="adoc-stage-icon">{stage.icon}</span>
|
| 279 |
+
<strong>{stage.label}</strong>
|
| 280 |
+
<code className="adoc-stage-key">{stage.state}</code>
|
| 281 |
+
</div>
|
| 282 |
+
<p className="adoc-stage-desc">
|
| 283 |
+
{stageDescriptions[stage.state] || 'Pipeline stage.'}
|
| 284 |
+
</p>
|
| 285 |
+
</div>
|
| 286 |
+
</div>
|
| 287 |
+
))}
|
| 288 |
+
</div>
|
| 289 |
+
</div>
|
| 290 |
+
|
| 291 |
+
{/* Flow Diagram */}
|
| 292 |
+
<div className="adoc-paper-card">
|
| 293 |
+
<h2>State Transition Flow</h2>
|
| 294 |
+
<div className="adoc-flow-diagram">
|
| 295 |
+
{stages.map((s, i) => (
|
| 296 |
+
<span key={s.state} className="adoc-flow-node">
|
| 297 |
+
<span className="adoc-flow-badge">{s.icon}</span>
|
| 298 |
+
<span className="adoc-flow-label">{s.state}</span>
|
| 299 |
+
{i < stages.length - 1 && <span className="adoc-flow-arrow">β</span>}
|
| 300 |
+
</span>
|
| 301 |
+
))}
|
| 302 |
+
</div>
|
| 303 |
+
<p className="adoc-meta-text" style={{ marginTop: '0.75rem' }}>
|
| 304 |
+
<strong>Convergence loops:</strong> SIGNOFF β ECO_PATCH β HARDENING β CONVERGENCE_REVIEW β SIGNOFF. <br />
|
| 305 |
+
<strong>Terminal states:</strong> SUCCESS, FAIL.
|
| 306 |
+
</p>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
)}
|
| 310 |
+
|
| 311 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 312 |
+
{/* TAB: CONFIGURATION */}
|
| 313 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 314 |
+
{tab === 'config' && (
|
| 315 |
+
<div className="adoc-section">
|
| 316 |
+
<div className="adoc-config-header">
|
| 317 |
+
<h2>Configuration Reference</h2>
|
| 318 |
+
<input
|
| 319 |
+
className="adoc-search"
|
| 320 |
+
type="text"
|
| 321 |
+
placeholder="Search parametersβ¦"
|
| 322 |
+
value={searchQuery}
|
| 323 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 324 |
+
/>
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
{filteredOptions.map((group) => (
|
| 328 |
+
<div className="adoc-paper-card adoc-config-group" key={group.name}>
|
| 329 |
+
<h3 className="adoc-config-group-title">{group.name}</h3>
|
| 330 |
+
<div className="adoc-table-wrap">
|
| 331 |
+
<table className="adoc-table adoc-config-table">
|
| 332 |
+
<thead>
|
| 333 |
+
<tr>
|
| 334 |
+
<th>Parameter</th>
|
| 335 |
+
<th>Type</th>
|
| 336 |
+
<th>Default</th>
|
| 337 |
+
<th>Range / Values</th>
|
| 338 |
+
<th>Description</th>
|
| 339 |
+
</tr>
|
| 340 |
+
</thead>
|
| 341 |
+
<tbody>
|
| 342 |
+
{group.options.map((opt) => (
|
| 343 |
+
<tr key={opt.key}>
|
| 344 |
+
<td><code className="adoc-param-key">{opt.key}</code></td>
|
| 345 |
+
<td><span className="adoc-type-badge">{opt.type}</span></td>
|
| 346 |
+
<td><code>{String(opt.default)}</code></td>
|
| 347 |
+
<td>
|
| 348 |
+
{opt.values
|
| 349 |
+
? opt.values.map((v) => (
|
| 350 |
+
<span className="adoc-enum-val" key={v}>{v}</span>
|
| 351 |
+
))
|
| 352 |
+
: opt.min !== undefined
|
| 353 |
+
? `${opt.min} β ${opt.max}`
|
| 354 |
+
: 'β'}
|
| 355 |
+
</td>
|
| 356 |
+
<td>{opt.description}</td>
|
| 357 |
+
</tr>
|
| 358 |
+
))}
|
| 359 |
+
</tbody>
|
| 360 |
+
</table>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
))}
|
| 364 |
+
</div>
|
| 365 |
+
)}
|
| 366 |
+
|
| 367 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 368 |
+
{/* TAB: DOCUMENTS (markdown reader) */}
|
| 369 |
+
{/* ββββββββββββββββββββββββββββββββββββββββββββββββββ */}
|
| 370 |
+
{tab === 'docs' && (
|
| 371 |
+
<div className="adoc-docs-layout">
|
| 372 |
+
{/* Left nav */}
|
| 373 |
+
<aside className="adoc-docs-nav">
|
| 374 |
+
<div className="adoc-docs-nav-title">Documents</div>
|
| 375 |
+
{sections.map(([section, items]) => (
|
| 376 |
+
<div className="adoc-docs-group" key={section}>
|
| 377 |
+
<div className="adoc-docs-section-label">{section}</div>
|
| 378 |
+
{items.map((doc) => (
|
| 379 |
+
<button
|
| 380 |
+
key={doc.id}
|
| 381 |
+
className={`adoc-docs-link ${selectedDoc === doc.id ? 'active' : ''}`}
|
| 382 |
+
onClick={() => setSelectedDoc(doc.id)}
|
| 383 |
+
>
|
| 384 |
+
<span className="adoc-docs-link-title">{doc.title}</span>
|
| 385 |
+
<span className="adoc-docs-link-sub">{doc.summary}</span>
|
| 386 |
+
</button>
|
| 387 |
+
))}
|
| 388 |
+
</div>
|
| 389 |
+
))}
|
| 390 |
+
</aside>
|
| 391 |
+
|
| 392 |
+
{/* Content */}
|
| 393 |
+
<main className="adoc-docs-content" ref={contentRef}>
|
| 394 |
+
{loading ? (
|
| 395 |
+
<div className="adoc-loading">
|
| 396 |
+
<span className="spinner" /> Loading documentβ¦
|
| 397 |
+
</div>
|
| 398 |
+
) : (
|
| 399 |
+
<article className="adoc-prose">
|
| 400 |
+
<ReactMarkdown
|
| 401 |
+
remarkPlugins={[remarkGfm]}
|
| 402 |
+
components={{
|
| 403 |
+
h1: ({ children, ...props }) => {
|
| 404 |
+
const id = String(children).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
| 405 |
+
return <h1 id={id} {...props}>{children}</h1>;
|
| 406 |
+
},
|
| 407 |
+
h2: ({ children, ...props }) => {
|
| 408 |
+
const id = String(children).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
| 409 |
+
return <h2 id={id} {...props}>{children}</h2>;
|
| 410 |
+
},
|
| 411 |
+
h3: ({ children, ...props }) => {
|
| 412 |
+
const id = String(children).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
| 413 |
+
return <h3 id={id} {...props}>{children}</h3>;
|
| 414 |
+
},
|
| 415 |
+
}}
|
| 416 |
+
>{content}</ReactMarkdown>
|
| 417 |
+
</article>
|
| 418 |
+
)}
|
| 419 |
+
</main>
|
| 420 |
+
|
| 421 |
+
{/* Right TOC */}
|
| 422 |
+
<aside className="adoc-toc">
|
| 423 |
+
<div className="adoc-toc-title">On This Page</div>
|
| 424 |
+
{toc.map((item, i) => (
|
| 425 |
+
<button
|
| 426 |
+
key={i}
|
| 427 |
+
className="adoc-toc-link"
|
| 428 |
+
style={{ paddingLeft: `${(item.level - 1) * 0.75 + 0.5}rem` }}
|
| 429 |
+
onClick={() => scrollToHeading(item.id)}
|
| 430 |
+
>
|
| 431 |
+
{item.text}
|
| 432 |
+
</button>
|
| 433 |
+
))}
|
| 434 |
+
</aside>
|
| 435 |
+
</div>
|
| 436 |
+
)}
|
| 437 |
+
</div>
|
| 438 |
+
);
|
| 439 |
+
};
|
| 440 |
+
|
| 441 |
+
/* ββ Stage descriptions ββββββββββββββββββββββββββββ */
|
| 442 |
+
const stageDescriptions: Record<string, string> = {
|
| 443 |
+
INIT: 'Create workspace directory structure, validate dependencies (Verilator, Icarus Verilog, OpenLane), and initialize build artifacts dictionary.',
|
| 444 |
+
SPEC: 'LLM generates a detailed architecture specification from the natural-language prompt, including module interfaces, FSM descriptions, and clock/reset requirements.',
|
| 445 |
+
RTL_GEN: 'Generate synthesizable RTL (SystemVerilog or Verilog-2005) from the architecture spec using a CrewAI RTL agent. Falls back to golden template library when available.',
|
| 446 |
+
RTL_FIX: 'Run Verilator lint, pre-synthesis semantic checks, and iterative LLM-based auto-repair. Supports strategy pivot (SV β Verilog-2005) when fixes stall.',
|
| 447 |
+
VERIFICATION: 'Generate self-checking testbenches, compile with Icarus Verilog, run simulation, and check for TEST PASSED. Includes TB static contract checking and fingerprint deduplication.',
|
| 448 |
+
FORMAL_VERIFY: 'Generate SVA properties, convert to Yosys SBY format, run formal property checking. Includes CDC heuristic analysis.',
|
| 449 |
+
COVERAGE_CHECK: 'Run coverage analysis with Verilator or Icarus backend. Compare against profile-based thresholds (line, branch, toggle, functional). Anti-regression guard restores best TB on coverage drop.',
|
| 450 |
+
REGRESSION: 'Generate and run multiple directed test scenarios (corner cases, reset stress, rapid fire) to verify robustness beyond basic functional verification.',
|
| 451 |
+
SDC_GEN: 'Generate SDC timing constraints from the RTL module interface. Auto-detects clock ports and applies post-processing to fix multi-port get_ports syntax.',
|
| 452 |
+
FLOORPLAN: 'LLM-driven floorplan estimation based on gate count, wire routing complexity, and PDK parameters. Produces die area and utilization targets.',
|
| 453 |
+
HARDENING: 'Generate OpenLane config.tcl and run the full RTL-to-GDSII flow inside Docker. Collects metrics.csv for convergence analysis.',
|
| 454 |
+
CONVERGENCE_REVIEW: 'Analyze PPA metrics (WNS, TNS, area, power, congestion) across iteration history. Determines whether to accept, resize die, or pivot strategy.',
|
| 455 |
+
ECO_PATCH: 'Apply engineering change orders β gate-level patch first, RTL micro-patch fallback. Re-enters hardening loop after successful application.',
|
| 456 |
+
SIGNOFF: 'Multi-dimensional check: DRC/LVS compliance, STA timing closure, power/IR-drop analysis, logic equivalence checking, and coverage re-validation.',
|
| 457 |
+
SUCCESS: 'Build completed β all quality gates passed. GDSII, metrics, and documentation artifacts are finalized.',
|
| 458 |
+
FAIL: 'Build terminated β one or more quality gates failed after exhausting retry budgets.',
|
| 459 |
+
};
|
web/src/pages/Fabrication.tsx
CHANGED
|
@@ -16,11 +16,11 @@ export const Fabrication: React.FC<FabricationProps> = ({ selectedDesign, hasGds
|
|
| 16 |
|
| 17 |
return (
|
| 18 |
<div className="page-container">
|
| 19 |
-
<h2
|
| 20 |
|
| 21 |
<div className="sci-fi-card" style={{ marginBottom: '20px' }}>
|
| 22 |
-
<h3
|
| 23 |
-
<p
|
| 24 |
|
| 25 |
<div style={{ display: 'flex', gap: '15px', marginTop: '15px', alignItems: 'center' }}>
|
| 26 |
<input
|
|
@@ -28,8 +28,8 @@ export const Fabrication: React.FC<FabricationProps> = ({ selectedDesign, hasGds
|
|
| 28 |
value={selectedDesign || 'No Design Selected'}
|
| 29 |
readOnly
|
| 30 |
style={{
|
| 31 |
-
background: '
|
| 32 |
-
padding: '10px', borderRadius: '
|
| 33 |
}}
|
| 34 |
/>
|
| 35 |
<button
|
|
@@ -43,18 +43,18 @@ export const Fabrication: React.FC<FabricationProps> = ({ selectedDesign, hasGds
|
|
| 43 |
</div>
|
| 44 |
|
| 45 |
<div className="sci-fi-card">
|
| 46 |
-
<h3
|
| 47 |
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
| 48 |
<button
|
| 49 |
className="btn-primary"
|
| 50 |
-
style={{
|
| 51 |
onClick={() => setViewMode('2D')}
|
| 52 |
>
|
| 53 |
2D Top-Down (SVG)
|
| 54 |
</button>
|
| 55 |
<button
|
| 56 |
className="btn-primary"
|
| 57 |
-
style={{
|
| 58 |
onClick={() => setViewMode('3D')}
|
| 59 |
>
|
| 60 |
3D Layer Stack
|
|
@@ -62,8 +62,8 @@ export const Fabrication: React.FC<FabricationProps> = ({ selectedDesign, hasGds
|
|
| 62 |
</div>
|
| 63 |
|
| 64 |
<div style={{
|
| 65 |
-
width: '100%', height: '400px', backgroundColor: '
|
| 66 |
-
border: '1px dashed
|
| 67 |
}}>
|
| 68 |
<p style={{ color: '#555', fontFamily: 'Fira Code' }}>
|
| 69 |
[{viewMode} Render Canvas Placeholder - Awaiting FastAPI GDS parser]
|
|
|
|
| 16 |
|
| 17 |
return (
|
| 18 |
<div className="page-container">
|
| 19 |
+
<h2 className="app-title">ποΈ Fabrication & GDSII</h2>
|
| 20 |
|
| 21 |
<div className="sci-fi-card" style={{ marginBottom: '20px' }}>
|
| 22 |
+
<h3>Tapeout Ready Files</h3>
|
| 23 |
+
<p className="app-subtitle">Download your final GDSII layout for physical manufacturing.</p>
|
| 24 |
|
| 25 |
<div style={{ display: 'flex', gap: '15px', marginTop: '15px', alignItems: 'center' }}>
|
| 26 |
<input
|
|
|
|
| 28 |
value={selectedDesign || 'No Design Selected'}
|
| 29 |
readOnly
|
| 30 |
style={{
|
| 31 |
+
background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)',
|
| 32 |
+
padding: '10px', borderRadius: 'var(--radius)', fontFamily: 'Fira Code', width: '300px'
|
| 33 |
}}
|
| 34 |
/>
|
| 35 |
<button
|
|
|
|
| 43 |
</div>
|
| 44 |
|
| 45 |
<div className="sci-fi-card">
|
| 46 |
+
<h3>Layout Viewer</h3>
|
| 47 |
<div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
|
| 48 |
<button
|
| 49 |
className="btn-primary"
|
| 50 |
+
style={{ border: '1px solid var(--border)', background: viewMode === '2D' ? 'var(--accent-soft)' : 'var(--bg)', color: viewMode === '2D' ? 'var(--accent)' : 'var(--text-mid)' }}
|
| 51 |
onClick={() => setViewMode('2D')}
|
| 52 |
>
|
| 53 |
2D Top-Down (SVG)
|
| 54 |
</button>
|
| 55 |
<button
|
| 56 |
className="btn-primary"
|
| 57 |
+
style={{ border: '1px solid var(--border)', background: viewMode === '3D' ? 'var(--accent-soft)' : 'var(--bg)', color: viewMode === '3D' ? 'var(--accent)' : 'var(--text-mid)' }}
|
| 58 |
onClick={() => setViewMode('3D')}
|
| 59 |
>
|
| 60 |
3D Layer Stack
|
|
|
|
| 62 |
</div>
|
| 63 |
|
| 64 |
<div style={{
|
| 65 |
+
width: '100%', height: '400px', backgroundColor: 'var(--bg)',
|
| 66 |
+
border: '1px dashed var(--border-mid)', borderRadius: 'var(--radius)', display: 'flex', alignItems: 'center', justifyContent: 'center'
|
| 67 |
}}>
|
| 68 |
<p style={{ color: '#555', fontFamily: 'Fira Code' }}>
|
| 69 |
[{viewMode} Render Canvas Placeholder - Awaiting FastAPI GDS parser]
|