RV32E + WritebackStage=0: illegal instruction using x16-x31 in rd/rs1 still writes CSRs
Maintainer thường phản hồi trong vòng 2 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 35/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Lĩnh vực
- operating-systems
Hướng nghiên cứu
Start with rtl/ibex_decoder.sv, especially the illegal_reg_16 logic and side-effect suppression block, then trace how csr_access_o and data_req_o reach the CSR and LSU paths. Reproduce the WB=0 behavior using the test setup and programs described in the issue, comparing against WB=1. Done means illegal RV32E register references still trap and have no CSR or LSU side effects, while legal instructions continue to work.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Bug report for lowRISC/ibex. Target HEAD:
a896ef22777a40ad3f5a2a3b97fecd5116480c4c(2026-10-23)
Observed Behavior
With RV32E=1 and WritebackStage=0, an instruction that uses a reserved RV32E register number (x16–x31) in rd or rs1 raises the correct illegal-instruction exception (mcause=2, mepc at the instruction, mtval = the instruction word) and still performs the CSR write in the same cycle. WritebackStage=1 is clean.
Test setup
- DUT:
ibex_core,BaseIsa=RV32I,RV32E=1,WritebackStage=0,RV32M=RV32MFast,RV32B=RV32BNone,RV32ZC=RV32ZcaZcbZcmp,PMPEnable=1,PMPNumRegions=16,ICache=0,SecureIbex=0,DummyInstructions=0, external register file,MemDataWidth=32. - Harness: external RF, 64 KiB memory, 1-cycle OBI instruction/data slaves,
boot_addr_i=0x2000. Ibex'sPC_BOOTis{boot_addr_i[31:8], 8'h80}(rtl/ibex_if_stage.sv:244), so execution starts at 0x2080. - Memory is zero-filled. The program is poked by the TB; no program file is needed.
- The program first writes
mtvec=0x2100andmscratch=0x12345678, then executes one CSR instruction at 0x20A0. Illegal variants trap to the handler at 0x2100, which records the architectural state at 0x3000. A store of0xD00Dto 0x3100 ends the run. mtvecis WARL and its MODE field is fixed to2'b01(doc/03_reference/cs_registers.rst:219), so writing0x2100reads back0x0000_2101.
Signature layout (written by the trap handler; the legal control writes SIG[0] on the normal path):
| Word | Content |
|---|---|
SIG[0] |
mscratch read after the instruction |
SIG[1] |
mtvec |
SIG[2] |
mcause |
SIG[3] |
mepc |
SIG[4] |
mtval |
Test programs
The only difference between the five runs is the CSR instruction at 0x20A0 (and the value loaded into t2):
+PROG |
Instruction at 0x20A0 | Encoding |
|---|---|---|
| 0 | csrrw x16, mscratch, t2 (t2=0x55AA55AA) |
0x34039873 |
| 1 | csrrw x16, mtvec, t2 (t2=0x2000) |
0x30539873 |
| 2 | csrrw x16, mscratch, x0 |
0x34001873 |
| 3 | csrrw x0, mscratch, x16 |
0x34081073 |
| 4 | csrrw x6, mscratch, t2 (legal control) |
0x34039373 |
Results
+PROG |
WB=0 (unmodified RTL) | WB=1 (control) |
|---|---|---|
| 0 | SIG[0]=0x55aa55aa — the CSR write landed |
SIG[0]=0x12345678 — unchanged |
| 1 | TIMEOUT pc=0x2000 mcause=2 — mtvec was overwritten |
SIG[1]=0x00002101, mcause=2, mepc=0x20a0 |
| 2 | SIG[0]=0x00000000 |
SIG[0]=0x12345678 |
| 3 | SIG[0]=0x00000000 |
SIG[0]=0x12345678 |
| 4 | SIG[0]=0x55aa55aa, mcause=0 — legal write works |
same |
Full signature for PROG=0, WB=0:
TB WB=0 PROG=0
RESULT done cycles=58
RESULT SIG[0]=0x55aa55aa # mscratch after "csrrw x16, mscratch, t2"
RESULT SIG[1]=0x00002101 # mtvec (base 0x2100, mode 01)
RESULT SIG[2]=0x00000002 # mcause = illegal instruction
RESULT SIG[3]=0x000020a0 # mepc = the illegal instruction
RESULT SIG[4]=0x34039873 # mtval = faulting instruction
RESULT SIG[5]=0x00000000
RESULT SIG[6]=0x00000000
RESULT SIG[7]=0x00000000
RESULT mstatus=0c mepc=000020a0 mcause=02 mtval=34039873
Same image with WB=1:
TB WB=1 PROG=0
RESULT done cycles=58
RESULT SIG[0]=0x12345678 # no write; correct
RESULT SIG[1]=0x00002101
RESULT SIG[2]=0x00000002
RESULT SIG[3]=0x000020a0
RESULT SIG[4]=0x34039873
PROG=1, WB=0 (the trapping instruction changes the trap vector):
TB WB=0 PROG=1
TIMEOUT cycles=20001 pc=00002000 mepc=00002000 mcause=02
PROG=1, WB=1:
TB WB=1 PROG=1
RESULT SIG[0]=0x12345678 # mscratch unchanged
RESULT SIG[1]=0x00002101 # mtvec unchanged
RESULT SIG[2]=0x00000002 # mcause
RESULT SIG[3]=0x000020a0 # mepc
RESULT SIG[4]=0x30539873 # mtval
For PROG=2 and PROG=3 the illegal instruction leaves mscratch at 0x00000000 at WB=0 (writing x0 / rs1=x16 = 0), while WB=1 keeps 0x12345678. The legal control (PROG=4) writes 0x55aa55aa and does not trap (mcause=0) in either build.
Root cause
At a896ef2:
-
rtl/ibex_decoder.sv:202-205computes the reserved-register condition:assign illegal_reg_16 = (RV32E || (cheriot_enable_i == IbexMuBiOn)) && ((raddr_a[4] && rf_ren_a_o) || (raddr_b[4] && rf_ren_b_o) || (instr_rd[4] && rf_we_or_load)); -
The decoder's side-effect suppression block is guarded by
illegal_insnonly (rtl/ibex_decoder.sv:844-857):// make sure illegal instructions detected in the decoder do not propagate from decoder // into register file, LSU, EX, WB, CSRs, PC if (illegal_insn) begin rf_we = 1'b0; data_req_o = 1'b0; data_we_o = 1'b0; jump_in_dec_o = 1'b0; jump_set_o = 1'b0; branch_in_dec_o = 1'b0; csr_access_o = 1'b0; endillegal_reg_16is merged intoillegal_insn_oatrtl/ibex_decoder.sv:1257, but the only effect on the datapath is the RF write mask at:1260:assign illegal_insn_o = illegal_insn | illegal_reg_16; assign rf_we_o = rf_we & ~illegal_reg_16;Therefore
csr_access_o,data_req_o,data_we_oand the jump/branch requests survive an instruction whose only problem is a reserved register number. -
With
WritebackStage=0, the ID-stage execution qualifier does not include an exception kill (rtl/ibex_id_stage.sv:1149-1150):assign instr_executing_spec = instr_valid_i & ~instr_fetch_err_i & controller_run; assign instr_executing = instr_executing_spec;The WB=1 path (
:1050-1053) does include~instr_kill, andinstr_kill(:1024-1027) includesid_exception_nc, i.e. the illegal-instruction exception:assign instr_kill = instr_fetch_err_i | wb_exception | id_exception_nc | ~controller_run;Hence, at WB=0,
csr_op_en_o = csr_access_o & instr_executing & ...(:744-746) — andlsu_req(:729) for the load/store form of the same bug — are still asserted in the cycle the trap is recognized. The controller flushes the instruction in the following cycle, but the CSR write has already been committed. -
The CSR write itself is not blocked later either.
csr_we_int(rtl/ibex_cs_registers.sv:1018-1021) excludes onlyillegal_csr_insn_o:assign csr_we_int = csr_wr & csr_op_en_i & (~(BaseIsa == BaseIsaRV32IorCHERIoT) | (cheriot_enable_i != IbexMuBiOn) | debug_mode_i | pcc_cap_q.perms.SR) & ~illegal_csr_insn_o;illegal_csr_insn_ocovers a non-existent CSR, a write to a read-only CSR, insufficient privilege and debug-only CSRs. It does not coverillegal_reg_16: the addressed CSR (e.g.mscratch,mtvec) exists, is writable, and M-mode is allowed to write it. The write therefore proceeds whenevercsr_op_en_iis high.
Why WB=1 is clean: instr_kill includes id_exception_nc, so instr_executing (and therefore csr_op_en_o / lsu_req) is low in the trap cycle. WB=0 has no equivalent term; it relies only on the controller's later flush to discard the instruction, which does not prevent the CSR (or LSU) operation from committing first.
Impact
- The demonstrated writes are from M-mode (the only mode that can write
mtvec). U-mode CSR writes are still rejected by the existing privilege check, and U-mode memory accesses are still bounded by PMP, so this is not an unconditional privilege escalation. - In M-mode, a trapping instruction can rewrite
mtvec(demonstrated),mepc,mstatus,pmpcfg,pmpaddr,mseccfgand other writable CSRs before the trap is taken. A monitor that treats the illegal-instruction trap as "the instruction had no effect" is unsound. - With
mtvecas the target the hart can be redirected into non-code memory and never reaches its handler (TIMEOUTatpc=0x2000above). - The same
illegal_reg_16predicate also applies to the runtime CHERIoT 16-register mode, whose productionsmallconfiguration isWritebackStage=0. The CSR half is narrower there (csr_we_intrequirespcc_cap_q.perms.SRor debug mode), but the load/store half uses the normal LSU path. The CHERIoT configuration was not built for this report.
Related upstream issues
Closest issues found (checked against the open/closed list on 2026-10-26):
- #2192 (open):
jalr x2,0(x16)on two consecutive multicycle invalid instructions updates x2. That is a jalr/rd-update path; no CSR write. - #2278 (open): a store fetched from PMP non-executable memory drives
data_we_o. Same "invalid instruction still drives a bus" symptom class, different trigger and RTL path. - #2537 (open): RV32E + Zcmp reserved moves partially update s0/s1.
- #1132 (open):
minstretcounts illegal instructions.
None describes a CSR write from an illegal instruction, the mtvec corruption, or the writeback-stage dependence. Issue #2473/#2474 (MPRV with DRET / Debug Mode) are unrelated.
Expected Behavior
RV32E defines only x0–x15; an instruction that references x16–x31 is reserved and must raise an illegal-instruction exception. Ibex documents the 16-register file in doc/03_reference/register_file.rst:7 and doc/03_reference/cheriot.rst:53.
The exception must be precise: the trapping instruction must have no architecturally visible effect, including no CSR write. This is also what the decoder comment at rtl/ibex_decoder.sv:844-848 states as the intent ("do not propagate ... into register file, LSU, EX, WB, CSRs, PC"), and the design already implements the RF half of the rule (rf_we_o = rf_we & ~illegal_reg_16); the CSR/LSU escape is an omission.
A minimal fix is a separate suppression block in the decoder that does not clear rf_we:
--- a/rtl/ibex_decoder.sv
+++ b/rtl/ibex_decoder.sv
@@ -855,6 +855,19 @@
branch_in_dec_o = 1'b0;
csr_access_o = 1'b0;
end
+
+ // RV32E/CHERIoT reserved register numbers must not have any side effect.
+ // rf_we is deliberately not cleared here: rf_we_o already masks it, and
+ // illegal_reg_16 is derived from rf_we (via rf_we_or_load), so clearing it
+ // in this block would create a combinational loop for CSR instructions.
+ if (illegal_reg_16) begin
+ data_req_o = 1'b0;
+ data_we_o = 1'b0;
+ jump_in_dec_o = 1'b0;
+ jump_set_o = 1'b0;
+ branch_in_dec_o = 1'b0;
+ csr_access_o = 1'b0;
+ end
end
/////////////////////////////
rf_we must not be cleared in this block: illegal_reg_16 is derived from rf_we through rf_we_or_load (rtl/ibex_decoder.sv:195-205), so clearing it inside the same condition creates a combinational loop for CSR instructions. The naive if (illegal_insn || illegal_reg_16) form including rf_we = 1'b0 makes Verilator abort with DIDNOTCONVERGE (reproduced during this investigation). An alternative fix is to add ~instr_kill to the WB=0 instr_executing assignment, matching the WB=1 path; the decoder change is narrower because it preserves the "illegal instruction has no side effect" intent even if another controller path asserts instr_executing.
With the patch above applied to a896ef2 (no other changes), this harness reports at WB=0:
+PROG |
Patched WB=0 |
|---|---|
| 0 | SIG[0]=0x12345678 (write suppressed) |
| 1 | SIG[1]=0x00002101, mcause=2, mepc=0x20a0 (handler reached) |
| 2 | SIG[0]=0x12345678 |
| 3 | SIG[0]=0x12345678 |
The patch is a suggestion, not applied to the tree used for this report.
Steps to reproduce the issue
Everything needed is in this report: one SystemVerilog file, one Verilator command per configuration, and the run commands.
1. Requirements
-
Verilator 5.x (this report used 5.051 devel).
-
A clean Ibex checkout at
a896ef2; the normal upstream tree already containsvendor/lowrisc_ip:git clone https://github.com/lowRISC/ibex git -C ibex checkout a896ef22777a40ad3f5a2a3b97fecd5116480c4c -
No RTL changes; the bug reproduces on the unmodified tree.
2. Save the testbench as tb.sv
tb.sv (self-contained: the program is poked into memory, no hex file)
// Minimal, self-contained repro for:
// RV32E + WritebackStage=0: an illegal instruction using x16-x31 in rd/rs1
// still writes the CSR.
//
// ibex_core + external RF + 64 KiB memory + 1-cycle OBI slaves. No external
// program file: the test program is poked into memory below. No RTL changes.
//
// -GWB=0|1 WritebackStage (default 0 = bug, 1 = control)
// +PROG=0..4 test program (default 0)
// 0: csrrw x16, mscratch, t2 (t2=0x55AA55AA)
// 1: csrrw x16, mtvec, t2 (t2=0x2000)
// 2: csrrw x16, mscratch, x0
// 3: csrrw x0, mscratch, x16
// 4: csrrw x6, mscratch, t2 (legal control)
//
// The program preloads mscratch=0x12345678, mtvec=0x2100. Illegal variants
// trap to 0x2100, which records mscratch/mtvec/mcause/mepc/mtval at 0x3000;
// the legal variant records mscratch on the normal path. A store of 0xD00D
// to 0x3100 ends the run; the TB prints the signature.
module tb #(
parameter bit WB = 1'b0
);
import ibex_pkg::*;
localparam [31:0] SIG_BASE = 32'h0000_3000;
localparam [31:0] DONE_ADDR = 32'h0000_3100;
localparam int SIG_WORDS = 8;
logic clk = 0;
logic rst_n = 0;
always #5 clk = ~clk;
// OBI instruction
logic instr_req, instr_gnt, instr_rvalid, instr_err;
logic [31:0] instr_addr, instr_rdata;
// OBI data
logic data_req, data_gnt, data_rvalid, data_we, data_tag, data_err;
logic [3:0] data_be;
logic [31:0] data_addr, data_wdata, data_rdata;
// Register file
logic [31:0] rf [0:31];
logic [4:0] rf_raddr_a, rf_raddr_b, rf_waddr;
logic rf_we;
logic [31:0] rf_wdata;
// 64 KiB memory
logic [31:0] mem [0:16383];
logic [IC_TAG_SIZE-1:0] ic_tag_rdata [IC_NUM_WAYS];
logic [IC_LINE_SIZE-1:0] ic_data_rdata [IC_NUM_WAYS];
int unsigned cycle = 0;
integer prog = 0;
// Fill memory with NOPs, then build the test program at 0x2080.
task automatic poke(input int unsigned a, input logic [31:0] d);
mem[a[15:2]] = d;
endtask
initial begin
if (!$value$plusargs("PROG=%d", prog)) prog = 0;
$display("TB WB=%0d PROG=%0d", WB, prog);
for (int i = 0; i < 16384; i++) mem[i] = 32'h0000_0000; // zeros
for (int i = 0; i < 32; i++) rf[i] = 32'b0;
for (int i = 0; i < IC_NUM_WAYS; i++) begin
ic_tag_rdata[i] = '0;
ic_data_rdata[i] = '0;
end
// ---- program at 0x2080 ------------------------------------------------
poke(32'h2080, 32'h0000_22b7); // lui t0, 2
poke(32'h2084, 32'h1002_8293); // addi t0, t0, 0x100 -> t0 = 0x2100
poke(32'h2088, 32'h3052_9073); // csrw mtvec, t0
poke(32'h208c, 32'h1234_52b7); // lui t0, 0x12345
poke(32'h2090, 32'h6782_8293); // addi t0, t0, 0x678 -> t0 = 0x12345678
poke(32'h2094, 32'h3402_9073); // csrw mscratch, t0
case (prog)
1: begin
poke(32'h2098, 32'h0000_23b7); // lui t2, 2
poke(32'h209c, 32'h0000_0013); // nop -> t2 = 0x2000
poke(32'h20a0, 32'h3053_9873); // csrrw x16, mtvec, t2 (illegal rd)
end
2: begin
poke(32'h2098, 32'h55aa_53b7); // lui t2, 0x55AA5
poke(32'h209c, 32'h5aa3_8393); // addi t2, t2, 0x5AA -> t2 = 0x55AA55AA
poke(32'h20a0, 32'h3400_1873); // csrrw x16, mscratch, x0 (illegal rd)
end
3: begin
poke(32'h2098, 32'h55aa_53b7);
poke(32'h209c, 32'h5aa3_8393);
poke(32'h20a0, 32'h3408_1073); // csrrw x0, mscratch, x16 (illegal rs1)
end
4: begin
poke(32'h2098, 32'h55aa_53b7);
poke(32'h209c, 32'h5aa3_8393);
poke(32'h20a0, 32'h3403_9373); // csrrw x6, mscratch, t2 (legal)
end
default: begin
poke(32'h2098, 32'h55aa_53b7);
poke(32'h209c, 32'h5aa3_8393);
poke(32'h20a0, 32'h3403_9873); // csrrw x16, mscratch, t2 (illegal rd)
end
endcase
// Normal path (executes only for the legal variant; illegal ones trap).
poke(32'h20a4, 32'h0000_34b7); // lui s1, 3 -> s1 = 0x3000
poke(32'h20a8, 32'h3400_2373); // csrr t1, mscratch
poke(32'h20ac, 32'h0064_a023); // sw t1, 0(s1)
poke(32'h20b0, 32'h0000_d337); // lui t1, 0xD
poke(32'h20b4, 32'h00d3_0313); // addi t1, t1, 0xD -> t1 = 0xD00D
poke(32'h20b8, 32'h0000_32b7); // lui t0, 3
poke(32'h20bc, 32'h1002_8293); // addi t0, t0, 0x100 -> t0 = 0x3100
poke(32'h20c0, 32'h0062_a023); // sw t1, 0(t0) -> done marker
poke(32'h20c4, 32'h0000_006f); // j .
// ---- trap handler at 0x2100 ------------------------------------------
poke(32'h2100, 32'h0000_34b7); // lui s1, 3 -> s1 = 0x3000
poke(32'h2104, 32'h3400_2373); // csrr t1, mscratch
poke(32'h2108, 32'h0064_a023); // sw t1, 0(s1) -> SIG[0]
poke(32'h210c, 32'h3050_2373); // csrr t1, mtvec
poke(32'h2110, 32'h0064_a223); // sw t1, 4(s1) -> SIG[1]
poke(32'h2114, 32'h3420_2373); // csrr t1, mcause
poke(32'h2118, 32'h0064_a423); // sw t1, 8(s1) -> SIG[2]
poke(32'h211c, 32'h3410_2373); // csrr t1, mepc
poke(32'h2120, 32'h0064_a623); // sw t1, 12(s1) -> SIG[3]
poke(32'h2124, 32'h3430_2373); // csrr t1, mtval
poke(32'h2128, 32'h0064_a823); // sw t1, 16(s1) -> SIG[4]
poke(32'h212c, 32'h0000_d337); // lui t1, 0xD
poke(32'h2130, 32'h00d3_0313); // addi t1, t1, 0xD
poke(32'h2134, 32'h0000_32b7); // lui t0, 3
poke(32'h2138, 32'h1002_8293); // addi t0, t0, 0x100
poke(32'h213c, 32'h0062_a023); // sw t1, 0(t0) -> done marker
poke(32'h2140, 32'h0000_006f); // j .
end
ibex_core #(
.BaseIsa (BaseIsaRV32I),
.PMPEnable (1'b1),
.PMPGranularity (0),
.PMPNumRegions (16),
.MHPMCounterNum (0),
.RV32E (1'b1),
.RV32M (RV32MFast),
.RV32B (RV32BNone),
.RV32ZC (RV32ZcaZcbZcmp),
.BranchTargetALU (1'b0),
.WritebackStage (WB),
.ICache (1'b0),
.ICacheECC (1'b0),
.BranchPredictor (1'b0),
.DbgTriggerEn (1'b1),
.DbgHwBreakNum (2),
.DmHaltAddr (32'h0000_2200),
.DmExceptionAddr (32'h0000_2204),
.SecureIbex (1'b0),
.DummyInstructions(1'b0),
.RegFileECC (1'b0),
.RegFileDataWidth (32),
.MemECC (1'b0),
.MemDataWidth (32)
) dut (
.clk_i (clk),
.rst_ni (rst_n),
.hart_id_i (32'b0),
.boot_addr_i (32'h0000_2000),
.cheriot_enable_i (IbexMuBiOff),
.instr_req_o (instr_req),
.instr_gnt_i (instr_gnt),
.instr_rvalid_i (instr_rvalid),
.instr_addr_o (instr_addr),
.instr_rdata_i (instr_rdata),
.instr_err_i (instr_err),
.data_req_o (data_req),
.data_gnt_i (data_gnt),
.data_rvalid_i (data_rvalid),
.data_we_o (data_we),
.data_be_o (data_be),
.data_addr_o (data_addr),
.data_wdata_o (data_wdata),
.data_tag_o (data_tag),
.data_rdata_i (data_rdata),
.data_tag_i (1'b0),
.data_err_i (data_err),
.dummy_instr_id_o (),
.dummy_instr_wb_o (),
.rf_raddr_a_o (rf_raddr_a),
.rf_raddr_b_o (rf_raddr_b),
.rf_waddr_wb_o (rf_waddr),
.rf_we_wb_o (rf_we),
.rf_wdata_wb_ecc_o (rf_wdata),
.rf_rdata_a_ecc_i (rf[rf_raddr_a]),
.rf_rdata_b_ecc_i (rf[rf_raddr_b]),
.rf_wcap_ecc_wb_o (),
.rf_rcap_a_ecc_i ('0),
.rf_rcap_b_ecc_i ('0),
.ic_tag_req_o (),
.ic_tag_write_o (),
.ic_tag_addr_o (),
.ic_tag_wdata_o (),
.ic_tag_rdata_i (ic_tag_rdata),
.ic_data_req_o (),
.ic_data_write_o (),
.ic_data_addr_o (),
.ic_data_wdata_o (),
.ic_data_rdata_i (ic_data_rdata),
.ic_scr_key_valid_i (1'b0),
.ic_scr_key_req_o (),
.irq_software_i (1'b0),
.irq_timer_i (1'b0),
.irq_external_i (1'b0),
.irq_fast_i (15'b0),
.irq_nm_i (1'b0),
.irq_pending_o (),
.debug_req_i (1'b0),
.crash_dump_o (),
.double_fault_seen_o (),
.fetch_enable_i (IbexMuBiOn),
.mcounteren_writable_i (IbexMuBiOn),
.alert_minor_o (),
.alert_major_internal_o (),
.alert_major_bus_o (),
.core_busy_o ()
);
// 1-cycle OBI slaves: gnt combinational, rvalid one cycle later.
assign instr_gnt = instr_req & ~instr_rvalid;
assign data_gnt = data_req & ~data_rvalid;
assign instr_err = 1'b0;
assign data_err = 1'b0;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
instr_rvalid <= 1'b0;
data_rvalid <= 1'b0;
instr_rdata <= 32'b0;
data_rdata <= 32'b0;
end else begin
instr_rvalid <= instr_req & ~instr_rvalid;
if (instr_req) instr_rdata <= mem[instr_addr[15:2]];
data_rvalid <= data_req & ~data_rvalid;
if (data_req) data_rdata <= mem[data_addr[15:2]];
if (data_req & data_gnt & data_we) begin
if (data_be == 4'b1111) begin
mem[data_addr[15:2]] <= data_wdata;
end else begin
for (int k = 0; k < 4; k++) begin
if (data_be[k]) mem[data_addr[15:2]][8*k +: 8] <= data_wdata[8*k +: 8];
end
end
end
end
end
always_ff @(posedge clk) begin
if (rf_we && (rf_waddr != 5'b0)) rf[rf_waddr] <= rf_wdata;
end
always @(posedge clk) begin
cycle <= cycle + 1;
if (data_req && data_gnt && data_we && (data_addr == DONE_ADDR) &&
(data_wdata == 32'h0000_D00D)) begin
$display("RESULT done cycles=%0d", cycle);
for (int j = 0; j < SIG_WORDS; j++) begin
$display("RESULT SIG[%0d]=0x%08x", j, mem[(SIG_BASE >> 2) + j]);
end
$display("RESULT mstatus=%h mepc=%h mcause=%h mtval=%h",
dut.cs_registers_i.mstatus_q, dut.cs_registers_i.mepc_q,
dut.cs_registers_i.mcause_q, dut.cs_registers_i.mtval_q);
$finish;
end
if (cycle > 20000) begin
$display("TIMEOUT cycles=%0d pc=%h mepc=%h mcause=%h",
cycle, dut.pc_id, dut.cs_registers_i.mepc_q,
dut.cs_registers_i.mcause_q);
$finish;
end
end
initial begin
repeat (10) @(posedge clk);
rst_n = 1;
end
endmodule
3. Build both configurations
IBEX=/path/to/ibex
verilator --binary --timing -Wno-fatal -Wno-PINMISSING --top-module tb \
-GWB=0 -j 4 \
-I"$IBEX/vendor/lowrisc_ip/ip/prim/rtl" \
-I"$IBEX/vendor/lowrisc_ip/ip/prim_generic/rtl" \
-I"$IBEX/vendor/lowrisc_ip/dv/sv/dv_utils" \
"$IBEX/rtl/ibex_pkg.sv" "$IBEX/rtl/ibex_cheriot_pkg.sv" \
"$IBEX/vendor/lowrisc_ip/ip/prim/rtl/prim_cipher_pkg.sv" \
"$IBEX/rtl/ibex_alu.sv" \
"$IBEX/rtl/ibex_branch_predict.sv" \
"$IBEX/rtl/ibex_cheriot_ex.sv" \
"$IBEX/rtl/ibex_compressed_decoder.sv" \
"$IBEX/rtl/ibex_controller.sv" \
"$IBEX/rtl/ibex_core.sv" \
"$IBEX/rtl/ibex_counter.sv" \
"$IBEX/rtl/ibex_cs_registers.sv" \
"$IBEX/rtl/ibex_csr.sv" \
"$IBEX/rtl/ibex_decoder.sv" \
"$IBEX/rtl/ibex_dummy_instr.sv" \
"$IBEX/rtl/ibex_ex_block.sv" \
"$IBEX/rtl/ibex_fetch_fifo.sv" \
"$IBEX/rtl/ibex_icache.sv" \
"$IBEX/rtl/ibex_id_stage.sv" \
"$IBEX/rtl/ibex_if_stage.sv" \
"$IBEX/rtl/ibex_load_store_unit.sv" \
"$IBEX/rtl/ibex_wb_stage.sv" \
"$IBEX/rtl/ibex_multdiv_fast.sv" \
"$IBEX/rtl/ibex_multdiv_slow.sv" \
"$IBEX/rtl/ibex_pmp.sv" \
"$IBEX/rtl/ibex_prefetch_buffer.sv" \
"$IBEX/rtl/ibex_register_file_ff.sv" \
"$IBEX/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_buf.sv" \
tb.sv --Mdir obj_wb0
# same command with -GWB=1 and --Mdir obj_wb1
-GWB=0|1 only overrides the TB's WB parameter; tb.sv is identical for both builds.
4. Run and compare
for p in 0 1 2 3 4; do echo "== PROG=$p WB=0"; ./obj_wb0/Vtb +PROG=$p; done
for p in 0 1 2 3 4; do echo "== PROG=$p WB=1"; ./obj_wb1/Vtb +PROG=$p; done
The binaries have no working-directory or file dependency. The runs terminate on the 0xD00D store to 0x3100 or after 20000 cycles.
Expected (full signature in the Observed Behavior section):
PROG=0 WB=0: SIG[0]=0x55aa55aa, SIG[1]=0x00002101, SIG[2]=2, SIG[3]=0x20a0
PROG=0 WB=1: SIG[0]=0x12345678, SIG[1]=0x00002101, SIG[2]=2, SIG[3]=0x20a0
PROG=1 WB=0: TIMEOUT pc=0x2000 mcause=2
PROG=1 WB=1: SIG[1]=0x00002101, SIG[2]=2, SIG[3]=0x20a0
PROG=2 WB=0: SIG[0]=0x00000000 PROG=2 WB=1: SIG[0]=0x12345678
PROG=3 WB=0: SIG[0]=0x00000000 PROG=3 WB=1: SIG[0]=0x12345678
PROG=4 WB=0/1: SIG[0]=0x55aa55aa, mcause=0
Program layout
The TB pokes the same skeleton for every +PROG; only the instruction at 0x20A0 (and the t2 value) changes:
0x2080 li t0, 0x2100
0x2088 csrw mtvec, t0
0x208c li t0, 0x12345678
0x2094 csrw mscratch, t0
0x2098 li t2, <value> # 0x55AA55AA, or 0x2000 for PROG=1
0x20a0 <the CSR instruction under test>
# normal path (taken only by the legal control)
0x20a4 li s1, 0x3000
0x20a8 csrr t1, mscratch
0x20ac sw t1, 0(s1)
0x20b0 li t1, 0xD00D
0x20b8 li t0, 0x3100
0x20c0 sw t1, 0(t0)
0x20c4 j .
# trap handler
0x2100 li s1, 0x3000
0x2104 csrr t1, mscratch ; 0x2108 sw t1, 0(s1)
0x210c csrr t1, mtvec ; 0x2110 sw t1, 4(s1)
0x2114 csrr t1, mcause ; 0x2118 sw t1, 8(s1)
0x211c csrr t1, mepc ; 0x2120 sw t1, 12(s1)
0x2124 csrr t1, mtval ; 0x2128 sw t1, 16(s1)
0x212c li t1, 0xD00D
0x2134 li t0, 0x3100
0x213c sw t1, 0(t0)
0x2140 j .
The source code of the TB above contains the exact instruction words and the address of every poke.
My Environment
EDA tool and version: Verilator 5.051 devel (rev vUNKNOWN-built20260806-e413e67), --binary --timing
Operating system: Ubuntu 24.04.5 LTS, x86_64 (WSL2, kernel 6.18.33.2-microsoft-standard-WSL2)
Version of the Ibex source code: a896ef22777a40ad3f5a2a3b97fecd5116480c4c (2026-10-23). No changes to the Ibex source tree (git status --porcelain empty); only the TB parameter WB differs between the two builds.
- Ngôn ngữ chính
- SystemVerilog
- Star
- 2.2k
- Fork
- 836
- Merge trung bình
- 5 ngày 8 giờ
- Pull request đã merge (30 ngày)
- 22
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Không có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của lowRISC/ibex
-
[Bug Report] rvfi_intr is not set on the first instruction of an exception handlerCó thể đã có người làm @h-filali đã nhận 7 ngày trước. Đang mởType:Bug
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 68/100
lowRISC/ibex#2556 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Type:Bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 83/100
lowRISC/ibex#2528 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
mip documentation errorĐang mởType:Bug
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 2 ngày
-
[doc] Document basic commands for running/checking IbexCó thể đã có người làm @shrutikbalwan đã nhận 47 ngày trước. Đang mởComponent:Doc Good First Issue Type:Task
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
lowRISC/ibex#741 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 42/100
Maintainer thường phản hồi trong vòng 2 ngày
Issue tương tự
-
🇩🇪 Aufruf zur Mitarbeit und Übersetzungskontrolle: Deutschsprachige Community (README.de.md)Đang mởdocumentation good first issue help wanted i18n
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
EchoTools/nevr-runtime#2730 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
eliben/pyelftools#701 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
tailscale/tailscale-rs#485 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
status: waiting-for-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 63/100
fluent/fluent-bit#12787 ·
Maintainer thường phản hồi trong vòng 1 ngày