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(指令白名单校验)
|
||||
@@ -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 |
|
||||
| ------------- | ----------- | ------------------------- | ------------------------------------------------------------------- |
|
||||
| `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 | MMIO(`LED_ADDR`)写入值的低 16 位 |
|
||||
| `led_we` | R12 | LED16_B | MMIO 写入指示,每写一次拉长约 2.7 s |
|
||||
| `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/`)
|
||||
|
||||
@@ -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 镜像
|
||||
|
||||
@@ -71,9 +71,19 @@ add_files -norecurse -fileset sources_1 [list \
|
||||
set_property file_type {Verilog Header} \
|
||||
[get_files "$origin_dir/srcs/board/cpu21_board_rom.vh"]
|
||||
|
||||
# 默认跑哪个内置程序:1 = 扫雷(定义宏 SAOLEI_ROM),0 = 走马灯 demo。
|
||||
# 只有 cpu21_saolei_rom.vh 存在且 use_saolei = 1 时才会定义宏。
|
||||
set use_saolei 1
|
||||
# 默认跑哪个内置程序:中断测试程序优先,其次是扫雷,最后是走马灯 demo。
|
||||
# 修改下面两个开关即可切换板上程序;同一时间只应启用一个。
|
||||
set use_intrupt_ccab 1
|
||||
set use_saolei 0
|
||||
|
||||
# 中断测试程序的 ROM 初始化。
|
||||
set intrupt_ccab_rom "$origin_dir/srcs/board/risc-v-intrupt-ccab.vh"
|
||||
if {[file exists $intrupt_ccab_rom]} {
|
||||
add_files -norecurse -fileset sources_1 [list $intrupt_ccab_rom]
|
||||
set_property file_type {Verilog Header} [get_files $intrupt_ccab_rom]
|
||||
} else {
|
||||
puts "NOTE: 还没有 $intrupt_ccab_rom"
|
||||
}
|
||||
|
||||
# 扫雷程序的 ROM 初始化(由 saolei/tools/build.ps1 生成,可能还不存在)。
|
||||
# 综合/仿真时定义宏 SAOLEI_ROM 就会用它替换上面的跑马灯 ROM。
|
||||
@@ -87,7 +97,10 @@ if {[file exists $saolei_rom]} {
|
||||
|
||||
# 关键:sources_1 定义 SAOLEI_ROM,cpu21_board_top.v 里的 `ifdef 链才会用扫雷 ROM
|
||||
# (该宏只影响综合用的 sources_1,仿真 fileset 仍各自用 tb 自带的 ROM)
|
||||
if {$use_saolei && [file exists $saolei_rom]} {
|
||||
if {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
|
||||
set_property verilog_define {RISC_V_INTRUPT_CCAB_ROM} [get_filesets sources_1]
|
||||
puts "NOTE: sources_1 已定义 RISC_V_INTRUPT_CCAB_ROM —— 上板跑 risc-v-intrupt-ccab"
|
||||
} elseif {$use_saolei && [file exists $saolei_rom]} {
|
||||
set_property verilog_define {SAOLEI_ROM} [get_filesets sources_1]
|
||||
puts "NOTE: sources_1 已定义 SAOLEI_ROM —— 上板跑扫雷;想跑走马灯把本脚本里 use_saolei 改成 0"
|
||||
} else {
|
||||
@@ -129,7 +142,14 @@ foreach fs {sim_1 sim_2} {
|
||||
# ---------------------------------------------------------------- 结果汇总
|
||||
puts "工程已创建:$origin_dir/$proj_name.xpr"
|
||||
puts " sources_1 顶层 = cpu21_board_top(上板:时钟分频 + 片内 ROM/RAM + 引脚)"
|
||||
puts " sources_1 程序 = [expr {$use_saolei && [file exists $saolei_rom] ? {扫雷 (SAOLEI_ROM)} : {走马灯 demo}}]"
|
||||
if {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
|
||||
set selected_program {risc-v-intrupt-ccab (RISC_V_INTRUPT_CCAB_ROM)}
|
||||
} elseif {$use_saolei && [file exists $saolei_rom]} {
|
||||
set selected_program {扫雷 (SAOLEI_ROM)}
|
||||
} else {
|
||||
set selected_program {走马灯 demo}
|
||||
}
|
||||
puts " sources_1 程序 = $selected_program"
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
# =============================================================================
|
||||
# 注意:上板/仿真真正使用的镜像是 programs/risc-v-intrupt-ccab.hex(270 字),
|
||||
# 本文件是课程原始清单。由 tools/adapt_intrupt_ccab.py 生成 ROM 时会做六处适配:
|
||||
# 1) 三个 ISR 里的 "addi a7,zero,10 + ecall" 会让这颗 CPU 停机(a7 != 34 的
|
||||
# ecall 是"停机等 go_i"的约定),中断服务程序跑到那里就卡住、后面的中断显示
|
||||
# 循环永远不执行 -> ROM 里换成两条空操作;
|
||||
# 2) 原来的 "addi a7,zero,34 + ecall" 显示改成写 MMIO 的 LED 寄存器(0x1000),
|
||||
# 并且每个显示点都改为调用 DISP_DLY 加约 10 ms 延时 —— 否则 50 MHz 下刷新
|
||||
# 太快,数码管的动态扫描根本分辨不出来,只会看到一片亮;
|
||||
# 3) DISP_DLY 追加在程序末尾(0x438),因此所有指令地址、特别是三个中断入口
|
||||
# 0x0AC / 0x1E4 / 0x308 都保持不变;
|
||||
# 4) **hex 里 ISR2 / ISR3 的现场恢复(出栈)顺序被写乱了**(本清单是正确的,
|
||||
# ISR1 的 hex 也是正确的)。ISR3 把 s1 从 s3 的槽位弹出,于是 s1 被写成
|
||||
# s3 保存的 0xFFFFF111;ISR3 返回后,正在跑 BLT 测试的 ISR2 的循环计数 s1
|
||||
# 就变成 -3823,循环要跑约 170 万周期才退出,表现就是"二级/三级中断返回
|
||||
# 之后卡住";ISR2 的 s0/s2/a0/a7/s1/t3/t1 也全部错位。适配脚本按本清单的
|
||||
# 正确 LIFO 顺序改回 12 条 lw 的目标寄存器(字数不变、地址不动)。
|
||||
# 5) **ISR3 的 SB 测试用了它自己没有保存的寄存器**(t1 当地址、t3 当计数、
|
||||
# s2 当增量)。ISR3 的现场只保存了 s1/a7/a0/s0/s3/s4/s5/s6/uepc,所以:
|
||||
# 若 IRQ3 直接抢占正在跑 DIVU 测试的 ISR1(ISR1 用 t3 递减计数),ISR1 的
|
||||
# 计数会被改掉;ISR1 恢复后 `addi t3,t3,-1` 得到负数,`bne t3,zero` 永远
|
||||
# 成立,DIVU 循环再也回不来 —— 表现就是“拨了 SW0 再拨 SW2 之后卡住”;
|
||||
# 主程序的跑马灯步长 t1 也会被改掉。适配脚本把这 14 处寄存器域改成 ISR3
|
||||
# 自己保存的 s4/s5/s6(SB 测试结束后紧接着的 IntLoop3 会重新给它们赋值,
|
||||
# 所以这样换是安全的),字数与地址不变。
|
||||
# 6) **ISR 不能破坏被打断代码的 ra**:三个 ISR 都保存 s1/a7/a0/s0/s3/s4/s5/s6,
|
||||
# 但都没有保存 ra,而它们要调用显示例程,"jal ra,DISP_DLY" 会把 ra 写成 ISR
|
||||
# 内部的返回地址。如果中断恰好落在被打断代码依赖 ra 的位置(典型就是主程序
|
||||
# 正停在 DISP_DLY 末尾的 jalr zero,0(ra) 那一条上),中断返回后就会跳进 ISR
|
||||
# 代码里,一路执行到 ISR 结尾的 csrrci zero,0x4,1,把 MIE 永久清零:表现
|
||||
# 就是“跑一阵子之后拨开关再也不响应,只能复位”。窗口只有 1~2 拍,平时很
|
||||
# 难遇到(仿真里把延时改到极小才会碰上)。适配脚本给 ISR 另加一份显示例程
|
||||
# DISP_DLY_ISR(0x458),链接寄存器改用 a0、显示值走 a7(这两个寄存器三个
|
||||
# ISR 都保存/恢复),于是 ISR 完全不碰 ra;ISR 的显示点由 "jal ra,DISP_DLY +
|
||||
# nop" 换成 "add a7,a0,zero + jal a0,DISP_DLY_ISR",仍是 2 字换 2 字,
|
||||
# 字数与地址不变。
|
||||
# =============================================================================
|
||||
|
||||
.text
|
||||
addi sp, zero, 2000 #内存堆栈初始化
|
||||
|
||||
#############################################################
|
||||
#走马灯测?,测试addi,andi,slli,srli,srai,or,ori,nor,ecall LED按走马灯方式来回显示数据
|
||||
#############################################################
|
||||
|
||||
.text
|
||||
START:
|
||||
|
||||
addi s0,zero,1
|
||||
slli s3, s0, 31 # s3=0x80000000
|
||||
srai s3, s3, 31 # s3=0xFFFFFFFF
|
||||
add s0,zero,zero # s0=0
|
||||
addi s2,zero,12
|
||||
|
||||
addi s6,zero,8 #走马灯计数
|
||||
zmd_loop:
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
andi s0, s0, 15
|
||||
|
||||
#######################################
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
left:
|
||||
|
||||
slli s3, s3, 4 #走马灯左移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,left
|
||||
#######################################
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
addi t6,zero,15
|
||||
and s0, s0, t6
|
||||
slli s0, s0, 28
|
||||
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
|
||||
zmd_right:
|
||||
|
||||
srli s3, s3, 4 #走马灯右移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,zmd_right
|
||||
srli s0, s0, 28
|
||||
#######################################
|
||||
|
||||
sub s6,s6,t1
|
||||
beq s6,zero, exit
|
||||
j zmd_loop
|
||||
|
||||
exit:
|
||||
|
||||
add t0,zero,zero
|
||||
xori t0,t0,-1 #test nor ori
|
||||
slli t0,t0,16
|
||||
ori t0,t0,-1
|
||||
|
||||
add a0,zero,t0 # display t0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
j START # loop forever
|
||||
|
||||
|
||||
#InteruptProgram1:
|
||||
#############################################################################################
|
||||
# exceptoin 1
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram1:
|
||||
sw t0, 0(sp) #保护t0现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# DIVU测试
|
||||
addi t0,zero,2 # /2
|
||||
addi s1,zero, 0x11
|
||||
slli s1,s1,8
|
||||
addi s1,s1,0x11
|
||||
slli s1,s1,16
|
||||
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t3,zero,28 #ѭ������
|
||||
|
||||
divu_branch:
|
||||
divu s1,s1,t0 #����ָ��
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,divu_branch #ѭ��
|
||||
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,1 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop1:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift1:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift1
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop1
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t0, 0(sp) #恢复t0现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram2:
|
||||
#############################################################################################
|
||||
# exceptoin 2
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram2:
|
||||
sw t1, 0(sp) #保护t1现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw s2, 0(sp) #保护s2现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# BLT测试
|
||||
addi s1,zero,-15 #初始值
|
||||
blt_branch:
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi s1,s1,1
|
||||
blt s1,zero,blt_branch #当前指令
|
||||
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
################# 中断服务
|
||||
|
||||
addi s6,zero,2 #中断?1,2,3 不同中断号显示值不一样
|
||||
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop2:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift2:
|
||||
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift2
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop2
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp) #恢复s0现场
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s2, 0(sp) #恢复s2现场
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t1, 0(sp) #恢复t1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram3:
|
||||
#############################################################################################
|
||||
# exceptoin 3
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram3:
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# SB测试
|
||||
# 注:ROM 镜像里下面这段的 t1/t3/s2 已改成 ISR3 自己保存的 s4/s5/s6
|
||||
# (t1 当地址 -> s4,t3 当计数 -> s5,s2 当增量 -> s6),避免抢占 ISR1
|
||||
# 时破坏 ISR1 的 DIVU 循环计数,详见文件头部第 5 项。
|
||||
addi t1,zero,0 #init_addr
|
||||
addi t3,zero,32 #counter
|
||||
|
||||
#sb写入 01,02,03,04
|
||||
addi s1,zero, 0x00 #
|
||||
addi s2,zero, 0x01 #
|
||||
|
||||
sb_store:
|
||||
sb s1,(t1)
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
add s1,s1,s2 #data +1
|
||||
addi t1,t1,1 # addr ++
|
||||
addi t3,t3,-1 #counter
|
||||
bne t3,zero,sb_store
|
||||
|
||||
addi t3,zero,8
|
||||
addi t1,zero,0 # addr
|
||||
sb_branch:
|
||||
lw s1,(t1) #读出数据
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t1,t1,4
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,sb_branch
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,3 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop3:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift3:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift3
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop3
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DISP_DLY —— 显示 + 延时(对应 ROM 末尾 0x438 起的 8 条指令;11 个显示点都用
|
||||
# "jal ra, DISP_DLY" 调用它)
|
||||
#
|
||||
# sw ra, 256(sp) # 保存返回地址(故意存在 sp+256,见下面的说明)
|
||||
# lui a7, 1 # a7 = 0x1000 = LED_ADDR
|
||||
# sw a0, 0(a7) # 把 a0 写进 LED 寄存器(数码管显示它的 8 位十六进制)
|
||||
# lui t2, 0xFFFC0 # 计数初值为负(-262144),向上数到 0 ≈ 10 ms @50 MHz
|
||||
# 1:addi t2, t2, 1
|
||||
# blt t2, zero, 1b
|
||||
# lw ra, 256(sp)
|
||||
# jalr zero, ra, 0
|
||||
#
|
||||
# 为什么返回地址存在 sp+256,而不是按常规压在 sp-8 的栈帧里:
|
||||
# 本程序的 ISR 用的是"先把 sp 加上去、再往里存"的向上增长栈,一层最多 48 字节,
|
||||
# 最多嵌套 4 层(IRQ_STACK_DEPTH=4)共 192 字节 —— 也就是说 [sp, sp+192) 是中断
|
||||
# 随时可能写到的区域。而显示调用既可能被中断打断,也可能从 ISR 内部发出,所以:
|
||||
# * 若压在 sp-8(常规做法),从 ISR 里调用时会正好盖掉 ISR 存好的 s6 / uepc;
|
||||
# uepc 一坏,uret 就返回到 ISR 内部地址,最后执行到 ISR 尾部的 csrrci 把 MIE
|
||||
# 永久清零,之后再也不响应中断(实测就是这个现象);
|
||||
# * 放在 sp+256 越过了中断可能压栈的 192 字节区域,嵌套中断打断延时也不会破坏
|
||||
# 它;每次调用各用自己的 sp+256,嵌套调用互不影响。
|
||||
#
|
||||
# 延时计数用"负数向上数到 0"而不是"递减到 0":万一 t2 被嵌套调用改掉,循环只会
|
||||
# 在 t2 >= 0 时立即退出,不会像递减写法那样回绕成 40 亿次而死等。
|
||||
# -----------------------------------------------------------------------------
|
||||
DISP_DLY:
|
||||
sw ra, 256(sp)
|
||||
lui a7, 1
|
||||
sw a0, 0(a7)
|
||||
lui t2, 0xFFFC0
|
||||
DISP_DLY_WAIT:
|
||||
addi t2, t2, 1
|
||||
blt t2, zero, DISP_DLY_WAIT
|
||||
lw ra, 256(sp)
|
||||
jalr zero, ra, 0
|
||||
+22
-22
@@ -12,8 +12,8 @@
|
||||
00499993
|
||||
0089e9b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
406282b3
|
||||
fe0294e3
|
||||
00140413
|
||||
@@ -25,8 +25,8 @@ fe0294e3
|
||||
0049d993
|
||||
0089e9b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
406282b3
|
||||
fe0294e3
|
||||
01c45413
|
||||
@@ -38,8 +38,8 @@ fff2c293
|
||||
01029293
|
||||
fff2e293
|
||||
00500533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
f5dff06f
|
||||
00512023
|
||||
00410113
|
||||
@@ -71,13 +71,13 @@ f5dff06f
|
||||
01148493
|
||||
01049493
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
01c00e13
|
||||
0254d4b3
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fffe0e13
|
||||
fe0e16e3
|
||||
00a00893
|
||||
@@ -89,8 +89,8 @@ fe0e16e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -147,8 +147,8 @@ ffc10113
|
||||
0040e073
|
||||
ff100493
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
00148493
|
||||
fe04c8e3
|
||||
00a00893
|
||||
@@ -160,8 +160,8 @@ fe04c8e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -218,8 +218,8 @@ ffc10113
|
||||
00100913
|
||||
00930023
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
012484b3
|
||||
00130313
|
||||
fffe0e13
|
||||
@@ -228,8 +228,8 @@ fe0e12e3
|
||||
00000313
|
||||
00032483
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
00430313
|
||||
fffe0e13
|
||||
fe0e14e3
|
||||
@@ -242,8 +242,8 @@ fe0e14e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -1,421 +0,0 @@
|
||||
.text
|
||||
addi sp, zero, 2000 #内存堆栈初始化
|
||||
|
||||
#############################################################
|
||||
#走马灯测?,测试addi,andi,slli,srli,srai,or,ori,nor,ecall LED按走马灯方式来回显示数据
|
||||
#############################################################
|
||||
|
||||
.text
|
||||
START:
|
||||
|
||||
addi s0,zero,1
|
||||
slli s3, s0, 31 # s3=0x80000000
|
||||
srai s3, s3, 31 # s3=0xFFFFFFFF
|
||||
add s0,zero,zero # s0=0
|
||||
addi s2,zero,12
|
||||
|
||||
addi s6,zero,8 #走马灯计数
|
||||
zmd_loop:
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
andi s0, s0, 15
|
||||
|
||||
#######################################
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
left:
|
||||
|
||||
slli s3, s3, 4 #走马灯左移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,left
|
||||
#######################################
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
addi t6,zero,15
|
||||
and s0, s0, t6
|
||||
slli s0, s0, 28
|
||||
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
|
||||
zmd_right:
|
||||
|
||||
srli s3, s3, 4 #走马灯右移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,zmd_right
|
||||
srli s0, s0, 28
|
||||
#######################################
|
||||
|
||||
sub s6,s6,t1
|
||||
beq s6,zero, exit
|
||||
j zmd_loop
|
||||
|
||||
exit:
|
||||
|
||||
add t0,zero,zero
|
||||
xori t0,t0,-1 #test nor ori
|
||||
slli t0,t0,16
|
||||
ori t0,t0,-1
|
||||
|
||||
add a0,zero,t0 # display t0
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
j START # loop forever
|
||||
|
||||
|
||||
#InteruptProgram1:
|
||||
#############################################################################################
|
||||
# exceptoin 1
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram1:
|
||||
sw t0, 0(sp) #保护t0现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# DIVU测试
|
||||
addi t0,zero,2 # /2
|
||||
addi s1,zero, 0x11
|
||||
slli s1,s1,8
|
||||
addi s1,s1,0x11
|
||||
slli s1,s1,16
|
||||
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall
|
||||
addi t3,zero,28 #ѭ������
|
||||
|
||||
divu_branch:
|
||||
divu s1,s1,t0 #����ָ��
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall #�����ǰֵ
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,divu_branch #ѭ��
|
||||
|
||||
|
||||
addi a7,zero,10
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,1 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop1:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift1:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift1
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop1
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t0, 0(sp) #恢复t0现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram2:
|
||||
#############################################################################################
|
||||
# exceptoin 2
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram2:
|
||||
sw t1, 0(sp) #保护t1现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw s2, 0(sp) #保护s2现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# BLT测试
|
||||
addi s1,zero,-15 #初始值
|
||||
blt_branch:
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall #输出当前值
|
||||
addi s1,s1,1
|
||||
blt s1,zero,blt_branch #当前指令
|
||||
|
||||
|
||||
addi a7,zero,10
|
||||
|
||||
################# 中断服务
|
||||
|
||||
addi s6,zero,2 #中断?1,2,3 不同中断号显示值不一样
|
||||
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop2:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift2:
|
||||
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift2
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop2
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp) #恢复s0现场
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s2, 0(sp) #恢复s2现场
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t1, 0(sp) #恢复t1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram3:
|
||||
#############################################################################################
|
||||
# exceptoin 3
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram3:
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# SB测试
|
||||
addi t1,zero,0 #init_addr
|
||||
addi t3,zero,32 #counter
|
||||
|
||||
#sb写入 01,02,03,04
|
||||
addi s1,zero, 0x00 #
|
||||
addi s2,zero, 0x01 #
|
||||
|
||||
sb_store:
|
||||
sb s1,(t1)
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34 # system call for print
|
||||
ecall # print
|
||||
|
||||
add s1,s1,s2 #data +1
|
||||
addi t1,t1,1 # addr ++
|
||||
addi t3,t3,-1 #counter
|
||||
bne t3,zero,sb_store
|
||||
|
||||
addi t3,zero,8
|
||||
addi t1,zero,0 # addr
|
||||
sb_branch:
|
||||
lw s1,(t1) #读出数据
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34 # system call for print
|
||||
ecall # print
|
||||
addi t1,t1,4
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,sb_branch
|
||||
|
||||
addi a7,zero,10 # system call for exit
|
||||
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,3 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop3:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift3:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift3
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop3
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
|
||||
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
|
||||
//
|
||||
// clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU)
|
||||
// clk100 (E3) -> CPU 时钟(100 MHz,经 CPU_DIV 分频后给 CPU;默认 50 MHz,
|
||||
// 跑中断综合测试程序时用更慢的 CLK_DIV_INTRUPT,
|
||||
// 让数码管上每个数值停留更久,便于观察)
|
||||
// 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[15:0] -> 板载 LED[15:0]:LED[2:0] = 三路中断指示,从该路中断
|
||||
// 请求出现开始点亮,一直到它的 ISR 执行 uret 返回后才熄灭;
|
||||
// LED[15:3] 熄灭
|
||||
// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察
|
||||
// an/seg/dp -> 板载 8 位七段数码管:显示程序写入 LED_ADDR 的 32 位
|
||||
// 十六进制数据(每 4 位对应一个数码管)
|
||||
//
|
||||
// 片内存储器:
|
||||
// ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出
|
||||
@@ -38,6 +44,14 @@ module cpu21_board_top #(
|
||||
// CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。
|
||||
// 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。
|
||||
parameter integer CLK_DIV = 2,
|
||||
// 中断综合测试程序(RISC_V_INTRUPT_CCAB_ROM)专用的 CPU 分频比。
|
||||
// 该程序每次显示之间的延时常量是按 50 MHz 调的:50 MHz 下每个数值只停留
|
||||
// 约 10 ms,数码管上根本读不出来。停留时间与分频比成正比,例如:
|
||||
// 8 → 12.5 MHz(每个数值约 40 ms)、16 → 6.25 MHz(约 80 ms)、
|
||||
// 32 → 3.125 MHz(约 160 ms)、64 → 1.5625 MHz(约 320 ms)。
|
||||
// 取大于 2 的值只是把实际时钟变慢,XDC 的 cpu_clk 仍按最坏情况 50 MHz 约束,
|
||||
// 所以不会掩盖时序违例(约束偏严)。
|
||||
parameter integer CLK_DIV_INTRUPT = 32,
|
||||
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
|
||||
parameter integer USE_PS2_MOUSE = 0
|
||||
) (
|
||||
@@ -69,16 +83,41 @@ module cpu21_board_top #(
|
||||
localparam ROM_AW = $clog2(ROM_WORDS);
|
||||
localparam RAM_AW = $clog2(RAM_WORDS);
|
||||
|
||||
// CPU 时钟实际用的分频比:中断综合测试程序用更慢的 CLK_DIV_INTRUPT,
|
||||
// 其余程序用 CLK_DIV。两者必须都是偶数(保证 50% 占空比)。
|
||||
`ifdef RISC_V_INTRUPT_CCAB_ROM
|
||||
localparam integer CPU_DIV = CLK_DIV_INTRUPT;
|
||||
`else
|
||||
localparam integer CPU_DIV = CLK_DIV;
|
||||
`endif
|
||||
|
||||
// 数码管扫描分频:目标是“每位数码管约 125 Hz 刷新”(即每步 1 ms),与 CPU
|
||||
// 时钟频率无关。每步时间 = REFRESH_DIV / f_cpu,而 f_cpu = 100 MHz / CPU_DIV,
|
||||
// 所以 REFRESH_DIV = 1 ms × f_cpu = 100000 / CPU_DIV:
|
||||
// CPU_DIV = 2( 50 MHz)-> 50000(每步 1 ms,每位数码管 125 Hz)
|
||||
// CPU_DIV = 8(12.5 MHz)-> 12500(同上)
|
||||
// 例:CPU_DIV = 32(3.125 MHz)-> 3125(每步仍是 1 ms)。
|
||||
// 不这样缩放的话,CPU 降频时扫描会按同样比例变慢,肉眼能看到闪烁。
|
||||
localparam integer SEG_REFRESH_DIV = 100000 / CPU_DIV;
|
||||
|
||||
wire arst = ~rst_n; // 异步复位(高有效)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。
|
||||
// 时钟:100 MHz / CPU_DIV 给 CPU。默认 50 MHz(CLK_DIV=2);跑中断综合测试
|
||||
// 程序时用更慢的 CLK_DIV_INTRUPT(见该参数说明),让数码管上的数值停留更久。
|
||||
//
|
||||
// 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的
|
||||
// 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,
|
||||
// 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。
|
||||
// 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明,
|
||||
// 改分频比时两处要一起改。
|
||||
// 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明。
|
||||
// XDC 按最坏情况固定成 -divide_by 2(50 MHz),所以这里把实际时钟变慢属于
|
||||
// “约束偏严”,不会掩盖时序违例。
|
||||
//
|
||||
// 中断综合测试程序会用更慢的 CPU_DIV(见 CLK_DIV_INTRUPT):程序里的延时常量
|
||||
// 按 50 MHz 调好,50 MHz 下每个数值只停留约 10 ms,读数根本看不清;
|
||||
// 停留时间与分频比成正比,具体见 CLK_DIV_INTRUPT 的说明。
|
||||
// 注意:VGA 的 25 MHz 像素节拍也是从这个 CPU 时钟分频出来的,CPU 降频时
|
||||
// VGA 行场时序会跟着变慢(超出 VGA 规范)。中断测试程序不用 VGA,无影响。
|
||||
// ---------------------------------------------------------------------------
|
||||
reg [15:0] clk_cnt;
|
||||
reg clk_div;
|
||||
@@ -87,12 +126,10 @@ module cpu21_board_top #(
|
||||
if (arst) begin
|
||||
clk_cnt <= 16'd0;
|
||||
clk_div <= 1'b0;
|
||||
end
|
||||
else if (clk_cnt == (CLK_DIV / 2 - 1)) begin
|
||||
end else if (clk_cnt == (CPU_DIV / 2 - 1)) begin
|
||||
clk_cnt <= 16'd0;
|
||||
clk_div <= ~clk_div;
|
||||
end
|
||||
else begin
|
||||
end else begin
|
||||
clk_cnt <= clk_cnt + 16'd1;
|
||||
end
|
||||
end
|
||||
@@ -105,10 +142,8 @@ module cpu21_board_top #(
|
||||
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};
|
||||
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];
|
||||
@@ -119,11 +154,11 @@ module cpu21_board_top #(
|
||||
wire [31:0] data_wdata;
|
||||
wire [ 3:0] data_wstrb;
|
||||
wire data_we;
|
||||
wire [31:0] led_data;
|
||||
wire [31:0] led_data; // LED_ADDR 写入值,同时作为七段数码管显示数据
|
||||
wire led_valid;
|
||||
// 扫雷扩展外设的连线(先声明,例化在下面)
|
||||
wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动)
|
||||
wire [31:0] seg_data; // 数码管:8 位十六进制
|
||||
wire [31:0] seg_data; // 保留的独立数码管 MMIO 数据
|
||||
wire seg_valid;
|
||||
wire fb_we; // 显存写端口
|
||||
wire [ 9:0] fb_waddr;
|
||||
@@ -131,6 +166,7 @@ module cpu21_board_top #(
|
||||
wire [ 3:0] fb_wstrb;
|
||||
wire [ 9:0] fb_raddr; // 显存读端口(VGA)
|
||||
wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口)
|
||||
wire [1:0] irq_level; // 当前正在服务的中断级别(0 = 不在中断里,1/2/3 = 在 ISR 里)
|
||||
wire frame_tick;
|
||||
wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw};
|
||||
// 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。
|
||||
@@ -143,12 +179,14 @@ module cpu21_board_top #(
|
||||
integer i;
|
||||
|
||||
initial begin
|
||||
for (i = 0; i < ROM_WORDS; i = i + 1)
|
||||
rom[i] = 32'h0000_0013; // NOP 填充
|
||||
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 RISC_V_INTRUPT_CCAB_ROM
|
||||
// 中断综合测试程序:三个中断入口由镜像中的实际标签位置决定。
|
||||
`include "risc-v-intrupt-ccab.vh"
|
||||
`elsif BOARD_ROM_FAST
|
||||
// 仿真专用:把演示程序的延时改小的版本(综合流程不会用到)
|
||||
`include "cpu21_board_rom_fast.vh"
|
||||
@@ -165,8 +203,7 @@ 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;
|
||||
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];
|
||||
@@ -178,14 +215,10 @@ 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];
|
||||
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
|
||||
|
||||
@@ -195,14 +228,18 @@ end
|
||||
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) = 备用
|
||||
// 中断入口必须与所选 ROM 镜像中的 ISR 起点一致。
|
||||
`ifdef RISC_V_INTRUPT_CCAB_ROM
|
||||
// risc-v-intrupt-ccab.hex:字 43/121/194
|
||||
.IRQ1_VECTOR (32'h0000_00AC),
|
||||
.IRQ2_VECTOR (32'h0000_01E4),
|
||||
.IRQ3_VECTOR (32'h0000_0308)
|
||||
`else
|
||||
// 扫雷程序:linker.ld 中固定的 0x100/0x200/0x300
|
||||
.IRQ1_VECTOR (32'h0000_0100),
|
||||
.IRQ2_VECTOR (32'h0000_0200),
|
||||
.IRQ3_VECTOR (32'h0000_0300)
|
||||
`endif
|
||||
) u_cpu (
|
||||
.clk (clk_cpu),
|
||||
.reset (reset),
|
||||
@@ -237,23 +274,56 @@ cpu21_riscv_redirect_int_bpb #(
|
||||
.sw_i (sw_vec),
|
||||
.frame_tick_i (frame_tick),
|
||||
.mouse_irq_o (mouse_irq),
|
||||
.level_irq_o (level_irq)
|
||||
.level_irq_o (level_irq),
|
||||
// 中断服务级别,供板上的中断指示 LED 使用(见下面 LED 那一段)
|
||||
.irq_current_level_o(irq_level)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察
|
||||
// 板载 LED:三路中断指示;十六进制数据改由七段数码管显示。
|
||||
//
|
||||
// 第 k 路中断从"请求出现"开始点亮,一直亮到它的 ISR 执行完 uret(中断返回)
|
||||
// 之后才熄灭:
|
||||
// 置位:irq_vec[k] 的上升沿。中断控制器内部也是按上升沿响应中断的
|
||||
// (cpu21_riscv_irq_ctrl 里的 irq_event),两者行为一致;开关一直
|
||||
// 接通不会重复点亮,只有新的上升沿才会重新置位。
|
||||
// 清零:服务级别 irq_level 从 k+1 **降下来**,说明第 k+1 级的中断已经返回。
|
||||
// 注意必须判断“下降”:级别从 1 升到 2 是深层中断抢占,此时第 1 级
|
||||
// 的中断还在服务中,LED 不能熄。中断可以嵌套,返回时按级别逐层降
|
||||
// 下来,因此每一路都在自己的 ISR 结束时熄灭。
|
||||
// ---------------------------------------------------------------------------
|
||||
assign led = led_data[15:0];
|
||||
reg [2:0] irq_req_q; // 中断请求打一拍,用于取上升沿
|
||||
reg [1:0] irq_level_q; // 服务级别打一拍,用于检测"返回"
|
||||
reg [2:0] irq_led_q; // 三路中断指示
|
||||
|
||||
wire isr_ret0 = (irq_level_q == 2'd1) && (irq_level < irq_level_q);
|
||||
wire isr_ret1 = (irq_level_q == 2'd2) && (irq_level < irq_level_q);
|
||||
wire isr_ret2 = (irq_level_q == 2'd3) && (irq_level < irq_level_q);
|
||||
|
||||
always @(posedge clk_cpu or posedge arst) begin
|
||||
if (arst) begin
|
||||
irq_req_q <= 3'b000;
|
||||
irq_level_q <= 2'd0;
|
||||
irq_led_q <= 3'b000;
|
||||
end else begin
|
||||
irq_req_q <= irq_vec;
|
||||
irq_level_q <= irq_level;
|
||||
// 请求上升沿 -> 点亮;对应级别的 ISR 返回 -> 熄灭
|
||||
irq_led_q[0] <= (irq_vec[0] & ~irq_req_q[0]) | (irq_led_q[0] & ~isr_ret0);
|
||||
irq_led_q[1] <= (irq_vec[1] & ~irq_req_q[1]) | (irq_led_q[1] & ~isr_ret1);
|
||||
irq_led_q[2] <= (irq_vec[2] & ~irq_req_q[2]) | (irq_led_q[2] & ~isr_ret2);
|
||||
end
|
||||
end
|
||||
|
||||
assign led = {{13{1'b0}}, irq_led_q};
|
||||
|
||||
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;
|
||||
if (arst) led_we_cnt <= 24'd0;
|
||||
else if (led_valid) led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz
|
||||
// (12.5 MHz 下约 1.36 s)
|
||||
else if (led_we_cnt != 24'd0) led_we_cnt <= led_we_cnt - 24'd1;
|
||||
end
|
||||
|
||||
assign led_we = (led_we_cnt != 24'd0);
|
||||
@@ -273,20 +343,15 @@ 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;
|
||||
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];
|
||||
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
|
||||
|
||||
@@ -295,10 +360,8 @@ 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;
|
||||
if (arst) px_q <= 1'b0;
|
||||
else px_q <= ~px_q;
|
||||
end
|
||||
wire px_tick = ~px_q;
|
||||
|
||||
@@ -318,11 +381,14 @@ cpu21_vga u_vga (
|
||||
.frame_tick_o(frame_tick)
|
||||
);
|
||||
|
||||
// ---- 七段数码管(显示项目程序写的 8 位十六进制数)----
|
||||
cpu21_seg7 u_seg7 (
|
||||
// ---- 七段数码管(显示程序写入 LED_ADDR 的 8 位十六进制数)----
|
||||
// 扫描分频按 CPU 时钟频率缩放,保持每位数码管约 125 Hz 刷新(不闪烁)。
|
||||
cpu21_seg7 #(
|
||||
.REFRESH_DIV(SEG_REFRESH_DIV)
|
||||
) u_seg7 (
|
||||
.clk (clk_cpu),
|
||||
.reset (reset),
|
||||
.seg_data_i(seg_data),
|
||||
.seg_data_i(led_data),
|
||||
.an_en_i (8'hFF),
|
||||
.an_o (an),
|
||||
.seg_o (seg),
|
||||
@@ -349,10 +415,8 @@ generate
|
||||
.ev_dy_o (ev_dy),
|
||||
.ev_btn_o (ev_btn),
|
||||
.rx_count_o ()
|
||||
)
|
||||
;
|
||||
end
|
||||
else begin : g_btn
|
||||
);
|
||||
end else begin : g_btn
|
||||
cpu21_pointer #(
|
||||
.SAMPLE_DIV(50000)
|
||||
) u_ptr (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,11 @@ create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { c
|
||||
create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
|
||||
-divide_by 2 [get_pins { clk_div_reg/Q }]
|
||||
|
||||
## 这里固定按最坏情况 50 MHz(-divide_by 2)约束。跑中断综合测试程序时 RTL 里的
|
||||
## CPU_DIV 会变成 8(12.5 MHz,见 cpu21_board_top.v 的 CLK_DIV_INTRUPT),实际
|
||||
## 时钟比这里的约束更慢,因此仍然满足——约束偏严,不会掩盖时序违例。
|
||||
## (真要改分频比时,两处都要改;把这里的 -divide_by 调大反而会放松约束。)
|
||||
|
||||
## 复位按键(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 }]
|
||||
@@ -22,7 +27,8 @@ set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { irq_sw
|
||||
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 位
|
||||
## 16 位 LED:LED[2:0] = 三路中断指示(请求出现即亮,ISR 执行 uret 返回后灭),
|
||||
## LED[15:3] 熄灭
|
||||
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] }]
|
||||
@@ -40,7 +46,7 @@ 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)
|
||||
## LED16_B:MMIO 写入指示(约 0.34 s @50 MHz;中断程序的 12.5 MHz 下约 1.36 s)
|
||||
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }]
|
||||
|
||||
## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G,
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
// an_en_i 每一位对应一个数码管的使能(1 = 显示,0 = 熄灭)。
|
||||
// 输出:an_o[7:0] 位选(低有效)、seg_o[6:0] 段选 a..g(低有效)、dp_o 小数点。
|
||||
//
|
||||
// 扫描频率:CLK_DIV 分频后约 1 kHz 切换一位(8 位 => 每位数码管 125 Hz 刷新)。
|
||||
// 扫描频率:REFRESH_DIV 分频后约 1 kHz 切换一位(8 位 => 每位数码管 125 Hz
|
||||
// 刷新)。REFRESH_DIV 由顶层按 CPU 时钟频率给出(默认 50000 @50 MHz),
|
||||
// 这样 CPU 降频时扫描刷新率不会跟着变慢。
|
||||
// 板载数码管是共阳极:位选拉低选中,段选拉低点亮。
|
||||
// -----------------------------------------------------------------------------
|
||||
module cpu21_seg7 #(
|
||||
parameter integer REFRESH_DIV = 50000 // 50 MHz / 50000 = 1 kHz
|
||||
parameter integer REFRESH_DIV = 50000 // 50 MHz / 50000 = 1 kHz 切换一位
|
||||
) (
|
||||
input wire clk,
|
||||
input wire reset,
|
||||
@@ -26,20 +28,21 @@ module cpu21_seg7 #(
|
||||
// ---------------------------------------------------------------------------
|
||||
// 刷新计数与当前位
|
||||
// ---------------------------------------------------------------------------
|
||||
reg [15:0] div_q;
|
||||
// 计数位宽要能装下 REFRESH_DIV-1:顶层按 CPU 频率等比给出该值
|
||||
//(50 MHz 时 50000,12.5 MHz 时 12500;若不分频直接跑 100 MHz 则 100000),
|
||||
// 因此这里用 20 位(最大约 100 万)留足余量。
|
||||
reg [19: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;
|
||||
div_q <= 20'd0;
|
||||
digit_q <= 3'd0;
|
||||
end
|
||||
else if (tick) begin
|
||||
div_q <= 16'd0;
|
||||
end else if (tick) begin
|
||||
div_q <= 20'd0;
|
||||
digit_q <= digit_q + 3'd1;
|
||||
end
|
||||
else begin
|
||||
end else begin
|
||||
div_q <= div_q + 16'd1;
|
||||
end
|
||||
end
|
||||
@@ -48,22 +51,14 @@ module cpu21_seg7 #(
|
||||
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];
|
||||
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
|
||||
|
||||
@@ -73,38 +68,22 @@ module cpu21_seg7 #(
|
||||
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
|
||||
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
|
||||
|
||||
@@ -116,8 +95,7 @@ module cpu21_seg7 #(
|
||||
an_o <= 8'hFF;
|
||||
seg_o <= 7'h7F;
|
||||
dp_o <= 1'b1;
|
||||
end
|
||||
else if (tick) begin
|
||||
end else if (tick) begin
|
||||
an_o <= ~(8'b0000_0001 << digit_q) | ~an_en_i;
|
||||
seg_o <= ~seg_on;
|
||||
dp_o <= 1'b1; // 不用小数点
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env python3
|
||||
"""把中断测试程序适配到这块 CPU 上板运行(只改 ROM 内容,不改 CPU 语义)。
|
||||
|
||||
背景
|
||||
----
|
||||
programs/risc-v-intrupt-ccab.hex 是课程的中断/异常测试程序(270 字)。
|
||||
直接放进板上 ROM 会有两个问题:
|
||||
|
||||
1) ISR 里的 ecall 会停机
|
||||
三个中断服务程序里各有一条 "addi a7,zero,10" + "ecall"。这颗 CPU 的约定是
|
||||
a7==34 的 ecall 是历史"显示"调用(无操作),其余 a7 值一律让流水线停机并等待
|
||||
go_i。于是 ISR 跑到这里就卡住:后面的中断服务显示循环永远不执行,主程序的
|
||||
跑马灯也停在原地。
|
||||
-> 把 "addi a7,zero,10" + "ecall" 换成两条 NOP。
|
||||
|
||||
2) 显示刷新太快,数码管上看不出跑马灯
|
||||
原来的显示是 "lui a7,1" + "sw a0,0(a7)"(写 MMIO 的 LED 寄存器)。主循环里
|
||||
两次显示只隔几拍(50 MHz 下约 0.1 us),而数码管是按位动态扫描的,根本来不及
|
||||
分辨,只会看到一片亮。
|
||||
-> 把每条显示换成 "jal ra,DISP_DLY" + "nop",由追加在 ROM 末尾的 DISP_DLY
|
||||
完成"写 LED 寄存器 + 约 10 ms 延时"。
|
||||
(注意:早先把旧的 "addi a7,zero,34 + ecall" 换成 MMIO 存储时,rs1 域算错,
|
||||
把 sw 写成了 sb —— 只更新 LED 寄存器的低字节,高位一直是旧值,显示自然是
|
||||
错的。DISP_DLY 内部统一用正确的 sw 写整个字,这个脚本会顺带纠正。)
|
||||
|
||||
3) **修正 ISR2 / ISR3 的现场恢复顺序**
|
||||
原始 hex 是手工改过的,两个 ISR 的出栈顺序被写乱(ISR1 是对的):
|
||||
* ISR3:把 s1 从 s3 的槽位弹出,于是 s1 被写成 s3 保存的值 0xFFFFF111;
|
||||
ISR3 返回后,正在跑 BLT 测试的 ISR2 的循环计数 s1 就变成了 -3823,
|
||||
循环要跑 3800 多次(约 170 万周期)才退出 —— 看起来就是“二级中断返回
|
||||
之后卡住”;
|
||||
* ISR2:s0/s2/a0/a7/s1/t3/t1 全部错位,返回后调用者的这些寄存器被破坏。
|
||||
-> 按 asm 清单里正确的 LIFO 顺序,只改 12 条 `lw` 的目标寄存器(字数不变、
|
||||
地址不动),ISR3 改 5 条、ISR2 改 7 条。
|
||||
|
||||
5) **修正 ISR3 的 SB 测试用到的未保存寄存器**
|
||||
ISR3 的现场只保存了 s1/a7/a0/s0/s3/s4/s5/s6/uepc,但它的 SB 测试却用
|
||||
t1(地址)、t3(计数)、s2(增量)—— 这三个都没保存。后果:
|
||||
* 若 IRQ3 抢占正在跑 DIVU 测试的 ISR1(ISR1 用 t3 做递减计数),ISR1 的
|
||||
计数会被 ISR3 改成 0;ISR1 恢复后 `addi t3,t3,-1` 得到 -1,`bne t3,zero`
|
||||
永远成立 —— ISR1 的 DIVU 循环再也出不来,表现为“三级中断之后卡住”;
|
||||
* 主程序的跑马灯步长 t1 也会被改掉。
|
||||
-> 改用 ISR3 自己保存的 s4/s5/s6:SB 测试结束后紧接着的 IntLoop3
|
||||
(0x3B4 起)会重新给它们赋值,所以这样换是安全的;同样只改寄存器域,
|
||||
字数与地址不变。
|
||||
|
||||
6) **ISR 不能破坏被打断代码的 ra**
|
||||
三个 ISR 的现场里都保存了 s1/a7/a0/s0/s3/s4/s5/s6(外加各自的测试寄存器),
|
||||
但**都没有保存 ra**,而它们要调用显示例程,`jal ra,DISP_DLY` 会把 ra 写成
|
||||
ISR 内部的返回地址。后果:如果中断恰好落在被打断代码依赖 ra 的位置
|
||||
—— 典型就是主程序正停在 DISP_DLY 末尾的 `jalr zero,0(ra)`(第 0x454 字)
|
||||
—— 中断返回后 `jalr` 会跳到 ra 里的地址,也就是 ISR 内部的某条指令上,
|
||||
于是一路执行到 ISR 结尾的 `csrrci zero,0x4,1`,把 MIE 永久清零:
|
||||
“跑一阵子之后拨开关再也不响应,只能按复位”。窗口只有 1~2 拍,所以平时
|
||||
很难遇到(仿真里把延时改到极小才会碰上)。
|
||||
-> 给 ISR 另加一份显示例程 DISP_DLY_ISR(0x458),链接寄存器改用 **a0**,
|
||||
显示值改走 **a7**:这两个寄存器三个 ISR 都保存/恢复,所以
|
||||
* ISR 完全不碰 ra,被打断的代码停在 DISP_DLY 哪里都安全;
|
||||
* ISR 自己被内层中断打断时,内层 ISR 返回也会把 a0/a7 恢复成被打断时
|
||||
的值,所以 DISP_DLY_ISR 自己的 `jalr zero,0(a0)` 同样安全。
|
||||
主程序的 3 个显示点仍用原来的 DISP_DLY(链接在 ra,行为不变)。
|
||||
每个 ISR 显示点:`jal ra,DISP_DLY` + `nop` 换成
|
||||
`add a7,a0,zero` + `jal a0,DISP_DLY_ISR` —— 仍是 2 字换 2 字。
|
||||
|
||||
以上替换都是 **2 字换 2 字**,两个例程又追加在最后一条指令(uret)之后,所以
|
||||
**所有指令地址都保持不变**,三个中断入口仍然是 0x0AC / 0x1E4 / 0x308。
|
||||
|
||||
DISP_DLY(8 字,追加在程序末尾)
|
||||
--------------------------------
|
||||
sw ra, 256(sp) # 保存返回地址
|
||||
lui a7, 1 # a7 = 0x1000 = LED_ADDR
|
||||
sw a0, 0(a7) # 把 a0 写进 LED 寄存器(数码管显示它的 8 位十六进制)
|
||||
lui t2, DELAY # 计数初值为负,向上数到 0(约 10 ms @50 MHz)
|
||||
1: addi t2, t2, 1
|
||||
blt t2, zero, 1b
|
||||
lw ra, 256(sp)
|
||||
jalr zero, ra, 0
|
||||
|
||||
为什么返回地址存在 sp+256 而不是程序栈里:
|
||||
本程序的 ISR 是“先把 sp 加上去,再往里存”的向上增长栈,一层最多占 48 字节,
|
||||
最多嵌套 4 层(IRQ_STACK_DEPTH=4)共 192 字节,所以 [sp, sp+192) 是中断随时
|
||||
可能写到的区域。把返回地址放在 sp+256(越过了这个区域):
|
||||
* 从 ISR 里调用时,不会覆盖 ISR 存好的 s6 / uepc(早期用 sp-8 就是这么坏掉的:
|
||||
uepc 被写坏后 uret 会返回到 ISR 内部地址,最后执行到 csrrci 把 MIE 永久清零);
|
||||
* 中断(包括多层嵌套)随时打断延时也不会破坏它,因为嵌套 ISR 只会在
|
||||
[sp, sp+192) 里压栈;
|
||||
* 每次调用各用自己 sp+256 的位置,所以嵌套调用也互不影响。
|
||||
延时计数不用“递减到 0”而用“负数向上数到 0”:万一被嵌套调用改掉了 t2,循环也
|
||||
只会在 t2 >= 0 时立即退出,不会像递减写法那样绕成 40 亿次而死等。
|
||||
|
||||
用法
|
||||
----
|
||||
python tools/adapt_intrupt_ccab.py <输入.hex> <输出.vh> [输出_fast.hex]
|
||||
|
||||
<输入.hex> programs/risc-v-intrupt-ccab.hex(未适配的原始镜像)
|
||||
<输出.vh> srcs/board/risc-v-intrupt-ccab.vh(上板综合用)
|
||||
[输出_fast.hex] 可选:延时改成约 0.6 us 的仿真专用版本。上板一次中断服务要跑
|
||||
几十毫秒(几十万个时钟),仿真跑完整流程会很慢,因此仿真用这个
|
||||
快版来验证控制流(入口 / 中断返回 / 不停机),延时量本身不影响
|
||||
控制流。
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
NOP = 0x00000013
|
||||
|
||||
LED_ADDR_HI = 1 # lui a7, 1 -> a7 = 0x1000(LED_ADDR)
|
||||
# 延时计数:计数初值为负,向上数到 0。0xFFFC0000 -> 0x40000 = 262144 次
|
||||
# (每轮约 2 个时钟)= 52.4 万个时钟 ≈ 10 ms @50 MHz。
|
||||
DELAY_NEG_HI = 0xFFFC0
|
||||
DELAY_FAST_NEG = -16 # 仿真用:addi t2, zero, -16 ≈ 0.6 us
|
||||
|
||||
# DISP_DLY 保存返回地址的偏移:必须大于 ISR 最大嵌套栈用量(4 层 x 48B = 192B)
|
||||
LINK_SLOT = 256
|
||||
|
||||
# 寄存器号
|
||||
ZERO, RA, SP, GP, T2, A0, A7 = 0, 1, 2, 3, 7, 10, 17
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
HALT_LOAD = enc_i(10, ZERO, 0, A7) # addi a7, zero, 10
|
||||
ECALL = 0x00000073
|
||||
DISP_LUI = enc_u(LED_ADDR_HI, A7) # lui a7, 1
|
||||
DISP_SW = enc_s(0, A0, A7, 2) # sw a0, 0(a7) <- 正确:字写
|
||||
DISP_SB_BAD = enc_s(0, A0, A7, 0) # sb a0, 0(a7) <- 误写成字节写,一并修正
|
||||
# 说明:这两种都当成"显示点"。DISP_DLY 内部一律用正确的 sw 写整个字,因此即使
|
||||
# 输入镜像里是 sb(只写低字节,显示的高位会残留旧值),适配后也会恢复正常。
|
||||
|
||||
|
||||
def disp_dly(delay_word):
|
||||
"""DISP_DLY 的 8 条指令(delay_word 决定延时长短)。
|
||||
|
||||
返回地址存在 sp+LINK_SLOT,位于 ISR 可能压栈的区域之上,所以中断(含嵌套)
|
||||
打断延时也不会破坏它 —— 见文件头的说明。
|
||||
"""
|
||||
return [
|
||||
enc_s(LINK_SLOT, RA, SP, 2), # sw ra, 256(sp)
|
||||
DISP_LUI, # lui a7, 1
|
||||
DISP_SW, # sw a0, 0(a7)
|
||||
delay_word, # lui t2, 0xFFFC0 / addi t2, zero, -16
|
||||
enc_i(1, T2, 0, T2), # addi t2, t2, 1
|
||||
enc_b(-4, ZERO, T2, 4), # blt t2, zero, -4
|
||||
enc_i(LINK_SLOT, SP, 2, RA, 0x03), # lw ra, 256(sp)
|
||||
enc_i(0, RA, 0, ZERO, 0x67), # jalr zero, ra, 0
|
||||
]
|
||||
|
||||
|
||||
def disp_dly_isr(delay_word):
|
||||
"""ISR 专用的显示/延时例程(8 条)—— 链接寄存器用 a0,不用 ra。
|
||||
|
||||
三个 ISR 的现场都保存了 a7 与 a0(s1/a7/a0/s0/s3/s4/s5/s6),所以把链接放
|
||||
a0、把显示值放 a7 不会额外破坏任何东西;而 ra 完全不被触碰,被打断的代码
|
||||
即使停在主 DISP_DLY 的 `jalr zero,0(ra)` 上也不会被破坏。
|
||||
"""
|
||||
return [
|
||||
enc_s(LINK_SLOT, A0, SP, 2), # sw a0, 256(sp) 保存链接寄存器
|
||||
enc_u(LED_ADDR_HI, A0), # lui a0, 1 a0 = 0x1000
|
||||
enc_s(0, A7, A0, 2), # sw a7, 0(a0) 显示 a7
|
||||
delay_word, # lui t2, 0xFFFC0 / addi t2,zero,-16
|
||||
enc_i(1, T2, 0, T2), # addi t2, t2, 1
|
||||
enc_b(-4, ZERO, T2, 4), # blt t2, zero, -4
|
||||
enc_i(LINK_SLOT, SP, 2, A0, 0x03), # lw a0, 256(sp)
|
||||
enc_i(0, A0, 0, ZERO, 0x67), # jalr zero, a0, 0
|
||||
]
|
||||
|
||||
|
||||
# 三个中断入口的原指令(核对地址没有跑偏)
|
||||
ISR_ENTRY = {43: 0x00512023, 121: 0x00612023, 194: 0x00912023}
|
||||
# 三个 ISR 的代码从 ISR1 入口开始一直排到程序末尾(主程序在它之前)
|
||||
ISR1_WORD = min(ISR_ENTRY)
|
||||
|
||||
# 需要修正目标寄存器的 `lw X,0(sp)`:addr -> (当前错误的 rd, 正确的 rd)
|
||||
# ISR3(入口 0x308)恢复顺序应为:uepc,s6,s5,s4,s3,s0,a0,a7,s1
|
||||
# ISR2(入口 0x1E4)恢复顺序应为:uepc,s6,s5,s4,s3,s0,a0,a7,s2,s1,t3,t1
|
||||
FIX_POPS = {
|
||||
0x410: (9, 19), 0x418: (19, 8), 0x420: (8, 10),
|
||||
0x428: (10, 17), 0x430: (17, 9), # ISR3
|
||||
0x2D0: (18, 8), 0x2D8: (9, 10), 0x2E0: (28, 17),
|
||||
0x2E8: (6, 18), 0x2F0: (8, 9), 0x2F8: (10, 28),
|
||||
0x300: (17, 6), # ISR2
|
||||
}
|
||||
|
||||
# ISR3 的 SB 测试用了未保存的 t1/t3/s2 -> 改成 ISR3 自己保存的 s4/s5/s6
|
||||
# addr -> (要改的域, 原寄存器号, 新寄存器号)
|
||||
T1, T3, S2, S4, S5, S6 = 6, 28, 18, 20, 21, 22
|
||||
FIX_ISR3_SB = {
|
||||
0x358: ('rd', T1, S4), 0x35C: ('rd', T3, S5),
|
||||
0x364: ('rd', S2, S6), 0x368: ('rs1', T1, S4),
|
||||
0x378: ('rs2', S2, S6), 0x37C: ('rd_rs1', T1, S4),
|
||||
0x380: ('rd_rs1', T3, S5), 0x384: ('rs1', T3, S5),
|
||||
0x388: ('rd', T3, S5), 0x38C: ('rd', T1, S4),
|
||||
0x390: ('rs1', T1, S4), 0x3A0: ('rd_rs1', T1, S4),
|
||||
0x3A4: ('rd_rs1', T3, S5), 0x3A8: ('rs1', T3, S5),
|
||||
}
|
||||
|
||||
|
||||
def read_hex(path):
|
||||
words = []
|
||||
with open(path, encoding="ascii") as f:
|
||||
for line in f:
|
||||
line = line.split("//")[0].strip()
|
||||
if line:
|
||||
words.append(int(line, 16))
|
||||
return words
|
||||
|
||||
|
||||
def patch(words):
|
||||
"""返回 (适配后的字表, 适配后的快版字表, 统计信息)。"""
|
||||
halt_sites = [i for i in range(len(words) - 1)
|
||||
if words[i] == HALT_LOAD and words[i + 1] == ECALL]
|
||||
if len(halt_sites) != 3:
|
||||
raise SystemExit("预期 3 处 'addi a7,zero,10 + ecall'(ISR 停机点),"
|
||||
"实际找到 %d 处;输入文件不是预期的原始镜像?" % len(halt_sites))
|
||||
|
||||
disp_sites = [i for i in range(len(words) - 1)
|
||||
if words[i] == DISP_LUI
|
||||
and words[i + 1] in (DISP_SW, DISP_SB_BAD)]
|
||||
if len(disp_sites) != 11:
|
||||
raise SystemExit("预期 11 处 'lui a7,1 + sw/sb a0,0(a7)'(显示点),"
|
||||
"实际找到 %d 处;输入文件不是预期的原始镜像?" % len(disp_sites))
|
||||
|
||||
bad_store = [i for i in disp_sites if words[i + 1] == DISP_SB_BAD]
|
||||
|
||||
for i in halt_sites:
|
||||
words[i] = NOP
|
||||
words[i + 1] = NOP
|
||||
|
||||
# plain 变体:只去掉停机 ecall,显示保持原来的 "lui + sw" 两字写法
|
||||
# (必须在把显示点改成 jal 之前取副本,否则会生成“跳到没有函数体的地址”的镜像)
|
||||
plain = list(words)
|
||||
|
||||
# DISP_DLY 紧跟在原程序的最后一条指令之后(原程序长度不变)
|
||||
disp_pc = 4 * len(words)
|
||||
isr_pc = disp_pc + 32 # ISR 专用的 DISP_DLY_ISR 再占 8 字
|
||||
isr_sites = []
|
||||
for i in disp_sites:
|
||||
if i >= ISR1_WORD:
|
||||
# 三个 ISR 里的显示点:改用 a0 做链接寄存器、显示值走 a7
|
||||
# 注意 jal 落在显示点的后一条(i+1)上,偏移要相对它自己算
|
||||
isr_sites.append(i)
|
||||
words[i] = enc_i(0, A0, 0, A7) # add a7, a0, zero
|
||||
words[i + 1] = enc_j(isr_pc - 4 * (i + 1), A0) # jal a0, DISP_DLY_ISR
|
||||
else:
|
||||
# 主程序里的显示点:照旧用 ra 链接
|
||||
words[i] = enc_j(disp_pc - 4 * i, RA)
|
||||
words[i + 1] = NOP
|
||||
if not isr_sites:
|
||||
raise SystemExit("没有找到 ISR 里的显示点;中断入口地址对不上?")
|
||||
|
||||
# 自检:把刚写进去的 jal 偏移反解出来,确认真的跳到两个例程的入口
|
||||
def jal_target(w, pc):
|
||||
imm = (((w >> 31) & 1) << 20) | (((w >> 21) & 0x3FF) << 1) \
|
||||
| (((w >> 20) & 1) << 11) | (((w >> 12) & 0xFF) << 12)
|
||||
if imm & 0x100000:
|
||||
imm -= 0x200000
|
||||
return pc + imm
|
||||
|
||||
for i in disp_sites:
|
||||
if i >= ISR1_WORD:
|
||||
want, at = isr_pc, 4 * (i + 1) # ISR:jal 在显示点后一条
|
||||
else:
|
||||
want, at = disp_pc, 4 * i # 主程序:jal 就在显示点上
|
||||
got = jal_target(words[at // 4], at)
|
||||
if got != want:
|
||||
raise SystemExit("显示点 0x%03X 的跳转目标算错了:0x%03X != 0x%03X"
|
||||
% (4 * i, got, want))
|
||||
|
||||
for idx, want in ISR_ENTRY.items():
|
||||
if words[idx] != want:
|
||||
raise SystemExit("中断入口 0x%03X 处的指令被改动了(%08X != %08X)"
|
||||
% (4 * idx, words[idx], want))
|
||||
|
||||
# --- 4) 修正 ISR2 / ISR3 的现场恢复顺序(只改 lw 的 rd 域) ---
|
||||
fixed_pops = 0
|
||||
for pc, (bad_rd, rd) in FIX_POPS.items():
|
||||
w = words[pc // 4]
|
||||
op, f3 = w & 0x7F, (w >> 12) & 7
|
||||
rs1, imm = (w >> 15) & 0x1F, (w >> 20) & 0xFFF
|
||||
old_rd = (w >> 7) & 0x1F
|
||||
if not (op == 0x03 and f3 == 2 and rs1 == SP and imm == 0):
|
||||
raise SystemExit("0x%03X 不是预期的 `lw X,0(sp)`(%08X)" % (pc, w))
|
||||
if old_rd == rd:
|
||||
continue # 已经是对的(重复运行时跳过)
|
||||
if old_rd != bad_rd:
|
||||
raise SystemExit("0x%03X 的目标寄存器不是预期的 %d(实际 %d)"
|
||||
% (pc, bad_rd, old_rd))
|
||||
words[pc // 4] = (w & ~(0x1F << 7)) | (rd << 7)
|
||||
fixed_pops += 1
|
||||
|
||||
# --- 5) 把 ISR3 SB 测试里的 t1/t3/s2 换成 s4/s5/s6 ---
|
||||
def set_field(w, name, val):
|
||||
if name == 'rd':
|
||||
return (w & ~(0x1F << 7)) | (val << 7)
|
||||
if name == 'rs1':
|
||||
return (w & ~(0x1F << 15)) | (val << 15)
|
||||
if name == 'rs2':
|
||||
return (w & ~(0x1F << 20)) | (val << 20)
|
||||
if name == 'rd_rs1':
|
||||
return ((w & ~(0x1F << 7)) | (val << 7)) & ~(0x1F << 15) | (val << 15)
|
||||
raise SystemExit('未知域 ' + name)
|
||||
|
||||
fixed_sb = 0
|
||||
for pc, (field, old, new) in FIX_ISR3_SB.items():
|
||||
w = words[pc // 4]
|
||||
idx = {'rd': (w >> 7) & 0x1F, 'rs1': (w >> 15) & 0x1F,
|
||||
'rs2': (w >> 20) & 0x1F}
|
||||
if field == 'rd_rs1':
|
||||
cur = idx['rd']
|
||||
if idx['rs1'] != cur:
|
||||
raise SystemExit('0x%03X 的 rd/rs1 不一致' % pc)
|
||||
else:
|
||||
cur = idx[field]
|
||||
if cur == new:
|
||||
continue # 已经改过了
|
||||
if cur != old:
|
||||
raise SystemExit('0x%03X 的 %s 不是预期的 %d(实际 %d)'
|
||||
% (pc, field, old, cur))
|
||||
words[pc // 4] = set_field(w, field, new)
|
||||
fixed_sb += 1
|
||||
|
||||
slow = disp_dly(enc_u(DELAY_NEG_HI, T2)) + disp_dly_isr(enc_u(DELAY_NEG_HI, T2))
|
||||
fast = disp_dly(enc_i(DELAY_FAST_NEG, ZERO, 0, T2)) \
|
||||
+ disp_dly_isr(enc_i(DELAY_FAST_NEG, ZERO, 0, T2))
|
||||
return (words + slow, words + fast, plain,
|
||||
(halt_sites, disp_sites, disp_pc,
|
||||
[i for i in disp_sites if i < ISR1_WORD], isr_sites,
|
||||
bad_store, fixed_pops, fixed_sb))
|
||||
|
||||
|
||||
def write_hex(path, words):
|
||||
with open(path, "w", encoding="ascii", newline="\n") as f:
|
||||
for w in words:
|
||||
f.write("%08X\n" % w)
|
||||
|
||||
|
||||
def write_vh(path, words, src_name, total, note):
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(" // 由 tools/adapt_intrupt_ccab.py 生成 —— 不要手改\n")
|
||||
f.write(" // source: %s(%d 字,其余地址填 NOP)\n"
|
||||
% (src_name, len(words)))
|
||||
f.write(" // %s\n" % note)
|
||||
for addr in range(total):
|
||||
w = words[addr] if addr < len(words) else NOP
|
||||
f.write(" rom[%d] = 32'h%08X;\n" % (addr, w))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
raise SystemExit(__doc__.strip().split("用法")[-1])
|
||||
src, dst_vh = sys.argv[1], sys.argv[2]
|
||||
dst_fast = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
dst_plain = sys.argv[4] if len(sys.argv) > 4 else None
|
||||
|
||||
words = read_hex(src)
|
||||
slow, fast, plain, (halt_sites, disp_sites, disp_pc, main_sites, isr_sites,
|
||||
bad_store, fixed_pops, fixed_sb) = patch(words)
|
||||
|
||||
total = 1024
|
||||
write_vh(dst_vh, slow, src, total,
|
||||
"已适配:去掉 ISR 停机 ecall、显示改为 MMIO+DISP_DLY(约 10 ms)")
|
||||
if dst_fast:
|
||||
write_hex(dst_fast, fast)
|
||||
if dst_plain:
|
||||
write_hex(dst_plain, plain)
|
||||
|
||||
print("适配完成:%s(原程序 %d 字 + DISP_DLY %d 字 = %d 字)"
|
||||
% (dst_vh, len(words), len(slow) - len(words), len(slow)))
|
||||
print(" 去掉的停机 ecall 位置:%s"
|
||||
% ", ".join("0x%03X" % (4 * i) for i in halt_sites))
|
||||
print(" 换成显示调用的点:主程序 %s|ISR %s"
|
||||
% (", ".join("0x%03X" % (4 * i) for i in main_sites),
|
||||
", ".join("0x%03X" % (4 * i) for i in isr_sites)))
|
||||
print(" DISP_DLY 本体:0x%03X - 0x%03X(主程序用,链接在 ra)"
|
||||
% (disp_pc, disp_pc + 28))
|
||||
print(" DISP_DLY_ISR 本体:0x%03X - 0x%03X(ISR 用,链接在 a0,不碰 ra)"
|
||||
% (disp_pc + 32, disp_pc + 60))
|
||||
print(" 修正的现场恢复顺序:%d 条" % fixed_pops)
|
||||
print(" ISR3 SB 测试改用的已保存寄存器:%d 条" % fixed_sb)
|
||||
if bad_store:
|
||||
print(" 顺带修正的字节写显示:%s"
|
||||
% ", ".join("0x%03X" % (4 * i) for i in bad_store))
|
||||
print(" 中断入口(不变):0x0AC / 0x1E4 / 0x308")
|
||||
if dst_fast:
|
||||
print(" 仿真快版:%s(延时约 0.6 us)" % dst_fast)
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user