1 Commits
Author SHA1 Message Date
Alpha f50639e90f feat(saolei): 新增纯 C 扫雷(MMIO 显示 + 中断读鼠标),扩展 CPU 外设并清理仓库
## 背景

在原有五级流水 CPU 的基础上,用一颗 RISC-V 工具链把"纯 C 写的扫雷"编译成机器码
固化进片内 ROM 运行,并为此把 MMIO 外设从"只有一个 LED 寄存器"扩展成
显示(VGA 显存)+ 七段数码管 + 鼠标事件(含中断)的完整子系统。
原 LED MMIO 的语义保持不变,原有跑马灯/中断/指令回归不受影响。

## 硬件(pipelined-cpu/srcs)

- cpu21_mmio.v:扩展成 MMIO 中枢,新增
  * 七段数码管寄存器(0x1004,8 位十六进制)
  * 鼠标寄存器组(0x1100+):STATUS/DATA/ACK/CTRL/INPUT/TIME/FRAME/EVCNT/LEVEL/MAGIC
    —— 含 16 项事件 FIFO 与 mouse_irq_o(读空自动回 0,不丢事件)
  * 显示控制器(0x1140+):CTRL(光标可见/背景色)、CURSOR(列、行)
  * 显存窗口(0x2000-0x27FF):写端口输出到板级顶层的双口 RAM,读保持组合读
  * LEVEL(0x1120):软件中断寄存器,写 1 拉起 level_irq_o(用作"关卡入口")
  * 地址译码、字节写合并、读数据 mux 全部按原风格实现
- 新增外设 RTL:cpu21_vga.v(640x480@60 按格子扫描,8x8 字模放大 2 倍)、
  cpu21_seg7.v(8 位数码管动态扫描)、cpu21_pointer.v(5 向按键→鼠标事件,1 ms 去抖
  + 事件排队)、cpu21_ps2_mouse.v(可选真鼠标,PS/2 11 位帧接收 + 3 字节包解析)
- cpu21_riscv_redirect_int_bpb.v:新增外设总线端口(显存写/读回、鼠标事件输入、
  中断输出、数码管数据、帧脉冲),例化扩展后的 cpu21_mmio
- cpu21_board_top.v:接入显存双口 RAM + VGA + 数码管 + 鼠标事件源,中断接线为
  irq_i = {SW2, LEVEL|SW1, 鼠标|SW0};ROM 宏切换(定义 SAOLEI_ROM 加载扫雷程序);
  ROM_WORDS 1024→8192
- cpu21_board_top.xdc:新增按键、SW[3]、8 位数码管、VGA、PS/2 的全部引脚约束
- tb_board_top.v:新端口显式接 0/1,避免悬空影响原跑马灯回归
- create_project.tcl:把 cpu21_saolei_rom.vh 按 Verilog Header 条件加入

## 软件(saolei/)

- sw/mmio.h:全部 MMIO 地址与格子编码(换地址只改这一个文件)
- sw/saolei.c:纯 C 扫雷 —— 背景"伪操作系统" main()(开始界面)+ 关卡 level_main()
  (在 IRQ1 里跑完整局)+ 棋盘位图/布雷(首点安全)/BFS 展开/插旗/胜负判定/逐格绘制
- sw/isr.c:**中断读鼠标的代码**。三个固定地址入口:
  * isr1_entry(.text.isr1=0x100)关卡入口:保存全部调用者保存寄存器 +
    csrrsi 打开 MIE(让鼠标中断可嵌套)→ level_main
  * isr2_entry(.text.isr2=0x200)鼠标读取:只存 ra → 纯 C 主体 isr_body
  * isr3_entry(.text.isr3=0x300)备用:直接 uret
- sw/start.c:设 sp、清 .bss、进 main(只有这一处必要内联汇编)
- sw/linker.ld:复位 0x0、三个中断入口 0x100/0x200/0x300、栈顶 0x0FFC;
  用 ASSERT 禁止 .data/.rodata(这颗核读不到 ROM 里的数据)

## 构建与工具链适配(saolei/tools)

- build.ps1:一键 C → .o → ELF → .hex + cpu21_saolei_rom.vh,任一步失败立刻停止
- check_isa.py:用"指令白名单"逐条检查反汇编,出现本核未实现的指令即报错并给修复提示
- elf2hex.py:裸二进制 → $readmemh hex + ROM 初始化头文件
- 三处针对本核的关键配置(都已写进脚本注释):
  1) -march=rv32im_zicsr(GCC 15 起 rv32im 不含 Zicsr,csrrsi 会报错)
  2) 不能禁链接器松弛:gcc 函数调用默认是 auipc+jalr,靠松弛压成 jal,
     禁掉后 35 条 auipc 会留在代码里(本核没有 auipc);
     用 -msmall-data-limit=0 防止松弛引入 gp 相对寻址
  3) 中断入口地址必须写在段声明上(.text.isr1 0x100 : ...),
     只写 ". = 0x100;" 会被 ld 忽略(实测 isr1 掉到了 0x14)
- 实测结果:text=3368B、data=0、bss=1436B,842 条指令全部通过 ISA 检查,
  入口地址 _start=0x0 / isr1_entry=0x100 / isr2_entry=0x200 / isr3_entry=0x300

## 仓库清理

- 删除被工程取代的 saolei/hw、saolei/constrs(外设已并入 srcs/rtl)
- 删除全部 Vivado 生成物:.xpr、.runs、.sim、.cache、.hw、.ip_user_files、.Xil、
  vivado.jou/log —— 需要工程时用 source create_project.tcl 一键重建(约 0.3 MB 源文件)
- .gitignore 增加 saolei/build/(构建产物可一键重建)

## 文档

- README:更新目录结构、仓库约定(不放生成物)、MMIO 地址表、文件与模块划分表、
  上板说明,新增"扫雷程序(saolei/)"章节
- saolei/README.md:重写 —— 程序结构(背景/关卡/鼠标 + 三个中断入口)、
  本核指令子集与对应写法、构建步骤与实测数据、MMIO 地址表、上板/仿真步骤、
  操作说明、验证状态与已知可改进项

## 验证状态(如实说明)

- 已实测:C 工具链链路(编译/链接/ISA 检查/hex 生成/入口地址逐条核对)
- 未验证:所有 RTL 未经综合与仿真(本机无 Vivado),C 程序也未在仿真器/硬件上运行过;
  建议先跑 tools/run_regress.ps1 确认原回归仍全 PASS,再用 SAOLEI_ROM 宏做行为仿真
2026-09-16 20:11:00 +08:00
69 changed files with 16258 additions and 912 deletions
+18
View File
@@ -1 +1,19 @@
# 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/
# saolei 的构建产物(tools/build.ps1 一键重建,不入库)
saolei/build/
+402
View File
@@ -0,0 +1,402 @@
# `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/
├─ README.md 本文档
├─ pipelined-cpu/ Vivado 工程目录(也是源文件所在地)
│ ├─ create_project.tcl 一键重建工程的 Tcl 脚本
│ ├─ srcs/rtl/*.v 处理器与外设 RTL(17 个文件,cpu21_riscv_redirect_int_bpb.v 为处理器顶层)
│ │ 外设:cpu21_mmio.v(MMIO 中枢)、cpu21_vga.v(显示)、
│ │ cpu21_seg7.v(数码管)、cpu21_pointer.v(按键当鼠标)、
│ │ cpu21_ps2_mouse.v(可选 PS/2 鼠标)
│ ├─ 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)
└─ saolei/ 纯 C 扫雷(MMIO 显示 + 中断读鼠标),见下文
├─ sw/ C 源码 + 链接脚本(三个固定中断入口)
└─ tools/ build.ps1(C → ROM)、check_isa.py(指令白名单校验)
```
**仓库里不放任何 Vivado 生成物**(`.xpr`、`.runs`、`.sim`、`.cache`、`.hw`、
`.ip_user_files`、`.Xil`、`vivado.jou/log` 本地已清理干净):需要工程时用
`source create_project.tcl` 一键重建,这样仓库始终是最清爽的源文件状态。
约定: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 级译码。
这些地址都在 4 KB 数据 RAM(`0x0000`–`0x0FFC`)之外,因此不会与 RAM 冲突。
每行的字节偏移都按**字**对齐译码(同一 32 位寄存器内任意字节偏移都命中)。
| 地址 | 外设 | 写 | 读 |
| ---------------------- | -------------- | ---------------------------------------------------------------------------- | -------------------------- |
| `0x0000_1000` | LED 数据寄存器 | `sw` 写全字;`sb` 按 `data_wstrb_o` 只改对应字节通道,`led_valid_o` 拉高一拍 | `lw` 返回 LED 寄存器当前值 |
| `0x0000_1004` | 七段数码管 | `sw`/`sb` 写 8 位十六进制数(低 4 位 = 最右边那个数码管) | `lw` 读回 |
| `0x0000_1100`+ | 鼠标寄存器组 | STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC | 同左(DATA 读无副作用) |
| `0x0000_1140`+ | 显示控制器 | CTRL(光标可见、背景色)/ CURSOR(列、行) | 同左(另含 MAGIC 自检值) |
| `0x0000_2000`–`0x27FF` | 显存 | 40 列 × 30 行、每格 1 字节(字形 + 颜色),写端口接到板级顶层的双口 RAM | `lw` 组合读回 |
其中 `LEVEL`(`0x1120`)是一个**软件中断**寄存器:写 1 会拉起 `irq_i[1]`,用来让程序
"跳到关卡入口";`MAGIC`(`0xCAFE0001`/`0xCAFE0002`)可以读来自检 MMIO 是否活着。
鼠标事件 FIFO(深度 16)配合 `mouse_irq_o` 提供"事件到达即中断",`isr_body` 读空 FIFO 后
中断线自动回 0,因此不会丢事件。
地址译码按**字**对齐:同一 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、七段数码管、鼠标寄存器组 + 事件 FIFO + 中断、显示控制器、显存窗口 |
| `pipelined-cpu/srcs/rtl/cpu21_vga.v` | `cpu21_vga` | 640x480@60 显示控制器:按"格子"扫描,8x8 字模放大 2 倍填满 16x16 的格子 |
| `pipelined-cpu/srcs/rtl/cpu21_seg7.v` | `cpu21_seg7` | 板载 8 位数码管动态扫描驱动(共阳极,位选/段选低有效) |
| `pipelined-cpu/srcs/rtl/cpu21_pointer.v` | `cpu21_pointer` | 把 5 向按键变成"鼠标"事件(去抖 + 事件排队,单位 1/8 格) |
| `pipelined-cpu/srcs/rtl/cpu21_ps2_mouse.v` | `cpu21_ps2_mouse` | 可选的真鼠标:PS/2 接收(11 位帧 + 奇校验 + 3 字节包解析),只实现接收 |
| `pipelined-cpu/srcs/board/cpu21_board_top.v` | `cpu21_board_top` | 板级顶层:时钟分频、复位同步、片内 ROM/RAM、显存双口 RAM、VGA/数码管/鼠标/中断连线 |
| `pipelined-cpu/srcs/board/cpu21_saolei_rom.vh` | — | 扫雷程序的 ROM 初始化(由 `saolei/tools/build.ps1` 生成) |
| `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` 作为顶层,
只引出板上真正需要的那些引脚(LED、按键、拨码、VGA、8 位数码管、可选的 PS/2),
其余调试端口不连接。定义宏 `SAOLEI_ROM` 时还会把扫雷程序固化进 ROM。
### 引脚分配(`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 每次写入时点亮。
## 扫雷程序(`saolei/`)
`saolei/` 是一个**纯 C 写的扫雷**,用 RISC-V 工具链编译成机器码后固化进片内 ROM 运行,
反过来验证这套 MMIO/中断/显示/鼠标外设。完整说明见 `saolei/README.md`,要点:
- **三段中断入口**(地址由 `saolei/sw/linker.ld` 定死,与顶层 `IRQ1/2/3_VECTOR` 成对):
IRQ1=`0x100` 关卡(游戏)程序、IRQ2=`0x200` 鼠标读取、IRQ3=`0x300` 备用;
- **两层结构**:背景 `main()` 画开始界面并一直运行 → 点"开始"写 `LEVEL` 寄存器拉起软件中断
→ CPU 跳进 `0x100` 的关卡程序跑完整局 → 点左上角黄格 `uret` 回到背景被打断的那条指令;
- **纯 C**,只有三处必要的内联汇编:设 `sp`、三个中断入口的现场保护、`uret`;
- **指令子集适配**:没有 `auipc`/`lb`/`lbu`/有符号 `div`,所以程序只按字节写显存、
索引全用移位、且不含 `.data`/`.rodata`(链接脚本用 `ASSERT` 兜住);
- **构建**:`saolei/tools/build.ps1`(C → ELF → `.hex` + `cpu21_saolei_rom.vh`),
并用 `check_isa.py` 逐条反汇编检查有没有用到未实现的指令。实测 842 条指令全部合法;
- **上板切换**:Vivado 里定义宏 `SAOLEI_ROM` 就加载扫雷程序,不定义仍是下面的跑马灯。
## 回归测试
工程里带了一个 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
```
-60
View File
@@ -1,60 +0,0 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// ALU:纯组合运算单元。
//
// result 为主结果;result2 为第二结果,用于 MUL 的高 32 位与 DIVU 的余数。
// 除法做了除零保护:b==0 时商为 32'hffff_ffff、余数为被除数。
// -----------------------------------------------------------------------------
module cpu21_riscv_alu (
input wire [ 3:0] op,
input wire [31:0] a,
input wire [31:0] b,
output reg [31:0] result,
output reg [31:0] result2
);
localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1;
localparam ALU_SRL = 4'd2;
localparam ALU_MUL = 4'd3;
localparam ALU_DIVU = 4'd4;
localparam ALU_ADD = 4'd5;
localparam ALU_SUB = 4'd6;
localparam ALU_AND = 4'd7;
localparam ALU_OR = 4'd8;
localparam ALU_XOR = 4'd9;
localparam ALU_NOR = 4'd10;
localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12;
reg [63:0] mult_result;
always @* begin
mult_result = a * b;
result2 = 32'b0;
case (op)
ALU_SLL: result = a << b[4:0];
ALU_SRA: result = $signed(a) >>> b[4:0];
ALU_SRL: result = a >> b[4:0];
ALU_MUL: begin
result = mult_result[31:0];
result2 = mult_result[63:32];
end
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_SUB: result = a - b;
ALU_AND: result = a & b;
ALU_OR: result = a | b;
ALU_XOR: result = a ^ b;
ALU_NOR: result = ~(a | b);
ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0;
ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0;
default: result = 32'b0;
endcase
end
endmodule
`default_nettype wire
-733
View File
@@ -1,733 +0,0 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21-risc-v-4.circ -> 可综合 Verilog 实现(顶层)
//
// 处理器为五级流水线 IF/ID/EX/MEM/WB。本文件只保留:
// * 五级流水寄存器与各级之间的连线;
// * 各功能子模块的例化;
// * PC / IF-ID / ID-EX 的控制优先级(中断、URET、停机、重定向、停顿、
// 正常推进);
// * 对外输出与调试信号。
//
// 各功能单元已拆分为独立文件,便于单独仿真调试:
// cpu21_riscv_alu.v 组合 ALU(result2 用于 MUL 高位 / 余数)
// cpu21_bpb_8.v 八表项全相联分支目标缓冲
// cpu21_riscv_decoder.v ID 级组合译码器
// cpu21_riscv_regfile.v 寄存器堆(写优先旁路)
// cpu21_riscv_forward_unit.v EX 级前递网络
// cpu21_riscv_store_unit.v EX 级存储数据对齐
// cpu21_riscv_branch_unit.v EX 级控制流裁决
// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定
// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈
// cpu21_riscv_perf_counters.v 性能计数器
//
// 主要特性:
// * EX 级前递(forwarding)与 load-use 冒险互锁;
// * EX 级控制流裁决与流水线重定向;
// * 八表项全相联分支目标缓冲(BPB);
// * 三路采样的中断源,带优先级与可嵌套的 URET 返回;
// * 沿用原 ecall 约定:a7==34 时写出 LED 值,否则流水线等待 go_i。
//
// 存储器接口约定:
// instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码,
// 因此外部字宽 RAM 可用 data_addr_o[11:2] 作为字索引,再依据
// data_wstrb_o 完成字节合并。
// -----------------------------------------------------------------------------
module cpu21_riscv_redirect_int_bpb #(
parameter RESET_PC = 32'h0000_0000,
parameter IRQ1_VECTOR = 32'h0000_30ac,
parameter IRQ2_VECTOR = 32'h0000_31e4,
parameter IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4,
// ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。
parameter [31:0] USTATUS_INIT = 32'h0000_0001
) (
input wire clk,
input wire reset,
input wire [31:0] instr_i,
input wire [31:0] data_rdata_i,
input wire [ 2:0] irq_i,
input wire go_i,
output wire [31:0] instr_addr_o,
output wire [31:0] data_addr_o,
output wire [31:0] data_wdata_o,
output wire [ 3:0] data_wstrb_o,
output wire data_we_o,
output wire [31:0] led_data_o,
output wire led_valid_o,
output wire [31:0] if_pc_o,
output wire [31:0] id_pc_o,
output wire [31:0] ex_pc_o,
output wire [31:0] mem_pc_o,
output wire [31:0] wb_pc_o,
output wire [31:0] if_ir_o,
output wire [31:0] id_ir_o,
output wire [31:0] ex_ir_o,
output wire [31:0] mem_ir_o,
output wire [31:0] wb_ir_o,
output wire [31:0] rdin_o,
output wire [31:0] mdin_o,
output wire reg_write_o,
output wire mem_write_o,
output wire halted_o,
output wire [ 2:0] irq_pending_o,
output wire [ 1:0] irq_current_level_o,
output wire [31:0] uepc_o,
output wire [31:0] ustatus_o,
output wire bpb_predict_hit_o,
output wire bpb_predict_taken_o,
output wire bpb_mispredict_o,
output wire [15:0] cycle_count_o,
output wire [15:0] stall_count_o,
output wire [15:0] bubble_count_o,
output wire [15:0] conditional_taken_count_o,
output wire [15:0] unconditional_branch_count_o,
output wire [15:0] prediction_success_count_o,
output wire [15:0] prediction_failure_count_o
);
// 顶层只用到少量常量,完整译码表见 cpu21_riscv_decoder.v。
localparam OP_BRANCH = 5'h18;
localparam ALU_ADD = 4'd5;
// -------------------------------------------------------------------------
// 流水线寄存器
// -------------------------------------------------------------------------
// 当前取指 PC。
reg [31:0] pc_q;
// IF/ID 流水寄存器(含本条指令所携带的分支预测信息)。
reg ifid_valid_q;
reg [31:0] ifid_pc_q;
reg [31:0] ifid_ir_q;
reg ifid_pred_taken_q;
reg [31:0] ifid_pred_target_q;
// ID/EX 流水寄存器:保存译码结果与操作数。
reg idex_valid_q;
reg [31:0] idex_pc_q;
reg [31:0] idex_ir_q;
reg [31:0] idex_rs1_value_q;
reg [31:0] idex_rs2_value_q;
reg [4:0] idex_rs1_idx_q;
reg [4:0] idex_rs2_idx_q;
reg [4:0] idex_rd_q;
reg [31:0] idex_imm_q;
reg [3:0] idex_aluop_q;
reg idex_alu_src_q;
reg idex_reg_write_q;
reg idex_mem_to_reg_q;
reg idex_mem_write_q;
reg idex_mem_byte_q;
reg [2:0] idex_wb_sel_q;
reg [11:0] idex_csr_addr_q;
reg idex_branch_q;
reg idex_beq_q;
reg idex_bne_q;
reg idex_blt_q;
reg idex_bltu_q;
reg idex_jal_q;
reg idex_jalr_q;
reg idex_ecall_q;
reg idex_uret_q;
reg idex_csr_set_q;
reg idex_csr_clear_q;
reg idex_csr_write_q;
reg idex_pred_taken_q;
reg [31:0] idex_pred_target_q;
// EX/MEM 流水寄存器:保存 ALU/存储结果与写回控制。
reg exmem_valid_q;
reg [31:0] exmem_pc_q;
reg [31:0] exmem_ir_q;
reg [31:0] exmem_alu_result_q;
reg [31:0] exmem_alu_result2_q;
reg [31:0] exmem_csr_old_q;
reg [31:0] exmem_write_data_q;
reg [4:0] exmem_rd_q;
reg exmem_reg_write_q;
reg exmem_mem_to_reg_q;
reg exmem_mem_write_q;
reg [3:0] exmem_wstrb_q;
reg [2:0] exmem_wb_sel_q;
// MEM/WB 流水寄存器:保存访存数据与最终写回信息。
reg memwb_valid_q;
reg [31:0] memwb_pc_q;
reg [31:0] memwb_ir_q;
reg [31:0] memwb_alu_result_q;
reg [31:0] memwb_alu_result2_q;
reg [31:0] memwb_csr_old_q;
reg [31:0] memwb_mem_data_q;
reg [4:0] memwb_rd_q;
reg memwb_reg_write_q;
reg memwb_mem_to_reg_q;
reg [2:0] memwb_wb_sel_q;
// LED 输出与停机标志。
reg [31:0] led_data_q;
reg led_valid_q;
reg halted_q;
// -------------------------------------------------------------------------
// IF 级:分支预测。JAL/JALR 在 EX 级裁决,条件分支则可以直接依据 BPB
// 给出的目标地址重定向取指 PC。
// -------------------------------------------------------------------------
wire [4:0] f_opcode = instr_i[6:2];
wire [2:0] f_funct3 = instr_i[14:12];
wire f_is_branch = (f_opcode == OP_BRANCH) &&
((f_funct3 == 3'b000) || (f_funct3 == 3'b001) ||
(f_funct3 == 3'b100) || (f_funct3 == 3'b110));
wire [31:0] bpb_predict_target;
wire bpb_predict_hit;
wire bpb_predict_taken;
wire ex_branch_taken;
wire [31:0] ex_target;
wire ex_controlflow;
wire ex_redirect_valid;
wire [31:0] ex_redirect_pc;
// BPB 更新端口:在 EX 级用条件分支的实际结果训练预测器。
wire bpb_update_enable = idex_valid_q && idex_branch_q;
wire bpb_update_taken = ex_branch_taken;
wire [31:0] bpb_update_target = ex_target;
// 分支目标缓冲实例。
cpu21_bpb_8 u_bpb (
.clk (clk),
.reset (reset),
.predict_pc (pc_q),
.predict_enable (f_is_branch),
.predict_hit (bpb_predict_hit),
.predict_taken (bpb_predict_taken),
.predict_target (bpb_predict_target),
.update_enable (bpb_update_enable),
.update_controlflow(1'b1),
.update_pc (idex_pc_q),
.update_target (bpb_update_target),
.update_taken (bpb_update_taken)
);
// 下一取指 PC:预测跳转命中时使用 BPB 目标地址,否则顺序加 4。
wire [31:0] fetch_next_pc =
(f_is_branch && bpb_predict_hit && bpb_predict_taken) ?
bpb_predict_target : (pc_q + 32'd4);
// -------------------------------------------------------------------------
// ID 级:译码与寄存器堆读取
// -------------------------------------------------------------------------
wire [4:0] d_src1_idx;
wire [4:0] d_src2_idx;
wire [4:0] d_rd;
wire [31:0] d_imm;
wire [3:0] d_aluop;
wire [2:0] d_wb_sel;
wire [11:0] d_csr_addr;
wire d_uses_rs1;
wire d_uses_rs2;
wire d_reg_write;
wire d_mem_to_reg;
wire d_mem_write;
wire d_mem_byte;
wire d_alu_src;
wire d_branch;
wire d_beq;
wire d_bne;
wire d_blt;
wire d_bltu;
wire d_jal;
wire d_jalr;
wire d_ecall;
wire d_uret;
wire d_csr_set;
wire d_csr_clear;
wire d_csr_write;
cpu21_riscv_decoder u_decoder (
.ir_i (ifid_ir_q),
.rs1_idx_o (d_src1_idx),
.rs2_idx_o (d_src2_idx),
.rd_o (d_rd),
.imm_o (d_imm),
.alu_op_o (d_aluop),
.wb_sel_o (d_wb_sel),
.csr_addr_o (d_csr_addr),
.uses_rs1_o (d_uses_rs1),
.uses_rs2_o (d_uses_rs2),
.reg_write_o (d_reg_write),
.mem_to_reg_o(d_mem_to_reg),
.mem_write_o (d_mem_write),
.mem_byte_o (d_mem_byte),
.alu_src_o (d_alu_src),
.branch_o (d_branch),
.beq_o (d_beq),
.bne_o (d_bne),
.blt_o (d_blt),
.bltu_o (d_bltu),
.jal_o (d_jal),
.jalr_o (d_jalr),
.ecall_o (d_ecall),
.uret_o (d_uret),
.csr_set_o (d_csr_set),
.csr_clear_o (d_csr_clear),
.csr_write_o (d_csr_write)
);
// 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4,3=MUL 高位/余数,
// 4=CSR 旧值。
wire [31:0] wb_value;
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'd4) ? memwb_csr_old_q :
memwb_alu_result_q;
// WB 级写回使能(同时用于寄存器堆的写端口与写优先旁路)。
wire memwb_reg_write_en = memwb_valid_q && memwb_reg_write_q;
// ID 级读寄存器:x0 恒为 0,并对同周期 WB 的结果做写优先旁路。
wire [31:0] d_rs1_value;
wire [31:0] d_rs2_value;
cpu21_riscv_regfile u_regfile (
.clk (clk),
.reset (reset),
.rs1_idx_i (d_src1_idx),
.rs2_idx_i (d_src2_idx),
.rs1_data_o(d_rs1_value),
.rs2_data_o(d_rs2_value),
.we_i (memwb_reg_write_en),
.waddr_i (memwb_rd_q),
.wdata_i (wb_value)
);
// 中断/CSR 相关连线(中断控制器实例见下方"中断 / CSR"小节)。
wire take_irq;
wire [31:0] irq_vector;
wire [31:0] ex_return_pc;
wire [2:0] irq_pending;
wire [1:0] irq_current_level;
wire [31:0] uepc_value;
wire [31:0] ustatus_value;
// -------------------------------------------------------------------------
// EX 级:前递网络、ALU 运算、存储对齐与控制流裁决
// -------------------------------------------------------------------------
// EX/MEM 级要前递的值(按 wb_sel 选取)。
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'd4) ? exmem_csr_old_q :
exmem_alu_result_q;
// 前递优先级:EX/MEM 的结果最新,其次为 MEM/WB 写回值,最后才是
// 译码时读到的寄存器值。
wire [31:0] ex_src1;
wire [31:0] ex_src2;
cpu21_riscv_forward_unit u_forward_unit (
.rs1_value_i (idex_rs1_value_q),
.rs2_value_i (idex_rs2_value_q),
.rs1_idx_i (idex_rs1_idx_q),
.rs2_idx_i (idex_rs2_idx_q),
.exmem_valid_i (exmem_valid_q),
.exmem_reg_write_i (exmem_reg_write_q),
.exmem_mem_to_reg_i(exmem_mem_to_reg_q),
.exmem_rd_i (exmem_rd_q),
.exmem_value_i (exmem_forward_value),
.memwb_valid_i (memwb_valid_q),
.memwb_reg_write_i (memwb_reg_write_q),
.memwb_rd_i (memwb_rd_q),
.memwb_value_i (wb_value),
.src1_o (ex_src1),
.src2_o (ex_src2)
);
// ALU 的 B 端:立即数或 rs2。
wire [31:0] ex_alu_b = idex_alu_src_q ? idex_imm_q : ex_src2;
wire [31:0] ex_alu_result;
wire [31:0] ex_alu_result2;
// 读取 CSR 旧值(ustatus=0x004、uepc=0x041),供 CSR 指令回写 rd。
wire [31:0] ex_csr_old_value =
(idex_csr_addr_q == 12'h004) ? ustatus_value :
(idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0;
// ALU 实例(纯组合逻辑)。
cpu21_riscv_alu u_ex_alu (
.op (idex_aluop_q),
.a (ex_src1),
.b (ex_alu_b),
.result (ex_alu_result),
.result2(ex_alu_result2)
);
// 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。
cpu21_riscv_branch_unit u_branch_unit (
.valid_i (idex_valid_q),
.pc_i (idex_pc_q),
.imm_i (idex_imm_q),
.src1_i (ex_src1),
.src2_i (ex_src2),
.branch_i (idex_branch_q),
.beq_i (idex_beq_q),
.bne_i (idex_bne_q),
.blt_i (idex_blt_q),
.bltu_i (idex_bltu_q),
.jal_i (idex_jal_q),
.jalr_i (idex_jalr_q),
.pred_taken_i (idex_pred_taken_q),
.pred_target_i (idex_pred_target_q),
.branch_taken_o (ex_branch_taken),
.controlflow_o (ex_controlflow),
.target_o (ex_target),
.redirect_valid_o(ex_redirect_valid),
.redirect_pc_o (ex_redirect_pc)
);
// 存储数据对齐:依据字节地址低两位生成写数据与字节写掩码。
wire [31:0] ex_store_data;
wire [ 3:0] ex_store_wstrb;
cpu21_riscv_store_unit u_store_unit (
.addr_i (ex_alu_result),
.data_i (ex_src2),
.mem_write_i (idex_mem_write_q),
.mem_byte_i (idex_mem_byte_q),
.store_data_o (ex_store_data),
.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);
wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34);
// -------------------------------------------------------------------------
// 中断 / CSR / 嵌套返回栈
// -------------------------------------------------------------------------
wire ex_uret_redirect = idex_valid_q && idex_uret_q;
// EX 级正在重定向/URET/停机时不允许响应中断。
wire ex_block_irq = ex_redirect_valid || ex_uret_redirect || ex_halt_event;
// 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确
// 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中
// 保存的就是尚未执行的最老指令。
wire [31:0] irq_save_pc = idex_valid_q ?
((idex_branch_q && ex_branch_taken) ? ex_target : (idex_pc_q + 32'd4)) :
(ifid_valid_q ? ifid_pc_q : pc_q);
cpu21_riscv_irq_ctrl #(
.IRQ1_VECTOR (IRQ1_VECTOR),
.IRQ2_VECTOR (IRQ2_VECTOR),
.IRQ3_VECTOR (IRQ3_VECTOR),
.IRQ_STACK_DEPTH(IRQ_STACK_DEPTH),
.USTATUS_INIT (USTATUS_INIT)
) u_irq_ctrl (
.clk (clk),
.reset (reset),
.irq_i (irq_i),
.halted_i (halted_q),
.ex_block_i (ex_block_irq),
.save_pc_i (irq_save_pc),
.uret_i (ex_uret_redirect),
.csr_valid_i(idex_valid_q),
.csr_set_i (idex_csr_set_q),
.csr_clear_i(idex_csr_clear_q),
.csr_write_i(idex_csr_write_q),
.csr_addr_i (idex_csr_addr_q),
.csr_imm_i (idex_imm_q),
.csr_wdata_i(ex_src1),
.take_irq_o (take_irq),
.vector_o (irq_vector),
.return_pc_o(ex_return_pc),
.pending_o (irq_pending),
.level_o (irq_current_level),
.uepc_o (uepc_value),
.ustatus_o (ustatus_value)
);
// -------------------------------------------------------------------------
// 冒险 / 停顿 / 冲刷判定
// -------------------------------------------------------------------------
wire load_use_hazard;
wire flush_younger;
wire pipeline_stall;
cpu21_riscv_hazard_unit u_hazard_unit (
.ifid_valid_i (ifid_valid_q),
.idex_valid_i (idex_valid_q),
.idex_mem_to_reg_i(idex_mem_to_reg_q),
.idex_rd_i (idex_rd_q),
.d_uses_rs1_i (d_uses_rs1),
.d_uses_rs2_i (d_uses_rs2),
.d_src1_idx_i (d_src1_idx),
.d_src2_idx_i (d_src2_idx),
.take_irq_i (take_irq),
.uret_redirect_i (ex_uret_redirect),
.halt_event_i (ex_halt_event),
.redirect_valid_i (ex_redirect_valid),
.halted_i (halted_q),
.load_use_hazard_o(load_use_hazard),
.flush_younger_o (flush_younger),
.pipeline_stall_o (pipeline_stall)
);
// -------------------------------------------------------------------------
// 对外输出与调试信号。
// -------------------------------------------------------------------------
// 存储器接口:指令地址来自取指 PC;数据地址/写数据/写掩码来自 MEM 级。
assign instr_addr_o = pc_q;
assign data_addr_o = exmem_alu_result_q;
assign data_wdata_o = exmem_write_data_q;
assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0;
assign data_we_o = |data_wstrb_o;
// LED 输出与流水线各级 PC/指令回显(用于调试与波形观察)。
assign led_data_o = led_data_q;
assign led_valid_o = led_valid_q;
assign if_pc_o = pc_q;
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 mem_pc_o = exmem_valid_q ? exmem_pc_q : 32'b0;
assign wb_pc_o = memwb_valid_q ? memwb_pc_q : 32'b0;
assign if_ir_o = instr_i;
assign id_ir_o = ifid_valid_q ? ifid_ir_q : 32'b0;
assign ex_ir_o = idex_valid_q ? idex_ir_q : 32'b0;
assign mem_ir_o = exmem_valid_q ? exmem_ir_q : 32'b0;
assign wb_ir_o = memwb_valid_q ? memwb_ir_q : 32'b0;
assign rdin_o = wb_value;
assign mdin_o = exmem_write_data_q;
assign reg_write_o = memwb_valid_q && memwb_reg_write_q;
assign mem_write_o = exmem_valid_q && exmem_mem_write_q;
assign halted_o = halted_q;
assign irq_pending_o = irq_pending;
assign irq_current_level_o = irq_current_level;
assign uepc_o = uepc_value;
assign ustatus_o = ustatus_value;
assign bpb_predict_hit_o = bpb_predict_hit;
assign bpb_predict_taken_o = bpb_predict_taken;
assign bpb_mispredict_o = ex_redirect_valid && idex_branch_q;
// -------------------------------------------------------------------------
// 状态更新。顺序遵循五级流水线:先捕获 MEM/WB、EX/MEM,再处理
// ID/EX 与 IF/ID/PC 的控制。
// -------------------------------------------------------------------------
// 复位时清空所有流水寄存器与 LED/停机标志(寄存器堆、中断状态与
// 性能计数器在各自的子模块内复位)。
always @(posedge clk or posedge reset) begin
if (reset) begin
pc_q <= RESET_PC;
ifid_valid_q <= 1'b0;
ifid_pc_q <= 32'b0;
ifid_ir_q <= 32'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
idex_valid_q <= 1'b0;
idex_pc_q <= 32'b0;
idex_ir_q <= 32'b0;
idex_rs1_value_q <= 32'b0;
idex_rs2_value_q <= 32'b0;
idex_rs1_idx_q <= 5'b0;
idex_rs2_idx_q <= 5'b0;
idex_rd_q <= 5'b0;
idex_imm_q <= 32'b0;
idex_aluop_q <= ALU_ADD;
idex_alu_src_q <= 1'b0;
idex_reg_write_q <= 1'b0;
idex_mem_to_reg_q <= 1'b0;
idex_mem_write_q <= 1'b0;
idex_mem_byte_q <= 1'b0;
idex_wb_sel_q <= 3'b0;
idex_csr_addr_q <= 12'b0;
idex_branch_q <= 1'b0;
idex_beq_q <= 1'b0;
idex_bne_q <= 1'b0;
idex_blt_q <= 1'b0;
idex_bltu_q <= 1'b0;
idex_jal_q <= 1'b0;
idex_jalr_q <= 1'b0;
idex_ecall_q <= 1'b0;
idex_uret_q <= 1'b0;
idex_csr_set_q <= 1'b0;
idex_csr_clear_q <= 1'b0;
idex_csr_write_q <= 1'b0;
idex_pred_taken_q <= 1'b0;
idex_pred_target_q <= 32'b0;
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;
memwb_valid_q <= 1'b0;
memwb_pc_q <= 32'b0;
memwb_ir_q <= 32'b0;
memwb_alu_result_q <= 32'b0;
memwb_alu_result2_q <= 32'b0;
memwb_csr_old_q <= 32'b0;
memwb_mem_data_q <= 32'b0;
memwb_rd_q <= 5'b0;
memwb_reg_write_q <= 1'b0;
memwb_mem_to_reg_q <= 1'b0;
memwb_wb_sel_q <= 3'b0;
led_data_q <= 32'b0;
led_valid_q <= 1'b0;
halted_q <= 1'b0;
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 (ex_halt_event) halted_q <= 1'b1;
// MEM/WB 流水寄存器捕获。
memwb_valid_q <= exmem_valid_q;
memwb_pc_q <= exmem_pc_q;
memwb_ir_q <= exmem_ir_q;
memwb_alu_result_q <= exmem_alu_result_q;
memwb_alu_result2_q <= exmem_alu_result2_q;
memwb_csr_old_q <= exmem_csr_old_q;
memwb_mem_data_q <= data_rdata_i;
memwb_rd_q <= exmem_rd_q;
memwb_reg_write_q <= exmem_reg_write_q;
memwb_mem_to_reg_q <= exmem_mem_to_reg_q;
memwb_wb_sel_q <= exmem_wb_sel_q;
// EX/MEM 流水寄存器捕获。
exmem_valid_q <= idex_valid_q;
exmem_pc_q <= idex_pc_q;
exmem_ir_q <= idex_ir_q;
exmem_alu_result_q <= ex_alu_result;
exmem_alu_result2_q <= ex_alu_result2;
exmem_csr_old_q <= ex_csr_old_value;
exmem_write_data_q <= ex_store_data;
exmem_rd_q <= idex_rd_q;
exmem_reg_write_q <= idex_reg_write_q;
exmem_mem_to_reg_q <= idex_mem_to_reg_q;
exmem_mem_write_q <= idex_mem_write_q;
exmem_wstrb_q <= ex_store_wstrb;
exmem_wb_sel_q <= idex_wb_sel_q;
// 控制优先级:中断 > URET > ecall 停机 > 分支重定向 >
// load-use 停顿 > 正常流水推进。
if (flush_younger || pipeline_stall || halted_q) begin
idex_valid_q <= 1'b0;
idex_pred_taken_q <= 1'b0;
idex_pred_target_q <= 32'b0;
end else begin
idex_valid_q <= ifid_valid_q;
idex_pc_q <= ifid_pc_q;
idex_ir_q <= ifid_ir_q;
idex_rs1_value_q <= d_rs1_value;
idex_rs2_value_q <= d_rs2_value;
idex_rs1_idx_q <= d_src1_idx;
idex_rs2_idx_q <= d_src2_idx;
idex_rd_q <= d_rd;
idex_imm_q <= d_imm;
idex_aluop_q <= d_aluop;
idex_alu_src_q <= d_alu_src;
idex_reg_write_q <= d_reg_write;
idex_mem_to_reg_q <= d_mem_to_reg;
idex_mem_write_q <= d_mem_write;
idex_mem_byte_q <= d_mem_byte;
idex_wb_sel_q <= d_wb_sel;
idex_csr_addr_q <= d_csr_addr;
idex_branch_q <= d_branch;
idex_beq_q <= d_beq;
idex_bne_q <= d_bne;
idex_blt_q <= d_blt;
idex_bltu_q <= d_bltu;
idex_jal_q <= d_jal;
idex_jalr_q <= d_jalr;
idex_ecall_q <= d_ecall;
idex_uret_q <= d_uret;
idex_csr_set_q <= d_csr_set;
idex_csr_clear_q <= d_csr_clear;
idex_csr_write_q <= d_csr_write;
idex_pred_taken_q <= ifid_pred_taken_q;
idex_pred_target_q <= ifid_pred_target_q;
end
if (take_irq) begin
pc_q <= irq_vector;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end else if (ex_uret_redirect) begin
pc_q <= ex_return_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end else if (ex_halt_event) begin
pc_q <= idex_pc_q + 32'd4;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end else if (ex_redirect_valid) begin
pc_q <= ex_redirect_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end else if (pipeline_stall) begin
pc_q <= pc_q;
ifid_valid_q <= ifid_valid_q;
ifid_pc_q <= ifid_pc_q;
ifid_ir_q <= ifid_ir_q;
ifid_pred_taken_q <= ifid_pred_taken_q;
ifid_pred_target_q <= ifid_pred_target_q;
end else if (halted_q) begin
pc_q <= pc_q;
ifid_valid_q <= 1'b0;
end else begin
pc_q <= fetch_next_pc;
ifid_valid_q <= 1'b1;
ifid_pc_q <= pc_q;
ifid_ir_q <= instr_i;
ifid_pred_taken_q <= f_is_branch && bpb_predict_hit && bpb_predict_taken;
ifid_pred_target_q <= bpb_predict_target;
end
end
end
// -------------------------------------------------------------------------
// 性能计数器(只影响统计量,不参与控制)。
// -------------------------------------------------------------------------
cpu21_riscv_perf_counters u_perf_counters (
.clk (clk),
.reset (reset),
.inc_cycle_i (!halted_q),
.stall_i (pipeline_stall),
.flush_i (flush_younger),
.cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken),
.cond_valid_i (idex_valid_q && idex_branch_q),
.mispredict_i (ex_redirect_valid),
.uncond_i (idex_valid_q && (idex_jal_q || idex_jalr_q)),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
endmodule
`default_nettype wire
-81
View File
@@ -1,81 +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`(有符号小于)、`bltu`(无符号小于)在 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`,支持按字节写入。
## 指令编码
表中的 `opcode` 是控制器使用的五位字段 `IR[6:2]`,不是包含最低两位的 7 位原始 opcode。
因此它与标准 RISC-V 指令的低 7 位 opcode 相差右移两位。
| 指令类别 | opcode | 说明 |
| ------------------------------ | -----: | --------------------------------------- |
| R 型 | `0x0c` | add/sub/and/or/xor/slt/sltu/sll/srl/sra |
| I 型 ALU | `0x04` | addi/andi/ori/xori/slti/slli/srli/srai |
| `lw` | `0x00` | `funct3=010` |
| `sw` / `sb` | `0x08` | `funct3=010` / `000` |
| `beq` / `bne` / `blt` / `bltu` | `0x18` | `funct3=000` / `001` / `100` / `110` |
| `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 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支
跳转、预测成败)完全一致。
+116
View File
@@ -0,0 +1,116 @@
# ============================================================================
# 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"]
# 扫雷程序的 ROM 初始化(由 saolei/tools/build.ps1 生成,可能还不存在)。
# 综合/仿真时定义宏 SAOLEI_ROM 就会用它替换上面的跑马灯 ROM。
set saolei_rom "$origin_dir/srcs/board/cpu21_saolei_rom.vh"
if {[file exists $saolei_rom]} {
add_files -norecurse -fileset sources_1 [list $saolei_rom]
set_property file_type {Verilog Header} [get_files $saolei_rom]
} else {
puts "NOTE: 还没有 $saolei_rom —— 需要先跑 saolei/tools/build.ps1 生成"
}
# 约束(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
+110
View File
@@ -0,0 +1,110 @@
# 指令扩展验证程序:每条被测指令的结果都通过 MMIO 写 LED 输出,最后用 halt 停机。
# 期望输出顺序见每行注释(共 24 次输出),同时保存在同目录 isa_test.expected(每行一项 LED 值)。
#
# 用法(在仓库根目录执行):
# 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 时会被测试
# 平台优先从仿真工作目录载入(参见 tb_no_intr.v 的 ROM 探测顺序)。
# print / halt 是 tools/rv_asm.py 提供的双字伪指令(print 会占用 t6):
# print = lui t6,0x1000>>12 + sw a0,0(t6)(写 MMIO 的 LED 寄存器);
# halt = addi a7,zero,10 + ecall(ecall 停机)。
# ---- sltiu:立即数符号扩展后做无符号比较 ----
addi t0, zero, 1
addi t1, zero, -1 # t1 = 0xffffffff
sltiu a0, t0, -1 # 1 <u 0xffffffff -> 00000001
print
sltiu a0, t1, 1 # 0xffffffff <u 1 -> 00000000
print
# ---- xor / sll / srl / sra(原有指令回归)----
addi t0, zero, 0xff
addi t1, zero, -1
xor a0, t0, t1 # 0xff ^ 0xffffffff -> ffffff00
print
addi t0, zero, 1
addi t1, zero, 31
sll a0, t0, t1 # 1 << 31 -> 80000000
print
addi t0, zero, -1
addi t1, zero, 28
srl a0, t0, t1 # 逻辑右移 -> 0000000f
print
sra a0, t0, t1 # 算术右移 -> ffffffff
print
# ---- mul:无符号乘积低 32 位 ----
addi t0, zero, 1234
addi t1, zero, 567
mul a0, t0, t1 # 1234*567 = 0x000aad1e
print
# ---- mulh / mulhu / mulhsu:64 位乘积的高 32 位 ----
addi t0, zero, -1 # 有符号 -1 / 无符号 0xffffffff
addi t1, zero, -1
mulh a0, t0, t1 # (-1)*(-1)=1 -> 00000000
print
mulhu a0, t0, t1 # 0xffffffff^2 -> fffffffe
print
addi t0, zero, -1
addi t1, zero, 2
mulhsu a0, t0, t1 # (-1)*2=-2 -> ffffffff
print
# ---- bltu:0 <u 0xffffffff 成立(按有符号比较则不成立)----
addi t0, zero, -1
bltu zero, t0, bltu_ok
addi a0, zero, 0x101 # 不应执行
print
bltu_ok:
addi a0, zero, 0x601 # 00000601
print
# ---- bge:有符号 >= ,(-1) >= 0 不成立,应走 fall-through ----
addi t0, zero, -1
bge t0, zero, bge_bad
addi a0, zero, 0x602 # 00000602
print
jal zero, bge_done
bge_bad:
addi a0, zero, 0x102 # 不应执行
print
bge_done:
# ---- bgeu:无符号 >= ,0xffffffff >=u 0 成立 ----
addi t0, zero, -1
bgeu t0, zero, bgeu_ok
addi a0, zero, 0x103 # 不应执行
print
bgeu_ok:
addi a0, zero, 0x603 # 00000603
print
# ---- bge 循环:从 -3 递增到 0(共 4 次输出)----
addi s1, zero, -3
bge_loop:
add a0, zero, s1
print # fffffffd / fffffffe / ffffffff / 00000000
addi s1, s1, 1
bge zero, s1, bge_loop
# ---- bgeu 循环:3 -> 1(共 3 次输出)----
addi t0, zero, 3
bgeu_loop:
add a0, zero, t0
print # 00000003 / 00000002 / 00000001
addi t0, t0, -1
addi t2, zero, 1
bgeu t0, t2, bgeu_loop
# ---- bltu 循环:4 -> 1(共 4 次输出)----
addi s1, zero, 4
bltu_loop:
add a0, zero, s1
print # 00000004 / 00000003 / 00000002 / 00000001
addi s1, s1, -1
bltu zero, s1, bltu_loop
halt
+24
View File
@@ -0,0 +1,24 @@
00000001
00000000
ffffff00
80000000
0000000f
ffffffff
000aad1e
00000000
fffffffe
ffffffff
00000601
00000602
00000603
fffffffd
fffffffe
ffffffff
00000000
00000003
00000002
00000001
00000004
00000003
00000002
00000001
+21
View File
@@ -0,0 +1,21 @@
# LUI 指令测试:依次把 lui 的结果写到 MMIO 的 LED 寄存器,最后停机。
# 期望输出:12345000 / fffff000 / 80000000 / 12345678,随后停机(halted=1)。
# 用法:python tools/rv_asm.py testbench/programs/lui_test.asm <仿真工作目录>/risc-v-benchmark_ccab.hex
lui t0, 0x12345 # t0 = 0x12345000
add a0, zero, t0
print # MMIO 写 LED -> 12345000
lui t0, 0xfffff # t0 = 0xfffff000
add a0, zero, t0
print # -> fffff000
lui t0, 0x80000 # t0 = 0x80000000
add a0, zero, t0
print # -> 80000000
lui t0, 0x12345 # 常见用法 lui+addi:0x12345678
addi t0, t0, 0x678
add a0, zero, t0
print # -> 12345678
halt # 停机
+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
+365
View File
@@ -0,0 +1,365 @@
`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 = 8192,
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,
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
parameter integer USE_PS2_MOUSE = 0
) (
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]:原来的 3 路中断源(保持兼容)
input wire btnu, // M18:光标上
input wire btnd, // P18:光标下
input wire btnl, // P17:光标左
input wire btnr, // M17:光标右
input wire sw_flag, // R15:SW[3],插旗模式(软件读 INPUT 寄存器)
output wire [15:0] led, // LED[15:0]
output wire led_we, // LED16_B:MMIO 写入指示
// 七段数码管(共阳极:位选/段选都是低有效)
output wire [ 7:0] an,
output wire [ 6:0] seg,
output wire dp,
// VGA
output wire [ 3:0] vga_r,
output wire [ 3:0] vga_g,
output wire [ 3:0] vga_b,
output wire vga_hs,
output wire vga_vs,
// PS/2(只有 USE_PS2_MOUSE=1 时才用)
input wire ps2_clk,
input wire ps2_data
);
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;
// 扫雷扩展外设的连线(先声明,例化在下面)
wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动)
wire [31:0] seg_data; // 数码管:8 位十六进制
wire seg_valid;
wire fb_we; // 显存写端口
wire [ 9:0] fb_waddr;
wire [31:0] fb_wdata;
wire [ 3:0] fb_wstrb;
wire [ 9:0] fb_raddr; // 显存读端口(VGA)
wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口)
wire frame_tick;
wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw};
// 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。
wire [ 2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])};
for (i = 0; i < ROM_WORDS; i = i + 1)
rom[i] = 32'h0000_0013; // NOP 填充
`ifdef SAOLEI_ROM
// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。
// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。
`include "cpu21_saolei_rom.vh"
`elsif 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),
// 三个中断入口:必须与 sw/linker.ld 里
// .text.isr1/2/3 的地址一致
// IRQ1(0x100) = 关卡(游戏)入口
// IRQ2(0x200) = 鼠标读取
// IRQ3(0x300) = 备用
.IRQ1_VECTOR (32'h0000_0100),
.IRQ2_VECTOR (32'h0000_0200),
.IRQ3_VECTOR (32'h0000_0300)
) u_cpu (
.clk (clk_cpu),
.reset (reset),
.instr_i (instr_word),
.data_rdata_i(data_rdata),
.irq_i (irq_vec),
.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),
// 扫雷扩展外设
.seg_data_o (seg_data),
.seg_valid_o (seg_valid),
.fb_we_o (fb_we),
.fb_waddr_o (fb_waddr),
.fb_wdata_o (fb_wdata),
.fb_wstrb_o (fb_wstrb),
.fb_rdata_i (fb_rdata),
.disp_ctrl_o (disp_ctrl),
.disp_cursor_o (disp_cursor),
.mouse_ev_valid_i(ev_valid),
.mouse_dx_i (ev_dx),
.mouse_dy_i (ev_dy),
.mouse_btn_i (ev_btn),
.sw_i (sw_vec),
.frame_tick_i (frame_tick),
.mouse_irq_o (mouse_irq),
.level_irq_o (level_irq)
);
// ---------------------------------------------------------------------------
// 板载 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);
// ===========================================================================
// 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线
//
// 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的
// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的
// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。
// ===========================================================================
localparam integer FB_WORDS = 300; // 300 字 = 1200 字节
// ---- 显存 ----
reg [31:0] fb [0:FB_WORDS-1];
integer k;
initial begin
for (k = 0; k < FB_WORDS; k = k + 1)
fb[k] = 32'b0;
end
always @(posedge clk_cpu) begin
if (fb_we) begin
if (fb_wstrb[0])
fb[fb_waddr][7:0] <= fb_wdata[7:0];
if (fb_wstrb[1])
fb[fb_waddr][15:8] <= fb_wdata[15:8];
if (fb_wstrb[2])
fb[fb_waddr][23:16] <= fb_wdata[23:16];
if (fb_wstrb[3])
fb[fb_waddr][31:24] <= fb_wdata[31:24];
end
end
wire [31:0] fb_rdata = fb[fb_raddr];
// ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ----
reg px_q;
always @(posedge clk_cpu or posedge arst) begin
if (arst)
px_q <= 1'b0;
else
px_q <= ~px_q;
end
wire px_tick = ~px_q;
cpu21_vga u_vga (
.clk (clk_cpu),
.reset (reset),
.tick (px_tick),
.fb_raddr_o (fb_raddr),
.fb_word_i (fb_rdata),
.ctrl_i (disp_ctrl),
.cursor_i (disp_cursor),
.vga_r (vga_r),
.vga_g (vga_g),
.vga_b (vga_b),
Tcl 一键重建
ga_hs (vga_hs),
.vga_vs (vga_vs),
.frame_tick_o(frame_tick)
);
// ---- 七段数码管(显示项目程序写的 8 位十六进制数)----
cpu21_seg7 u_seg7 (
.clk (clk_cpu),
.reset (reset),
.seg_data_i(seg_data),
.an_en_i (8'hFF),
.an_o (an),
.seg_o (seg),
.dp_o (dp)
);
// ---- 鼠标事件源 ----
wire ev_valid;
wire [7:0] ev_dx, ev_dy;
wire [4:0] ev_btn;
wire [4:0] btn_vec = {go, btnu, btnd, btnl, btnr}; // {BTNC, BTNU, BTND, BTNL, BTNR}
generate
if (USE_PS2_MOUSE != 0) begin : g_ps2
cpu21_ps2_mouse u_ps2 (
.clk (clk_cpu),
.reset (reset),
.ps2_clk_i (ps2_clk),
.ps2_clk_oe_o (),
.ps2_data_i (ps2_data),
.ps2_data_oe_o(),
.ev_valid_o (ev_valid),
.ev_dx_o (ev_dx),
.ev_dy_o (ev_dy),
.ev_btn_o (ev_btn),
.rx_count_o ()
)
;
end
else begin : g_btn
cpu21_pointer #(
.SAMPLE_DIV(50000)
) u_ptr (
.clk (clk_cpu),
.reset (reset),
.btn_i (btn_vec),
.ev_valid_o(ev_valid),
.ev_dx_o (ev_dx),
.ev_dy_o (ev_dy),
.ev_btn_o (ev_btn)
);
end
endgenerate
endmodule
`default_nettype wire
File diff suppressed because it is too large Load Diff
@@ -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,96 @@
## 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 争用。
## ======================= 扫雷扩展外设 =======================
## 5 向按键:BTNC 已作 go(同时也当鼠标左键),另外 4 个当光标方向
set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { btnu }]
set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { btnd }]
set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { btnl }]
set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { btnr }]
## SW[3]:插旗模式(1 = 点击时插旗/取消旗)
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { sw_flag }]
## 七段数码管(共阳极:位选/段选都是低有效)
set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { seg[0] }]
set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { seg[1] }]
set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { seg[2] }]
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { seg[3] }]
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { seg[4] }]
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { seg[5] }]
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { seg[6] }]
set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { dp }]
set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { an[0] }]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { an[1] }]
set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { an[2] }]
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { an[3] }]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { an[4] }]
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { an[5] }]
set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { an[6] }]
set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { an[7] }]
## VGA(640x480@60,25 MHz 像素节拍由 CPU 时钟使能产生)
set_property -dict { PACKAGE_PIN A3 IOSTANDARD LVCMOS33 } [get_ports { vga_r[0] }]
set_property -dict { PACKAGE_PIN B4 IOSTANDARD LVCMOS33 } [get_ports { vga_r[1] }]
set_property -dict { PACKAGE_PIN C5 IOSTANDARD LVCMOS33 } [get_ports { vga_r[2] }]
set_property -dict { PACKAGE_PIN A4 IOSTANDARD LVCMOS33 } [get_ports { vga_r[3] }]
set_property -dict { PACKAGE_PIN C6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[0] }]
set_property -dict { PACKAGE_PIN A5 IOSTANDARD LVCMOS33 } [get_ports { vga_g[1] }]
set_property -dict { PACKAGE_PIN B6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[2] }]
set_property -dict { PACKAGE_PIN A6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[3] }]
set_property -dict { PACKAGE_PIN B7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[0] }]
set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[1] }]
set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[2] }]
set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 } [get_ports { vga_b[3] }]
set_property -dict { PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports { vga_hs }]
set_property -dict { PACKAGE_PIN B12 IOSTANDARD LVCMOS33 } [get_ports { vga_vs }]
## PS/2 鼠标(可选:Pmod JA 插 Pmod PS2,顶层 USE_PS2_MOUSE=1)
set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 PULLUP true } [get_ports { ps2_clk }]
set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 PULLUP true } [get_ports { ps2_data }]
+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
+310
View File
@@ -0,0 +1,310 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// MMIO:把外设寄存器映射到数据存储器地址空间。
//
// 地址映射(字节地址,都是"字对齐"译码:同一 32 位寄存器内的任意字节偏移都命中,
// 具体改哪几个字节由 wstrb_i 决定,与原来 LED 的语义一致):
//
// 0x0000_1000 LED 数据寄存器(**保持原有语义不变**,led_valid_o 拉高一拍)
// 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右)
// 0x0000_1100 + 4*k 鼠标寄存器组(见下)
// 0x0000_1140 + 4*k 显示控制器寄存器(见下)
// 0x0000_2000 - 0x27FF 显存窗口:40x30 格、每格 1 字节(只用到前 1200 字节)
//
// 鼠标寄存器(基址 0x1100):
// +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满
// +0x04 DATA RO [7:0] dx [15:8] dy [20:16] 按键(读无副作用)
// +0x08 ACK WO 写任意值 -> 弹出 FIFO 一项
// +0x0C CTRL RW [0] 中断使能(复位默认 1)
// +0x10 INPUT RO [15:0] 拨码开关 [20:16] 按键(原样)
// +0x14 TIME RO 自由运行计数器(随机种子)
// +0x18 FRAME RO VGA 帧计数
// +0x1C EVCNT RO 累计鼠标事件数
// +0x20 LEVEL RW [0] 写 1 -> 拉起 level_irq_o(软件中断,用作"关卡入口");
// 关卡程序返回前要写 0 清掉,否则下次没有上升沿
// +0x24 MAGIC RO 0xCAFE0002(自检用:读得到说明 MMIO 活着)
//
// 显示控制器寄存器(基址 0x1140):
// +0x00 CTRL RW [0] 光标可见 [6:4] 背景色(3 位调色板)
// +0x04 CURSOR RW [5:0] 列(0..39) [13:8] 行(0..29)
// +0x08 MAGIC RO 0xCAFE0001
//
// 显存本体不在本模块里:写操作通过 fb_* 端口直接送到板级顶层的双口 RAM(VGA 读
// 同一块 RAM),读操作把 fb_rdata_i 组合回读数据,保持"组合读"的时序不变。
//
// 两个中断源:
// mouse_irq_o = 中断使能 && 事件 FIFO 非空(接 irq_i[0])
// level_irq_o = LEVEL 寄存器(软件中断,接 irq_i[1],用来"进入关卡")
//
// 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。
// -----------------------------------------------------------------------------
module cpu21_mmio #(
parameter [31:0] LED_ADDR = 32'h0000_1000,
parameter integer FB_WORDS = 300,
parameter integer FIFO_DEPTH = 16
) (
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,
// 七段数码管:8 位十六进制数
output reg [31:0] seg_data_o,
output reg seg_valid_o,
// 显存写端口(板级顶层的双口 RAM)
output wire fb_we_o,
output wire [ 9:0] fb_waddr_o,
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i,
// 显示控制器寄存器(给 VGA 用)
output wire [31:0] disp_ctrl_o,
output wire [31:0] disp_cursor_o,
// 鼠标事件源(来自板级顶层:按键去抖 / PS/2 解析)
input wire mouse_ev_valid_i,
input wire [ 7:0] mouse_dx_i,
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i,
input wire [15:0] sw_i,
input wire frame_tick_i,
// 中断
output wire mouse_irq_o,
output wire level_irq_o
);
// ---------------------------------------------------------------------------
// 地址译码(都按"字"对齐:同一 32 位寄存器内的任意字节偏移都算命中)
// ---------------------------------------------------------------------------
localparam [31:0] SEG_ADDR = 32'h0000_1004;
localparam [31:0] MOUSE_BASE = 32'h0000_1100;
localparam [31:0] DISP_BASE = 32'h0000_1140;
localparam integer FIFO_AW = 4;
localparam [FIFO_AW:0] FIFO_FULL_V = FIFO_DEPTH[FIFO_AW:0];
wire sel_led = (addr_i[31:2] == LED_ADDR[31:2]);
wire sel_seg = (addr_i[31:2] == SEG_ADDR[31:2]);
wire sel_mouse = (addr_i[31:8] == MOUSE_BASE[31:8]) && (addr_i[7:6] == 2'b00);
wire sel_disp = (addr_i[31:8] == DISP_BASE[31:8]) && (addr_i[7:6] == 2'b01);
// 显存窗口 0x2000 - 0x27FF(addr[12]=1 且 addr[11]=0)
wire sel_fb = (addr_i[31:13] == 19'd0) && addr_i[12] && !addr_i[11];
wire [3:0] midx = addr_i[5:2];
wire [3:0] didx = addr_i[5:2];
wire [9:0] fb_widx = addr_i[10:2];
wire fb_ok = (fb_widx < FB_WORDS);
wire led_wr = mem_valid_i && mem_write_i && sel_led;
wire led_rd = mem_valid_i && mem_read_i && sel_led;
wire seg_wr = mem_valid_i && mem_write_i && sel_seg;
wire seg_rd = mem_valid_i && mem_read_i && sel_seg;
wire mo_wr = mem_valid_i && mem_write_i && sel_mouse;
wire mo_rd = mem_valid_i && mem_read_i && sel_mouse;
wire dp_wr = mem_valid_i && mem_write_i && sel_disp;
wire dp_rd = mem_valid_i && mem_read_i && sel_disp;
wire fb_wr = mem_valid_i && mem_write_i && sel_fb && fb_ok;
wire fb_rd = mem_valid_i && mem_read_i && sel_fb && fb_ok;
assign fb_we_o = fb_wr;
assign fb_waddr_o = fb_widx;
assign fb_wdata_o = wdata_i;
assign fb_wstrb_o = wstrb_i;
assign rdata_hit_o = led_rd | seg_rd | mo_rd | dp_rd | fb_rd;
// 字节写合并:sw 改 4 字节、sb 只改 1 字节(与 RAM 的字节写语义一致)
function [31:0] merge_bytes;
input [31:0] new_v;
input [31:0] old_v;
input [ 3:0] strb;
begin
merge_bytes[7:0] = strb[0] ? new_v[7:0] : old_v[7:0];
merge_bytes[15:8] = strb[1] ? new_v[15:8] : old_v[15:8];
merge_bytes[23:16] = strb[2] ? new_v[23:16] : old_v[23:16];
merge_bytes[31:24] = strb[3] ? new_v[31:24] : old_v[31:24];
end
endfunction
// ---------------------------------------------------------------------------
// LED:保持原有语义(led_valid_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 <= 1'b0;
if (led_wr) begin
led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i);
led_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 七段数码管:8 位十六进制数(低 4 位 = 最右边那个数码管)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
seg_data_o <= 32'b0;
seg_valid_o <= 1'b0;
end
else begin
seg_valid_o <= 1'b0;
if (seg_wr) begin
seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i);
seg_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 显示控制器寄存器(显存本体在板级顶层)
// ---------------------------------------------------------------------------
reg [31:0] disp_ctrl_q, disp_cursor_q;
assign disp_ctrl_o = disp_ctrl_q;
assign disp_cursor_o = disp_cursor_q;
// ---------------------------------------------------------------------------
// 鼠标:事件 FIFO + 寄存器 + 中断
// ---------------------------------------------------------------------------
reg [31:0] fifo [0:FIFO_DEPTH-1];
reg [FIFO_AW-1:0] wr_ptr_q, rd_ptr_q;
reg [FIFO_AW:0] cnt_q;
reg [31:0] mctrl_q, time_q, frame_q, evcnt_q;
reg level_q;
wire fifo_empty = (cnt_q == {FIFO_AW+1{1'b0}});
wire fifo_full = (cnt_q == FIFO_FULL_V);
wire [31:0] fifo_head = fifo[rd_ptr_q];
wire irq_en = mctrl_q[0];
assign mouse_irq_o = irq_en && !fifo_empty;
assign level_irq_o = level_q;
wire push = mouse_ev_valid_i && !fifo_full;
wire pop = mo_wr && (midx == 4'd2) && !fifo_empty;
always @(posedge clk or posedge reset) begin
if (reset) begin
mctrl_q <= 32'h0000_0001; // 鼠标中断默认打开
time_q <= 32'd0;
frame_q <= 32'd0;
evcnt_q <= 32'd0;
level_q <= 1'b0;
wr_ptr_q <= {FIFO_AW{1'b0}};
rd_ptr_q <= {FIFO_AW{1'b0}};
cnt_q <= {(FIFO_AW+1){1'b0}};
disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0
disp_cursor_q <= 32'h0000_0000;
end
else begin
time_q <= time_q + 32'd1;
if (frame_tick_i)
frame_q <= frame_q + 32'd1;
if (push) begin
fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i};
wr_ptr_q <= wr_ptr_q + 1'b1;
evcnt_q <= evcnt_q + 32'd1;
end
if (pop)
rd_ptr_q <= rd_ptr_q + 1'b1;
cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0);
// 鼠标寄存器:CTRL(3) / LEVEL(8)
if (mo_wr) begin
case (midx)
4'd3:
mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i);
4'd8:
level_q <= wdata_i[0];
default:
;
endcase
end
// 显示控制器:CTRL(0) / CURSOR(1)
if (dp_wr) begin
case (didx)
4'd0:
disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i);
4'd1:
disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i);
default:
;
endcase
end
end
end
// ---------------------------------------------------------------------------
// 读数据(组合,与"指令存储器组合读/数据存储器组合读"的时序一致)
// ---------------------------------------------------------------------------
reg [31:0] rdata_mux;
always @* begin
rdata_mux = 32'b0;
if (led_rd) begin
rdata_mux = led_data_o;
end
else if (seg_rd) begin
rdata_mux = seg_data_o;
end
else if (fb_rd) begin
rdata_mux = fb_rdata_i;
end
else if (mo_rd) begin
case (midx)
4'd0:
rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty};
4'd1:
rdata_mux = fifo_head;
4'd3:
rdata_mux = mctrl_q;
4'd4:
rdata_mux = {11'b0, mouse_btn_i, sw_i};
4'd5:
rdata_mux = time_q;
4'd6:
rdata_mux = frame_q;
4'd7:
rdata_mux = evcnt_q;
4'd8:
rdata_mux = {31'b0, level_q};
4'd9:
rdata_mux = 32'hCAFE_0002;
default:
rdata_mux = 32'b0;
endcase
end
else if (dp_rd) begin
case (didx)
4'd0:
rdata_mux = disp_ctrl_q;
4'd1:
rdata_mux = disp_cursor_q;
4'd2:
rdata_mux = 32'hCAFE_0001;
default:
rdata_mux = 32'b0;
endcase
end
end
assign rdata_o = rdata_mux;
endmodule
`default_nettype wire
+106
View File
@@ -0,0 +1,106 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_pointer —— 把板上的 5 向按键变成"鼠标"事件源(板级顶层用它给 MMIO 送事件)。
//
// 事件单位:1/8 格。按键一次 = 8 个单位 = 光标移动一格,这样软件端用
// 纯整数加减(不需要任何除法)就能把累积量换算成格,配合 PS/2 鼠标时
// 手感也一致(PS/2 的原始计数直接按 1/8 格处理)。
//
// BTNU -> dy = -8 BTND -> dy = +8
// BTNL -> dx = -8 BTNR -> dx = +8
// BTNC -> 左键按下(btn[0] = 1,dx = dy = 0)
//
// 去抖方式:用 ~1 ms 的采样节拍(50 MHz 下计数 50000)采样两级同步后的按键,
// 与上一次的稳定值比较,只在"按下"的边沿产生事件。一次采样可能同时检测到
// 多个按下,此时用 press_pend_q 排队,由优先级选择器逐个上报(每个时钟周期
// 最多一个事件),因此不会丢按键。
// -----------------------------------------------------------------------------
module cpu21_pointer #(
// 采样节拍 = clk / (SAMPLE_DIV),默认 50 MHz / 50000 = 1 kHz
parameter integer SAMPLE_DIV = 50000
) (
input wire clk,
input wire reset,
input wire [4:0] btn_i, // {BTNC, BTNU, BTND, BTNL, BTNR}
output wire ev_valid_o, // 单周期脉冲
output wire [7:0] ev_dx_o, // 有符号,1/8 格
output wire [7:0] ev_dy_o,
output wire [4:0] ev_btn_o
);
// ---------------------------------------------------------------------------
// 1 ms 采样节拍
// ---------------------------------------------------------------------------
reg [15:0] div_q;
wire sample = (div_q == (SAMPLE_DIV - 1));
always @(posedge clk or posedge reset) begin
if (reset)
div_q <= 16'd0;
else if (sample)
div_q <= 16'd0;
else
div_q <= div_q + 16'd1;
end
// ---------------------------------------------------------------------------
// 两级同步 + 采样保持(采样率远低于按键抖动频率,等效去抖)
// ---------------------------------------------------------------------------
reg [4:0] btn_sync1_q, btn_sync2_q, btn_stable_q;
always @(posedge clk or posedge reset) begin
if (reset) begin
btn_sync1_q <= 5'b0;
btn_sync2_q <= 5'b0;
btn_stable_q <= 5'b0;
end
else begin
btn_sync1_q <= btn_i;
btn_sync2_q <= btn_sync1_q;
if (sample)
btn_stable_q <= btn_sync2_q;
end
end
// 只在按下的边沿产生新事件
wire [4:0] press_new = sample ? (btn_sync2_q & ~btn_stable_q) : 5'b0;
// ---------------------------------------------------------------------------
// 待上报队列 + 优先级选择(每周期最多上报一个)
// ---------------------------------------------------------------------------
reg [4:0] press_pend_q;
wire [4:0] pend_next = press_pend_q | press_new;
reg [4:0] sel;
always @* begin
sel = 5'b0;
if (pend_next[4])
sel = 5'b10000; // BTNC:点击
else if (pend_next[3])
sel = 5'b01000; // BTNU:上
else if (pend_next[2])
sel = 5'b00010; // BTND:下
else if (pend_next[1])
sel = 5'b00001; // BTNL:左
else if (pend_next[0])
sel = 5'b00100; // BTNR:右
end
always @(posedge clk or posedge reset) begin
if (reset)
press_pend_q <= 5'b0;
else
press_pend_q <= pend_next & ~sel;
end
assign ev_valid_o = |pend_next;
assign ev_dx_o = sel[1] ? 8'd248 : (sel[0] ? 8'd8 : 8'd0); // 248 = -8 的补码
assign ev_dy_o = sel[3] ? 8'd248 : (sel[2] ? 8'd8 : 8'd0);
assign ev_btn_o = {4'b0, sel[4]};
endmodule
`default_nettype wire
Tcl 一键重建
+145
View File
@@ -0,0 +1,145 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_ps2_mouse —— 可选的真鼠标输入:PS/2 鼠标(Digilent Pmod PS2 插在 JA 上)。
//
// JA[1] = PS/2 DATA,JA[2] = PS/2 CLK(两条线都需要上拉,见 XDC 里的 PULLUP)。
//
// 注意:本模块没有在真实硬件上验证过(作者手头没有 PS/2 鼠标),如果你不用真
// 鼠标,请让顶层用 cpu21_pointer(5 向按键)作为事件源即可,C 程序完全不用改。
// 板级顶层 cpu21_board_top 用参数 USE_PS2_MOUSE 选择这两者。
//
// 实现内容:
// 1) 接收:在 ps2_clk 的下降沿采样 ps2_data,收满 11 位(起始 0 + 8 数据
// LSB 先 + 奇校验 + 停止 1)后校验并拼成 3 字节一包;
// 2) 解析:byte0 = 标志(bit0 左键、bit1 右键、bit3 固定 1),byte1 = dx,
// byte2 = dy,bit7 为符号位;
// 3) 上报:当 dx/dy 非 0 或按键状态变化时产生一个事件(单位与
// cpu21_pointer 相同,即原始计数 = 1/8 格);
// 4) 本模块只做"接收",不实现主机→鼠标的命令发送(0xF4 使能上报)。多数
// PS/2 鼠标上电后就会主动上报,这类鼠标可以直接用;如果你的鼠标上电毫无
// 反应,说明它需要主机先发 0xF4 —— 那部分需要自行补上,或者直接用默认的
// 5 向按键(cpu21_pointer),C 程序完全不用改。
// -----------------------------------------------------------------------------
module cpu21_ps2_mouse (
input wire clk,
input wire reset,
// PS/2 双向线(开漏)
input wire ps2_clk_i,
output wire ps2_clk_oe_o,
input wire ps2_data_i,
output wire ps2_data_oe_o,
// 事件上报
output reg ev_valid_o,
output reg [7:0] ev_dx_o,
output reg [7:0] ev_dy_o,
output reg [4:0] ev_btn_o,
// 调试:收到的字节数(可选观察)
output reg [15:0] rx_count_o
);
// 开漏:oe = 1 时把线拉低,否则高阻(外部上拉)
assign ps2_clk_oe_o = 1'b0; // 本设计只接收,不主动拉低时钟(除了初始化)
assign ps2_data_oe_o = init_oe;
。本模块只接收,两条线都不驱动。
assign ps2_clk_oe_o = 1'b0;
assign ps2_data_oe_o = 1'b02_data_s1, ps2_data_s2;
always @(posedge clk or posedge reset) begin
if (reset) begin
ps2_clk_s1 <= 1'b1;
ps2_clk_s2 <= 1'b1;
ps2_data_s1 <= 1'b1;
ps2_data_s2 <= 1'b1;
end
else begin
ps2_clk_s1 <= ps2_clk_i;
ps2_clk_s2 <= ps2_clk_s1;
ps2_data_s1 <= ps2_data_i;
ps2_data_s2 <= ps2_data_s1;
end
end
wire clk_fall = ~ps2_clk_s1 & ps2_clk_s2; // 同步后的下降沿
wire clk_rise = ps2_clk_s1 & ~ps2_clk_s2;
// ---------------------------------------------------------------------------
// 接收移位:11 位一帧,bit0 = 起始位,bit1..8 = 数据,bit9 = 校验,bit10 = 停止
// ---------------------------------------------------------------------------
reg [3:0] bit_cnt_q;
reg [10:0] frame_q;
reg frame_ok_q;
reg [23:0] pkt_q; // 3 字节包
reg [1:0] pkt_cnt_q;
reg [7:0] byte_cnt_q; // 一包内的字节序号(0..2)
reg [7:0] dx_q, dy_q;
reg [4:0] btn_q; // {4'b0, middle, right, left} -> 只取低 3 位使用
// 校验:11 位中 1 的个数应为奇数(奇校验)
function automatic odd_parity_ok(input [10:0] f);
odd_parity_ok = ^{f[9:1]}; // data + parity 共 9 位,1 的个数应为偶数
endfunction
always @(posedge clk or posedge reset) begin
if (reset) begin
bit_cnt_q <= 4'd0;
frame_q <= 11'd0;
frame_ok_q <= 1'b0;
byte_cnt_q <= 8'd0;
pkt_q <= 24'd0;
rx_count_o <= 16'd0;
ev_valid_o <= 1'b0;
end
else begin
ev_valid_o <= 1'b0;
if (clk_fall) begin
if (bit_cnt_q == 4'd0 && ps2_data_s2 == 1'b0) begin
// 起始位有效,开始收
frame_q <= {ps2_data_s2, frame_q[10:1]};
bit_cnt_q <= 4'd1;
end
else if (bit_cnt_q != 4'd0) begin
frame_q <= {ps2_data_s2, frame_q[10:1]};
if (bit_cnt_q == 4'd10) begin
bit_cnt_q <= 4'd0;
// f = {停止, 校验, 8 数据, 起始},这里只关心数据与校验
if (odd_parity_ok(frame_q) && (frame_q[10] == 1'b1)) begin
rx_count_o <= rx_count_o + 16'd1;
case (byte_cnt_q)
8'd0: begin
// 标志字节:bit3 必须为 1(否则是鼠标发来的其他响应,丢弃)
if (frame_q[8:1][3] == 1'b1) begin
pkt_q[7:0] <= frame_q[8:1];
byte_cnt_q <= 8'd1;
end
end
8'd1: begin
pkt_q[15:8] <= frame_q[8:1]; // dx
byte_cnt_q <= 8'd2;
end
8'd2: begin
byte_cnt_q <= 8'd0;
ev_dx_o <= frame_q[8:1]; // dy
ev_dy_o <= pkt_q[15:8]; // 注意:byte1 是 dx
ev_btn_o <= {2'b0, pkt_q[2], pkt_q[1]}; // {middle, right, left}
ev_valid_o <= 1'b1;
end
default:
byte_cnt_q <= 8'd0;
endcase
end
end
else begin
bit_cnt_q <= bit_cnt_q + 4'd1;
end
end
end
end
end
endmodule
`default_nettype wire
Tcl 一键重建
+65
View File
@@ -0,0 +1,65 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// ALU:组合运算单元(只做加减/移位/逻辑/比较等单拍运算)。
//
// 乘法与除法已移到 cpu21_riscv_muldiv.v(时序多周期单元),因为组合的 a*b / a%b
// 关键路径过长(综合后约 94 ns,100 MHz 下无法收敛)。因此本模块不再实现
// ALU_MUL / ALU_DIVU:这两个操作码只作为“这是乘除法指令”的标识,对应结果由
// 顶层从 muldiv 的输出选取(result2 的 wb_sel=3 约定保持不变)。
//
// result 为主结果;result2 为第二结果,由译码器用 wb_sel=3 选取。
// -----------------------------------------------------------------------------
module cpu21_riscv_alu (
input wire [ 3:0] op,
input wire [31:0] a,
input wire [31:0] b,
output reg [31:0] result,
output reg [31:0] result2
);
localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1;
localparam ALU_SRL = 4'd2;
localparam ALU_MUL = 4'd3;
localparam ALU_DIVU = 4'd4;
localparam ALU_ADD = 4'd5;
localparam ALU_SUB = 4'd6;
localparam ALU_AND = 4'd7;
localparam ALU_OR = 4'd8;
localparam ALU_XOR = 4'd9;
localparam ALU_NOR = 4'd10;
localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12;
localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // 直接输出 B 端(供 LUI 使用)
always @* begin
result = 32'b0;
result2 = 32'b0;
case (op)
ALU_SLL: result = a << b[4:0];
ALU_SRA: result = $signed(a) >>> b[4:0];
ALU_SRL: result = a >> b[4:0];
// 乘法(ALU_MUL)与除法(ALU_DIVU)在本模块内不产生结果:
// 它们由 cpu21_riscv_muldiv 多周期单元完成,顶层用 ex_muldiv 选择其输出。
ALU_MUL: ;
ALU_DIVU: ;
ALU_ADD: result = a + b;
ALU_SUB: result = a - b;
ALU_AND: result = a & b;
ALU_OR: result = a | b;
ALU_XOR: result = a ^ b;
ALU_NOR: result = ~(a | b);
ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0;
ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0;
ALU_MULH: ;
ALU_MULHSU: ;
ALU_PASSB: result = b;
default: result = 32'b0;
endcase
end
endmodule
`default_nettype wire
@@ -18,7 +18,9 @@ module cpu21_riscv_branch_unit (
input wire beq_i, input wire beq_i,
input wire bne_i, input wire bne_i,
input wire blt_i, input wire blt_i,
input wire bge_i,
input wire bltu_i, input wire bltu_i,
input wire bgeu_i,
input wire jal_i, input wire jal_i,
input wire jalr_i, input wire jalr_i,
// IF 级携带过来的预测信息 // IF 级携带过来的预测信息
@@ -31,15 +33,20 @@ module cpu21_riscv_branch_unit (
output wire redirect_valid_o, output wire redirect_valid_o,
output wire [31:0] redirect_pc_o output wire [31:0] redirect_pc_o
); );
// 条件分支裁决:beq 相等、bne 不等、blt 有符号小于、bltu 无符号小于。 // 比较结果预先算好,避免表达式过长被自动换行拆散。
wire lt_signed = $signed(src1_i) < $signed(src2_i);
wire ge_signed = $signed(src1_i) >= $signed(src2_i);
wire lt_unsigned = src1_i < src2_i;
wire ge_unsigned = src1_i >= src2_i;
// 条件分支裁决:beq 相等、bne 不等、blt/bge 有符号比较、bltu/bgeu 无符号比较。
assign branch_taken_o = branch_i && assign branch_taken_o = branch_i &&
((beq_i && (src1_i == src2_i)) || ((beq_i && (src1_i == src2_i)) ||
(bne_i && (src1_i != src2_i)) || (bne_i && (src1_i != src2_i)) ||
(blt_i && ($signed( (blt_i && lt_signed) ||
src1_i (bge_i && ge_signed) ||
) < $signed( (bltu_i && lt_unsigned) ||
src2_i (bgeu_i && ge_unsigned));
))) || (bltu_i && (src1_i < src2_i)));
// 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。 // 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。
assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i); assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i);
@@ -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,
@@ -29,7 +30,9 @@ module cpu21_riscv_decoder (
output reg beq_o, output reg beq_o,
output reg bne_o, output reg bne_o,
output reg blt_o, output reg blt_o,
output reg bge_o,
output reg bltu_o, output reg bltu_o,
output reg bgeu_o,
output reg jal_o, output reg jal_o,
output reg jalr_o, output reg jalr_o,
output reg ecall_o, output reg ecall_o,
@@ -47,6 +50,8 @@ module cpu21_riscv_decoder (
localparam OP_BRANCH = 5'h18; localparam OP_BRANCH = 5'h18;
localparam OP_JAL = 5'h1b; localparam OP_JAL = 5'h1b;
localparam OP_SYS = 5'h1c; localparam OP_SYS = 5'h1c;
// LUI 为扩展指令(标准 RISC-V opcode 0x37 -> IR[6:2]=0x0d)。
localparam OP_LUI = 5'h0d;
localparam ALU_SLL = 4'd0; localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1; localparam ALU_SRA = 4'd1;
@@ -60,6 +65,18 @@ module cpu21_riscv_decoder (
localparam ALU_XOR = 4'd9; localparam ALU_XOR = 4'd9;
localparam ALU_SLT = 4'd11; localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12; localparam ALU_SLTU = 4'd12;
localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14;
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];
@@ -76,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;
@@ -87,7 +105,9 @@ module cpu21_riscv_decoder (
beq_o = 1'b0; beq_o = 1'b0;
bne_o = 1'b0; bne_o = 1'b0;
blt_o = 1'b0; blt_o = 1'b0;
bge_o = 1'b0;
bltu_o = 1'b0; bltu_o = 1'b0;
bgeu_o = 1'b0;
jal_o = 1'b0; jal_o = 1'b0;
jalr_o = 1'b0; jalr_o = 1'b0;
ecall_o = 1'b0; ecall_o = 1'b0;
@@ -102,13 +122,40 @@ 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;
// 电路中额外的 REMU 控制对应标准 R 型的 funct7=1/funct3=111 // M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,以及课程真值表里的
// 形式;此时 ALU 的 result2 即为余数。 // divu(funct3=101,商)与 remu(funct3=111,余数)。它们都由多周期
if ((funct7 == 7'b0000001) && (funct3 == 3'b111)) begin // 乘除法单元完成,这里只给出操作类型 md_op_o;mulhu 与 remu 依旧用
alu_op_o = ALU_DIVU; // wb_sel=3 取 result2(高位乘积 / 余数)。
wb_sel_o = 3'd3; if (funct7 == 7'b0000001) begin
end else if ((funct7 == 7'b0000001) && (funct3 == 3'b000)) begin case (funct3)
alu_op_o = ALU_MUL; 3'b000: begin // mul:低 32 位
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;
end
3'b101: begin // 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;
end
default: reg_write_o = 1'b0;
endcase
end else begin end else begin
// 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。 // 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。
case (funct3) case (funct3)
@@ -125,7 +172,7 @@ module cpu21_riscv_decoder (
end end
end end
// I 型运算(addi/slli/slti/xori/srai/srli/ori/andi)。 // I 型运算(addi/slli/slti/sltiu/xori/srai/srli/ori/andi)。
OP_I: begin OP_I: begin
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
alu_src_o = 1'b1; alu_src_o = 1'b1;
@@ -134,6 +181,7 @@ module cpu21_riscv_decoder (
3'b000: alu_op_o = ALU_ADD; // addi:立即数加 3'b000: alu_op_o = ALU_ADD; // addi:立即数加
3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移 3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移
3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1 3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1
3'b011: alu_op_o = ALU_SLTU; // sltiu:无符号小于置 1(立即数符号扩展)
3'b100: alu_op_o = ALU_XOR; // xori:立即数异或 3'b100: alu_op_o = ALU_XOR; // xori:立即数异或
3'b101: 3'b101:
alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移 alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移
@@ -143,6 +191,16 @@ module cpu21_riscv_decoder (
endcase endcase
end end
// 加载高位立即数 lui:rd = imm[31:12] << 12。
// 不读寄存器;用 ALU_SRC 选立即数,ALU 直通 B 端输出。
OP_LUI: begin
reg_write_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_PASSB;
imm_o = {ir_i[31:12], 12'b0};
wb_sel_o = 3'd0;
end
// 加载指令,当前仅支持 lw(funct3=010)。 // 加载指令,当前仅支持 lw(funct3=010)。
OP_LOAD: begin OP_LOAD: begin
if (funct3 == 3'b010) begin if (funct3 == 3'b010) begin
@@ -168,19 +226,22 @@ module cpu21_riscv_decoder (
end end
end end
// 条件分支:beq(000)/bne(001)/blt(100,有符号小于)/bltu(110,无符号小于)。 // 条件分支:beq(000)/bne(001)/blt(100)/bge(101)/bltu(110)/bgeu(111)。
OP_BRANCH: begin OP_BRANCH: begin
if ((funct3 == 3'b000) || (funct3 == 3'b001) || if ((funct3 == 3'b000) || (funct3 == 3'b001) || (funct3 == 3'b100) ||
(funct3 == 3'b100) || (funct3 == 3'b110)) begin (funct3 == 3'b101) || (funct3 == 3'b110) || (funct3 == 3'b111)) begin
uses_rs1_o = 1'b1; uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1; uses_rs2_o = 1'b1;
branch_o = 1'b1; branch_o = 1'b1;
beq_o = (funct3 == 3'b000); beq_o = (funct3 == 3'b000);
bne_o = (funct3 == 3'b001); bne_o = (funct3 == 3'b001);
blt_o = (funct3 == 3'b100); blt_o = (funct3 == 3'b100);
bge_o = (funct3 == 3'b101);
bltu_o = (funct3 == 3'b110); bltu_o = (funct3 == 3'b110);
// 真值表:beq/bne 用减法比较,blt 用 ALU_SLT,bltu 用 ALU_SLTU。 bgeu_o = (funct3 == 3'b111);
alu_op_o = blt_o ? ALU_SLT : (bltu_o ? ALU_SLTU : ALU_SUB); // 有符号比较用 ALU_SLT、无符号比较用 ALU_SLTU,相等/不等用减法;
// 分支是否成立由 EX 级的分支裁决单元决定,这里只与真值表保持一致。
alu_op_o = (blt_o || bge_o) ? ALU_SLT : ((bltu_o || bgeu_o) ? ALU_SLTU : ALU_SUB);
imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0}; imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0};
end end
end end
@@ -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 级条件分支有效
@@ -0,0 +1,895 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21-risc-v-4.circ -> 可综合 Verilog 实现(顶层)
//
// 处理器为五级流水线 IF/ID/EX/MEM/WB。本文件只保留:
// * 五级流水寄存器与各级之间的连线;
// * 各功能子模块的例化;
// * PC / IF-ID / ID-EX 的控制优先级(中断、URET、停机、重定向、停顿、
// 正常推进);
// * 对外输出与调试信号。
//
// 各功能单元已拆分为独立文件,便于单独仿真调试:
// cpu21_riscv_alu.v 组合 ALU(result2 用于 MUL 高位 / 余数)
// cpu21_bpb_8.v 八表项全相联分支目标缓冲
// cpu21_riscv_decoder.v ID 级组合译码器
// cpu21_riscv_regfile.v 寄存器堆(写优先旁路)
// cpu21_riscv_forward_unit.v EX 级前递网络
// cpu21_riscv_store_unit.v EX 级存储数据对齐
// cpu21_riscv_branch_unit.v EX 级控制流裁决
// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定
// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈
// cpu21_riscv_perf_counters.v 性能计数器
// cpu21_mmio.v MMIO:LED 数据寄存器(可写、可读回)
//
// 主要特性:
// * EX 级前递(forwarding)与 load-use 冒险互锁;
// * EX 级控制流裁决与流水线重定向;
// * 八表项全相联分支目标缓冲(BPB);
// * 三路采样的中断源,带优先级与可嵌套的 URET 返回;
// * LED 显示通过 MMIO 完成:向 LED_ADDR(默认 0x0000_1000)存储即写 LED,
// 也可以从该地址读回 LED 当前值;
// * ecall 仍用于停机:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
// 令流水线停机并等待 go_i。
//
// 存储器接口约定:
// instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码,
// 因此外部字宽 RAM 可用 data_addr_o[11:2] 作为字索引,再依据
// data_wstrb_o 完成字节合并。
// -----------------------------------------------------------------------------
module cpu21_riscv_redirect_int_bpb #(
parameter RESET_PC = 32'h0000_0000,
parameter IRQ1_VECTOR = 32'h0000_30ac,
parameter IRQ2_VECTOR = 32'h0000_31e4,
parameter IRQ3_VECTOR = 32'h0000_3310,
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 中的说明。
parameter [31:0] USTATUS_INIT = 32'h0000_0001
) (
input wire clk,
input wire reset,
input wire [31:0] instr_i,
input wire [31:0] data_rdata_i,
input wire [ 2:0] irq_i,
input wire go_i,
output wire [31:0] instr_addr_o,
output wire [31:0] data_addr_o,
output wire [31:0] data_wdata_o,
output wire [ 3:0] data_wstrb_o,
output wire data_we_o,
output wire [31:0] led_data_o,
output wire led_valid_o,
// ---- 扫雷扩展外设(由 cpu21_mmio 驱动;板级顶层接 VGA/数码管/鼠标)----
output wire [31:0] seg_data_o, // 七段数码管:8 位十六进制数
output wire seg_valid_o,
output wire fb_we_o, // 显存写端口(板级顶层的双口 RAM)
output wire [ 9:0] fb_waddr_o, // 显存字地址(0..299)
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i, // 显存读回(组合)
output wire [31:0] disp_ctrl_o, // 显示控制器寄存器(给 VGA)
output wire [31:0] disp_cursor_o,
input wire mouse_ev_valid_i, // 鼠标事件源(板级顶层:按键去抖 / PS/2)
input wire [ 7:0] mouse_dx_i, // 单位 1/8 格,有符号
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i, // {BTNC,BTNU,BTND,BTNL,BTNR}
input wire [15:0] sw_i, // 板载拨码(软件读 INPUT 寄存器)
input wire frame_tick_i, // VGA 每帧一个脉冲
output wire mouse_irq_o, // 鼠标事件中断(板级顶层接 irq_i[0])
output wire level_irq_o, // 软件中断:关卡入口(接 irq_i[1])
output wire [31:0] if_pc_o,
output wire [31:0] id_pc_o,
output wire [31:0] ex_pc_o,
output wire [31:0] mem_pc_o,
output wire [31:0] wb_pc_o,
output wire [31:0] if_ir_o,
output wire [31:0] id_ir_o,
output wire [31:0] ex_ir_o,
output wire [31:0] mem_ir_o,
output wire [31:0] wb_ir_o,
output wire [31:0] rdin_o,
output wire [31:0] mdin_o,
output wire reg_write_o,
output wire mem_write_o,
output wire halted_o,
output wire [ 2:0] irq_pending_o,
output wire [ 1:0] irq_current_level_o,
output wire [31:0] uepc_o,
output wire [31:0] ustatus_o,
output wire bpb_predict_hit_o,
output wire bpb_predict_taken_o,
output wire bpb_mispredict_o,
output wire [15:0] cycle_count_o,
output wire [15:0] stall_count_o,
output wire [15:0] bubble_count_o,
output wire [15:0] conditional_taken_count_o,
output wire [15:0] unconditional_branch_count_o,
output wire [15:0] prediction_success_count_o,
output wire [15:0] prediction_failure_count_o
);
// 顶层只用到少量常量,完整译码表见 cpu21_riscv_decoder.v。
localparam OP_BRANCH = 5'h18;
localparam ALU_ADD = 4'd5;
// -------------------------------------------------------------------------
// 流水线寄存器
// -------------------------------------------------------------------------
// 当前取指 PC。
reg [31:0] pc_q;
// IF/ID 流水寄存器(含本条指令所携带的分支预测信息)。
reg ifid_valid_q;
reg [31:0] ifid_pc_q;
reg [31:0] ifid_ir_q;
reg ifid_pred_taken_q;
reg [31:0] ifid_pred_target_q;
// ID/EX 流水寄存器:保存译码结果与操作数。
reg idex_valid_q;
reg [31:0] idex_pc_q;
reg [31:0] idex_ir_q;
reg [31:0] idex_rs1_value_q;
reg [31:0] idex_rs2_value_q;
reg [4:0] idex_rs1_idx_q;
reg [4:0] idex_rs2_idx_q;
reg [4:0] idex_rd_q;
reg [31:0] idex_imm_q;
reg [3:0] idex_aluop_q;
reg idex_alu_src_q;
reg idex_reg_write_q;
reg idex_mem_to_reg_q;
reg idex_mem_write_q;
reg idex_mem_byte_q;
reg [2:0] idex_wb_sel_q;
reg [2:0] idex_md_op_q; // 乘除法操作类型(0=非乘除法)
reg [11:0] idex_csr_addr_q;
reg idex_branch_q;
reg idex_beq_q;
reg idex_bne_q;
reg idex_blt_q;
reg idex_bge_q;
reg idex_bltu_q;
reg idex_bgeu_q;
reg idex_jal_q;
reg idex_jalr_q;
reg idex_ecall_q;
reg idex_uret_q;
reg idex_csr_set_q;
reg idex_csr_clear_q;
reg idex_csr_write_q;
reg idex_pred_taken_q;
reg [31:0] idex_pred_target_q;
// EX/MEM 流水寄存器:保存 ALU/存储结果与写回控制。
reg exmem_valid_q;
reg [31:0] exmem_pc_q;
reg [31:0] exmem_ir_q;
reg [31:0] exmem_alu_result_q;
reg [31:0] exmem_alu_result2_q;
reg [31:0] exmem_csr_old_q;
reg [31:0] exmem_write_data_q;
reg [4:0] exmem_rd_q;
reg exmem_reg_write_q;
reg exmem_mem_to_reg_q;
reg exmem_mem_write_q;
reg [3:0] exmem_wstrb_q;
reg [2:0] exmem_wb_sel_q;
// MEM/WB 流水寄存器:保存访存数据与最终写回信息。
reg memwb_valid_q;
reg [31:0] memwb_pc_q;
reg [31:0] memwb_ir_q;
reg [31:0] memwb_alu_result_q;
reg [31:0] memwb_alu_result2_q;
reg [31:0] memwb_csr_old_q;
reg [31:0] memwb_mem_data_q;
reg [4:0] memwb_rd_q;
reg memwb_reg_write_q;
reg memwb_mem_to_reg_q;
reg [2:0] memwb_wb_sel_q;
// 停机标志(LED 寄存器已移入 cpu21_mmio.v)。
reg halted_q;
// -------------------------------------------------------------------------
// IF 级:分支预测。JAL/JALR 在 EX 级裁决,条件分支则可以直接依据 BPB
// 给出的目标地址重定向取指 PC。
// -------------------------------------------------------------------------
wire [4:0] f_opcode = instr_i[6:2];
wire [2:0] f_funct3 = instr_i[14:12];
wire f_is_branch = (f_opcode == OP_BRANCH) &&
((f_funct3 == 3'b000) || (f_funct3 == 3'b001) ||
(f_funct3 == 3'b100) || (f_funct3 == 3'b101) ||
(f_funct3 == 3'b110) || (f_funct3 == 3'b111));
wire [31:0] bpb_predict_target;
wire bpb_predict_hit;
wire bpb_predict_taken;
wire ex_branch_taken;
wire [31:0] ex_target;
wire ex_controlflow;
wire ex_redirect_valid;
wire [31:0] ex_redirect_pc;
// BPB 更新端口:在 EX 级用条件分支的实际结果训练预测器。
wire bpb_update_enable = idex_valid_q && idex_branch_q;
wire bpb_update_taken = ex_branch_taken;
wire [31:0] bpb_update_target = ex_target;
// 分支目标缓冲实例。
cpu21_bpb_8 u_bpb (
.clk (clk),
.reset (reset),
.predict_pc (pc_q),
.predict_enable (f_is_branch),
.predict_hit (bpb_predict_hit),
.predict_taken (bpb_predict_taken),
.predict_target (bpb_predict_target),
.update_enable (bpb_update_enable),
.update_controlflow(1'b1),
.update_pc (idex_pc_q),
.update_target (bpb_update_target),
.update_taken (bpb_update_taken)
);
// 下一取指 PC:预测跳转命中时使用 BPB 目标地址,否则顺序加 4。
wire [31:0] fetch_next_pc =
(f_is_branch && bpb_predict_hit && bpb_predict_taken) ?
bpb_predict_target : (pc_q + 32'd4);
// -------------------------------------------------------------------------
// ID 级:译码与寄存器堆读取
// -------------------------------------------------------------------------
wire [4:0] d_src1_idx;
wire [4:0] d_src2_idx;
wire [4:0] d_rd;
wire [31:0] d_imm;
wire [3:0] d_aluop;
wire [2:0] d_wb_sel;
wire [2:0] d_md_op;
wire [11:0] d_csr_addr;
wire d_uses_rs1;
wire d_uses_rs2;
wire d_reg_write;
wire d_mem_to_reg;
wire d_mem_write;
wire d_mem_byte;
wire d_alu_src;
wire d_branch;
wire d_beq;
wire d_bne;
wire d_blt;
wire d_bge;
wire d_bltu;
wire d_bgeu;
wire d_jal;
wire d_jalr;
wire d_ecall;
wire d_uret;
wire d_csr_set;
wire d_csr_clear;
wire d_csr_write;
cpu21_riscv_decoder u_decoder (
.ir_i (ifid_ir_q),
.rs1_idx_o (d_src1_idx),
.rs2_idx_o (d_src2_idx),
.rd_o (d_rd),
.imm_o (d_imm),
.alu_op_o (d_aluop),
.wb_sel_o (d_wb_sel),
.md_op_o (d_md_op),
.csr_addr_o (d_csr_addr),
.uses_rs1_o (d_uses_rs1),
.uses_rs2_o (d_uses_rs2),
.reg_write_o (d_reg_write),
.mem_to_reg_o(d_mem_to_reg),
.mem_write_o (d_mem_write),
.mem_byte_o (d_mem_byte),
.alu_src_o (d_alu_src),
.branch_o (d_branch),
.beq_o (d_beq),
.bne_o (d_bne),
.blt_o (d_blt),
.bge_o (d_bge),
.bltu_o (d_bltu),
.bgeu_o (d_bgeu),
.jal_o (d_jal),
.jalr_o (d_jalr),
.ecall_o (d_ecall),
.uret_o (d_uret),
.csr_set_o (d_csr_set),
.csr_clear_o (d_csr_clear),
.csr_write_o (d_csr_write)
);
// 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4(已存在 alu_result 里),
// 3=MUL 高位/余数,4=CSR 旧值。
wire [31:0] wb_value;
assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q :
(memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q :
(memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q :
memwb_alu_result_q;
// WB 级写回使能(同时用于寄存器堆的写端口与写优先旁路)。
wire memwb_reg_write_en = memwb_valid_q && memwb_reg_write_q;
// ID 级读寄存器:x0 恒为 0,并对同周期 WB 的结果做写优先旁路。
wire [31:0] d_rs1_value;
wire [31:0] d_rs2_value;
cpu21_riscv_regfile u_regfile (
.clk (clk),
.reset (reset),
.rs1_idx_i (d_src1_idx),
.rs2_idx_i (d_src2_idx),
.rs1_data_o(d_rs1_value),
.rs2_data_o(d_rs2_value),
.we_i (memwb_reg_write_en),
.waddr_i (memwb_rd_q),
.wdata_i (wb_value)
);
// 中断/CSR 相关连线(中断控制器实例见下方"中断 / CSR"小节)。
wire take_irq;
wire [31:0] irq_vector;
wire [31:0] ex_return_pc;
wire [2:0] irq_pending;
wire [1:0] irq_current_level;
wire [31:0] uepc_value;
wire [31:0] ustatus_value;
// -------------------------------------------------------------------------
// EX 级:前递网络、ALU 运算、存储对齐与控制流裁决
// -------------------------------------------------------------------------
// 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 =
(exmem_wb_sel_q == 3'd3) ? exmem_alu_result2_q :
(exmem_wb_sel_q == 3'd4) ? exmem_csr_old_q :
exmem_alu_result_q;
// 前递优先级:EX/MEM 的结果最新,其次为 MEM/WB 写回值,最后才是
// 译码时读到的寄存器值。
wire [31:0] ex_src1;
wire [31:0] ex_src2;
cpu21_riscv_forward_unit u_forward_unit (
.rs1_value_i (idex_rs1_value_q),
.rs2_value_i (idex_rs2_value_q),
.rs1_idx_i (idex_rs1_idx_q),
.rs2_idx_i (idex_rs2_idx_q),
.exmem_valid_i (exmem_valid_q),
.exmem_reg_write_i (exmem_reg_write_q),
.exmem_mem_to_reg_i(exmem_mem_to_reg_q),
.exmem_rd_i (exmem_rd_q),
.exmem_value_i (exmem_forward_value),
.memwb_valid_i (memwb_valid_q),
.memwb_reg_write_i (memwb_reg_write_q),
.memwb_rd_i (memwb_rd_q),
.memwb_value_i (wb_value),
.src1_o (ex_src1),
.src2_o (ex_src2)
);
// ALU 的 B 端:立即数或 rs2。
wire [31:0] ex_alu_b = idex_alu_src_q ? idex_imm_q : ex_src2;
wire [31:0] ex_alu_result;
wire [31:0] ex_alu_result2;
// 读取 CSR 旧值(ustatus=0x004、uepc=0x041),供 CSR 指令回写 rd。
wire [31:0] ex_csr_old_value =
(idex_csr_addr_q == 12'h004) ? ustatus_value :
(idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0;
// ALU 实例(只做单拍运算;乘除法见下面的多周期单元)。
cpu21_riscv_alu u_ex_alu (
.op (idex_aluop_q),
.a (ex_src1),
.b (ex_alu_b),
.result (ex_alu_result),
.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 目标、是否需要重定向。
cpu21_riscv_branch_unit u_branch_unit (
.valid_i (idex_valid_q),
.pc_i (idex_pc_q),
.imm_i (idex_imm_q),
.src1_i (ex_src1),
.src2_i (ex_src2),
.branch_i (idex_branch_q),
.beq_i (idex_beq_q),
.bne_i (idex_bne_q),
.blt_i (idex_blt_q),
.bge_i (idex_bge_q),
.bltu_i (idex_bltu_q),
.bgeu_i (idex_bgeu_q),
.jal_i (idex_jal_q),
.jalr_i (idex_jalr_q),
.pred_taken_i (idex_pred_taken_q),
.pred_target_i (idex_pred_target_q),
.branch_taken_o (ex_branch_taken),
.controlflow_o (ex_controlflow),
.target_o (ex_target),
.redirect_valid_o(ex_redirect_valid),
.redirect_pc_o (ex_redirect_pc)
);
// 存储数据对齐:依据字节地址低两位生成写数据与字节写掩码。
wire [31:0] ex_store_data;
wire [ 3:0] ex_store_wstrb;
cpu21_riscv_store_unit u_store_unit (
.addr_i (ex_alu_result),
.data_i (ex_src2),
.mem_write_i (idex_mem_write_q),
.mem_byte_i (idex_mem_byte_q),
.store_data_o (ex_store_data),
.store_wstrb_o(ex_store_wstrb)
);
// -------------------------------------------------------------------------
// 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(原样)
.led_data_o (led_data_o),
.led_valid_o(led_valid_o),
// 七段数码管
.seg_data_o (seg_data_o),
.seg_valid_o(seg_valid_o),
// 显存(本体在板级顶层)
.fb_we_o (fb_we_o),
.fb_waddr_o (fb_waddr_o),
.fb_wdata_o (fb_wdata_o),
.fb_wstrb_o (fb_wstrb_o),
.fb_rdata_i (fb_rdata_i),
// 显示控制器
.disp_ctrl_o (disp_ctrl_o),
.disp_cursor_o(disp_cursor_o),
// 鼠标事件源与中断
.mouse_ev_valid_i(mouse_ev_valid_i),
.mouse_dx_i (mouse_dx_i),
.mouse_dy_i (mouse_dy_i),
.mouse_btn_i (mouse_btn_i),
.sw_i (sw_i),
.frame_tick_i (frame_tick_i),
.mouse_irq_o (mouse_irq_o),
.level_irq_o (level_irq_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);
// -------------------------------------------------------------------------
// 中断 / CSR / 嵌套返回栈
// -------------------------------------------------------------------------
wire ex_uret_redirect = idex_valid_q && idex_uret_q;
// EX 级正在 URET,或乘除法还没算完时,不响应中断:前者与中断同时发生会丢掉
// 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 为空,则 IF/ID 中
// 保存的就是尚未执行的最老指令。
wire [31:0] irq_save_pc = idex_valid_q ?
((idex_branch_q && ex_branch_taken) ? ex_target : (idex_pc_q + 32'd4)) :
(ifid_valid_q ? ifid_pc_q : pc_q);
cpu21_riscv_irq_ctrl #(
.IRQ1_VECTOR (IRQ1_VECTOR),
.IRQ2_VECTOR (IRQ2_VECTOR),
.IRQ3_VECTOR (IRQ3_VECTOR),
.IRQ_STACK_DEPTH(IRQ_STACK_DEPTH),
.USTATUS_INIT (USTATUS_INIT)
) u_irq_ctrl (
.clk (clk),
.reset (reset),
.irq_i (irq_i),
.halted_i (halted_q),
.ex_block_i (ex_block_irq),
.save_pc_i (irq_save_pc),
.uret_i (ex_uret_redirect),
.csr_valid_i(idex_valid_q),
.csr_set_i (idex_csr_set_q),
.csr_clear_i(idex_csr_clear_q),
.csr_write_i(idex_csr_write_q),
.csr_addr_i (idex_csr_addr_q),
.csr_imm_i (idex_imm_q),
.csr_wdata_i(ex_src1),
.take_irq_o (take_irq),
.vector_o (irq_vector),
.return_pc_o(ex_return_pc),
.pending_o (irq_pending),
.level_o (irq_current_level),
.uepc_o (uepc_value),
.ustatus_o (ustatus_value)
);
// -------------------------------------------------------------------------
// 冒险 / 停顿 / 冲刷判定
// -------------------------------------------------------------------------
wire load_use_hazard;
wire flush_younger;
wire pipeline_stall;
cpu21_riscv_hazard_unit u_hazard_unit (
.ifid_valid_i (ifid_valid_q),
.idex_valid_i (idex_valid_q),
.idex_mem_to_reg_i(idex_mem_to_reg_q),
.idex_rd_i (idex_rd_q),
.d_uses_rs1_i (d_uses_rs1),
.d_uses_rs2_i (d_uses_rs2),
.d_src1_idx_i (d_src1_idx),
.d_src2_idx_i (d_src2_idx),
.take_irq_i (take_irq),
.uret_redirect_i (ex_uret_redirect),
.halt_event_i (ex_halt_event),
.redirect_valid_i (ex_redirect_valid),
.halted_i (halted_q),
.load_use_hazard_o(load_use_hazard),
.flush_younger_o (flush_younger),
.pipeline_stall_o (pipeline_stall)
);
// -------------------------------------------------------------------------
// 对外输出与调试信号。
// -------------------------------------------------------------------------
// 存储器接口:指令地址来自取指 PC;数据地址/写数据/写掩码来自 MEM 级。
assign instr_addr_o = pc_q;
assign data_addr_o = exmem_alu_result_q;
assign data_wdata_o = exmem_write_data_q;
assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0;
assign data_we_o = |data_wstrb_o;
// LED 输出由 cpu21_mmio 驱动,这里只保留流水线各级 PC/指令回显。
assign if_pc_o = pc_q;
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 mem_pc_o = exmem_valid_q ? exmem_pc_q : 32'b0;
assign wb_pc_o = memwb_valid_q ? memwb_pc_q : 32'b0;
assign if_ir_o = instr_i;
assign id_ir_o = ifid_valid_q ? ifid_ir_q : 32'b0;
assign ex_ir_o = idex_valid_q ? idex_ir_q : 32'b0;
assign mem_ir_o = exmem_valid_q ? exmem_ir_q : 32'b0;
assign wb_ir_o = memwb_valid_q ? memwb_ir_q : 32'b0;
assign rdin_o = wb_value;
assign mdin_o = exmem_write_data_q;
assign reg_write_o = memwb_valid_q && memwb_reg_write_q;
assign mem_write_o = exmem_valid_q && exmem_mem_write_q;
assign halted_o = halted_q;
assign irq_pending_o = irq_pending;
assign irq_current_level_o = irq_current_level;
assign uepc_o = uepc_value;
assign ustatus_o = ustatus_value;
assign bpb_predict_hit_o = bpb_predict_hit;
assign bpb_predict_taken_o = bpb_predict_taken;
assign bpb_mispredict_o = ex_redirect_valid && idex_branch_q;
// -------------------------------------------------------------------------
// 状态更新。顺序遵循五级流水线:先捕获 MEM/WB、EX/MEM,再处理
// ID/EX 与 IF/ID/PC 的控制。
// -------------------------------------------------------------------------
// 复位时清空所有流水寄存器与 LED/停机标志(寄存器堆、中断状态与
// 性能计数器在各自的子模块内复位)。
always @(posedge clk or posedge reset) begin
if (reset) begin
pc_q <= RESET_PC;
ifid_valid_q <= 1'b0;
ifid_pc_q <= 32'b0;
ifid_ir_q <= 32'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
idex_valid_q <= 1'b0;
idex_pc_q <= 32'b0;
idex_ir_q <= 32'b0;
idex_rs1_value_q <= 32'b0;
idex_rs2_value_q <= 32'b0;
idex_rs1_idx_q <= 5'b0;
idex_rs2_idx_q <= 5'b0;
idex_rd_q <= 5'b0;
idex_imm_q <= 32'b0;
idex_aluop_q <= ALU_ADD;
idex_alu_src_q <= 1'b0;
idex_reg_write_q <= 1'b0;
idex_mem_to_reg_q <= 1'b0;
idex_mem_write_q <= 1'b0;
idex_mem_byte_q <= 1'b0;
idex_wb_sel_q <= 3'b0;
idex_md_op_q <= 3'b0;
idex_csr_addr_q <= 12'b0;
idex_branch_q <= 1'b0;
idex_beq_q <= 1'b0;
idex_bne_q <= 1'b0;
idex_blt_q <= 1'b0;
idex_bge_q <= 1'b0;
idex_bltu_q <= 1'b0;
idex_bgeu_q <= 1'b0;
idex_jal_q <= 1'b0;
idex_jalr_q <= 1'b0;
idex_ecall_q <= 1'b0;
idex_uret_q <= 1'b0;
idex_csr_set_q <= 1'b0;
idex_csr_clear_q <= 1'b0;
idex_csr_write_q <= 1'b0;
idex_pred_taken_q <= 1'b0;
idex_pred_target_q <= 32'b0;
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;
memwb_valid_q <= 1'b0;
memwb_pc_q <= 32'b0;
memwb_ir_q <= 32'b0;
memwb_alu_result_q <= 32'b0;
memwb_alu_result2_q <= 32'b0;
memwb_csr_old_q <= 32'b0;
memwb_mem_data_q <= 32'b0;
memwb_rd_q <= 5'b0;
memwb_reg_write_q <= 1'b0;
memwb_mem_to_reg_q <= 1'b0;
memwb_wb_sel_q <= 3'b0;
halted_q <= 1'b0;
end
else begin
if (go_i)
halted_q <= 1'b0;
if (ex_halt_event)
halted_q <= 1'b1;
// MEM/WB 流水寄存器捕获。
memwb_valid_q <= exmem_valid_q;
memwb_pc_q <= exmem_pc_q;
memwb_ir_q <= exmem_ir_q;
memwb_alu_result_q <= exmem_alu_result_q;
memwb_alu_result2_q <= exmem_alu_result2_q;
memwb_csr_old_q <= exmem_csr_old_q;
memwb_mem_data_q <= mem_rdata;
memwb_rd_q <= exmem_rd_q;
memwb_reg_write_q <= exmem_reg_write_q;
memwb_mem_to_reg_q <= exmem_mem_to_reg_q;
memwb_wb_sel_q <= exmem_wb_sel_q;
// 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_pc_q <= idex_pc_q;
exmem_ir_q <= idex_ir_q;
exmem_alu_result_q <= ex_result;
exmem_alu_result2_q <= ex_result2;
exmem_csr_old_q <= ex_csr_old_value;
exmem_write_data_q <= ex_store_data;
exmem_rd_q <= idex_rd_q;
exmem_reg_write_q <= idex_reg_write_q;
exmem_mem_to_reg_q <= idex_mem_to_reg_q;
exmem_mem_write_q <= idex_mem_write_q;
exmem_wstrb_q <= ex_store_wstrb;
exmem_wb_sel_q <= idex_wb_sel_q;
end
// ---------------------------------------------------------------------
// ID/EX 装载。
//
// payload(PC/IR/控制位/预测位)只在 load-use 停顿、乘除法迭代、停机时
// 保持(时钟使能与“冲刷”无关),其余情况一律装载;只有 valid 位受冲刷
// 控制,冲刷时清 0。这样“EX 级组合判决(分支/中断)→ 流水寄存器时钟
// 使能”这条 100 MHz 下最长的路径就只剩下一个触发器(valid),payload
// 即使装进错误路径的数据也无害——所有使用者都以 idex_valid_q 为前提。
// ---------------------------------------------------------------------
if (!(pipeline_stall || halted_q || ex_md_stall)) begin
idex_pc_q <= ifid_pc_q;
idex_ir_q <= ifid_ir_q;
idex_rs1_value_q <= d_rs1_value;
idex_rs2_value_q <= d_rs2_value;
idex_rs1_idx_q <= d_src1_idx;
idex_rs2_idx_q <= d_src2_idx;
idex_rd_q <= d_rd;
idex_imm_q <= d_imm;
idex_aluop_q <= d_aluop;
idex_alu_src_q <= d_alu_src;
idex_reg_write_q <= d_reg_write;
idex_mem_to_reg_q <= d_mem_to_reg;
idex_mem_write_q <= d_mem_write;
idex_mem_byte_q <= d_mem_byte;
idex_wb_sel_q <= d_wb_sel;
idex_md_op_q <= d_md_op;
idex_csr_addr_q <= d_csr_addr;
idex_branch_q <= d_branch;
idex_beq_q <= d_beq;
idex_bne_q <= d_bne;
idex_blt_q <= d_blt;
idex_bge_q <= d_bge;
idex_bltu_q <= d_bltu;
idex_bgeu_q <= d_bgeu;
idex_jal_q <= d_jal;
idex_jalr_q <= d_jalr;
idex_ecall_q <= d_ecall;
idex_uret_q <= d_uret;
idex_csr_set_q <= d_csr_set;
idex_csr_clear_q <= d_csr_clear;
idex_csr_write_q <= d_csr_write;
idex_pred_taken_q <= ifid_pred_taken_q;
idex_pred_target_q <= ifid_pred_target_q;
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
pc_q <= irq_vector;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_uret_redirect) begin
pc_q <= ex_return_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_halt_event) begin
pc_q <= idex_pc_q + 32'd4;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_redirect_valid) begin
pc_q <= ex_redirect_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (pipeline_stall || ex_md_stall) begin
pc_q <= pc_q;
ifid_valid_q <= ifid_valid_q;
ifid_pc_q <= ifid_pc_q;
ifid_ir_q <= ifid_ir_q;
ifid_pred_taken_q <= ifid_pred_taken_q;
ifid_pred_target_q <= ifid_pred_target_q;
end
else if (halted_q) begin
pc_q <= pc_q;
ifid_valid_q <= 1'b0;
end
else begin
pc_q <= fetch_next_pc;
ifid_valid_q <= 1'b1;
ifid_pc_q <= pc_q;
ifid_ir_q <= instr_i;
ifid_pred_taken_q <= f_is_branch && bpb_predict_hit && bpb_predict_taken;
ifid_pred_target_q <= bpb_predict_target;
end
end
end
// -------------------------------------------------------------------------
// 性能计数器(只影响统计量,不参与控制)。
// -------------------------------------------------------------------------
cpu21_riscv_perf_counters u_perf_counters (
.clk (clk),
.reset (reset),
.inc_cycle_i (!halted_q),
.stall_i (pipeline_stall || ex_md_stall),
.flush_i (flush_younger),
.cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken),
.cond_valid_i (idex_valid_q && idex_branch_q),
.mispredict_i (ex_redirect_valid),
.uncond_i (idex_valid_q && (idex_jal_q || idex_jalr_q)),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
endmodule
`default_nettype wire
+129
View File
@@ -0,0 +1,129 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_seg7 —— Nexys4DDR 板载 8 位数码管的动态扫描驱动。
//
// 输入:seg_data_i 每 4 位一个十六进制数字(低 4 位 = 最右边那个数码管),
// an_en_i 每一位对应一个数码管的使能(1 = 显示,0 = 熄灭)。
// 输出:an_o[7:0] 位选(低有效)、seg_o[6:0] 段选 a..g(低有效)、dp_o 小数点。
//
// 扫描频率:CLK_DIV 分频后约 1 kHz 切换一位(8 位 => 每位数码管 125 Hz 刷新)。
// 板载数码管是共阳极:位选拉低选中,段选拉低点亮。
// -----------------------------------------------------------------------------
module cpu21_seg7 #(
parameter integer REFRESH_DIV = 50000 // 50 MHz / 50000 = 1 kHz
) (
input wire clk,
input wire reset,
input wire [31:0] seg_data_i, // 8 个十六进制数字
input wire [ 7:0] an_en_i, // 每位是否显示
output reg [ 7:0] an_o, // 位选,低有效
output reg [ 6:0] seg_o, // 段选 a..g,低有效
output reg dp_o // 小数点,低有效
);
// ---------------------------------------------------------------------------
// 刷新计数与当前位
// ---------------------------------------------------------------------------
reg [15:0] div_q;
reg [2:0] digit_q;
wire tick = (div_q == (REFRESH_DIV - 1));
always @(posedge clk or posedge reset) begin
if (reset) begin
div_q <= 16'd0;
digit_q <= 3'd0;
end
else if (tick) begin
div_q <= 16'd0;
digit_q <= digit_q + 3'd1;
end
else begin
div_q <= div_q + 16'd1;
end
end
// 当前要显示的十六进制数字
reg [3:0] nib;
always @* begin
case (digit_q)
3'd0:
nib = seg_data_i[3:0];
3'd1:
nib = seg_data_i[7:4];
3'd2:
nib = seg_data_i[11:8];
3'd3:
nib = seg_data_i[15:12];
3'd4:
nib = seg_data_i[19:16];
3'd5:
nib = seg_data_i[23:20];
3'd6:
nib = seg_data_i[27:24];
default:
nib = seg_data_i[31:28];
endcase
end
// ---------------------------------------------------------------------------
// 十六进制 -> 七段码(bit0=a ... bit6=g,1 表示该段点亮)
// ---------------------------------------------------------------------------
reg [6:0] seg_on;
always @* begin
case (nib)
4'h0:
seg_on = 7'b0111111;
4'h1:
seg_on = 7'b0000110;
4'h2:
seg_on = 7'b1011011;
4'h3:
seg_on = 7'b1001111;
4'h4:
seg_on = 7'b1100110;
4'h5:
seg_on = 7'b1101101;
4'h6:
seg_on = 7'b1111101;
4'h7:
seg_on = 7'b0000111;
4'h8:
seg_on = 7'b1111111;
4'h9:
seg_on = 7'b1101111;
4'hA:
seg_on = 7'b1110111;
4'hB:
seg_on = 7'b1111100;
4'hC:
seg_on = 7'b0111001;
4'hD:
seg_on = 7'b1011110;
4'hE:
seg_on = 7'b1111001;
default:
seg_on = 7'b1110001; // F
endcase
end
// ---------------------------------------------------------------------------
// 位选与段选输出(共阳极 => 低有效)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
an_o <= 8'hFF;
seg_o <= 7'h7F;
dp_o <= 1'b1;
end
else if (tick) begin
an_o <= ~(8'b0000_0001 << digit_q) | ~an_en_i;
seg_o <= ~seg_on;
dp_o <= 1'b1; // 不用小数点
end
end
endmodule
`default_nettype wire
+242
View File
@@ -0,0 +1,242 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。
//
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 1 字节"(字形 + 颜色),
// 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。
// 这样一格只要 1 次字节写,刷屏开销极小。
//
// 网格:40 列 x 30 行,每格 16x16 像素 = 640x480。
//
// 时钟:本模块工作在 CPU 时钟(clk)下,用 tick(每 2 个 clk 一个使能)产生
// 25 MHz 的像素节拍。**不引入第二个时钟域**,因此显示缓冲区的读(本模块)
// 与写(MMIO,同一时钟)之间没有跨时钟域问题。
//
// 光标:由 CURSOR 寄存器的 (x,y) 决定,在对应格子上画一个白色闪动的方框
// (CTRL[0] 控制显示与否)。缓冲区字节的 bit7 在本模块中不使用(保留)。
// -----------------------------------------------------------------------------
module cpu21_vga #(
parameter integer H_VISIBLE = 640,
parameter integer H_FP = 16,
parameter integer H_SYNC = 96,
parameter integer H_BP = 48,
parameter integer V_VISIBLE = 480,
parameter integer V_FP = 10,
parameter integer V_SYNC = 2,
parameter integer V_BP = 33,
parameter integer COLS = 40,
parameter integer ROWS = 30
) (
input wire clk,
input wire reset,
input wire tick, // 25 MHz 像素使能(每 2 个 clk 一拍)
// 显示缓冲区读端口
output wire [ 9:0] fb_raddr_o,
input wire [31:0] fb_word_i,
// 控制
input wire [31:0] ctrl_i,
input wire [31:0] cursor_i,
// VGA 输出
output reg [ 3:0] vga_r,
output reg [ 3:0] vga_g,
output reg [ 3:0] vga_b,
output reg vga_hs,
output reg vga_vs,
output reg frame_tick_o
);
localparam integer H_TOTAL = H_VISIBLE + H_FP + H_SYNC + H_BP; // 800
localparam integer V_TOTAL = V_VISIBLE + V_FP + V_SYNC + V_BP; // 525
// ---------------------------------------------------------------------------
// 扫描计数
// ---------------------------------------------------------------------------
reg [9:0] hx, vy;
always @(posedge clk or posedge reset) begin
if (reset) begin
hx <= 10'd0;
vy <= 10'd0;
end
else if (tick) begin
if (hx == H_TOTAL - 1) begin
hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end
else begin
hx <= hx + 10'd1;
end
end
end
wire de = (hx < H_VISIBLE) && (vy < V_VISIBLE);
// 同步信号在 tick 上寄存输出
wire hs_n = ~((hx >= (H_VISIBLE + H_FP)) && (hx < (H_VISIBLE + H_FP + H_SYNC)));
wire vs_n = ~((vy >= (V_VISIBLE + V_FP)) && (vy < (V_VISIBLE + V_FP + V_SYNC)));
// ---------------------------------------------------------------------------
// 格子索引与缓冲区读出
// ---------------------------------------------------------------------------
wire [5:0] ccol = hx[9:4]; // 0..39
wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col
assign fb_raddr_o = cidx[10:2]; // 字地址(1 字 = 4 格)
wire [1:0] lane = cidx[1:0];
wire [31:0] cell_word = fb_word_i;
wire [7:0] cell_byte = cell_word >> {lane, 3'b000};
wire [3:0] glyph = cell_byte[3:0];
wire [2:0] gcolor = cell_byte[6:4];
// 全局背景色(字模点为 0 时显示的颜色)
wire [2:0] bgcolor = ctrl_i[7:4];
// ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子
// ---------------------------------------------------------------------------
wire [2:0] fx = hx[3:1];
wire [2:0] fy = vy[3:1];
function [7:0] font_row;
input [3:0] g;
input [2:0] r;
begin
case (g)
4'd0:
font_row = 8'h00; // 空白
4'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h7E : 8'h00; // 1
4'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h30 :
(r == 3'd6) ? 8'h7E : 8'h00; // 2
4'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h1C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 3
4'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h6C : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h0C :
(r == 3'd6) ? 8'h0C : 8'h00; // 4
4'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C :
(r == 3'd3) ? 8'h06 : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 5
4'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 6
4'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // 7
4'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h3C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 8
4'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD :
(r == 3'd3) ? 8'hA5 : (r == 3'd4) ? 8'hA5 : (r == 3'd5) ? 8'hBD :
(r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块
4'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 :
(r == 3'd3) ? 8'h70 : (r == 3'd4) ? 8'h40 : (r == 3'd5) ? 8'h40 :
(r == 3'd6) ? 8'h40 : 8'h00; // 旗
4'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF :
(r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : (r == 3'd5) ? 8'hFF :
(r == 3'd6) ? 8'h7E : 8'h3C; // 雷
4'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h24 :
(r == 3'd6) ? 8'h42 : 8'h81; // 爆炸
4'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h00 :
(r == 3'd6) ? 8'h18 : 8'h00; // 问号
4'd14:
font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
4'd15:
font_row = 8'hFF; // 实心
default:
font_row = 8'h00;
endcase
end
endfunction
wire [7:0] frow = font_row(glyph, fy);
wire glyph_px = frow[3'd7 - fx]; // 1 = 画字模点
// ---------------------------------------------------------------------------
// 光标框(闪动)
// ---------------------------------------------------------------------------
reg [24:0] blink_q;
always @(posedge clk or posedge reset) begin
if (reset)
blink_q <= 25'd0;
else if (tick)
blink_q <= blink_q + 25'd1;
end
wire blink = blink_q[24];
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow);
wire on_border = (hx[3:0] == 4'd0) || (hx[3:0] == 4'd15) ||
(vy[3:0] == 4'd0) || (vy[3:0] == 4'd15);
wire cursor_box = cursor_here && blink && ctrl_i[0] && on_border;
// ---------------------------------------------------------------------------
// 颜色(3 位调色板 -> 4:4:4 RGB)
// ---------------------------------------------------------------------------
function [11:0] pal;
input [2:0] c;
begin
case (c)
3'd0:
pal = {4'h0, 4'h0, 4'h0}; // 黑
3'd1:
pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd2:
pal = {4'h0, 4'hA, 4'h0}; // 绿
3'd3:
pal = {4'h0, 4'hA, 4'hA}; // 青
3'd4:
pal = {4'hA, 4'h0, 4'h0}; // 红
3'd5:
pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd6:
pal = {4'hA, 4'hA, 4'h0}; // 黄
default:
pal = {4'hA, 4'hA, 4'hA}; // 浅灰
endcase
end
endfunction
wire [2:0] px_color = glyph_px ? gcolor : bgcolor;
wire [2:0] px_final = cursor_box ? 3'd7 : px_color;
wire [11:0] px_rgb = de ? pal(px_final) : 12'h000;
always @(posedge clk or posedge reset) begin
if (reset) begin
{vga_r, vga_g, vga_b} <= 12'h000;
vga_hs <= 1'b1;
vga_vs <= 1'b1;
frame_tick_o <= 1'b0;
end
else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n;
vga_vs <= vs_n;
frame_tick_o <= (hx == H_TOTAL - 1) && (vy == V_TOTAL - 1);
end
end
endmodule
`default_nettype wire
Tcl 一键重建
+73
View File
@@ -0,0 +1,73 @@
`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),
// 扫雷扩展端口:本测试平台不复位测试这些功能,输入显式接 0/1,
// 避免悬空(z)传到中断控制器里影响跑马灯的比对结果。
.btnu (1'b0),
.btnd (1'b0),
.btnl (1'b0),
.btnr (1'b0),
.sw_flag (1'b0),
.ps2_clk (1'b1),
.ps2_data(1'b1),
.an (),
.seg (),
.dp (),
.vga_r (),
.vga_g (),
.vga_b (),
.vga_hs (),
.vga_vs ()
);
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
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""极简 RISC-V 汇编器:仅支持本项目测试所需的指令子集。
用法: python tools/rv_asm.py <input.asm> <output.hex>
同时生成 <output.hex>.lst 反汇编/编码清单,便于核对。
支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、
mul/mulh/mulhsu/mulhu、
addi/slti/sltiu/xori/ori/andi、slli/srli/srai、
lw/sw/sb、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释
伪指令:print(= lui t6,LED_ADDR>>12 + sw a0,0(t6),向 MMIO 的 LED 寄存器写入 a0)、
halt(= addi a7,zero,10 + ecall),各占两个字。
示例:python tools/rv_asm.py programs/isa_test.asm srcs/rom/isa_test.hex
"""
import re
import sys
REGS = {
"zero": 0,
"ra": 1,
"sp": 2,
"gp": 3,
"tp": 4,
"t0": 5,
"t1": 6,
"t2": 7,
"s0": 8,
"s1": 9,
"a0": 10,
"a1": 11,
"a2": 12,
"a3": 13,
"a4": 14,
"a5": 15,
"a6": 16,
"a7": 17,
"s2": 18,
"s3": 19,
"s4": 20,
"s5": 21,
"s6": 22,
"s7": 23,
"s8": 24,
"s9": 25,
"s10": 26,
"s11": 27,
"t3": 28,
"t4": 29,
"t5": 30,
"t6": 31,
}
def reg(t):
t = t.strip()
if t in REGS:
return REGS[t]
m = re.fullmatch(r"x(\d+)", t)
if m:
return int(m.group(1))
raise SystemExit("未知寄存器: " + t)
def num(t):
return int(t.strip(), 0)
def enc_r(f7, rs2, rs1, f3, rd):
return (f7 << 25) | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x33
def enc_i(imm, rs1, f3, rd, op=0x13):
return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | op
def enc_s(imm, rs2, rs1, f3):
imm &= 0xFFF
return (
((imm >> 5) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (f3 << 12)
| ((imm & 0x1F) << 7)
| 0x23
)
def enc_b(imm, rs2, rs1, f3):
imm &= 0x1FFF
return (
(((imm >> 12) & 1) << 31)
| (((imm >> 5) & 0x3F) << 25)
| (rs2 << 20)
| (rs1 << 15)
| (f3 << 12)
| (((imm >> 1) & 0xF) << 8)
| (((imm >> 11) & 1) << 7)
| 0x63
)
def enc_u(imm, rd):
return ((imm & 0xFFFFF) << 12) | (rd << 7) | 0x37
def enc_j(imm, rd):
imm &= 0x1FFFFF
return (
(((imm >> 20) & 1) << 31)
| (((imm >> 1) & 0x3FF) << 21)
| (((imm >> 11) & 1) << 20)
| (((imm >> 12) & 0xFF) << 12)
| (rd << 7)
| 0x6F
)
RTYPE = {
"add": (0x00, 0),
"sub": (0x20, 0),
"sll": (0x00, 1),
"slt": (0x00, 2),
"sltu": (0x00, 3),
"xor": (0x00, 4),
"srl": (0x00, 5),
"sra": (0x20, 5),
"or": (0x00, 6),
"and": (0x00, 7),
"mul": (0x01, 0),
"mulh": (0x01, 1),
"mulhsu": (0x01, 2),
"mulhu": (0x01, 3),
"divu": (0x01, 5),
"remu": (0x01, 7),
}
ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7}
SHIFT = {"slli": 1, "srli": 5, "srai": 5}
BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7}
# MMIO:LED 数据寄存器地址,需与 RTL 顶层 parameter LED_ADDR 保持一致
LED_ADDR = 0x1000
# 双字伪指令:print 向 MMIO 的 LED 写寄存器写入 a0;halt 停机
PSEUDO_SIZE = {"print": 2, "halt": 2}
def assemble(lines):
labels, items, addr = {}, [], 0
for raw in lines:
line = raw.split("#")[0].strip()
if not line:
continue
while True:
m = re.match(r"^([A-Za-z_.$][\w.$]*)\s*:\s*(.*)$", line)
if not m:
break
labels[m.group(1)] = addr
line = m.group(2).strip()
if not line:
break
if not line:
continue
items.append((addr, line))
addr += 4 * PSEUDO_SIZE.get(line.split()[0].lower(), 1)
out = []
for addr, line in items:
parts = re.split(r"[\s,]+", line)
mn, a = parts[0].lower(), parts[1:]
if mn in RTYPE:
f7, f3 = RTYPE[mn]
w = enc_r(f7, reg(a[2]), reg(a[1]), f3, reg(a[0]))
elif mn in ITYPE:
im = num(a[2])
if not (-2048 <= im <= 2047):
raise SystemExit("I 型立即数超出 12 位有符号范围: " + line)
w = enc_i(im, reg(a[1]), ITYPE[mn], reg(a[0]))
elif mn in SHIFT:
w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0]))
elif mn == "lui":
w = enc_u(num(a[1]), reg(a[0]))
elif mn in ("lw", "sw", "sb"):
m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1])
if not m:
raise SystemExit("内存操作数格式应为 imm(rs1): " + line)
im = num(m.group(1))
if not (-2048 <= im <= 2047):
raise SystemExit("访存偏移超出 12 位有符号范围: " + line)
if mn == "lw":
w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03)
elif mn == "sw":
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:
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])
elif mn == "jal":
tgt = labels[a[1]] if a[1] in labels else num(a[1])
w = enc_j(tgt - addr, reg(a[0]))
elif mn == "jalr":
w = enc_i(num(a[2]), reg(a[1]), 0, reg(a[0]), 0x67)
elif mn == "ecall":
w = 0x00000073
elif mn == "print":
# 写 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":
w = [enc_i(10, 0, 0, 17), 0x00000073]
else:
raise SystemExit("未知助记符: " + mn)
out.append((addr, line, w if isinstance(w, list) else [w]))
return out
def main():
src, dst = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8-sig") as f: # 兼容带/不带 BOM
prog = assemble(f.readlines())
with open(dst, "w", encoding="ascii") as f:
for _, _, ws in prog:
for w in ws:
f.write("%08X\n" % w)
with open(dst + ".lst", "w", encoding="utf-8") as f:
for addr, line, ws in prog:
f.write("%04X: %s %s\n" % (addr, " ".join("%08X" % w for w in ws), line))
print("已生成 %s(%d 个字)" % (dst, sum(len(ws) for _, _, ws in prog)))
main()
+184
View File
@@ -0,0 +1,184 @@
# 扫雷(RISC-V / cpu21)— 纯 C + MMIO 显示 + 中断读鼠标
给 `pipelined-cpu` 里那颗 CPU(`cpu21_riscv_redirect_int_bpb`)写的扫雷。
**游戏逻辑和三个中断入口都由纯 C 写成**,用 RISC-V 工具链编译成机器码后固化进片内 ROM 运行。
```
saolei/
├─ sw/
│ ├─ mmio.h 全部 MMIO 地址与格子编码(换外设地址只改这一个文件)
│ ├─ saolei.c 纯 C 扫雷:背景界面(main) + 关卡(level_main) + 棋盘与绘制
│ ├─ isr.c **中断读鼠标的代码**:三个固定地址入口 + 纯 C 服务主体 isr_body
│ ├─ start.c C 启动(设 sp、清 .bss、进 main)——只有这一处必需的内联汇编
│ └─ linker.ld ROM/RAM 布局:复位 0x0、中断入口 0x100/0x200/0x300、栈顶 0x0FFC
└─ tools/
├─ build.ps1 一键构建:C → .o → ELF → .hex + cpu21_saolei_rom.vh
├─ check_isa.py 逐条反汇编检查:有没有用到这颗核没实现的指令
└─ elf2hex.py 裸二进制 → $readmemh hex + ROM 初始化头文件
```
配套硬件在 Vivado 工程里(不在本目录):`srcs/rtl/cpu21_mmio.v`(外设中枢)、
`cpu21_vga.v`(显示)、`cpu21_seg7.v`(数码管)、`cpu21_pointer.v`(按键当鼠标)、
`cpu21_ps2_mouse.v`(可选真鼠标)、`srcs/board/cpu21_board_top.v`(上板顶层)。
---
## 1. 程序结构:背景"伪操作系统" + 关卡 + 鼠标,正好占三个中断
CPU 复位后从 `0x0` 开始跑 `main()`(**背景程序**,从头到尾一直运行),它画开始界面
并等鼠标。整套交互是这样串起来的:
```mermaid
sequenceDiagram
participant BG as 背景 main()(0x0 起,一直跑)
participant LV as 关卡 level_main()(IRQ1 = 0x100)
participant MS as 鼠标 ISR(IRQ2 = 0x200)
BG->>BG: 画开始界面(标题 + 绿色"开始"按钮 + 光标框)
BG->>BG: 点中"开始"按钮 → 写 LEVEL 寄存器 = 1
Note over BG: 硬件拉起 irq_i[1],CPU 跳到 0x100
BG->>LV: isr1_entry:保存现场 + 打开 MIE → level_main()
LV->>LV: 初始化关卡(布雷、画盘面)
MS-->>LV: 鼠标事件中断(IRQ2 优先级更高,嵌套进来)
LV->>LV: 轮询事件队列:移动光标 / 揭开 / 插旗
LV->>BG: 点左上角黄格 = 返回 → 清 LEVEL + uret
Note over BG: 回到被打断的那条指令,继续等下一次"开始"
```
| 入口 | 地址 | 作用 | 对应 RTL 参数 |
| ---- | ------- | ----------------------------------- | ------------- |
| 复位 | `0x000` | `_start`:设栈、进 `main` | `RESET_PC` |
| IRQ1 | `0x100` | **关卡(游戏)程序** `level_main()` | `IRQ1_VECTOR` |
| IRQ2 | `0x200` | **鼠标读取** `isr_body()` | `IRQ2_VECTOR` |
| IRQ3 | `0x300` | 备用(只 `uret`) | `IRQ3_VECTOR` |
**地址是链接脚本定死的**(`linker.ld` 里 `.text.isr1 0x100 : {...}`),所以先定地址、
再设 RTL 参数,两边成对;不存在"编完才知道地址"的问题。优先级 IRQ3 > IRQ2 > IRQ1,
所以"关卡跑在 IRQ1、鼠标跑在 IRQ2"天然允许鼠标嵌套打断关卡。
中断硬件行为(见 `cpu21_riscv_irq_ctrl.v`):`irq_i` 上升沿置挂起位;响应时硬件自动清
挂起位、清 `ustatus[0]`(MIE)、把返回地址压进内部 EPC 栈;返回用 **`uret`**;**硬件不保存
任何通用寄存器**。因此:
- `isr1_entry`(游戏体量大会调函数)保存**全部调用者保存寄存器**,并用
`csrrsi x0,0x004,1` 打开 MIE 让鼠标中断能嵌套;
- `isr2_entry` 只存 `ra`,C 主体 `isr_body` 用 `-ffixed-x1,x5..x7,x10..x17,x28..x31`
编译,逼 GCC 只用 `s0-s11`(callee-saved,函数自己会存/恢复)和 `sp` —— 这样纯 C
主体不会破坏被打断的代码;
- 事件流:鼠标/按键 → RTL 事件 FIFO(深 16)→ 中断 → `isr_body` 搬进软件环形队列 →
主循环 `handle_event()` 处理。FIFO 读空后中断线自动回 0,下一个事件重新产生上升沿,
所以不丢事件;主循环只能轮询(这颗核没有 `wfi`)。
## 2. 针对这颗 CPU 的硬约束(改 C 代码时别踩)
`cpu21_riscv_decoder.v` 实现的是 RV32I 的一个子集:
| 类别 | 有 | **没有** |
| ------ | -------------------------------------------- | ---------------------------------- |
| R 型 | add sub sll slt sltu xor srl sra or and | — |
| M 扩展 | mul mulh mulhsu mulhu **divu remu** | **div / rem(有符号)** |
| I 型 | addi slti sltiu xori ori andi slli srli srai | — |
| 访存 | **lw / sw / sb** | **lb / lh / lbu / lhu / sh** |
| 高位 | lui | **auipc** |
| 控制流 | 6 条分支 + jal + jalr | — |
| 系统 | ecall uret csrrw csrrsi csrrci | mret / sret / fence / ebreak / wfi |
对应做法:不按字节*读*内存(**写**显存用 `sb` 可以)、不做有符号除法、索引运算全用
2 的幂 + 移位/掩码、**不能有 `.data`/`.rodata`**(数据空间读不到 ROM,所以没有带初值的
全局变量、没有字符串/常量表);字模放在 RTL 里,不在软件里。
## 3. 构建(已实测通过)
工具链:xPack `riscv-none-elf-gcc`(本机装在
`C:\Users\29653\tools\riscv\xpack-riscv-none-elf-gcc-15.2.0-1\bin`,已加入用户 PATH)。
```powershell
cd <仓库>\saolei
powershell -ExecutionPolicy Bypass -File tools\build.ps1
# 若没配 PATH:-Prefix 'C:\Users\29653\tools\riscv\xpack-riscv-none-elf-gcc-15.2.0-1\bin\riscv-none-elf-'
```
| 步骤 | 说明 |
| ------------ | -------------------------------------------------------------------------------------------- |
| 编译 | `start.c`/`saolei.c` 正常编;`isr.c` 额外加 `-ffixed-x...` |
| 链接 | `-T sw/linker.ld`;脚本里的 `ASSERT` 会拦住 `.data`/`.rodata` |
| **反汇编** | `build/saolei.lst` ← 这就是"编译出来的汇编",可以直接读 |
| **ISA 检查** | `tools/check_isa.py` 逐条比对指令白名单,出现 `auipc`/`lb`/`lbu`/`div`… 直接报错并给修复提示 |
| 转格式 | `build/saolei.hex`(`$readmemh`)+ `pipelined-cpu/srcs/board/cpu21_saolei_rom.vh` |
当前实测结果:`text=3368 B, data=0, bss=1436 B`,**842 条指令全部通过 ISA 检查**,
入口地址 `_start=0x0`、`isr1_entry=0x100`、`isr2_entry=0x200`、`isr3_entry=0x300`。
编译参数里有三处是这颗核/工具链的坑,已经写在脚本注释里:
1. `-march=rv32im_zicsr` —— GCC 15 起 `rv32im` 不含 Zicsr,`csrrsi` 会报
`unrecognized opcode ... extension 'zicsr' required`;
2. **不要禁链接器松弛**(别加 `-mno-relax`/`-Wl,--no-relax`)—— gcc 的函数调用默认是
`auipc ra,%pcrel_hi` + `jalr`,要靠链接器松弛压成一条 `jal`;禁掉后 35 条 `auipc`
会留在代码里(这颗核没有)。用 `-msmall-data-limit=0` 保证松弛不会引入 gp 相对寻址;
3. 中断入口地址必须写在**段声明**上(`.text.isr1 0x100 : {...} > ROM`)——
只写 `. = 0x100;` 会被 `ld` 忽略(实测 isr1 掉到了 0x14)。
## 4. MMIO 地址表(与 `cpu21_mmio.v` 的译码一致)
| 地址 | 外设 |
| -------------------- | ------------------------------------------------------------------------------- |
| `0x0000_1000` | LED 数据寄存器(低 16 位点亮板载 LED) |
| `0x0000_1004` | 七段数码管:8 位十六进制数(低 4 位 = 最右) |
| `0x0000_1100+` | 鼠标:STATUS / DATA / ACK / CTRL / INPUT / TIME / FRAME / EVCNT / LEVEL / MAGIC |
| `0x0000_1140+` | 显示控制器:CTRL(光标可见、背景色)/ CURSOR(列、行)/ MAGIC |
| `0x0000_2000-0x27FF` | 显存:40 列 × 30 行、每格 1 字节(只用到前 1200 字节) |
`LEVEL` 是**软件中断**寄存器:写 1 拉起 `irq_i[1]`(关卡入口),关卡返回前写 0 清掉,
否则下次点"开始"没有新的上升沿。`MAGIC`(`0xCAFE0001/2`)可以读来自检 MMIO 是否活着。
格子编码 1 字节 = `[3:0] 字形 [6:4] 颜色 [7] 保留`;字形 0 空白、1..8 数字、9 未揭开、
10 旗、11 雷、12 爆炸、13 问号、14 横线、15 实心;颜色是 3 位调色板。
## 5. 怎么跑起来
**仿真**(用你现成的测试平台):
```powershell
copy build\saolei.hex <仓库>\pipelined-cpu\srcs\rom\saolei.hex
# 把 srcs/tb/tb_no_intr.v 顶部的 ROM_FILE 改成 "saolei.hex"
```
**上板**:在 Vivado 里给综合/仿真**定义宏 `SAOLEI_ROM`**(Settings → Verilog Macros),
`cpu21_board_top.v` 就会加载 `cpu21_saolei_rom.vh`;不定义仍是原来的跑马灯程序。
接 VGA 显示器,按 `CPU_RESETN` 复位 → 出现开始界面(标题 + 绿色按钮 + 闪动光标框)。
**操作**(默认用板上 5 向按键):
| 操作 | 按键 |
| ------------------------------------ | --------------------------------------- |
| 移动光标 | `BTNU` / `BTND` / `BTNL` / `BTNR` |
| 左键(揭开;在开始界面上 = 开始) | `BTNC` |
| 插旗模式(拨上去后左键 = 插/取消旗) | `SW0` 或 `SW3` |
| 返回上一层(关卡内) | 把光标移到盘面**左上角黄格**再按 `BTNC` |
**鼠标**:默认按键;想用真鼠标就把顶层参数 `USE_PS2_MOUSE` 设为 1(需要 Digilent
Pmod PS2 插在 JA + 一个 PS/2 鼠标)。`cpu21_ps2_mouse.v` 只实现了**接收**(发 0xF4
初始化要靠鼠标产生的时钟脉冲,不是固定延时能糊的,所以没写假的);多数鼠标上电即上报,
需要 0xF4 的那种得自己补。USB 鼠标在这个核上不现实(协议栈放不下)。C 程序两种方式
完全不用改。
## 6. 验证状态(说实话)
| 部分 | 状态 |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| C 工具链链路 | **已实测**:编译/链接/ISA 检查/hex 生成/入口地址逐条核对 全部通过 |
| `saolei.c` / `isr.c` / `start.c` / `linker.ld` | 逻辑对着 RTL 写;**未在仿真器/硬件上跑过** |
| `cpu21_mmio.v` 扩展、`cpu21_vga.v`、`cpu21_seg7.v`、`cpu21_pointer.v`、板级顶层、XDC | **未综合、未上板** |
建议验证顺序:① 跑 `pipelined-cpu/tools/run_regress.ps1` 确认原来的跑马灯/中断/指令
测试仍全 PASS;② 定义 `SAOLEI_ROM` 跑行为仿真,看有没有往 `0x2000` 起的显存写字节、
按键后有没有跳进 `0x200`;③ 再上板。
## 7. 已知可改进项
- **屏幕上没有字母**:字模是 4 位字形(0-15),所以"开始"是绿色按钮条 + 光标框而不是
"START" 字样。要显示文字就把格子编码扩成 5 位字形(`mmio.h` 的 `CELL()` 与
`cpu21_vga.v` 的字模表各改一处,颜色降到 3 位)。
- 棋盘固定 16×16、40 雷(16 是 2 的幂,索引全用移位,一次除法都不用);要改大小就改
`saolei.c` 顶部的 `BW/BH/NMINES`。
- `run_regress.ps1` 里还没有扫雷的自动测试项。
+129
View File
@@ -0,0 +1,129 @@
/* -----------------------------------------------------------------------------
* isr.c —— 中断服务程序:从 MMIO 读鼠标事件(**这就是"中断读鼠标操作的指令"**)
*
* 这颗核的中断硬件行为(见 srcs/rtl/cpu21_riscv_irq_ctrl.v):
* * irq_i 上升沿 -> 挂起位置 1;响应中断时硬件自动清挂起位、清 ustatus[0](MIE)、
* 把返回地址压进内部 EPC 栈;
* * 从 uepc 返回用 `uret`(编码 0x00200073,不在标准 C 里,只能内联汇编);
* * **硬件不保存任何通用寄存器**,所以软件必须自己保证不破坏被打断的代码。
*
* 因此这里分了三个固定地址的入口 + 一个纯 C 主体:
* 1) isr1_entry(.text.isr1 = 0x100):**关卡(游戏)入口**,调 level_main()。
* 游戏是一整段会调用别的函数的 C 程序,所以入口把**所有调用者保存寄存器**
* (ra、t0-t6、a0-a7)都存起来,并用 csrrsi 打开 MIE 让鼠标中断能嵌套;
* 2) isr2_entry(.text.isr2 = 0x200):**鼠标读取**,只存 ra 后调下面的 isr_body;
* 3) isr3_entry(.text.isr3 = 0x300):备用,直接 uret;
* 4) isr_body:**纯 C**,本文件用 -ffixed-x1,x5..x7,x10..x17,x28..x31 编译,
* 逼 GCC 只用 s0-s11(callee-saved,函数自己会存/恢复)和 sp,于是被打断的
* 代码的调用者保存寄存器绝对不会被破坏 —— 这样主体就是安全的纯 C 叶子函数。
*
* 主体要做的事:把硬件事件 FIFO(深度 16)里的项全部搬到软件环形队列,
* 主循环再从队列里取。FIFO 读空后中断线自动回 0,下一个事件会重新产生上升沿,
* 所以不会丢事件。
* ---------------------------------------------------------------------------*/
#include "mmio.h"
/* ---- 与 saolei.c 共用的软件事件队列(定义在这里,声明在 mmio.h) ---- */
volatile unsigned g_evq[EVQ_SIZE];
volatile unsigned g_evq_head; /* 主循环消费 */
volatile unsigned g_evq_tail; /* 中断生产 */
volatile unsigned g_evq_lost; /* 队列满丢弃计数(调试) */
/* 中断主体:纯 C 叶子函数(不调用任何函数),只使用 s 寄存器和 sp。
* 编译本文件时必须加 -ffixed-x1 -ffixed-x5..x7 -ffixed-x10..x17 -ffixed-x28..x31
* (见 tools/build.ps1),否则它会用到调用者保存寄存器从而破坏被打断的代码。 */
__attribute__((used, noinline)) void isr_body(void);
void isr_body(void)
{
unsigned ev, next;
/* 硬件 FIFO 非空就一直搬(STATUS[0] = 非空) */
while ((REG_R(REG_STATUS) & 1u) != 0u)
{
ev = REG_R(REG_MDATA); /* lw:{btn[20:16], dy[15:8], dx[7:0]} */
next = (g_evq_tail + 1u) & EVQ_MASK;
if (next != g_evq_head)
{
g_evq[g_evq_tail] = ev;
g_evq_tail = next;
}
else
{
g_evq_lost = g_evq_lost + 1u; /* 软件队列满:丢弃并计数 */
}
REG_W(REG_MACK) = 1u; /* sw:弹出硬件 FIFO 一项 */
}
}
/* ---------------------------------------------------------------------------
* 三个中断入口,分别在链接脚本固定住的 0x100 / 0x200 / 0x300。
*
* IRQ1 = 0x100:**关卡(游戏)入口**。游戏是一整段会调用别的函数的 C 程序,
* 所以入口必须把**所有调用者保存寄存器**(ra、t0-t6、a0-a7)都存起来,
* 否则返回时会把被打断的背景程序弄坏。进入中断时硬件已把 MIE 清 0,这里再
* 重新打开,鼠标中断(IRQ2 优先级更高)才能嵌套进来;uret 时硬件会从内部
* 栈里恢复原来的 MIE。
* IRQ2 = 0x200:**鼠标读取**。C 主体用 -ffixed 编译(只用 s 寄存器和 sp),
* 所以入口只要保存 ra。
* IRQ3 = 0x300:备用(保留给拨码开关等外部源)。
* ------------------------------------------------------------------------- */
extern void level_main(void);
__attribute__((naked, section(".text.isr1"), used, noinline)) void isr1_entry(void)
{
__asm__ volatile(
"addi sp, sp, -64\n\t"
"sw ra, 0(sp)\n\t"
"sw t0, 4(sp)\n\t"
"sw t1, 8(sp)\n\t"
"sw t2, 12(sp)\n\t"
"sw t3, 16(sp)\n\t"
"sw t4, 20(sp)\n\t"
"sw t5, 24(sp)\n\t"
"sw t6, 28(sp)\n\t"
"sw a0, 32(sp)\n\t"
"sw a1, 36(sp)\n\t"
"sw a2, 40(sp)\n\t"
"sw a3, 44(sp)\n\t"
"sw a4, 48(sp)\n\t"
"sw a5, 52(sp)\n\t"
"sw a6, 56(sp)\n\t"
"sw a7, 60(sp)\n\t"
"csrrsi x0, 0x004, 1\n\t" /* ustatus[0]=MIE:允许鼠标中断嵌套 */
"jal ra, level_main\n\t"
"lw a7, 60(sp)\n\t"
"lw a6, 56(sp)\n\t"
"lw a5, 52(sp)\n\t"
"lw a4, 48(sp)\n\t"
"lw a3, 44(sp)\n\t"
"lw a2, 40(sp)\n\t"
"lw a1, 36(sp)\n\t"
"lw a0, 32(sp)\n\t"
"lw t6, 28(sp)\n\t"
"lw t5, 24(sp)\n\t"
"lw t4, 20(sp)\n\t"
"lw t3, 16(sp)\n\t"
"lw t2, 12(sp)\n\t"
"lw t1, 8(sp)\n\t"
"lw t0, 4(sp)\n\t"
"lw ra, 0(sp)\n\t"
"addi sp, sp, 64\n\t"
"uret\n\t");
}
__attribute__((naked, section(".text.isr2"), used, noinline)) void isr2_entry(void)
{
__asm__ volatile(
"addi sp, sp, -8\n\t" /* 保存被打断代码的 ra(jal 会覆盖它) */
"sw ra, 0(sp)\n\t"
"jal ra, isr_body\n\t" /* 调纯 C 主体(它自己保存 s 寄存器) */
"lw ra, 0(sp)\n\t"
"addi sp, sp, 8\n\t"
"uret\n\t"); /* 从 uepc 返回:这颗核的中断返回指令 */
}
__attribute__((naked, section(".text.isr3"), used, noinline)) void isr3_entry(void)
{
__asm__ volatile("uret\n\t");
}
+99
View File
@@ -0,0 +1,99 @@
/* -----------------------------------------------------------------------------
* linker.ld —— 扫雷程序的内存布局
*
* 这颗 CPU 是"哈佛"结构:指令从 ROM 取(instr_addr_o),数据访问 RAM
* (data_addr_o),两个地址空间都从 0 开始,所以这里用两个 MEMORY 区域,
* 都在 0 处开头。
*
* ROM 0x0000_0000 32 KB(= cpu21_board_top 的 ROM_WORDS = 8192 字)
* +0x0000 .text.init 复位入口 _start
* +0x0100 .text.isr1 中断入口 isr1_entry(关卡/游戏,**必须等于 IRQ1_VECTOR**)
* +0x0200 .text.isr2 中断入口 isr2_entry(鼠标读取,必须等于 IRQ2_VECTOR)
* +0x0300 .text.isr3 中断入口 isr3_entry(备用,必须等于 IRQ3_VECTOR)
* +0x0400 .text 其余代码(紧跟 isr3 之后)
*
* RAM 0x0000_0000 4 KB(= saolei_board_top 的 RAM_WORDS = 1024 字)
* +0x0000 .bss 全局变量(全部零初始化,不需要 ROM 里的初值)
* +0x0FFC __stack_top 栈顶(向下增长)
*
* 两个断言很关键(都是这颗 CPU 的硬限制):
* 1) 不能有 .data —— 数据空间读不到 ROM,没有地方放"带初值的变量",
* 所有全局变量必须零初始化;
* 2) 不能有 .rodata —— 同理,程序里不能出现字符串/常量表
* (字模在 RTL 里,不在软件里)。
* ---------------------------------------------------------------------------*/
ENTRY(_start)
MEMORY
{
ROM (rx) : ORIGIN = 0x00000000, LENGTH = 32K
RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 4K
}
SECTIONS
{
/* ------------------------- 数据段(RAM) ------------------------- */
. = 0x00000000;
.bss (NOLOAD) :
{
__bss_start = .;
*(.sbss)
*(.sbss.*)
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
__bss_end = .;
} > RAM
/* 有初值的变量:不允许(见文件头的说明) */
.data : { *(.data) *(.data.*) *(.sdata) *(.sdata.*) } > RAM
/* 只读数据:不允许(没有 auipc / 数据空间读不到 ROM) */
.rodata : { *(.rodata) *(.rodata.*) } > RAM
ASSERT(SIZEOF(.data) == 0, "错误:程序里出现了带初值的全局变量(.data),这颗核没有地方存放它")
ASSERT(SIZEOF(.rodata) == 0, "错误:程序里出现了字符串或常量表(.rodata),这颗核读不到 ROM 里的数据")
/* ------------------------- 代码段(ROM) -------------------------
* 三个中断入口的地址**必须写在段声明里**(.text.isr1 0x100 : ...):
* 只写 ". = 0x100;" 这种位置计数器赋值,在段带 "> ROM" 时会被 ld 忽略,
* 它会按区域顺序把段紧挨着排下去(实测 isr1 掉到了 0x14)。
* 这三个地址要和顶层 cpu21_board_top 的 IRQ1/2/3_VECTOR 参数一致。 */
.text.init 0x00000000 :
{
KEEP(*(.text.init))
} > ROM
.text.isr1 0x00000100 : /* IRQ1:关卡(游戏)入口 */
{
KEEP(*(.text.isr1))
} > ROM
.text.isr2 0x00000200 : /* IRQ2:鼠标读取 */
{
KEEP(*(.text.isr2))
} > ROM
.text.isr3 0x00000300 : /* IRQ3:备用 */
{
KEEP(*(.text.isr3))
} > ROM
.text :
{
*(.text)
*(.text.*)
. = ALIGN(4);
} > ROM
/* ------------------------- 符号 ------------------------- */
__stack_top = ORIGIN(RAM) + LENGTH(RAM) - 4; /* 0x0FFC */
__isr_vector = 0x00000100;
/* .bss 不能长到撞到栈 */
ASSERT(__bss_end < (__stack_top - 0x200),
"错误:.bss 太大,会覆盖栈;请缩小棋盘或加大 RAM_WORDS")
}
+108
View File
@@ -0,0 +1,108 @@
/* -----------------------------------------------------------------------------
* mmio.h —— 扫雷程序的全部 MMIO 地址与编码定义(**只改这一个文件就能换地址**)
*
* 约定:本文件里的所有地址都是"数据空间"的字节地址,和 CPU 的 data_addr_o 一致。
* 写用 *(volatile unsigned *)addr(sw 全字写)或 *(volatile unsigned char *)addr
* (sb 字节写,显存必须用这个)。
*
* -----------------------------------------------------------------------------
* 地址表(与 pipelined-cpu/srcs/rtl/cpu21_mmio.v 里的译码完全一致):
*
* 0x0000_0000 - 0x0000_0FFF 数据 RAM(4 KB,栈 + 全局变量)
* 0x0000_1000 LED 数据寄存器(低 16 位点亮 LED)
* 0x0000_1004 七段数码管:8 位十六进制(低 4 位 = 最右)
* 0x0000_1100 + 0x00 STATUS [0] 事件FIFO非空 [5:1] 项数 [6] 中断使能 [7] 满
* 0x0000_1100 + 0x04 DATA [7:0] dx [15:8] dy [20:16] 按键(读无副作用)
* 0x0000_1100 + 0x08 ACK 写任意值 -> 弹出一次事件
* 0x0000_1100 + 0x0C CTRL [0] 鼠标中断使能
* 0x0000_1100 + 0x10 INPUT [15:0] 拨码开关 [20:16] 按键
* 0x0000_1100 + 0x14 TIME 自由计数器(随机种子)
* 0x0000_1100 + 0x18 FRAME VGA 帧计数(闪烁用)
* 0x0000_1100 + 0x1C EVENTCNT 累计事件数
* 0x0000_1100 + 0x20 LEVEL 写 1 -> 触发"关卡入口"软件中断(IRQ2)
* 0x0000_1100 + 0x24 MAGIC 0xCAFE0002(自检:读得到说明 MMIO 活着)
* 0x0000_1140 + 0x00 DISP_CTRL [0] 光标可见 [6:4] 背景色
* 0x0000_1140 + 0x04 DISP_CURSOR [5:0] 列 [13:8] 行
* 0x0000_1140 + 0x08 DISP_MAGIC 0xCAFE0001
* 0x0000_2000 - 0x0000_27FF 显存:40 列 x 30 行,每格 1 字节,共 1200 字节
*
* 鼠标事件单位:1/8 格(按键一次 = 8 = 移动一格;PS/2 鼠标用原始计数)。
* 左键点击 -> 事件的 btn 位 0 置 1;右键(PS/2)-> 位 1。
*
* ⚠ 如果你的外设地址和上表不同:只改下面这几个宏,.c 文件不用动。
* ⚠ 三个中断入口必须与 linker.ld 里 .text.isr1/2/3 的地址一致:
* IRQ1(0x100) = 关卡(游戏)入口,IRQ2(0x200) = 鼠标读取,IRQ3(0x300) = 备用。
* 顶层 cpu21_board_top 的 IRQ1/2/3_VECTOR 参数也要跟着改(默认就是这个布局)。
* ---------------------------------------------------------------------------*/
#ifndef SAOLEI_MMIO_H
#define SAOLEI_MMIO_H
/* ---- LED / 七段数码管 ---- */
#define LED_ADDR 0x00001000u
#define REG_SEG 0x00001004u
/* ---- 显存 ---- */
#define FB_BASE 0x00002000u /* 显存起始(字节地址) */
#define FB_PITCH 40u /* 每行 40 格 */
#define FB_ROWS 30u /* 30 行 */
#define GRID_COLS 40u
#define GRID_ROWS 30u
/* ---- 鼠标寄存器组 ---- */
#define REG_STATUS 0x00001100u
#define REG_MDATA 0x00001104u
#define REG_MACK 0x00001108u
#define REG_MCTRL 0x0000110Cu
#define REG_INPUT 0x00001110u
#define REG_TIME 0x00001114u
#define REG_FRAME 0x00001118u
#define REG_EVCNT 0x0000111Cu
#define REG_LEVEL 0x00001120u
#define REG_MMAGIC 0x00001124u
/* ---- 显示控制器寄存器 ---- */
#define REG_CTRL 0x00001140u
#define REG_CURSOR 0x00001144u
#define REG_DMAGIC 0x00001148u
/* ---- 访问宏 ---- */
#define REG_R(a) (*(volatile unsigned *)((unsigned)(a)))
#define REG_W(a) (*(volatile unsigned *)((unsigned)(a)))
/* 显存按字节写(只能 sb,不能 lb —— 这颗核没有 lb/lbu) */
static inline void fb_write(unsigned idx, unsigned val)
{
*(volatile unsigned char *)(FB_BASE + idx) = (unsigned char)(val & 0xFFu);
}
/* ---- 格子编码:1 字节 = [3:0] 字形 [6:4] 颜色 [7] 保留 ---- */
#define G_BLANK 0u /* 空 */
#define G_N1 1u /* 数字 1..8 = 1..8 */
#define G_COVER 9u /* 未揭开 */
#define G_FLAG 10u /* 旗 */
#define G_MINE 11u /* 雷 */
#define G_BOOM 12u /* 爆炸 */
#define G_QUEST 13u /* 问号 */
#define G_BAR 14u /* 横线 */
#define G_SOLID 15u /* 实心 */
#define C_BLACK 0u
#define C_BLUE 1u
#define C_GREEN 2u
#define C_CYAN 3u
#define C_RED 4u
#define C_MAGENTA 5u
#define C_YELLOW 6u
#define C_GRAY 7u
#define CELL(g, c) ((unsigned)((g) | ((c) << 4)))
/* ---- 软件事件环形队列:中断服务程序(isr.c)生产,主循环(saolei.c)消费 ---- */
#define EVQ_SIZE 64u
#define EVQ_MASK (EVQ_SIZE - 1u)
extern volatile unsigned g_evq[EVQ_SIZE];
extern volatile unsigned g_evq_head; /* 主循环消费 */
extern volatile unsigned g_evq_tail; /* 中断生产 */
extern volatile unsigned g_evq_lost; /* 队列满丢弃计数(调试用) */
#endif /* SAOLEI_MMIO_H */
+635
View File
@@ -0,0 +1,635 @@
/* -----------------------------------------------------------------------------
* saolei.c —— 纯 C 扫雷(RV32IM 子集:只用这颗核实现了的指令)
*
* 显示:把每个格子写成显存里的 1 个字节(MMIO 0x2000 起)
* 输入:鼠标事件(中断驱动)——中断里搬到软件队列,主循环在下面处理
*
* 针对这颗 CPU 的硬约束(很重要,改代码时别踩):
* 1) 存储只有 lw / sw / sb,**没有 lb/lh/lbu/lhu** ——
* 所以只按字节"写"显存,从不按字节"读";所有状态都用 32 位字保存。
* 2) **没有 auipc** —— 编译时必须 -fno-pic -mcmodel=medlow,并且不能出现
* 只读数据(.rodata),所以本文件没有任何字符串/常量表(字模在 RTL 里)。
* 3) 乘除只有 mul/mulh/mulhsu/mulhu 和**无符号**的 divu/remu ——
* 所以本文件所有索引运算都用"2 的幂 + 移位/掩码",一次除法都不用。
* 4) 没有 fence/ebreak 等;中断返回是 uret(在 isr.c 里)。
*
* 棋盘:16 x 16,40 颗雷(16 是 2 的幂 => 索引全用移位,颜色/字形查表也不用除法)
* 网格:40 列 x 30 行,棋盘居中放在 (行 7..22, 列 12..27)
* ---------------------------------------------------------------------------*/
#include "mmio.h"
#define BW 16u /* 棋盘宽(格) */
#define BH 16u /* 棋盘高(格) */
#define NCELLS (BW * BH) /* 256 */
#define NMINES 40u
#define BM_WORDS (NCELLS / 32u) /* 8 */
#define BOARD_C0 12u /* 棋盘左上角在网格里的列 */
#define BOARD_R0 7u /* 棋盘左上角在网格里的行 */
/* 游戏状态 */
#define ST_PLAY 0u
#define ST_WIN 1u
#define ST_LOSE 2u
/* ---- 棋盘状态:3 个位图,每个 8 个字(256 位) ---- */
static unsigned mine_bm[BM_WORDS];
static unsigned open_bm[BM_WORDS];
static unsigned flag_bm[BM_WORDS];
/* 其它全局状态(全部进 .bss,靠 start.c 清零) */
static unsigned opened_cnt;
static unsigned flags_cnt;
static unsigned g_state;
static unsigned g_first_done;
static unsigned rnd_state;
static unsigned cur_r, cur_c; /* 光标所在的网格格 */
static int acc_x, acc_y; /* 指针累积量(1/8 格,有符号) */
static unsigned bfs_q[NCELLS]; /* 揭开用的队列 */
/* ============================ 小工具 ============================ */
/* 位图取位/置位/清位:i = r*16 + c,全部用移位和掩码 */
static unsigned bget(const unsigned *bm, unsigned i)
{
return (bm[i >> 5] >> (i & 31u)) & 1u;
}
static void bset(unsigned *bm, unsigned i)
{
bm[i >> 5] |= (1u << (i & 31u));
}
static void bclr(unsigned *bm, unsigned i)
{
bm[i >> 5] &= ~(1u << (i & 31u));
}
static void draw_cell(unsigned gr, unsigned gc, unsigned glyph, unsigned color)
{
fb_write(gr * GRID_COLS + gc, CELL(glyph, color));
}
/* 把棋盘格 (r,c) 画出来 */
static void draw_tile(unsigned r, unsigned c, unsigned glyph, unsigned color)
{
draw_cell(BOARD_R0 + r, BOARD_C0 + c, glyph, color);
}
static unsigned num_color(unsigned n)
{
if (n == 1u)
return C_BLUE;
if (n == 2u)
return C_GREEN;
if (n == 3u)
return C_RED;
if (n == 4u)
return C_MAGENTA;
if (n == 5u)
return C_YELLOW;
if (n == 6u)
return C_CYAN;
return C_GRAY;
}
static unsigned in_board(void)
{
if (cur_r < BOARD_R0)
return 0u;
if (cur_r >= (BOARD_R0 + BH))
return 0u;
if (cur_c < BOARD_C0)
return 0u;
if (cur_c >= (BOARD_C0 + BW))
return 0u;
return 1u;
}
static unsigned cur_index(void)
{
return ((cur_r - BOARD_R0) << 4) + (cur_c - BOARD_C0); /* *BW(=16) 用移位 */
}
/* LCG 随机数:只用 mul/add/移位 */
static unsigned rnd_next(void)
{
rnd_state = rnd_state * 1103515245u + 12345u;
return rnd_state >> 16;
}
/* ============================ 画面 ============================ */
static void clear_screen(void)
{
unsigned i;
for (i = 0u; i < (GRID_COLS * GRID_ROWS); i = i + 1u)
{
fb_write(i, 0u);
}
}
static void draw_frame(void)
{
unsigned gc, gr;
for (gc = (BOARD_C0 - 1u); gc <= (BOARD_C0 + BW); gc = gc + 1u)
{
draw_cell(BOARD_R0 - 1u, gc, G_SOLID, C_CYAN);
draw_cell(BOARD_R0 + BH, gc, G_SOLID, C_CYAN);
}
for (gr = (BOARD_R0 - 1u); gr <= (BOARD_R0 + BH); gr = gr + 1u)
{
draw_cell(gr, BOARD_C0 - 1u, G_SOLID, C_CYAN);
draw_cell(gr, BOARD_C0 + BW, G_SOLID, C_CYAN);
}
}
static void draw_all_covered(void)
{
unsigned r, c;
for (r = 0u; r < BH; r = r + 1u)
{
for (c = 0u; c < BW; c = c + 1u)
{
draw_tile(r, c, G_COVER, C_GRAY);
}
}
}
static void update_cursor_reg(void)
{
REG_W(REG_CURSOR) = (cur_c & 31u) | ((cur_r & 31u) << 8);
}
static void set_bg(unsigned color)
{
REG_W(REG_CTRL) = (color << 4) | 1u; /* bit0 = 光标显示 */
}
/* ============================ 游戏逻辑 ============================ */
static unsigned count_adj(unsigned i)
{
unsigned r = i >> 4, c = i & 15u, n = 0u, rr, cc;
unsigned r0 = (r == 0u) ? 0u : (r - 1u);
unsigned r1 = (r + 1u >= BH) ? (BH - 1u) : (r + 1u);
unsigned c0 = (c == 0u) ? 0u : (c - 1u);
unsigned c1 = (c + 1u >= BW) ? (BW - 1u) : (c + 1u);
for (rr = r0; rr <= r1; rr = rr + 1u)
{
for (cc = c0; cc <= c1; cc = cc + 1u)
{
if ((rr == r) && (cc == c))
continue;
n = n + bget(mine_bm, (rr << 4) + cc);
}
}
return n;
}
static void lose(unsigned hit)
{
unsigned k;
for (k = 0u; k < NCELLS; k = k + 1u)
{
if (bget(mine_bm, k))
{
if (k == hit)
{
draw_tile(k >> 4, k & 15u, G_BOOM, C_RED);
}
else if (!bget(flag_bm, k))
{
draw_tile(k >> 4, k & 15u, G_MINE, C_RED);
}
}
}
REG_W(LED_ADDR) = 0xDEADu;
g_state = ST_LOSE;
}
static void win(void)
{
unsigned k;
for (k = 0u; k < NCELLS; k = k + 1u)
{
if (bget(mine_bm, k))
{
draw_tile(k >> 4, k & 15u, G_FLAG, C_YELLOW);
}
}
REG_W(LED_ADDR) = 0xF00Du;
g_state = ST_WIN;
}
static void reveal_at(unsigned i)
{
unsigned qh = 0u, qt = 0u, cur, n, r, c, rr, cc, ni;
unsigned r0, r1, c0, c1;
if (bget(open_bm, i) || bget(flag_bm, i))
return;
if (bget(mine_bm, i))
{
lose(i);
return;
}
bfs_q[qt] = i;
qt = qt + 1u;
while (qh < qt)
{
cur = bfs_q[qh];
qh = qh + 1u;
if (bget(open_bm, cur))
continue;
bset(open_bm, cur);
opened_cnt = opened_cnt + 1u;
n = count_adj(cur);
draw_tile(cur >> 4, cur & 15u, n, num_color(n));
if (n != 0u)
continue;
/* 空白格:把周围 8 格入队(队列有界,满了就丢,不会越界) */
r = cur >> 4;
c = cur & 15u;
r0 = (r == 0u) ? 0u : (r - 1u);
r1 = (r + 1u >= BH) ? (BH - 1u) : (r + 1u);
c0 = (c == 0u) ? 0u : (c - 1u);
c1 = (c + 1u >= BW) ? (BW - 1u) : (c + 1u);
for (rr = r0; rr <= r1; rr = rr + 1u)
{
for (cc = c0; cc <= c1; cc = cc + 1u)
{
if ((rr == r) && (cc == c))
continue;
ni = (rr << 4) + cc;
if (qt < NCELLS)
{
bfs_q[qt] = ni;
qt = qt + 1u;
}
}
}
}
if (opened_cnt == (NCELLS - NMINES))
win();
}
static void place_mines(unsigned safe)
{
unsigned placed = 0u, i, r, c;
unsigned sr = safe >> 4;
unsigned sc = safe & 15u;
while (placed < NMINES)
{
i = rnd_next() & 255u; /* NCELLS = 256,正好 2 的幂,不需要取模 */
r = i >> 4;
c = i & 15u;
/* 首点及其 8 邻居不放雷,保证第一次点击一定能展开 */
if ((r + 1u >= sr) && (r <= (sr + 1u)) &&
(c + 1u >= sc) && (c <= (sc + 1u)))
continue;
if (bget(mine_bm, i))
continue;
bset(mine_bm, i);
placed = placed + 1u;
}
}
static void click_here(void)
{
unsigned i;
if (!in_board())
return;
i = cur_index();
if (bget(flag_bm, i))
return;
if (!g_first_done)
{
place_mines(i);
g_first_done = 1u;
}
reveal_at(i);
}
static void toggle_flag(void)
{
unsigned i, r, c;
if (!in_board())
return;
i = cur_index();
if (bget(open_bm, i))
return;
r = cur_r - BOARD_R0;
c = cur_c - BOARD_C0;
if (bget(flag_bm, i))
{
bclr(flag_bm, i);
flags_cnt = flags_cnt - 1u;
draw_tile(r, c, G_COVER, C_GRAY);
}
else
{
bset(flag_bm, i);
flags_cnt = flags_cnt + 1u;
draw_tile(r, c, G_FLAG, C_YELLOW);
}
REG_W(LED_ADDR) = NMINES - flags_cnt;
}
/* ============================ 鼠标事件 ============================ */
/* 关卡内:光标允许落在盘面外那一圈边框上(左上角那格是"返回") */
static void move_cursor(int dr, int dc)
{
int nr = (int)cur_r + dr;
int nc = (int)cur_c + dc;
if (nr < (int)(BOARD_R0 - 1u))
nr = (int)(BOARD_R0 - 1u);
if (nr > (int)(BOARD_R0 + BH))
nr = (int)(BOARD_R0 + BH);
if (nc < (int)(BOARD_C0 - 1u))
nc = (int)(BOARD_C0 - 1u);
if (nc > (int)(BOARD_C0 + BW))
nc = (int)(BOARD_C0 + BW);
cur_r = (unsigned)nr;
cur_c = (unsigned)nc;
update_cursor_reg();
}
/* ======================= 运行环境状态(背景 / 关卡) =======================
* 程序分成两层,正好对应"伪操作系统 + 关卡"的设想:
* 背景程序 main() 从头跑到尾:画开始界面、等鼠标;
* 关卡程序 level_main() 跑在 IRQ1(0x100) 的中断服务程序里:点"开始"进入,
* 点"返回"就 uret 回到背景被打断的那条指令。
* ========================================================================= */
static unsigned g_in_level; /* 0 = 背景界面,1 = 关卡内 */
static unsigned g_exit; /* 关卡内请求返回 */
/* 背景界面上"开始"按钮的位置(行 20,列 17..22) */
#define START_R 20u
#define START_C0 17u
#define START_C1 22u
/* 背景用:光标可以在整个画面上移动 */
static void move_cursor_screen(int dr, int dc)
{
int nr = (int)cur_r + dr;
int nc = (int)cur_c + dc;
if (nr < 0)
nr = 0;
if (nr > (int)(GRID_ROWS - 1u))
nr = (int)(GRID_ROWS - 1u);
if (nc < 0)
nc = 0;
if (nc > (int)(GRID_COLS - 1u))
nc = (int)(GRID_COLS - 1u);
cur_r = (unsigned)nr;
cur_c = (unsigned)nc;
update_cursor_reg();
}
/* 处理一个鼠标事件:ev = {btn[20:16], dy[15:8], dx[7:0]} */
static void handle_event(unsigned ev)
{
unsigned ux = ev & 0xFFu;
unsigned uy = (ev >> 8) & 0xFFu;
unsigned btn = (ev >> 16) & 31u;
int dx = (ux & 0x80u) ? ((int)ux - 256) : (int)ux;
int dy = (uy & 0x80u) ? ((int)uy - 256) : (int)uy;
unsigned want_flag = (REG_R(REG_INPUT) & 9u) != 0u; /* SW0 或 SW3 = 插旗模式 */
if ((btn & 1u) != 0u)
{ /* 左键 */
if (g_in_level == 0u)
{
/* 背景:点到"开始"按钮 -> 拉起软件中断,跳到 IRQ1 里的关卡程序 */
if ((cur_r == START_R) && (cur_c >= START_C0) && (cur_c <= START_C1))
{
g_in_level = 1u; /* 关卡程序返回时自己清 0 */
REG_W(REG_LEVEL) = 1u;
}
}
else if ((cur_r == (BOARD_R0 - 1u)) && (cur_c == (BOARD_C0 - 1u)))
{
g_exit = 1u; /* 关卡:点左上角黄格 = 返回上一层 */
}
else if (want_flag != 0u)
{
toggle_flag();
}
else
{
click_here();
}
return;
}
if ((btn & 2u) != 0u)
{ /* 右键(PS/2 鼠标有;按键没有) */
if (g_in_level != 0u)
toggle_flag();
return;
}
/* 移动:单位是 1/8 格,累计到 8 就动一格(纯加减比较,没有除法) */
acc_x += dx;
acc_y += dy;
while (acc_x >= 8)
{
if (g_in_level != 0u)
move_cursor(0, 1);
else
move_cursor_screen(0, 1);
acc_x -= 8;
}
while (acc_x <= -8)
{
if (g_in_level != 0u)
move_cursor(0, -1);
else
move_cursor_screen(0, -1);
acc_x += 8;
}
while (acc_y >= 8)
{
if (g_in_level != 0u)
move_cursor(1, 0);
else
move_cursor_screen(1, 0);
acc_y -= 8;
}
while (acc_y <= -8)
{
if (g_in_level != 0u)
move_cursor(-1, 0);
else
move_cursor_screen(-1, 0);
acc_y += 8;
}
}
/* ============================ 背景程序(伪操作系统)============================
* 从头跑到尾:画开始界面 -> 等鼠标 -> 点到"开始"按钮就拉起软件中断。
* ========================================================================== */
static void draw_start_screen(void)
{
unsigned i;
clear_screen();
/* 标题区:两排雷图案,纯图形(字模里没有字母;要显示文字就得扩字模,见 README) */
for (i = 0u; i < 6u; i = i + 1u)
{
draw_cell(6u, 15u + i * 2u, G_MINE, C_RED);
draw_cell(8u, 15u + i * 2u, G_MINE, C_RED);
}
/* "开始"按钮:绿色实心条,上下各一条黄线框住它 */
for (i = START_C0; i <= START_C1; i = i + 1u)
{
draw_cell(START_R - 1u, i, G_BAR, C_YELLOW);
draw_cell(START_R, i, G_SOLID, C_GREEN);
draw_cell(START_R + 1u, i, G_BAR, C_YELLOW);
}
/* 下方提示条 */
for (i = 12u; i <= 27u; i = i + 1u)
{
draw_cell(24u, i, G_BAR, C_CYAN);
}
set_bg(C_BLACK);
cur_r = START_R;
cur_c = (START_C0 + START_C1) / 2u;
update_cursor_reg();
REG_W(LED_ADDR) = 0x0001u; /* LED = 背景状态码 */
REG_W(REG_SEG) = 0u;
}
/* ============================ 关卡(游戏)程序 ============================
* **运行在 IRQ1 的中断服务程序里**(入口 isr1_entry,固定地址 0x100):
* 背景 main() 点"开始" -> 写 LEVEL 寄存器 -> 硬件拉起 irq_i[1]
* -> CPU 跳到 0x100 -> isr1_entry 保存现场、打开 MIE、调用本函数
* -> 这里跑完整一局(鼠标中断 0x200 优先级更高,会嵌套进来把事件放进队列,
* 所以本循环只要轮询队列)
* -> 点左上角黄格 = 返回:清 LEVEL、return -> isr1_entry 恢复现场并 uret
* -> 回到背景程序被打断的那条指令,继续等下一次"开始"。
* ========================================================================== */
#define RET_R (BOARD_R0 - 1u)
#define RET_C (BOARD_C0 - 1u)
void level_main(void)
{
unsigned r;
/* ---- 关卡初始化 ---- */
for (r = 0u; r < BM_WORDS; r = r + 1u)
{
mine_bm[r] = 0u;
open_bm[r] = 0u;
flag_bm[r] = 0u;
}
opened_cnt = 0u;
flags_cnt = 0u;
g_first_done = 0u;
g_state = ST_PLAY;
g_exit = 0u;
acc_x = 0;
acc_y = 0;
cur_r = BOARD_R0 + 8u;
cur_c = BOARD_C0 + 8u;
clear_screen();
draw_frame();
draw_all_covered();
draw_cell(RET_R, RET_C, G_BAR, C_YELLOW); /* 左上角黄格 = 返回 */
set_bg(C_BLUE);
update_cursor_reg();
REG_W(LED_ADDR) = NMINES;
REG_W(REG_SEG) = NMINES; /* 数码管显示剩余雷数 */
/* ---- 关卡主循环 ---- */
while (g_exit == 0u)
{
if (g_evq_head != g_evq_tail)
{
unsigned ev = g_evq[g_evq_head];
g_evq_head = (g_evq_head + 1u) & EVQ_MASK;
if (g_state == ST_PLAY)
{
handle_event(ev);
}
else if ((ev >> 16) & 1u)
{
g_exit = 1u; /* 分出胜负后任意点击 = 返回 */
}
}
else if (g_state != ST_PLAY)
{
/* 分出胜负后让背景闪动(用 VGA 帧计数做时间基准,不需要定时器) */
static unsigned phase;
if ((REG_R(REG_FRAME) & 31u) == 0u)
{
phase = phase ^ 1u;
if (phase != 0u)
{
set_bg((g_state == ST_WIN) ? C_GREEN : C_RED);
}
else
{
set_bg(C_BLUE);
}
}
}
}
REG_W(REG_LEVEL) = 0u; /* 清软件中断:否则下次点"开始"没有新的上升沿 */
g_in_level = 0u; /* 回到背景状态 */
draw_start_screen(); /* 恢复开始界面 */
}
/* ============================ 背景主程序 ============================ */
extern void crt_zero_bss(void);
int main(void)
{
crt_zero_bss(); /* 清 .bss(上电 RAM 本来就是 0,保险起见) */
rnd_state = REG_R(REG_TIME) | 1u; /* 用自由计数器当随机种子 */
REG_W(REG_MCTRL) = 1u; /* 打开鼠标中断(IRQ2,入口 0x200) */
REG_W(REG_LEVEL) = 0u; /* 清关卡软中断 */
g_in_level = 0u;
draw_start_screen();
/* 背景循环:只服务鼠标事件;点"开始"后会被 IRQ1 里的关卡程序"接管",
* 关卡返回时又回到这个循环里继续。 */
for (;;)
{
if (g_evq_head != g_evq_tail)
{
unsigned ev = g_evq[g_evq_head];
g_evq_head = (g_evq_head + 1u) & EVQ_MASK;
handle_event(ev);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
/* -----------------------------------------------------------------------------
* start.c —— C 运行时启动:设置栈指针、把控制权交给 main。
*
* 这是全工程唯一非写不可的内联汇编(C 语言无法表达"设置 sp")。
* 复位时 PC = 0,所以 _start 必须落在 .text.init(见 linker.ld)。
*
* 注意用的是 lui/addi 绝对寻址 + jalr,**不是** auipc —— 这颗核没有 auipc。
* ---------------------------------------------------------------------------*/
extern unsigned __stack_top[];
int main(void);
__attribute__((naked, section(".text.init"), used, noreturn)) void _start(void)
{
__asm__ volatile(
"lui sp, %hi(__stack_top)\n\t"
"addi sp, sp, %lo(__stack_top)\n\t"
"lui t0, %hi(main)\n\t"
"addi t0, t0, %lo(main)\n\t"
"jalr x0, t0, 0\n\t");
}
/* 清 .bss(纯 C)。上电时 FPGA 里的 RAM 本来就是 0,这一步只是保险。 */
extern unsigned __bss_start[];
extern unsigned __bss_end[];
void crt_zero_bss(void)
{
unsigned *p = __bss_start;
while (p < __bss_end)
{
*p = 0u;
p = p + 1u;
}
}
+174
View File
@@ -0,0 +1,174 @@
# =============================================================================
# build.ps1 —— 扫雷程序一键构建:C -> 汇编/目标文件 -> ELF -> .hex + .vh
#
# 用法(在 saolei\ 目录下):
# powershell -ExecutionPolicy Bypass -File tools\build.ps1
# powershell -ExecutionPolicy Bypass -File tools\build.ps1 -Prefix riscv64-unknown-elf-
#
# 产物(都在 saolei\build\ 下):
# saolei.lst objdump -d 反汇编清单(这就是"编译出来的汇编",可以直接看)
# saolei.elf 可执行文件
# saolei.bin 裸二进制
# saolei.hex $readmemh 格式:拷到 pipelined-cpu\srcs\rom\saolei.hex 就能
# 用现成的测试平台(tb_no_intr 等)仿真
# saolei.map 链接 map(看段地址和大小)
# 并且会生成 pipelined-cpu\srcs\board\cpu21_saolei_rom.vh —— 供 cpu21_board_top.v 用
# `include 固化进 ROM(在 Vivado 里定义宏 SAOLEI_ROM 即可切换到扫雷程序)
#
# 构建流程里有三道针对这颗 CPU 的关卡:
# 1) -fno-pic -mcmodel=medlow -mno-relax:避免 auipc / 链接器松弛
# 2) 链接脚本里的 ASSERT:禁止 .data / .rodata(这颗核读不到 ROM 里的数据)
# 3) tools\check_isa.py:逐条检查反汇编,凡是用到未实现指令(auipc/lb/lbu/div...)
# 直接报错并给出修复提示
# =============================================================================
param(
[string]$Prefix = "",
[int]$RomWords = 8192,
[switch]$SkipIsaCheck
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$sw = Join-Path $root 'sw'
$build = Join-Path $root 'build'
New-Item -ItemType Directory -Force $build | Out-Null
# ---------------------------------------------------------------- 工具链探测
if (-not $Prefix) {
foreach ($cand in 'riscv-none-elf-', 'riscv64-unknown-elf-', 'riscv32-unknown-elf-',
'riscv-none-embed-', 'riscv64-linux-gnu-') {
if (Get-Command ($cand + 'gcc') -ErrorAction SilentlyContinue) { $Prefix = $cand; break }
}
}
if (-not $Prefix) {
Write-Output "ERROR: 找不到 RISC-V 交叉编译器(riscv*-gcc)。"
Write-Output ""
Write-Output "装一个即可(推荐 xPack 的 riscv-none-elf-gcc,解压就用,不用管理员权限):"
Write-Output " 1) 打开 https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases"
Write-Output " 2) 下载 win32-x64 的 .zip(例如 xpack-riscv-none-elf-gcc-14.2.0-3-win32-x64.zip)"
Write-Output " 3) 解压到例如 C:\tools\riscv-none-elf-gcc"
Write-Output " 4) 把 C:\tools\riscv-none-elf-gcc\bin 加进 PATH(或者直接运行本脚本时):"
Write-Output " powershell -ExecutionPolicy Bypass -File tools\build.ps1 -Prefix 'C:\tools\riscv-none-elf-gcc\bin\riscv-none-elf-'"
exit 1
}
$GCC = $Prefix + 'gcc'
$OBJDUMP = $Prefix + 'objdump'
$OBJCOPY = $Prefix + 'objcopy'
$SIZE = $Prefix + 'size'
Write-Output "工具链前缀: $Prefix"
# ---------------------------------------------------------------- 编译参数
# -march=rv32im_zicsr : RV32I + M 扩展 + Zicsr(CSR 指令:csrrw/csrrsi/csrrci)。
# GCC 15 起不再默认给 rv32im 开 zicsr,中断入口里的
# csrrsi 必须显式打开这个扩展才认。
# -mcmodel=medlow -fno-pic : 用 lui/addi 绝对寻址,不产生 auipc
# **必须放开链接器松弛**(不要 -mno-relax):gcc 的函数调用默认是
# auipc ra, %pcrel_hi(f) + jalr ra, ra, %pcrel_lo(f)
# 要靠链接器松弛把它变成一条 jal,否则 auipc 会留在最终代码里(这颗核没有)。
# 松弛只会在两种情况下改变指令:把 pcrel 调用变成 jal,或在使用 gp 相对寻址时
# 变成 gp 相对访问 —— 后者已被 -msmall-data-limit=0 从根上禁止。
# -msmall-data-limit=0 : 不用 gp 相对寻址
# -fno-builtin -nostdlib -ffreestanding : 不要 libc(没有环境,也没有 printf)
# -fno-jump-tables : 不生成跳转表(跳转表会落在 .rodata,这颗核读不到)
$ccCommon = @(
'-march=rv32im_zicsr', '-mabi=ilp32',
'-mcmodel=medlow',
'-fno-pic', '-fno-PIC', '-fno-pie',
'-msmall-data-limit=0', '-fno-common',
'-fno-builtin', '-fno-stack-protector', '-fno-jump-tables',
'-ffreestanding', '-nostdlib',
'-ffunction-sections', '-fdata-sections',
'-Os', '-Wall', '-Wextra'
)
# 中断主体(isr.c)必须只使用 callee-saved 寄存器,否则会破坏被打断的代码。
# 下面把所有"调用者保存"的通用寄存器都固定住,GCC 就只能用 s0-s11 和 sp。
$fixedRegs = @()
foreach ($r in 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 28, 29, 30, 31) {
$fixedRegs += ('-ffixed-x' + $r)
}
# ---------------------------------------------------------------- 编译
$objs = @()
# 跑外部命令并在非 0 退出码时立刻停下(native 命令失败不会抛异常,必须查 $LASTEXITCODE)
function Invoke-Tool {
param([string]$Exe, [string[]]$ArgList, [string]$What)
& $Exe @ArgList
if ($LASTEXITCODE -ne 0) {
Write-Output ("ERROR: 失败 -> " + $What)
exit 1
}
}
Write-Output "编译 start.c ..."
Invoke-Tool -Exe $GCC -ArgList ($ccCommon + @('-c', (Join-Path $sw 'start.c'), '-o', (Join-Path $build 'start.o'))) -What 'start.c'
$objs += (Join-Path $build 'start.o')
Write-Output "编译 saolei.c ..."
Invoke-Tool -Exe $GCC -ArgList ($ccCommon + @('-c', (Join-Path $sw 'saolei.c'), '-o', (Join-Path $build 'saolei.o'))) -What 'saolei.c'
$objs += (Join-Path $build 'saolei.o')
Write-Output "编译 isr.c(额外加 -ffixed 约束)..."
Invoke-Tool -Exe $GCC -ArgList ($ccCommon + $fixedRegs + @('-c', (Join-Path $sw 'isr.c'), '-o', (Join-Path $build 'isr.o'))) -What 'isr.c'
$objs += (Join-Path $build 'isr.o')
# ---------------------------------------------------------------- 链接
$elf = Join-Path $build 'saolei.elf'
$map = Join-Path $build 'saolei.map'
$ld = Join-Path $sw 'linker.ld'
Write-Output "链接 ..."
# 注意:PowerShell 会把未加引号的 "-Wl,--no-relax" 当成参数对解析(逗号引起),
# 所以这里统一用参数数组,所有选项都带引号。
$linkArgs = @(
'-march=rv32im_zicsr', '-mabi=ilp32', '-nostdlib', '-nostartfiles',
"-Wl,-Map=$map",
'-T', $ld, '-o', $elf
) + $objs
Invoke-Tool -Exe $GCC -ArgList $linkArgs -What '链接(-T sw/linker.ld)'
# ---------------------------------------------------------------- 反汇编 + ISA 检查
$lst = Join-Path $build 'saolei.lst'
& $OBJDUMP -d -M no-aliases $elf | Out-File -Encoding utf8 $lst
Write-Output "反汇编清单: $lst"
$isaOk = $true
if (-not $SkipIsaCheck) {
Write-Output ""
& py (Join-Path $PSScriptRoot 'check_isa.py') $lst
if ($LASTEXITCODE -ne 0) { $isaOk = $false }
}
# ---------------------------------------------------------------- 转 hex / vh
$bin = Join-Path $build 'saolei.bin'
$hex = Join-Path $build 'saolei.hex'
$vh = Join-Path $root '..\pipelined-cpu\srcs\board\cpu21_saolei_rom.vh'
& $OBJCOPY -O binary $elf $bin
& py (Join-Path $PSScriptRoot 'elf2hex.py') $bin $hex $RomWords $vh
# ---------------------------------------------------------------- 汇总
Write-Output ""
Write-Output "==== 段大小 ===="
& $SIZE $elf
$isrLine = Select-String -Path $lst -Pattern '^[0-9a-f]+ <(isr1_entry|isr2_entry|isr3_entry|_start)>:' |
Select-Object -ExpandProperty Line
Write-Output "==== 入口地址 ===="
foreach ($l in $isrLine) { Write-Output (" " + $l) }
Write-Output " 三个中断入口必须和顶层 cpu21_board_top 的参数一致:"
Write-Output " linker.ld 的 .text.isr1/2/3 = IRQ1/2/3_VECTOR = 0x100/0x200/0x300"
Write-Output ""
if ($isaOk) {
Write-Output "构建完成:$hex 与 $vh 已生成。"
Write-Output " 仿真:把 saolei.hex 拷到 pipelined-cpu\srcs\rom\saolei.hex,用 tb_no_intr 加载"
Write-Output " 上板:cpu21_saolei_rom.vh 已生成到 srcs/board/,Vivado 里定义宏 SAOLEI_ROM 后综合"
} else {
Write-Output "构建完成,但 ISA 检查没通过(见上面的清单),这个 hex 放到 CPU 上会跑飞。"
exit 1
}
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""check_isa.py —— 校验反汇编清单里没有这颗 CPU 未实现的指令。
用法: python tools/check_isa.py <objdump输出.txt>
为什么需要它:本 CPU(srcs/rtl/cpu21_riscv_decoder.v)只实现了 RV32I 的一个
子集,最容易踩的三个坑是:
* 没有 auipc -> 编译器一旦生成它,整段代码就废了(多为 PIC/rodata 引起)
* 没有 lb/lh/lbu/lhu -> 只要碰 char/short 类型的读就会出现
* 没有有符号 div/rem(只有 divu/remu) -> 有符号除法/取余会被静默算错
所以这里用"白名单"方式检查:出现名单外的助记符就报错并给出修复提示。
退出码 = 违规条数(>255 时截断),0 表示通过。
"""
import sys
# 这颗 CPU 真正实现了的指令(见 cpu21_riscv_decoder.v)
ALLOWED = {
# R 型
"add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and",
# M 扩展(只有这几个)
"mul", "mulh", "mulhsu", "mulhu", "divu", "remu",
# I 型
"addi", "slti", "sltiu", "xori", "ori", "andi", "slli", "srli", "srai",
# 访存:只有 lw / sw / sb(没有 lb/lh/lbu/lhu/sh)
"lw", "sw", "sb",
# 控制流
"beq", "bne", "blt", "bge", "bltu", "bgeu", "jal", "jalr",
# 高位立即数
"lui",
# 系统
"ecall", "uret", "csrrw", "csrrsi", "csrrci",
}
# objdump 有时仍打印伪指令;这些展开后都是上面支持的指令
ALIAS_OK = {
"nop", "mv", "li", "j", "jr", "ret", "neg", "not",
"beqz", "bnez", "blez", "bgez", "bltz", "bgtz",
"seqz", "snez", "sltz", "sgtz",
}
# 违规时给出针对性提示
HINTS = {
"auipc": "出现了 auipc:说明编译带了 PIC/PIE 或访问了只读数据。"
"请用 -fno-pic -mcmodel=medlow,并去掉字符串/常量表(.rodata)",
"lb": "没有 lb:不要用 char/short 类型读内存,改用 32 位字",
"lh": "没有 lh:同上",
"lbu": "没有 lbu:不要用 unsigned char/short 类型读内存(写显存可以用 sb,但别读回来)",
"lhu": "没有 lhu:同上",
"sh": "没有 sh:只用 sw / sb",
"div": "没有有符号 div:改用 unsigned 运算(unsigned 除法会编成 divu)",
"rem": "没有有符号 rem:改用 unsigned 运算(unsigned 取余会编成 remu)",
"divw": "不支持 64 位运算",
"remw": "不支持 64 位运算",
"call": "call 会展开成 auipc+jalr:请直接用普通函数调用(jal)",
"tail": "tail 会展开成 auipc:请用普通跳转",
"fence": "没有 fence/fence.i",
"ebreak": "没有 ebreak",
"mret": "这颗核的中断返回是 uret,不是 mret",
"sret": "这颗核的中断返回是 uret",
"wfi": "没有 wfi:主循环只能用轮询",
"unimp": "出现了 unimp(非法指令/内联汇编占位)",
}
def main():
if len(sys.argv) < 2:
print("用法: python tools/check_isa.py <objdump输出.txt>")
return 2
path = sys.argv[1]
total = 0
violations = []
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
if "\t" not in line:
continue
parts = line.split("\t")
if len(parts) < 3 or not parts[0].strip().endswith(":"):
continue
mnemonic = parts[2].strip().split()[0]
if not mnemonic:
continue
total += 1
if (mnemonic not in ALLOWED) and (mnemonic not in ALIAS_OK):
violations.append((parts[0].strip(), mnemonic, line.rstrip()))
print("检查了 %d 条指令。" % total)
if not violations:
print("ISA 检查通过:全部指令都在本 CPU 的实现范围内。")
return 0
print("")
print("!! ISA 检查失败:有 %d 条指令这颗 CPU 没有实现:" % len(violations))
seen = set()
for addr, mnemonic, raw in violations[:40]:
print(" %s %s" % (addr, raw.strip()))
seen.add(mnemonic)
if len(violations) > 40:
print(" ...(只列出前 40 条)")
print("")
print("修复提示:")
for m in sorted(seen):
print(" * %s: %s" % (m, HINTS.get(m, "不在本 CPU 的指令白名单里")))
return min(len(violations), 255)
if __name__ == "__main__":
sys.exit(main())
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""elf2hex.py —— 把 objcopy 出来的裸二进制转成 ROM 需要的两份文件。
用法: python tools/elf2hex.py <input.bin> <output.hex> [ROM_WORDS] [output.vh]
产出:
1) <output.hex> 每行一个 32 位字($readmemh 格式,供 srcs/rom/ 下的
测试平台加载,例如拷成 srcs/rom/saolei.hex)
2) <output.vh>(可选) rom[i] = 32'h...; 语句,供
pipelined-cpu/srcs/board/cpu21_board_top.v 里 `include
固化进片内 ROM(未用到的地址填 NOP 0x00000013)
(未用到的地址填 NOP 0x00000013)
字序:输入的 .bin 是按字节小端排列的机器码,这里按小端拼成 32 位字。
"""
import sys
NOP = 0x00000013
def main():
if len(sys.argv) < 3:
print("用法: python tools/elf2hex.py <input.bin> <output.hex> [ROM_WORDS] [output.vh]")
return 2
src = sys.argv[1]
dst = sys.argv[2]
words_total = int(sys.argv[3]) if len(sys.argv) > 3 else 8192
vh_path = sys.argv[4] if len(sys.argv) > 4 else None
with open(src, "rb") as f:
data = f.read()
if len(data) % 4 != 0:
print("警告:二进制长度 %d 不是 4 的倍数,尾部会被补 0" % len(data))
data += b"\x00" * (4 - len(data) % 4)
words = []
for i in range(0, len(data), 4):
words.append(int.from_bytes(data[i:i + 4], "little"))
if len(words) > words_total:
print("错误:程序 %d 个字,超过 ROM 容量 %d 个字(改大 ROM_WORDS 或精简程序)"
% (len(words), words_total))
return 1
with open(dst, "w", encoding="ascii") as f:
for w in words:
f.write("%08X\n" % w)
if vh_path:
with open(vh_path, "w", encoding="utf-8") as f:
f.write(" // 由 tools/elf2hex.py 生成,请勿手工编辑\n")
f.write(" // 源文件:%s(%d 个字,其余填 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 个字)%s" % (dst, len(words),
("、%s(%d 个字,NOP 填充)" % (vh_path, words_total)) if vh_path else ""))
return 0
if __name__ == "__main__":
sys.exit(main())