feat(board): 中断综合测试程序上板 + 可变分频/刷新率;README 大幅扩写

板级顶层(srcs/board/cpu21_board_top.v)
- ROM 增加 RISC_V_INTRUPT_CCAB_ROM 分支,中断入口 0x0AC/0x1E4/0x308;
  与 SAOLEI_ROM / BOARD_ROM_FAST / 默认跑马灯构成 ifdef 链
- 新增参数 CLK_DIV_INTRUPT:跑中断测试程序时用更慢的 CPU 时钟(默认 32
  = 3.125 MHz),让数码管上每个数值停留约 160 ms 便于观察
- 数码管扫描分频随 CPU 分频缩放(SEG_REFRESH_DIV = 100000/CPU_DIV),
  保证每位数码管仍是 125 Hz(不闪);数码管改显示 MMIO 的 led_data;
  LED[2:0] 改为锁存式中断指示(请求上升沿点亮、对应 ISR 执行 uret 后熄灭)
- 复位同步器/led_we 指示等注释同步更新;srcs/rtl/cpu21_seg7.v 分频计数
  加宽到 20 位以容纳随主频变化的 REFRESH_DIV

工具与程序
- tools/adapt_intrupt_ccab.py:把课程中断测试镜像适配成上板 ROM。六处适配
  (ISR 停机 ecall、显示改 MMIO+DISP_DLY、ISR2/ISR3 出栈顺序、ISR3 SB 测试
  用未保存寄存器、ISR 破坏 ra 的 DISP_DLY_ISR),全部 2 字换 2 字,
  不移动任何指令地址;带断言与 jal 目标自检
- programs/risc-v-intrupt-ccab.asm/.hex:课程中断综合测试程序(含六处适配说明)
- create_project.tcl:新增 use_intrupt_ccab / use_saolei 开关,自动把对应
  ROM 头文件按 Verilog Header 加入 sources_1 并定义宏
- tools/rars_f0c874c.jar:课程 RARS 汇编器移到 tools/

文档(README.md)
- 新增"各模块的实现方式":按流水线主体 / 外设 / 板级逐模块讲实现要点
- 新增"内置程序与宏的用法":ROM 宏表(含各自的中断入口)、GUI/Tcl/命令行
  三种定义方式、中断程序的适配流程与仿真验证方法
- 新增"分频与刷新率一览":CLK_DIV / CLK_DIV_INTRUPT / SEG_REFRESH_DIV /
  led_we 时长 / DISP_DLY 延时常量分别在哪里改、怎么换算
- 修正引脚表(LED[2:0] 中断指示、led_we 0.34 s、CPU_DIV)与 ROM/RAM 说明
  (ROM 8192 字 = 32 KB),补充中断程序上板现象与 ROM 头文件清单

验证:不带宏 / 带 RISC_V_INTRUPT_CCAB_ROM 两种配置 xvlog 编译均无错误;
三路嵌套与 IRQ3 抢占 ISR1 的仿真通过(level 逐级升降、uepc 正确恢复、
halted=0);tools/check_rtl_text.ps1 体检 OK。
This commit is contained in:
2026-09-19 15:44:47 +08:00
parent 4f82331dbe
commit f7844b7b1c
11 changed files with 2702 additions and 890 deletions
+83 -105
View File
@@ -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