Bug Report: With WritebackStage=1, a write of minstreth/minstret drops the minstret update of the older instruction retiring in WB
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
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
minstrethwrite keeps the old lower half, so the lower half misses the older instruction (cases A and B below); - a
minstretwrite keeps the old upper half, so the older instruction's carry intominstrethis 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.
git clone https://github.com/lowRISC/ibex && cd ibex && git checkout 626034d7- 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 - Each
SIGline is one case (A-E). Output:
TheCHERIoT mode: SIG 00000000 SIG 00000000 SIG 00000000 SIG 00000000 SIG 00000000 RV32E mode: SIG 00000000 SIG 00000000 SIG 00000000 SIG 00000000 SIG 00000000WritebackStage = 0build prints1, 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:
- Lingua principale
- SystemVerilog
- Stelle
- 2.2k
- Fork
- 836
- Merge medio
- 5g 8h
- PR unite (30g)
- 22
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di lowRISC/ibex
-
[Bug Report] rvfi_intr is not set on the first instruction of an exception handlerForse già presa @h-filali l’ha presa 6 giorni fa. ApertaType:Bug
Difficoltà 2/5 Mezza giornata Idoneità per principianti 68/100
lowRISC/ibex#2556 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
Type:Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
lowRISC/ibex#2528 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
mip documentation errorApertaType:Bug
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
I maintainer di solito rispondono entro 2 giorni
-
[doc] Document basic commands for running/checking IbexForse già presa @shrutikbalwan l’ha presa 46 giorni fa. ApertaComponent:Doc Good First Issue Type:Task
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
lowRISC/ibex#741 · 3 commenti ·
I maintainer di solito rispondono entro 2 giorni
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 42/100
I maintainer di solito rispondono entro 2 giorni
Tutte le issue di lowRISC/ibex
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
FinanceFlash/unvibecode#699 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
coder/openstorage#807 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
libnoname/noname#4626 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
`epoch.Integer` raises a raw OverflowError for integers too large to convert to a floatForse già presa @arindam77 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
pydantic/pydantic-extra-types#479 ·
I maintainer di solito rispondono entro 1 giorno