Bug Report: With WritebackStage=1, a write of minstreth/minstret drops the minstret update of the older instruction retiring in WB
Los mantenedores suelen responder en 2 días
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 55/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Área
- backend
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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:
- Lenguaje dominante
- SystemVerilog
- Estrellas
- 2.2k
- Forks
- 836
- Merge medio
- 5 d 8 h
- PR fusionados (30 d)
- 22
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de lowRISC/ibex
-
[Bug Report] rvfi_intr is not set on the first instruction of an exception handlerPosiblemente ocupada @h-filali la tomó hace 6 días. AbiertoType:Bug
Dificultad 2/5 Medio día Aptitud para principiantes 68/100
lowRISC/ibex#2556 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
Type:Bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
lowRISC/ibex#2528 · 1 comentario ·
Los mantenedores suelen responder en 2 días
-
mip documentation errorAbiertoType:Bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
Los mantenedores suelen responder en 2 días
-
[doc] Document basic commands for running/checking IbexPosiblemente ocupada @shrutikbalwan la tomó hace 46 días. AbiertoComponent:Doc Good First Issue Type:Task
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
lowRISC/ibex#741 · 3 comentarios ·
Los mantenedores suelen responder en 2 días
-
Dificultad 4/5 3-5 días Aptitud para principiantes 42/100
Los mantenedores suelen responder en 2 días
Todos los issues de lowRISC/ibex
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 67/100
FinanceFlash/unvibecode#699 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
coder/openstorage#807 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
libnoname/noname#4626 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
`epoch.Integer` raises a raw OverflowError for integers too large to convert to a floatPosiblemente ocupada @arindam77 la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
pydantic/pydantic-extra-types#479 ·
Los mantenedores suelen responder en 1 día