Verified · reproducibleDigital RTL

SERV — bit-serial RISC-V CPU

The award-winning bit-serial RV32I RISC-V core, widely used and RISC-V-compliance tested for the M, C and Zicsr extensions.

commit 41e8aeesource

Method: Verilator 5.020 against the real RTL; every finding re-reproduced by hand with a passing positive control, and cross-checked against the ratified RISC-V spec text. Each finding below survived two independent adversarial refutations — one tracing the logic, one checking intent and documentation — before publication. Reproduce any of it against the commit above.

#1Correctness bug

MRET discards mepc[1]: trap-return lands two bytes early with the C extension

rtl/serv_ctrl.v:73-78
if (W == 1) begin : gen_new_pc_w_eq_1
   assign new_pc = i_trap ? (i_csr_pc & !(i_cnt0 || i_cnt1)) : i_jump ? pc_plus_offset_aligned : pc_plus_4;
end else if (W == 4) begin : gen_new_pc_w_eq_4
   assign new_pc = i_trap ? (i_csr_pc & ((i_cnt0 || i_cnt1) ? 4'b1100 : 4'b1111)) : ... ;

Why it is wrong

The i_trap new_pc path is shared by trap ENTRY (i_csr_pc = mtvec, where clearing the low bits is correct — they are the mtvec MODE field) and trap EXIT via MRET (i_csr_pc = mepc). It unconditionally forces pc[1:0] = 0. Under IALIGN=16 (COMPRESSED=1) the ratified RISC-V privileged ISA permits masking mepc[1] only when IALIGN=32; with the C extension enabled, mepc[1] is a valid address bit and MRET must deliver it to pc unchanged. SERV strips it, so MRET returns to (mepc & ~3).

Reproduction scenario

COMPRESSED=1. Any trap (interrupt, ebreak, ecall or fault) whose mepc is halfword- but not word-aligned — routine in C-extension code. Example: ebreak at 0x0a → handler → mret resumes at 0x08 instead of 0x0a, re-executing the previous instruction. Silent control-flow corruption from then on.

Confirmed by

RISC-V priv ISA (mepc): mepc[1] masking is permitted only for IALIGN=32; MRET sets pc = mepc. Verilator: tb_mret_c.v FAIL → returns 0x08 (trap-captured mepc); an independent second reproduction tb_mret_c2.v with a software-written mepc=0x1a FAIL → returns 0x18; positive control (word-aligned mepc) PASS. It escapes SERV's own flow: the arch-test C and privilege suites never intersect (privilege runs IALIGN=32), and YosysHQ riscv-formal does not model trap entry / MRET restore.

Why it matters

With RVC roughly half of all instruction boundaries are 2-mod-4, so any trap taken in compressed code returns to the wrong PC — the class of bug that survives to silicon because straight-line C tests and word-aligned privilege tests both pass.

#2Correctness risk

An interrupt preempting MRET or a mepc/mtval CSR read vectors to MTVAL instead of MTVEC

rtl/serv_rf_if.v:122-125
wire sel_rs2 = !(i_trap | i_mret | i_csr_en);
assign o_rreg1 = {~sel_rs2,
   i_rs2_raddr[4:2] & {3{sel_rs2}},
   {1'b0,i_trap} | {i_mret,1'b0} | ({2{i_csr_en}} & i_csr_addr) | ({2{sel_rs2}} & i_rs2_raddr[1:0])};

Why it is wrong

The low bits of o_rreg1 are a plain OR of the trap select (01 = MTVEC), the mret select (10 = MEPC) and the CSR address. The maintainer comment asserts trap/mret/csr_en are mutually exclusive — true architecturally, but NOT at the preemption boundary. When an interrupt is sampled at the fetch-ack of an MRET (or a csrr of mepc/mtval), the preempted instruction's decode is still live and ungated, so the selects OR to 01|10 = 11 = MTVAL. The core reads MTVAL and streams it as the trap vector.

Reproduction scenario

WITH_CSR, timer interrupt enabled. The interrupt coincides with the fetch-ack of `mret` (or `csrr x,mtval`): o_rreg1 = MTVAL → pc ← mtval & ~3 — an arbitrary, stale, potentially data-controlled address instead of mtvec. A control-flow-integrity failure.

Confirmed by

Verilator on the real serv_rf_top driven only through the i_timer_irq pin (no internal poking), with three distinct decoy CSR values: tb_irq_csr.v (the csr_en term) and an independent tb_irq_mret.v (the mret term) both FAIL → vector to MTVAL (0x80). It escapes SERV's compliance flow, whose trap tests use synchronous exceptions, never an interrupt preempting an MRET or CSR read.

Why it matters

Control-flow integrity: the fetched PC becomes the contents of MTVAL — on that very trap the "bad address" — i.e. an attacker- or data-influenced program counter.

Get this on your design — privately
Same flow, your RTL or analog netlist, findings delivered to you rather than published.

A paid pilot hunt returns confirmed findings on a module you choose, each reproducible on your own code. Design-services firms resell the report; startups de-risk before tapeout. Pay for proof.

Start a pilot
SERV — bit-serial RISC-V CPU — RTL verification report — ActGen