# `cpu21-riscv-4.circ` 的 Verilog 实现 处理器顶层文件:`pipelined-cpu/srcs/rtl/cpu21_riscv_redirect_int_bpb.v` 该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为 `cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为 独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。 ## 目录结构 ```text risc-v/ ├─ pipelined-cpu/ Vivado 工程目录(也是源文件所在地) │ ├─ create_project.tcl 一键重建工程的 Tcl 脚本 │ ├─ srcs/rtl/*.v 处理器 RTL(13 个文件,cpu21_riscv_redirect_int_bpb.v 为顶层) │ ├─ srcs/board/*.v|*.vh 板级顶层(时钟分频 + 片内 ROM/RAM + 引脚)、片内 ROM 初始化 │ ├─ srcs/constrs/*.xdc 引脚与分频时钟约束(+ Nexys4DDR 母版) │ ├─ srcs/tb/*.v 三个测试平台 │ ├─ srcs/rom/*.hex 测试平台加载的 ROM 镜像 │ ├─ tools/ rv_asm.py(迷你汇编器)、hex2vh.py(hex → ROM 初始化)、run_regress.ps1(一键回归) │ ├─ programs/ 汇编源程序、期望值、课程原始资产(RARS) │ └─ pipelined-cpu.xpr(+*.runs/…) 本机工程与生成物(不入库,可删) ├─ cpu21_riscv_redirect_int_bpb说明.md 本文档 └─ _tmp/、_vivado/ 本地临时目录(不入库) ``` 约定:git 只跟踪 `pipelined-cpu/` 下的源文件(`srcs/`、`tools/`、`programs/`、 `create_project.tcl`),Vivado 生成物(`*.xpr`、`*.runs`、`*.sim`、`*.cache`…)由 `.gitignore` 排除。`srcs/rtl/` 只放可综合的处理器源码;板级顶层与约束在 `srcs/board/`、 `srcs/constrs/`;测试平台与 ROM 镜像在 `srcs/tb/`、`srcs/rom/`(镜像是按 MMIO 写 LED 修改过的版本,课程原始程序与 RARS 输出保留在 `programs/`)。 ## 已转换的功能 - 五级流水:IF、ID、EX、MEM、WB。 - EX/MEM 与 MEM/WB 前递,以及 load-use 一拍停顿。 - 条件分支 `beq`、`bne`、`blt`/`bge`(有符号)、`bltu`/`bgeu`(无符号)在 EX 段判定,错误预测时清空 IF/ID、ID/EX 并重定向 PC。真值表中 BLT 的 `ALU_OP` 为 SLT,与实现一致。 - `jal`、`jalr` 在 EX 段重定向,返回地址写回 `rd`。 - 8 项全相联 BPB:标签 `PC[11:2]`,每项包含 valid、目标 PC、2 位饱和计数器和 3 位年龄字段。BPB 只由条件分支更新,避免 `jal`、`jalr` 占用不会被查询的表项;未命中时优先使用无效项,否则替换年龄最大的项。 - 三路中断输入 `irq_i[2:0]`:两级同步、上升沿挂起、IRQ3 > IRQ2 > IRQ1 优先级,仅在 `ustatus[0]`(MIE)允许时响应。 - 支持嵌套中断:保存 EPC、`ustatus` 和当前优先级;`URET` 使用当前 `uepc` 返回,并从硬件栈恢复外层上下文。三个入口按照原电路的 `INTpc` 选择:IRQ1=`0x30ac`、IRQ2=`0x31e4`、IRQ3=`0x3310`。`0x22` 是 `ecall` 的服务号 34,不是中断向量。 - CSRRSI、CSRRCI,以及 CSRRW:支持 CSR 地址 `0x004`(`ustatus`)和 `0x041`(`uepc`)。立即数为 `IR[19:15]` 的零扩展值,并按 RISC-V 规则把旧 CSR 值写回 `rd`;这覆盖任务书中的 `csrrsi s6,0x41,0` 和 `csrrw zero,0x41,s6` 用法。 - `ecall`:停机和调试用。`a7 == 34` 是历史遗留的“显示”调用,现已保留为无操作;其余取值(例如程序结尾惯用的 `a7 = 10`)置 `halted_o`,直到 `go_i` 为高。LED 显示改由 MMIO 存储完成(见下文“MMIO 与外设”)。 - `sw` 和 `sb`:输出 `data_wstrb_o`,支持按字节写入。 - `lui`:加载高位立即数,`rd = imm[31:12] << 12`(按需求补充的扩展指令,标准 opcode `0x37`)。 - `sltiu`:I 型 `funct3=011`,立即数符号扩展后按无符号与 rs1 比较。 - M 扩展乘法(`funct7=0000001`):`mul`(乘积低 32 位)、`mulh`(有符号×有符号高 32 位)、`mulhsu`(有符号×无符号高 32 位)、`mulhu`(无符号×无符号高 32 位)。 - M 扩展除法:课程真值表里的 `divu`(`funct3=101`,无符号商)与 `remu`(`funct3=111`,无符号余数)。 以上六条乘法/除法指令都由 **多周期单元** `pipelined-cpu/srcs/rtl/cpu21_riscv_muldiv.v` 完成(见“时序和使用说明”第 7 条), 而不是组合逻辑。 ## 指令编码 表中的 `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、divu(`funct3=101`)、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 与外设 外设寄存器映射在数据存储器地址空间,由 `pipelined-cpu/srcs/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`)都算命中, 具体修改哪几个字节由 `data_wstrb_o` 决定。`data_addr_o` 在命中 MMIO 时不会 访问外部 RAM;读数据由 `cpu21_mmio` 的输出在顶层与 `data_rdata_i` 做选择。 典型用法(LED 写 `0x12345678`,可用 `pipelined-cpu/tools/rv_asm.py` 汇编): ```asm lui t0, 0x1 # t0 = LED_ADDR = 0x00001000 lui t1, 0x12345 addi t1, t1, 0x678 # t1 = 0x12345678 sw t1, 0(t0) # MMIO 写 LED -> LED 输出 12345678 lw a0, 0(t0) # MMIO 读回 ``` `pipelined-cpu/programs/mmio_test.asm`(期望值见 `mmio_test.expected`)覆盖了 全字写、读回、字节写三种情况。加入新外设(拨码开关、UART 等)时只需在 `pipelined-cpu/srcs/rtl/cpu21_mmio.v` 里扩展地址译码,处理器流水线无需改动。 ## 顶层端口约定 - `instr_i`:当前 `instr_addr_o` 对应的 32 位指令。原 Logisim 中由 ROM 提供。 - `data_rdata_i`:当前 `data_addr_o` 的读数据。原 Logisim 中由 MIPS RAM 提供。 - `data_addr_o`、`data_wdata_o`、`data_wstrb_o`、`data_we_o`:数据存储器接口。地址按字节地址输出;若使用原 32 位 RAM,可用 `data_addr_o[11:2]` 作为字地址。 - `irq_i[0]`、`irq_i[1]`、`irq_i[2]`:1、2、3 号中断源;输入应为脉冲或边沿信号,模块内部会同步并锁存挂起状态。 - `go_i`:对应原电路的“继续运行”按键。 - `if_pc_o`、`id_pc_o`、`ex_pc_o`、`mem_pc_o`、`wb_pc_o`:五级流水当前保存的 PC,气泡时输出 0,便于替代原电路的调试探针。 - `rdin_o`、`mdin_o`、`reg_write_o`、`mem_write_o`:对应原电路的寄存器写回和存储器写调试输出。 - `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`:对应原电路和课程要求的周期、停顿/气泡、分支和动态预测统计量。 ## 时序和使用说明 1. `reset` 为高时异步清空流水线、寄存器文件、BPB、挂起中断和中断栈,PC 置为 `RESET_PC`;`ustatus` 复位为参数 `USTATUS_INIT`,默认 `0x0000_0001`(MIE 上电即使能)。这样做的原因是课程的中断测试程序只在中断处理程序的“保护现场”之后写 MIE,主流程不会开中断;若不默认使能,第一次中断只会挂在 `irq_pending_o` 上而永不响应。参数可覆盖,例如把 `USTATUS_INIT` 设为 `32'h0` 即可恢复“上电关中断”的行为。 2. 指令存储器按组合读处理;数据存储器读数据在一个 MEM 周期内返回。若 RAM 为同步读,需要在外部增加一级数据寄存或相应调整 MEM/WB。 3. 响应中断时允许当前 EX 指令完成,并保存下一条尚未执行指令的 PC;若 EX 是已正确预测的跳转分支,则保存实际目标地址。EX 为空时优先保存 IF/ID 中尚未执行的 PC。这样 `URET` 不会重复执行已经提交的存储或寄存器写操作。 4. 默认 BPB 新表项采用弱不跳转/弱跳转状态:首次更新为不跳转时计数器为 `01`,首次更新为跳转时为 `10`;命中后按 2 位饱和规则增减。 5. `bpb_predict_hit_o`、`bpb_predict_taken_o`、`bpb_mispredict_o` 和 `irq_*_o` 是观测端口,不参与外部控制,可以直接接逻辑分析仪或测试平台。 6. `cycle_count_o` 在 `halted_o` 为高时暂停;load-use 每次计入一个气泡,重定向或中断清空 IF/ID 与当前 IF 时计入两个气泡。 7. 乘除法是多周期指令:结果算完之前 `ex_md_stall` 有效,ID/EX 保持(指令继续占用 EX)、EX/MEM 插气泡、取指/译码冻结,同时屏蔽中断(避免结果未定型就写回)。一次乘/除共占用 34 拍(1 拍装载 + 32 拍迭代 + 1 拍定型),这些等待周期也计入 `stall_count_o` / `bubble_count_o`。乘除法迭代期间不会把错误值写进 rd,也不会因为中断被打断。 原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、MMIO(`led_data_o`/`led_valid_o`)和调试端口替代。 ## 文件与模块划分 | 文件 | 模块 | 职责 | | --------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------- | | `pipelined-cpu/srcs/rtl/cpu21_riscv_redirect_int_bpb.v` | `cpu21_riscv_redirect_int_bpb` | 顶层:五级流水寄存器、子模块例化、PC/IF-ID/ID-EX 控制、对外输出 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_alu.v` | `cpu21_riscv_alu` | 组合 ALU(加减/移位/逻辑/比较,不做乘除) | | `pipelined-cpu/srcs/rtl/cpu21_riscv_muldiv.v` | `cpu21_riscv_muldiv` | 多周期乘除法单元:32 拍移位迭代,mul/mulh/mulhsu/mulhu/divu/remu | | `pipelined-cpu/srcs/rtl/cpu21_bpb_8.v` | `cpu21_bpb_8` | 8 项全相联分支目标缓冲(2 位饱和计数器 + 近似 LRU) | | `pipelined-cpu/srcs/rtl/cpu21_riscv_decoder.v` | `cpu21_riscv_decoder` | ID 级组合译码器(操作码`IR[6:2]`) | | `pipelined-cpu/srcs/rtl/cpu21_riscv_regfile.v` | `cpu21_riscv_regfile` | 寄存器堆,`x0` 恒为 0,同周期 WB 写优先旁路 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_forward_unit.v` | `cpu21_riscv_forward_unit` | EX 级前递网络(EX/MEM 优先于 MEM/WB) | | `pipelined-cpu/srcs/rtl/cpu21_riscv_store_unit.v` | `cpu21_riscv_store_unit` | EX 级存储数据对齐:`sw`/`sb` 的写数据与字节写掩码 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_branch_unit.v` | `cpu21_riscv_branch_unit` | EX 级控制流裁决:分支方向/目标、JAL/JALR 重定向 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_hazard_unit.v` | `cpu21_riscv_hazard_unit` | load-use 冒险、停顿(stall)与冲刷(flush)判定 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_irq_ctrl.v` | `cpu21_riscv_irq_ctrl` | 中断采样与优先级、`ustatus`/`uepc` CSR、嵌套返回栈 | | `pipelined-cpu/srcs/rtl/cpu21_riscv_perf_counters.v` | `cpu21_riscv_perf_counters` | 周期/停顿/气泡/分支/预测成功率等统计计数器 | | `pipelined-cpu/srcs/rtl/cpu21_mmio.v` | `cpu21_mmio` | MMIO 外设:LED 数据寄存器(可写、可读回,支持字节写) | | `pipelined-cpu/srcs/board/cpu21_board_top.v` | `cpu21_board_top` | 板级顶层:时钟分频、复位同步、片内 ROM/RAM、引脚外设连接 | | `pipelined-cpu/srcs/constrs/cpu21_board_top.xdc` | — | Nexys4DDR 引脚约束 + 分频时钟(`create_generated_clock`) | | `pipelined-cpu/srcs/board/cpu21_board_rom.vh` | — | 片内 ROM 内容(生成文件,`rom[i] = 32'h…;`) | | `pipelined-cpu/srcs/tb/tb_board_top.v` | `tb_board_top` | 板级顶层的 RTL 仿真(观察 LED 跑马灯与写入指示) | | `pipelined-cpu/srcs/tb/tb_no_intr.v` | `tb_no_intr` | 无中断测试平台(指令集/MMIO 测试,默认加载 benchmark 镜像) | | `pipelined-cpu/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v` | `tb_cpu21_riscv_redirect_int_bpb` | 中断与分支预测测试平台(默认加载中断测试镜像) | | `pipelined-cpu/tools/rv_asm.py` | — | 迷你汇编器:`.asm` → 机器码 hex(`.lst` 带反汇编清单) | | `pipelined-cpu/tools/hex2vh.py` | — | hex →`pipelined-cpu/srcs/board/cpu21_board_rom.vh`(`rom[i] = 32'h…;` 初始化语句) | 拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序 在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支 跳转、预测成败)完全一致。 ## 上板(Nexys4DDR + Vivado) 处理器顶层 `cpu21_riscv_redirect_int_bpb` 有 775 位调试端口(各级 PC/IR、计数器、 BPB/中断观测值)。如果直接把它设为实现顶层,这些端口会全部变成引脚,导致 `[Place 30-415] IO Placement failed due to overutilization`(占用 775 个 IO, 而 `xc7a100tcsg324-1` 只有 210 个)。因此上板时用 `cpu21_board_top` 作为顶层, 只引出板上真正需要的 23 个引脚,其余调试端口不连接。 ### 引脚分配(`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 | ### 时钟:为什么还保留 2 分频 乘除法原本是 ALU 里的组合运算(`a*b`、`a%b`),实测关键路径约 94 ns(325 级逻辑、 286 个 CARRY4),在 100 MHz 下必然不收敛,所以之前只能把时钟 16 分频到 6.25 MHz。 现在乘除法改成了 `pipelined-cpu/srcs/rtl/cpu21_riscv_muldiv.v` 里的 **32 拍迭代多周期单元**,关键路径上 不再有组合乘/除,也不需要再分频了;但把所有加法器/比较器都换成迭代后,在 100 MHz 下仍有约 1.1 ns 的违例——那是 **EX 级“分支判决 / 中断保存 → BPB 更新”这条与乘除法 无关的组合路径**(逻辑只占约 3 ns,剩下 7~8 ns 全是布线)。 因此在 `cpu21_board_top` 里保留一个参数化的分频器 `CLK_DIV`,默认取 **2(50 MHz, 周期 20 ns)**,既比原来的 16 分频快 8 倍,又留出充足余量: | 配置 | CPU 时钟 | 实测 WNS(xc7a100tcsg324-1) | 说明 | | ------------ | -------- | ---------------------------- | ------------------------------------- | | `CLK_DIV=1` | 100 MHz | -1.1 ns ✗ | 违例路径与乘除法无关(分支/中断/BPB) | | `CLK_DIV=2` | 50 MHz | **+5.1 ns** ✓ | 默认,余量约 25% | | `CLK_DIV=16` | 6.25 MHz | +6.1 ns ✓ | 改造前的设置,现在没必要了 | 分频后的时钟在综合/实现时由 Vivado 自动插入 BUFG 走全局时钟网络(RTL 里不例化 `BUFG` 原语,这样在不认识 Xilinx 原语的仿真器里也能直接仿真)。XDC 中必须用 `create_generated_clock` 声明这个分频时钟,否则时序分析会按 10 ns 去要求 CPU 内部路径而误报大量违例: ```tcl create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \ -divide_by 2 [get_pins { clk_div_reg/Q }] ``` 改分频比时 RTL 的 `CLK_DIV` 与 XDC 的 `-divide_by` 要同时改。 ### 片内 ROM / RAM - ROM:`ROM_WORDS = 1024` 字(4 KB),组合读,取指地址 `instr_addr[11:2]`; 内容由 `` `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。 - 两个 `initial` 块都用 NOP(`32'h0000_0013`)或 0 预填充,ROM 未使用区域是 NOP, 这样即使程序跑飞也不会执行到随机指令而乱写寄存器。 ### 演示程序的生成与仿真 ```powershell # 在工程目录 pipelined-cpu/ 下执行: # 1) 汇编 + 生成 ROM 初始化文件 python tools/rv_asm.py programs/board_demo.asm _tmp/board_demo.hex python tools/hex2vh.py _tmp/board_demo.hex srcs/board/cpu21_board_rom.vh 1024 # 2) 上板顶层的 RTL 仿真(默认程序延时约 20 ms,只需确认第一次写入 LED=0001) xvlog srcs/rtl/*.v srcs/board/cpu21_board_top.v srcs/tb/tb_board_top.v xelab -top tb_board_top -snapshot board_sim xsim board_sim -tclbatch run_quit.tcl ``` `board_demo.asm` 是一段 16 步跑马灯(`0001 → 0002 → … → 8000 → 0001`), 每步之间用软件循环延时约 20 ms(50 MHz 下)。仓库里同时提供了两份 ROM 初始化 文件,由 `BOARD_ROM_FAST` 宏选择(综合流程不会定义该宏): | 文件 | 用途 | 每步耗时 | | -------------------------------------------------- | ------------------------------ | -------- | | `pipelined-cpu/srcs/board/cpu21_board_rom.vh` | 综合/上板(默认) | 约 20 ms | | `pipelined-cpu/srcs/board/cpu21_board_rom_fast.vh` | 仅仿真:一次跑完整个跑马灯序列 | 2.6 µs | ```powershell # 快速版仿真:编译时加上 -d BOARD_ROM_FAST(xvlog 与 xelab 都要带) xvlog -d BOARD_ROM_FAST srcs/rtl/*.v srcs/board/cpu21_board_top.v srcs/tb/tb_board_top.v xelab -top tb_board_top -snapshot board_sim_fast -d BOARD_ROM_FAST ``` 两份 ROM 初始化文件由 `programs/board_demo.asm`(默认,≈ 20 ms/步)与 `programs/board_demo_fast.asm`(快速版,≈ 2.6 µs/步,只差延时循环的初值)生成: ```powershell python tools/rv_asm.py programs/board_demo_fast.asm _tmp/board_demo_fast.hex python tools/hex2vh.py _tmp/board_demo_fast.hex srcs/board/cpu21_board_rom_fast.vh 1024 ``` ### Vivado 工程设置与实现结果 Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要手动设置: 1. `sources_1` 顶层设为 `cpu21_board_top`(纯 RTL 仿真时也可切回 `cpu21_riscv_redirect_int_bpb`); 2. 加入 `pipelined-cpu/srcs/board/cpu21_board_top.v`、`pipelined-cpu/srcs/constrs/cpu21_board_top.xdc`, `pipelined-cpu/srcs/board/cpu21_board_rom.vh` 的文件类型设成 `Verilog Header`(不能当普通源码, 否则会被当成顶层候选); 3. 三个 testbench 放在**仿真 fileset**(`sim_1` 顶层 `tb_cpu21_riscv_redirect_int_bpb`、 `sim_2` 顶层 `tb_no_intr`),不参与综合,因此不会再出现“testbench 被当成顶层” 导致的 `[Place 30-494] The design is empty`。 `xc7a100tcsg324-1` 上的实现结果(乘除法改为多周期单元、`CLK_DIV=2` = 50 MHz): | 项目 | 结果 | | -------- | -------------------------------------------------------------------------------------------- | | 综合 | `synth_design Complete!` | | 实现 | `route_design Complete!`、`write_bitstream Complete!`,`WNS = +5.109 ns`(无 Failed Timing) | | 资源 | LUT 3420 (5.39%)、FF 2507、**DSP 0**(组合乘法器已消失)、IOB 23 (10.95%) | | bit 文件 | `pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit` | 对比改造前(组合乘除法 + `CLK_DIV=16`):LUT 5400 → 3420、DSP 12 → 0、 CPU 时钟 6.25 MHz → 50 MHz(快 8 倍),并且乘除法不再位于关键路径上。 上板操作:用 Vivado 打开工程 → `Open Hardware Manager` → `Program Device`,选上述 `.bit`。复位后跑马灯立即从 `0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用 按 `go`;`go` 只对执行了 `ecall` 停机、等待继续运行的程序有意义),LED16_B 每次写入时点亮。 ## 回归测试 工程里带了一个 xsim 回归脚本,在 `pipelined-cpu/` 目录下运行: ```powershell powershell -ExecutionPolicy Bypass -File tools\run_regress.ps1 ``` 它会按顺序做四件事: 1. 编译 `srcs/rtl/` 下的处理器源码; 2. 跑 4 个指令测试(`muldiv_test`、`isa_test`、`lui_test`、`mmio_test`),把测试平台 打印的 LED 序列与 `programs/*.expected` 逐值比对; 3. 跑两个流水基线(`tb_no_intr`、`tb_cpu21_riscv_redirect_int_bpb`),打印 FINAL 统计行; 4. 跑板级顶层(`-d BOARD_ROM_FAST`),检查跑马灯 LED 序列。 最后汇总 PASS/FAIL,并以失败数作为退出码。编译与仿真的中间文件都写在 `%TEMP%\cpu21_regress` 下,不会污染工程目录;需要 `xvlog`/`xelab`/`xsim` 在 PATH 上。 期望结果(2026-09,多周期乘除法 + `CLK_DIV=2`): | 测试 | 期望 | | ---- | ---- | | muldiv_test / isa_test / lui_test / mmio_test | 13 / 24 / 4 / 5 个 LED 值全部匹配 | | tb_no_intr(benchmark,5560 周期) | `stalls=1528 bubbles=1830 cond_taken=639 uncond=71 pred_ok=687 pred_fail=80` | | tb_cpu21_riscv_redirect_int_bpb(中断,6003 周期) | `stalls=963 bubbles=1205 cond_taken=547 uncond=28 pred_ok=557 pred_fail=87` | | 板级(快速 ROM) | 跑马灯 `0001 → … → 8000` 循环,每步约 2.6 µs | ## 工程分享(Tcl 一键重建) `pipelined-cpu/` 已整理成“自包含 + 可直接分享”的工程目录,设计文件都在里面, 别人拿到整个文件夹后**不需要改任何路径**: ```text pipelined-cpu/ ├─ create_project.tcl 一键重建脚本(用 [info script] 定位自身目录) ├─ srcs/rtl/*.v 处理器 RTL(13 个文件) ├─ srcs/board/*.v|*.vh 板级顶层、片内 ROM 初始化(含 __FAST 仿真版本) ├─ srcs/constrs/*.xdc 引脚约束 + 板级母版 ├─ srcs/tb/*.v 三个测试平台 ├─ srcs/rom/*.hex 测试平台加载的 ROM 镜像 ├─ tools/、programs/ 迷你汇编器与测试程序(重生成 ROM 用) ├─ cpu21_riscv_redirect_int_bpb说明.md └─ pipelined-cpu.xpr(+ *.runs/…) 本机工程,分享时可删 ``` 用法(在 Vivado Tcl Shell 里,或命令行 `vivado -mode batch -source create_project.tcl`): ```tcl cd source create_project.tcl ``` 脚本会就地创建 `pipelined-cpu.xpr`,并配好: - `sources_1` 顶层 `cpu21_board_top`(处理器 RTL + 板级顶层 + 片内 ROM 初始化, `cpu21_board_rom.vh` 标记为 `Verilog Header`); - `constrs_1`:`cpu21_board_top.xdc`(引脚 + `CLK_DIV` 分频时钟)与板级母版; - `sim_1`(顶层 `tb_cpu21_riscv_redirect_int_bpb`)与 `sim_2`(顶层 `tb_no_intr`); 测试平台会依次探测 ROM 镜像,其中 `../../../../../srcs/rom/` 对应 GUI 仿真的 工作目录(`.sim/sim_1/behav/xsim`),所以创建完就能直接 `Run Simulation`; - 两个仿真 fileset 的 `xsim.simulate.runtime` 设为 `all`,避免 GUI 仿真只跑 1000 ns 就停下(看起来像卡死)。 分享前建议删掉生成目录:`*.xpr`、`*.runs`、`*.sim`、`*.cache`、`*.hw`、`*.gen`、 `*.ip_user_files`、`.Xil`。实测只带 `create_project.tcl` + `srcs/` + `pipelined-cpu/tools/` + `programs/` + 说明文档约 **0.3 MB**,在任意路径重建工程并综合成功。 > 源文件只有一份,就在 `pipelined-cpu/srcs/`(`tools/`、`programs/` 同理), > 直接在工程目录里改即可,没有需要同步的副本。 也可以用 Vivado 自动生成: ```tcl write_project_tcl -force -paths_relative_to <工程目录> <工程目录>/create_project.tcl ``` 注意自动生成的版本会把源路径写成**生成时的绝对路径**、并且要求 `origin_dir` 指向 工程目录,所以要手动改两行才能给别人用: ```tcl set origin_dir [file dirname [file normalize [info script]]] create_project ${_xil_proj_name_} $origin_dir -part xc7a100tcsg324-1 ```