PPABench / Benchmarks / MCU B

MCU B Open ISA — AES-128-CBC over random input

A verify-by-result track with no fixed ISA. You are not handed a binary to run — you are handed a behavior: encrypt random plaintext under a random key delivered at runtime and prove the ciphertext on UART. Any instruction set, any microarchitecture, even a programmable crypto datapath, as long as the bytes come out right and the chip hardens clean into the locked chassis. Ranked by energy per byte, not IPC.

PPABench harness drives Wishbone CTRL.start counts wb_clk_i externally checks output vs golden random input per run QSPI flash boot image + injected input UART host results / proof out OpenFrame / chipignite chassis user_project_wrapper 🔒 LOCKED Agent IP the only thing you design RV32I core · open-ISA · crypto datapath · … 🔒 Wishbone ctrl 🔒 done / error 🔒 QSPI flash 🔒 UART 🔒 GPIO map @0x30000000 io[35:36] io[24:29] io[30:31] io_in/out/oeb[37:0] chipignite 44-pad ring → sky130 MPW shuttle 🔒 Locked — identical for every submission Your design
The chassis is fixed; you design only the core inside it. Every submission wears the same user_project_wrapper with the same locked pinout — Wishbone control, QSPI flash, UART, and the GPIO map are identical for everyone — so results are directly comparable and the design is manufacturable on the chipignite shuttle.

Overview

MCU A locks the ISA to RV32I so that two submissions retire the same instruction stream and IPC becomes a fair contest. MCU B does the opposite. It removes the ISA from the contract entirely. The deliverable is defined purely by input → output behavior: build a chip that reads random 16-byte plaintext blocks delivered at runtime, encrypts them with AES-128 in CBC mode under a random key and IV, and emits the resulting ciphertext plus a digest on UART. How you get there is your design space. A classic soft core running C. A custom ISA tuned for the inner loop. A tiny sequencer driving a hardwired AES round datapath. A wide programmable crypto engine. All are legal, because the grader never inspects how the answer was produced — only that the right ciphertext appears for inputs the submitter could not have seen at design time.

The chip still has to be real. The same harden flow that grades every PPABench track applies here: yosys synthesis → OpenROAD place & route + STA → Magic DRC → netgen LVS, hardened into the fixed user_project_wrapper pinout on sky130 HD. A submission that produces correct ciphertext in a Verilator run but cannot route, fails timing, or breaks LVS does not score. Correctness and silicon-realizability are both gates, not bonuses.

Why open ISA changes the metric: cycles, not IPC

IPC (instructions per cycle) is a perfectly good figure of merit on MCU A and a meaningless one here. The reason is structural. IPC only compares across designs when the instruction stream is held constant. The moment the ISA is free, "instruction" stops being a fixed unit of work. A CISC-leaning ISA retires fewer, fatter instructions — one op might do a full AES column mix that a RISC core spreads across a dozen. A submission could win on IPC while doing strictly more work, or lose on IPC while being the faster, leaner chip. Counting instructions across different ISAs is counting in different currencies.

So MCU B drops IPC and ranks on quantities that are ISA-invariant for a fixed workload:

  • Cycles to complete the fixed AES-128-CBC workload, counted externally at the wrapper boundary.
  • Energy per byte — total energy to encrypt the workload divided by plaintext bytes. This is the primary ranking key.
  • Cycles per block and EDP (energy-delay product) as secondary efficiency views.

The workload is fixed even though the ISA is not. Both submissions encrypt the same number of bytes through the same cipher, so cycles and joules are directly comparable no matter how the work is sliced into instructions. Contrast this explicitly with MCU A · fixed RV32I, where the ISA lock is what makes IPC legitimate; here the ISA freedom is what makes IPC illegitimate. Same benchmark family, opposite metric, for the same underlying reason: you can only divide by "instructions" when everyone agrees what an instruction is.

The challenge

The functional task is AES-128-CBC encryption over N random plaintext blocks under a random key and IV. Concretely, per run the testbench:

  • draws a random 128-bit key, a random 128-bit IV, and N random 16-byte plaintext blocks (the reference workload is N = 4 blocks, 64 bytes);
  • injects those bytes into the flash image the design boots from, so the key/IV/plaintext only exist at runtime — they are not present when the design is built;
  • starts the design and lets it run to completion, expecting the final ciphertext and a digest emitted on UART.

CBC chaining is the deliberately hard part. In CBC, each plaintext block is XORed with the previous ciphertext block before the AES core runs, so block k+1 cannot begin until block k is finished. The chain is strictly sequential. That kills the obvious shortcut of precomputing all blocks in parallel ahead of time, and it means a submission's throughput is bounded by its real per-block AES latency, not by how many blocks it can fan out. The digest emitted alongside the ciphertext lets the testbench check the whole output cheaply and catch partial or truncated runs.

Anti-cheat — the core idea

The entire benchmark is built so that the only way to score is to actually compute AES. Four mechanisms stack:

  1. Random input per run. The key, IV, and plaintext are drawn fresh and injected into the flash image at runtime. There is no fixed answer to hardcode, because the correct ciphertext is a function of inputs that did not exist when the design was submitted. A lookup table of canned outputs is useless — it would have to contain a 2128-entry map.
  2. Software golden re-derivation. The grader runs a reference AES implementation over the same random key/IV/plaintext and re-derives the expected ciphertext independently. The DUT's UART output is compared byte-for-byte against that golden. There is no "self-reported correct" path.
  3. K fresh random trials, all must pass. Functional credit requires exact-match across K independent random trials, each with new key/IV/plaintext. A design that happens to handle one input but not the general case fails. Passing means the design implements the cipher, not a special case.
  4. Externally-counted cycles. The graded cycle count is wb_clk_i edges counted by the testbench from CTRL.start to STATUS.done at the wrapper boundary — never a counter the DUT reports about itself. The design cannot under-report its own runtime; the clock is counted outside the chip.

This is precisely the structural defense against the classic benchmark fraud. A Dhrystone-style design that detects the benchmark and emits a memorized answer fails here, because the answer changes every run and is checked against an independent golden. A design that fakes a fast cycle count fails, because cycles are counted externally. To score, you must build hardware that takes arbitrary key/IV/plaintext and emits the correct CBC ciphertext within the cycle and PPA budget — there is no path that skips the computation.

Held-out variant. Alongside the public AES task, PPABench keeps a held-out keyed challenge — a private keyed function exercised through the exact same machinery (random runtime input, external cycles, golden re-derivation). It is run off-leaderboard to check for contamination and overfitting: a submission that only "works" because it was tuned to the public task will diverge on the held-out challenge. The specific function is intentionally not disclosed on this page.

Interface & protocol

MCU B uses the same locked chassis as the rest of PPABench — the fixed user_project_wrapper pinout on sky130 HD. The relevant pins and registers:

  • QSPI flash on io[24:29] — the design boots and executes from this flash, and it is also the channel the testbench uses to inject the per-run key, IV, and plaintext into the flash image (input injection + program).
  • UART on io[30:31] — the proof channel; the ciphertext and digest are decoded here.
  • Wishbone CTRL/STATUS at 0x30000000CTRL.start kicks the run, STATUS.done reports completion.
  • done on io[35] (and error on io[36]) — the wrapper-boundary completion signal that bounds the externally-counted cycle window.

Per-trial flow as the hidden testbench runs it:

# MCU B — one random trial (repeated K times, all must pass) seed = rng() # fresh per trial — never seen at design time key, iv = rand(128), rand(128) # random AES-128 key + CBC IV pt = rand_blocks(N=4) # N random 16-byte plaintext blocks inject_into_flash(image, key, iv, pt) # bytes land in QSPI image at runtime, io[24:29] wb_write(0x30000000, CTRL.start) # start — begin counting wb_clk_i edges NOW run_until(STATUS.done | io[35]) # external clock count, start -> done ct_dut, dg = uart_decode(io[30:31]) # ciphertext + digest off UART ct_gold = aes128_cbc(key, iv, pt) # software golden, re-derived independently assert ct_dut == ct_gold # exact match or trial fails cycles += edges_counted() # graded cycles — never a DUT register

Grading

A submission must clear every gate, then it is ranked on efficiency:

  • Functional — exact-match ciphertext vs the software AES golden across all K fresh random trials. Any miss fails the track.
  • Performancecycles, cycles/block, energy/byte (pJ/byte), total energy, and EDP. Energy/byte is the primary ranking key.
  • PPA caps — area, power, and the other harden KPIs must sit within the track's caps.
  • Timing — STA must close with WNS ≥ 0 at the target clock.
  • DRC / LVS — Magic DRC-clean and netgen LVS-match, hardened into the locked wrapper.

Everyone who passes is, by construction, a real chip that actually encrypts. Ranking among passers is by efficiency — lower energy/byte ranks higher.

Reference baseline — picosoc + tiny-AES-c

The reference runs AES-128-CBC entirely in software on the stock picosoc (picorv32 RV32IMC core, spimemio QSPI XIP, simpleuart) using the tiny-AES-c library — no crypto acceleration of any kind. These are real numbers: functional matched on 3/3 random seeds; cycles and PPA from the live Verilator/cocotb harden + sim flow.

MCU B Reference baseline · picosoc + tiny-AES-c

AES-128-CBC, 4 random blocks (64 B) · energy = routed PPA × sim cycles @ 50 MHz
MetricValueNotes
Functional✓ 3/3exact-match vs software golden, 3 fresh random seeds
Cycles (4 blocks)1,088,825externally counted, start → done
Cycles / block272,206AES round logic emulated in software
pJ / cycle3.54P/f × 1000 @ 50 MHz
Energy3,854 nJP × cycles / f
Energy / byte60,226 pJprimary ranking key
Area3,810 µm²routed sky130 HD
fmax88.5 MHz8.7 ns slack at 50 MHz target
PPA figures are the reference routed picosoc harden; energy = reference routed PPA × the real sim cycles at 50 MHz. Held-out keyed challenge run off-leaderboard for contamination checks.
This baseline is a floor, not a ceiling. The reference grinds every AES round through a general-purpose integer core: ~272k cycles per 16-byte block, ~60 nJ per byte. A purpose-built submission has enormous headroom — a hardware AES round datapath, T-table SubBytes/MixColumns, a pipelined key schedule, and a wider word can cut cycles-per-block by orders of magnitude and energy/byte with them. The reference exists to anchor the metric, not to set the bar.

What a strong submission looks like

The headroom over the software baseline is almost entirely about moving the AES round into hardware. Strong directions, and their costs:

  • Hardware AES rounds. A combinational or lightly-pipelined round (SubBytes via S-box ROM or composite-field logic, ShiftRows as wiring, MixColumns as fixed GF(28) arithmetic) collapses thousands of instructions per block into a handful of cycles. This is the single biggest lever on both cycles/block and energy/byte.
  • Key-expansion pipelining. AES-128 needs 11 round keys; precomputing the key schedule once and overlapping its expansion with the data path removes it from the per-block critical path. With one fixed key across the CBC workload, the schedule is amortized to near-zero.
  • Wider datapath. A 128-bit state path processes a full block per round instead of marshaling it 32 bits at a time, slashing cycle count — at the cost of area and a tougher timing closure.

Every one of these is a trade against the PPA caps. A fully-unrolled AES pipeline is fast and energy-cheap per byte but area-hungry and can stress timing; a compact iterative round is small and easy to close but slower. Because CBC is sequential, throughput-via-parallelism is off the table — the win comes from cutting per-block latency and the energy each round burns, not from running blocks side by side. The interesting submissions are the ones that find the best energy/byte at a given area, not the ones that simply throw the most gates at the problem.

Tracked metrics

The full KPI set graded and reported for MCU B (formulas per ppabench/chassis/KPIS.md):

  • Functional — K-of-K exact-match ciphertext vs software AES golden over fresh random trials.
  • Cycles — externally counted wb_clk_i edges, startdone.
  • Cycles / block and wall time = cycles / f.
  • pJ / cycle = P[mW] / f[MHz] × 1000.
  • Total energy (nJ) = P × cycles / f.
  • Energy / byte (pJ/byte) — workload-normalized, primary ranking key.
  • Throughput — bytes/s, cycles/byte.
  • EDP = energy × wall time; EDAP = EDP × area.
  • Area (µm²), area per kgate; total / dynamic / leakage power, leakage fraction.
  • fmax (MHz) and timing closure (WNS ≥ 0).
  • DRC-clean + LVS-match in the locked wrapper.

Where a figure derives from the reference routed PPA rather than a per-submission harden of its own, it is a reference-route number and is labeled as such — energy figures throughout are the reference routed PPA combined with the real sim cycles at 50 MHz. A submitted design carries its own routed PPA, and its energy/byte follows from its own power and its own externally-counted cycles.

PPABench v0.1 · MCU B reference baseline picosoc + tiny-AES-c on sky130 HD · grading: harden (OpenROAD/Magic/netgen) + hidden cocotb TB, external cycles, random input · github.com/balbekov