Integration guide for instantiating Stoic-V (the deterministic RV32 core) as
the mbv slot-in inside any host SoC. The IP is branded Stoic-V, but its
synth top module is module mbv with the exact MicroBlaze-V LMB v10 port
signature — that fixed name is the drop-in contract, so the core binds into a
MicroBlaze-V socket unchanged; do not rename it.
Pairs with README.md (authoritative spec),
verification-status.md (evidence), and
timing-and-resources.md (closure).
One-line summary: drop the 9
rtl/core/*files in as yourmbv, point the instruction LMB at firmware, give the host's pblock for this core a DSP column, and honour the single-outstanding LMB contract (one rising-edge strobe per access, DReady-handshaked). It already passes the full verification suite (cosim/archtest/riscv-tests/lmb-contract/irq-stress/ determinism), is lock-stepped against Spike as compiled-C firmware on the canonical free-running BRAM (make cosim-fw, 12/12 — the slave model your tile uses), is re-validated under Vivado xsim (make xsim, 21/21, cycle-identical to Verilator), and meets 250 MHz OOC. Everything else below is detail.
A rv32im_zba_zbb_zbs_zicsr, Machine-mode-only, in-order, statically
WCET-analysable core (no caches, no MMU, no speculation, no branch prediction,
no early-terminating mul/div). It presents the MicroBlaze-V mbv signature
so a host SoC that instantiates a MicroBlaze-V can bind it unchanged. It is a
functional slot-in for the MicroBlaze-V soft core.
Self-contained; the only inter-file dependency is the package, compiled first. Compile order:
| # | File | Role |
|---|---|---|
| 1 | rtl/core/rv_pkg.sv |
types, opcodes, CSR addrs, params (must be first) |
| 2 | rtl/core/rv_alu.sv |
base ALU |
| 3 | rtl/core/rv_bitmanip.sv |
Zba/Zbb/Zbs (combinational) |
| 4 | rtl/core/rv_regfile.sv |
2R1W GPRs (LUTRAM, no reset) |
| 5 | rtl/core/rv_decode.sv |
decoder |
| 6 | rtl/core/rv_muldiv.sv |
M ext: fixed-latency mul (DSP) + restoring divider |
| 7 | rtl/core/rv_csr.sv |
Zicsr + trap/IRQ |
| 8 | rtl/core/rv_core.sv |
5-stage pipeline + fetch FIFO |
| 9 | rtl/core/mbv.sv |
top — wraps rv_core, presents the mbv ports |
The deliverable is exactly the 9 rtl/core/* files above plus the standalone
OOC harness. vivado/timing.xdc and vivado/build.tcl are that standalone OOC
harness (logic sanity only — see §7); the host owns the real constraints. No
host-side sources travel with the IP.
module mbv #(parameter logic [31:0] RESET_VEC = 32'h0000_0000) with the exact
MicroBlaze-V port list (rtl/core/mbv.sv). The host
instantiates module mbv by name with every port matched — no wrapper edits
required. Ports of note:
Clk— single synchronous clock.Reset— active-high (the wrapper drives~rst_n; the core inverts).- Instruction LMB:
Instr_Addr/Instr/IFetch/I_AS/IReadyused;IWAIT/ICE/IUEignored (contract §5). - Data LMB:
Data_Addr/Data_Read/Data_Write/D_AS/Read_Strobe/Write_Strobe/Byte_Enable/DReadyused;DWait/DCE/DUEignored. Interrupt— a single level-sensitive external interrupt from the host (write-1-clear at the host; no hardware-acknowledge handshake) →mip.MEIP(vector viamtvec;Interrupt_Addressunused).Interrupt_Acktied 0.- Debug port group — present for binding, functionally omitted in v1
(
Dbg_*tied off). No JTAG-debug.
- Single synchronous clock
Clk. No derived/gated clocks. - Synchronous reset throughout (core-wide convention — Xilinx UG901). Reset
must be held ≥ 1
Clkedge (always true in the host). Rationale: an async reset blocks DSP48E2 register packing and proliferates control sets; sync reset is the FPGA-preferred style and lets the multiply pack into the DSP. RESET_VEC(default0x0000_0000) — boot PC. The host bindsmbvwithout overriding it, so the core boots from0x0(the host's instruction-memory base). Override only if your reset vector differs. (The conformance sim overrides it to0x8000_0000to match Spike; synth/firmware use the0x0default.)- Memory aperture (recommended end-target sizing): 24 KiB I / 16 KiB D, no
compressed (
C) ISA. The core RTL is size-agnostic — it only drives LMB addresses, so aperture depth lives entirely host-side; this is an integration recommendation, not a core parameter. Rationale: the host targets realistic MCU-class kernels, not just boot/ISR stubs. 24 KiB uncompressed ≈ ~18 KB of Thumb-2/RVC in logical capacity → M0-class footprint, holds a CoreMark-class kernel (16.9 KB at-O3, ~11 KB-Os) with headroom; the code-heavy 1.5:1 I:D shape leans the way shipping similar-perf MCUs do (flash:SRAM ~4:1–8:1). TheCextension is deliberately omitted — on a cacheless coreCis 1:1 expansion (≈0 IPC gain) and it adds fetch-alignment logic plus a WCET fetch- uniformity term; the asymmetric aperture carries the density instead.- Validated against real kernels: all 17 Embench-IoT realistic MCU
kernels (crypto, JPEG, regex, sort, state-machines, DSP) fit and pass
cycle-exact on the core — max
.text=wikisort24.3 KiB (right at the 24 KiB edge), max data+bss =tarfind9.1 KiB (seesim/embench/). Embench speed score 0.851 (geomean, ref Arm Cortex-M4 = 1.0, default 2-cycle load-use config; 0.874 with the optionalCORE_PERF1-cycle build). This is the evidence the 24/16 budget is correctly sized for the workload. - BRAM budget: 24 KiB I = 6× RAMB36, 16 KiB D = 4× → 10 RAMB36 / instance.
- ⚠ Aperture Tco: the instruction-aperture read is the load-use timing limiter (~1.8 ns on the cascaded read; see §7.3). Bank/organise the 24 KiB I-aperture to keep the read mux shallow — a 6-deep ripple cascade worsens that Tco; parallel banks + one mux level do not. This directly affects Fmax.
- Validated against real kernels: all 17 Embench-IoT realistic MCU
kernels (crypto, JPEG, regex, sort, state-machines, DSP) fit and pass
cycle-exact on the core — max
The core is a single-outstanding LMB master that drives one rising-edge address strobe per access and honours DReady. It works with any FIXED-latency registered slave on both buses — level-ready, edge-detect (held-ready), or back-pressure — and matches MicroBlaze-V's LMB. A combinational/zero-wait response is not supported. Consecutive accesses take a fixed 1-cycle gap (deterministic):
- read data / instruction is available no earlier than the cycle after the strobe (registered, fixed-latency);
- address is held until consumed; one request outstanding at a time;
- the core drives one rising-edge address strobe per access and honours
DReady/IReadybefore completing it — the strobe is never held across back-to-back accesses (single-outstanding sequencing).
This is by design (a variable-latency memory would make WCET contention-
dependent — it violates the determinism prime directive). The host's LMB slave
MUST present a fixed-latency registered response (data no earlier than the cycle
after the strobe). A combinational / zero-wait slave corrupts execution — there
is a negative regression for exactly this (make lmb-contract).
| Resource | This core | ZU9EG | Note |
|---|---|---|---|
| CLB LUTs | 2855 (1.0 %) | 274,080 | incl. the load-data latch (ld_word) + commit mux |
| CLB FFs | 1068 (0.2 %) | 548,160 | measured OOC; incl. the wmd muldiv-forward register, d_inflight, the ld_word load latch; multiply PREG packs into the DSP; quot/rem dropped |
| DSP48E2 | 4 (0.2 %) | 2,520 | signed 33×33 multiply, pipelined (PREG packed) |
| BRAM | 0 | 912 | regfile is LUTRAM; IMEM/DMEM are host-side |
Per-instance (post-route, xczu9eg-2): u_core own 1569 LUT / 415 FF · u_md
599 / 290 / 4 DSP · u_csr 466 / 259 · u_rf 124 / 0 (LUTRAM). A comfortably
small footprint, competitive with MicroBlaze-V.
- The host's pblock for this core MUST span a DSP column. The multiply uses 4 DSP48E2. If a replicated pblock region contains no DSP column, either extend the region to include one, or force a LUT-based multiply (different resource profile + a likely timing hit — not recommended). Verify per replicated instance, since pblocks repeat across the device. (This is the one hard integration gotcha.)
- The host's pblock for this core resource budget must hold ~3.5k LUT / ~1.2k FF / 4 DSP / 0 BRAM (§6). Very likely already satisfied if it held MicroBlaze-V.
- Timing: default config meets 250 MHz OOC.
The default 2-cycle load-use config meets 250 MHz OOC (WNS ≈ +0.167 ns
at the 4.000 ns period; the residual is OOC routing of a floorplan-less design, not
logic depth); the core logic worst-path ≈ 1.23 ns
closes 250 MHz with large margin. The load result is registered (
P_LOADUSE= 2): the load result is registered (wl,dont_touchso the host flatten can't merge it into the cross-boundary bypass LUTs) and the load-use bypass forwards that register, never combinationalload_ext; the load-use stall is 2 cycles. The mul/div result is likewise forwarded from a dedicateddont_touchregister (wmd), removing it from the combinational EX-forward→ALU loop whose slow source routes through the DSP (u_md) — the binding net that routing congestion can stretch; +1 cycle only when a mul/div feeds the immediately-next dependent (P_MDUSE), the generic ALU forward stays combinational. The host serves the IMEM aperture as a data read (cascaded BRAM →Data_Read), soData_Read → load_ext → forward → ALU → m_resultis the load-use path the harness models (cascaded-BRAM read Tco). The full suite is green (cosim 48/48, cosim-fw 12/12, archtest 75/75, riscv-tests 48/48, lmb-contract, irq-stress, xsim 21/21; CoreMark 1.949 CRC-validated; Embench 0.851; determinism golden 286). To integrate, instantiate asmbvand close timing in the host's pblock for this core (it supplies the locality the floorplan-less OOC lacks); a single-instance in-context P&R has closed at +0.30 ns @ 4.0 ns.CORE_PERFis the 1-cycle load-use option for relaxed/banked deployments (+6 % CoreMark / +3 % Embench; still deterministic); its 1-cycle IMEM-aperture load needs the aperture banked or made multicycle to close 250 MHz. Area/FF (§6) unaffected. - Both LMB adapters stay registered single-outstanding (one rising-edge strobe per access) (§5) — the
P_LOADUSEfix is core-internal and does not change the bus contract. Confirm the cascaded-BRAM read Tco the core sees at bring-up (the harness models ~1.8 ns IMEM-aperture / ~1.05 ns DMEM; see §7.3).
Per the §10.3 carve-out, v1 omits precise synchronous exceptions and assumes aligned-only access:
ecall/ebreak/illegal-instruction/misaligned access do NOT trap; an unrecognised instruction decodes to a defined NOP.- Consequently the misaligned / illegal-instruction / sync-cause arch-test and
rv32misuites are out of signoff, and Spike lock-step excludes misaligned ops. - The controlled firmware target must not rely on synchronous traps. Closing
this is a localised
rv_csr/LSU change that does not touch the bus contract — a clean v2 item.
Required must-haves are present (firmware depends on them): wfi legal
NOP/sleep; mret MIE/MPIE restore; level-sensitive mip.MEIP (no
edge-latch); MEI trap → mtvec; the mstatus/mie/mtvec/mepc/mcause set.
Full detail in verification-status.md. Summary:
| Gate | Result |
|---|---|
archtest (RISCOF vs Spike, I/M/Zba/Zbb/Zbs) |
75/75, 0 failed (carve-out) |
riscv-tests (Berkeley rv32ui+rv32um) |
48/48 |
cosim (Spike --log-commits lock-step, riscv-tests) |
48/48 GPR-write streams identical |
cosim-fw (system-level: compiled-C firmware vs Spike on the free-running BRAM) |
12/12 (-O0/-O2/-O3; ~22 k retires each) — the slave model your tile uses |
scoreboard (randomized compliant slave timing, arch-invariant vs Spike) |
128/128 (workload × opt × seed) — catches bus-capture bugs across the timing space |
sva (LMB-protocol assertions bound onto rv_core) |
84 runs, 0 failures (single-outstanding, one-strobe-per-access, sequencing) |
formal (LMB handshake, yosys-smtbmc + z3) |
PROVEN — BMC + k-induction (unbounded); the bus-handshake protocol your tile relies on |
crv (constrained-random hazard-biased programs vs Spike, incl. CSR RMW) |
200/200 — datapath/forwarding/hazard/CSR logic robust across thousands of random instructions |
determinism |
8 datasets × 286 cycles (data-independent) |
lmb-contract (level / edge-detect / free-running / back-pressure; incl. store→load) |
1-cycle-registered required (negative tests fire) |
irq-stress |
99/99 MEI injection points |
dmem-matrix (dmem_stall × {IRQ/branch/muldiv} coincidence) |
1410/1410 — MEI swept on stalling/free-running slaves while a data access is outstanding |
wcet (latency constants + FU data-independence) |
per-op latency pinned vs µarch §4 (div 37 / mul 6 / load-use 4 / branch 4); mul & div latency operand-invariant |
xsim (second simulator — directed suite under Vivado xsim, the host's sim) |
21/21, cycle-identical to Verilator; catches the Verilator↔xsim divergence class |
coverage |
89.8 % core lines (datapath ~98–100 %) |
The above was re-confirmed after the synchronous-reset tidy (lint / det / riscv-tests / cosim / archtest all still green), proving that change is behaviour-neutral.