Author SHA1 Message Date
Anccend e57d97b0ab 更新md文件名 2026-09-13 19:37:23 +08:00
Anccend cb0cb433ac 源文件收敛到 pipelined-cpu/ 一份:删除根目录 rtl/board/testbench/tools,补充回归脚本
- 现在只有一份源文件,就在 pipelined-cpu/ 下:
    srcs/rtl(13 个处理器 RTL)、srcs/board、srcs/constrs、srcs/tb、srcs/rom、
    tools/(rv_asm.py、hex2vh.py、run_regress.ps1)、programs/(汇编源码与期望值)。
  仓库根目录的 rtl/、board/、testbench/、tools/ 已删除(删除前逐个文件比对过 MD5,
  与工程目录里的副本完全一致),说明文档仍放在最外层
  cpu21_riscv_redirect_int_bpb说明.md。
- .gitignore 改为:忽略 pipelined-cpu/ 下的工程生成物(*.xpr、*.runs、*.sim、*.cache…),
  只跟踪 srcs/、tools/、programs/、create_project.tcl;_tmp/、_vivado/ 等本地临时目录
  继续忽略。
- 新增 tools/run_regress.ps1:一键回归脚本。编译 srcs/rtl/ → 跑 muldiv/isa/lui/mmio
  四个指令测试并与 programs/*.expected 逐值比对 → 跑两个流水基线 → 跑板级顶层
  (-d BOARD_ROM_FAST)检查跑马灯,最后汇总 PASS/FAIL 并返回失败数作为退出码。
  编译与仿真的中间文件写在 %TEMP%\cpu21_regress,不污染工程目录。实测全部通过。
- 新增 programs/board_demo_fast.asm:快速版演示程序(延时初值 64,约 2.6 µs/步),
  对应 srcs/board/cpu21_board_rom_fast.vh;仿真时用 -d BOARD_ROM_FAST 才会选到它。
- 两个测试平台增加 srcs/rom 探测路径,工程目录内直接 Run Simulation 即可加载镜像。
- 文档:目录结构一节按新布局重写;新增“回归测试”一节(脚本用法 + 期望基线数字);
  上板章节的生成/仿真命令改为相对工程目录(python tools/rv_asm.py programs/…、
  xvlog -d BOARD_ROM_FAST srcs/rtl/*.v …);tools 里的用法示例同步更新;
  “工程分享”一节去掉已不存在的副本同步说明。
- 清理:删除本地 _tmp/(约 57 MB)、_vivado/、.Xil/ 临时目录。
2026-09-13 19:09:36 +08:00
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
Anccend 4a81685066 乘除法改为多周期时序单元,CPU 时钟从 6.25 MHz 提到 50 MHz
- 新增 rtl/cpu21_riscv_muldiv.v:移位迭代的多周期乘除法单元。
  * 乘法:32 拍移位加法得到 64 位乘积;有符号变体先取操作数绝对值,迭代结束后
    按符号对 64 位乘积取负。
  * 除法:32 拍恢复余数法得到商与余数;除零按 RISC-V 约定(商全 1、余数为被除数)。
  * 一次乘/除独占 EX 共 34 拍(1 拍装载 + 32 拍迭代 + 1 拍定型):期间 ID/EX 保持、
    EX/MEM 插气泡、取指/译码冻结,并屏蔽中断(避免结果未定型就写回 rd)。
- rtl/cpu21_riscv_alu.v:删除组合的 32x32 乘法与除法/取余(原来关键路径约 94 ns、
  325 级逻辑、286 个 CARRY4,是 100 MHz 无法收敛的根因)。ALU_MUL/ALU_DIVU 现在
  只作为“这是乘除法指令”的标识,结果由顶层从 muldiv 选取(wb_sel=3 取 result2 的
  约定保持不变,译码器与写回选择都不用改)。
- rtl/cpu21_riscv_decoder.v:新增 md_op_o(乘除法操作类型),并补上课程真值表里的
  divu(funct3=101)——之前这一项译码缺失,会导致 divu 不写 rd。
- rtl/cpu21_riscv_redirect_int_bpb.v:ID/EX 增加 md_op;EX 级停留、结果选择与中断
  屏蔽。另外为时序做了三处行为等价的重构(100 MHz 下 WNS 从 -3.54 ns 改善到
  -1.10 ns):
  * JAL/JALR 的链接地址 PC+4 改在 EX 级预先算好并随流水寄存器传递,去掉
    “写回值/前递 → 停机判定 → 流水控制”路径上的两个 32 位加法器;
  * ID/EX 的 payload 时钟使能不再依赖冲刷(只有 valid 位受冲刷控制),把
    “EX 级组合判决 → 流水寄存器时钟使能”这条最长路径缩到只剩 1 位;
  * ex_block_irq 不再包含 ex_redirect_valid / ex_halt_event(它们本身就会让
    flush_younger 有效,中断优先时返回地址由 irq_save_pc 正确给出),切断
    “分支判决 → 中断控制器 → flush”的绕行路径。
- rtl/cpu21_riscv_hazard_unit.v:停顿条件去掉 flush_younger(冲刷由 valid 位与
  PC/IF-ID 的冲刷分支处理,同时发生停顿只会多保持一拍 payload),避免把长路径
  引到 payload 的时钟使能上。
- board/cpu21_board_top.v / .xdc:乘除法已不在关键路径上,分频比从 16 降到 2
  (50 MHz,周期 20 ns)。实测(xc7a100tcsg324-1):CLK_DIV=1(100 MHz)下
  WNS=-1.1 ns,剩余违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,与乘除法无关
  (逻辑只占约 3 ns,其余 ~8 ns 是布线);CLK_DIV=2 时 WNS=+5.109 ns,余量约 25%。
- 上板 ROM 按 50 MHz 重新生成(延时计数 0x7A000 ≈ 20 ms/步,快速版 64 次循环),
  CPU 时钟比改造前快 8 倍。
- tools/rv_asm.py:支持 divu / remu 助记符。新增 testbench/programs/muldiv_test.asm
  与 muldiv_test.expected(13 个用例:mul/mulh/mulhsu/mulhu/divu/remu、负数、
  0xffffffff、除零、0x12345678 大数)。
- 验证:muldiv_test 13/13、isa_test 24/24、lui_test 4/4、mmio_test 5/5 全部 PASS;
  两个基线(5560 / 6003 周期)与改造前的流水统计一致(差异只来自多周期乘除法带来的
  停顿/气泡计数);中断测试仍能正常进入 0x30ac / 0x31e0 / 0x3300 三层嵌套处理程序;
  板级仿真:默认 ROM 首次写入 LED=0001、快速 ROM 跑马灯 0001..8000 循环正常。
- 实现结果:synth_design / route_design / write_bitstream 全部完成,
  LUT 5400 → 3420(5.39%)、DSP 12 → 0、IOB 23,时钟 6.25 MHz → 50 MHz,
  bit 文件已更新。
2026-09-13 18:45:17 +08:00
Anccend 556fbe9530 目录整理:RTL/上板/仿真分开存放,测试 ROM 镜像归入 testbench/rom
- rtl/:12 个可综合的处理器源文件(顶层 cpu21_riscv_redirect_int_bpb.v,其余为各功能单元)。
- board/:板级顶层 cpu21_board_top.v、引脚约束 cpu21_board_top.xdc、片内 ROM 初始化
  cpu21_board_rom.vh 与短延时版本 cpu21_board_rom_fast.vh。
- testbench/:保留三个测试平台;ROM 镜像统一放到 testbench/rom/,测试程序源码与期望值
  仍在 testbench/programs/(其中的 .hex 是 RARS 原始输出,与 rom/ 里改过显示方式的版本
  内容不同,两者都保留)。
- 同步更新:两个测试平台的 ROM 探测路径改为 testbench/rom/(并在头注释里补充说明:
  PowerShell 会把 -generic_top/--testplusarg 参数里的 "=" 拆开、Verilog 字符串中的
  路径要用正斜杠);tools/hex2vh.py 的用法示例指向 board/cpu21_board_top.v;
  说明文档新增“目录结构”一节并同步所有路径引用;.gitignore 补充 .Xil/。
- 清理:删除 *.hex.lst 中间文件与空的 .Xil/ 目录。
- 验证:isa_test 24/24、lui_test 4/4、mmio_test 5/5 三项回归通过;两个基线(5560 / 6003
  周期)与整理前一致;板级仿真(默认 ROM 首次写入 LED=0001、快速 ROM 跑马灯循环)正常;
  Vivado 工程源文件路径同步到 rtl/ 与 board/ 后综合通过(LUT 5276、23 个引脚,与整理前
  完全一致),因此已有 bit 文件仍然有效。
2026-09-13 18:06:21 +08:00
Anccend 1ae6fe6ebe 上板支持:Nexys4DDR 顶层、片内 ROM/RAM 与 MMIO 跑马灯演示
- 新增 cpu21_board_top.v:上板顶层。处理器顶层暴露了 775 位调试端口,直接作为
  实现顶层会因 IO 超限无法布局布线([Place 30-415] IO Placement failed due to
  overutilization),本模块只引出板上真正需要的 23 个引脚
  (clk100 / rst_n / go / irq_sw[2:0] / led[15:0] / led_we)。
  * 100 MHz 经 CLK_DIV=16 分频到 6.25 MHz:ALU 的除法/取余是组合电路,
    100 MHz 下 WNS=-84.126 ns(325 级逻辑)无法收敛,分频后 WNS=+6.083 ns。
  * 分频时钟由 Vivado 自动插入 BUFG;RTL 中不例化原语,以便跨工具仿真。
  * 复位同步器、片内 ROM(1024x32,组合读,`include cpu21_board_rom.vh`)、
    片内 RAM(1024x32,字节写使能,只响应地址 <4KB,避免与 MMIO 地址别名)。
- 新增 cpu21_board_top.xdc:Nexys4DDR 引脚约束 + create_generated_clock。
- 新增 tools/hex2vh.py:把 hex 机器码转成 rom[i] = 32'h...; 初始化包含文件。
- 新增 testbench/programs/board_demo.asm 与 testbench/tb_board_top.v:
  16 步跑马灯演示程序(通过 MMIO 写 LED)。仿真验证:默认 ROM 下首次写入
  LED=0001,快速 ROM 下完整序列 0001..8000 循环。
- 新增 cpu21_board_rom.vh / cpu21_board_rom_fast.vh,由 BOARD_ROM_FAST 宏选择。
- tools/rv_asm.py:读源文件时兼容带 BOM 的 UTF-8。
- 测试资源:补齐 lui_test.expected;修正 mmio_test.expected(print 由
  lui+sw 实现,本身就是一次 LED 写,共 5 次)。三个回归全部通过:
  isa_test 24/24、lui_test 4/4、mmio_test 5/5。
- .gitignore:忽略 _tmp/、_vivado/、*.vcd、*.pb、*.hex.lst。
- 说明文档:模块划分表补上上板相关模块,新增“上板(Nexys4DDR + Vivado)”
  章节(引脚分配、分频原因、ROM/RAM 组织、ROM 生成流程、Vivado 工程设置、
  实现结果)。

实现结果:synth_design / route_design / write_bitstream 全部完成,
LUT 5400 (8.52%)、LUTRAM 512、FF 2507、DSP 12、IOB 23 (10.95%),
bit 文件为 pipelined-cpu/pipelined-cpu.runs/impl_1/cpu21_board_top.bit
(Vivado 工程目录被 .gitignore 排除,不入库)。
2026-09-13 17:37:44 +08:00
Anccend 192e28795a 实现 MMIO,LED 显示从 ecall 改为内存映射存储
cpu21_mmio.v(新增)
- 在 MEM 级做地址译码,把外设寄存器映射进数据地址空间;目前实现 LED 数据
  寄存器(地址由顶层 parameter LED_ADDR 指定,默认 0x0000_1000,位于 4 KB
  数据 RAM 之外,不会与 RAM 冲突);
- 地址译码按“字”对齐:同一 32 位寄存器内任意字节偏移都算命中,具体修改
  哪几个字节由 wstrb 决定,因此 sw 写全字、sb 只写单字节(与 RAM 字节写
  语义一致);
- 读命中时由顶层用 rdata_o 取代 data_rdata_i,从而支持把 LED 值读回;
- led_valid_o 在写入后拉高一拍,作为“本周期有 LED 写入”的指示。

cpu21_riscv_redirect_int_bpb.v
- 顶层参数新增 LED_ADDR;例化 cpu21_mmio 并新增访存读数据选择
  mem_rdata = mmio_rdata_hit ? mmio_rdata : data_rdata_i(MEM/WB 捕获用它);
- 删除 ecall 驱动的 LED 逻辑(原 ex_led_event 与 led_data_q/led_valid_q 寄存器
  移入 MMIO 模块),LED 输出端口改由 cpu21_mmio 驱动;
- ecall 保留停机语义:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
  仍置 halted_o。

测试资产
- tools/rv_asm.py:print 伪指令改为“lui t6,LED_ADDR>>12 + sw a0,0(t6)”(写 MMIO
  LED 寄存器,仍需两个字);新增 sb 支持;文末说明同步更新;
- testbench/programs/mmio_test.asm + mmio_test.expected(新增):覆盖全字写、
  读回、字节写三种情况;
- isa_test.asm / lui_test.asm 迁移到 MMIO 输出方式;
- 两个 testbench 的 RAM 模型处注明 MMIO 区域不属于 RAM。

验证(xsim,按提交后的正式路径生成 hex)
- mmio_test:4 项输出全部匹配(12345678 / 12345678 / ffffffff / ffff7fff,
  最后一项为 sb 覆盖 led[15:8] 后读回);
- isa_test:24 项输出与 isa_test.expected 逐项一致;lui_test:4 项输出正确;
- 中断测试程序回归:IRQ1@cyc85、IRQ2@cyc266、IRQ3@cyc447 与改动前一致,
  FINAL 统计完全一致(stalls=11 bubbles=297 cond_taken=648 pred_ok=663
  pred_fail=102,6003 周期);
- benchmark 长仿真回归:FINAL 统计与改动前完全一致(stalls=3195 bubbles=6955
  cond_taken=8857 pred_ok=9455 pred_fail=1050,60000 周期),且 LED 输出次数为 0,
  确认 ecall 已不再驱动 LED;
- cpu21_mmio.v 已加入 Vivado 工程 sources_1。
2026-09-13 16:30:57 +08:00
53 changed files with 4143 additions and 204 deletions
+15
View File
@@ -1 +1,16 @@
# Vivado 工程生成物不入库;源文件都在 pipelined-cpu/ 下(srcs/、tools/、programs/)
pipelined-cpu/* pipelined-cpu/*
!pipelined-cpu/srcs/
!pipelined-cpu/tools/
!pipelined-cpu/programs/
!pipelined-cpu/create_project.tcl
# 本地临时目录(仿真脚本、日志、中间文件)
_tmp/
_vivado/
# 仿真/综合中间产物
*.vcd
*.pb
*.hex.lst
.Xil/
+363
View File
@@ -0,0 +1,363 @@
# `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 <pipelined-cpu 所在目录>
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 仿真的
工作目录(`<proj>.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
```
-86
View File
@@ -1,86 +0,0 @@
# `cpu21-riscv-4.circ` 的 Verilog 实现
顶层文件:`cpu21_riscv_redirect_int_bpb.v`
该设计对应原电路中的“重定向流水线+中断+分支预测”部分,顶层模块为
`cpu21_riscv_redirect_int_bpb`。为便于单独仿真与调试,各功能单元已拆分为
独立文件,顶层的 ALU、BPB 也已移出,具体见文末“文件与模块划分”。
## 已转换的功能
- 五级流水: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` 时把 `a0` 锁存到 `led_data_o`;否则置 `halted_o`,直到 `go_i` 为高。
- `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 位)。
## 指令编码
表中的 `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 |
`ecall` 和 `URET` 都是 `opcode=0x1c、funct3=0`,由 `IR[21]` 区分:
`IR[21]=0` 是 `ecall`,`IR[21]=1` 是 `URET`。这一点来自原控制器中的 `IR21` 分支逻辑。
## 顶层端口约定
- `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 时计入两个气泡。
原 `.circ` 文件中的 ROM、MIPS RAM、按钮、LED 和调试显示器属于 Logisim 外围,不直接搬入 RTL;其功能分别由指令接口、数据接口、`go_i`、`led_*_o` 和调试端口替代。
## 文件与模块划分
| 文件 | 模块 | 职责 |
| -------------------------------- | ------------------------------ | --------------------------------------------------------------- |
| `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` | 周期/停顿/气泡/分支/预测成功率等统计计数器 |
拆分只调整了模块边界与文件组织,端口、时序与行为完全等价:使用同一 ROM 程序
在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支
跳转、预测成败)完全一致。
+106
View File
@@ -0,0 +1,106 @@
# ============================================================================
# cpu21-riscv 流水线 CPU —— 一键重建 Vivado 工程
# FPGA:Nexys4DDR / xc7a100tcsg324-1(Vivado 2025.2 实测通过)
#
# 目录结构(本脚本所在目录就是工程目录):
# create_project.tcl 本脚本
# srcs/rtl/*.v 处理器 RTL(顶层 cpu21_riscv_redirect_int_bpb)
# 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
#
# 用法:
# 1) 建议先删掉本目录下的 pipelined-cpu.xpr,以及 *.runs / *.sim / *.cache /
# *.hw / *.gen / *.ip_user_files 等生成目录(不删会在下面报错提示);
# 2) 在 Vivado Tcl Shell 里执行: source create_project.tcl
# 或命令行: vivado -mode batch -source create_project.tcl -log create.log
# 3) 工程就地创建:pipelined-cpu.xpr(源文件仍在 srcs/ 下,不会被搬动)。
#
# 脚本用 [info script] 定位自身目录,所以整个文件夹拷到任何路径都能直接用。
# 若想用 Vivado 自动生成的版本,见说明文档“工程分享”一节。
# ============================================================================
set origin_dir [file dirname [file normalize [info script]]]
set proj_name "pipelined-cpu"
set part_name "xc7a100tcsg324-1"
# ---------------------------------------------------------------- 自检
set required [list \
"$origin_dir/srcs/board/cpu21_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom.vh" \
"$origin_dir/srcs/board/cpu21_board_rom_fast.vh" \
"$origin_dir/srcs/constrs/cpu21_board_top.xdc" \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v" \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_board_top.v"]
set missing {}
foreach f $required { if {![file exists $f]} { lappend missing $f } }
if {[llength [glob -nocomplain "$origin_dir/srcs/rtl/*.v"]] == 0} {
lappend missing "$origin_dir/srcs/rtl/*.v"
}
if {[llength $missing] > 0} {
puts "ERROR: 缺少下列文件,请确认 srcs/ 目录完整:"
foreach f $missing { puts " $f" }
return 1
}
if {[file exists "$origin_dir/$proj_name.xpr"]} {
puts "ERROR: 工程已存在:$origin_dir/$proj_name.xpr"
puts " 请先删除 .xpr(以及 *.runs / *.sim 等生成目录)后重跑本脚本。"
return 1
}
# ---------------------------------------------------------------- 建工程
create_project $proj_name $origin_dir -part $part_name
# 设计源文件:处理器 RTL + 板级顶层 + 片内 ROM 初始化
add_files -norecurse -fileset sources_1 [glob "$origin_dir/srcs/rtl/*.v"]
add_files -norecurse -fileset sources_1 [list \
"$origin_dir/srcs/board/cpu21_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom.vh"]
set_property file_type {Verilog Header} \
[get_files "$origin_dir/srcs/board/cpu21_board_rom.vh"]
# 约束(cpu21_board_top.xdc 是引脚与分频时钟约束)
add_files -norecurse -fileset constrs_1 [glob "$origin_dir/srcs/constrs/*.xdc"]
# 仿真文件:测试平台 + ROM 镜像 + 快速版 ROM 头文件
add_files -norecurse -fileset sim_1 [list \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v" \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_board_top.v" \
"$origin_dir/srcs/board/cpu21_board_rom_fast.vh"]
add_files -norecurse -fileset sim_1 [glob "$origin_dir/srcs/rom/*.hex"]
set_property file_type {Verilog Header} \
[get_files "$origin_dir/srcs/board/cpu21_board_rom_fast.vh"]
# 第二个仿真 fileset:无中断测试平台(tb_no_intr)
create_fileset -simset sim_2
add_files -norecurse -fileset sim_2 [list \
"$origin_dir/srcs/tb/tb_no_intr.v" \
"$origin_dir/srcs/tb/tb_cpu21_riscv_redirect_int_bpb.v"]
# ---------------------------------------------------------------- 顶层与顺序
set_property top cpu21_board_top [get_filesets sources_1]
set_property top tb_cpu21_riscv_redirect_int_bpb [get_filesets sim_1]
set_property top tb_no_intr [get_filesets sim_2]
update_compile_order -fileset sources_1
update_compile_order -fileset sim_1
update_compile_order -fileset sim_2
# GUI 仿真默认只跑 1000 ns,这里改成跑完(否则看起来像卡住)
foreach fs {sim_1 sim_2} {
catch { set_property -name {xsim.simulate.runtime} -value {all} -objects [get_filesets $fs] }
}
# ---------------------------------------------------------------- 结果汇总
puts "工程已创建:$origin_dir/$proj_name.xpr"
puts " sources_1 顶层 = cpu21_board_top(上板:时钟分频 + 片内 ROM/RAM + 引脚)"
puts " sim_1 顶层 = tb_cpu21_riscv_redirect_int_bpb(中断/分支预测)"
puts " sim_2 顶层 = tb_no_intr(指令集测试,ROM 镜像在 srcs/rom/)"
puts " 下一步:Run Synthesis → Run Implementation → Generate Bitstream,或 Run Simulation"
puts " 提示:CPU 时钟 = 100 MHz / CLK_DIV(默认 2)= 50 MHz;改分频比时"
puts " cpu21_board_top.v 的 CLK_DIV 与 cpu21_board_top.xdc 的 -divide_by 要一起改。"
+22
View File
@@ -0,0 +1,22 @@
# 上板演示程序(Nexys4DDR):通过 MMIO 写 LED 数据寄存器,做 16 位跑马灯。
# LED_ADDR = 0x0000_1000(与 cpu21_board_top.v / cpu21_mmio.v 的默认参数一致)。
# 用 tools/hex2vh.py 把它转成 cpu21_board_rom.vh,供综合时初始化片内 ROM。
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
addi t1, zero, 1 # 跑马灯初值:bit0
main_loop:
sw t1, 0(t0) # MMIO 写 LED -> 板载 LED[15:0]
slli t1, t1, 1 # 左移一位
lui t4, 0x8 # t4 = 0x00008000
bltu t4, t1, restart # 移出 16 位后重新开始
jal zero, delay
restart:
addi t1, zero, 1
delay: # 软件延时,让人眼能看到跑马灯(50 MHz 下约 20 ms)
lui t2, 0x7a # 延时计数 0x0007A000 ≈ 5e5 次循环
addi t3, zero, 1
delay_loop:
sub t2, t2, t3
bne t2, zero, delay_loop
jal zero, main_loop
@@ -0,0 +1,25 @@
# 上板演示程序的**快速版**(仅仿真用):与 board_demo.asm 只差延时循环的初值——
# 从 `lui t2, 0x7a`(≈ 20 ms/步)改成 `addi t2, zero, 64`(≈ 2.6 µs/步 @50 MHz),
# 这样一次仿真(tb_board_top,300 µs 窗口)就能看完整个跑马灯序列。
# 重新生成: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
# 综合时用 -d BOARD_ROM_FAST 才会选到它(见 srcs/board/cpu21_board_top.v)。
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
addi t1, zero, 1 # 跑马灯初值:bit0
main_loop:
sw t1, 0(t0) # MMIO 写 LED -> 板载 LED[15:0]
slli t1, t1, 1 # 左移一位
lui t4, 0x8 # t4 = 0x00008000
bltu t4, t1, restart # 移出 16 位后重新开始
jal zero, delay
restart:
addi t1, zero, 1
delay: # 短延时(仿真用,人眼看不见)
addi t2, zero, 64 # 延时计数 64 次循环
addi t3, zero, 1
delay_loop:
sub t2, t2, t3
bne t2, zero, delay_loop
jal zero, main_loop
@@ -1,12 +1,13 @@
# 指令扩展验证程序:每条被测指令的结果都通过 ecall(a7=34) 输出,最后用 halt 停机。 # 指令扩展验证程序:每条被测指令的结果都通过 MMIO 写 LED 输出,最后用 halt 停机。
# 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。 # 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。
# #
# 用法(在仓库根目录执行): # 用法(在仓库根目录执行):
# python tools/rv_asm.py testbench/programs/isa_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex # python tools/rv_asm.py testbench/programs/isa_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
# 再用 testbench/tb_no_intr.v 仿真;hex 命名为 risc-v-benchmark_ccab.hex 时会被测试 # 再用 testbench/tb_no_intr.v 仿真;hex 命名为 risc-v-benchmark_ccab.hex 时会被测试
# 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。 # 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。
# print / halt 是 tools/rv_asm.py 提供的双字伪指令: # print / halt 是 tools/rv_asm.py 提供的双字伪指令(print 会占用 t6):
# print = addi a7,zero,34 + ecall;halt = addi a7,zero,10 + ecall。 # print = lui t6,0x1000>>12 + sw a0,0(t6)(写 MMIO 的 LED 寄存器);
# halt = addi a7,zero,10 + ecall(ecall 停机)。
# ---- sltiu:立即数符号扩展后做无符号比较 ---- # ---- sltiu:立即数符号扩展后做无符号比较 ----
addi t0, zero, 1 addi t0, zero, 1
@@ -1,26 +1,21 @@
# LUI 指令测试:依次打印 lui 的结果,最后停机。 # LUI 指令测试:依次把 lui 的结果写到 MMIO 的 LED 寄存器,最后停机。
# 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。 # 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。
# 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex # 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x12345 # t0 = 0x12345000 lui t0, 0x12345 # t0 = 0x12345000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # MMIO 写 LED -> 12345000
ecall
lui t0, 0xfffff # t0 = 0xfffff000 lui t0, 0xfffff # t0 = 0xfffff000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> fffff000
ecall
lui t0, 0x80000 # t0 = 0x80000000 lui t0, 0x80000 # t0 = 0x80000000
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> 80000000
ecall
lui t0, 0x12345 # 常见用法 lui+addi:0x12345678 lui t0, 0x12345 # 常见用法 lui+addi:0x12345678
addi t0, t0, 0x678 addi t0, t0, 0x678
add a0, zero, t0 add a0, zero, t0
addi a7, zero, 34 print # -> 12345678
ecall
addi a7, zero, 10 # a7=10 -> 停机 halt # 停机
ecall
+4
View File
@@ -0,0 +1,4 @@
12345000
fffff000
80000000
12345678
+25
View File
@@ -0,0 +1,25 @@
# MMIO 验证程序:写 LED 寄存器(0x0000_1000)-> 读回 -> 字节写 -> 再读回。
# 期望 LED 输出顺序(共 5 次,见同目录 mmio_test.expected):
# ① 12345678 sw 0x12345678
# ② 12345678 lw 读回后 print(print = lui t6 + sw a0,0(t6),本身就是一次 MMIO 写)
# ③ ffffffff sw 0xffffffff
# ④ ffff7fff sb 0x7f 覆盖 led[15:8]
# ⑤ ffff7fff 读回后 print
# 用法:python tools/rv_asm.py testbench/programs/mmio_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x1 # t0 = LED_ADDR = 0x00001000
lui t1, 0x12345
addi t1, t1, 0x678 # t1 = 0x12345678
sw t1, 0(t0) # ① MMIO 写 LED
lw a0, 0(t0) # MMIO 读回
print # ② 应为 12345678
addi t2, zero, -1 # t2 = 0xffffffff
sw t2, 0(t0) # ③ 应为 ffffffff
addi t3, zero, 0x7f
sb t3, 1(t0) # 字节写:led[15:8] = 0x7f
lw a0, 0(t0) # 读回
print # ④ 应为 ffff7fff
halt
@@ -0,0 +1,5 @@
12345678
12345678
ffffffff
ffff7fff
ffff7fff
+95
View File
@@ -0,0 +1,95 @@
# 乘除法(多周期单元)测试:每个结果都写给 MMIO 的 LED 寄存器,供测试平台比对。
# 期望输出顺序见同目录 muldiv_test.expected(共 13 个值)。
#
# ① mul 7 * 6 = 42 -> 0000002a
# ② mul -5 * 4 = -20 -> ffffffec
# ③ mulh 0x40000000 * 4 高 32 位 -> 00000001
# ④ mulhu 0xffffffff * 2 高 32 位 -> 00000001
# ⑤ mulhsu -1 * 0xffffffff 高 32 位 -> ffffffff
# ⑥ divu 100 / 7 = 14 -> 0000000e
# ⑦ remu 100 % 7 = 2 -> 00000002
# ⑧ divu 0xffffffff / 2 = 2147483647 -> 7fffffff
# ⑨ divu 12345 / 0 除零约定 -> ffffffff
# ⑩ remu 12345 % 0 除零约定 -> 00003039
# ⑪ remu 0xffffffff % 16 = 15 -> 0000000f
# ⑫ mul 0x12345678 * 16 低 32 位 -> 23456780
# ⑬ mulh 0x12345678 * 0x1000 高 32 位 -> 00000123
#
# 用法:python tools/rv_asm.py testbench/programs/muldiv_test.asm testbench/rom/muldiv_test.hex
# ① mul:有符号乘法的低 32 位
addi t1, zero, 7
addi t2, zero, 6
mul a0, t1, t2
print
# ② mul:负乘数(低 32 位)
addi t1, zero, -5
addi t2, zero, 4
mul a0, t1, t2
print
# ③ mulh:正数相乘,高 32 位不为 0
lui t1, 0x40000
addi t2, zero, 4
mulh a0, t1, t2
print
# ④ mulhu:无符号高位(0xffffffff * 2 = 0x1_fffffffe)
addi t1, zero, -1
addi t2, zero, 2
mulhu a0, t1, t2
print
# ⑤ mulhsu:有符号 x 无符号(-1 * 4294967295 = 0xffffffff_00000001)
addi t1, zero, -1
addi t2, zero, -1
mulhsu a0, t1, t2
print
# ⑥ divu:普通除法
addi t1, zero, 100
addi t2, zero, 7
divu a0, t1, t2
print
# ⑦ remu:普通取余
remu a0, t1, t2
print
# ⑧ divu:无符号大数
addi t1, zero, -1
addi t2, zero, 2
divu a0, t1, t2
print
# ⑨ divu:除零(商为全 1)
lui t1, 0x3
addi t1, t1, 0x39 # t1 = 12345
addi t2, zero, 0
divu a0, t1, t2
print
# ⑩ remu:除零(余数为被除数)
remu a0, t1, t2
print
# ⑪ remu:0xffffffff % 16
addi t1, zero, -1
addi t2, zero, 16
remu a0, t1, t2
print
# ⑫ mul:0x12345678 * 16 的低 32 位
lui t1, 0x12345
addi t1, t1, 0x678
addi t2, zero, 16
mul a0, t1, t2
print
# ⑬ mulh:0x12345678 * 0x1000 的高 32 位
lui t2, 0x1
mulh a0, t1, t2
print
halt
@@ -0,0 +1,13 @@
0000002a
ffffffec
00000001
00000001
ffffffff
0000000e
00000002
7fffffff
ffffffff
00003039
0000000f
23456780
00000123
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 上板顶层(Nexys4DDR,xc7a100tcsg324-1)
//
// 处理器顶层 cpu21_riscv_redirect_int_bpb 暴露了 775 位调试端口(各级 PC/IR、
// 计数器、BPB/中断观测等),直接作为实现顶层会因 I/O 超限而无法布局布线
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
//
// clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU)
// rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器
// go (N17) -> 按键 BTNC:ecall 停机后按下继续运行
// irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源
// led[15:0] -> 板载 LED[15:0]:显示 MMIO(LED_ADDR)写入值的低 16 位
// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察
//
// 片内存储器:
// ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出
// (该文件由 tools/hex2vh.py 从 testbench/programs/*.hex 生成);
// RAM:RAM_WORDS 字,字节写使能 + 组合读;只响应地址 < 2^(RAM_AW+2),
// 因此 MMIO 地址不会落进 RAM(避免与 LED_ADDR 别名)。
//
// 注意: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,
// 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。
// 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上,
// 实测(xc7a100tcsg324-1):
// CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的
// 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns,
// 7~8 ns 都是布线);
// CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。
// 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。
parameter integer CLK_DIV = 2
) (
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; // 异步复位(高有效)
// ---------------------------------------------------------------------------
// 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。
//
// 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的
// 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,
// 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。
// 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明,
// 改分频比时两处要一起改。
// ---------------------------------------------------------------------------
reg [15:0] clk_cnt;
reg clk_div;
always @(posedge clk100 or posedge arst) begin
if (arst) begin
clk_cnt <= 16'd0;
clk_div <= 1'b0;
end else if (clk_cnt == (CLK_DIV / 2 - 1)) begin
clk_cnt <= 16'd0;
clk_div <= ~clk_div;
end else begin
clk_cnt <= clk_cnt + 16'd1;
end
end
wire clk_cpu = clk_div;
// ---------------------------------------------------------------------------
// 复位同步器:按键异步置位、释放与 CPU 时钟同步
// ---------------------------------------------------------------------------
reg [2:0] rst_sync_q;
always @(posedge clk_cpu or posedge arst) begin
if (arst) rst_sync_q <= 3'b111;
else rst_sync_q <= {rst_sync_q[1:0], 1'b0};
end
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;
// ---------------------------------------------------------------------------
// 指令 ROM:组合读
// ---------------------------------------------------------------------------
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 填充
`ifdef BOARD_ROM_FAST
// 仿真专用:把演示程序的延时改小的版本(综合流程不会用到)
`include "cpu21_board_rom_fast.vh"
`else
`include "cpu21_board_rom.vh"
`endif
end
// ---------------------------------------------------------------------------
// 数据 RAM:字节写使能 + 组合读
// ---------------------------------------------------------------------------
reg [31:0] ram[0:RAM_WORDS-1];
integer j;
initial begin
for (j = 0; j < RAM_WORDS; j = j + 1) ram[j] = 32'b0;
end
wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2];
wire [RAM_AW+1:2] ram_index = data_addr[RAM_AW+1:2];
wire ram_sel = (data_addr[31:RAM_AW+2] == 0);
wire [31:0] instr_word = rom[rom_index];
wire [31:0] data_rdata = ram_sel ? ram[ram_index] : 32'b0;
always @(posedge clk_cpu) begin
if (ram_sel && data_we) begin
if (data_wstrb[0]) ram[ram_index][7:0] <= data_wdata[7:0];
if (data_wstrb[1]) ram[ram_index][15:8] <= data_wdata[15:8];
if (data_wstrb[2]) ram[ram_index][23:16] <= data_wdata[23:16];
if (data_wstrb[3]) ram[ram_index][31:24] <= data_wdata[31:24];
end
end
// ---------------------------------------------------------------------------
// 处理器:只连接上板需要的端口,调试端口留空(不产生 I/O)
// ---------------------------------------------------------------------------
cpu21_riscv_redirect_int_bpb #(
.LED_ADDR (LED_ADDR),
.USTATUS_INIT(USTATUS_INIT)
) u_cpu (
.clk (clk_cpu),
.reset (reset),
.instr_i (instr_word),
.data_rdata_i(data_rdata),
.irq_i (irq_sw),
.go_i (go),
.instr_addr_o(instr_addr),
.data_addr_o (data_addr),
.data_wdata_o(data_wdata),
.data_wstrb_o(data_wstrb),
.data_we_o (data_we),
.led_data_o (led_data),
.led_valid_o(led_valid)
);
// ---------------------------------------------------------------------------
// 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察
// ---------------------------------------------------------------------------
assign led = led_data[15:0];
reg [23:0] led_we_cnt;
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; // 约 0.34 s @50 MHz
else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1;
end
assign led_we = (led_we_cnt != 24'd0);
endmodule
`default_nettype wire
@@ -0,0 +1,265 @@
## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project
## Clock signal
#set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
#create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {CLK100MHZ}];
##Switches
#set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { SW[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
#set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { SW[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
#set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { SW[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
#set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { SW[3] }]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
#set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { SW[4] }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
#set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { SW[5] }]; #IO_L7N_T1_D10_14 Sch=sw[5]
#set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { SW[6] }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
#set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { SW[7] }]; #IO_L5N_T0_D07_14 Sch=sw[7]
#set_property -dict { PACKAGE_PIN T8 IOSTANDARD LVCMOS18 } [get_ports { SW[8] }]; #IO_L24N_T3_34 Sch=sw[8]
#set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS18 } [get_ports { SW[9] }]; #IO_25_34 Sch=sw[9]
#set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { SW[10] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=sw[10]
#set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { SW[11] }]; #IO_L23P_T3_A03_D19_14 Sch=sw[11]
#set_property -dict { PACKAGE_PIN H6 IOSTANDARD LVCMOS33 } [get_ports { SW[12] }]; #IO_L24P_T3_35 Sch=sw[12]
#set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { SW[13] }]; #IO_L20P_T3_A08_D24_14 Sch=sw[13]
#set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { SW[14] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=sw[14]
#set_property -dict { PACKAGE_PIN V10 IOSTANDARD LVCMOS33 } [get_ports { SW[15] }]; #IO_L21P_T3_DQS_14 Sch=sw[15]
## LEDs
#set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L18P_T2_A24_15 Sch=led[0]
#set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1]
#set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[2]
#set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L8P_T1_D11_14 Sch=led[3]
#set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L7P_T1_D09_14 Sch=led[4]
#set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5]
#set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { LED[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6]
#set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { LED[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7]
#set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { LED[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8]
#set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { LED[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9]
#set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { LED[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10]
#set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { LED[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11]
#set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { LED[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12]
#set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { LED[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13]
#set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { LED[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14]
#set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { LED[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15]
#set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { LED16_B }]; #IO_L5P_T0_D06_14 Sch=led16_b
#set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { LED16_G }]; #IO_L10P_T1_D14_14 Sch=led16_g
#set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { LED16_R }]; #IO_L11P_T1_SRCC_14 Sch=led16_r
#set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { LED17_B }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led17_b
#set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { LED17_G }]; #IO_0_14 Sch=led17_g
#set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { LED17_R }]; #IO_L11N_T1_SRCC_14 Sch=led17_r
##7 segment display
#set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { CA }]; #IO_L24N_T3_A00_D16_14 Sch=ca
#set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { CB }]; #IO_25_14 Sch=cb
#set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { CC }]; #IO_25_15 Sch=cc
#set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { CD }]; #IO_L17P_T2_A26_15 Sch=cd
#set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { CE }]; #IO_L13P_T2_MRCC_14 Sch=ce
#set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { CF }]; #IO_L19P_T3_A10_D26_14 Sch=cf
#set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { CG }]; #IO_L4P_T0_D04_14 Sch=cg
#set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { DP }]; #IO_L19N_T3_A21_VREF_15 Sch=dp
#set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { AN[0] }]; #IO_L23P_T3_FOE_B_15 Sch=an[0]
#set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { AN[1] }]; #IO_L23N_T3_FWE_B_15 Sch=an[1]
#set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { AN[2] }]; #IO_L24P_T3_A01_D17_14 Sch=an[2]
#set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { AN[3] }]; #IO_L19P_T3_A22_15 Sch=an[3]
#set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { AN[4] }]; #IO_L8N_T1_D12_14 Sch=an[4]
#set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { AN[5] }]; #IO_L14P_T2_SRCC_14 Sch=an[5]
#set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { AN[6] }]; #IO_L23P_T3_35 Sch=an[6]
#set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { AN[7] }]; #IO_L23N_T3_A02_D18_14 Sch=an[7]
##Buttons
#set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { CPU_RESETN }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
#set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { BTNC }]; #IO_L9P_T1_DQS_14 Sch=btnc
#set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { BTNU }]; #IO_L4N_T0_D05_14 Sch=btnu
#set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { BTNL }]; #IO_L12P_T1_MRCC_14 Sch=btnl
#set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { BTNR }]; #IO_L10N_T1_D15_14 Sch=btnr
#set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { BTND }]; #IO_L9N_T1_DQS_D13_14 Sch=btnd
##Pmod Headers
##Pmod Header JA
#set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 } [get_ports { JA[1] }]; #IO_L20N_T3_A19_15 Sch=ja[1]
#set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { JA[2] }]; #IO_L21N_T3_DQS_A18_15 Sch=ja[2]
#set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { JA[3] }]; #IO_L21P_T3_DQS_15 Sch=ja[3]
#set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { JA[4] }]; #IO_L18N_T2_A23_15 Sch=ja[4]
#set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { JA[7] }]; #IO_L16N_T2_A27_15 Sch=ja[7]
#set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { JA[8] }]; #IO_L16P_T2_A28_15 Sch=ja[8]
#set_property -dict { PACKAGE_PIN F18 IOSTANDARD LVCMOS33 } [get_ports { JA[9] }]; #IO_L22N_T3_A16_15 Sch=ja[9]
#set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { JA[10] }]; #IO_L22P_T3_A17_15 Sch=ja[10]
##Pmod Header JB
#set_property -dict { PACKAGE_PIN D14 IOSTANDARD LVCMOS33 } [get_ports { JB[1] }]; #IO_L1P_T0_AD0P_15 Sch=jb[1]
#set_property -dict { PACKAGE_PIN F16 IOSTANDARD LVCMOS33 } [get_ports { JB[2] }]; #IO_L14N_T2_SRCC_15 Sch=jb[2]
#set_property -dict { PACKAGE_PIN G16 IOSTANDARD LVCMOS33 } [get_ports { JB[3] }]; #IO_L13N_T2_MRCC_15 Sch=jb[3]
#set_property -dict { PACKAGE_PIN H14 IOSTANDARD LVCMOS33 } [get_ports { JB[4] }]; #IO_L15P_T2_DQS_15 Sch=jb[4]
#set_property -dict { PACKAGE_PIN E16 IOSTANDARD LVCMOS33 } [get_ports { JB[7] }]; #IO_L11N_T1_SRCC_15 Sch=jb[7]
#set_property -dict { PACKAGE_PIN F13 IOSTANDARD LVCMOS33 } [get_ports { JB[8] }]; #IO_L5P_T0_AD9P_15 Sch=jb[8]
#set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { JB[9] }]; #IO_0_15 Sch=jb[9]
#set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { JB[10] }]; #IO_L13P_T2_MRCC_15 Sch=jb[10]
##Pmod Header JC
#set_property -dict { PACKAGE_PIN K1 IOSTANDARD LVCMOS33 } [get_ports { JC[1] }]; #IO_L23N_T3_35 Sch=jc[1]
#set_property -dict { PACKAGE_PIN F6 IOSTANDARD LVCMOS33 } [get_ports { JC[2] }]; #IO_L19N_T3_VREF_35 Sch=jc[2]
#set_property -dict { PACKAGE_PIN J2 IOSTANDARD LVCMOS33 } [get_ports { JC[3] }]; #IO_L22N_T3_35 Sch=jc[3]
#set_property -dict { PACKAGE_PIN G6 IOSTANDARD LVCMOS33 } [get_ports { JC[4] }]; #IO_L19P_T3_35 Sch=jc[4]
#set_property -dict { PACKAGE_PIN E7 IOSTANDARD LVCMOS33 } [get_ports { JC[7] }]; #IO_L6P_T0_35 Sch=jc[7]
#set_property -dict { PACKAGE_PIN J3 IOSTANDARD LVCMOS33 } [get_ports { JC[8] }]; #IO_L22P_T3_35 Sch=jc[8]
#set_property -dict { PACKAGE_PIN J4 IOSTANDARD LVCMOS33 } [get_ports { JC[9] }]; #IO_L21P_T3_DQS_35 Sch=jc[9]
#set_property -dict { PACKAGE_PIN E6 IOSTANDARD LVCMOS33 } [get_ports { JC[10] }]; #IO_L5P_T0_AD13P_35 Sch=jc[10]
##Pmod Header JD
#set_property -dict { PACKAGE_PIN H4 IOSTANDARD LVCMOS33 } [get_ports { JD[1] }]; #IO_L21N_T3_DQS_35 Sch=jd[1]
#set_property -dict { PACKAGE_PIN H1 IOSTANDARD LVCMOS33 } [get_ports { JD[2] }]; #IO_L17P_T2_35 Sch=jd[2]
#set_property -dict { PACKAGE_PIN G1 IOSTANDARD LVCMOS33 } [get_ports { JD[3] }]; #IO_L17N_T2_35 Sch=jd[3]
#set_property -dict { PACKAGE_PIN G3 IOSTANDARD LVCMOS33 } [get_ports { JD[4] }]; #IO_L20N_T3_35 Sch=jd[4]
#set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 } [get_ports { JD[7] }]; #IO_L15P_T2_DQS_35 Sch=jd[7]
#set_property -dict { PACKAGE_PIN G4 IOSTANDARD LVCMOS33 } [get_ports { JD[8] }]; #IO_L20P_T3_35 Sch=jd[8]
#set_property -dict { PACKAGE_PIN G2 IOSTANDARD LVCMOS33 } [get_ports { JD[9] }]; #IO_L15N_T2_DQS_35 Sch=jd[9]
#set_property -dict { PACKAGE_PIN F3 IOSTANDARD LVCMOS33 } [get_ports { JD[10] }]; #IO_L13N_T2_MRCC_35 Sch=jd[10]
##Pmod Header JXADC
#set_property -dict { PACKAGE_PIN A14 IOSTANDARD LVDS } [get_ports { XA_N[1] }]; #IO_L9N_T1_DQS_AD3N_15 Sch=xa_n[1]
#set_property -dict { PACKAGE_PIN A13 IOSTANDARD LVDS } [get_ports { XA_P[1] }]; #IO_L9P_T1_DQS_AD3P_15 Sch=xa_p[1]
#set_property -dict { PACKAGE_PIN A16 IOSTANDARD LVDS } [get_ports { XA_N[2] }]; #IO_L8N_T1_AD10N_15 Sch=xa_n[2]
#set_property -dict { PACKAGE_PIN A15 IOSTANDARD LVDS } [get_ports { XA_P[2] }]; #IO_L8P_T1_AD10P_15 Sch=xa_p[2]
#set_property -dict { PACKAGE_PIN B17 IOSTANDARD LVDS } [get_ports { XA_N[3] }]; #IO_L7N_T1_AD2N_15 Sch=xa_n[3]
#set_property -dict { PACKAGE_PIN B16 IOSTANDARD LVDS } [get_ports { XA_P[3] }]; #IO_L7P_T1_AD2P_15 Sch=xa_p[3]
#set_property -dict { PACKAGE_PIN A18 IOSTANDARD LVDS } [get_ports { XA_N[4] }]; #IO_L10N_T1_AD11N_15 Sch=xa_n[4]
#set_property -dict { PACKAGE_PIN B18 IOSTANDARD LVDS } [get_ports { XA_P[4] }]; #IO_L10P_T1_AD11P_15 Sch=xa_p[4]
##VGA Connector
#set_property -dict { PACKAGE_PIN A3 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[0] }]; #IO_L8N_T1_AD14N_35 Sch=vga_r[0]
#set_property -dict { PACKAGE_PIN B4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[1] }]; #IO_L7N_T1_AD6N_35 Sch=vga_r[1]
#set_property -dict { PACKAGE_PIN C5 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[2] }]; #IO_L1N_T0_AD4N_35 Sch=vga_r[2]
#set_property -dict { PACKAGE_PIN A4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[3] }]; #IO_L8P_T1_AD14P_35 Sch=vga_r[3]
#set_property -dict { PACKAGE_PIN C6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[0] }]; #IO_L1P_T0_AD4P_35 Sch=vga_g[0]
#set_property -dict { PACKAGE_PIN A5 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[1] }]; #IO_L3N_T0_DQS_AD5N_35 Sch=vga_g[1]
#set_property -dict { PACKAGE_PIN B6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[2] }]; #IO_L2N_T0_AD12N_35 Sch=vga_g[2]
#set_property -dict { PACKAGE_PIN A6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[3] }]; #IO_L3P_T0_DQS_AD5P_35 Sch=vga_g[3]
#set_property -dict { PACKAGE_PIN B7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[0] }]; #IO_L2P_T0_AD12P_35 Sch=vga_b[0]
#set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[1] }]; #IO_L4N_T0_35 Sch=vga_b[1]
#set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[2] }]; #IO_L6N_T0_VREF_35 Sch=vga_b[2]
#set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[3] }]; #IO_L4P_T0_35 Sch=vga_b[3]
#set_property -dict { PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports { VGA_HS }]; #IO_L4P_T0_15 Sch=vga_hs
#set_property -dict { PACKAGE_PIN B12 IOSTANDARD LVCMOS33 } [get_ports { VGA_VS }]; #IO_L3N_T0_DQS_AD1N_15 Sch=vga_vs
##Micro SD Connector
#set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { SD_RESET }]; #IO_L14P_T2_SRCC_35 Sch=sd_reset
#set_property -dict { PACKAGE_PIN A1 IOSTANDARD LVCMOS33 } [get_ports { SD_CD }]; #IO_L9N_T1_DQS_AD7N_35 Sch=sd_cd
#set_property -dict { PACKAGE_PIN B1 IOSTANDARD LVCMOS33 } [get_ports { SD_SCK }]; #IO_L9P_T1_DQS_AD7P_35 Sch=sd_sck
#set_property -dict { PACKAGE_PIN C1 IOSTANDARD LVCMOS33 } [get_ports { SD_CMD }]; #IO_L16N_T2_35 Sch=sd_cmd
#set_property -dict { PACKAGE_PIN C2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[0] }]; #IO_L16P_T2_35 Sch=sd_dat[0]
#set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[1] }]; #IO_L18N_T2_35 Sch=sd_dat[1]
#set_property -dict { PACKAGE_PIN F1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[2] }]; #IO_L18P_T2_35 Sch=sd_dat[2]
#set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[3] }]; #IO_L14N_T2_SRCC_35 Sch=sd_dat[3]
##Accelerometer
#set_property -dict { PACKAGE_PIN E15 IOSTANDARD LVCMOS33 } [get_ports { ACL_MISO }]; #IO_L11P_T1_SRCC_15 Sch=acl_miso
#set_property -dict { PACKAGE_PIN F14 IOSTANDARD LVCMOS33 } [get_ports { ACL_MOSI }]; #IO_L5N_T0_AD9N_15 Sch=acl_mosi
#set_property -dict { PACKAGE_PIN F15 IOSTANDARD LVCMOS33 } [get_ports { ACL_SCLK }]; #IO_L14P_T2_SRCC_15 Sch=acl_sclk
#set_property -dict { PACKAGE_PIN D15 IOSTANDARD LVCMOS33 } [get_ports { ACL_CSN }]; #IO_L12P_T1_MRCC_15 Sch=acl_csn
#set_property -dict { PACKAGE_PIN B13 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[1] }]; #IO_L2P_T0_AD8P_15 Sch=acl_int[1]
#set_property -dict { PACKAGE_PIN C16 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[2] }]; #IO_L20P_T3_A20_15 Sch=acl_int[2]
##Temperature Sensor
#set_property -dict { PACKAGE_PIN C14 IOSTANDARD LVCMOS33 } [get_ports { TMP_SCL }]; #IO_L1N_T0_AD0N_15 Sch=tmp_scl
#set_property -dict { PACKAGE_PIN C15 IOSTANDARD LVCMOS33 } [get_ports { TMP_SDA }]; #IO_L12N_T1_MRCC_15 Sch=tmp_sda
#set_property -dict { PACKAGE_PIN D13 IOSTANDARD LVCMOS33 } [get_ports { TMP_INT }]; #IO_L6N_T0_VREF_15 Sch=tmp_int
#set_property -dict { PACKAGE_PIN B14 IOSTANDARD LVCMOS33 } [get_ports { TMP_CT }]; #IO_L2N_T0_AD8N_15 Sch=tmp_ct
##Omnidirectional Microphone
#set_property -dict { PACKAGE_PIN J5 IOSTANDARD LVCMOS33 } [get_ports { M_CLK }]; #IO_25_35 Sch=m_clk
#set_property -dict { PACKAGE_PIN H5 IOSTANDARD LVCMOS33 } [get_ports { M_DATA }]; #IO_L24N_T3_35 Sch=m_data
#set_property -dict { PACKAGE_PIN F5 IOSTANDARD LVCMOS33 } [get_ports { M_LRSEL }]; #IO_0_35 Sch=m_lrsel
##PWM Audio Amplifier
#set_property -dict { PACKAGE_PIN A11 IOSTANDARD LVCMOS33 } [get_ports { AUD_PWM }]; #IO_L4N_T0_15 Sch=aud_pwm
#set_property -dict { PACKAGE_PIN D12 IOSTANDARD LVCMOS33 } [get_ports { AUD_SD }]; #IO_L6P_T0_15 Sch=aud_sd
##USB-RS232 Interface
#set_property -dict { PACKAGE_PIN C4 IOSTANDARD LVCMOS33 } [get_ports { UART_TXD_IN }]; #IO_L7P_T1_AD6P_35 Sch=uart_txd_in
#set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { UART_RXD_OUT }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { UART_CTS }]; #IO_L12N_T1_MRCC_35 Sch=uart_cts
#set_property -dict { PACKAGE_PIN E5 IOSTANDARD LVCMOS33 } [get_ports { UART_RTS }]; #IO_L5N_T0_AD13N_35 Sch=uart_rts
##USB HID (PS/2)
#set_property -dict { PACKAGE_PIN F4 IOSTANDARD LVCMOS33 } [get_ports { PS2_CLK }]; #IO_L13P_T2_MRCC_35 Sch=ps2_clk
#set_property -dict { PACKAGE_PIN B2 IOSTANDARD LVCMOS33 } [get_ports { PS2_DATA }]; #IO_L10N_T1_AD15N_35 Sch=ps2_data
##SMSC Ethernet PHY
#set_property -dict { PACKAGE_PIN C9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDC }]; #IO_L11P_T1_SRCC_16 Sch=eth_mdc
#set_property -dict { PACKAGE_PIN A9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDIO }]; #IO_L14N_T2_SRCC_16 Sch=eth_mdio
#set_property -dict { PACKAGE_PIN B3 IOSTANDARD LVCMOS33 } [get_ports { ETH_RSTN }]; #IO_L10P_T1_AD15P_35 Sch=eth_rstn
#set_property -dict { PACKAGE_PIN D9 IOSTANDARD LVCMOS33 } [get_ports { ETH_CRSDV }]; #IO_L6N_T0_VREF_16 Sch=eth_crsdv
#set_property -dict { PACKAGE_PIN C10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXERR }]; #IO_L13N_T2_MRCC_16 Sch=eth_rxerr
#set_property -dict { PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[0] }]; #IO_L13P_T2_MRCC_16 Sch=eth_rxd[0]
#set_property -dict { PACKAGE_PIN D10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[1] }]; #IO_L19N_T3_VREF_16 Sch=eth_rxd[1]
#set_property -dict { PACKAGE_PIN B9 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXEN }]; #IO_L11N_T1_SRCC_16 Sch=eth_txen
#set_property -dict { PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[0] }]; #IO_L14P_T2_SRCC_16 Sch=eth_txd[0]
#set_property -dict { PACKAGE_PIN A8 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[1] }]; #IO_L12N_T1_MRCC_16 Sch=eth_txd[1]
#set_property -dict { PACKAGE_PIN D5 IOSTANDARD LVCMOS33 } [get_ports { ETH_REFCLK }]; #IO_L11P_T1_SRCC_35 Sch=eth_refclk
#set_property -dict { PACKAGE_PIN B8 IOSTANDARD LVCMOS33 } [get_ports { ETH_INTN }]; #IO_L12P_T1_MRCC_16 Sch=eth_intn
##Quad SPI Flash
#set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[0] }]; #IO_L1P_T0_D00_MOSI_14 Sch=qspi_dq[0]
#set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[1] }]; #IO_L1N_T0_D01_DIN_14 Sch=qspi_dq[1]
#set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[2] }]; #IO_L2P_T0_D02_14 Sch=qspi_dq[2]
#set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[3] }]; #IO_L2N_T0_D03_14 Sch=qspi_dq[3]
#set_property -dict { PACKAGE_PIN L13 IOSTANDARD LVCMOS33 } [get_ports { QSPI_CSN }]; #IO_L6P_T0_FCS_B_14 Sch=qspi_csn
@@ -0,0 +1,47 @@
## cpu21_board_top.xdc —— Nexys4DDR (xc7a100tcsg324-1) 上板引脚
## 引脚编号取自 ConstraintsData/Nexys4DDR_Master.xdc(母版默认全部注释)。
## 只约束 cpu21_board_top 实际使用的端口。
## 100 MHz 板载时钟(分频器输入)
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk100 }]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk100 }]
## CPU 时钟:板上 100 MHz 经 CLK_DIV(=2) 分频到 50 MHz
## (与 cpu21_board_top.v 的 CLK_DIV 保持一致;改分频比时这里也要改。
## 乘除法已是多周期单元,不再需要 16 分频;实测 100 MHz 下剩余违例路径是
## EX 级分支判决/中断保存 → BPB 更新,50 MHz 则余量充足)
create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
-divide_by 2 [get_pins { clk_div_reg/Q }]
## 复位按键(CPU_RESETN,低有效)与"继续运行"按键(BTNC)
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rst_n }]
set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { go }]
## 拨码开关 SW[2:0] 作为三路中断源
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[0] }]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[1] }]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[2] }]
## 16 位 LED:显示 MMIO 写入值的低 16 位
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]
set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led[4] }]
set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led[5] }]
set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led[6] }]
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[7] }]
set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led[8] }]
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led[9] }]
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led[10] }]
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led[11] }]
set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led[12] }]
set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13] }]
set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }]
set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }]
## LED16_B:MMIO 写入指示(拉长约 0.34 s @50 MHz)
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }]
## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G,
## 但默认只用一个引脚,避免与 led_we 争用。
+13
View File
@@ -0,0 +1,13 @@
000012B7
00100313
0062A023
00131313
00008EB7
006EE463
0080006F
00100313
0000F3B7
00100E13
41C383B3
FE039EE3
FD9FF06F
+90
View File
@@ -0,0 +1,90 @@
00100293
FFF00313
FFF2B513
00001FB7
00AFA023
00133513
00001FB7
00AFA023
0FF00293
FFF00313
0062C533
00001FB7
00AFA023
00100293
01F00313
00629533
00001FB7
00AFA023
FFF00293
01C00313
0062D533
00001FB7
00AFA023
4062D533
00001FB7
00AFA023
4D200293
23700313
02628533
00001FB7
00AFA023
FFF00293
FFF00313
02629533
00001FB7
00AFA023
0262B533
00001FB7
00AFA023
FFF00293
00200313
0262A533
00001FB7
00AFA023
FFF00293
00506863
10100513
00001FB7
00AFA023
60100513
00001FB7
00AFA023
FFF00293
0002DA63
60200513
00001FB7
00AFA023
0100006F
10200513
00001FB7
00AFA023
FFF00293
0002F863
10300513
00001FB7
00AFA023
60300513
00001FB7
00AFA023
FFD00493
00900533
00001FB7
00AFA023
00148493
FE9058E3
00300293
00500533
00001FB7
00AFA023
FFF28293
00100393
FE72F6E3
00400493
00900533
00001FB7
00AFA023
FFF48493
FE9068E3
00A00893
00000073
+19
View File
@@ -0,0 +1,19 @@
123452B7
00500533
00001FB7
00AFA023
FFFFF2B7
00500533
00001FB7
00AFA023
800002B7
00500533
00001FB7
00AFA023
123452B7
67828293
00500533
00001FB7
00AFA023
00A00893
00000073
+16
View File
@@ -0,0 +1,16 @@
000012B7
12345337
67830313
0062A023
0002A503
00001FB7
00AFA023
FFF00393
0072A023
07F00E13
01C280A3
0002A503
00001FB7
00AFA023
00A00893
00000073
+64
View File
@@ -0,0 +1,64 @@
00700313
00600393
02730533
00001FB7
00AFA023
FFB00313
00400393
02730533
00001FB7
00AFA023
40000337
00400393
02731533
00001FB7
00AFA023
FFF00313
00200393
02733533
00001FB7
00AFA023
FFF00313
FFF00393
02732533
00001FB7
00AFA023
06400313
00700393
02735533
00001FB7
00AFA023
02737533
00001FB7
00AFA023
FFF00313
00200393
02735533
00001FB7
00AFA023
00003337
03930313
00000393
02735533
00001FB7
00AFA023
02737533
00001FB7
00AFA023
FFF00313
01000393
02737533
00001FB7
00AFA023
12345337
67830313
01000393
02730533
00001FB7
00AFA023
000013B7
02731533
00001FB7
00AFA023
00A00893
00000073
+62
View File
@@ -0,0 +1,62 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// MMIO:把外设寄存器映射到数据存储器地址空间。
//
// 目前实现 LED 数据寄存器(LED_ADDR 默认 0x0000_1000,位于 4 KB 数据 RAM 之外):
// * 写:sw / sb 命中该地址(按字对齐,即同一 32 位寄存器内的任意字节偏移
// 都算命中)时更新 LED 寄存器,led_valid_o 拉高一拍;sb 依据 wstrb_i 只
// 修改对应的字节通道(与 RAM 的字节写语义一致)。
// * 读:lw 命中该地址时 rdata_hit_o 拉高,顶层据此选择本模块的 rdata_o
// 而不是外部 data_rdata_i,从而支持把 LED 值读回。
//
// 以后要增加外设(拨码开关、UART 等)时只需在这里扩展地址译码,
// 处理器核心与流水线时序不需要改动。
// -----------------------------------------------------------------------------
module cpu21_mmio #(
parameter [31:0] LED_ADDR = 32'h0000_1000
) (
input wire clk,
input wire reset,
// MEM 级的访存请求(来自 EX/MEM 流水寄存器)
input wire mem_valid_i,
input wire mem_write_i,
input wire mem_read_i,
input wire [31:0] addr_i,
input wire [31:0] wdata_i,
input wire [ 3:0] wstrb_i,
// MMIO 读回
output wire [31:0] rdata_o,
output wire rdata_hit_o,
// LED 外设
output reg [31:0] led_data_o,
output reg led_valid_o
);
// 地址译码按“字”对齐:同一 32 位寄存器内的任意字节偏移都算命中,
// 具体修改哪几个字节由 wstrb_i 决定(sw 全字、sb 单字节)。
wire led_wr = mem_valid_i && mem_write_i && (addr_i[31:2] == LED_ADDR[31:2]);
wire led_rd = mem_valid_i && mem_read_i && (addr_i[31:2] == LED_ADDR[31:2]);
assign rdata_hit_o = led_rd;
assign rdata_o = led_data_o;
always @(posedge clk or posedge reset) begin
if (reset) begin
led_data_o <= 32'b0;
led_valid_o <= 1'b0;
end else begin
// led_valid_o 只拉高一拍,作为“本周期有 LED 写入”的指示。
led_valid_o <= 1'b0;
if (led_wr) begin
if (wstrb_i[0]) led_data_o[7:0] <= wdata_i[7:0];
if (wstrb_i[1]) led_data_o[15:8] <= wdata_i[15:8];
if (wstrb_i[2]) led_data_o[23:16] <= wdata_i[23:16];
if (wstrb_i[3]) led_data_o[31:24] <= wdata_i[31:24];
led_valid_o <= 1'b1;
end
end
end
endmodule
`default_nettype wire
@@ -2,11 +2,14 @@
`default_nettype none `default_nettype none
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ALU:纯组合运算单元。 // ALU:组合运算单元(只做加减/移位/逻辑/比较等单拍运算)。
// //
// result 为主结果;result2 为第二结果(乘法的无符号高 32 位 / DIVU 的余数), // 乘法与除法已移到 cpu21_riscv_muldiv.v(时序多周期单元),因为组合的 a*b / a%b
// 由译码器用 wb_sel=3 选取,用于无符号高位乘法 mulhu 与课程约定的 remu。 // 关键路径过长(综合后约 94 ns,100 MHz 下无法收敛)。因此本模块不再实现
// 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。 // ALU_MUL / ALU_DIVU:这两个操作码只作为“这是乘除法指令”的标识,对应结果由
// 顶层从 muldiv 的输出选取(result2 的 wb_sel=3 约定保持不变)。
//
// result 为主结果;result2 为第二结果,由译码器用 wb_sel=3 选取。
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
module cpu21_riscv_alu ( module cpu21_riscv_alu (
input wire [ 3:0] op, input wire [ 3:0] op,
@@ -31,37 +34,28 @@ module cpu21_riscv_alu (
localparam ALU_MULH = 4'd13; localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14; localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // 直接输出 B 端(供 LUI 使用) localparam ALU_PASSB = 4'd15; // 直接输出 B 端(供 LUI 使用)
reg [63:0] mult_result; // 无符号 32x32 乘积
reg signed [63:0] mult_ss; // 有符号 x 有符号
reg signed [63:0] mult_su; // 有符号 x 无符号
always @* begin always @* begin
mult_result = a * b; result = 32'b0;
mult_ss = $signed(a) * $signed(b);
mult_su = $signed(a) * $signed({32'b0, b});
result2 = 32'b0; result2 = 32'b0;
case (op) case (op)
ALU_SLL: result = a << b[4:0]; ALU_SLL: result = a << b[4:0];
ALU_SRA: result = $signed(a) >>> b[4:0]; ALU_SRA: result = $signed(a) >>> b[4:0];
ALU_SRL: result = a >> b[4:0]; ALU_SRL: result = a >> b[4:0];
ALU_MUL: begin // 乘法(ALU_MUL)与除法(ALU_DIVU)在本模块内不产生结果:
result = mult_result[31:0]; // 它们由 cpu21_riscv_muldiv 多周期单元完成,顶层用 ex_muldiv 选择其输出。
result2 = mult_result[63:32]; ALU_MUL: ;
end ALU_DIVU: ;
ALU_DIVU: begin
result = (b == 32'b0) ? 32'hffff_ffff : a / b;
result2 = (b == 32'b0) ? a : a % b;
end
ALU_ADD: result = a + b; ALU_ADD: result = a + b;
ALU_SUB: result = a - b; ALU_SUB: result = a - b;
ALU_AND: result = a & b; ALU_AND: result = a & b;
ALU_OR: result = a | b; ALU_OR: result = a | b;
ALU_XOR: result = a ^ b; ALU_XOR: result = a ^ b;
ALU_NOR: result = ~(a | b); ALU_NOR: result = ~(a | b);
ALU_MULH: result = mult_ss[63:32];
ALU_MULHSU: result = mult_su[63:32];
ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0; ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0;
ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0; ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0;
ALU_MULH: ;
ALU_MULHSU: ;
ALU_PASSB: result = b; ALU_PASSB: result = b;
default: result = 32'b0; default: result = 32'b0;
endcase endcase
@@ -17,6 +17,7 @@ module cpu21_riscv_decoder (
output reg [31:0] imm_o, output reg [31:0] imm_o,
output reg [ 3:0] alu_op_o, output reg [ 3:0] alu_op_o,
output reg [ 2:0] wb_sel_o, output reg [ 2:0] wb_sel_o,
output reg [ 2:0] md_op_o,
output wire [11:0] csr_addr_o, output wire [11:0] csr_addr_o,
output reg uses_rs1_o, output reg uses_rs1_o,
output reg uses_rs2_o, output reg uses_rs2_o,
@@ -68,6 +69,15 @@ module cpu21_riscv_decoder (
localparam ALU_MULHSU = 4'd14; localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // LUI:直接输出 B 端(立即数) localparam ALU_PASSB = 4'd15; // LUI:直接输出 B 端(立即数)
// 乘除法单元(cpu21_riscv_muldiv)的操作编码,MD_NONE 表示非乘除法指令。
localparam [2:0] MD_NONE = 3'd0;
localparam [2:0] MD_MUL = 3'd1;
localparam [2:0] MD_MULH = 3'd2;
localparam [2:0] MD_MULHSU = 3'd3;
localparam [2:0] MD_MULHU = 3'd4;
localparam [2:0] MD_DIVU = 3'd5;
localparam [2:0] MD_REMU = 3'd6;
wire [4:0] opcode = ir_i[6:2]; wire [4:0] opcode = ir_i[6:2];
wire [2:0] funct3 = ir_i[14:12]; wire [2:0] funct3 = ir_i[14:12];
wire [6:0] funct7 = ir_i[31:25]; wire [6:0] funct7 = ir_i[31:25];
@@ -83,6 +93,7 @@ module cpu21_riscv_decoder (
imm_o = {{20{ir_i[31]}}, ir_i[31:20]}; imm_o = {{20{ir_i[31]}}, ir_i[31:20]};
alu_op_o = ALU_ADD; alu_op_o = ALU_ADD;
wb_sel_o = 3'd0; wb_sel_o = 3'd0;
md_op_o = MD_NONE;
uses_rs1_o = 1'b0; uses_rs1_o = 1'b0;
uses_rs2_o = 1'b0; uses_rs2_o = 1'b0;
reg_write_o = 1'b0; reg_write_o = 1'b0;
@@ -111,19 +122,36 @@ module cpu21_riscv_decoder (
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1; uses_rs2_o = 1'b1;
reg_write_o = 1'b1; reg_write_o = 1'b1;
// M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,外加课程约定的 remu // M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,以及课程真值表里的
// (funct3=111 形式)。mulhu 与 remu 都取 ALU 的 result2(wb_sel=3)。 // divu(funct3=101,商)与 remu(funct3=111,余数)。它们都由多周期
// 乘除法单元完成,这里只给出操作类型 md_op_o;mulhu 与 remu 依旧用
// wb_sel=3 取 result2(高位乘积 / 余数)。
if (funct7 == 7'b0000001) begin if (funct7 == 7'b0000001) begin
case (funct3) case (funct3)
3'b000: alu_op_o = ALU_MUL; // mul:无符号乘积低 32 位 3'b000: begin // mul:低 32 位
3'b001: alu_op_o = ALU_MULH; // mulh:有符号乘积高 32 位
3'b010: alu_op_o = ALU_MULHSU; // mulhsu:有符号 x 无符号高 32 位
3'b011: begin // mulhu:无符号乘积高 32 位
alu_op_o = ALU_MUL; alu_op_o = ALU_MUL;
md_op_o = MD_MUL;
end
3'b001: begin // mulh:有符号高 32 位
alu_op_o = ALU_MULH;
md_op_o = MD_MULH;
end
3'b010: begin // mulhsu:有符号 x 无符号高 32 位
alu_op_o = ALU_MULHSU;
md_op_o = MD_MULHSU;
end
3'b011: begin // mulhu:无符号高 32 位
alu_op_o = ALU_MUL;
md_op_o = MD_MULHU;
wb_sel_o = 3'd3; wb_sel_o = 3'd3;
end end
3'b111: begin // remu(课程约定):取余数 3'b101: begin // divu:无符号商
alu_op_o = ALU_DIVU; alu_op_o = ALU_DIVU;
md_op_o = MD_DIVU;
end
3'b111: begin // remu:无符号余数
alu_op_o = ALU_DIVU;
md_op_o = MD_REMU;
wb_sel_o = 3'd3; wb_sel_o = 3'd3;
end end
default: reg_write_o = 1'b0; default: reg_write_o = 1'b0;
@@ -37,7 +37,10 @@ module cpu21_riscv_hazard_unit (
assign flush_younger_o = take_irq_i || uret_redirect_i || halt_event_i || redirect_valid_i; assign flush_younger_o = take_irq_i || uret_redirect_i || halt_event_i || redirect_valid_i;
assign pipeline_stall_o = load_use_hazard_o && !flush_younger_o && !halted_i; // 停顿条件里不再包含 flush_younger:冲刷时 valid 位会被清 0、PC/IF-ID 也由
// 冲刷分支(优先级高于停顿)处理,同时发生停顿只会多保持一拍 payload,
// 不影响正确性。这样可以避免把“冲刷”这条长路径引到 payload 的时钟使能上。
assign pipeline_stall_o = load_use_hazard_o && !halted_i;
endmodule endmodule
`default_nettype wire `default_nettype wire
+169
View File
@@ -0,0 +1,169 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 多周期乘除法单元(时序逻辑,32 拍迭代)
//
// 原来乘除法直接写在 ALU 里(组合的 a*b / a%b):综合后关键路径约 94 ns
// (325 级逻辑、286 个 CARRY4),100 MHz 下无法收敛,只能把板上 100 MHz 分频
// 到 6.25 MHz 再跑。这里改成移位迭代:
// * 乘法:32 拍移位加法得到 64 位乘积;有符号变体先取操作数绝对值,
// 迭代结束后按符号对 64 位乘积取负。
// * 除法:32 拍恢复余数法得到商与余数;除零按 RISC-V 约定处理
// (商为全 1、余数为被除数)。DIVU/REMU 都是无符号运算,不需要符号修正。
//
// 顶层的 EX 级会停留若干拍(往 EX/MEM 插气泡)等待 done_o 有效,然后照常写回。
// 迭代期间顶层会屏蔽中断,避免在结果未定型时提前推进流水线。
//
// op_i 编码(由 cpu21_riscv_decoder 给出,MD_NONE 表示不是乘除法指令):
// MD_MUL mul :无符号乘积低 32 位(有符号乘法的低 32 位与之相同)
// MD_MULH mulh :有符号 x 有符号,取高 32 位
// MD_MULHSU mulhsu :有符号 x 无符号,取高 32 位
// MD_MULHU mulhu :无符号 x 无符号,取高 32 位
// MD_DIVU divu :无符号商
// MD_REMU remu :无符号余数
//
// result_o / result2_o 的含义与原来的 ALU 保持一致:乘法时 result_o 为低 32 位、
// result2_o 为高 32 位;除法时 result_o 为商、result2_o 为余数。译码器用 wb_sel
// 决定写回哪一个(mulhu、remu 用 wb_sel=3 取 result2),因此顶层只需把这一对
// 结果接到原来的 EX 结果总线上,其余流水逻辑不用改。
// -----------------------------------------------------------------------------
module cpu21_riscv_muldiv (
input wire clk,
input wire reset, // 异步复位,高有效(与顶层一致)
input wire start_i, // 指令在 EX 级且是乘除法(电平,模块内部只在空闲时响应)
input wire [2:0] op_i, // 乘除法操作类型
input wire [31:0] a_i, // EX 级 src1
input wire [31:0] b_i, // EX 级 src2(ALU 的 B 端)
output wire busy_o, // 迭代中(不含结果定型那一拍)
output wire done_o, // 结果有效(单拍,此时顶层推进流水线)
output wire [31:0] result_o, // 主结果
output wire [31:0] result2_o // 第二结果(乘法高 32 位 / 除法余数)
);
localparam [2:0] MD_NONE = 3'd0;
localparam [2:0] MD_MUL = 3'd1;
localparam [2:0] MD_MULH = 3'd2;
localparam [2:0] MD_MULHSU = 3'd3;
localparam [2:0] MD_MULHU = 3'd4;
localparam [2:0] MD_DIVU = 3'd5;
localparam [2:0] MD_REMU = 3'd6;
localparam [1:0] S_IDLE = 2'd0;
localparam [1:0] S_RUN = 2'd1;
localparam [1:0] S_DONE = 2'd2;
reg [ 1:0] state_q;
reg [ 5:0] cnt_q; // 0..32:0..31 为迭代,32 为结果定型
reg [ 2:0] op_q;
reg is_div_q;
// 乘法数据通路:64 位被乘数(每拍左移)、32 位乘数(每拍右移)、64 位累加器。
reg [63:0] mcand_q;
reg [31:0] mplier_q;
reg [63:0] prod_q;
reg sign_q;
// 除法数据通路:32 位除数、32 位商/被除数移位寄存器、33 位余数。
reg [31:0] dvsr_q;
reg [31:0] quo_q;
reg [32:0] rem_q;
reg [31:0] dvd_q; // 原始被除数(除零时原样返回)
reg div_zero_q;
// 结果寄存器。
reg [31:0] res_q;
reg [31:0] res2_q;
assign busy_o = (state_q == S_RUN);
assign done_o = (state_q == S_DONE);
assign result_o = res_q;
assign result2_o = res2_q;
// 乘法操作数准备:按操作类型决定是否取绝对值,并给出结果符号。
wire a_signed = (op_i == MD_MULH) || (op_i == MD_MULHSU);
wire b_signed = (op_i == MD_MULH);
wire [31:0] a_mag = (a_signed && a_i[31]) ? (~a_i + 32'd1) : a_i;
wire [31:0] b_mag = (b_signed && b_i[31]) ? (~b_i + 32'd1) : b_i;
wire sign_in = ((op_i == MD_MULH) && (a_i[31] ^ b_i[31])) || ((op_i == MD_MULHSU) && a_i[31]);
// 除法的一步(恢复余数法):把被除数最高位移进余数,够减则减并把商位置 1。
wire [32:0] rem_shift = {rem_q[31:0], quo_q[31]};
wire rem_ge = (rem_shift >= {1'b0, dvsr_q});
wire [32:0] rem_next = rem_ge ? (rem_shift - {1'b0, dvsr_q}) : rem_shift;
wire [31:0] quo_next = {quo_q[30:0], rem_ge};
// 迭代结束时的符号修正:prod_mag 为无符号乘积,sign_q 为 1 时取负。
wire [63:0] prod_fix = sign_q ? (~prod_q + 64'd1) : prod_q;
always @(posedge clk or posedge reset) begin
if (reset) begin
state_q <= S_IDLE;
cnt_q <= 6'd0;
op_q <= MD_NONE;
is_div_q <= 1'b0;
mcand_q <= 64'd0;
mplier_q <= 32'd0;
prod_q <= 64'd0;
sign_q <= 1'b0;
dvsr_q <= 32'd0;
quo_q <= 32'd0;
rem_q <= 33'd0;
dvd_q <= 32'd0;
div_zero_q <= 1'b0;
res_q <= 32'd0;
res2_q <= 32'd0;
end else begin
case (state_q)
// 空闲:指令进入 EX 且是乘除法时装载操作数,下一拍开始迭代。
S_IDLE: begin
if (start_i) begin
state_q <= S_RUN;
cnt_q <= 6'd0;
op_q <= op_i;
is_div_q <= (op_i == MD_DIVU) || (op_i == MD_REMU);
// 乘法初始化(先取绝对值,符号留到最后修正)。
mcand_q <= {32'b0, a_mag};
mplier_q <= b_mag;
prod_q <= 64'd0;
sign_q <= sign_in;
// 除法初始化(无符号)。
dvsr_q <= b_i;
quo_q <= a_i;
rem_q <= 33'd0;
dvd_q <= a_i;
div_zero_q <= (b_i == 32'd0);
end
end
// 迭代:cnt 0..31 各走一步,32 拍后把结果定型。
S_RUN: begin
if (cnt_q == 6'd32) begin
state_q <= S_DONE;
if (is_div_q) begin
res_q <= div_zero_q ? 32'hffff_ffff : quo_q;
res2_q <= div_zero_q ? dvd_q : rem_q[31:0];
end else begin
res_q <= (op_q == MD_MULHU || op_q == MD_MUL) ? prod_fix[31:0] : prod_fix[63:32];
res2_q <= prod_fix[63:32];
end
end else begin
cnt_q <= cnt_q + 6'd1;
if (is_div_q) begin
rem_q <= rem_next;
quo_q <= quo_next;
end else begin
prod_q <= mplier_q[0] ? (prod_q + mcand_q) : prod_q;
mcand_q <= {mcand_q[62:0], 1'b0};
mplier_q <= {1'b0, mplier_q[31:1]};
end
end
end
// 结果有效一拍,顶层据此推进流水线;随后回到空闲。
default: state_q <= S_IDLE;
endcase
end
end
endmodule
`default_nettype wire
@@ -12,7 +12,7 @@ module cpu21_riscv_perf_counters (
input wire clk, input wire clk,
input wire reset, input wire reset,
input wire inc_cycle_i, // 未停机时周期数 +1 input wire inc_cycle_i, // 未停机时周期数 +1
input wire stall_i, // load-use 停顿 input wire stall_i, // load-use 停顿或多周期乘除法停留
input wire flush_i, // 冲刷年轻指令 input wire flush_i, // 冲刷年轻指令
input wire cond_taken_i, // EX 级条件分支实际跳转 input wire cond_taken_i, // EX 级条件分支实际跳转
input wire cond_valid_i, // EX 级条件分支有效 input wire cond_valid_i, // EX 级条件分支有效
@@ -22,13 +22,17 @@
// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定 // cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定
// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈 // cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈
// cpu21_riscv_perf_counters.v 性能计数器 // cpu21_riscv_perf_counters.v 性能计数器
// cpu21_mmio.v MMIO:LED 数据寄存器(可写、可读回)
// //
// 主要特性: // 主要特性:
// * EX 级前递(forwarding)与 load-use 冒险互锁; // * EX 级前递(forwarding)与 load-use 冒险互锁;
// * EX 级控制流裁决与流水线重定向; // * EX 级控制流裁决与流水线重定向;
// * 八表项全相联分支目标缓冲(BPB); // * 八表项全相联分支目标缓冲(BPB);
// * 三路采样的中断源,带优先级与可嵌套的 URET 返回; // * 三路采样的中断源,带优先级与可嵌套的 URET 返回;
// * 沿用原 ecall 约定:a7==34 时写出 LED 值,否则流水线等待 go_i。 // * LED 显示通过 MMIO 完成:向 LED_ADDR(默认 0x0000_1000)存储即写 LED,
// 也可以从该地址读回 LED 当前值;
// * ecall 仍用于停机:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
// 令流水线停机并等待 go_i。
// //
// 存储器接口约定: // 存储器接口约定:
// instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码, // instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码,
@@ -41,6 +45,8 @@ module cpu21_riscv_redirect_int_bpb #(
parameter IRQ2_VECTOR = 32'h0000_31e4, parameter IRQ2_VECTOR = 32'h0000_31e4,
parameter IRQ3_VECTOR = 32'h0000_3310, parameter IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4, parameter IRQ_STACK_DEPTH = 4,
// MMIO:LED 数据寄存器地址(默认 0x0000_1000,位于 4 KB 数据 RAM 之外)。
parameter [31:0] LED_ADDR = 32'h0000_1000,
// ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。 // ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。
parameter [31:0] USTATUS_INIT = 32'h0000_0001 parameter [31:0] USTATUS_INIT = 32'h0000_0001
) ( ) (
@@ -125,6 +131,7 @@ module cpu21_riscv_redirect_int_bpb #(
reg idex_mem_write_q; reg idex_mem_write_q;
reg idex_mem_byte_q; reg idex_mem_byte_q;
reg [2:0] idex_wb_sel_q; reg [2:0] idex_wb_sel_q;
reg [2:0] idex_md_op_q; // 乘除法操作类型(0=非乘除法)
reg [11:0] idex_csr_addr_q; reg [11:0] idex_csr_addr_q;
reg idex_branch_q; reg idex_branch_q;
reg idex_beq_q; reg idex_beq_q;
@@ -171,9 +178,7 @@ module cpu21_riscv_redirect_int_bpb #(
reg memwb_mem_to_reg_q; reg memwb_mem_to_reg_q;
reg [2:0] memwb_wb_sel_q; reg [2:0] memwb_wb_sel_q;
// LED 输出与停机标志。 // 停机标志(LED 寄存器已移入 cpu21_mmio.v)。
reg [31:0] led_data_q;
reg led_valid_q;
reg halted_q; reg halted_q;
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@@ -230,6 +235,7 @@ module cpu21_riscv_redirect_int_bpb #(
wire [31:0] d_imm; wire [31:0] d_imm;
wire [3:0] d_aluop; wire [3:0] d_aluop;
wire [2:0] d_wb_sel; wire [2:0] d_wb_sel;
wire [2:0] d_md_op;
wire [11:0] d_csr_addr; wire [11:0] d_csr_addr;
wire d_uses_rs1; wire d_uses_rs1;
wire d_uses_rs2; wire d_uses_rs2;
@@ -261,6 +267,7 @@ module cpu21_riscv_redirect_int_bpb #(
.imm_o (d_imm), .imm_o (d_imm),
.alu_op_o (d_aluop), .alu_op_o (d_aluop),
.wb_sel_o (d_wb_sel), .wb_sel_o (d_wb_sel),
.md_op_o (d_md_op),
.csr_addr_o (d_csr_addr), .csr_addr_o (d_csr_addr),
.uses_rs1_o (d_uses_rs1), .uses_rs1_o (d_uses_rs1),
.uses_rs2_o (d_uses_rs2), .uses_rs2_o (d_uses_rs2),
@@ -285,11 +292,10 @@ module cpu21_riscv_redirect_int_bpb #(
.csr_write_o (d_csr_write) .csr_write_o (d_csr_write)
); );
// 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4,3=MUL 高位/余数, // 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4(已存在 alu_result 里),
// 4=CSR 旧值。 // 3=MUL 高位/余数,4=CSR 旧值。
wire [31:0] wb_value; wire [31:0] wb_value;
assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q : assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q :
(memwb_wb_sel_q == 3'd2) ? (memwb_pc_q + 32'd4) :
(memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q : (memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q :
(memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q : (memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q :
memwb_alu_result_q; memwb_alu_result_q;
@@ -325,9 +331,10 @@ module cpu21_riscv_redirect_int_bpb #(
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// EX 级:前递网络、ALU 运算、存储对齐与控制流裁决 // EX 级:前递网络、ALU 运算、存储对齐与控制流裁决
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// EX/MEM 级要前递的值(按 wb_sel 选取)。 // EX/MEM 级要前递的值(按 wb_sel 选取)。JAL/JALR 的链接地址(PC+4)已在
// EX 级预先算好并存入 exmem_alu_result_q,所以这里 wb_sel=2 直接取该寄存器,
// 前递路径上不再有 PC+4 加法器(100 MHz 下这是最长路径之一)。
wire [31:0] exmem_forward_value = wire [31:0] exmem_forward_value =
(exmem_wb_sel_q == 3'd2) ? (exmem_pc_q + 32'd4) :
(exmem_wb_sel_q == 3'd3) ? exmem_alu_result2_q : (exmem_wb_sel_q == 3'd3) ? exmem_alu_result2_q :
(exmem_wb_sel_q == 3'd4) ? exmem_csr_old_q : (exmem_wb_sel_q == 3'd4) ? exmem_csr_old_q :
exmem_alu_result_q; exmem_alu_result_q;
@@ -364,7 +371,7 @@ module cpu21_riscv_redirect_int_bpb #(
(idex_csr_addr_q == 12'h004) ? ustatus_value : (idex_csr_addr_q == 12'h004) ? ustatus_value :
(idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0; (idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0;
// ALU 实例(纯组合逻辑)。 // ALU 实例(只做单拍运算;乘除法见下面的多周期单元)。
cpu21_riscv_alu u_ex_alu ( cpu21_riscv_alu u_ex_alu (
.op (idex_aluop_q), .op (idex_aluop_q),
.a (ex_src1), .a (ex_src1),
@@ -373,6 +380,42 @@ module cpu21_riscv_redirect_int_bpb #(
.result2(ex_alu_result2) .result2(ex_alu_result2)
); );
// -------------------------------------------------------------------------
// 多周期乘除法单元:EX 级停留若干拍等待结果,不再把 32x32 乘法/除法
// 放在组合路径上(原来这部分的组合延时约 94 ns,100 MHz 下无法收敛)。
// 指令在 EX 级期间 ex_md_stall 有效:ID/EX 保持不变、EX/MEM 插气泡、
// 取指/译码冻结;迭代结束(md_done)后照常推进。
// -------------------------------------------------------------------------
wire md_busy;
wire md_done;
wire [31:0] md_result;
wire [31:0] md_result2;
wire ex_is_muldiv = idex_valid_q && (idex_md_op_q != 3'd0);
wire ex_md_stall = ex_is_muldiv && !md_done;
cpu21_riscv_muldiv u_muldiv (
.clk (clk),
.reset (reset),
.start_i (ex_is_muldiv),
.op_i (idex_md_op_q),
.a_i (ex_src1),
.b_i (ex_alu_b),
.busy_o (md_busy),
.done_o (md_done),
.result_o (md_result),
.result2_o(md_result2)
);
// EX 级最终结果:乘除法取多周期单元的输出;JAL/JALR(wb_sel=2)的链接地址
// PC+4 在这里算好随流水寄存器传下去,避免“写回值 → 前递 → 停机判定 →
// 流水寄存器时钟使能”这条组合路径上出现 32 位加法器。
wire [31:0] ex_result =
ex_is_muldiv ? md_result :
(idex_wb_sel_q == 3'd2) ? (idex_pc_q + 32'd4) :
ex_alu_result;
wire [31:0] ex_result2 = ex_is_muldiv ? md_result2 : ex_alu_result2;
// 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。 // 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。
cpu21_riscv_branch_unit u_branch_unit ( cpu21_riscv_branch_unit u_branch_unit (
.valid_i (idex_valid_q), .valid_i (idex_valid_q),
@@ -411,16 +454,49 @@ module cpu21_riscv_redirect_int_bpb #(
.store_wstrb_o(ex_store_wstrb) .store_wstrb_o(ex_store_wstrb)
); );
// ecall 约定:a7==34 时输出 LED;否则令处理器停机,等待 go_i 重新启动。 // -------------------------------------------------------------------------
wire ex_led_event = idex_valid_q && idex_ecall_q && (ex_src1 == 32'd34); // MMIO:LED 等外设映射到数据地址空间,在 MEM 级做地址译码。
// -------------------------------------------------------------------------
wire [31:0] mmio_rdata;
wire mmio_rdata_hit;
cpu21_mmio #(
.LED_ADDR(LED_ADDR)
) u_mmio (
.clk (clk),
.reset (reset),
.mem_valid_i(exmem_valid_q),
.mem_write_i(exmem_mem_write_q),
.mem_read_i (exmem_mem_to_reg_q),
.addr_i (exmem_alu_result_q),
.wdata_i (exmem_write_data_q),
.wstrb_i (exmem_wstrb_q),
.rdata_o (mmio_rdata),
.rdata_hit_o(mmio_rdata_hit),
.led_data_o (led_data_o),
.led_valid_o(led_valid_o)
);
// 访存读数据:命中 MMIO 时用外设数据,否则用外部存储器数据。
wire [31:0] mem_rdata = mmio_rdata_hit ? mmio_rdata : data_rdata_i;
// ecall 约定:LED 显示已改由 MMIO 存储完成(见上方的 cpu21_mmio 例化)。
// a7==34 的历史“显示”调用保留为无操作;其余(例如 a7=10)令处理器停机,
// 等待 go_i 重新启动。
wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34); wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34);
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 中断 / CSR / 嵌套返回栈 // 中断 / CSR / 嵌套返回栈
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
wire ex_uret_redirect = idex_valid_q && idex_uret_q; wire ex_uret_redirect = idex_valid_q && idex_uret_q;
// EX 级正在重定向/URET/停机时不允许响应中断。 // EX 级正在 URET,或乘除法还没算完时,不响应中断:前者与中断同时发生会丢掉
wire ex_block_irq = ex_redirect_valid || ex_uret_redirect || ex_halt_event; // URET 的返回地址,后者会在结果未定型时推进流水线,向 rd 写回错误值。
// 这里不再包含 ex_redirect_valid / ex_halt_event:它们本身就会让
// flush_younger 有效(流水线照常被冲刷),而中断优先于重定向时返回地址由
// irq_save_pc 给出(已正确跳转的分支取目标地址)。这样就把
// “分支判决 → 中断控制器 → flush → 流水寄存器控制”这条 100 MHz 下的
// 关键路径切断了(原来要绕经中断控制器再回到 flush)。
wire ex_block_irq = ex_uret_redirect || ex_md_stall;
// 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确 // 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确
// 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中 // 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中
// 保存的就是尚未执行的最老指令。 // 保存的就是尚未执行的最老指令。
@@ -495,9 +571,7 @@ module cpu21_riscv_redirect_int_bpb #(
assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0; assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0;
assign data_we_o = |data_wstrb_o; assign data_we_o = |data_wstrb_o;
// LED 输出与流水线各级 PC/指令回显(用于调试与波形观察)。 // LED 输出由 cpu21_mmio 驱动,这里只保留流水线各级 PC/指令回显。
assign led_data_o = led_data_q;
assign led_valid_o = led_valid_q;
assign if_pc_o = pc_q; assign if_pc_o = pc_q;
assign id_pc_o = ifid_valid_q ? ifid_pc_q : 32'b0; assign id_pc_o = ifid_valid_q ? ifid_pc_q : 32'b0;
assign ex_pc_o = idex_valid_q ? idex_pc_q : 32'b0; assign ex_pc_o = idex_valid_q ? idex_pc_q : 32'b0;
@@ -552,6 +626,7 @@ module cpu21_riscv_redirect_int_bpb #(
idex_mem_write_q <= 1'b0; idex_mem_write_q <= 1'b0;
idex_mem_byte_q <= 1'b0; idex_mem_byte_q <= 1'b0;
idex_wb_sel_q <= 3'b0; idex_wb_sel_q <= 3'b0;
idex_md_op_q <= 3'b0;
idex_csr_addr_q <= 12'b0; idex_csr_addr_q <= 12'b0;
idex_branch_q <= 1'b0; idex_branch_q <= 1'b0;
idex_beq_q <= 1'b0; idex_beq_q <= 1'b0;
@@ -596,16 +671,8 @@ module cpu21_riscv_redirect_int_bpb #(
memwb_mem_to_reg_q <= 1'b0; memwb_mem_to_reg_q <= 1'b0;
memwb_wb_sel_q <= 3'b0; memwb_wb_sel_q <= 3'b0;
led_data_q <= 32'b0;
led_valid_q <= 1'b0;
halted_q <= 1'b0; halted_q <= 1'b0;
end else begin end else begin
led_valid_q <= 1'b0;
if (ex_led_event) begin
led_data_q <= ex_src2;
led_valid_q <= 1'b1;
end
if (go_i) halted_q <= 1'b0; if (go_i) halted_q <= 1'b0;
if (ex_halt_event) halted_q <= 1'b1; if (ex_halt_event) halted_q <= 1'b1;
@@ -616,18 +683,34 @@ module cpu21_riscv_redirect_int_bpb #(
memwb_alu_result_q <= exmem_alu_result_q; memwb_alu_result_q <= exmem_alu_result_q;
memwb_alu_result2_q <= exmem_alu_result2_q; memwb_alu_result2_q <= exmem_alu_result2_q;
memwb_csr_old_q <= exmem_csr_old_q; memwb_csr_old_q <= exmem_csr_old_q;
memwb_mem_data_q <= data_rdata_i; memwb_mem_data_q <= mem_rdata;
memwb_rd_q <= exmem_rd_q; memwb_rd_q <= exmem_rd_q;
memwb_reg_write_q <= exmem_reg_write_q; memwb_reg_write_q <= exmem_reg_write_q;
memwb_mem_to_reg_q <= exmem_mem_to_reg_q; memwb_mem_to_reg_q <= exmem_mem_to_reg_q;
memwb_wb_sel_q <= exmem_wb_sel_q; memwb_wb_sel_q <= exmem_wb_sel_q;
// EX/MEM 流水寄存器捕获。 // EX/MEM 流水寄存器捕获。乘除法迭代期间(ex_md_stall)往 EX/MEM 插气泡,
// 让乘除法指令继续占用 EX,结果在 md_done 那一拍才写入 EX/MEM。
if (ex_md_stall) begin
exmem_valid_q <= 1'b0;
exmem_pc_q <= 32'b0;
exmem_ir_q <= 32'b0;
exmem_alu_result_q <= 32'b0;
exmem_alu_result2_q <= 32'b0;
exmem_csr_old_q <= 32'b0;
exmem_write_data_q <= 32'b0;
exmem_rd_q <= 5'b0;
exmem_reg_write_q <= 1'b0;
exmem_mem_to_reg_q <= 1'b0;
exmem_mem_write_q <= 1'b0;
exmem_wstrb_q <= 4'b0;
exmem_wb_sel_q <= 3'b0;
end else begin
exmem_valid_q <= idex_valid_q; exmem_valid_q <= idex_valid_q;
exmem_pc_q <= idex_pc_q; exmem_pc_q <= idex_pc_q;
exmem_ir_q <= idex_ir_q; exmem_ir_q <= idex_ir_q;
exmem_alu_result_q <= ex_alu_result; exmem_alu_result_q <= ex_result;
exmem_alu_result2_q <= ex_alu_result2; exmem_alu_result2_q <= ex_result2;
exmem_csr_old_q <= ex_csr_old_value; exmem_csr_old_q <= ex_csr_old_value;
exmem_write_data_q <= ex_store_data; exmem_write_data_q <= ex_store_data;
exmem_rd_q <= idex_rd_q; exmem_rd_q <= idex_rd_q;
@@ -636,15 +719,18 @@ module cpu21_riscv_redirect_int_bpb #(
exmem_mem_write_q <= idex_mem_write_q; exmem_mem_write_q <= idex_mem_write_q;
exmem_wstrb_q <= ex_store_wstrb; exmem_wstrb_q <= ex_store_wstrb;
exmem_wb_sel_q <= idex_wb_sel_q; exmem_wb_sel_q <= idex_wb_sel_q;
end
// 控制优先级:中断 > URET > ecall 停机 > 分支重定向 > // ---------------------------------------------------------------------
// load-use 停顿 > 正常流水推进。 // ID/EX 装载。
if (flush_younger || pipeline_stall || halted_q) begin //
idex_valid_q <= 1'b0; // payload(PC/IR/控制位/预测位)只在 load-use 停顿、乘除法迭代、停机时
idex_pred_taken_q <= 1'b0; // 保持(时钟使能与“冲刷”无关),其余情况一律装载;只有 valid 位受冲刷
idex_pred_target_q <= 32'b0; // 控制,冲刷时清 0。这样“EX 级组合判决(分支/中断)→ 流水寄存器时钟
end else begin // 使能”这条 100 MHz 下最长的路径就只剩下一个触发器(valid),payload
idex_valid_q <= ifid_valid_q; // 即使装进错误路径的数据也无害——所有使用者都以 idex_valid_q 为前提。
// ---------------------------------------------------------------------
if (!(pipeline_stall || halted_q || ex_md_stall)) begin
idex_pc_q <= ifid_pc_q; idex_pc_q <= ifid_pc_q;
idex_ir_q <= ifid_ir_q; idex_ir_q <= ifid_ir_q;
idex_rs1_value_q <= d_rs1_value; idex_rs1_value_q <= d_rs1_value;
@@ -660,6 +746,7 @@ module cpu21_riscv_redirect_int_bpb #(
idex_mem_write_q <= d_mem_write; idex_mem_write_q <= d_mem_write;
idex_mem_byte_q <= d_mem_byte; idex_mem_byte_q <= d_mem_byte;
idex_wb_sel_q <= d_wb_sel; idex_wb_sel_q <= d_wb_sel;
idex_md_op_q <= d_md_op;
idex_csr_addr_q <= d_csr_addr; idex_csr_addr_q <= d_csr_addr;
idex_branch_q <= d_branch; idex_branch_q <= d_branch;
idex_beq_q <= d_beq; idex_beq_q <= d_beq;
@@ -679,6 +766,16 @@ module cpu21_riscv_redirect_int_bpb #(
idex_pred_target_q <= ifid_pred_target_q; idex_pred_target_q <= ifid_pred_target_q;
end end
if (halted_q || ex_md_stall) begin
// 停机 / 乘除法迭代:valid 保持不变(指令继续占用 EX)。
end else if (pipeline_stall || flush_younger) begin
// load-use 停顿:load 已经进 EX/MEM,这里给 EX 装一个气泡(payload 保持
// 原值,valid=0 时无害);冲刷同理。
idex_valid_q <= 1'b0;
end else begin
idex_valid_q <= ifid_valid_q;
end
if (take_irq) begin if (take_irq) begin
pc_q <= irq_vector; pc_q <= irq_vector;
ifid_valid_q <= 1'b0; ifid_valid_q <= 1'b0;
@@ -699,7 +796,7 @@ module cpu21_riscv_redirect_int_bpb #(
ifid_valid_q <= 1'b0; ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0; ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0; ifid_pred_target_q <= 32'b0;
end else if (pipeline_stall) begin end else if (pipeline_stall || ex_md_stall) begin
pc_q <= pc_q; pc_q <= pc_q;
ifid_valid_q <= ifid_valid_q; ifid_valid_q <= ifid_valid_q;
ifid_pc_q <= ifid_pc_q; ifid_pc_q <= ifid_pc_q;
@@ -727,7 +824,7 @@ module cpu21_riscv_redirect_int_bpb #(
.clk (clk), .clk (clk),
.reset (reset), .reset (reset),
.inc_cycle_i (!halted_q), .inc_cycle_i (!halted_q),
.stall_i (pipeline_stall), .stall_i (pipeline_stall || ex_md_stall),
.flush_i (flush_younger), .flush_i (flush_younger),
.cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken), .cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken),
.cond_valid_i (idex_valid_q && idex_branch_q), .cond_valid_i (idex_valid_q && idex_branch_q),
+56
View File
@@ -0,0 +1,56 @@
`timescale 1ns / 1ps
// -----------------------------------------------------------------------------
// cpu21_board_top 的仿真测试(不上板,纯 RTL 仿真)
//
// 检查内容:
// 1) 复位释放后 CPU 能从片内 ROM 取指(ROM 由 cpu21_board_rom.vh 初始化);
// 2) 程序通过 MMIO 写 LED,板载 LED 端口上能看到 16 位跑马灯序列。
//
// 默认的 board_demo 程序带约 20 ms 软件延时,仿真只需确认第一次写入;
// 若要完整验证跑马灯序列,可生成一份短延时的 ROM 并用 -d BOARD_ROM_FAST 编译:
// xvlog -d BOARD_ROM_FAST board/cpu21_board_top.v <rtl/*.v> testbench/tb_board_top.v
// -----------------------------------------------------------------------------
module tb_board_top;
reg clk100 = 1'b0;
reg rst_n = 1'b0;
reg go = 1'b0;
reg [2:0] irq_sw = 3'b000;
wire [15:0] led;
wire led_we;
always #5 clk100 = ~clk100; // 100 MHz
cpu21_board_top #(
.LED_ADDR(32'h0000_1000)
) dut (
.clk100(clk100),
.rst_n (rst_n),
.go (go),
.irq_sw(irq_sw),
.led (led),
.led_we(led_we)
);
integer changes = 0;
reg [15:0] led_prev = 16'hxxxx;
always @(posedge clk100) begin
if (led !== led_prev) begin
changes = changes + 1;
led_prev = led;
$display("t=%t LED=%04h led_we=%b", $time, led, led_we);
end
end
initial begin
$timeformat(-9, 3, " ns", 12); // %t 按 ns 显示(默认按精度 ps 显示)
$display("NOTE: 复位后观察 LED(默认程序首次写入应为 0001)");
repeat (10) @(posedge clk100);
rst_n = 1'b1;
repeat (30000) @(posedge clk100); // 300 us(CPU 时钟为 clk100/2 = 50 MHz)
$display("FINAL: LED 变化次数=%0d 当前 LED=%04h", changes, led);
$finish;
end
endmodule
@@ -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 // vvp cpu21_sim
// 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。 // 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。
// //
@@ -8,9 +8,13 @@
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 // <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径,并报告实际命中的那个。显式给出的路径始终优先: // 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp) // vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim) // xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
module tb_cpu21_riscv_redirect_int_bpb #( module tb_cpu21_riscv_redirect_int_bpb #(
parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex" parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex"
// risc-v-benchmark_ccab.hex // risc-v-benchmark_ccab.hex
@@ -152,7 +156,8 @@ module tb_cpu21_riscv_redirect_int_bpb #(
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end end
// 带字节使能的同步写模型。 // 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) 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[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
@@ -226,10 +231,15 @@ module tb_cpu21_riscv_redirect_int_bpb #(
rom_loaded = 1'b0; rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded); 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(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_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../testbench/rom/", 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_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) if (!rom_loaded)
$display( $display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
@@ -1,9 +1,9 @@
// 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架, // 仿真顶层: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 // vvp cpu21_sim
// 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。 // 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。
// //
@@ -11,9 +11,13 @@
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP // 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在 // (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选 // <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径,并报告实际命中的那个。显式给出的路径始终优先: // 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp) // vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim) // 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 #( module tb_no_intr #(
parameter ROM_FILE = "risc-v-benchmark_ccab.hex" parameter ROM_FILE = "risc-v-benchmark_ccab.hex"
// risc-v-benchmark_ccab.hex // risc-v-benchmark_ccab.hex
@@ -155,7 +159,8 @@ module tb_no_intr #(
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]]; if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end end
// 带字节使能的同步写模型。 // 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) 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[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
@@ -229,10 +234,15 @@ module tb_no_intr #(
rom_loaded = 1'b0; rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded); 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(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_FILE}, rom_loaded); if (!rom_loaded) load_rom_image({"../testbench/rom/", 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_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) if (!rom_loaded)
$display( $display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).", "WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""把 $readmemh 格式的 hex 文件转换成 Verilog include 文件,供综合时初始化 ROM。
用法: python tools/hex2vh.py <input.hex> <output.vh> [ROM_WORDS]
输出内容形如(每行一条赋值,未用到的地址填 NOP 0x00000013):
rom[0] = 32'h00001037;
rom[1] = 32'h00100093;
配套 RTL 用法(见 srcs/board/cpu21_board_top.v,两者同目录,所以 include 能直接解析):
initial begin
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充
`include "cpu21_board_rom.vh"
end
这样 ROM 内容直接跟着 RTL 走,不依赖综合时的当前工作目录,
避免了 $readmemh 相对路径在 Vivado 综合流程里找不到文件(且只报 warning)的坑。
"""
import sys
NOP = 0x00000013
def main():
src, dst = sys.argv[1], sys.argv[2]
words_total = int(sys.argv[3]) if len(sys.argv) > 3 else 1024
words = []
with open(src, encoding="ascii") as f:
for line in f:
line = line.split("//")[0].strip()
if not line:
continue
words.append(int(line, 16))
if 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))
)
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))
print("已生成 %s(%d 个字,源程序 %d 个字)" % (dst, words_total, len(words)))
main()
+109
View File
@@ -0,0 +1,109 @@
# ============================================================================
# cpu21-riscv 回归脚本(Vivado xsim)
#
# 在工程目录 pipelined-cpu/ 下运行:
# powershell -ExecutionPolicy Bypass -File tools\run_regress.ps1
#
# 做四件事:
# 1) 逐个跑指令测试:muldiv_test / isa_test / lui_test / mmio_test,
# 把测试平台打印的 LED 序列与 programs/<名字>.expected 逐值比对;
# 2) 跑两个流水基线(默认 ROM):tb_no_intr、tb_cpu21_riscv_redirect_int_bpb,
# 只打印 FINAL 统计行,便于和已知基线核对;
# 3) 跑板级顶层(-d BOARD_ROM_FAST),检查跑马灯 LED 序列;
# 4) 最后汇总 PASS/FAIL 并按失败数设置退出码。
#
# 编译与仿真都放在 $env:TEMP\cpu21_regress 下,不会往工程目录写中间文件。
# 需要 xvlog / xelab / xsim 在 PATH 上(Vivado 安装目录的 bin)。
# ============================================================================
$ErrorActionPreference = 'Stop'
$proj = Split-Path -Parent $PSScriptRoot # tools/ 的上一级就是工程目录
$projF = ($proj -replace '\\', '/') # Verilog 字符串里要用正斜杠
$sim = Join-Path $env:TEMP 'cpu21_regress'
$enc = New-Object System.Text.UTF8Encoding($false)
foreach ($tool in 'xvlog', 'xelab', 'xsim') {
if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) {
Write-Output "ERROR: 找不到 $tool,请把 Vivado 的 bin 目录加到 PATH"
exit 1
}
}
New-Item -ItemType Directory -Force $sim | Out-Null
Set-Content -Path "$sim\run.tcl" -Value @('run all', 'quit') -Encoding ascii
$RTL = @(Get-ChildItem "$proj\srcs\rtl\*.v" | ForEach-Object FullName)
$fail = 0
# TB 里的 ROM_FILE 参数默认是文件名,这里替换成绝对路径(PowerShell 传参有坑,
# 直接改副本最稳)
function Sim-Top {
param($Top, $TbFile, $DefaultRom, $RealRom, $Snap)
$txt = [System.IO.File]::ReadAllText($TbFile, $enc)
$new = $txt.Replace('"' + $DefaultRom + '"', '"' + $projF + '/srcs/rom/' + $RealRom + '"')
[System.IO.File]::WriteAllText("$sim\$Snap.v", $new, $enc)
Remove-Item -Recurse -Force "$sim\xsim.dir" -ErrorAction SilentlyContinue
& xvlog @RTL "$sim\$Snap.v" 2>&1 | Out-File -Encoding ascii "$sim\xvlog_$Snap.txt"
& xelab -top $Top -snapshot $Snap 2>&1 | Out-File -Encoding ascii "$sim\xelab_$Snap.txt"
& xsim $Snap -tclbatch run.tcl 2>&1 | Out-File -Encoding ascii "$sim\run_$Snap.txt"
$err = Select-String -Path "$sim\xvlog_$Snap.txt", "$sim\xelab_$Snap.txt" -Pattern 'ERROR' |
Select-Object -First 1 -ExpandProperty Line
if ($err) { Write-Output "!! $Snap : $err"; $script:fail++ }
}
Push-Location $sim
try {
Write-Output '===== 1) 指令测试(tb_no_intr,与 programs/*.expected 比对)====='
foreach ($t in 'muldiv_test', 'isa_test', 'lui_test', 'mmio_test') {
Sim-Top -Top tb_no_intr -TbFile "$proj\srcs\tb\tb_no_intr.v" `
-DefaultRom 'risc-v-benchmark_ccab.hex' -RealRom "$t.hex" -Snap "sim_$t"
$log = "$sim\run_sim_$t.txt"
$obs = @(Select-String -Path $log -Pattern 'LEDv=1 LED=([0-9a-fA-F]{8})' -AllMatches |
ForEach-Object { $_.Matches[0].Groups[1].Value.ToLower() })
$exp = @(Get-Content "$proj\programs\$t.expected" |
ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ -ne '' })
if (($obs -join ',') -eq ($exp -join ',')) {
Write-Output (" {0,-13} PASS ({1} 个 LED 值)" -f $t, $obs.Count)
} else {
Write-Output (" {0,-13} FAIL 期望 {1} / 观测 {2}" -f $t, $exp.Count, $obs.Count)
Write-Output (" 期望: " + ($exp -join ' '))
Write-Output (" 观测: " + ($obs -join ' '))
$fail++
}
Write-Output (' ' + (Select-String -Path $log -Pattern 'FINAL:' |
Select-Object -First 1 -ExpandProperty Line))
}
Write-Output '===== 2) 流水基线(默认 ROM)====='
Sim-Top -Top tb_no_intr -TbFile "$proj\srcs\tb\tb_no_intr.v" `
-DefaultRom 'risc-v-benchmark_ccab.hex' -RealRom 'risc-v-benchmark_ccab.hex' -Snap 'base_no_intr'
Write-Output (' tb_no_intr : ' + (Select-String -Path "$sim\run_base_no_intr.txt" -Pattern 'FINAL:' |
Select-Object -First 1 -ExpandProperty Line))
Sim-Top -Top tb_cpu21_riscv_redirect_int_bpb -TbFile "$proj\srcs\tb\tb_cpu21_riscv_redirect_int_bpb.v" `
-DefaultRom 'cpu21_riscv_redirect_int_bpb_rom.hex' -RealRom 'cpu21_riscv_redirect_int_bpb_rom.hex' `
-Snap 'base_intr'
Write-Output (' tb_intr : ' + (Select-String -Path "$sim\run_base_intr.txt" -Pattern 'FINAL:' |
Select-Object -First 1 -ExpandProperty Line))
Write-Output '===== 3) 板级顶层(-d BOARD_ROM_FAST,跑马灯)====='
Remove-Item -Recurse -Force "$sim\xsim.dir" -ErrorAction SilentlyContinue
& xvlog -d BOARD_ROM_FAST @RTL "$proj\srcs\board\cpu21_board_top.v" "$proj\srcs\tb\tb_board_top.v" 2>&1 |
Out-File -Encoding ascii "$sim\xvlog_board.txt"
& xelab -top tb_board_top -snapshot board -d BOARD_ROM_FAST 2>&1 | Out-File -Encoding ascii "$sim\xelab_board.txt"
& xsim board -tclbatch run.tcl 2>&1 | Out-File -Encoding ascii "$sim\run_board.txt"
$seq = @(Select-String -Path "$sim\run_board.txt" -Pattern 'LED=([0-9a-f]{4})' -AllMatches |
ForEach-Object { $_.Matches[0].Groups[1].Value })
Write-Output (' 前 18 个 LED: ' + (($seq[0..17]) -join ' '))
Write-Output (" 变化次数: " + $seq.Count)
if (($seq[0..17] -join ' ') -ne '0000 0001 0002 0004 0008 0010 0020 0040 0080 0100 0200 0400 0800 1000 2000 4000 8000 0001') {
Write-Output ' FAIL:跑马灯序列与预期不符'; $fail++
} else {
Write-Output ' PASS'
}
} finally {
Pop-Location
}
Write-Output ''
if ($fail -eq 0) { Write-Output '全部通过 (所有回归 PASS)' } else { Write-Output "有 $fail 项失败" }
exit $fail
@@ -6,9 +6,10 @@
支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、 支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、
mul/mulh/mulhsu/mulhu、 mul/mulh/mulhsu/mulhu、
addi/slti/sltiu/xori/ori/andi、slli/srli/srai、 addi/slti/sltiu/xori/ori/andi、slli/srli/srai、
lw/sw、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释 lw/sw/sb、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释
伪指令:print(= addi a7,zero,34 + ecall)、halt(= addi a7,zero,10 + ecall),各占两个字。 伪指令:print(= lui t6,LED_ADDR>>12 + sw a0,0(t6),向 MMIO 的 LED 寄存器写入 a0)、
示例:python tools/rv_asm.py testbench/programs/isa_test.asm out.hex halt(= addi a7,zero,10 + ecall),各占两个字。
示例:python tools/rv_asm.py programs/isa_test.asm srcs/rom/isa_test.hex
""" """
import re import re
@@ -129,11 +130,16 @@ RTYPE = {
"mulh": (0x01, 1), "mulh": (0x01, 1),
"mulhsu": (0x01, 2), "mulhsu": (0x01, 2),
"mulhu": (0x01, 3), "mulhu": (0x01, 3),
"divu": (0x01, 5),
"remu": (0x01, 7),
} }
ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7} ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7}
SHIFT = {"slli": 1, "srli": 5, "srai": 5} SHIFT = {"slli": 1, "srli": 5, "srai": 5}
BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7} BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7}
# 双字伪指令:print = addi a7,zero,34 + ecall;halt = addi a7,zero,10 + ecall # MMIO:LED 数据寄存器地址,需与 RTL 顶层 parameter LED_ADDR 保持一致
LED_ADDR = 0x1000
# 双字伪指令:print 向 MMIO 的 LED 写寄存器写入 a0;halt 停机
PSEUDO_SIZE = {"print": 2, "halt": 2} PSEUDO_SIZE = {"print": 2, "halt": 2}
@@ -172,7 +178,7 @@ def assemble(lines):
w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0])) w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0]))
elif mn == "lui": elif mn == "lui":
w = enc_u(num(a[1]), reg(a[0])) w = enc_u(num(a[1]), reg(a[0]))
elif mn in ("lw", "sw"): elif mn in ("lw", "sw", "sb"):
m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1]) m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1])
if not m: if not m:
raise SystemExit("内存操作数格式应为 imm(rs1): " + line) raise SystemExit("内存操作数格式应为 imm(rs1): " + line)
@@ -181,8 +187,10 @@ def assemble(lines):
raise SystemExit("访存偏移超出 12 位有符号范围: " + line) raise SystemExit("访存偏移超出 12 位有符号范围: " + line)
if mn == "lw": if mn == "lw":
w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03) w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03)
else: elif mn == "sw":
w = enc_s(im, reg(a[0]), reg(m.group(2)), 2) w = enc_s(im, reg(a[0]), reg(m.group(2)), 2)
else: # sb
w = enc_s(im, reg(a[0]), reg(m.group(2)), 0)
elif mn in BTYPE: elif mn in BTYPE:
tgt = labels[a[2]] if a[2] in labels else num(a[2]) tgt = labels[a[2]] if a[2] in labels else num(a[2])
w = enc_b(tgt - addr, reg(a[1]), reg(a[0]), BTYPE[mn]) w = enc_b(tgt - addr, reg(a[1]), reg(a[0]), BTYPE[mn])
@@ -194,7 +202,11 @@ def assemble(lines):
elif mn == "ecall": elif mn == "ecall":
w = 0x00000073 w = 0x00000073
elif mn == "print": elif mn == "print":
w = [enc_i(34, 0, 0, 17), 0x00000073] # 写 MMIO 的 LED 寄存器:lui t6, LED_ADDR>>12 ; sw a0, 0(t6)
w = [
enc_u(LED_ADDR >> 12, reg("t6")),
enc_s(0, reg("a0"), reg("t6"), 2),
]
elif mn == "halt": elif mn == "halt":
w = [enc_i(10, 0, 0, 17), 0x00000073] w = [enc_i(10, 0, 0, 17), 0x00000073]
else: else:
@@ -205,7 +217,7 @@ def assemble(lines):
def main(): def main():
src, dst = sys.argv[1], sys.argv[2] src, dst = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8") as f: with open(src, encoding="utf-8-sig") as f: # 兼容带/不带 BOM
prog = assemble(f.readlines()) prog = assemble(f.readlines())
with open(dst, "w", encoding="ascii") as f: with open(dst, "w", encoding="ascii") as f:
for _, _, ws in prog: for _, _, ws in prog: