Skip to main content
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:
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

Replace BOOT_OK with a message your firmware emits. For a repeatable check, save this as .embedder/hardware/sim_boot.py:
Run it through Embedder’s hardware-script runner. 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 can show simulated application output. Use 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:
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, live during recording or after xsim.trace_stop():
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. 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 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. 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.
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. 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:
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.
Last modified on September 18, 2026