diff --git a/.gitignore b/.gitignore index a4a0d37..8330fd0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ _vivado/ *.vcd *.pb *.hex.lst +.Xil/ diff --git a/cpu21_board_rom.vh b/board/cpu21_board_rom.vh similarity index 100% rename from cpu21_board_rom.vh rename to board/cpu21_board_rom.vh diff --git a/cpu21_board_rom_fast.vh b/board/cpu21_board_rom_fast.vh similarity index 100% rename from cpu21_board_rom_fast.vh rename to board/cpu21_board_rom_fast.vh diff --git a/cpu21_board_top.v b/board/cpu21_board_top.v similarity index 83% rename from cpu21_board_top.v rename to board/cpu21_board_top.v index c7f69a3..7c5dfbf 100644 --- a/cpu21_board_top.v +++ b/board/cpu21_board_top.v @@ -25,33 +25,33 @@ // 注意:ROM_WORDS / RAM_WORDS 需为 2 的幂(默认 1024,对应地址位 [11:2])。 // ----------------------------------------------------------------------------- module cpu21_board_top #( - parameter [31:0] LED_ADDR = 32'h0000_1000, - parameter ROM_WORDS = 1024, - parameter RAM_WORDS = 1024, - parameter [31:0] USTATUS_INIT = 32'h0000_0001, + parameter [31:0] LED_ADDR = 32'h0000_1000, + parameter ROM_WORDS = 1024, + parameter RAM_WORDS = 1024, + parameter [31:0] USTATUS_INIT = 32'h0000_0001, // 板载 100 MHz 分频给 CPU。ALU 里的除法/取余是组合电路(实测约 94 ns), // 100 MHz 下无法收敛;默认 16 分频(6.25 MHz,周期 160 ns)留出足够余量。 - parameter integer CLK_DIV = 16 + parameter integer CLK_DIV = 16 ) ( - input wire clk100, // E3 - input wire rst_n, // C12:CPU_RESETN,低有效 - input wire go, // N17:BTNC - input wire [ 2:0] irq_sw, // SW[2:0] - output wire [15:0] led, // LED[15:0] - output wire led_we // LED16_B:MMIO 写入指示 + input wire clk100, // E3 + input wire rst_n, // C12:CPU_RESETN,低有效 + input wire go, // N17:BTNC + input wire [ 2:0] irq_sw, // SW[2:0] + output wire [15:0] led, // LED[15:0] + output wire led_we // LED16_B:MMIO 写入指示 ); localparam ROM_AW = $clog2(ROM_WORDS); localparam RAM_AW = $clog2(RAM_WORDS); - wire arst = ~rst_n; // 异步复位(高有效) + wire arst = ~rst_n; // 异步复位(高有效) // --------------------------------------------------------------------------- // 时钟分频:100 MHz / CLK_DIV,经 BUFG 走全局时钟网络 // (XDC 中用 create_generated_clock 声明该分频时钟,否则时序分析会按 // 100 MHz 的 10 ns 要求去约束 CPU 内部路径而报大量违例) // --------------------------------------------------------------------------- - reg [15:0] clk_cnt; - reg clk_div; + reg [15:0] clk_cnt; + reg clk_div; always @(posedge clk100 or posedge arst) begin if (arst) begin @@ -79,22 +79,22 @@ module cpu21_board_top #( else rst_sync_q <= {rst_sync_q[1:0], 1'b0}; end - wire reset = rst_sync_q[2]; + wire reset = rst_sync_q[2]; // CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到) - wire [31:0] instr_addr; - wire [31:0] data_addr; - wire [31:0] data_wdata; - wire [ 3:0] data_wstrb; - wire data_we; - wire [31:0] led_data; - wire led_valid; + wire [31:0] instr_addr; + wire [31:0] data_addr; + wire [31:0] data_wdata; + wire [ 3:0] data_wstrb; + wire data_we; + wire [31:0] led_data; + wire led_valid; // --------------------------------------------------------------------------- // 指令 ROM:组合读 // --------------------------------------------------------------------------- - reg [31:0] rom[0:ROM_WORDS-1]; - integer i; + reg [31:0] rom [0:ROM_WORDS-1]; + integer i; initial begin for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充 @@ -152,8 +152,8 @@ module cpu21_board_top #( .data_wstrb_o(data_wstrb), .data_we_o (data_we), - .led_data_o (led_data), - .led_valid_o (led_valid) + .led_data_o (led_data), + .led_valid_o(led_valid) ); // --------------------------------------------------------------------------- @@ -165,7 +165,8 @@ module cpu21_board_top #( always @(posedge clk_cpu or posedge arst) begin if (arst) led_we_cnt <= 24'd0; - else if (led_valid) led_we_cnt <= 24'hFF_FFFF; // 约 2.7 s @6.25 MHz(分频后的 CPU 时钟) + else if (led_valid) + led_we_cnt <= 24'hFF_FFFF; // 约 2.7 s @6.25 MHz(分频后的 CPU 时钟) else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1; end diff --git a/cpu21_board_top.xdc b/board/cpu21_board_top.xdc similarity index 100% rename from cpu21_board_top.xdc rename to board/cpu21_board_top.xdc diff --git a/cpu21_riscv_redirect_int_bpb说明.md b/cpu21_riscv_redirect_int_bpb说明.md index 077a3a7..32bbace 100644 --- a/cpu21_riscv_redirect_int_bpb说明.md +++ b/cpu21_riscv_redirect_int_bpb说明.md @@ -1,11 +1,36 @@ # `cpu21-riscv-4.circ` 的 Verilog 实现 -顶层文件:`cpu21_riscv_redirect_int_bpb.v` +处理器顶层文件:`rtl/cpu21_riscv_redirect_int_bpb.v` 该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为 `cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为 独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。 +## 目录结构 + +```text +risc-v/ +├─ rtl/ 处理器 RTL(13 个文件,cpu21_riscv_redirect_int_bpb.v 为顶层) +├─ board/ 上板(Nexys4DDR):板级顶层、引脚约束、片内 ROM 初始化 +│ ├─ cpu21_board_top.v 板级顶层(时钟分频 + 片内 ROM/RAM + 引脚) +│ ├─ cpu21_board_top.xdc 引脚约束与分频时钟约束 +│ ├─ cpu21_board_rom.vh 片内 ROM 内容(由 tools/hex2vh.py 生成,上板用) +│ └─ cpu21_board_rom_fast.vh 短延时的 ROM 内容(仅仿真,-d BOARD_ROM_FAST 选择) +├─ testbench/ 仿真 +│ ├─ tb_cpu21_riscv_redirect_int_bpb.v 中断/分支预测测试平台 +│ ├─ tb_no_intr.v 无中断测试平台(指令集测试用) +│ ├─ tb_board_top.v 板级顶层仿真 +│ ├─ rom/ 仿真实际加载的 ROM 镜像(.hex) +│ └─ programs/ 汇编源程序、期望值、课程原始资产(RARS) +├─ tools/ rv_asm.py(迷你汇编器)、hex2vh.py(hex → ROM 初始化) +├─ pipelined-cpu/ Vivado 工程(本地目录,不入库) +└─ cpu21_riscv_redirect_int_bpb说明.md 本文档 +``` + +约定:`rtl/` 只放可综合的处理器源码;板级/仿真相关的东西都在 `board/`、 +`testbench/` 下;`testbench/rom/` 里的镜像是测试平台默认加载的那个版本 +(已按 MMIO 写 LED 修改),`testbench/programs/` 保留课程原始程序与 RARS 输出。 + ## 已转换的功能 - 五级流水:IF、ID、EX、MEM、WB。 @@ -27,30 +52,30 @@ 表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。 因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。 -| 指令类别 | opcode | 说明 | -| ----------------------------------------------- | -----: | ---------------------------------------------------------------------------- | -| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | -| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) | -| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | -| `lui`(扩展) | `0x0d` | 加载高位立即数,`rd = imm[31:12] << 12` | -| `lw` | `0x00` | `funct3=010` | -| `sw` / `sb` | `0x08` | `funct3=010` / `000` | -| `beq` / `bne` / `blt` / `bge` / `bltu` / `bgeu` | `0x18` | `funct3=000` / `001` / `100` / `101` / `110` / `111` | -| `jal` | `0x1b` | J 型立即数 | -| `jalr` | `0x19` | `funct3=000` | -| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI | +| 指令类别 | opcode | 说明 | +| ----------------------------------------------------------- | -------: | -------------------------------------------------------------------------------- | +| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra | +| M 扩展(R 型) | `0x0c` | `funct7=0000001`:mul/mulh/mulhsu/mulhu(另有课程约定的 remu,`funct3=111`) | +| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/sltiu/slli/srli/srai | +| `lui`(扩展) | `0x0d` | 加载高位立即数,`rd = imm[31:12] << 12` | +| `lw` | `0x00` | `funct3=010` | +| `sw` / `sb` | `0x08` | `funct3=010` / `000` | +| `beq` / `bne` / `blt` / `bge` / `bltu` / `bgeu` | `0x18` | `funct3=000` / `001` / `100` / `101` / `110` / `111` | +| `jal` | `0x1b` | J 型立即数 | +| `jalr` | `0x19` | `funct3=000` | +| 系统类 | `0x1c` | `ecall`、`URET`、CSRRSI、CSRRCI | `ecall` 和 `URET` 都是 `opcode=0x1c、funct3=0`,由 `IR[21]` 区分: `IR[21]=0` 是 `ecall`,`IR[21]=1` 是 `URET`。这一点来自原控制器中的 `IR21` 分支逻辑。 ## MMIO 与外设 -外设寄存器映射在数据存储器地址空间,由 `cpu21_mmio.v` 在 MEM 级译码, +外设寄存器映射在数据存储器地址空间,由 `rtl/cpu21_mmio.v` 在 MEM 级译码, 顶层参数 `LED_ADDR`(默认 `0x0000_1000`)指定 LED 数据寄存器的地址。 该地址位于 4 KB 数据 RAM(`0x0000`–`0x0FFC`)之外,因此不会与 RAM 冲突。 -| 地址 | 外设 | 写 | 读 | -| ------------- | -------------- | ---------------------------------------------------------------------------- | -------------------------- | +| 地址 | 外设 | 写 | 读 | +| --------------- | -------------- | ------------------------------------------------------------------------------------ | ---------------------------- | | `0x0000_1000` | LED 数据寄存器 | `sw` 写全字;`sb` 按 `data_wstrb_o` 只改对应字节通道,`led_valid_o` 拉高一拍 | `lw` 返回 LED 寄存器当前值 | 地址译码按**字**对齐:同一 32 位寄存器内的任意字节偏移(如 `0x1001`)都算命中, @@ -69,7 +94,7 @@ `testbench/programs/mmio_test.asm`(期望值见 `mmio_test.expected`)覆盖了 全字写、读回、字节写三种情况。加入新外设(拨码开关、UART 等)时只需在 -`cpu21_mmio.v` 里扩展地址译码,处理器流水线无需改动。 +`rtl/cpu21_mmio.v` 里扩展地址译码,处理器流水线无需改动。 ## 顶层端口约定 @@ -95,24 +120,28 @@ ## 文件与模块划分 -| 文件 | 模块 | 职责 | -| -------------------------------- | ------------------------------ | --------------------------------------------------------------- | -| `cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | -| `cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) | -| `cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | -| `cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码 `IR[6:2]`) | -| `cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | -| `cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | -| `cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | -| `cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | -| `cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | -| `cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | -| `cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | -| `cpu21_mmio.v` | `cpu21_mmio` | MMIO 外设:LED 数据寄存器(可写、可读回,支持字节写) | -| `cpu21_board_top.v` | `cpu21_board_top` | 上板顶层:时钟分频、复位同步、片内 ROM/RAM、引脚外设连接 | -| `testbench/tb_board_top.v` | `tb_board_top` | 上板顶层的 RTL 仿真(观察 LED 跑马灯与写入指示) | -| `tools/rv_asm.py` | — | 迷你汇编器:`.asm` → 机器码 hex(`.lst` 带反汇编清单) | -| `tools/hex2vh.py` | — | hex → `cpu21_board_rom.vh`(`rom[i] = 32'h…;` 初始化语句) | +| 文件 | 模块 | 职责 | +| ----------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------- | +| `rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | +| `rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(`result2` 用于 MUL 高位 / DIVU 余数) | +| `rtl/cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | +| `rtl/cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码`IR[6:2]`) | +| `rtl/cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | +| `rtl/cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | +| `rtl/cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | +| `rtl/cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | +| `rtl/cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | +| `rtl/cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | +| `rtl/cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | +| `rtl/cpu21_mmio.v` | `cpu21_mmio` | MMIO 外设:LED 数据寄存器(可写、可读回,支持字节写) | +| `board/cpu21_board_top.v` | `cpu21_board_top` | 板级顶层:时钟分频、复位同步、片内 ROM/RAM、引脚外设连接 | +| `board/cpu21_board_top.xdc` | — | Nexys4DDR 引脚约束 + 分频时钟(`create_generated_clock`) | +| `board/cpu21_board_rom.vh` | — | 片内 ROM 内容(生成文件,`rom[i] = 32'h…;`) | +| `testbench/tb_board_top.v` | `tb_board_top` | 板级顶层的 RTL 仿真(观察 LED 跑马灯与写入指示) | +| `testbench/tb_no_intr.v` | `tb_no_intr` | 无中断测试平台(指令集/MMIO 测试,默认加载 benchmark 镜像) | +| `testbench/tb_cpu21_riscv_redirect_int_bpb.v` | `tb_cpu21_riscv_redirect_int_bpb` | 中断与分支预测测试平台(默认加载中断测试镜像) | +| `tools/rv_asm.py` | — | 迷你汇编器:`.asm` → 机器码 hex(`.lst` 带反汇编清单) | +| `tools/hex2vh.py` | — | hex →`board/cpu21_board_rom.vh`(`rom[i] = 32'h…;` 初始化语句) | 拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序 在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支 @@ -128,14 +157,14 @@ BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会 ### 引脚分配(`cpu21_board_top.xdc`) -| 端口 | 引脚 | 板上元件 | 说明 | -| ------------- | --------- | ------------------------- | ----------------------------------------------- | -| `clk100` | E3 | 100 MHz 晶振 | 经 `CLK_DIV` 分频后给 CPU | -| `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 | -| `go` | N17 | 按键 BTNC | 对应原电路“继续运行”(`ecall` 停机后恢复执行) | -| `irq_sw[2:0]` | J15/L16/M13 | 拨码开关 SW0–SW2 | 三路中断源,拨动即触发(内部同步并锁存) | -| `led[15:0]` | H17…V11 | LED0–LED15 | MMIO(`LED_ADDR`)写入值的低 16 位 | -| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 2.7 s | +| 端口 | 引脚 | 板上元件 | 说明 | +| --------------- | ----------- | ------------------------- | -------------------------------------------------- | +| `clk100` | E3 | 100 MHz 晶振 | 经`CLK_DIV` 分频后给 CPU | +| `rst_n` | C12 | 按键 CPU_RESETN(低有效) | 异步置位、与 CPU 时钟同步释放 | +| `go` | N17 | 按键 BTNC | 对应原电路“继续运行”(`ecall` 停机后恢复执行) | +| `irq_sw[2:0]` | J15/L16/M13 | 拨码开关 SW0–SW2 | 三路中断源,拨动即触发(内部同步并锁存) | +| `led[15:0]` | H17…V11 | LED0–LED15 | MMIO(`LED_ADDR`)写入值的低 16 位 | +| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 2.7 s | ### 时钟:为什么要把 100 MHz 分频 @@ -164,7 +193,8 @@ create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \ ### 片内 ROM / RAM - ROM:`ROM_WORDS = 1024` 字(4 KB),组合读,取指地址 `instr_addr[11:2]`; - 内容由 `` `include "cpu21_board_rom.vh" `` 给出(文件里是 1024 条 + 内容由 `` `include "cpu21_board_rom.vh" `` 给出(与 `cpu21_board_top.v` 同目录, + 文件里是 1024 条 `rom[i] = 32'h…;`,放在 `initial` 里,等价于 `$readmemh`,且不依赖文件路径)。 - RAM:`RAM_WORDS = 1024` 字(4 KB),字节写使能 + 组合读,只响应 `data_addr < 4 KB`,因此 MMIO 地址(默认 `0x1000`)不会落进 RAM。 @@ -176,10 +206,10 @@ create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \ ```powershell # 1) 汇编 + 生成 ROM 初始化文件(在仓库根目录执行) python tools/rv_asm.py testbench/programs/board_demo.asm _tmp/board_demo.hex -python tools/hex2vh.py _tmp/board_demo.hex cpu21_board_rom.vh 1024 +python tools/hex2vh.py _tmp/board_demo.hex board/cpu21_board_rom.vh 1024 # 2) 上板顶层的 RTL 仿真(默认程序延时约 20 ms,只需确认第一次写入 LED=0001) -xvlog testbench/tb_board_top.v +xvlog rtl/*.v board/cpu21_board_top.v testbench/tb_board_top.v xelab -top tb_board_top -snapshot board_sim xsim board_sim -tclbatch run_quit.tcl ``` @@ -188,19 +218,20 @@ xsim board_sim -tclbatch run_quit.tcl 每步之间用软件循环延时约 20 ms(6.25 MHz 下)。仓库里同时提供了两份 ROM 初始化 文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏): -| 文件 | 用途 | 每步耗时 | -| ------------------------ | ------------------------------------- | -------- | -| `cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms | -| `cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 4.8 µs | +| 文件 | 用途 | 每步耗时 | +| --------------------------------- | ------------------------------ | -------- | +| `board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms | +| `board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 4.8 µs | ```powershell -# 快速版仿真:生成/重新生成 CPU 时加上 -d BOARD_ROM_FAST -xvlog -d BOARD_ROM_FAST testbench/tb_board_top.v +# 快速版仿真:编译时加上 -d BOARD_ROM_FAST(注意 xvlog/elaborate 都要带) +xvlog -d BOARD_ROM_FAST rtl/*.v board/cpu21_board_top.v testbench/tb_board_top.v +xelab -top tb_board_top -snapshot board_sim_fast -d BOARD_ROM_FAST ``` 两份文件都由同样的两步生成,只有延时循环的初值不同(把 `board_demo.asm` 里的 `lui t2, 0xf` 换成 `addi t2, zero, 8` 再照上面两步走一遍,输出到 -`cpu21_board_rom_fast.vh`)。 +`board/cpu21_board_rom_fast.vh`)。 ### Vivado 工程设置与实现结果 @@ -208,22 +239,22 @@ Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要 1. `sources_1` 顶层设为 `cpu21_board_top`(纯 RTL 仿真时也可切回 `cpu21_riscv_redirect_int_bpb`); -2. 加入 `cpu21_board_top.v`、`cpu21_board_top.xdc`,`cpu21_board_rom.vh` 的文件类型设成 - `Verilog Header`(不能当普通源码,否则会被当成顶层候选); -3. 三个 testbench(`tb_cpu21_riscv_redirect_int_bpb.v`、`tb_no_intr.v`、`tb_board_top.v`)的 - `USED_IN_SYNTHESIS` / `USED_IN_IMPLEMENTATION` 设为 `0`,否则综合可能把 - testbench 当顶层,报 `[Place 30-494] The design is empty`。 +2. 加入 `board/cpu21_board_top.v`、`board/cpu21_board_top.xdc`, + `board/cpu21_board_rom.vh` 的文件类型设成 `Verilog Header`(不能当普通源码, + 否则会被当成顶层候选); +3. 三个 testbench(`testbench/tb_cpu21_riscv_redirect_int_bpb.v`、`tb_no_intr.v`、 + `tb_board_top.v`)的 `USED_IN_SYNTHESIS` / `USED_IN_IMPLEMENTATION` 设为 `0`, + 否则综合可能把 testbench 当顶层,报 `[Place 30-494] The design is empty`。 `xc7a100tcsg324-1` 上的实现结果(`CLK_DIV=16`): -| 项目 | 结果 | -| -------------- | ------------------------------------------------------------- | -| 综合 | `synth_design Complete!` | -| 实现 | `route_design Complete!`,`WNS = +6.083 ns`(无 Failed Timing) | -| 资源 | LUT 5400 (8.52%)、LUTRAM 512、FF 2507 (1.98%)、DSP 12 (5%)、IOB 23 (10.95%) | -| bit 文件 | `pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit` | +| 项目 | 结果 | +| -------- | --------------------------------------------------------------------------- | +| 综合 | `synth_design Complete!` | +| 实现 | `route_design Complete!`,`WNS = +6.083 ns`(无 Failed Timing) | +| 资源 | LUT 5400 (8.52%)、LUTRAM 512、FF 2507 (1.98%)、DSP 12 (5%)、IOB 23 (10.95%) | +| bit 文件 | `pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit` | 上板操作:用 Vivado 打开工程 → `Open Hardware Manager` → `Program Device`,选上述 `.bit`。复位后跑马灯立即从 `0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用 按 `go`;`go` 只对执行了 `ecall` 停机、等待继续运行的程序有意义),LED16_B 每次写入时点亮。 - diff --git a/cpu21_bpb_8.v b/rtl/cpu21_bpb_8.v similarity index 100% rename from cpu21_bpb_8.v rename to rtl/cpu21_bpb_8.v diff --git a/cpu21_mmio.v b/rtl/cpu21_mmio.v similarity index 100% rename from cpu21_mmio.v rename to rtl/cpu21_mmio.v diff --git a/cpu21_riscv_alu.v b/rtl/cpu21_riscv_alu.v similarity index 100% rename from cpu21_riscv_alu.v rename to rtl/cpu21_riscv_alu.v diff --git a/cpu21_riscv_branch_unit.v b/rtl/cpu21_riscv_branch_unit.v similarity index 100% rename from cpu21_riscv_branch_unit.v rename to rtl/cpu21_riscv_branch_unit.v diff --git a/cpu21_riscv_decoder.v b/rtl/cpu21_riscv_decoder.v similarity index 100% rename from cpu21_riscv_decoder.v rename to rtl/cpu21_riscv_decoder.v diff --git a/cpu21_riscv_forward_unit.v b/rtl/cpu21_riscv_forward_unit.v similarity index 100% rename from cpu21_riscv_forward_unit.v rename to rtl/cpu21_riscv_forward_unit.v diff --git a/cpu21_riscv_hazard_unit.v b/rtl/cpu21_riscv_hazard_unit.v similarity index 100% rename from cpu21_riscv_hazard_unit.v rename to rtl/cpu21_riscv_hazard_unit.v diff --git a/cpu21_riscv_irq_ctrl.v b/rtl/cpu21_riscv_irq_ctrl.v similarity index 100% rename from cpu21_riscv_irq_ctrl.v rename to rtl/cpu21_riscv_irq_ctrl.v diff --git a/cpu21_riscv_perf_counters.v b/rtl/cpu21_riscv_perf_counters.v similarity index 100% rename from cpu21_riscv_perf_counters.v rename to rtl/cpu21_riscv_perf_counters.v diff --git a/cpu21_riscv_redirect_int_bpb.v b/rtl/cpu21_riscv_redirect_int_bpb.v similarity index 100% rename from cpu21_riscv_redirect_int_bpb.v rename to rtl/cpu21_riscv_redirect_int_bpb.v diff --git a/cpu21_riscv_regfile.v b/rtl/cpu21_riscv_regfile.v similarity index 100% rename from cpu21_riscv_regfile.v rename to rtl/cpu21_riscv_regfile.v diff --git a/cpu21_riscv_store_unit.v b/rtl/cpu21_riscv_store_unit.v similarity index 100% rename from cpu21_riscv_store_unit.v rename to rtl/cpu21_riscv_store_unit.v diff --git a/testbench/board_demo.hex b/testbench/rom/board_demo.hex similarity index 100% rename from testbench/board_demo.hex rename to testbench/rom/board_demo.hex diff --git a/testbench/cpu21_riscv_redirect_int_bpb_rom.hex b/testbench/rom/cpu21_riscv_redirect_int_bpb_rom.hex similarity index 100% rename from testbench/cpu21_riscv_redirect_int_bpb_rom.hex rename to testbench/rom/cpu21_riscv_redirect_int_bpb_rom.hex diff --git a/testbench/isa_test.hex b/testbench/rom/isa_test.hex similarity index 100% rename from testbench/isa_test.hex rename to testbench/rom/isa_test.hex diff --git a/testbench/lui_test.hex b/testbench/rom/lui_test.hex similarity index 100% rename from testbench/lui_test.hex rename to testbench/rom/lui_test.hex diff --git a/testbench/mmio_test.hex b/testbench/rom/mmio_test.hex similarity index 100% rename from testbench/mmio_test.hex rename to testbench/rom/mmio_test.hex diff --git a/testbench/risc-v-benchmark_ccab.hex b/testbench/rom/risc-v-benchmark_ccab.hex similarity index 100% rename from testbench/risc-v-benchmark_ccab.hex rename to testbench/rom/risc-v-benchmark_ccab.hex diff --git a/testbench/risc-v-branch-predict.hex b/testbench/rom/risc-v-branch-predict.hex similarity index 100% rename from testbench/risc-v-branch-predict.hex rename to testbench/rom/risc-v-branch-predict.hex diff --git a/testbench/tb_board_top.v b/testbench/tb_board_top.v index 0c335de..8081204 100644 --- a/testbench/tb_board_top.v +++ b/testbench/tb_board_top.v @@ -9,7 +9,7 @@ // // 默认的 board_demo 程序带约 20 ms 软件延时,仿真只需确认第一次写入; // 若要完整验证跑马灯序列,可生成一份短延时的 ROM 并用 -d BOARD_ROM_FAST 编译: -// xvlog -d BOARD_ROM_FAST cpu21_board_top.v testbench/tb_board_top.v +// xvlog -d BOARD_ROM_FAST board/cpu21_board_top.v testbench/tb_board_top.v // ----------------------------------------------------------------------------- module tb_board_top; reg clk100 = 1'b0; diff --git a/testbench/tb_cpu21_riscv_redirect_int_bpb.v b/testbench/tb_cpu21_riscv_redirect_int_bpb.v index 978ab4b..98f290c 100644 --- a/testbench/tb_cpu21_riscv_redirect_int_bpb.v +++ b/testbench/tb_cpu21_riscv_redirect_int_bpb.v @@ -1,6 +1,6 @@ -// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 +// 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。 // 示例: -// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_cpu21_riscv_redirect_int_bpb.v +// iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_cpu21_riscv_redirect_int_bpb.v // vvp cpu21_sim // 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。 // @@ -8,9 +8,13 @@ // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // .sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 -// 路径,并报告实际命中的那个。显式给出的路径始终优先: +// 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际 +// 命中的那个。显式给出的路径始终优先: // vvp cpu21_sim +ROM_FILE= (iverilog/vvp) // xelab ... -generic_top "ROM_FILE=" (Vivado xsim) +// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报 +// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可; +// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。 module tb_cpu21_riscv_redirect_int_bpb #( parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex" // risc-v-benchmark_ccab.hex @@ -227,10 +231,10 @@ module tb_cpu21_riscv_redirect_int_bpb #( 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) 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); if (!rom_loaded) $display( "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", diff --git a/testbench/tb_no_intr.v b/testbench/tb_no_intr.v index ed92a84..e7f4229 100644 --- a/testbench/tb_no_intr.v +++ b/testbench/tb_no_intr.v @@ -1,9 +1,9 @@ // 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架, // 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。 // -// 与 RTL 文件(cpu21_riscv_*.v、cpu21_bpb_8.v)一起编译。 +// 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。 // 示例: -// iverilog -g2012 -o cpu21_sim cpu21_*.v tb_no_intr.v +// iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_no_intr.v // vvp cpu21_sim // 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。 // @@ -11,9 +11,13 @@ // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // .sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 -// 路径,并报告实际命中的那个。显式给出的路径始终优先: +// 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际 +// 命中的那个。显式给出的路径始终优先: // vvp cpu21_sim +ROM_FILE= (iverilog/vvp) // xelab ... -generic_top "ROM_FILE=" (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 @@ -230,10 +234,10 @@ module tb_no_intr #( 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) 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); if (!rom_loaded) $display( "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", diff --git a/tools/hex2vh.py b/tools/hex2vh.py index a894399..af4c263 100644 --- a/tools/hex2vh.py +++ b/tools/hex2vh.py @@ -8,7 +8,7 @@ rom[0] = 32'h00001037; rom[1] = 32'h00100093; -配套 RTL 用法(见 cpu21_board_top.v): +配套 RTL 用法(见 board/cpu21_board_top.v,两者同目录,所以 include 能直接解析): initial begin for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充 @@ -18,6 +18,7 @@ 这样 ROM 内容直接跟着 RTL 走,不依赖综合时的当前工作目录, 避免了 $readmemh 相对路径在 Vivado 综合流程里找不到文件(且只报 warning)的坑。 """ + import sys NOP = 0x00000013 @@ -36,11 +37,16 @@ def main(): words.append(int(line, 16)) if len(words) > words_total: - raise SystemExit("程序 %d 个字,超过 ROM 容量 %d 个字" % (len(words), words_total)) + raise SystemExit( + "程序 %d 个字,超过 ROM 容量 %d 个字" % (len(words), words_total) + ) with open(dst, "w", encoding="utf-8") as f: f.write(" // generated by tools/hex2vh.py - do not edit by hand\n") - f.write(" // source: %s (%d words, remaining entries filled with NOP)\n" % (src, len(words))) + f.write( + " // source: %s (%d words, remaining entries filled with NOP)\n" + % (src, len(words)) + ) for addr in range(words_total): w = words[addr] if addr < len(words) else NOP f.write(" rom[%d] = 32'h%08X;\n" % (addr, w))