PPABench / Benchmarks / MCU A

MCU A Fixed RV32I — innovate on microarchitecture

Ship an RV32I core that drops into the locked user_project_wrapper, boots a binary we provide from QSPI flash, and runs a fixed deterministic CRC32 kernel. The ISA is frozen so every submission retires the same instruction stream — the entire optimization space is microarchitecture, and the score is correctness, IPC, and energy per instruction.

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 is the simplest contract on PPABench, and the most direct test of core design. The deliverable is one line: prompt → an RV32I core in the chassis. Your core replaces the reference picorv32 inside the fixed OpenFrame / chipignite user_project_wrapper — same pinout, same QSPI flash model, same UART, same Wishbone control registers. We hand you a compiled RV32I binary; your job is to fetch it from flash, execute it correctly, and finish quickly and efficiently.

Grading runs both halves of a real chip. The harden path (yosys → OpenROAD P&R + STA → Magic DRC → netgen LVS) produces the PPA: area, power, fmax, DRC-clean, LVS-match. The hidden testbench (cocotb / Verilator) boots your firmware, drives the start strobe, decodes the UART result against a software golden, and — critically — counts clock edges externally at the wrapper boundary. Because the kernel is deterministic given its inputs, the golden retired-instruction count is known exactly, so IPC, CPI, MIPS, and energy/instruction are all well-defined.

Why the ISA is fixed

IPC and CPI are only meaningful within a fixed ISA. Comparing "instructions per cycle" across two different instruction sets is meaningless — one design's single fused instruction is another's five. By locking the ISA to RV32I we get three things for free:

  • A real, off-the-shelf toolchain compiles the kernel, so the binary is reproducible and auditable.
  • A reference instruction-set simulator (ISS) gives the golden retired-instruction count directly — no need to trust anything the DUT reports.
  • The retired count is held constant across every submission, so two cores running this benchmark differ only in how many cycles they take and how much energy they burn to retire the identical 17,229-instruction stream.

That turns the whole problem into microarchitecture: instruction cache, prefetch, pipeline depth and forwarding, branch prediction, and — for the ambitious — superscalar issue. None of those change the program; all of them change IPC and energy.

The workload

The kernel is a reflected CRC32 (polynomial 0xEDB88320, the standard zlib/PNG reflected form) computed over a 256-byte table. To stop anyone hardcoding a single answer, a host scalar delivered through Wishbone register CFG0 (offset 0x08) perturbs the table before the CRC is taken. The core emits its result on UART as:

@T-A:CRC32=<8hex>;

The computation is fully deterministic given the pair (binary, CFG0): the same firmware and the same CFG0 always produce the same retired-instruction count and the same CRC string. The testbench picks CFG0 at runtime, so the correct output is not knowable ahead of the run, but the golden is trivially computable in software once CFG0 is fixed.

Interface & protocol

The chassis is the locked OpenFrame / chipignite user_project_wrapper. Your core talks to the outside world only through the fixed pins and registers below — nothing else is connected:

# Chassis pinout (locked user_project_wrapper) QSPI flash io[24:29] # firmware image, executed in place (XIP) UART io[30:31] # tx/rx — result string @T-A:CRC32=...; done io[35] # asserted when STATUS.done goes high error io[36] # Wishbone CTRL / STATUS block @ 0x30000000 0x30000000 CTRL bit0 = start # testbench strobes start 0x30000004 STATUS bit0 = done # external cycle counter stops here 0x30000008 CFG0 # host scalar; perturbs the CRC table # Firmware → testbench completion handshake sw 0x600DF00D, 0x04000000 # MMIO store signals "kernel done"

The run sequence is: the testbench loads the flash image and CFG0, strobes CTRL.start, and begins counting wb_clk_i edges. Your core boots from flash, runs the CRC kernel, and writes the magic value 0x600DF00D to MMIO 0x04000000 to signal completion; the SoC reflects that into STATUS.done / io[35], at which point the external counter stops. The UART line is decoded and compared to the golden.

Grading

Functional is binary: the decoded UART string must match the software golden CRC32 for the CFG0 used in that run. No partial credit.

Performance is derived from the externally-counted cycles and the golden retired count:

  • IPC = retired / cycles, and CPI = cycles / retired.
  • MIPS = IPC × f (operating clock 50 MHz).
  • Figure of merit = energy per instruction (pJ/instr), with EDP (energy × wall-time) as the efficiency tiebreaker.
Cycles are counted externally. The graded cycle count is the number of wb_clk_i edges between CTRL.start and STATUS.done, measured at the wrapper boundary by the testbench. It is never read from a DUT-internal performance counter — a register you increment yourself does not count and cannot be trusted.

Pass/fail is a gate, not a curve: a submission passes only if it clears the PPA caps, functional_ok, timing_met (WNS ≥ 0), DRC-clean, and LVS-match. Everyone who passes is a real chip; passers are then ranked by efficiency (EDP / energy / area / perf KPIs), so the leaderboard sorts on energy-per-instruction with EDP as the ranking key.

Anti-cheat

The defenses are structural rather than policy-based:

  • You run our binary — you don't get to choose or shrink the instruction stream.
  • Cycles are externally timed at the wrapper boundary, so a self-reported counter is worthless.
  • The output is checked against a software golden, so a fast-but-wrong core fails functional.
  • The CFG0 perturbation makes the correct CRC depend on a runtime value, so the answer cannot be precomputed or baked into the design.

The net effect: there is no way to win except to actually execute the program faster and more efficiently in real silicon.

Reference baseline — picosoc

The reference is picosoc (picorv32 RV32IMC + spimemio QSPI XIP + simpleuart), hardened on sky130 HD. These are the real measured numbers: functional and cycles come from the live Verilator/cocotb run (cycles counted externally, start→done); energy is the reference routed PPA applied to the real sim cycles at 50 MHz.

MCU A picosoc reference baseline

picorv32 · sky130 HD · 50 MHz · CRC32 kernel
MetricValueNote
FunctionalUART matches software golden
Cycles (external)1,433,997wb_clk_i edges, start→done
Retired instr17,229golden, from ISS
IPC0.0120retired / cycles
CPI83.23cycles / retired
MIPS0.60IPC × 50 MHz
Energy / cycle3.54pJ/cycle (P/f)
Total energy5,076nJ (P × cycles / f)
Energy / instr295pJ/instr — figure of merit
Area3,810µm² (routed sky130)
fmax88.5MHz (8.7 ns slack @ 50 MHz)
EDP1.46e8nJ·µs (energy × wall-time)
CPI = 83 is the headroom, not the ceiling. The reference executes straight from QSPI flash via XIP with no I-cache, so nearly every instruction fetch pays full SPI latency — that single bottleneck dominates the cycle count. The retired stream is fixed at 17,229 instructions; a stronger core retires exactly the same stream but slashes the cycles by hiding that fetch latency. The baseline is a floor to beat, not a target to match.

Note: the PPA figures (area, power, fmax) are the reference routed sky130 numbers, and the energy figures are that PPA applied to the real measured cycle count at 50 MHz. They are a reference route, not yet a per-submission picosoc harden; your submission is hardened and measured as its own single design.

What a strong submission looks like

Every microarchitectural lever here attacks the same fixed instruction stream. The big wins, roughly in order of impact on this XIP-bound workload:

  • Instruction cache. The single largest lever. The CRC kernel is small and loop-heavy, so even a modest I-cache turns repeated flash fetches into hits and collapses CPI from ~83 toward single digits — raising IPC by an order of magnitude with little area cost.
  • Prefetch / burst fetch. Streaming sequential words from QSPI ahead of demand hides SPI latency on cold instructions and on the first pass through the loop body.
  • Pipelining + forwarding. A deeper pipeline with proper bypass raises steady-state IPC once fetch is no longer the bottleneck; without forwarding the data hazards in the CRC inner loop would cap you.
  • Branch prediction. The CRC loop is dominated by one tight backward branch; even a simple predictor removes the misprediction bubbles that otherwise eat the IPC you bought with caching.
  • Superscalar issue. For the most aggressive designs, dual-issue of the independent XOR/shift/load operations in the inner loop pushes IPC above 1 — but watch area and fmax, since both feed the PPA caps and EDP ranking.

The design tension is real: caching and superscalar issue raise IPC and cut total energy by finishing sooner, but add area and can hurt fmax. Because the score is energy/instruction with EDP as the tiebreaker, the winning design is the one that retires 17,229 instructions in the fewest joules and the least time — not simply the biggest core.

Tracked metrics

The full KPI set recorded for every MCU A submission:

  • Functional: functional_ok (UART vs golden), DRC-clean, LVS-match, timing_met (WNS ≥ 0).
  • Throughput / latency: cycles (external), wall_time_us = cycles/f, IPC, CPI, MIPS.
  • Energy: energy_per_cycle_pj = P/f×1000, total_energy_nj = P×cycles/f, energy_per_instruction_pj, leakage_fraction = leakage/total.
  • Area / power: area_um2, area_per_kgate_um2, total_power_mw, dynamic and leakage power, fmax_mhz.
  • Efficiency (ranking): edp_nj_us = energy × wall_time, edap_nj_us_um2 = edp × area.

PPABench v0.1 · MCU A reference baseline picosoc/picorv32 on sky130 HD · grading: harden (OpenROAD/Magic/netgen) + hidden cocotb TB · cycles counted externally, CFG0 randomized per run · github.com/balbekov