Bug Report: With WritebackStage=1, a write of minstreth/minstret drops the minstret update of the older instruction retiring in WB

Aperta
#2,573 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Ambito
backend

Direzione di ricerca

Start with rtl/ibex_counter.sv and the retirement path in rtl/ibex_cs_registers.sv; the report identifies how a same-cycle counter write overrides the older instruction’s retirement increment. Reproduce cases A and E using the attached bench and scripts, then inspect the counter update logic and existing counter tests. Done when the affected cases retain the older instruction’s increment and carry while the writing instruction remains uncounted.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Type:Bug

Hello, I believe I have found a bug in this core.

Observed Behavior

With WritebackStage = 1, a write of minstreth (or minstret) drops the minstret increment of the older instruction retiring in WB in the same cycle. The CSR write happens in ID/EX while the increment of that cycle belongs to the older instruction:

  • a minstreth write keeps the old lower half, so the lower half misses the older instruction (cases A and B below);
  • a minstret write keeps the old upper half, so the older instruction's carry into minstreth is lost (case E).

This happens for any instruction directly followed by such a CSR write. #2446 fixed the writing instruction's own increment; this is the older instruction's.

case expected CHERIoT On RV32E WritebackStage = 0 (RV32E)
A: addi, then csrw minstreth, read minstret 1 0x0 0x0 0x1
B: c.nop, then csrw minstreth, read minstret 1 0x0 0x0 0x1
C (control): nothing in between 0 0x0 0x0 0x0
D (control): addi, then csrw minstret, x0 0 0x0 0x0 0x0
E: carry of the addi into minstreth, read minstreth 1 0x0 0x0 0x1

BranchTargetALU = 1 (the opentitan / maxperf* setting) gives the same as the default build.

Cause: ibex_counter loads the other half from the counter register without the same-cycle increment, and the write takes priority over that increment.

what where
a write replaces the counter's next value (we = either half written) rtl/ibex_counter.sv:35
a minstret (low) write keeps the old upper half rtl/ibex_counter.sv:36
a minstreth (high) write keeps the old lower half rtl/ibex_counter.sv:40
the write takes priority over the increment of the same cycle rtl/ibex_counter.sv:44
minstret increments on instr_ret_i, the WB-stage retirement rtl/ibex_cs_registers.sv:1586

Attached: simulation logs (evidence/shipped_*.log) and a formal counterexample (waveform/formal_cex.vcd, depth 4): csrrsi x8, minstreth, 1 writes minstreth in ID/EX while an older addi x0, x0, 0 retires in WB, and minstret goes from 9 to 0x1_0000_0009 instead of 0x1_0000_000a.

Expected Behavior

Case A reads minstret = 1 and case E reads minstreth = 1, as with WritebackStage = 0. RISC-V Privileged Specification:

The minstret CSR counts the number of instructions the hart has retired.

When XLEN=32, [...] writes change only bits 31-0; reads of the mcycleh, minstreth, mhpmcounternh, and mhpmeventnh CSRs return bits 63-32 of the corresponding register, and writes change only bits 63-32.

A possible fix: in ibex_counter, let a write replace only the half it writes and keep the same-cycle increment in the other half:

    counter_load[63:32] = counter_inc_i ? counter_upd[63:32] : counter[63:32];   // minstret (low) write: keep the carry
    counter_load[31:0]  = counter_val_i;
    if (counterh_we_i) begin
      counter_load[63:32] = counter_val_i;
      counter_load[31:0]  = counter_inc_i ? counter_upd[31:0] : counter[31:0];  // minstreth (high) write: keep the increment
    end

For a counter narrower than 64 bits the upper slice needs zero extension. This also changes mcycleh writes (the low half then counts the write cycle). The writing instruction stays uncounted (ibex_id_stage.sv:1209-1211).

Steps to reproduce the issue

Parameters: ibex_top with BaseIsa = ibex_pkg::BaseIsaRV32IorCHERIoT, RV32E = 1, WritebackStage = 1, other parameters at their defaults; cheriot_enable_i held On (CHERIoT mode) and, in a second run, Off (RV32E mode). Only WritebackStage = 1 is needed. ibex_configs.yaml sets it in opentitan, maxperf* and experimental-branch-predictor.

The upstream Simple System ties cheriot_enable_i off, so the attached bench tb_ibex_cheriot.sv instantiates ibex_top directly (see README.txt; +define+AB_WB0 builds WritebackStage = 0, +define+AB_BTALU builds BranchTargetALU = 1).

minstreth_wr.S (with crt.S, common.h, link.ld), case A:

  csrw minstreth, x0
  csrw minstret, x0
  addi x9, x0, 5            # older instruction, retires in WB ...
  csrw minstreth, x0        # ... in the cycle this one writes minstreth in ID/EX
  csrr x10, minstret        # expected 1

Case B uses c.nop instead of addi; C and D are controls; E writes minstret = 0xffffffff, retires an addi, writes minstret = 0 and reads minstreth.

  1. git clone https://github.com/lowRISC/ibex && cd ibex && git checkout 626034d7
  2. Run the attached script, which assembles the program, builds the bench with Verilator (+define+SYNTHESIS) and runs it in both modes:
    sh run.sh <ibex checkout>
    # assembles with: riscv32-unknown-elf-gcc -march=rv32emc_zicsr_zifencei_zcb -mabi=ilp32e -nostdlib -nostartfiles -T link.ld crt.S minstreth_wr.S
    
  3. Each SIG line is one case (A-E). Output:
    CHERIoT mode:
    SIG 00000000
    SIG 00000000
    SIG 00000000
    SIG 00000000
    SIG 00000000
    RV32E mode:
    SIG 00000000
    SIG 00000000
    SIG 00000000
    SIG 00000000
    SIG 00000000
    
    The WritebackStage = 0 build prints 1, 1, 0, 0, 1.

No changes to the Ibex source code.

My Environment

EDA tool and version:
Verilator 5.044 (simulation); riscv32-unknown-elf-gcc 16.1.0.

Operating system:
Red Hat Enterprise Linux 8.10

Version of the Ibex source code:
626034d7557ab85a6d989b13011b44296bad26fa (master, 2026-11-06), unmodified.

All of the relevant materials pertaining to the bug:

MINSTRETH_INC_bug.zip

Lingua principale
SystemVerilog
Stelle
2.2k
Fork
836
Merge medio
5g 8h
PR unite (30g)
22

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di lowRISC/ibex

Tutte le issue di lowRISC/ibex

Issue simili

Altre issue su Backend & API Design

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.