Bring up a behavioral (RTL) simulation end-to-end: stand up or fix a testbench, compile (xvlog/xvhdl), elaborate (xelab), run (xsim), and methodically diagnose compile errors, elaboration failures, $fatal/assertion failures, X-propagation, and hangs. Use when the user says "simulate this", "run the testbench", "set up simulation", "my sim fails / hangs / shows X", "why does my testbench not pass", "add a testbench and verify the DUT", or "debug this RTL in simulation". Requires the SynthPilot...
Scanned 9/8/2026
Install to Claude Code
npx -y skills add LNC0831/oh-my-fpga --skill sim-bringup --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sim Bringup?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lnc0831-sim-bringup)More formats (shields.io, HTML) on the badges page.
---
name: sim-bringup
description: >-
Bring up a behavioral (RTL) simulation end-to-end: stand up or fix a testbench,
compile (xvlog/xvhdl), elaborate (xelab), run (xsim), and methodically diagnose
compile errors, elaboration failures, $fatal/assertion failures, X-propagation,
and hangs. Use when the user says "simulate this", "run the testbench", "set up
simulation", "my sim fails / hangs / shows X", "why does my testbench not pass",
"add a testbench and verify the DUT", or "debug this RTL in simulation". Requires
the SynthPilot MCP server (AI -> server.py -> TCP:9999 -> tcl_server.tcl running
inside an open Vivado session). This is BEHAVIORAL sim only (no gate-level/timing
sim); it does NOT prove timing closure or signoff. Measure (compile log / sim log
/ probe) before changing anything, classify the failure, apply the smallest safe
fix, then re-run.
---
# Simulation Bring-Up (Behavioral / RTL)
A methodology playbook for getting a behavioral simulation **running and passing** on the SynthPilot xsim engine, and for diagnosing *why* it fails when it doesn't. The discipline is the same as timing closure: **observe with a tool, classify the symptom, apply the smallest safe change, re-run, and never claim a pass without fresh `sim_run` output that proves it.**
This skill drives the standalone xsim pipeline (`sim_compile` → `sim_run` → probe/diagnose), which runs `xvlog`/`xvhdl` → `xelab` → `xsim` directly and bypasses Vivado's `launch_simulation`. It does **not** require synthesis.
## When to use
- User says "simulate this", "run the testbench", "set up simulation", "verify this module in sim".
- A testbench exists but **fails to compile, fails to elaborate, fails its assertions, prints X, or hangs**.
- You need a behavioral testbench authored from scratch for a DUT and then run to a verdict.
- You want to inspect DUT internals over time (waveform-style) to understand a functional bug.
- You want functional code coverage of an RTL block (statement/branch/condition/toggle).
## When NOT to use
- **Timing signoff / timing closure / CDC / lint.** Behavioral sim has no notion of routed delays. Use the timing-closure / cdc-analysis / linting skills. A passing behavioral sim is **necessary, not sufficient** for signoff.
- **Gate-level or post-implementation (SDF) simulation.** This engine elaborates RTL behavioral models; it does not run timing-annotated netlists.
- **Hardware bring-up / on-chip debug** (ILA/VIO/JTAG). Use the hw-debug skill — `hw_ila_*`, `hw_vio_*`, `program_device`.
- **Pure RTL bug hunting with no testbench and no intent to run a sim** — if the user only wants static checks, prefer `check_syntax` / `run_quick_lint` (cheaper than a full compile).
## Prerequisites (verify first)
Run these *before* touching files. Do not assume state.
1. **MCP / Vivado link is alive** — `test_connection`. If it fails, STOP and tell the user to open Vivado and ensure the Tcl server (`tcl_server.tcl`) is running in the Tcl Console. Nothing below works without it.
2. **A project is open** — `get_project_info`. The xsim engine writes its scratch to `<project>/mcp_sim/` and resolves files via `get_files -compile_order sources -used_in simulation`; there must be a current project. If none, `open_project` / `create_project` (ask the user which).
3. **Know the design** — `list_source_files` and `list_simulation_files`. Confirm the DUT RTL is in the project and the intended testbench (if any) is in the **sim_1** fileset, not sources_1. A TB in the wrong fileset will silently be excluded from the sim compile order.
4. **Know the simulation top** — the testbench module name. `sim_compile(top_module=...)` takes it explicitly, so confirm the exact module name (case-sensitive) from the file, not a guess.
If any prerequisite is missing, resolve it (or ask) before proceeding. Do not fabricate a green result on a half-configured project.
## Methodology
One **change-class per iteration** so cause→effect stays attributable. The pipeline is strictly ordered: **author/locate TB → compile → elaborate → run → diagnose**. Each later stage assumes the earlier stage produced fresh, clean output.
### 0. Decide: author a testbench, or run an existing one?
- **Existing TB**: confirm it is in sim_1 (`list_simulation_files`); read it if needed (`read_file`) to get the exact `top_module` name. Skip to step 2.
- **No TB / author one**: continue to step 1.
### 1. Author a testbench (only if one does not exist)
Prefer structured, machine-checkable output over `$display` soup — it gives a clean pass/fail verdict the parser can read.
1. `sim_setup_report` — deploys `mcp_report.vh` into the project sim dir and tells you the `` `include `` directive. (`sim_compile` also auto-deploys it, but calling this first documents the macros: `` `MCP_INIT ``, `` `MCP_ASSERT ``, `` `MCP_ASSERT_EQ ``, `` `MCP_SIGNAL ``, `` `MCP_LOG ``, `` `MCP_FINISH ``.)
2. Write the TB with `create_testbench_file(file_name="tb_<dut>.v", content=...)` — this writes the file **and** adds it to the sim_1 fileset in one step. Inside the module: `` `include "mcp_report.vh" ``, call `` `MCP_INIT `` in the initial block, drive stimulus, check with `` `MCP_ASSERT_EQ(name, actual, expected) ``, and call `` `MCP_FINISH `` then `$finish`.
3. Testbench hygiene (you are responsible for these — they are the #1 cause of false fails and hangs):
- **Always have a finite end** — a `$finish` reached by a counter/timeout, or use `sim_run(run_time="<bounded>")`. A free-running clock with no `$finish` will hang `run all`.
- **Reset before checking.** Assert reset for ≥1 clock, deassert, *then* sample. Checking during reset yields X and false FAILs.
- Use `===`/`!==` (4-state) for X-aware comparisons; `` `MCP_ASSERT_EQ `` already uses `===`.
- Match the DUT port widths/names exactly — width mismatches elaborate as truncation, not errors.
If the user asks you to *change the DUT RTL* to make a test pass, treat that as a design change: **recommend** the edit and ask before applying it (see Safety rails). Fixing the *testbench* is in-scope; silently rewriting the DUT to chase a green is not.
### 2. Compile + elaborate
`sim_compile(top_module="tb_<name>")`.
This compiles all sim-used files (VHDL via `xvhdl`, Verilog/SV via `xvlog`), auto-discovers `glbl.v` and include dirs, defines `MCP_SIM_REPORT`, then elaborates with `xelab` into snapshot `mcp_snapshot`. Read the **status token** on the first line:
| Token | Meaning | Next action |
|---|---|---|
| `COMPILE_OK: ...` | xvlog/xvhdl + xelab succeeded | Go to step 3 (run). Note `warnings=N` — investigate if non-trivial. |
| `COMPILE_ERROR: N errors in xvlog/xvhdl` | Syntax / file / language error before elaboration | `sim_get_compile_log(stage="compile")`, classify (see table), fix, re-`sim_compile`. |
| `ELABORATE_ERROR: N errors in xelab` | Compiled, but linking the hierarchy failed (missing module, unresolved instance, library, param) | `sim_get_compile_log(stage="elaborate")`, classify, fix, re-`sim_compile`. |
| `SIM_LOCKED: ...` | A stale `xsimk.exe` kernel is locking the snapshot; xelab can't rebuild it | `sim_stop()` (kills the kernel + clears the lock), then re-`sim_compile`. Do **not** edit RTL — this is not a code error. |
Fix **one error class at a time**, then re-compile. Do not batch unrelated fixes — a single syntax error often cascades into dozens of phantom downstream errors that vanish once the first is fixed.
### 3. Run
Choose by expected runtime:
- **Short (< ~30 s wall):** `sim_run(run_time="all", detail_level=1)`. Returns the parsed test verdict (`detail_level`: 0=summary, 1=+failures, 2=+signals at failure times, 3=full JSONL) plus captured `$display` output.
- **Long / multi-minute:** `sim_run_async(run_time="all")`, then poll `sim_get_sim_status()` until it reports `COMPLETED` (it returns `RUNNING` with a tail + elapsed time while in progress). This is the required pattern for long sims — never block on `sim_run` for minutes.
- If `sim_run` returns a **timeout** message, the sim may still be running: re-issue via `sim_run_async` + `sim_get_sim_status`, or read progress with `sim_get_compile_log(stage="sim")`.
Read the result header:
- `=== Simulation Completed ===` with `tests=P/T, 0 failed` → candidate pass. Verify per Safety rails before claiming success.
- `=== Simulation Error ===`, any `FAIL`, `$fatal`, or `ERROR:` lines → go to step 4.
### 4. Diagnose (only when a stage failed or the verdict is FAIL)
Classify with the table below **before** changing anything. Pull the evidence that matches the failing stage:
- Compile/elab errors → `sim_get_compile_log(stage="compile"|"elaborate")`.
- Run-time failures / X / wrong values → `sim_get_report(detail_level=2)` for failure times + signals, then `sim_probe(...)` to watch the offending signals across the relevant window, and `sim_list_signals(scope=...)` first if you are unsure of exact hierarchical paths (escaped generate-block names are common).
- Hang/timeout → there is no `$finish` or the stimulus never satisfies the exit condition; inspect the TB and bound the run.
## Classification table (symptom → cause → smallest safe fix)
Apply the **first** fix that addresses the actual cause. Testbench and config fixes come before any DUT RTL change.
| Stage / Symptom | Likely cause | Smallest safe fix (in order) |
|---|---|---|
| `COMPILE_ERROR`: `syntax error`, `unexpected token` | TB or RTL syntax; SV used without `--sv` | Read the cited file:line (`read_file_lines`). Fix the source. If SV constructs in a `.v` file: `sim_compile(extra_args="--sv")`. Optionally pre-check with `check_syntax_file`. |
| `COMPILE_ERROR`: `cannot find include file` | `` `include "mcp_report.vh" `` placed before the module, or header not on a discovered include path | Move the include **inside** the module. Ensure headers are registered as `Verilog Header` file type so `sim_compile` adds their dir via `-i`. |
| `COMPILE_ERROR`: missing `` `define ``/macro | A required Verilog define isn't set for sim | `add_verilog_define` / `set_verilog_defines`, or pass `sim_compile(extra_args="-d NAME=VALUE")`. State the assumption you're making about the define's value. |
| `ELABORATE_ERROR`: `module <X> not found` / `instance ... has no ...` | DUT or a sub-module isn't in the sim compile order; wrong module name | `list_simulation_files` + `list_source_files`. If RTL missing from sim, `add_source_file`/`add_simulation_file`; check compile order with `check_compile_order`. Verify exact module name (case-sensitive). |
| `ELABORATE_ERROR`: unresolved Xilinx primitive / `unisim` / IP black box | IP behavioral model or library not pulled in; IP outputs not generated | Confirm IP outputs exist (`generate_ip_outputs` / `report_ip_status`). Pass extra libs via `sim_compile(extra_elab_args="-L <lib>")`. (`glbl.v` and the standard unisim/xpm libs are added automatically.) |
| `ELABORATE_ERROR`: port-width / param mismatch | TB instantiates DUT with wrong width/param | Fix the TB instantiation to match the DUT. Optionally confirm with `check_port_width_mismatch`. |
| `SIM_LOCKED` | Stale `xsimk.exe` from a prior async run holds the snapshot lock | `sim_stop()` then re-`sim_compile`. Never an RTL fix. |
| Run: `FAIL` with expected≠actual | Real functional mismatch — DUT bug **or** wrong expected value in TB | First confirm the TB's expected value and timing are correct (sample after reset, on the right edge). `sim_probe` the DUT inputs/outputs around the fail time. If TB is correct and DUT is wrong, **recommend** the RTL fix — do not silently patch the DUT. |
| Run: outputs are `X`/`Z` | Uninitialized reg, missing reset, multiple drivers, unconnected port, or sampling during reset | `sim_probe` from t=0 to find where X originates. If TB sampled too early → fix TB (delay/reset). If RTL has an uninitialized/unreset reg or a real X source → `check_latches`/`check_async_reset` and **recommend** the RTL fix. |
| Run: hang / timeout, no `$finish` | Free-running clock with no terminating condition; stimulus never reaches exit | Bound it: `sim_run(run_time="<finite>")`. Then fix the TB to reach `$finish` deterministically (counter/timeout). If async already launched and stuck: `sim_stop()`. |
| Run: `$fatal` / assertion from RTL or TB | An RTL `assert`/`$fatal` fired, or a TB self-check `` `MCP_ASSERT `` failed | `sim_get_report(detail_level=2)` for the time + message, `sim_probe` the relevant signals at that time, classify as TB-expectation vs real DUT bug, then fix the responsible side. |
| Pass but suspiciously fast / 0 tests | TB exits before exercising anything, or `` `MCP_INIT ``/`` `MCP_FINISH `` missing so no JSONL was produced | Check `tests=P/T`. If `T==0`, the TB ran no assertions — fix the TB to actually drive + check. A "pass" with zero assertions is **not** a pass. |
## Diagnose → fix → re-run loop (with STOP conditions)
```
1. RUN the current stage (sim_compile, then sim_run / sim_run_async+status).
2. READ the status token / verdict (fresh tool output, every iteration).
3. If clean and verdict = pass with tests > 0 -> go to Output (success path).
4. Else CLASSIFY via the table using stage-appropriate evidence
(sim_get_compile_log / sim_get_report / sim_probe / sim_list_signals).
5. Apply ONE smallest-safe fix (config/TB before DUT RTL).
6. Re-run the SAME stage. Record before/after (status token, error count,
tests P/T) so the change's effect is attributable.
7. Repeat.
```
**STOP and hand back to the human when:**
- The remaining fix requires a **DUT RTL or architectural change** — present the exact change, the evidence (probe/report), and the trade-off; let the user approve. Do not silently edit the DUT.
- A `FAIL`/`X`/`$fatal` could reflect a **real DUT bug** and the correct expected behavior is ambiguous — ask the user for the intended behavior rather than tuning the TB to mask it.
- Two consecutive iterations produce **no net improvement** (same error count, same failing test) — stop thrashing; report what you tried and what's left.
- You'd need to **delete/weaken the testbench's checks** (remove an assertion, loosen a comparison, shrink the run so a failing region never executes) to get green. That is a fake-pass — refuse and surface it.
- A prerequisite turns out to be missing mid-flow (project closed, connection dropped) — re-run prerequisites, don't paper over it.
## Safety rails (do not violate)
1. **Evidence before claims.** Never say "simulation passes" / "the DUT works" without a **fresh `sim_run`/`sim_get_sim_status` result** in this session showing `=== Simulation Completed ===`, `0 failed`, **and** `tests > 0`. A successful *compile* (`COMPILE_OK`) is **not** a passing simulation — it only proves it built.
2. **No fake-pass.** Do not get to green by deleting/weakening assertions, loosening `===` to `==`, shortening the run so a failing window is skipped, ignoring `$fatal`/`X`, or declaring a 0-assertion run a "pass." If a check is genuinely wrong, fix it transparently and say why.
3. **Smallest safe change first.** Order of preference: testbench/stimulus fix → compile/elab args & defines → file/fileset/compile-order fix → (only with user approval) DUT RTL change. Never jump to editing the DUT to chase a verdict.
4. **Recommend RTL edits, never silently apply them.** TB edits are in-scope. DUT/architectural edits get presented with evidence and a trade-off, then wait for the user.
5. **One change-class per iteration.** So you can attribute the effect. Re-measure after each change; show before/after.
6. **Behavioral ≠ signoff.** A passing behavioral sim says nothing about timing, CDC, DRC, or routability. Never imply otherwise; point to the appropriate signoff skill.
7. **Long runs go async.** Use `sim_run_async` + `sim_get_sim_status` for multi-minute sims; do not block. If a kernel is stuck or `SIM_LOCKED` appears, `sim_stop()` to recover — and note it kills *all* xsim kernels on the machine.
8. **Real tools only.** Use only the SynthPilot tool names above. If a needed capability (e.g., SDF/gate-level sim, UVM) isn't available, say so plainly rather than inventing a tool.
## Output
Report back in this shape:
```
SIMULATION BRING-UP — <tb_top> on <project>
Pipeline status:
Compile/Elaborate : COMPILE_OK (files=<n>, vhdl=<n>, warnings=<n>) | or COMPILE_ERROR/ELABORATE_ERROR/SIM_LOCKED
Run : Completed | Error | Hang(bounded)
Verdict : <P>/<T> tests passed, <F> failed (tests=0 => NOT a pass)
Evidence (fresh, this session):
- sim_run / sim_get_sim_status: <header line + tests line>
- <sim_get_report / sim_probe / compile log excerpt for any failure>
Failures diagnosed (if any):
- <symptom> -> <classified cause> -> <fix applied> (before: <metric>, after: <metric>)
Changes I made:
- <testbench / fileset / compile-arg edits — list them>
Changes I recommend (NOT applied — need your call):
- <DUT RTL / architectural edits, each with the probe/report evidence and trade-off>
Open questions / STOP reason (if handing back):
- <ambiguous expected behavior, exhausted safe options, etc.>
```
State explicitly whether the simulation **passes with a non-trivial set of assertions** (the only thing that counts as success here) or whether it merely compiled. Never round a compile-clean-but-unrun design up to "working."
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!