feat(board): 中断综合测试程序上板 + 可变分频/刷新率;README 大幅扩写
板级顶层(srcs/board/cpu21_board_top.v) - ROM 增加 RISC_V_INTRUPT_CCAB_ROM 分支,中断入口 0x0AC/0x1E4/0x308; 与 SAOLEI_ROM / BOARD_ROM_FAST / 默认跑马灯构成 ifdef 链 - 新增参数 CLK_DIV_INTRUPT:跑中断测试程序时用更慢的 CPU 时钟(默认 32 = 3.125 MHz),让数码管上每个数值停留约 160 ms 便于观察 - 数码管扫描分频随 CPU 分频缩放(SEG_REFRESH_DIV = 100000/CPU_DIV), 保证每位数码管仍是 125 Hz(不闪);数码管改显示 MMIO 的 led_data; LED[2:0] 改为锁存式中断指示(请求上升沿点亮、对应 ISR 执行 uret 后熄灭) - 复位同步器/led_we 指示等注释同步更新;srcs/rtl/cpu21_seg7.v 分频计数 加宽到 20 位以容纳随主频变化的 REFRESH_DIV 工具与程序 - tools/adapt_intrupt_ccab.py:把课程中断测试镜像适配成上板 ROM。六处适配 (ISR 停机 ecall、显示改 MMIO+DISP_DLY、ISR2/ISR3 出栈顺序、ISR3 SB 测试 用未保存寄存器、ISR 破坏 ra 的 DISP_DLY_ISR),全部 2 字换 2 字, 不移动任何指令地址;带断言与 jal 目标自检 - programs/risc-v-intrupt-ccab.asm/.hex:课程中断综合测试程序(含六处适配说明) - create_project.tcl:新增 use_intrupt_ccab / use_saolei 开关,自动把对应 ROM 头文件按 Verilog Header 加入 sources_1 并定义宏 - tools/rars_f0c874c.jar:课程 RARS 汇编器移到 tools/ 文档(README.md) - 新增"各模块的实现方式":按流水线主体 / 外设 / 板级逐模块讲实现要点 - 新增"内置程序与宏的用法":ROM 宏表(含各自的中断入口)、GUI/Tcl/命令行 三种定义方式、中断程序的适配流程与仿真验证方法 - 新增"分频与刷新率一览":CLK_DIV / CLK_DIV_INTRUPT / SEG_REFRESH_DIV / led_we 时长 / DISP_DLY 延时常量分别在哪里改、怎么换算 - 修正引脚表(LED[2:0] 中断指示、led_we 0.34 s、CPU_DIV)与 ROM/RAM 说明 (ROM 8192 字 = 32 KB),补充中断程序上板现象与 ROM 头文件清单 验证:不带宏 / 带 RISC_V_INTRUPT_CCAB_ROM 两种配置 xvlog 编译均无错误; 三路嵌套与 IRQ3 抢占 ISR1 的仿真通过(level 逐级升降、uepc 正确恢复、 halted=0);tools/check_rtl_text.ps1 体检 OK。
This commit is contained in:
@@ -17,12 +17,19 @@ risc-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/board/*.v|*.vh 板级顶层(时钟分频 + 片内 ROM/RAM + 引脚);四份 ROM 初始化:
|
||||
│ │ cpu21_board_rom.vh / _fast.vh(跑马灯)、risc-v-intrupt-ccab.vh
|
||||
│ │ (课程中断综合测试程序)、cpu21_saolei_rom.vh(扫雷)
|
||||
│ ├─ srcs/constrs/*.xdc 引脚与分频时钟约束(+ Nexys4DDR 母版)
|
||||
│ ├─ srcs/tb/*.v 三个测试平台
|
||||
│ ├─ srcs/rom/*.hex 测试平台加载的 ROM 镜像
|
||||
│ ├─ tools/ rv_asm.py(迷你汇编器)、hex2vh.py(hex → ROM 初始化)、run_regress.ps1(一键回归)
|
||||
│ └─ programs/ 汇编源程序、期望值、课程原始资产(RARS)
|
||||
│ ├─ tools/ rv_asm.py(迷你汇编器)、hex2vh.py(hex → ROM 初始化)、
|
||||
│ │ adapt_intrupt_ccab.py(课程中断程序 → 上板 ROM)、
|
||||
│ │ run_regress.ps1(一键回归)、check_rtl_text.ps1(RTL 文本体检)、
|
||||
│ │ rars_f0c874c.jar(课程提供的 RARS 汇编器)
|
||||
│ └─ programs/ 汇编源程序与期望值:跑马灯 demo、指令集/MMIO 测试、课程原始
|
||||
│ 资产(risc-v-benchmark_ccab、真值表),以及中断综合测试程序
|
||||
│ risc-v-intrupt-ccab.asm / .hex
|
||||
└─ saolei/ 纯 C 扫雷(MMIO 显示 + 中断读鼠标),见下文
|
||||
├─ sw/ C 源码 + 链接脚本(三个固定中断入口)
|
||||
└─ tools/ build.ps1(C → ROM)、check_isa.py(指令白名单校验)
|
||||
@@ -160,7 +167,7 @@ risc-v/
|
||||
| `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/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 跑马灯与写入指示) |
|
||||
@@ -173,6 +180,89 @@ risc-v/
|
||||
在 xsim 下对拆分前后逐周期对比,波形输出与最终统计(周期、停顿、气泡、分支
|
||||
跳转、预测成败)完全一致。
|
||||
|
||||
## 各模块的实现方式
|
||||
|
||||
下面按“处理器流水线 → 外设 → 板级”的顺序说明每个模块的实现要点(对应上面
|
||||
“文件与模块划分”表)。全部是可综合的纯 RTL,没有例化 Xilinx 原语,
|
||||
因此 `xvlog`/`xelab` 可以直接仿真。
|
||||
|
||||
### 流水线主体
|
||||
|
||||
- **`cpu21_riscv_redirect_int_bpb`(顶层)**:五级寄存器 IF/ID、ID/EX、EX/MEM、
|
||||
MEM/WB 各自带 valid 位;下一个 PC 由一个多路选择器决定(复位 `RESET_PC`、
|
||||
顺序 PC+4、分支/跳转重定向目标、`URET` 返回地址、中断入口)。停顿与冲刷分别
|
||||
由 hazard unit 的 `pipeline_stall_o`、`flush_younger_o` 控制:停顿时冻结
|
||||
PC/IF-ID 并给 ID/EX 插气泡;冲刷时只把年轻指令的 valid 清零,所以已经提交的
|
||||
存储/寄存器写不会被执行两次。
|
||||
- **`cpu21_riscv_alu`**:纯组合,4 位 `alu_op` 直接选择加减/逻辑/移位/比较;
|
||||
减法用“B 取反加 1”实现,比较分有符号/无符号两条路径。乘除法不在 ALU 里
|
||||
(历史上的组合 `a*b`/`a%b` 是关键路径,已拆到多周期单元)。
|
||||
- **`cpu21_riscv_muldiv`**:32 拍移位迭代(乘法 shift-add、除法 restoring),
|
||||
加装载与定型共占用 EX **34 拍**;期间 `ex_md_stall` 有效 → 保持 ID/EX、给
|
||||
EX/MEM 插气泡、冻结取指、屏蔽中断(见“时序和使用说明”第 7 条)。接口沿用
|
||||
ALU 约定:`result_o` 是低位/商、`result2_o` 是高位/余数;除零时商全 1、
|
||||
余数取被除数。
|
||||
- **`cpu21_riscv_decoder`**:ID 级组合译码,操作码取 `IR[6:2]`,`funct3`/
|
||||
`funct7`/`IR[21]` 区分 R 型细分、M 扩展、CSR 与 `ecall`/`URET`;输出一组
|
||||
控制位(`alu_op`、`alu_src`、立即数、`reg_write`、访存使能、`wb_sel`、
|
||||
分支/跳转、CSR 操作、`md_op`)。
|
||||
- **`cpu21_riscv_regfile`**:32×32 寄存器堆,`x0` 恒 0;两个组合读口 + 一个写口,
|
||||
同周期 WB 的数据通过“写优先旁路”直送到读口,省掉一拍。
|
||||
- **`cpu21_riscv_forward_unit`**:EX 级前递,把 EX/MEM 与 MEM/WB 的结果和 ID/EX
|
||||
的 `rs1/rs2` 比较,EX/MEM 优先;EX/MEM 是 load 时不参与前递(load 结果要
|
||||
到 MEM 才有效,改由 hazard unit 停顿一拍);store 的数据同样可以前递。
|
||||
- **`cpu21_riscv_store_unit`**:EX 级数据对齐,`sw` 产生四个字节通道、`sb` 按
|
||||
`addr[1:0]` 只打开一个通道,并把数据搬到对应字节位置,输出 `data_wstrb_o`。
|
||||
- **`cpu21_riscv_branch_unit`**:EX 级裁决分支方向与目标:6 条条件分支各用有
|
||||
符号/无符号比较,`jal`/`jalr` 总是重定向,并把 `PC+4` 写回 `rd`(链接地址
|
||||
在 EX 级预先算好放进 EX/MEM,避免在写回/前递路径上再加一个加法器)。
|
||||
- **`cpu21_riscv_hazard_unit`**:load-use 冒险(ID/EX 是 load 且和 ID 级要用的
|
||||
寄存器相同)插一个气泡;`flush_younger` 的优先级高于停顿:中断、`URET`、
|
||||
`ecall` 停机、分支/跳转重定向都会丢弃年轻指令,此时不再插气泡。
|
||||
- **`cpu21_riscv_irq_ctrl`**:`irq_i` 先打两拍同步,再与“上一拍”比较取出
|
||||
**上升沿**(挂起位锁存),优先级 IRQ3 > IRQ2 > IRQ1;响应条件是“有挂起 +
|
||||
`ustatus[0]`(MIE) + 嵌套深度 < 4 + 未停机 + EX 级没有重定向/停机”。
|
||||
`ustatus`(0x004) 与 `uepc`(0x041) 用组合逻辑实现 CSRRS/CSRRC/CSRRW 三种读写
|
||||
语义(旧值回写 `rd`);嵌套栈保存 EPC / `ustatus` / 上一层优先级,`URET` 时
|
||||
逐层恢复。响应时保存“下一条尚未执行的指令的 PC”,EX 是已正确预测的跳转
|
||||
分支则保存实际目标,避免 `URET` 重复执行已提交的操作。
|
||||
- **`cpu21_riscv_perf_counters`**:周期、停顿、气泡、条件分支、无条件跳转、
|
||||
预测成功/失败六个计数器;`halted_o` 期间不计周期;load-use 记 1 个气泡,
|
||||
重定向/中断清空记 2 个气泡。
|
||||
|
||||
### 外设
|
||||
|
||||
- **`cpu21_mmio`**:MEM 级译码;先用**字对齐**地址比较命中哪个寄存器(这样
|
||||
`sb` 写到 `0x1001` 也能命中 LED 寄存器),再用 `data_wstrb_o` 决定改哪几个
|
||||
字节;命中时给出 `rdata_hit_o`,顶层据此在 RAM 与 MMIO 之间选择读数据。
|
||||
内部包含 LED/数码管寄存器、鼠标寄存器组(含 16 深事件 FIFO 与 `mouse_irq_o`)、
|
||||
显示控制器、显存窗口,以及用于自检的 `MAGIC` 常量。
|
||||
- **`cpu21_vga`**:工作在 CPU 时钟下,用 `tick`(每 2 拍一个使能)产生 25 MHz
|
||||
像素节拍(**不引入第二个时钟域**);按 640×480@60 的行场计数产生同步信号,
|
||||
并按“格子”寻址显存:一个显存字对应 4 个像素,8×8 字模放大 2 倍填满 16×16
|
||||
的格子(40×30 格),帧末产生 `frame_tick_o`。
|
||||
- **`cpu21_seg7`**:8 位动态扫描。计数到 `REFRESH_DIV` 换一位(默认 50 MHz 下
|
||||
50000 → 每步 1 ms,每位数码管 125 Hz 刷新);十六进制→七段码用查表;共阳极
|
||||
所以位选/段选都取反输出,`an_en_i` 可以把某几位熄灭。
|
||||
- **`cpu21_pointer`**:把 5 向按键变成鼠标事件。用约 1 ms 的采样节拍对两级同步
|
||||
后的按键采样,只在“按下”边沿产生事件;一次采样可能同时按下多个键,用待发
|
||||
队列 + 优先级选择器逐个上报(每拍最多一个事件,不丢键)。事件单位是 1/8 格,
|
||||
软件端只用整数加减就能换算成格(不需要除法)。
|
||||
- **`cpu21_ps2_mouse`**:可选的真鼠标,只实现接收:`ps2_clk` 下降沿把 11 位帧
|
||||
移位进来(起始位 + 8 数据 + 奇校验 + 停止位),校验后按 3 字节包解析出
|
||||
dx/dy/按键,产生 `ev_valid_o` 脉冲。
|
||||
|
||||
### 板级
|
||||
|
||||
- **`cpu21_board_top`**:把这些接起来并裁到 23 个引脚。内含参数化分频器
|
||||
(`CLK_DIV` / `CLK_DIV_INTRUPT`)、复位同步器(异步置位 + 同步释放)、片内
|
||||
ROM(组合读、`nop` 填充、`ifdef` 链选程序)与 RAM(字节写使能 + 组合读、
|
||||
只响应 4 KB 以内地址,避免和 MMIO 别名)、显存双口 RAM(CPU 写口 + VGA 读口
|
||||
同处一个时钟域,用像素使能而不是第二时钟)、数码管、VGA、鼠标源选择
|
||||
(`USE_PS2_MOUSE`)、中断连线(SW0/1/2 分别与鼠标事件/关卡软件中断相或)、
|
||||
锁存式中断指示 LED(请求上升沿置位、对应级别的 ISR 执行 `uret` 后熄灭)和
|
||||
`led_we` 写入指示。
|
||||
|
||||
## 上板(Nexys4DDR + Vivado)
|
||||
|
||||
处理器顶层 `cpu21_riscv_redirect_int_bpb` 有 775 位调试端口(各级 PC/IR、计数器、
|
||||
@@ -180,20 +270,22 @@ 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。
|
||||
其余调试端口不连接。定义宏 `SAOLEI_ROM` 时还会把扫雷程序固化进 ROM;当前
|
||||
`create_project.tcl` 默认定义的是 `RISC_V_INTRUPT_CCAB_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 |
|
||||
| 端口 | 引脚 | 板上元件 | 说明 |
|
||||
| ------------- | ----------- | ------------------------- | ------------------------------------------------------------------- |
|
||||
| `clk100` | E3 | 100 MHz 晶振 | 经 `CPU_DIV` 分频后给 CPU(默认 50 MHz) |
|
||||
| `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 | `LED[2:0]` = 三路中断指示(锁存,ISR 返回后熄灭),`LED[15:3]` 熄灭 |
|
||||
| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 0.34 s @50 MHz |
|
||||
|
||||
### 时钟:为什么还保留 2 分频
|
||||
### 时钟:分频比怎么选(`CLK_DIV` / `CLK_DIV_INTRUPT`)
|
||||
|
||||
乘除法原本是 ALU 里的组合运算(`a*b`、`a%b`),实测关键路径约 94 ns(325 级逻辑、
|
||||
286 个 CARRY4),在 100 MHz 下必然不收敛,所以之前只能把时钟 16 分频到 6.25 MHz。
|
||||
@@ -221,19 +313,135 @@ 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` 要同时改。
|
||||
改分频比时:**变慢**(分频比变大)不用动 XDC;**变快**(分频比小于 2)就要把 XDC 的
|
||||
`-divide_by` 一起改小,否则约束会比实际时钟松、可能掩盖违例。
|
||||
|
||||
### 中断测试程序为什么还要再慢一档
|
||||
|
||||
标着 `RISC_V_INTRUPT_CCAB_ROM` 的课程中断综合测试程序里,两次显示之间的延时常量
|
||||
是按 50 MHz 写的:50 MHz 下每个数值只停留约 10 ms,动态扫描的数码管上根本读不
|
||||
出来。所以顶层在跑这个程序时改用**另一个分频比参数 `CLK_DIV_INTRUPT`**:停留时间
|
||||
与分频比成正比:取 8 → 12.5 MHz(约 40 ms/值)、取 32 → 3.125 MHz(约 160 ms/值)——
|
||||
当前默认 32,在 `cpu21_board_top.v` 里改。这个分频比只在定义了
|
||||
`RISC_V_INTRUPT_CCAB_ROM` 时生效,其它程序仍用 `CLK_DIV`。
|
||||
|
||||
### 分频与刷新率一览(想改速度看这里)
|
||||
|
||||
| 想改什么 | 改哪里 | 当前值 / 换算 |
|
||||
| -------------------------- | ----------------------------------------------- | -------------------------------------------------------- |
|
||||
| CPU 主频(程序快慢) | 参数 `CLK_DIV` | 2 → 50 MHz(`100 MHz / CLK_DIV`) |
|
||||
| 中断程序的 CPU 主频 | 参数 `CLK_DIV_INTRUPT`(只在该 ROM 宏下生效) | 32 → 3.125 MHz;每个数值约 10 ms × (`CLK_DIV_INTRUPT`/2) |
|
||||
| 数码管扫描刷新率 | 顶层 `SEG_REFRESH_DIV`(自动跟随 `CPU_DIV`) | `100000 / CPU_DIV` → 每步 1 ms、每位 125 Hz |
|
||||
| LED16_B 写入指示点亮时长 | `led_we_cnt` 的装载值 `24'hFF_FFFF` | 约 0.34 s @50 MHz(`0xFFFFFF / f_cpu`) |
|
||||
| 中断程序每个数值的停留时间 | `tools/adapt_intrupt_ccab.py` 的 `DELAY_NEG_HI` | `0xFFFC0` → 约 10 ms @50 MHz |
|
||||
|
||||
- **数码管**:`SEG_REFRESH_DIV = 100000 / CPU_DIV` 保证“每步 1 ms、每位数码管
|
||||
125 Hz”与主频无关(不这样缩放的话,主频降 4 倍时全屏刷新只剩 31 Hz,能看到
|
||||
闪烁)。想更亮可以把它改小(扫描更快),但每位刷新率要远高于 50 Hz。
|
||||
`cpu21_seg7` 的分频计数是 20 位,最大约 100 万,所以 100 MHz(不分频)也放得下。
|
||||
- **慢下来的副作用**:`cpu21_pointer` 的按键采样节拍(`SAMPLE_DIV = 50000`)也会
|
||||
跟着变慢(3.125 MHz 下约 16 ms 采样一次,仍在可接受范围);`cpu21_vga` 的
|
||||
25 MHz 像素节拍是从 CPU 时钟分频出来的,主频一降 VGA 行场时序就超出规范
|
||||
(中断测试程序不用 VGA,无影响)。
|
||||
- **XDC 不用跟着改(只要不是变快)**:`cpu_clk` 固定按最坏情况 `-divide_by 2`
|
||||
(50 MHz)约束,实际时钟更慢属于“约束偏严”,不会掩盖违例;只有把分频比改到
|
||||
比 2 更小(更快)时,才需要同步改小 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`,且不依赖文件路径)。
|
||||
- ROM:`ROM_WORDS = 8192` 字(32 KB),组合读,取指地址 `instr_addr[ROM_AW+1:2]`;
|
||||
内容由 `` `include `` 一个 ROM 初始化文件给出(与 `cpu21_board_top.v` 同目录,
|
||||
文件里是 `rom[i] = 32'h…;` 语句,放在 `initial` 里,等价于 `$readmemh`,
|
||||
且不依赖仿真工作目录)。具体包含哪份由宏决定(见下文“内置程序与宏的用法”)。
|
||||
超出镜像长度的地址填 `nop`(`32'h0000_0013`),所以跳飞时不会执行到随机指令。
|
||||
- RAM:`RAM_WORDS = 1024` 字(4 KB),字节写使能 + 组合读,只响应
|
||||
`data_addr < 4 KB`,因此 MMIO 地址(默认 `0x1000`)不会落进 RAM。
|
||||
- 两个 `initial` 块都用 NOP(`32'h0000_0013`)或 0 预填充,ROM 未使用区域是 NOP,
|
||||
这样即使程序跑飞也不会执行到随机指令而乱写寄存器。
|
||||
|
||||
### 内置程序与宏的用法
|
||||
|
||||
板级顶层用一条 `` `ifdef `` 链决定 ROM 里固化哪个程序(连带决定中断入口地址),
|
||||
`create_project.tcl` 里的两个开关就是给这条链用的:
|
||||
|
||||
| 宏(`sources_1` 的 Verilog Define) | ROM 文件 | 中断入口 `IRQ1/2/3` | 说明 |
|
||||
| ----------------------------------- | ------------------------------------ | --------------------------- | ------------------------------------------ |
|
||||
| `SAOLEI_ROM`(优先级最高) | `srcs/board/cpu21_saolei_rom.vh` | `0x100` / `0x200` / `0x300` | 纯 C 扫雷(`saolei/tools/build.ps1` 生成) |
|
||||
| `RISC_V_INTRUPT_CCAB_ROM` | `srcs/board/risc-v-intrupt-ccab.vh` | `0x0AC` / `0x1E4` / `0x308` | 课程中断综合测试程序(当前默认) |
|
||||
| `BOARD_ROM_FAST` | `srcs/board/cpu21_board_rom_fast.vh` | `0x100` / `0x200` / `0x300` | 仿真专用:跑马灯延时改小(综合不用) |
|
||||
| 都不定义 | `srcs/board/cpu21_board_rom.vh` | `0x100` / `0x200` / `0x300` | 上板跑马灯 demo(默认) |
|
||||
|
||||
同一时间只应定义一个(`ifdef` 链按上表顺序取第一个命中的)。**中断入口必须和
|
||||
ROM 里 ISR 的起点一致**,所以换程序时宏与入口是一起换的:`cpu21_board_top.v`
|
||||
里 `.IRQ1/2/3_VECTOR` 也跟着 `RISC_V_INTRUPT_CCAB_ROM` 分支走。
|
||||
|
||||
#### 怎么定义宏
|
||||
|
||||
1. **Vivado GUI**:`Project` → `Settings` → `Synthesis` → `Verilog Options` →
|
||||
`Verilog Macros` 里填 `RISC_V_INTRUPT_CCAB_ROM`(只对 `sources_1` 有效),
|
||||
确定后 `Reset Synthesis Run` 再综合。也可以直接在 Tcl Console 里写:
|
||||
|
||||
```tcl
|
||||
set_property verilog_define {RISC_V_INTRUPT_CCAB_ROM} [get_filesets sources_1]
|
||||
reset_run synth_1
|
||||
launch_runs synth_1
|
||||
wait_on_run synth_1
|
||||
```
|
||||
|
||||
换回跑马灯就是 `set_property verilog_define {} [get_filesets sources_1]`。
|
||||
|
||||
2. **一键重建**:改 `create_project.tcl` 里的 `use_intrupt_ccab` / `use_saolei`
|
||||
两个开关(同一时间只开一个),重新 `source create_project.tcl`。
|
||||
3. **命令行仿真**:`xvlog` 与 `xelab` 都要带同一个 `-d`,并用 `-i` 指向头文件目录:
|
||||
|
||||
```powershell
|
||||
xvlog -d RISC_V_INTRUPT_CCAB_ROM -i srcs/board `
|
||||
srcs/rtl/*.v srcs/board/cpu21_board_top.v 你的tb.v
|
||||
xelab -top 你的tb -snapshot s -d RISC_V_INTRUPT_CCAB_ROM
|
||||
```
|
||||
|
||||
4. **注意**:这些宏只影响“综合用”的 `sources_1`。跑 `tb_no_intr` /
|
||||
`tb_cpu21_riscv_redirect_int_bpb` 时,程序来自测试平台自己探测的
|
||||
`srcs/rom/*.hex`,与宏无关。
|
||||
|
||||
#### 中断综合测试程序的适配(`tools/adapt_intrupt_ccab.py`)
|
||||
|
||||
课程给的 `programs/risc-v-intrupt-ccab.hex` 不能直接上板,脚本把它适配成
|
||||
`srcs/board/risc-v-intrupt-ccab.vh`,且**不移动任何指令地址**(三个中断入口
|
||||
仍是 `0x0AC` / `0x1E4` / `0x308`):
|
||||
|
||||
```powershell
|
||||
python tools/adapt_intrupt_ccab.py programs/risc-v-intrupt-ccab.hex `
|
||||
srcs/board/risc-v-intrupt-ccab.vh _tmp/fast.hex _tmp/plain.hex
|
||||
```
|
||||
|
||||
六处适配(脚本内每步都有断言,输入不对会直接报错):
|
||||
|
||||
1. 三个 ISR 里的 `addi a7,zero,10` + `ecall` → 两条 `nop`(`a7 != 34` 的
|
||||
`ecall` 会让这颗 CPU 停机等 `go_i`);
|
||||
2. 11 个显示点 `lui a7,1` + `sw a0,0(a7)` → `jal` 调用追加的显示例程;
|
||||
顺带纠正早期手改留下的 `sb` 误码(只写低字节会显示错值);
|
||||
3. 追加主程序用的 `DISP_DLY`(0x438):写 LED 寄存器(数码管显示它)+ 约 10 ms
|
||||
延时;返回地址存在 `sp+256`,位于 ISR 压栈区(最多 192 B)之上,所以中断
|
||||
随时打断延时也不会破坏它;
|
||||
4. 修正 ISR2/ISR3 出栈顺序写乱的 12 条 `lw`(否则 ISR2 的循环计数会变成
|
||||
-3823,看起来像“二级中断返回之后卡住”);
|
||||
5. ISR3 的 SB 测试改用 ISR3 自己保存的 `s4/s5/s6`(原来用未保存的 `t1/t3/s2`,
|
||||
抢占 ISR1 时会毁掉 ISR1 的 DIVU 循环计数,造成死循环);
|
||||
6. 追加 ISR 专用的 `DISP_DLY_ISR`(0x458):**链接寄存器改用 `a0`、显示值走
|
||||
`a7`**(这两个寄存器三个 ISR 都保存/恢复),于是 ISR 完全不碰 `ra`。否则
|
||||
中断若恰好落在主程序 `DISP_DLY` 末尾的 `jalr` 上,返回会跳进 ISR 代码、
|
||||
最后执行到 ISR 结尾的 `csrrci` 把 MIE 清零(“跑一阵子之后拨开关不再响应”);
|
||||
|
||||
第 3 个参数生成“延时极小”的仿真版,便于在 xsim 里跑完整流程;第 4 个参数生成
|
||||
只去掉停机 `ecall`、显示仍是 `lui`+`sw` 的“原样”版本,用于对比。
|
||||
|
||||
适配后的 ROM 在仿真里验证控制流很简单:用 `-d RISC_V_INTRUPT_CCAB_ROM` 编译
|
||||
`srcs/rtl/*.v` + `srcs/board/cpu21_board_top.v` 加一个自建的 tb(或直接用
|
||||
`srcs/tb/tb_board_top.v` 看 LED),观察 `irq_current_level_o`(0→1→2→3→…→0)、
|
||||
`uepc_o`、`halted_o` 是否依次回到 0,以及 `LED[2:0]` 是否随嵌套逐层点亮/熄灭。
|
||||
建议用第 3 个参数生成的快版 ROM(否则一次中断就是几十毫秒仿真时间)。
|
||||
|
||||
### 演示程序的生成与仿真
|
||||
|
||||
```powershell
|
||||
@@ -278,8 +486,9 @@ 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`(不能当普通源码,
|
||||
否则会被当成顶层候选);
|
||||
四份 ROM 初始化文件(`cpu21_board_rom.vh`、`cpu21_board_rom_fast.vh`、
|
||||
`risc-v-intrupt-ccab.vh`、`cpu21_saolei_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`。
|
||||
@@ -297,8 +506,25 @@ Vivado 工程 `pipelined-cpu/pipelined-cpu.xpr` 被 `.gitignore` 排除,需要
|
||||
CPU 时钟 6.25 MHz → 50 MHz(快 8 倍),并且乘除法不再位于关键路径上。
|
||||
|
||||
上板操作:用 Vivado 打开工程 → `Open Hardware Manager` → `Program Device`,选上述
|
||||
`.bit`。复位后跑马灯立即从 `0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用
|
||||
按 `go`;`go` 只对执行了 `ecall` 停机、等待继续运行的程序有意义),LED16_B 每次写入时点亮。
|
||||
`.bit`。复位后先跑 `sources_1` 里宏选中的那个程序。以跑马灯为例:复位后立即从
|
||||
`0001` 开始逐位左移(演示程序不执行 `ecall`,所以不用按 `go`;`go` 只对执行了
|
||||
`ecall` 停机、等待继续运行的程序有意义),LED16_B 每次写入时点亮。
|
||||
|
||||
#### 跑中断综合测试程序时的现象
|
||||
|
||||
(`create_project.tcl` 默认就是它,无需额外设置)
|
||||
|
||||
- 复位后主程序先跑自己的指令测试,数码管上显示程序写进 `LED_ADDR` 的 32 位十六进制值
|
||||
(低 4 位在最右边那个数码管);
|
||||
- 拨动 **SW0 / SW1 / SW2**(J15/L16/M13)就对应 1/2/3 号中断:数码管随即切成该 ISR 的
|
||||
显示内容(SB 读写测试、DIVU 测试、多层循环等),每个数值停留约 160 ms(3.125 MHz 下);
|
||||
- `LED[2:0]` 是**锁存**的中断指示:从该路中断请求出现开始亮,一直到它的 ISR 执行完
|
||||
`uret` 返回才熄灭。抢占(例如 ISR1 还没跑完就拨 SW2)不会把外层的灯灭掉,一级
|
||||
一级返回时才依次熄灭,正好能看出嵌套层次;
|
||||
- LED16_B 每次 MMIO 写入内一下;程序里若有 `ecall a7 != 34` 的停机点,会停在原地等
|
||||
按 **BTNC**(`go`)继续;
|
||||
- 想重新开始随时按 **CPU_RESETN**(`rst_n`);想让它更快/更慢就改 `CLK_DIV_INTRUPT`
|
||||
(见上文“分频与刷新率一览”)。
|
||||
|
||||
## 扫雷程序(`saolei/`)
|
||||
|
||||
@@ -342,7 +568,7 @@ powershell -ExecutionPolicy Bypass -File tools\run_regress.ps1
|
||||
| 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 |
|
||||
| 板级(快速 ROM) | 跑马灯`0001 → … → 8000` 循环,每步约 2.6 µs |
|
||||
|
||||
## 工程分享(Tcl 一键重建)
|
||||
|
||||
@@ -352,8 +578,8 @@ powershell -ExecutionPolicy Bypass -File tools\run_regress.ps1
|
||||
```text
|
||||
pipelined-cpu/
|
||||
├─ create_project.tcl 一键重建脚本(用 [info script] 定位自身目录)
|
||||
├─ srcs/rtl/*.v 处理器 RTL(13 个文件)
|
||||
├─ srcs/board/*.v|*.vh 板级顶层、片内 ROM 初始化(含 __FAST 仿真版本)
|
||||
├─ srcs/rtl/*.v 处理器与外设 RTL(17 个文件)
|
||||
├─ srcs/board/*.v|*.vh 板级顶层、四份 ROM 初始化(跑马灯/中断测试/扫雷/仿真快版)
|
||||
├─ srcs/constrs/*.xdc 引脚约束 + 板级母版
|
||||
├─ srcs/tb/*.v 三个测试平台
|
||||
├─ srcs/rom/*.hex 测试平台加载的 ROM 镜像
|
||||
|
||||
Reference in New Issue
Block a user