Files
risc-v-cpu/testbench/tb_no_intr.v
T
Anccend fed01f5910 工程整理:源文件复制进 pipelined-cpu/,提供一键重建脚本 create_project.tcl
- pipelined-cpu/ 变成自包含的工程目录,所有设计文件都在里面,可以整个拷给别人:
  srcs/rtl(12 个处理器 RTL)、srcs/board(板级顶层 + 片内 ROM 初始化,含 __FAST 版)、
  srcs/constrs(引脚约束 + 板级母版)、srcs/tb(三个测试平台)、srcs/rom(ROM 镜像),
  另外附上 tools/(迷你汇编器)与 programs/(测试程序源码与期望值)。
  Vivado 工程里的引用全部改到这些副本上(sources_1 ×15、constrs_1 ×2、sim_1 ×12、sim_2 ×2),
  并用副本重新综合通过。
- 新增 pipelined-cpu/create_project.tcl:手写的一键重建脚本。
  * 用 [info script] 定位自身目录,整个文件夹拷到任意路径都能用;
  * 就地创建工程(源文件留在 srcs/ 下不动),带文件自检与“工程已存在”提示;
  * 配好三个 fileset 的顶层、cpu21_board_rom(.vh) 的 Verilog Header 类型、
    xsim.simulate.runtime=all。
  实测:把工程文件夹拷到别处、从另一个工作目录运行脚本,工程重建成功且
  synth_design Complete!(分享包只有 create_project.tcl + srcs/ + tools/ + programs/ +
  说明文档,约 0.3 MB)。
- testbench 从 sources_1 移到仿真 fileset(sim_1 / sim_2):不再需要
  USED_IN_SYNTHESIS=0 这类设置,也彻底避免“testbench 被当成顶层”的
  [Place 30-494] The design is empty 问题。
- 两个 CPU 测试平台增加 srcs/rom/ 探测路径(含 GUI 仿真目录向上 5 层的相对路径),
  别人拿到工程后不用额外配置就能直接 Run Simulation。
- .gitignore:放行 pipelined-cpu/create_project.tcl(其余工程生成物仍不入库,
  这样重建脚本不会因为工程目录被忽略而丢失)。
- 说明文档新增“工程分享(Tcl 一键重建)”一节:目录结构、脚本用法、分享前应删除的
  生成目录、仓库源码与工程副本的同步命令,以及用 write_project_tcl 自动生成版本时
  需要改的两行(origin_dir / create_project 目标)。
- 验证:muldiv_test 13/13、isa_test 24/24、lui_test 4/4、mmio_test 5/5 以及两个
  流水基线全部通过(与改动前一致)。
2026-09-13 18:59:25 +08:00

269 lines
11 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 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。
// 示例:
// iverilog -g2012 -o cpu21_sim rtl/*.v testbench/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 下运行),因此本测试平台会依次探测若干候选
// 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
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
// 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
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/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/rom/", ROM_FILE}, rom_loaded);
// 分享出去的 Vivado 工程里的镜像目录(工程目录/srcs/rom/),GUI 仿真的工作
// 目录是 <proj>.sim/sim_1/behav/xsim,所以需要向上 5 层。
if (!rom_loaded) load_rom_image({"srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../srcs/rom/", 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