diff --git a/README.md b/README.md index b458744..cccac5e 100644 --- a/README.md +++ b/README.md @@ -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 镜像 diff --git a/pipelined-cpu/create_project.tcl b/pipelined-cpu/create_project.tcl index 60319e1..97d95a1 100644 --- a/pipelined-cpu/create_project.tcl +++ b/pipelined-cpu/create_project.tcl @@ -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" diff --git a/pipelined-cpu/programs/risc-v-intrupt-ccab.asm b/pipelined-cpu/programs/risc-v-intrupt-ccab.asm new file mode 100644 index 0000000..1894161 --- /dev/null +++ b/pipelined-cpu/programs/risc-v-intrupt-ccab.asm @@ -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 diff --git a/pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.hex b/pipelined-cpu/programs/risc-v-intrupt-ccab.hex similarity index 91% rename from pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.hex rename to pipelined-cpu/programs/risc-v-intrupt-ccab.hex index 493d05e..d363043 100644 --- a/pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.hex +++ b/pipelined-cpu/programs/risc-v-intrupt-ccab.hex @@ -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 diff --git a/pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.asm b/pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.asm deleted file mode 100644 index 8c8476a..0000000 --- a/pipelined-cpu/programs/risc-v多级中断测试(EPC内存堆栈保护) - ccab.asm +++ /dev/null @@ -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 - diff --git a/pipelined-cpu/srcs/board/cpu21_board_top.v b/pipelined-cpu/srcs/board/cpu21_board_top.v index ce58225..8896001 100644 --- a/pipelined-cpu/srcs/board/cpu21_board_top.v +++ b/pipelined-cpu/srcs/board/cpu21_board_top.v @@ -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"` 给出 @@ -25,347 +31,405 @@ // 注意:ROM_WORDS / RAM_WORDS 需为 2 的幂(默认 1024,对应地址位 [11:2])。 // ----------------------------------------------------------------------------- module cpu21_board_top #( - parameter [31:0] LED_ADDR = 32'h0000_1000, - parameter ROM_WORDS = 8192, - parameter RAM_WORDS = 1024, - parameter [31:0] USTATUS_INIT = 32'h0000_0001, - // 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。 - // 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上, - // 实测(xc7a100tcsg324-1): - // CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的 - // 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns, - // 7~8 ns 都是布线); - // CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。 - // 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。 - parameter integer CLK_DIV = 2, - // 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA) - parameter integer USE_PS2_MOUSE = 0 - ) ( - input wire clk100, // E3 - input wire rst_n, // C12:CPU_RESETN,低有效 - input wire go, // N17:BTNC(同时用作鼠标左键) - input wire [ 2:0] irq_sw, // SW[2:0]:原来的 3 路中断源(保持兼容) - input wire btnu, // M18:光标上 - input wire btnd, // P18:光标下 - input wire btnl, // P17:光标左 - input wire btnr, // M17:光标右 - input wire sw_flag, // R15:SW[3],插旗模式(软件读 INPUT 寄存器) - output wire [15:0] led, // LED[15:0] - output wire led_we, // LED16_B:MMIO 写入指示 - // 七段数码管(共阳极:位选/段选都是低有效) - output wire [ 7:0] an, - output wire [ 6:0] seg, - output wire dp, - // VGA - output wire [ 3:0] vga_r, - output wire [ 3:0] vga_g, - output wire [ 3:0] vga_b, - output wire vga_hs, - output wire vga_vs, - // PS/2(只有 USE_PS2_MOUSE=1 时才用) - input wire ps2_clk, - input wire ps2_data - ); - localparam ROM_AW = $clog2(ROM_WORDS); - localparam RAM_AW = $clog2(RAM_WORDS); + parameter [31:0] LED_ADDR = 32'h0000_1000, + parameter ROM_WORDS = 8192, + parameter RAM_WORDS = 1024, + parameter [31:0] USTATUS_INIT = 32'h0000_0001, + // 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。 + // 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上, + // 实测(xc7a100tcsg324-1): + // CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的 + // 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns, + // 7~8 ns 都是布线); + // CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。 + // 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。 + parameter integer CLK_DIV = 2, + // 中断综合测试程序(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 +) ( + input wire clk100, // E3 + input wire rst_n, // C12:CPU_RESETN,低有效 + input wire go, // N17:BTNC(同时用作鼠标左键) + input wire [ 2:0] irq_sw, // SW[2:0]:原来的 3 路中断源(保持兼容) + input wire btnu, // M18:光标上 + input wire btnd, // P18:光标下 + input wire btnl, // P17:光标左 + input wire btnr, // M17:光标右 + input wire sw_flag, // R15:SW[3],插旗模式(软件读 INPUT 寄存器) + output wire [15:0] led, // LED[15:0] + output wire led_we, // LED16_B:MMIO 写入指示 + // 七段数码管(共阳极:位选/段选都是低有效) + output wire [ 7:0] an, + output wire [ 6:0] seg, + output wire dp, + // VGA + output wire [ 3:0] vga_r, + output wire [ 3:0] vga_g, + output wire [ 3:0] vga_b, + output wire vga_hs, + output wire vga_vs, + // PS/2(只有 USE_PS2_MOUSE=1 时才用) + input wire ps2_clk, + input wire ps2_data +); + localparam ROM_AW = $clog2(ROM_WORDS); + localparam RAM_AW = $clog2(RAM_WORDS); - wire arst = ~rst_n; // 异步复位(高有效) - - // --------------------------------------------------------------------------- - // 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。 - // - // 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的 - // 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”, - // 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。 - // 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明, - // 改分频比时两处要一起改。 - // --------------------------------------------------------------------------- - reg [15:0] clk_cnt; - reg clk_div; - - always @(posedge clk100 or posedge arst) begin - if (arst) begin - clk_cnt <= 16'd0; - clk_div <= 1'b0; - end - else if (clk_cnt == (CLK_DIV / 2 - 1)) begin - clk_cnt <= 16'd0; - clk_div <= ~clk_div; - end - else begin - clk_cnt <= clk_cnt + 16'd1; - end - end - - wire clk_cpu = clk_div; - - // --------------------------------------------------------------------------- - // 复位同步器:按键异步置位、释放与 CPU 时钟同步 - // --------------------------------------------------------------------------- - reg [2:0] rst_sync_q; - - always @(posedge clk_cpu or posedge arst) begin - if (arst) - rst_sync_q <= 3'b111; - else - rst_sync_q <= {rst_sync_q[1:0], 1'b0}; - end - - wire reset = rst_sync_q[2]; - - // CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到) - wire [31:0] instr_addr; - wire [31:0] data_addr; - wire [31:0] data_wdata; - wire [ 3:0] data_wstrb; - wire data_we; - wire [31:0] led_data; - wire led_valid; - // 扫雷扩展外设的连线(先声明,例化在下面) - wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动) - wire [31:0] seg_data; // 数码管:8 位十六进制 - wire seg_valid; - wire fb_we; // 显存写端口 - wire [ 9:0] fb_waddr; - wire [31:0] fb_wdata; - wire [ 3:0] fb_wstrb; - wire [ 9:0] fb_raddr; // 显存读端口(VGA) - wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口) - wire frame_tick; - wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw}; - // 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。 - wire [ 2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])}; - - // --------------------------------------------------------------------------- - // 指令 ROM:组合读(未用到的地址填 NOP) - // --------------------------------------------------------------------------- - reg [31:0] rom [0:ROM_WORDS-1]; - integer i; - - initial begin - for (i = 0; i < ROM_WORDS; i = i + 1) - rom[i] = 32'h0000_0013; // NOP 填充 -`ifdef SAOLEI_ROM -// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。 -// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。 -`include "cpu21_saolei_rom.vh" -`elsif BOARD_ROM_FAST - // 仿真专用:把演示程序的延时改小的版本(综合流程不会用到) -`include "cpu21_board_rom_fast.vh" + // CPU 时钟实际用的分频比:中断综合测试程序用更慢的 CLK_DIV_INTRUPT, + // 其余程序用 CLK_DIV。两者必须都是偶数(保证 50% 占空比)。 +`ifdef RISC_V_INTRUPT_CCAB_ROM + localparam integer CPU_DIV = CLK_DIV_INTRUPT; `else -`include "cpu21_board_rom.vh" + localparam integer CPU_DIV = CLK_DIV; `endif -end + // 数码管扫描分频:目标是“每位数码管约 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; -// --------------------------------------------------------------------------- -// 数据 RAM:字节写使能 + 组合读 -// --------------------------------------------------------------------------- -reg [31:0] ram[0:RAM_WORDS-1]; -integer j; + wire arst = ~rst_n; // 异步复位(高有效) -initial begin - for (j = 0; j < RAM_WORDS; j = j + 1) - ram[j] = 32'b0; -end + // --------------------------------------------------------------------------- + // 时钟: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 声明。 + // 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; -wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2]; -wire [RAM_AW+1:2] ram_index = data_addr[RAM_AW+1:2]; -wire ram_sel = (data_addr[31:RAM_AW+2] == 0); + always @(posedge clk100 or posedge arst) begin + if (arst) begin + clk_cnt <= 16'd0; + clk_div <= 1'b0; + end else if (clk_cnt == (CPU_DIV / 2 - 1)) begin + clk_cnt <= 16'd0; + clk_div <= ~clk_div; + end else begin + clk_cnt <= clk_cnt + 16'd1; + end + end -wire [31:0] instr_word = rom[rom_index]; -wire [31:0] data_rdata = ram_sel ? ram[ram_index] : 32'b0; + wire clk_cpu = clk_div; -always @(posedge clk_cpu) begin + // --------------------------------------------------------------------------- + // 复位同步器:按键异步置位、释放与 CPU 时钟同步 + // --------------------------------------------------------------------------- + reg [2:0] rst_sync_q; + + always @(posedge clk_cpu or posedge arst) begin + if (arst) rst_sync_q <= 3'b111; + else rst_sync_q <= {rst_sync_q[1:0], 1'b0}; + end + + wire reset = rst_sync_q[2]; + + // CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到) + wire [31:0] instr_addr; + wire [31:0] data_addr; + wire [31:0] data_wdata; + wire [ 3:0] data_wstrb; + wire data_we; + wire [31:0] led_data; // LED_ADDR 写入值,同时作为七段数码管显示数据 + wire led_valid; + // 扫雷扩展外设的连线(先声明,例化在下面) + wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动) + wire [31:0] seg_data; // 保留的独立数码管 MMIO 数据 + wire seg_valid; + wire fb_we; // 显存写端口 + wire [ 9:0] fb_waddr; + wire [31:0] fb_wdata; + wire [ 3:0] fb_wstrb; + wire [ 9:0] fb_raddr; // 显存读端口(VGA) + wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口) + wire [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 与"关卡入口"共用一个入口。 + wire [2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])}; + + // --------------------------------------------------------------------------- + // 指令 ROM:组合读(未用到的地址填 NOP) + // --------------------------------------------------------------------------- + reg [31:0] rom[0:ROM_WORDS-1]; + integer i; + + initial begin + for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h0000_0013; // NOP 填充 +`ifdef 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" +`else + `include "cpu21_board_rom.vh" +`endif + + end + + // --------------------------------------------------------------------------- + // 数据 RAM:字节写使能 + 组合读 + // --------------------------------------------------------------------------- + reg [31:0] ram[0:RAM_WORDS-1]; + integer j; + + initial begin + for (j = 0; j < RAM_WORDS; j = j + 1) ram[j] = 32'b0; + end + + wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2]; + wire [RAM_AW+1:2] ram_index = data_addr[RAM_AW+1:2]; + wire ram_sel = (data_addr[31:RAM_AW+2] == 0); + + wire [31:0] instr_word = rom[rom_index]; + wire [31:0] data_rdata = ram_sel ? ram[ram_index] : 32'b0; + + always @(posedge clk_cpu) begin if (ram_sel && data_we) begin - if (data_wstrb[0]) - ram[ram_index][7:0] <= data_wdata[7:0]; - if (data_wstrb[1]) - ram[ram_index][15:8] <= data_wdata[15:8]; - if (data_wstrb[2]) - ram[ram_index][23:16] <= data_wdata[23:16]; - if (data_wstrb[3]) - ram[ram_index][31:24] <= data_wdata[31:24]; + 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 + end -// --------------------------------------------------------------------------- -// 处理器:只连接上板需要的端口,调试端口留空(不产生 I/O) -// --------------------------------------------------------------------------- -cpu21_riscv_redirect_int_bpb #( - .LED_ADDR (LED_ADDR), - .USTATUS_INIT(USTATUS_INIT), - // 三个中断入口:必须与 sw/linker.ld 里 - // .text.isr1/2/3 的地址一致 - // IRQ1(0x100) = 关卡(游戏)入口 - // IRQ2(0x200) = 鼠标读取 - // IRQ3(0x300) = 备用 - .IRQ1_VECTOR (32'h0000_0100), - .IRQ2_VECTOR (32'h0000_0200), - .IRQ3_VECTOR (32'h0000_0300) - ) u_cpu ( - .clk (clk_cpu), - .reset (reset), - .instr_i (instr_word), - .data_rdata_i(data_rdata), - .irq_i (irq_vec), - .go_i (go), + // --------------------------------------------------------------------------- + // 处理器:只连接上板需要的端口,调试端口留空(不产生 I/O) + // --------------------------------------------------------------------------- + cpu21_riscv_redirect_int_bpb #( + .LED_ADDR (LED_ADDR), + .USTATUS_INIT(USTATUS_INIT), + // 中断入口必须与所选 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), + .instr_i (instr_word), + .data_rdata_i(data_rdata), + .irq_i (irq_vec), + .go_i (go), - .instr_addr_o(instr_addr), - .data_addr_o (data_addr), - .data_wdata_o(data_wdata), - .data_wstrb_o(data_wstrb), - .data_we_o (data_we), + .instr_addr_o(instr_addr), + .data_addr_o (data_addr), + .data_wdata_o(data_wdata), + .data_wstrb_o(data_wstrb), + .data_we_o (data_we), - .led_data_o (led_data), - .led_valid_o(led_valid), + .led_data_o (led_data), + .led_valid_o(led_valid), - // 扫雷扩展外设 - .seg_data_o (seg_data), - .seg_valid_o (seg_valid), - .fb_we_o (fb_we), - .fb_waddr_o (fb_waddr), - .fb_wdata_o (fb_wdata), - .fb_wstrb_o (fb_wstrb), - .fb_rdata_i (fb_rdata), - .disp_ctrl_o (disp_ctrl), - .disp_cursor_o (disp_cursor), - .mouse_ev_valid_i(ev_valid), - .mouse_dx_i (ev_dx), - .mouse_dy_i (ev_dy), - .mouse_btn_i (ev_btn), - .sw_i (sw_vec), - .frame_tick_i (frame_tick), - .mouse_irq_o (mouse_irq), - .level_irq_o (level_irq) - ); + // 扫雷扩展外设 + .seg_data_o (seg_data), + .seg_valid_o (seg_valid), + .fb_we_o (fb_we), + .fb_waddr_o (fb_waddr), + .fb_wdata_o (fb_wdata), + .fb_wstrb_o (fb_wstrb), + .fb_rdata_i (fb_rdata), + .disp_ctrl_o (disp_ctrl), + .disp_cursor_o (disp_cursor), + .mouse_ev_valid_i (ev_valid), + .mouse_dx_i (ev_dx), + .mouse_dy_i (ev_dy), + .mouse_btn_i (ev_btn), + .sw_i (sw_vec), + .frame_tick_i (frame_tick), + .mouse_irq_o (mouse_irq), + .level_irq_o (level_irq), + // 中断服务级别,供板上的中断指示 LED 使用(见下面 LED 那一段) + .irq_current_level_o(irq_level) + ); -// --------------------------------------------------------------------------- -// 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察 -// --------------------------------------------------------------------------- -assign led = led_data[15:0]; + // --------------------------------------------------------------------------- + // 板载 LED:三路中断指示;十六进制数据改由七段数码管显示。 + // + // 第 k 路中断从"请求出现"开始点亮,一直亮到它的 ISR 执行完 uret(中断返回) + // 之后才熄灭: + // 置位:irq_vec[k] 的上升沿。中断控制器内部也是按上升沿响应中断的 + // (cpu21_riscv_irq_ctrl 里的 irq_event),两者行为一致;开关一直 + // 接通不会重复点亮,只有新的上升沿才会重新置位。 + // 清零:服务级别 irq_level 从 k+1 **降下来**,说明第 k+1 级的中断已经返回。 + // 注意必须判断“下降”:级别从 1 升到 2 是深层中断抢占,此时第 1 级 + // 的中断还在服务中,LED 不能熄。中断可以嵌套,返回时按级别逐层降 + // 下来,因此每一路都在自己的 ISR 结束时熄灭。 + // --------------------------------------------------------------------------- + reg [2:0] irq_req_q; // 中断请求打一拍,用于取上升沿 + reg [1:0] irq_level_q; // 服务级别打一拍,用于检测"返回" + reg [2:0] irq_led_q; // 三路中断指示 -reg [23:0] led_we_cnt; + 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) - led_we_cnt <= 24'd0; - else if (led_valid) - led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz - else if (led_we_cnt != 24'd0) - led_we_cnt <= led_we_cnt - 24'd1; -end + 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_we = (led_we_cnt != 24'd0); + assign led = {{13{1'b0}}, irq_led_q}; -// =========================================================================== -// 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线 -// -// 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的 -// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的 -// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。 -// =========================================================================== -localparam integer FB_WORDS = 300; // 300 字 = 1200 字节 + 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 + // (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); + + // =========================================================================== + // 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线 + // + // 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的 + // fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的 + // 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。 + // =========================================================================== + localparam integer FB_WORDS = 300; // 300 字 = 1200 字节 -// ---- 显存 ---- -reg [31:0] fb [0:FB_WORDS-1]; -integer k; + // ---- 显存 ---- + reg [31:0] fb[0:FB_WORDS-1]; + integer k; -initial begin - for (k = 0; k < FB_WORDS; k = k + 1) - fb[k] = 32'b0; -end + initial begin + for (k = 0; k < FB_WORDS; k = k + 1) fb[k] = 32'b0; + end -always @(posedge clk_cpu) begin + 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 + end -wire [31:0] fb_rdata = fb[fb_raddr]; + wire [31:0] fb_rdata = fb[fb_raddr]; -// ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ---- -reg px_q; -always @(posedge clk_cpu or posedge arst) begin - if (arst) - px_q <= 1'b0; - else - px_q <= ~px_q; -end -wire px_tick = ~px_q; + // ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ---- + reg px_q; + always @(posedge clk_cpu or posedge arst) begin + if (arst) px_q <= 1'b0; + else px_q <= ~px_q; + end + wire px_tick = ~px_q; -cpu21_vga u_vga ( - .clk (clk_cpu), - .reset (reset), - .tick (px_tick), - .fb_raddr_o (fb_raddr), - .fb_word_i (fb_rdata), - .ctrl_i (disp_ctrl), - .cursor_i (disp_cursor), - .vga_r (vga_r), - .vga_g (vga_g), - .vga_b (vga_b), - .vga_hs (vga_hs), - .vga_vs (vga_vs), - .frame_tick_o(frame_tick) - ); + cpu21_vga u_vga ( + .clk (clk_cpu), + .reset (reset), + .tick (px_tick), + .fb_raddr_o (fb_raddr), + .fb_word_i (fb_rdata), + .ctrl_i (disp_ctrl), + .cursor_i (disp_cursor), + .vga_r (vga_r), + .vga_g (vga_g), + .vga_b (vga_b), + .vga_hs (vga_hs), + .vga_vs (vga_vs), + .frame_tick_o(frame_tick) + ); -// ---- 七段数码管(显示项目程序写的 8 位十六进制数)---- -cpu21_seg7 u_seg7 ( - .clk (clk_cpu), - .reset (reset), - .seg_data_i(seg_data), - .an_en_i (8'hFF), - .an_o (an), - .seg_o (seg), - .dp_o (dp) - ); + // ---- 七段数码管(显示程序写入 LED_ADDR 的 8 位十六进制数)---- + // 扫描分频按 CPU 时钟频率缩放,保持每位数码管约 125 Hz 刷新(不闪烁)。 + cpu21_seg7 #( + .REFRESH_DIV(SEG_REFRESH_DIV) + ) u_seg7 ( + .clk (clk_cpu), + .reset (reset), + .seg_data_i(led_data), + .an_en_i (8'hFF), + .an_o (an), + .seg_o (seg), + .dp_o (dp) + ); -// ---- 鼠标事件源 ---- -wire ev_valid; -wire [7:0] ev_dx, ev_dy; -wire [4:0] ev_btn; -wire [4:0] btn_vec = {go, btnu, btnd, btnl, btnr}; // {BTNC, BTNU, BTND, BTNL, BTNR} + // ---- 鼠标事件源 ---- + wire ev_valid; + wire [7:0] ev_dx, ev_dy; + wire [4:0] ev_btn; + wire [4:0] btn_vec = {go, btnu, btnd, btnl, btnr}; // {BTNC, BTNU, BTND, BTNL, BTNR} -generate + generate if (USE_PS2_MOUSE != 0) begin : g_ps2 - cpu21_ps2_mouse u_ps2 ( - .clk (clk_cpu), - .reset (reset), - .ps2_clk_i (ps2_clk), - .ps2_clk_oe_o (), - .ps2_data_i (ps2_data), - .ps2_data_oe_o(), - .ev_valid_o (ev_valid), - .ev_dx_o (ev_dx), - .ev_dy_o (ev_dy), - .ev_btn_o (ev_btn), - .rx_count_o () - ) - ; + cpu21_ps2_mouse u_ps2 ( + .clk (clk_cpu), + .reset (reset), + .ps2_clk_i (ps2_clk), + .ps2_clk_oe_o (), + .ps2_data_i (ps2_data), + .ps2_data_oe_o(), + .ev_valid_o (ev_valid), + .ev_dx_o (ev_dx), + .ev_dy_o (ev_dy), + .ev_btn_o (ev_btn), + .rx_count_o () + ); + end else begin : g_btn + cpu21_pointer #( + .SAMPLE_DIV(50000) + ) u_ptr ( + .clk (clk_cpu), + .reset (reset), + .btn_i (btn_vec), + .ev_valid_o(ev_valid), + .ev_dx_o (ev_dx), + .ev_dy_o (ev_dy), + .ev_btn_o (ev_btn) + ); end - else begin : g_btn - cpu21_pointer #( - .SAMPLE_DIV(50000) - ) u_ptr ( - .clk (clk_cpu), - .reset (reset), - .btn_i (btn_vec), - .ev_valid_o(ev_valid), - .ev_dx_o (ev_dx), - .ev_dy_o (ev_dy), - .ev_btn_o (ev_btn) - ); - end -endgenerate + endgenerate endmodule diff --git a/pipelined-cpu/srcs/board/risc-v-intrupt-ccab.vh b/pipelined-cpu/srcs/board/risc-v-intrupt-ccab.vh new file mode 100644 index 0000000..cfd06dc --- /dev/null +++ b/pipelined-cpu/srcs/board/risc-v-intrupt-ccab.vh @@ -0,0 +1,1027 @@ + // 由 tools/adapt_intrupt_ccab.py 生成 —— 不要手改 + // source: programs/risc-v-intrupt-ccab.hex(286 字,其余地址填 NOP) + // 已适配:去掉 ISR 停机 ecall、显示改为 MMIO+DISP_DLY(约 10 ms) + rom[0] = 32'h7D000113; + rom[1] = 32'h00100413; + rom[2] = 32'h01F41993; + rom[3] = 32'h41F9D993; + rom[4] = 32'h00000433; + rom[5] = 32'h00C00913; + rom[6] = 32'h00800B13; + rom[7] = 32'h00140413; + rom[8] = 32'h00F47413; + rom[9] = 32'h00800293; + rom[10] = 32'h00100313; + rom[11] = 32'h00499993; + rom[12] = 32'h0089E9B3; + rom[13] = 32'h01300533; + rom[14] = 32'h400000EF; + rom[15] = 32'h00000013; + rom[16] = 32'h406282B3; + rom[17] = 32'hFE0294E3; + rom[18] = 32'h00140413; + rom[19] = 32'h00F00F93; + rom[20] = 32'h01F47433; + rom[21] = 32'h01C41413; + rom[22] = 32'h00800293; + rom[23] = 32'h00100313; + rom[24] = 32'h0049D993; + rom[25] = 32'h0089E9B3; + rom[26] = 32'h01300533; + rom[27] = 32'h3CC000EF; + rom[28] = 32'h00000013; + rom[29] = 32'h406282B3; + rom[30] = 32'hFE0294E3; + rom[31] = 32'h01C45413; + rom[32] = 32'h406B0B33; + rom[33] = 32'h000B0463; + rom[34] = 32'hF95FF06F; + rom[35] = 32'h000002B3; + rom[36] = 32'hFFF2C293; + rom[37] = 32'h01029293; + rom[38] = 32'hFFF2E293; + rom[39] = 32'h00500533; + rom[40] = 32'h398000EF; + rom[41] = 32'h00000013; + rom[42] = 32'hF5DFF06F; + rom[43] = 32'h00512023; + rom[44] = 32'h00410113; + rom[45] = 32'h01C12023; + rom[46] = 32'h00410113; + rom[47] = 32'h00912023; + rom[48] = 32'h00410113; + rom[49] = 32'h01112023; + rom[50] = 32'h00410113; + rom[51] = 32'h00A12023; + rom[52] = 32'h00410113; + rom[53] = 32'h00812023; + rom[54] = 32'h00410113; + rom[55] = 32'h01312023; + rom[56] = 32'h00410113; + rom[57] = 32'h01412023; + rom[58] = 32'h00410113; + rom[59] = 32'h01512023; + rom[60] = 32'h00410113; + rom[61] = 32'h01612023; + rom[62] = 32'h00410113; + rom[63] = 32'h04106B73; + rom[64] = 32'h01612023; + rom[65] = 32'h00410113; + rom[66] = 32'h0040E073; + rom[67] = 32'h00200293; + rom[68] = 32'h01100493; + rom[69] = 32'h00849493; + rom[70] = 32'h01148493; + rom[71] = 32'h01049493; + rom[72] = 32'h00900533; + rom[73] = 32'h00050893; + rom[74] = 32'h3300056F; + rom[75] = 32'h01C00E13; + rom[76] = 32'h0254D4B3; + rom[77] = 32'h00900533; + rom[78] = 32'h00050893; + rom[79] = 32'h31C0056F; + rom[80] = 32'hFFFE0E13; + rom[81] = 32'hFE0E16E3; + rom[82] = 32'h00000013; + rom[83] = 32'h00000013; + rom[84] = 32'h00100B13; + rom[85] = 32'h00300A13; + rom[86] = 32'h00100A93; + rom[87] = 32'h01600433; + rom[88] = 32'h00441413; + rom[89] = 32'h014469B3; + rom[90] = 32'h01300533; + rom[91] = 32'h00050893; + rom[92] = 32'h2E80056F; + rom[93] = 32'hFE0416E3; + rom[94] = 32'h415A0A33; + rom[95] = 32'hFE0A10E3; + rom[96] = 32'h0040F073; + rom[97] = 32'hFFC10113; + rom[98] = 32'h00012B03; + rom[99] = 32'h041B1073; + rom[100] = 32'hFFC10113; + rom[101] = 32'h00012B03; + rom[102] = 32'hFFC10113; + rom[103] = 32'h00012A83; + rom[104] = 32'hFFC10113; + rom[105] = 32'h00012A03; + rom[106] = 32'hFFC10113; + rom[107] = 32'h00012983; + rom[108] = 32'hFFC10113; + rom[109] = 32'h00012403; + rom[110] = 32'hFFC10113; + rom[111] = 32'h00012503; + rom[112] = 32'hFFC10113; + rom[113] = 32'h00012883; + rom[114] = 32'hFFC10113; + rom[115] = 32'h00012483; + rom[116] = 32'hFFC10113; + rom[117] = 32'h00012E03; + rom[118] = 32'hFFC10113; + rom[119] = 32'h00012283; + rom[120] = 32'h00200073; + rom[121] = 32'h00612023; + rom[122] = 32'h00410113; + rom[123] = 32'h01C12023; + rom[124] = 32'h00410113; + rom[125] = 32'h00912023; + rom[126] = 32'h00410113; + rom[127] = 32'h01212023; + rom[128] = 32'h00410113; + rom[129] = 32'h01112023; + rom[130] = 32'h00410113; + rom[131] = 32'h00A12023; + rom[132] = 32'h00410113; + rom[133] = 32'h00812023; + rom[134] = 32'h00410113; + rom[135] = 32'h01312023; + rom[136] = 32'h00410113; + rom[137] = 32'h01412023; + rom[138] = 32'h00410113; + rom[139] = 32'h01512023; + rom[140] = 32'h00410113; + rom[141] = 32'h01612023; + rom[142] = 32'h00410113; + rom[143] = 32'h04106B73; + rom[144] = 32'h01612023; + rom[145] = 32'h00410113; + rom[146] = 32'h0040E073; + rom[147] = 32'hFF100493; + rom[148] = 32'h00900533; + rom[149] = 32'h00050893; + rom[150] = 32'h2000056F; + rom[151] = 32'h00148493; + rom[152] = 32'hFE04C8E3; + rom[153] = 32'h00000013; + rom[154] = 32'h00000013; + rom[155] = 32'h00200B13; + rom[156] = 32'h00300A13; + rom[157] = 32'h00100A93; + rom[158] = 32'h01600433; + rom[159] = 32'h00441413; + rom[160] = 32'h014469B3; + rom[161] = 32'h01300533; + rom[162] = 32'h00050893; + rom[163] = 32'h1CC0056F; + rom[164] = 32'hFE0416E3; + rom[165] = 32'h415A0A33; + rom[166] = 32'hFE0A10E3; + rom[167] = 32'h0040F073; + rom[168] = 32'hFFC10113; + rom[169] = 32'h00012B03; + rom[170] = 32'h041B1073; + rom[171] = 32'hFFC10113; + rom[172] = 32'h00012B03; + rom[173] = 32'hFFC10113; + rom[174] = 32'h00012A83; + rom[175] = 32'hFFC10113; + rom[176] = 32'h00012A03; + rom[177] = 32'hFFC10113; + rom[178] = 32'h00012983; + rom[179] = 32'hFFC10113; + rom[180] = 32'h00012403; + rom[181] = 32'hFFC10113; + rom[182] = 32'h00012503; + rom[183] = 32'hFFC10113; + rom[184] = 32'h00012883; + rom[185] = 32'hFFC10113; + rom[186] = 32'h00012903; + rom[187] = 32'hFFC10113; + rom[188] = 32'h00012483; + rom[189] = 32'hFFC10113; + rom[190] = 32'h00012E03; + rom[191] = 32'hFFC10113; + rom[192] = 32'h00012303; + rom[193] = 32'h00200073; + rom[194] = 32'h00912023; + rom[195] = 32'h00410113; + rom[196] = 32'h01112023; + rom[197] = 32'h00410113; + rom[198] = 32'h00A12023; + rom[199] = 32'h00410113; + rom[200] = 32'h00812023; + rom[201] = 32'h00410113; + rom[202] = 32'h01312023; + rom[203] = 32'h00410113; + rom[204] = 32'h01412023; + rom[205] = 32'h00410113; + rom[206] = 32'h01512023; + rom[207] = 32'h00410113; + rom[208] = 32'h01612023; + rom[209] = 32'h00410113; + rom[210] = 32'h04106B73; + rom[211] = 32'h01612023; + rom[212] = 32'h00410113; + rom[213] = 32'h0040E073; + rom[214] = 32'h00000A13; + rom[215] = 32'h02000A93; + rom[216] = 32'h00000493; + rom[217] = 32'h00100B13; + rom[218] = 32'h009A0023; + rom[219] = 32'h00900533; + rom[220] = 32'h00050893; + rom[221] = 32'h0E40056F; + rom[222] = 32'h016484B3; + rom[223] = 32'h001A0A13; + rom[224] = 32'hFFFA8A93; + rom[225] = 32'hFE0A92E3; + rom[226] = 32'h00800A93; + rom[227] = 32'h00000A13; + rom[228] = 32'h000A2483; + rom[229] = 32'h00900533; + rom[230] = 32'h00050893; + rom[231] = 32'h0BC0056F; + rom[232] = 32'h004A0A13; + rom[233] = 32'hFFFA8A93; + rom[234] = 32'hFE0A94E3; + rom[235] = 32'h00000013; + rom[236] = 32'h00000013; + rom[237] = 32'h00300B13; + rom[238] = 32'h00300A13; + rom[239] = 32'h00100A93; + rom[240] = 32'h01600433; + rom[241] = 32'h00441413; + rom[242] = 32'h014469B3; + rom[243] = 32'h01300533; + rom[244] = 32'h00050893; + rom[245] = 32'h0840056F; + rom[246] = 32'hFE0416E3; + rom[247] = 32'h415A0A33; + rom[248] = 32'hFE0A10E3; + rom[249] = 32'h0040F073; + rom[250] = 32'hFFC10113; + rom[251] = 32'h00012B03; + rom[252] = 32'h041B1073; + rom[253] = 32'hFFC10113; + rom[254] = 32'h00012B03; + rom[255] = 32'hFFC10113; + rom[256] = 32'h00012A83; + rom[257] = 32'hFFC10113; + rom[258] = 32'h00012A03; + rom[259] = 32'hFFC10113; + rom[260] = 32'h00012983; + rom[261] = 32'hFFC10113; + rom[262] = 32'h00012403; + rom[263] = 32'hFFC10113; + rom[264] = 32'h00012503; + rom[265] = 32'hFFC10113; + rom[266] = 32'h00012883; + rom[267] = 32'hFFC10113; + rom[268] = 32'h00012483; + rom[269] = 32'h00200073; + rom[270] = 32'h10112023; + rom[271] = 32'h000018B7; + rom[272] = 32'h00A8A023; + rom[273] = 32'hFFFC03B7; + rom[274] = 32'h00138393; + rom[275] = 32'hFE03CEE3; + rom[276] = 32'h10012083; + rom[277] = 32'h00008067; + rom[278] = 32'h10A12023; + rom[279] = 32'h00001537; + rom[280] = 32'h01152023; + rom[281] = 32'hFFFC03B7; + rom[282] = 32'h00138393; + rom[283] = 32'hFE03CEE3; + rom[284] = 32'h10012503; + rom[285] = 32'h00050067; + rom[286] = 32'h00000013; + rom[287] = 32'h00000013; + rom[288] = 32'h00000013; + rom[289] = 32'h00000013; + rom[290] = 32'h00000013; + rom[291] = 32'h00000013; + rom[292] = 32'h00000013; + rom[293] = 32'h00000013; + rom[294] = 32'h00000013; + rom[295] = 32'h00000013; + rom[296] = 32'h00000013; + rom[297] = 32'h00000013; + rom[298] = 32'h00000013; + rom[299] = 32'h00000013; + rom[300] = 32'h00000013; + rom[301] = 32'h00000013; + rom[302] = 32'h00000013; + rom[303] = 32'h00000013; + rom[304] = 32'h00000013; + rom[305] = 32'h00000013; + rom[306] = 32'h00000013; + rom[307] = 32'h00000013; + rom[308] = 32'h00000013; + rom[309] = 32'h00000013; + rom[310] = 32'h00000013; + rom[311] = 32'h00000013; + rom[312] = 32'h00000013; + rom[313] = 32'h00000013; + rom[314] = 32'h00000013; + rom[315] = 32'h00000013; + rom[316] = 32'h00000013; + rom[317] = 32'h00000013; + rom[318] = 32'h00000013; + rom[319] = 32'h00000013; + rom[320] = 32'h00000013; + rom[321] = 32'h00000013; + rom[322] = 32'h00000013; + rom[323] = 32'h00000013; + rom[324] = 32'h00000013; + rom[325] = 32'h00000013; + rom[326] = 32'h00000013; + rom[327] = 32'h00000013; + rom[328] = 32'h00000013; + rom[329] = 32'h00000013; + rom[330] = 32'h00000013; + rom[331] = 32'h00000013; + rom[332] = 32'h00000013; + rom[333] = 32'h00000013; + rom[334] = 32'h00000013; + rom[335] = 32'h00000013; + rom[336] = 32'h00000013; + rom[337] = 32'h00000013; + rom[338] = 32'h00000013; + rom[339] = 32'h00000013; + rom[340] = 32'h00000013; + rom[341] = 32'h00000013; + rom[342] = 32'h00000013; + rom[343] = 32'h00000013; + rom[344] = 32'h00000013; + rom[345] = 32'h00000013; + rom[346] = 32'h00000013; + rom[347] = 32'h00000013; + rom[348] = 32'h00000013; + rom[349] = 32'h00000013; + rom[350] = 32'h00000013; + rom[351] = 32'h00000013; + rom[352] = 32'h00000013; + rom[353] = 32'h00000013; + rom[354] = 32'h00000013; + rom[355] = 32'h00000013; + rom[356] = 32'h00000013; + rom[357] = 32'h00000013; + rom[358] = 32'h00000013; + rom[359] = 32'h00000013; + rom[360] = 32'h00000013; + rom[361] = 32'h00000013; + rom[362] = 32'h00000013; + rom[363] = 32'h00000013; + rom[364] = 32'h00000013; + rom[365] = 32'h00000013; + rom[366] = 32'h00000013; + rom[367] = 32'h00000013; + rom[368] = 32'h00000013; + rom[369] = 32'h00000013; + rom[370] = 32'h00000013; + rom[371] = 32'h00000013; + rom[372] = 32'h00000013; + rom[373] = 32'h00000013; + rom[374] = 32'h00000013; + rom[375] = 32'h00000013; + rom[376] = 32'h00000013; + rom[377] = 32'h00000013; + rom[378] = 32'h00000013; + rom[379] = 32'h00000013; + rom[380] = 32'h00000013; + rom[381] = 32'h00000013; + rom[382] = 32'h00000013; + rom[383] = 32'h00000013; + rom[384] = 32'h00000013; + rom[385] = 32'h00000013; + rom[386] = 32'h00000013; + rom[387] = 32'h00000013; + rom[388] = 32'h00000013; + rom[389] = 32'h00000013; + rom[390] = 32'h00000013; + rom[391] = 32'h00000013; + rom[392] = 32'h00000013; + rom[393] = 32'h00000013; + rom[394] = 32'h00000013; + rom[395] = 32'h00000013; + rom[396] = 32'h00000013; + rom[397] = 32'h00000013; + rom[398] = 32'h00000013; + rom[399] = 32'h00000013; + rom[400] = 32'h00000013; + rom[401] = 32'h00000013; + rom[402] = 32'h00000013; + rom[403] = 32'h00000013; + rom[404] = 32'h00000013; + rom[405] = 32'h00000013; + rom[406] = 32'h00000013; + rom[407] = 32'h00000013; + rom[408] = 32'h00000013; + rom[409] = 32'h00000013; + rom[410] = 32'h00000013; + rom[411] = 32'h00000013; + rom[412] = 32'h00000013; + rom[413] = 32'h00000013; + rom[414] = 32'h00000013; + rom[415] = 32'h00000013; + rom[416] = 32'h00000013; + rom[417] = 32'h00000013; + rom[418] = 32'h00000013; + rom[419] = 32'h00000013; + rom[420] = 32'h00000013; + rom[421] = 32'h00000013; + rom[422] = 32'h00000013; + rom[423] = 32'h00000013; + rom[424] = 32'h00000013; + rom[425] = 32'h00000013; + rom[426] = 32'h00000013; + rom[427] = 32'h00000013; + rom[428] = 32'h00000013; + rom[429] = 32'h00000013; + rom[430] = 32'h00000013; + rom[431] = 32'h00000013; + rom[432] = 32'h00000013; + rom[433] = 32'h00000013; + rom[434] = 32'h00000013; + rom[435] = 32'h00000013; + rom[436] = 32'h00000013; + rom[437] = 32'h00000013; + rom[438] = 32'h00000013; + rom[439] = 32'h00000013; + rom[440] = 32'h00000013; + rom[441] = 32'h00000013; + rom[442] = 32'h00000013; + rom[443] = 32'h00000013; + rom[444] = 32'h00000013; + rom[445] = 32'h00000013; + rom[446] = 32'h00000013; + rom[447] = 32'h00000013; + rom[448] = 32'h00000013; + rom[449] = 32'h00000013; + rom[450] = 32'h00000013; + rom[451] = 32'h00000013; + rom[452] = 32'h00000013; + rom[453] = 32'h00000013; + rom[454] = 32'h00000013; + rom[455] = 32'h00000013; + rom[456] = 32'h00000013; + rom[457] = 32'h00000013; + rom[458] = 32'h00000013; + rom[459] = 32'h00000013; + rom[460] = 32'h00000013; + rom[461] = 32'h00000013; + rom[462] = 32'h00000013; + rom[463] = 32'h00000013; + rom[464] = 32'h00000013; + rom[465] = 32'h00000013; + rom[466] = 32'h00000013; + rom[467] = 32'h00000013; + rom[468] = 32'h00000013; + rom[469] = 32'h00000013; + rom[470] = 32'h00000013; + rom[471] = 32'h00000013; + rom[472] = 32'h00000013; + rom[473] = 32'h00000013; + rom[474] = 32'h00000013; + rom[475] = 32'h00000013; + rom[476] = 32'h00000013; + rom[477] = 32'h00000013; + rom[478] = 32'h00000013; + rom[479] = 32'h00000013; + rom[480] = 32'h00000013; + rom[481] = 32'h00000013; + rom[482] = 32'h00000013; + rom[483] = 32'h00000013; + rom[484] = 32'h00000013; + rom[485] = 32'h00000013; + rom[486] = 32'h00000013; + rom[487] = 32'h00000013; + rom[488] = 32'h00000013; + rom[489] = 32'h00000013; + rom[490] = 32'h00000013; + rom[491] = 32'h00000013; + rom[492] = 32'h00000013; + rom[493] = 32'h00000013; + rom[494] = 32'h00000013; + rom[495] = 32'h00000013; + rom[496] = 32'h00000013; + rom[497] = 32'h00000013; + rom[498] = 32'h00000013; + rom[499] = 32'h00000013; + rom[500] = 32'h00000013; + rom[501] = 32'h00000013; + rom[502] = 32'h00000013; + rom[503] = 32'h00000013; + rom[504] = 32'h00000013; + rom[505] = 32'h00000013; + rom[506] = 32'h00000013; + rom[507] = 32'h00000013; + rom[508] = 32'h00000013; + rom[509] = 32'h00000013; + rom[510] = 32'h00000013; + rom[511] = 32'h00000013; + rom[512] = 32'h00000013; + rom[513] = 32'h00000013; + rom[514] = 32'h00000013; + rom[515] = 32'h00000013; + rom[516] = 32'h00000013; + rom[517] = 32'h00000013; + rom[518] = 32'h00000013; + rom[519] = 32'h00000013; + rom[520] = 32'h00000013; + rom[521] = 32'h00000013; + rom[522] = 32'h00000013; + rom[523] = 32'h00000013; + rom[524] = 32'h00000013; + rom[525] = 32'h00000013; + rom[526] = 32'h00000013; + rom[527] = 32'h00000013; + rom[528] = 32'h00000013; + rom[529] = 32'h00000013; + rom[530] = 32'h00000013; + rom[531] = 32'h00000013; + rom[532] = 32'h00000013; + rom[533] = 32'h00000013; + rom[534] = 32'h00000013; + rom[535] = 32'h00000013; + rom[536] = 32'h00000013; + rom[537] = 32'h00000013; + rom[538] = 32'h00000013; + rom[539] = 32'h00000013; + rom[540] = 32'h00000013; + rom[541] = 32'h00000013; + rom[542] = 32'h00000013; + rom[543] = 32'h00000013; + rom[544] = 32'h00000013; + rom[545] = 32'h00000013; + rom[546] = 32'h00000013; + rom[547] = 32'h00000013; + rom[548] = 32'h00000013; + rom[549] = 32'h00000013; + rom[550] = 32'h00000013; + rom[551] = 32'h00000013; + rom[552] = 32'h00000013; + rom[553] = 32'h00000013; + rom[554] = 32'h00000013; + rom[555] = 32'h00000013; + rom[556] = 32'h00000013; + rom[557] = 32'h00000013; + rom[558] = 32'h00000013; + rom[559] = 32'h00000013; + rom[560] = 32'h00000013; + rom[561] = 32'h00000013; + rom[562] = 32'h00000013; + rom[563] = 32'h00000013; + rom[564] = 32'h00000013; + rom[565] = 32'h00000013; + rom[566] = 32'h00000013; + rom[567] = 32'h00000013; + rom[568] = 32'h00000013; + rom[569] = 32'h00000013; + rom[570] = 32'h00000013; + rom[571] = 32'h00000013; + rom[572] = 32'h00000013; + rom[573] = 32'h00000013; + rom[574] = 32'h00000013; + rom[575] = 32'h00000013; + rom[576] = 32'h00000013; + rom[577] = 32'h00000013; + rom[578] = 32'h00000013; + rom[579] = 32'h00000013; + rom[580] = 32'h00000013; + rom[581] = 32'h00000013; + rom[582] = 32'h00000013; + rom[583] = 32'h00000013; + rom[584] = 32'h00000013; + rom[585] = 32'h00000013; + rom[586] = 32'h00000013; + rom[587] = 32'h00000013; + rom[588] = 32'h00000013; + rom[589] = 32'h00000013; + rom[590] = 32'h00000013; + rom[591] = 32'h00000013; + rom[592] = 32'h00000013; + rom[593] = 32'h00000013; + rom[594] = 32'h00000013; + rom[595] = 32'h00000013; + rom[596] = 32'h00000013; + rom[597] = 32'h00000013; + rom[598] = 32'h00000013; + rom[599] = 32'h00000013; + rom[600] = 32'h00000013; + rom[601] = 32'h00000013; + rom[602] = 32'h00000013; + rom[603] = 32'h00000013; + rom[604] = 32'h00000013; + rom[605] = 32'h00000013; + rom[606] = 32'h00000013; + rom[607] = 32'h00000013; + rom[608] = 32'h00000013; + rom[609] = 32'h00000013; + rom[610] = 32'h00000013; + rom[611] = 32'h00000013; + rom[612] = 32'h00000013; + rom[613] = 32'h00000013; + rom[614] = 32'h00000013; + rom[615] = 32'h00000013; + rom[616] = 32'h00000013; + rom[617] = 32'h00000013; + rom[618] = 32'h00000013; + rom[619] = 32'h00000013; + rom[620] = 32'h00000013; + rom[621] = 32'h00000013; + rom[622] = 32'h00000013; + rom[623] = 32'h00000013; + rom[624] = 32'h00000013; + rom[625] = 32'h00000013; + rom[626] = 32'h00000013; + rom[627] = 32'h00000013; + rom[628] = 32'h00000013; + rom[629] = 32'h00000013; + rom[630] = 32'h00000013; + rom[631] = 32'h00000013; + rom[632] = 32'h00000013; + rom[633] = 32'h00000013; + rom[634] = 32'h00000013; + rom[635] = 32'h00000013; + rom[636] = 32'h00000013; + rom[637] = 32'h00000013; + rom[638] = 32'h00000013; + rom[639] = 32'h00000013; + rom[640] = 32'h00000013; + rom[641] = 32'h00000013; + rom[642] = 32'h00000013; + rom[643] = 32'h00000013; + rom[644] = 32'h00000013; + rom[645] = 32'h00000013; + rom[646] = 32'h00000013; + rom[647] = 32'h00000013; + rom[648] = 32'h00000013; + rom[649] = 32'h00000013; + rom[650] = 32'h00000013; + rom[651] = 32'h00000013; + rom[652] = 32'h00000013; + rom[653] = 32'h00000013; + rom[654] = 32'h00000013; + rom[655] = 32'h00000013; + rom[656] = 32'h00000013; + rom[657] = 32'h00000013; + rom[658] = 32'h00000013; + rom[659] = 32'h00000013; + rom[660] = 32'h00000013; + rom[661] = 32'h00000013; + rom[662] = 32'h00000013; + rom[663] = 32'h00000013; + rom[664] = 32'h00000013; + rom[665] = 32'h00000013; + rom[666] = 32'h00000013; + rom[667] = 32'h00000013; + rom[668] = 32'h00000013; + rom[669] = 32'h00000013; + rom[670] = 32'h00000013; + rom[671] = 32'h00000013; + rom[672] = 32'h00000013; + rom[673] = 32'h00000013; + rom[674] = 32'h00000013; + rom[675] = 32'h00000013; + rom[676] = 32'h00000013; + rom[677] = 32'h00000013; + rom[678] = 32'h00000013; + rom[679] = 32'h00000013; + rom[680] = 32'h00000013; + rom[681] = 32'h00000013; + rom[682] = 32'h00000013; + rom[683] = 32'h00000013; + rom[684] = 32'h00000013; + rom[685] = 32'h00000013; + rom[686] = 32'h00000013; + rom[687] = 32'h00000013; + rom[688] = 32'h00000013; + rom[689] = 32'h00000013; + rom[690] = 32'h00000013; + rom[691] = 32'h00000013; + rom[692] = 32'h00000013; + rom[693] = 32'h00000013; + rom[694] = 32'h00000013; + rom[695] = 32'h00000013; + rom[696] = 32'h00000013; + rom[697] = 32'h00000013; + rom[698] = 32'h00000013; + rom[699] = 32'h00000013; + rom[700] = 32'h00000013; + rom[701] = 32'h00000013; + rom[702] = 32'h00000013; + rom[703] = 32'h00000013; + rom[704] = 32'h00000013; + rom[705] = 32'h00000013; + rom[706] = 32'h00000013; + rom[707] = 32'h00000013; + rom[708] = 32'h00000013; + rom[709] = 32'h00000013; + rom[710] = 32'h00000013; + rom[711] = 32'h00000013; + rom[712] = 32'h00000013; + rom[713] = 32'h00000013; + rom[714] = 32'h00000013; + rom[715] = 32'h00000013; + rom[716] = 32'h00000013; + rom[717] = 32'h00000013; + rom[718] = 32'h00000013; + rom[719] = 32'h00000013; + rom[720] = 32'h00000013; + rom[721] = 32'h00000013; + rom[722] = 32'h00000013; + rom[723] = 32'h00000013; + rom[724] = 32'h00000013; + rom[725] = 32'h00000013; + rom[726] = 32'h00000013; + rom[727] = 32'h00000013; + rom[728] = 32'h00000013; + rom[729] = 32'h00000013; + rom[730] = 32'h00000013; + rom[731] = 32'h00000013; + rom[732] = 32'h00000013; + rom[733] = 32'h00000013; + rom[734] = 32'h00000013; + rom[735] = 32'h00000013; + rom[736] = 32'h00000013; + rom[737] = 32'h00000013; + rom[738] = 32'h00000013; + rom[739] = 32'h00000013; + rom[740] = 32'h00000013; + rom[741] = 32'h00000013; + rom[742] = 32'h00000013; + rom[743] = 32'h00000013; + rom[744] = 32'h00000013; + rom[745] = 32'h00000013; + rom[746] = 32'h00000013; + rom[747] = 32'h00000013; + rom[748] = 32'h00000013; + rom[749] = 32'h00000013; + rom[750] = 32'h00000013; + rom[751] = 32'h00000013; + rom[752] = 32'h00000013; + rom[753] = 32'h00000013; + rom[754] = 32'h00000013; + rom[755] = 32'h00000013; + rom[756] = 32'h00000013; + rom[757] = 32'h00000013; + rom[758] = 32'h00000013; + rom[759] = 32'h00000013; + rom[760] = 32'h00000013; + rom[761] = 32'h00000013; + rom[762] = 32'h00000013; + rom[763] = 32'h00000013; + rom[764] = 32'h00000013; + rom[765] = 32'h00000013; + rom[766] = 32'h00000013; + rom[767] = 32'h00000013; + rom[768] = 32'h00000013; + rom[769] = 32'h00000013; + rom[770] = 32'h00000013; + rom[771] = 32'h00000013; + rom[772] = 32'h00000013; + rom[773] = 32'h00000013; + rom[774] = 32'h00000013; + rom[775] = 32'h00000013; + rom[776] = 32'h00000013; + rom[777] = 32'h00000013; + rom[778] = 32'h00000013; + rom[779] = 32'h00000013; + rom[780] = 32'h00000013; + rom[781] = 32'h00000013; + rom[782] = 32'h00000013; + rom[783] = 32'h00000013; + rom[784] = 32'h00000013; + rom[785] = 32'h00000013; + rom[786] = 32'h00000013; + rom[787] = 32'h00000013; + rom[788] = 32'h00000013; + rom[789] = 32'h00000013; + rom[790] = 32'h00000013; + rom[791] = 32'h00000013; + rom[792] = 32'h00000013; + rom[793] = 32'h00000013; + rom[794] = 32'h00000013; + rom[795] = 32'h00000013; + rom[796] = 32'h00000013; + rom[797] = 32'h00000013; + rom[798] = 32'h00000013; + rom[799] = 32'h00000013; + rom[800] = 32'h00000013; + rom[801] = 32'h00000013; + rom[802] = 32'h00000013; + rom[803] = 32'h00000013; + rom[804] = 32'h00000013; + rom[805] = 32'h00000013; + rom[806] = 32'h00000013; + rom[807] = 32'h00000013; + rom[808] = 32'h00000013; + rom[809] = 32'h00000013; + rom[810] = 32'h00000013; + rom[811] = 32'h00000013; + rom[812] = 32'h00000013; + rom[813] = 32'h00000013; + rom[814] = 32'h00000013; + rom[815] = 32'h00000013; + rom[816] = 32'h00000013; + rom[817] = 32'h00000013; + rom[818] = 32'h00000013; + rom[819] = 32'h00000013; + rom[820] = 32'h00000013; + rom[821] = 32'h00000013; + rom[822] = 32'h00000013; + rom[823] = 32'h00000013; + rom[824] = 32'h00000013; + rom[825] = 32'h00000013; + rom[826] = 32'h00000013; + rom[827] = 32'h00000013; + rom[828] = 32'h00000013; + rom[829] = 32'h00000013; + rom[830] = 32'h00000013; + rom[831] = 32'h00000013; + rom[832] = 32'h00000013; + rom[833] = 32'h00000013; + rom[834] = 32'h00000013; + rom[835] = 32'h00000013; + rom[836] = 32'h00000013; + rom[837] = 32'h00000013; + rom[838] = 32'h00000013; + rom[839] = 32'h00000013; + rom[840] = 32'h00000013; + rom[841] = 32'h00000013; + rom[842] = 32'h00000013; + rom[843] = 32'h00000013; + rom[844] = 32'h00000013; + rom[845] = 32'h00000013; + rom[846] = 32'h00000013; + rom[847] = 32'h00000013; + rom[848] = 32'h00000013; + rom[849] = 32'h00000013; + rom[850] = 32'h00000013; + rom[851] = 32'h00000013; + rom[852] = 32'h00000013; + rom[853] = 32'h00000013; + rom[854] = 32'h00000013; + rom[855] = 32'h00000013; + rom[856] = 32'h00000013; + rom[857] = 32'h00000013; + rom[858] = 32'h00000013; + rom[859] = 32'h00000013; + rom[860] = 32'h00000013; + rom[861] = 32'h00000013; + rom[862] = 32'h00000013; + rom[863] = 32'h00000013; + rom[864] = 32'h00000013; + rom[865] = 32'h00000013; + rom[866] = 32'h00000013; + rom[867] = 32'h00000013; + rom[868] = 32'h00000013; + rom[869] = 32'h00000013; + rom[870] = 32'h00000013; + rom[871] = 32'h00000013; + rom[872] = 32'h00000013; + rom[873] = 32'h00000013; + rom[874] = 32'h00000013; + rom[875] = 32'h00000013; + rom[876] = 32'h00000013; + rom[877] = 32'h00000013; + rom[878] = 32'h00000013; + rom[879] = 32'h00000013; + rom[880] = 32'h00000013; + rom[881] = 32'h00000013; + rom[882] = 32'h00000013; + rom[883] = 32'h00000013; + rom[884] = 32'h00000013; + rom[885] = 32'h00000013; + rom[886] = 32'h00000013; + rom[887] = 32'h00000013; + rom[888] = 32'h00000013; + rom[889] = 32'h00000013; + rom[890] = 32'h00000013; + rom[891] = 32'h00000013; + rom[892] = 32'h00000013; + rom[893] = 32'h00000013; + rom[894] = 32'h00000013; + rom[895] = 32'h00000013; + rom[896] = 32'h00000013; + rom[897] = 32'h00000013; + rom[898] = 32'h00000013; + rom[899] = 32'h00000013; + rom[900] = 32'h00000013; + rom[901] = 32'h00000013; + rom[902] = 32'h00000013; + rom[903] = 32'h00000013; + rom[904] = 32'h00000013; + rom[905] = 32'h00000013; + rom[906] = 32'h00000013; + rom[907] = 32'h00000013; + rom[908] = 32'h00000013; + rom[909] = 32'h00000013; + rom[910] = 32'h00000013; + rom[911] = 32'h00000013; + rom[912] = 32'h00000013; + rom[913] = 32'h00000013; + rom[914] = 32'h00000013; + rom[915] = 32'h00000013; + rom[916] = 32'h00000013; + rom[917] = 32'h00000013; + rom[918] = 32'h00000013; + rom[919] = 32'h00000013; + rom[920] = 32'h00000013; + rom[921] = 32'h00000013; + rom[922] = 32'h00000013; + rom[923] = 32'h00000013; + rom[924] = 32'h00000013; + rom[925] = 32'h00000013; + rom[926] = 32'h00000013; + rom[927] = 32'h00000013; + rom[928] = 32'h00000013; + rom[929] = 32'h00000013; + rom[930] = 32'h00000013; + rom[931] = 32'h00000013; + rom[932] = 32'h00000013; + rom[933] = 32'h00000013; + rom[934] = 32'h00000013; + rom[935] = 32'h00000013; + rom[936] = 32'h00000013; + rom[937] = 32'h00000013; + rom[938] = 32'h00000013; + rom[939] = 32'h00000013; + rom[940] = 32'h00000013; + rom[941] = 32'h00000013; + rom[942] = 32'h00000013; + rom[943] = 32'h00000013; + rom[944] = 32'h00000013; + rom[945] = 32'h00000013; + rom[946] = 32'h00000013; + rom[947] = 32'h00000013; + rom[948] = 32'h00000013; + rom[949] = 32'h00000013; + rom[950] = 32'h00000013; + rom[951] = 32'h00000013; + rom[952] = 32'h00000013; + rom[953] = 32'h00000013; + rom[954] = 32'h00000013; + rom[955] = 32'h00000013; + rom[956] = 32'h00000013; + rom[957] = 32'h00000013; + rom[958] = 32'h00000013; + rom[959] = 32'h00000013; + rom[960] = 32'h00000013; + rom[961] = 32'h00000013; + rom[962] = 32'h00000013; + rom[963] = 32'h00000013; + rom[964] = 32'h00000013; + rom[965] = 32'h00000013; + rom[966] = 32'h00000013; + rom[967] = 32'h00000013; + rom[968] = 32'h00000013; + rom[969] = 32'h00000013; + rom[970] = 32'h00000013; + rom[971] = 32'h00000013; + rom[972] = 32'h00000013; + rom[973] = 32'h00000013; + rom[974] = 32'h00000013; + rom[975] = 32'h00000013; + rom[976] = 32'h00000013; + rom[977] = 32'h00000013; + rom[978] = 32'h00000013; + rom[979] = 32'h00000013; + rom[980] = 32'h00000013; + rom[981] = 32'h00000013; + rom[982] = 32'h00000013; + rom[983] = 32'h00000013; + rom[984] = 32'h00000013; + rom[985] = 32'h00000013; + rom[986] = 32'h00000013; + rom[987] = 32'h00000013; + rom[988] = 32'h00000013; + rom[989] = 32'h00000013; + rom[990] = 32'h00000013; + rom[991] = 32'h00000013; + rom[992] = 32'h00000013; + rom[993] = 32'h00000013; + rom[994] = 32'h00000013; + rom[995] = 32'h00000013; + rom[996] = 32'h00000013; + rom[997] = 32'h00000013; + rom[998] = 32'h00000013; + rom[999] = 32'h00000013; + rom[1000] = 32'h00000013; + rom[1001] = 32'h00000013; + rom[1002] = 32'h00000013; + rom[1003] = 32'h00000013; + rom[1004] = 32'h00000013; + rom[1005] = 32'h00000013; + rom[1006] = 32'h00000013; + rom[1007] = 32'h00000013; + rom[1008] = 32'h00000013; + rom[1009] = 32'h00000013; + rom[1010] = 32'h00000013; + rom[1011] = 32'h00000013; + rom[1012] = 32'h00000013; + rom[1013] = 32'h00000013; + rom[1014] = 32'h00000013; + rom[1015] = 32'h00000013; + rom[1016] = 32'h00000013; + rom[1017] = 32'h00000013; + rom[1018] = 32'h00000013; + rom[1019] = 32'h00000013; + rom[1020] = 32'h00000013; + rom[1021] = 32'h00000013; + rom[1022] = 32'h00000013; + rom[1023] = 32'h00000013; diff --git a/pipelined-cpu/srcs/constrs/cpu21_board_top.xdc b/pipelined-cpu/srcs/constrs/cpu21_board_top.xdc index 0a9aa14..0bde45c 100644 --- a/pipelined-cpu/srcs/constrs/cpu21_board_top.xdc +++ b/pipelined-cpu/srcs/constrs/cpu21_board_top.xdc @@ -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, diff --git a/pipelined-cpu/srcs/rtl/cpu21_seg7.v b/pipelined-cpu/srcs/rtl/cpu21_seg7.v index a40005b..f6a117a 100644 --- a/pipelined-cpu/srcs/rtl/cpu21_seg7.v +++ b/pipelined-cpu/srcs/rtl/cpu21_seg7.v @@ -8,121 +8,99 @@ // 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 - ) ( - input wire clk, - input wire reset, - input wire [31:0] seg_data_i, // 8 个十六进制数字 - input wire [ 7:0] an_en_i, // 每位是否显示 - output reg [ 7:0] an_o, // 位选,低有效 - output reg [ 6:0] seg_o, // 段选 a..g,低有效 - output reg dp_o // 小数点,低有效 - ); + parameter integer REFRESH_DIV = 50000 // 50 MHz / 50000 = 1 kHz 切换一位 +) ( + input wire clk, + input wire reset, + input wire [31:0] seg_data_i, // 8 个十六进制数字 + input wire [ 7:0] an_en_i, // 每位是否显示 + output reg [ 7:0] an_o, // 位选,低有效 + output reg [ 6:0] seg_o, // 段选 a..g,低有效 + output reg dp_o // 小数点,低有效 +); - // --------------------------------------------------------------------------- - // 刷新计数与当前位 - // --------------------------------------------------------------------------- - reg [15:0] div_q; - reg [2:0] digit_q; - wire tick = (div_q == (REFRESH_DIV - 1)); + // --------------------------------------------------------------------------- + // 刷新计数与当前位 + // --------------------------------------------------------------------------- + // 计数位宽要能装下 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; - digit_q <= 3'd0; - end - else if (tick) begin - div_q <= 16'd0; - digit_q <= digit_q + 3'd1; - end - else begin - div_q <= div_q + 16'd1; - end + always @(posedge clk or posedge reset) begin + if (reset) begin + div_q <= 20'd0; + digit_q <= 3'd0; + end else if (tick) begin + div_q <= 20'd0; + digit_q <= digit_q + 3'd1; + end else begin + div_q <= div_q + 16'd1; end + end - // 当前要显示的十六进制数字 - reg [3:0] nib; - always @* begin - case (digit_q) - 3'd0: - nib = seg_data_i[3:0]; - 3'd1: - nib = seg_data_i[7:4]; - 3'd2: - nib = seg_data_i[11:8]; - 3'd3: - nib = seg_data_i[15:12]; - 3'd4: - nib = seg_data_i[19:16]; - 3'd5: - nib = seg_data_i[23:20]; - 3'd6: - nib = seg_data_i[27:24]; - default: - nib = seg_data_i[31:28]; - endcase - end + // 当前要显示的十六进制数字 + reg [3:0] nib; + always @* begin + case (digit_q) + 3'd0: nib = seg_data_i[3:0]; + 3'd1: nib = seg_data_i[7:4]; + 3'd2: nib = seg_data_i[11:8]; + 3'd3: nib = seg_data_i[15:12]; + 3'd4: nib = seg_data_i[19:16]; + 3'd5: nib = seg_data_i[23:20]; + 3'd6: nib = seg_data_i[27:24]; + default: nib = seg_data_i[31:28]; + endcase + end - // --------------------------------------------------------------------------- - // 十六进制 -> 七段码(bit0=a ... bit6=g,1 表示该段点亮) - // --------------------------------------------------------------------------- - reg [6:0] seg_on; - always @* begin - case (nib) - 4'h0: - seg_on = 7'b0111111; - 4'h1: - seg_on = 7'b0000110; - 4'h2: - seg_on = 7'b1011011; - 4'h3: - seg_on = 7'b1001111; - 4'h4: - seg_on = 7'b1100110; - 4'h5: - seg_on = 7'b1101101; - 4'h6: - seg_on = 7'b1111101; - 4'h7: - seg_on = 7'b0000111; - 4'h8: - seg_on = 7'b1111111; - 4'h9: - seg_on = 7'b1101111; - 4'hA: - seg_on = 7'b1110111; - 4'hB: - seg_on = 7'b1111100; - 4'hC: - seg_on = 7'b0111001; - 4'hD: - seg_on = 7'b1011110; - 4'hE: - seg_on = 7'b1111001; - default: - seg_on = 7'b1110001; // F - endcase - end + // --------------------------------------------------------------------------- + // 十六进制 -> 七段码(bit0=a ... bit6=g,1 表示该段点亮) + // --------------------------------------------------------------------------- + reg [6:0] seg_on; + always @* begin + case (nib) + 4'h0: seg_on = 7'b0111111; + 4'h1: seg_on = 7'b0000110; + 4'h2: seg_on = 7'b1011011; + 4'h3: seg_on = 7'b1001111; + 4'h4: seg_on = 7'b1100110; + 4'h5: seg_on = 7'b1101101; + 4'h6: seg_on = 7'b1111101; + 4'h7: seg_on = 7'b0000111; + 4'h8: seg_on = 7'b1111111; + 4'h9: seg_on = 7'b1101111; + 4'hA: seg_on = 7'b1110111; + 4'hB: seg_on = 7'b1111100; + 4'hC: seg_on = 7'b0111001; + 4'hD: seg_on = 7'b1011110; + 4'hE: seg_on = 7'b1111001; + default: seg_on = 7'b1110001; // F + endcase + end - // --------------------------------------------------------------------------- - // 位选与段选输出(共阳极 => 低有效) - // --------------------------------------------------------------------------- - always @(posedge clk or posedge reset) begin - if (reset) begin - an_o <= 8'hFF; - seg_o <= 7'h7F; - dp_o <= 1'b1; - end - else if (tick) begin - an_o <= ~(8'b0000_0001 << digit_q) | ~an_en_i; - seg_o <= ~seg_on; - dp_o <= 1'b1; // 不用小数点 - end + // --------------------------------------------------------------------------- + // 位选与段选输出(共阳极 => 低有效) + // --------------------------------------------------------------------------- + always @(posedge clk or posedge reset) begin + if (reset) begin + an_o <= 8'hFF; + seg_o <= 7'h7F; + dp_o <= 1'b1; + end else if (tick) begin + an_o <= ~(8'b0000_0001 << digit_q) | ~an_en_i; + seg_o <= ~seg_on; + dp_o <= 1'b1; // 不用小数点 end + end endmodule diff --git a/pipelined-cpu/tools/adapt_intrupt_ccab.py b/pipelined-cpu/tools/adapt_intrupt_ccab.py new file mode 100644 index 0000000..a9c30cf --- /dev/null +++ b/pipelined-cpu/tools/adapt_intrupt_ccab.py @@ -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() diff --git a/pipelined-cpu/programs/rars_f0c874c.jar b/pipelined-cpu/tools/rars_f0c874c.jar similarity index 100% rename from pipelined-cpu/programs/rars_f0c874c.jar rename to pipelined-cpu/tools/rars_f0c874c.jar