Files
risc-v-cpu/pipelined-cpu/srcs/rtl/cpu21_riscv_irq_ctrl.v
Alpha f50639e90f feat(saolei): 新增纯 C 扫雷(MMIO 显示 + 中断读鼠标),扩展 CPU 外设并清理仓库
## 背景

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

## 硬件(pipelined-cpu/srcs)

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

## 软件(saolei/)

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

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

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

## 仓库清理

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

## 文档

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

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

- 已实测:C 工具链链路(编译/链接/ISA 检查/hex 生成/入口地址逐条核对)
- 未验证:所有 RTL 未经综合与仿真(本机无 Vivado),C 程序也未在仿真器/硬件上运行过;
  建议先跑 tools/run_regress.ps1 确认原回归仍全 PASS,再用 SAOLEI_ROM 宏做行为仿真
2026-09-16 20:11:00 +08:00

189 lines
6.8 KiB
Verilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 中断控制器 + ustatus/uepc CSR + 嵌套返回栈。
//
// IRQ3 优先级最高,其次 IRQ2,最后 IRQ1;嵌套请求只有优先级高于当前级别
// 才会被接纳。中断入口地址由优先级选出,返回地址保存在 epc_stack 中,
// 当前上下文的活动返回 PC 放在 uepc_q(与课程讲义用 CSRRW 保存/恢复
// uepc 的流程一致)。
// -----------------------------------------------------------------------------
module cpu21_riscv_irq_ctrl #(
parameter [31:0] IRQ1_VECTOR = 32'h0000_30ac,
parameter [31:0] IRQ2_VECTOR = 32'h0000_31e4,
parameter [31:0] IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4,
// ustatus 复位值。bit0 为 MIE。课程的中断测试程序只会在中断处理
// 程序中设置 MIE,若复位后不立即置位 MIE,主程序将永远无法响应
// 第一次中断,因此这里默认打开 MIE。
parameter [31:0] USTATUS_INIT = 32'h0000_0001
) (
input wire clk,
input wire reset,
input wire [ 2:0] irq_i,
input wire halted_i,
// EX 级正在处理重定向/URET/停机,此时不允许响应中断
input wire ex_block_i,
// 中断发生时要保存的返回地址(由顶层根据流水线状态给出)
input wire [31:0] save_pc_i,
// EX 级的 URET
input wire uret_i,
// EX 级 CSR 指令(CSRRW/CSRRSI/CSRRCI)
input wire csr_valid_i,
input wire csr_set_i,
input wire csr_clear_i,
input wire csr_write_i,
input wire [11:0] csr_addr_i,
input wire [31:0] csr_imm_i,
input wire [31:0] csr_wdata_i,
// 控制结果
output wire take_irq_o,
output wire [31:0] vector_o,
output wire [31:0] return_pc_o,
output wire [ 2:0] pending_o,
output wire [ 1:0] level_o,
output wire [31:0] uepc_o,
output wire [31:0] ustatus_o
);
reg [31:0] ustatus_q;
reg [31:0] uepc_q;
reg [ 1:0] irq_current_q;
reg [ 2:0] irq_pending_q;
reg [ 2:0] irq_sync1_q;
reg [ 2:0] irq_sync2_q;
reg [ 2:0] irq_prev_q;
reg [31:0] epc_stack [0:IRQ_STACK_DEPTH-1];
reg [31:0] status_stack [0:IRQ_STACK_DEPTH-1];
reg [ 1:0] priority_stack [0:IRQ_STACK_DEPTH-1];
reg [ 2:0] irq_depth_q;
reg irq_selected_valid;
reg [ 1:0] irq_selected_level;
reg [31:0] irq_vector;
reg [ 2:0] irq_pending_d;
reg [31:0] ustatus_d;
reg [31:0] uepc_d;
integer s;
// 上升沿检测:只在 irq_i 由 0 变 1 的那一拍产生中断事件。
wire [ 2:0] irq_event = irq_sync2_q & ~irq_prev_q;
// 优先级仲裁:在当前级别允许的条件下选出优先级最高的中断源。
always @* begin
irq_selected_valid = 1'b0;
irq_selected_level = 2'd0;
if (irq_pending_q[2] && (irq_current_q < 2'd3)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd3;
end else if (irq_pending_q[1] && (irq_current_q < 2'd2)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd2;
end else if (irq_pending_q[0] && (irq_current_q < 2'd1)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd1;
end
end
// 依据被选中的优先级选择对应的中断入口地址。
always @* begin
case (irq_selected_level)
2'd1: irq_vector = IRQ1_VECTOR;
2'd2: irq_vector = IRQ2_VECTOR;
2'd3: irq_vector = IRQ3_VECTOR;
default: irq_vector = IRQ1_VECTOR;
endcase
end
// 响应中断的条件:有已选中中断、MIE 使能、栈未溢出、未停机,且没有
// 正在处理的重定向/URET/停机事件。
assign take_irq_o = irq_selected_valid && ustatus_q[0] &&
(irq_depth_q < IRQ_STACK_DEPTH) && !halted_i &&
!ex_block_i;
assign vector_o = irq_vector;
assign return_pc_o = uepc_q;
assign pending_o = irq_pending_q;
assign level_o = irq_current_q;
assign uepc_o = uepc_q;
assign ustatus_o = ustatus_q;
// 组合逻辑计算下一拍的挂起位与 CSR(ustatus/uepc)取值。
always @* begin
irq_pending_d = irq_pending_q | irq_event;
if (take_irq_o) begin
case (irq_selected_level)
2'd1: irq_pending_d[0] = 1'b0;
2'd2: irq_pending_d[1] = 1'b0;
2'd3: irq_pending_d[2] = 1'b0;
default: irq_pending_d = irq_pending_d;
endcase
end
ustatus_d = ustatus_q;
uepc_d = uepc_q;
if (csr_valid_i && csr_set_i) begin
if (csr_addr_i == 12'h004) ustatus_d = ustatus_q | csr_imm_i;
else if (csr_addr_i == 12'h041) uepc_d = uepc_q | csr_imm_i;
end else if (csr_valid_i && csr_clear_i) begin
if (csr_addr_i == 12'h004) ustatus_d = ustatus_q & ~csr_imm_i;
else if (csr_addr_i == 12'h041) uepc_d = uepc_q & ~csr_imm_i;
end else if (csr_valid_i && csr_write_i) begin
if (csr_addr_i == 12'h004) ustatus_d = csr_wdata_i;
else if (csr_addr_i == 12'h041) uepc_d = csr_wdata_i;
end
if (take_irq_o) ustatus_d[0] = 1'b0;
else if (uret_i && (irq_depth_q != 0)) ustatus_d = status_stack[irq_depth_q-1'b1];
if (take_irq_o) uepc_d = save_pc_i;
else if (uret_i && (irq_depth_q > 1)) uepc_d = epc_stack[irq_depth_q-2];
else if (uret_i && (irq_depth_q == 1)) uepc_d = 32'b0;
end
// 复位时清空中断状态与嵌套栈。
always @(posedge clk or posedge reset) begin
if (reset) begin
ustatus_q <= USTATUS_INIT;
uepc_q <= 32'b0;
irq_current_q <= 2'b0;
irq_pending_q <= 3'b0;
irq_sync1_q <= 3'b0;
irq_sync2_q <= 3'b0;
irq_prev_q <= 3'b0;
irq_depth_q <= 3'b0;
for (s = 0; s < IRQ_STACK_DEPTH; s = s + 1) begin
epc_stack[s] <= 32'b0;
status_stack[s] <= 32'b0;
priority_stack[s] <= 2'b0;
end
end else begin
// 中断输入打两拍同步,并锁存挂起状态与 CSR。
irq_sync1_q <= irq_i;
irq_sync2_q <= irq_sync1_q;
irq_prev_q <= irq_sync2_q;
irq_pending_q <= irq_pending_d;
ustatus_q <= ustatus_d;
uepc_q <= uepc_d;
if (take_irq_o) begin
if (irq_depth_q < IRQ_STACK_DEPTH) begin
epc_stack[irq_depth_q] <= save_pc_i;
status_stack[irq_depth_q] <= ustatus_q;
priority_stack[irq_depth_q] <= irq_current_q;
irq_depth_q <= irq_depth_q + 3'd1;
end
irq_current_q <= irq_selected_level;
end else if (uret_i) begin
if (irq_depth_q != 0) begin
irq_depth_q <= irq_depth_q - 3'd1;
irq_current_q <= priority_stack[irq_depth_q-1'b1];
end
end
end
end
endmodule
`default_nettype wire