> ## Documentation Index
> Fetch the complete documentation index at: https://docs.embedder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# XMOS xsim

> Run an XMOS application in simulation, inspect its output, and record instruction traces.

Use xsim to run an XMOS `.xe` application without an xTAG or physical board. Results describe the simulated target. They do not establish real-board electrical behavior or hardware timing.

## Set up a simulated target

1. Install XMOS XTC Tools with `xrun`, `xsim`, `xgdbserver`, and `xgdb`, and build the `.xe` application.
2. For a custom installation, set `EMBEDDER_XTC_PATH` to the XTC root or `bin` directory in the environment launching Embedder.
3. Select the simulator explicitly in `.embedder/xmos.json`:

```json theme={"system"}
{
  "xe": "bin/app.xe",
  "target": "simulator"
}
```

Replace the artifact path with your project-relative `.xe` path. Ask Embedder to check simulator readiness before starting. The installed Embedder build must include simulator support for your host; finding XTC Tools alone does not establish that the complete integration is ready.

## Run and inspect output

```text theme={"system"}
Run bin/app.xe in xsim, using the simulator selection in .embedder/xmos.json.
Wait up to 10 seconds for BOOT_OK, show the console output, and stop the
simulation. Do not use the connected physical board.
```

Replace `BOOT_OK` with a message your firmware emits. For a repeatable check, save this as `.embedder/hardware/sim_boot.py`:

```python theme={"system"}
xsim.start(xe="bin/app.xe")
xsim.wait_for_output(text="BOOT_OK", timeout_seconds=10)
print(xsim.console_log(max_chars=4000))
xsim.stop()
```

Run it through Embedder's [hardware-script runner](/debug-mode/hardware-scripts). A missing message raises a timeout; inspect the output and simulator state before assuming a firmware defect. Cleanup stops a simulation started by that script. A simulation it reused is not automatically stopped by cleanup.

The [xSCOPE Monitor](/debug-mode/xscope) can show simulated application output. Use [XMOS debugging](/debug-mode/xmos-debugging) with `simulator=True` when you need breakpoints, stepping, tiles, registers, or stack frames. Starting a debugger is a separate workflow and can replace a standalone simulation.

## Record an instruction trace

Start the trace before launching an ordinary simulator run:

```text theme={"system"}
Record an xsim instruction trace for bin/app.xe. Retain the latest 100000
instructions, stop after a short boot run, and summarize the active tiles,
logical cores, and functions. Show the result in the Trace tab.
```

The trace records retired instructions with tile, logical core, PC, symbol, and tile-clock cycle. It uses symbols from the `.xe`; no separate ELF is required. Ask Embedder to stop recording when the interval is complete, then inspect the Trace tab or request a summary.

The default retained window is one million instructions across tiles and cores. The instruction limit is a rolling window: older rows are removed as newer ones arrive, rather than stopping execution at that count. A recording has a ten-minute wall-clock limit. Boot-ROM tracing is off by default; request it explicitly when needed.

Cycle values describe the simulator's tile clock. Convert cycles to time only with the correct simulated clock frequency. They are not host elapsed time or a measurement of a physical board.

If an existing simulation was started without instruction tracing, trace startup fails instead of silently replacing it. Stop that simulation and start the trace first. Stopping a trace also stops a simulation launched by that trace; a compatible simulation that was already running remains available.

## Analyze the capture from Python

`xsim.trace_start()` returns a `session_id` you can query from [hardware scripts](/debug-mode/hardware-scripts), live during recording or after `xsim.trace_stop()`:

```python theme={"system"}
session = xsim.trace_start(max_instructions=200_000)
xsim.wait_for_output("BENCH_DONE", timeout_seconds=30)
xsim.trace_stop()

print(xsim.trace_summary(session))
page = xsim.trace_instructions(session, tile=0, after_index=-1, limit=1000)
print(xsim.trace_hotspots(session, group_by="line", tile=0, limit=50))
```

`trace_summary`, `trace_instructions`, and `trace_hotspots` accept `tile`, `core`, `start_cycle`, and `end_cycle`. Cycle ranges are half-open (`start_cycle <= cycle < end_cycle`) and require a tile because tiles have separate clocks. Hotspots group by `function`, `line`, or `address` and count actual retained rows; unresolved rows appear as a separate counter rather than silently changing the total. Instruction pages use absolute indices, so pass the returned `next_after_index` to page forward and check `cursor_evicted` and `window_truncated` when the rolling window advances. New captures freeze the `.xe` and extract one application ELF per tile for source-line lookup; legacy or attached captures without a frozen image report their source provenance as unavailable.

## Sim IO and pin driving

Configure the sampled pins with `sim.pins` in `.embedder/xmos.json`. The plugin samples them each tick and streams edges to the Sim IO tab. `sim.sampleMhz` controls the sampling rate and `sim.vcd` also writes `ports.vcd` for tile\[0].

From a hardware script you can subscribe to pins, force them, release them, and sample memory through `xsim.*` helpers. From the Monitor's Sim IO tab you can watch pins animate and force values live.

xsim fills memory that nothing has written with `0xBADDF00D`. Until xsim loads the image, which happens once the simulation runs, this includes the whole program. `xsim.read_memory` warns when it reads this fill. `xsim.write_memory` refuses to write there, because the image load would overwrite the write. Resume the simulation first, write at a debugger breakpoint, or patch the image with `write_memory` in [`xsim.run_batch`](#run-test-cases-in-parallel). Pass `force=True` to write memory the running program has not used yet.

Never put user paths with spaces in `sim.args`; those arguments travel through `--sim-args` and cannot be shell-quoted. Do not put `--max-cycles` in a debugger-owned simulator; xgdbserver never stops on it.

## Run test cases in parallel

Use `xsim.run_batch` from a [hardware script](/debug-mode/hardware-scripts) for a test gate with independent cases, such as one run per stimulus, vector set, parameter set, or seed. Embedder starts a separate plain xsim for each run, several at once, and waits for all of them. One failing case does not stop the others. To debug a failing case with breakpoints or traces, reproduce it on its own with `xsim.start()` or [XMOS debugging](/debug-mode/xmos-debugging).

A batch runs alongside the single managed simulator. It does not use or disturb `xsim.start`, the Sim IO tab, or the Trace tab. If the script disconnects, Embedder aborts the batch.

```python theme={"system"}
import os

cases = ["silence", "impulse", "tone_1k_m20", "tone_1k_m3"]
runs = [
    {
        "name": name,
        "files": {"stim.raw": f"tests/stimuli/{name}.raw"},
        "args": ["stim.raw"],
    }
    for name in cases
]

batch = xsim.run_batch(
    runs=runs,
    xe="bin/test/app_limiter_test.xe",
    name="limiter",
    done_pattern=r"TEST DONE",
    fail_pattern=r"TEST FAIL",
    read_memory=[{"symbol": "capture", "length": 4 * 4800, "save_as": "capture.bin"}],
    timeout_seconds=600,
)
print(batch["jobs"], batch["jobs_reason"], batch["counts"])

for run in batch["runs"]:
    print(run["name"], run["status"], run["ended_by"], f"{run['duration_s']:.0f} s")
    if run["status"] == "passed":
        with open(os.path.join(run["dir"], "capture.bin"), "rb") as f:
            capture = f.read()  # compare with your reference model
    else:
        print(run["console_tail"][-400:])
```

Each run is a dict with a unique `name` made of letters, digits, `.`, `_`, and `-`. Keyword options apply to every run that does not set them, and `defaults=` sets the same options as a dict. A run can also override `xe`.

| Option | Effect |
| - | - |
| `args` | Program argv, passed with `xsim --args`. Build the image with `-fcmdline-buffer-bytes=N` (for example `256`), otherwise `argc` is 0. |
| `files` | `{"name": "project/path"}` copies project files into the run directory. Firmware `fopen` calls resolve relative paths against the run directory, so files the firmware writes stay with the run. |
| `sim_args`, `max_cycles` | Extra xsim arguments, applied after `sim.args`, and a cycle limit. |
| `timeout_seconds` | Wall-clock limit per run (default 1800 seconds). |
| `done_pattern`, `fail_pattern` | Console regular expressions that end the run as passed or failed. |
| `done_memory` | `{"symbol": "test_done", "nonzero": True}` or `"equals": n`, with optional `tile`, `offset`, and `poll_ms` (default `1000`). Use `address` instead of `symbol` for a raw address. |
| `write_memory` | `[{"symbol": "stim", "file": "path"}]` or `"data": [...]`, with optional `tile` and `offset`. Embedder patches a per-run copy of the image, so the program starts with the data in place. |
| `read_memory` | `[{"symbol": "capture", "length": n, "save_as": "capture.bin"}]`, with optional `tile` and `offset`. Requires `done_pattern` or `done_memory`. Embedder saves the bytes in the run directory after a done condition matches. Each read is limited to 1 MiB. |
| `xscope` | `"off"`, `"offline"` (writes `xscope.xmt`), or `"realtime"` (writes probe records and prints to `xscope.jsonl`). Requires an image built with `-fxscope`. |
| `uploads` | Host-to-target data for `xscope_data_from_host`, as `[{"file": "path"}]` or `"data": [...]` with `delay_ms`. Requires `xscope="realtime"`. |
| `pins`, `force_pins`, `sample_mhz`, `vcd` | Sample package pins into `pins.jsonl`, force levels such as `{"X0D49": "high"}`, set the sampling rate, and write `ports.vcd`. |

Startup zeroes `.bss`, so Embedder refuses a `write_memory` target there and reports the run as `error`. Give the variable an initializer, such as `int32_t stim[N] = {1};`, to place it in `.data`.

### How a run ends

Each run reports a `status` of `passed`, `failed`, `max_cycles`, `timeout`, `cancelled`, or `error`, and `ended_by` names the condition that ended it:

* **Exit code:** a single-tile C `main` return value becomes the xsim exit code. `0` is `passed`. Any other value is `failed`.
* **`done_pattern` or `done_memory`:** the run is `passed`. Multi-tile and RTOS images cannot call `exit()`, so end them this way. Keep the program running after the marker, for example in an idle loop, so `read_memory` can dump the buffer.
* **`fail_pattern`:** the run is `failed`.
* **`max_cycles` or `timeout_seconds`:** the run reports `max_cycles` or `timeout`.

When `done_pattern` or `done_memory` is set, a run that exits before the condition matches is `failed`, and Embedder skips its `read_memory`. `passed` means the run ended as specified, not that its output is correct. Compare each run's output with your reference.

`keep_going=False` cancels every other run after the first one that does not pass. `check=True` raises `RuntimeError` if any run did not pass.

Each run writes its files to `.embedder/artifacts/xsim-batch/<batch>/<name>/`, returned as `dir`. The directory holds the staged files, `console.log`, `result.json`, memory dumps, and any xSCOPE, pin, or VCD output. The batch result includes `batch_id`, `batch_dir`, `jobs`, `jobs_reason`, `all_passed`, `counts`, `duration_s`, and `runs`.

### Set the number of parallel runs

By default, Embedder runs one fewer job than the number of performance cores on Apple silicon, or one fewer than the available CPUs elsewhere. Free memory also caps that count. The count, including any override below, never exceeds 64 jobs or the number of runs. Override it, in order of precedence, with:

1. `jobs=` on `xsim.run_batch`.
2. The `EMBEDDER_XSIM_JOBS` environment variable in the environment launching Embedder.
3. `sim.jobs` in `.embedder/xmos.json`:

```json theme={"system"}
{
  "xe": "bin/app.xe",
  "target": "simulator",
  "sim": { "jobs": 4 }
}
```

`jobs_reason` in the result reports which setting applied.

### Keep cases short

xsim runs hundreds of times slower than real time, so one simulated second can take many minutes. Each run also slows by about 30% when several run at once. Time one short run of your image first, then set `timeout_seconds` from that estimate with margin.

Split a gate into many short cases instead of one long one. Give each case the shortest stimulus that proves it, and get coverage from more cases that run in parallel. Start batch scripts as background hardware-script runs, because a foreground run stops after 60 seconds by default.

## Troubleshoot

* **No `.xe` found:** build the project and correct `xe` in `.embedder/xmos.json`.
* **Simulator support unavailable:** check that the installed Embedder build supports your host and that XTC Tools is complete.
* **Output wait times out:** inspect the console, paused/running state, and expected message; increase the bounded wait only when the firmware needs it.
* **Instruction tracing was not enabled:** stop the ordinary simulation and start the trace before the next run.
* **The beginning of the trace is missing:** use a shorter capture or a larger retained instruction window.
* **A simulation exits at its cycle limit:** check additional simulator arguments. A debugger-controlled simulation does not honor the standalone `--max-cycles` limit in the same way.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.