Bug Report: With WritebackStage=1, a write of minstreth/minstret drops the minstret update of the older instruction retiring in WB
维护者通常 2 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 55/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 领域
- backend
调研方向
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.
由索引模型根据 Issue 内容生成。
描述
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:
- 主要语言
- SystemVerilog
- 星标
- 2.2k
- 派生
- 836
- 平均合并
- 5 天 8 小时
- 30 天内合并 PR
- 22
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
lowRISC/ibex 的其他 Issue
-
[Bug Report] rvfi_intr is not set on the first instruction of an exception handler可能已有人在做 @h-filali 于 6 天前认领。 未关闭Type:Bug
难度 2/5 半天 新手友好度 68/100
维护者通常 2 天内回复
-
Type:Bug
难度 2/5 1-3 小时 新手友好度 83/100
维护者通常 2 天内回复
-
Type:Bug
难度 1/5 1 小时以内 新手友好度 88/100
维护者通常 2 天内回复
-
[doc] Document basic commands for running/checking Ibex可能已有人在做 @shrutikbalwan 于 46 天前认领。 未关闭Component:Doc Good First Issue Type:Task
难度 2/5 1-3 小时 新手友好度 68/100
维护者通常 2 天内回复
-
难度 4/5 3-5 天 新手友好度 42/100
维护者通常 2 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 67/100
FinanceFlash/unvibecode#699 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
coder/openstorage#807 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 76/100
libnoname/noname#4626 · 1 条评论 ·
维护者通常 1 天内回复
-
`epoch.Integer` raises a raw OverflowError for integers too large to convert to a float可能已有人在做 @arindam77 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
pydantic/pydantic-extra-types#479 ·
维护者通常 1 天内回复