// 仿真顶层: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 在 // .sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 // 路径,并报告实际命中的那个。显式给出的路径始终优先: // vvp cpu21_sim +ROM_FILE= (iverilog/vvp) // xelab ... -generic_top "ROM_FILE=" (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