epio
A cycle-accurate RP2350 PIO emulator
Loading...
Searching...
No Matches
epio - A cycle exact RP2350 PIO Emulator

While RP2350 PIO programs are conceptually simple and easy to write, testing their precise behaviour is hard:

  • They operate at high speed, with limited visibility into their internal state.
  • When modifying, it is difficult to prevent them from regressing without time consuming testing on real hardware. This is particularly problematic for testing multiple hardware revisions and configuration.

epio solves both of these problems, by providing a cycle-accurate emulator for RP2350 PIO programs, that can run on non-RP2350 hosts, including CI runners. This allows you to test and verify your PIO programs in a convenient and deterministic way, without needing to rely on real hardware.

It provides a `wasm` build, allowing you to run and visualise PIO programs in the browser, with a simple JavaScript API.

To make it easy to setup, epio integrates with apio, a runtime PIO assembler and disassembler. apio allows you to write your PIO programs with C macros directly in your RP2350 firmware (avoding the need for a separate pioasm step), and run them both on real RP2350 hardware and emulated using epio on non-RP2350 hosts.

epio includes comprehensive automated testing to ensure correct, cycle exact PIO emulation behaviour, and to avoid regressions.

Why epio?

Existing PIO emulators lack comprehensive support for RP2350 PIOs, which are more advanced than RP2040 PIOs and support additional instructions. A fully tested implementation is essential to ensure the emulation can be relied upon. Being C-based allows integration with existing bare-metal RP2350 firmware, and the design targets portability across a wide range of hosts, including WASM.

Video

This video shows what is possible with epio:

https://youtu.be/eNu0JAY1Dvc?si=N3-KCYJ04APH3RlB

Video Title

Interactive Demo

Try the interactive demo of the WASM example.

Usage

See the example for a complete bare-metal RP2350 firmware with a PIO, using apio and epio. This example can be built and run on both a real RP2350 and emulated on non-RP2350 hosts. Here is a snippet that demonstrates how to use epio to run a PIO program assembled with apio:

// Call the firmware main function, which returns instead of entering an
// infinite loop. This firmware_main() function uses apio to set up a PIO
// program, in both the firmware and emulation builds.
firmware_main();
// Create an epio instance and configure it from the apio state.
// Step the epio instance - cycling every enabled PIO SM exactly one cycle.
// Check the state of the GPIOs
uint64_t gpio_states = epio_read_pin_states(epio);
// ...
struct epio_t epio_t
Opaque epio instance type.
Definition epio.h:37
EPIO_EXPORT epio_t * epio_from_apio(void)
Create an epio instance configured from the current apio state.
EPIO_EXPORT void epio_step_cycles(epio_t *epio, uint32_t cycles)
Advance all enabled state machines by a number of clock cycles.
EPIO_EXPORT uint64_t epio_read_pin_states(epio_t *epio)
Read the current state of all GPIO pins.

Features

  • Cycle-accurate emulation of RP2350 PIO programs.
  • Auto-configureing if used in conjunction with apio.
  • Supports emulating all 12 PIO state machines running simultaneously.
  • Single, multi-step modes, and run until supported conditions are met.
  • Supports internal PIO IRQs.
  • Supports GPIOBASE=0 and 16, and up to 48 GPIOs to support both RP2350A and B.
  • Provides an SRAM API, so tests can simulate reading and writing to the RP2350's SRAM, based on PIO RX/TX FIFOs.
  • WASM build, allowing you to run emulated firmware and visualise PIO programs and GPIO states in the browser.
  • Comprehensive unit testing, with 100% of reachable code branches tested.

Documentation

Start here.

Source Code

See the GitHub repo.

Versioning

epio follows semantic versioning, with each release tagged in git. While the major version is 0, breaking changes are released in a new minor version, and additions and fixes in a new patch version. See CHANGELOG.md for what changed in each release.

main is not a release branch, and may take breaking changes at any time. Pin your project to a tag:

git clone --depth 1 --branch v0.2.3 https://github.com/piersfinlayson/epio.git

If your project clones epio from a Makefile, keep the version in a variable, so it is visible and easy to bump:

EPIO_VERSION ?= v0.2.3
epio:
@if [ ! -d "$@" ]; then \
git -c advice.detachedHead=false clone --quiet --depth 1 \
--branch $(EPIO_VERSION) https://github.com/piersfinlayson/epio.git; \
fi

Checking out a tag leaves the repository with a detached HEAD, which is what you want for a vendored copy - the contents are then frozen at that release. The -c advice.detachedHead=false just suppresses git's advice message about it, to keep the build output readable.

Note that this target will not update an existing checkout - after bumping EPIO_VERSION, remove the epio/ directory so it is re-cloned.

apio compatibility

epio v0.2.3 requires apio v0.3.0 or later. epio_from_apio() reads GPIO pull, drive strength and slew rate state that apio did not track before v0.2.0, and epio's tests stage RX FIFO words in a way that apio v0.3.0 changed.

epio's own build clones apio at the version given by APIO_VERSION in the [Makefile](Makefile). If your project already vendors apio, make sure it is pinned to a compatible version too - the two are built from source together, so a mismatch shows up as a compile error in apio.h rather than anything more helpful.

Requirements

In order to use epio for emulation, you can either use apio to set up your PIO programs, or manually configure the epio instance to match the state of your PIOs.

You must stub out any RP2350-specific non-PIO functionality that your program relies on when operating in the emulator, such as direct hardware register access, SDK usage, interrupts, etc. Direct hardware register, SRAM, and other peripheral access will cause faults on the host.

Limitations

There are currently some limitations in epio's PIO emulation. If you need a feature that isn't implemented yet, please raise an issue or submit a PR.

Current limitations:

  • No side-set pin support (delays are supported). SIDESET_BASE, SIDE_EN and SIDE_PINDIR are ignored.
  • No OUT_STICKY or inline OUT enable (INLINE_OUT_EN, OUT_EN_SEL).
  • MOVs to/from the RX FIFO are not supported (FJOIN_RX_PUT, FJOIN_RX_GET).
  • Does not support 2 cycle GPIO input delay via flip-flops to avoid meta-stability.
  • Only supports 4 word FIFOs (FJOIN_RX and FJOIN_TX are ignored).
  • Ignores clock divider settings.
  • EXEC_STALLED in EXECCTRL isn't reported.
  • A forced instruction that has to wait, for example a WAIT on a pin that is low, is skipped. On real hardware, the state machine waits until the instruction completes.
  • Limited DMA support - a pair of channels that reads an address from one SM's RX FIFO and writes the SRAM value there to another SM's TX FIFO, and a channel that drains an SM's RX FIFO into an SRAM ring.

WASM

epio provides a wasm build, allowing you to run and visualise PIO programs in the browser, with a simple JavaScript API.

Try the live demo.

The example is documented in the example README, and the source code is in the example directory.

See also the JS API for details.

License

MIT License, see [LICENSE](LICENSE).