Files
risc-v-cpu/testbench/tb_no_intr.v
T
Anccend 266ec6a049 重构:按功能拆分流水线模块、修复 BLT 指令、注释中文化
1) 模块拆分(便于单独仿真调试)
   把原来单文件的三模块结构按功能拆成 10 个独立文件,顶层只保留五级流水
   寄存器、子模块例化、PC/IF-ID/ID-EX 控制优先级与对外输出:
     cpu21_riscv_alu.v            组合 ALU(result2 用于 MUL 高位/余数)
     cpu21_bpb_8.v                8 项全相联分支目标缓冲
     cpu21_riscv_decoder.v        ID 级组合译码器
     cpu21_riscv_regfile.v        寄存器堆(写优先旁路)
     cpu21_riscv_forward_unit.v   EX 级前递网络
     cpu21_riscv_store_unit.v     EX 级存储数据对齐(sw/sb)
     cpu21_riscv_branch_unit.v    EX 级控制流裁决
     cpu21_riscv_hazard_unit.v    load-use 冒险与停顿/冲刷判定
     cpu21_riscv_irq_ctrl.v       中断优先级、ustatus/uepc、嵌套返回栈
     cpu21_riscv_perf_counters.v  性能计数器
   已把上述文件加入 Vivado 工程 sources_1;端口、时序与行为经 xsim 对同一
   ROM 逐周期回归验证,统计量完全一致。

2) 修复 BLT 指令(真值表第 28 行:opcode IR[6:2]=0x18、funct3=100、ALU_OP=SLT)
   此前 funct3=100 未译码,blt 被当作空指令执行,于是
   `blt s1,zero,loop` 的循环只执行一次就顺序落到退出代码并停机
   (现象:只输出第一个值后就不再运行)。
     - cpu21_riscv_decoder.v:新增 blt_o,OP_BRANCH 接受 funct3=100,
       并按真值表给出 ALU_SLT(beq/bne 仍为 SUB,bltu 仍为 SLTU)
     - cpu21_riscv_branch_unit.v:新增 blt_i,用有符号比较
       $signed(src1) < $signed(src2) 裁决
     - cpu21_riscv_redirect_int_bpb.v:新增 idex_blt_q 流水寄存器,
       IF 级 f_is_branch 纳入 funct3=100,使 blt 也参与 BPB 预测
   验证:blt 小程序正确输出 -3/-2/-1 后停机(cond_taken=2);
   benchmark 第 [4] 段完整输出 fffffff1..ffffffff;
   中断测试程序的中断入口与停留周期与改动前一致。

3) 注释中文化
   - cpu21_riscv_redirect_int_bpb.v 及全部新增子模块使用中文注释;
   - testbench/tb_cpu21_riscv_redirect_int_bpb.v 与新增的 tb_no_intr.v
     注释全部译为中文。
   - testbench/tb_no_intr.v:与中断测试平台同框架但不注入 irq 脉冲的对照
     测试平台(修复其 $dumpvars 引用了不存在的模块名,并改用 tb_no_intr.vcd
     避免与另一个 TB 的波形文件互相覆盖)。

4) 其他
   - testbench/tb_cpu21_riscv_redirect_int_bpb.v 的默认 ROM_FILE 改为
     cpu21_riscv_redirect_int_bpb_rom.hex(中断测试程序);
   - cpu21_riscv_redirect_int_bpb说明.md 增补"文件与模块划分"章节与 BLT 说明;
   - 真值表.txt 移动到 testbench/programs/ 下。
2026-09-12 18:48:31 +08:00

259 lines
10 KiB
Verilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架,
// 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。
//
// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。
// 示例:
// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_no_intr.v
// vvp cpu21_sim
// 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。
//
// 程序镜像通过 $readmemh 从 ROM_FILE 载入 ROM 模型。$readmemh 找不到文件时
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// 0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径,并报告实际命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
module tb_no_intr #(
parameter ROM_FILE = "risc-v-benchmark_ccab.hex"
// risc-v-benchmark_ccab.hex
// cpu21_riscv_redirect_int_bpb_rom.hex
// risc-v-branch-predict.hex
);
localparam integer ROM_WORDS = 1024;
localparam integer RAM_WORDS = 1024;
localparam integer PATH_LEN = 512;
// 哨兵值:不可能出现在 RISC-V 镜像的第一个字中。
localparam [31:0] EMPTY_ROM_WORD = 32'hFFFF_FFFF;
// 中断入口地址:DUT 例化与下面的打印条件共用。DUT 用 PC[11:2] 访问 ROM
// 因此这三个入口位于程序镜像的第 43 / 120 / 192 个字(即每个中断处理
// 程序开头的第一条 "sw ..., 0(sp)")。
localparam [31:0] IRQ1_VECTOR = 32'h0000_30ac;
localparam [31:0] IRQ2_VECTOR = 32'h0000_31e0;
localparam [31:0] IRQ3_VECTOR = 32'h0000_3300;
reg clk;
reg reset;
reg [ 31:0] instr_i;
reg [ 31:0] data_rdata_i;
reg [ 2:0] irq_i;
reg go_i;
reg [ 31:0] rom [0:ROM_WORDS-1];
reg [ 31:0] ram [0:RAM_WORDS-1];
wire [ 31:0] instr_addr_o;
wire [ 31:0] data_addr_o;
wire [ 31:0] data_wdata_o;
wire [ 3:0] data_wstrb_o;
wire data_we_o;
wire [ 31:0] led_data_o;
wire led_valid_o;
wire [ 31:0] if_pc_o;
wire [ 31:0] id_pc_o;
wire [ 31:0] ex_pc_o;
wire [ 31:0] mem_pc_o;
wire [ 31:0] wb_pc_o;
wire [ 31:0] if_ir_o;
wire [ 31:0] id_ir_o;
wire [ 31:0] ex_ir_o;
wire [ 31:0] mem_ir_o;
wire [ 31:0] wb_ir_o;
wire [ 31:0] rdin_o;
wire [ 31:0] mdin_o;
wire reg_write_o;
wire mem_write_o;
wire halted_o;
wire [ 2:0] irq_pending_o;
wire [ 1:0] irq_current_level_o;
wire [ 31:0] uepc_o;
wire [ 31:0] ustatus_o;
wire bpb_predict_hit_o;
wire bpb_predict_taken_o;
wire bpb_mispredict_o;
wire [ 15:0] cycle_count_o;
wire [ 15:0] stall_count_o;
wire [ 15:0] bubble_count_o;
wire [ 15:0] conditional_taken_count_o;
wire [ 15:0] unconditional_branch_count_o;
wire [ 15:0] prediction_success_count_o;
wire [ 15:0] prediction_failure_count_o;
integer i;
integer tb_cycle;
reg rom_loaded;
reg [8*PATH_LEN-1:0] rom_file_path;
cpu21_riscv_redirect_int_bpb #(
.RESET_PC (32'h0000_0000),
.IRQ1_VECTOR(IRQ1_VECTOR),
.IRQ2_VECTOR(IRQ2_VECTOR),
.IRQ3_VECTOR(IRQ3_VECTOR),
// 与中断测试平台一致,复位后即使能 MIE。本测试平台从不产生 irq_i
// 脉冲,因此该参数只有在你自行添加脉冲时才有影响。
.USTATUS_INIT(32'h0000_0001)
) dut (
.clk (clk),
.reset (reset),
.instr_i (instr_i),
.data_rdata_i (data_rdata_i),
.irq_i (irq_i),
.go_i (go_i),
.instr_addr_o (instr_addr_o),
.data_addr_o (data_addr_o),
.data_wdata_o (data_wdata_o),
.data_wstrb_o (data_wstrb_o),
.data_we_o (data_we_o),
.led_data_o (led_data_o),
.led_valid_o (led_valid_o),
.if_pc_o (if_pc_o),
.id_pc_o (id_pc_o),
.ex_pc_o (ex_pc_o),
.mem_pc_o (mem_pc_o),
.wb_pc_o (wb_pc_o),
.if_ir_o (if_ir_o),
.id_ir_o (id_ir_o),
.ex_ir_o (ex_ir_o),
.mem_ir_o (mem_ir_o),
.wb_ir_o (wb_ir_o),
.rdin_o (rdin_o),
.mdin_o (mdin_o),
.reg_write_o (reg_write_o),
.mem_write_o (mem_write_o),
.halted_o (halted_o),
.irq_pending_o (irq_pending_o),
.irq_current_level_o (irq_current_level_o),
.uepc_o (uepc_o),
.ustatus_o (ustatus_o),
.bpb_predict_hit_o (bpb_predict_hit_o),
.bpb_predict_taken_o (bpb_predict_taken_o),
.bpb_mispredict_o (bpb_mispredict_o),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
always #5 clk = ~clk;
// Logisim 的 ROM 以 PC[11:2] 作为字地址;这也让原来的三个中断向量映射到
// 相同的 ROM 表项。
always @* begin
instr_i = 32'h00000013;
if (instr_addr_o[11:2] < ROM_WORDS) instr_i = rom[instr_addr_o[11:2]];
end
// 数据存储器的异步读模型。
always @* begin
data_rdata_i = 32'b0;
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end
// 带字节使能的同步写模型。
always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin
if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
if (data_wstrb_o[1]) ram[data_addr_o[11:2]][15:8] <= data_wdata_o[15:8];
if (data_wstrb_o[2]) ram[data_addr_o[11:2]][23:16] <= data_wdata_o[23:16];
if (data_wstrb_o[3]) ram[data_addr_o[11:2]][31:24] <= data_wdata_o[31:24];
end
end
always @(posedge clk) begin
if (reset) begin
tb_cycle = 0;
end else begin
tb_cycle = tb_cycle + 1;
if ((tb_cycle <= 20) || data_we_o || led_valid_o ||
(irq_pending_o != 3'b000) ||
(instr_addr_o == IRQ1_VECTOR) ||
(instr_addr_o == IRQ2_VECTOR) ||
(instr_addr_o == IRQ3_VECTOR)) begin
$display(
"t=%0t cyc=%0d PC=%08h ID=%08h EX=%08h MEM=%08h WB=%08h irq=%b pend=%b uepc=%08h LEDv=%b LED=%08h memwe=%b addr=%08h wdata=%08h wstrb=%b",
$time, tb_cycle, if_pc_o, id_ir_o, ex_ir_o, mem_ir_o, wb_ir_o, irq_i, irq_pending_o,
uepc_o, led_valid_o, led_data_o, data_we_o, data_addr_o, data_wdata_o, data_wstrb_o);
end
end
end
// 尝试从 "fname" 载入 ROM 镜像。"loaded" 为 1 表示文件存在且至少包含一个
// 字。利用 rom[0] 中的哨兵值,可以避免载入失败被静默忽略。
task load_rom_image;
input [8*PATH_LEN-1:0] fname;
output loaded;
integer fh;
begin
loaded = 1'b0;
fh = $fopen(fname, "r");
if (fh != 0) begin
$fclose(fh);
rom[0] = EMPTY_ROM_WORD;
$readmemh(fname, rom);
if (rom[0] === EMPTY_ROM_WORD) begin
// 文件能打开但内容为空:恢复默认的 NOP 填充。
rom[0] = 32'h0000_0013;
end else begin
rom_file_path = fname;
loaded = 1'b1;
$display("NOTE: ROM image loaded from \"%0s\".", fname);
end
end
end
endtask
initial begin
clk = 1'b0;
reset = 1'b1;
irq_i = 3'b000;
go_i = 1'b0;
tb_cycle = 0;
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h00000013;
for (i = 0; i < RAM_WORDS; i = i + 1) ram[i] = 32'b0;
// 预置几个字,便于在波形中观察数据存储器的活动。
ram[1] = 32'h1234_5678;
ram[2] = 32'h89ab_cdef;
// 依次探测 ROM 镜像的常见位置,第一个可读的候选文件生效;下面的探测
// 失败是无害的(在找到真正的镜像之前,ROM 保持 0x00000013 填充)。
$display("NOTE: searching for ROM image \"%0s\" ...", ROM_FILE);
rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded);
if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded);
if (!rom_loaded) load_rom_image({"testbench/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/", ROM_FILE}, rom_loaded);
if (!rom_loaded)
$display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
ROM_FILE
);
$dumpfile("tb_no_intr.vcd");
$dumpvars(0, tb_no_intr);
#22 reset = 1'b0;
// 本测试平台不注入中断脉冲:irq_i 始终为 3'b000,因此只走主程序
//(非中断)执行路径。
repeat (5560) @(negedge clk);
$display(
"FINAL: tb_cycles=%0d dut_cycles=%0d stalls=%0d bubbles=%0d cond_taken=%0d uncond=%0d pred_ok=%0d pred_fail=%0d halted=%b",
tb_cycle, cycle_count_o, stall_count_o, bubble_count_o, conditional_taken_count_o,
unconditional_branch_count_o, prediction_success_count_o, prediction_failure_count_o,
halted_o);
$finish;
end
endmodule