Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 54 of 56

The runtime: how it is built, ported and proven conformant

Three profiles generated from a forked bb-runtimes board, a porting checklist that is shorter than you would expect — and an ACATS sweep that grades the result one test per image.

Where the runtime comes from

Step 8 covered choosing a profile. This is where they come from. The runtime is not vendored as a binary: it is generated from a fork of AdaCore's bb-runtimes carrying a new esp32s3 board, and built on demand by gen_runtime.sh as the crate's Alire pre-build action.

ESP32S3_RTS_PROFILE=embedded   # light-tasking | embedded | full
#  -> crates/esp32s3_rts/embedded-esp32s3/  (adainclude + adalib)

Two things it needs, both supplied for you: XTENSA_GNU_CONFIG, set by the xtensa_dynconfig dependency, which selects the ESP32-S3 core configuration (little-endian, 64 address registers, FPU); and the cross toolchain plus gprbuild on PATH. It is idempotent — it regenerates only when the output is missing.

The crate is not published. You consume it via an Alire pin, which is why every example's alire.toml carries a [[pins]] entry pointing at crates/esp32s3_rts, and why an out-of-tree project resolves it through GPR_PROJECT_PATH instead (step 10).

The trap that will bite you

Editing a runtime source does not rebuild anything. The heavy compile-and-archive step runs only when the output is missing, so changing a file under bb-runtimes/ or full_overlay/ leaves the previous archive in place — the next build links the old one and your change silently does nothing. The book records that this has caught the authors more than once.

Delete the archives to force the rebuild:

rm crates/esp32s3_rts/<profile>-esp32s3/adalib/libgnat.a    crates/esp32s3_rts/<profile>-esp32s3/adalib/libgnarl.a

Or remove the generated tree entirely, as step 7's tips suggest.

Three profiles, three system.ads

The profiles differ by restriction set, expressed as different system-xi-xtensa-*.ads variants: light-tasking adds No_Exception_Propagation and No_Finalization; embedded lifts both (ZCX); full is the unrestricted GNARL, built from an overlay plus patches on top of the same board.

The register packages are separate again: ESP32S3_Registers.* is generated from the chip's SVD by svd2ada, not hand-written — which is why the drivers get typed record fields with representation clauses instead of shift-and-mask.

Porting to another Xtensa SoC

The structure makes the checklist short:

  1. Add a board class under bb-runtimes/xtensa/ — target triple, clock, interrupt range, SMP flag.
  2. Supply the five __<board> bodies: parameters, board support, console, reset, interrupt names.
  3. Write the two system-xi-xtensa-*.ads variants, or reuse these if the restriction set is the same.
  4. Regenerate the register packages from that chip's SVD.
  5. Point gen_runtime.sh at the new board name.

What that list deliberately excludes is the boot path — the 2nd-stage loader, clock and cache bring-up, PSRAM (step 6). Those are chip-specific and are the larger job; the runtime port is the small half.

ACATS: conformance, not confidence

A hand-written runtime either implements Ada or approximates it, and the difference is not something a test suite of your own devising can settle. So the runtime is run against the official ACATS 4.2 suite on real hardware.

ProfileTest listPASSGenuine failures
light-taskingjorvik_hw_runnable (846)~7000
embeddedjorvik_hw_runnable (846)840+0
fullfull_applicable (1,518)1,286+0

"Zero genuine failures" needs its qualifier stated, not buried: every non-passing test is an interactive test needing a bench-generated stimulus, a build-drop (a library unit the bare runtime omits), a correct NOT-APPLICABLE, or a documented limitation. That is a different claim from "everything passes", and the honest version is the useful one.

How the sweep works

The interesting engineering is the harness. Each test becomes one image containing exactly one test, built in parallel, then flashed and graded across a pool of boards. Two details make it work:

The ACATS suite and its sweep harness (acats_build.py, acats_run.py, the ACATS/ tree) are not shipped in this distribution — they live in the development repository. The book's ACATS chapter has the full breakdown if you want the detail.