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

未关闭
#2,573 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 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 内容生成。

描述

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

主要语言
SystemVerilog
星标
2.2k
派生
836
平均合并
5 天 8 小时
30 天内合并 PR
22

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

lowRISC/ibex 的其他 Issue

查看 lowRISC/ibex 的全部 Issue

相似的 Issue

更多 Backend & API Design Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。