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:
@@ -71,9 +71,19 @@ add_files -norecurse -fileset sources_1 [list \
|
||||
set_property file_type {Verilog Header} \
|
||||
[get_files "$origin_dir/srcs/board/cpu21_board_rom.vh"]
|
||||
|
||||
# 默认跑哪个内置程序:1 = 扫雷(定义宏 SAOLEI_ROM),0 = 走马灯 demo。
|
||||
# 只有 cpu21_saolei_rom.vh 存在且 use_saolei = 1 时才会定义宏。
|
||||
set use_saolei 1
|
||||
# 默认跑哪个内置程序:中断测试程序优先,其次是扫雷,最后是走马灯 demo。
|
||||
# 修改下面两个开关即可切换板上程序;同一时间只应启用一个。
|
||||
set use_intrupt_ccab 1
|
||||
set use_saolei 0
|
||||
|
||||
# 中断测试程序的 ROM 初始化。
|
||||
set intrupt_ccab_rom "$origin_dir/srcs/board/risc-v-intrupt-ccab.vh"
|
||||
if {[file exists $intrupt_ccab_rom]} {
|
||||
add_files -norecurse -fileset sources_1 [list $intrupt_ccab_rom]
|
||||
set_property file_type {Verilog Header} [get_files $intrupt_ccab_rom]
|
||||
} else {
|
||||
puts "NOTE: 还没有 $intrupt_ccab_rom"
|
||||
}
|
||||
|
||||
# 扫雷程序的 ROM 初始化(由 saolei/tools/build.ps1 生成,可能还不存在)。
|
||||
# 综合/仿真时定义宏 SAOLEI_ROM 就会用它替换上面的跑马灯 ROM。
|
||||
@@ -87,7 +97,10 @@ if {[file exists $saolei_rom]} {
|
||||
|
||||
# 关键:sources_1 定义 SAOLEI_ROM,cpu21_board_top.v 里的 `ifdef 链才会用扫雷 ROM
|
||||
# (该宏只影响综合用的 sources_1,仿真 fileset 仍各自用 tb 自带的 ROM)
|
||||
if {$use_saolei && [file exists $saolei_rom]} {
|
||||
if {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
|
||||
set_property verilog_define {RISC_V_INTRUPT_CCAB_ROM} [get_filesets sources_1]
|
||||
puts "NOTE: sources_1 已定义 RISC_V_INTRUPT_CCAB_ROM —— 上板跑 risc-v-intrupt-ccab"
|
||||
} elseif {$use_saolei && [file exists $saolei_rom]} {
|
||||
set_property verilog_define {SAOLEI_ROM} [get_filesets sources_1]
|
||||
puts "NOTE: sources_1 已定义 SAOLEI_ROM —— 上板跑扫雷;想跑走马灯把本脚本里 use_saolei 改成 0"
|
||||
} else {
|
||||
@@ -129,7 +142,14 @@ foreach fs {sim_1 sim_2} {
|
||||
# ---------------------------------------------------------------- 结果汇总
|
||||
puts "工程已创建:$origin_dir/$proj_name.xpr"
|
||||
puts " sources_1 顶层 = cpu21_board_top(上板:时钟分频 + 片内 ROM/RAM + 引脚)"
|
||||
puts " sources_1 程序 = [expr {$use_saolei && [file exists $saolei_rom] ? {扫雷 (SAOLEI_ROM)} : {走马灯 demo}}]"
|
||||
if {$use_intrupt_ccab && [file exists $intrupt_ccab_rom]} {
|
||||
set selected_program {risc-v-intrupt-ccab (RISC_V_INTRUPT_CCAB_ROM)}
|
||||
} elseif {$use_saolei && [file exists $saolei_rom]} {
|
||||
set selected_program {扫雷 (SAOLEI_ROM)}
|
||||
} else {
|
||||
set selected_program {走马灯 demo}
|
||||
}
|
||||
puts " sources_1 程序 = $selected_program"
|
||||
puts " sim_1 顶层 = tb_cpu21_riscv_redirect_int_bpb(中断/分支预测)"
|
||||
puts " sim_2 顶层 = tb_no_intr(指令集测试,ROM 镜像在 srcs/rom/)"
|
||||
puts " 下一步:Run Synthesis → Run Implementation → Generate Bitstream,或 Run Simulation"
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
# =============================================================================
|
||||
# 注意:上板/仿真真正使用的镜像是 programs/risc-v-intrupt-ccab.hex(270 字),
|
||||
# 本文件是课程原始清单。由 tools/adapt_intrupt_ccab.py 生成 ROM 时会做六处适配:
|
||||
# 1) 三个 ISR 里的 "addi a7,zero,10 + ecall" 会让这颗 CPU 停机(a7 != 34 的
|
||||
# ecall 是"停机等 go_i"的约定),中断服务程序跑到那里就卡住、后面的中断显示
|
||||
# 循环永远不执行 -> ROM 里换成两条空操作;
|
||||
# 2) 原来的 "addi a7,zero,34 + ecall" 显示改成写 MMIO 的 LED 寄存器(0x1000),
|
||||
# 并且每个显示点都改为调用 DISP_DLY 加约 10 ms 延时 —— 否则 50 MHz 下刷新
|
||||
# 太快,数码管的动态扫描根本分辨不出来,只会看到一片亮;
|
||||
# 3) DISP_DLY 追加在程序末尾(0x438),因此所有指令地址、特别是三个中断入口
|
||||
# 0x0AC / 0x1E4 / 0x308 都保持不变;
|
||||
# 4) **hex 里 ISR2 / ISR3 的现场恢复(出栈)顺序被写乱了**(本清单是正确的,
|
||||
# ISR1 的 hex 也是正确的)。ISR3 把 s1 从 s3 的槽位弹出,于是 s1 被写成
|
||||
# s3 保存的 0xFFFFF111;ISR3 返回后,正在跑 BLT 测试的 ISR2 的循环计数 s1
|
||||
# 就变成 -3823,循环要跑约 170 万周期才退出,表现就是"二级/三级中断返回
|
||||
# 之后卡住";ISR2 的 s0/s2/a0/a7/s1/t3/t1 也全部错位。适配脚本按本清单的
|
||||
# 正确 LIFO 顺序改回 12 条 lw 的目标寄存器(字数不变、地址不动)。
|
||||
# 5) **ISR3 的 SB 测试用了它自己没有保存的寄存器**(t1 当地址、t3 当计数、
|
||||
# s2 当增量)。ISR3 的现场只保存了 s1/a7/a0/s0/s3/s4/s5/s6/uepc,所以:
|
||||
# 若 IRQ3 直接抢占正在跑 DIVU 测试的 ISR1(ISR1 用 t3 递减计数),ISR1 的
|
||||
# 计数会被改掉;ISR1 恢复后 `addi t3,t3,-1` 得到负数,`bne t3,zero` 永远
|
||||
# 成立,DIVU 循环再也回不来 —— 表现就是“拨了 SW0 再拨 SW2 之后卡住”;
|
||||
# 主程序的跑马灯步长 t1 也会被改掉。适配脚本把这 14 处寄存器域改成 ISR3
|
||||
# 自己保存的 s4/s5/s6(SB 测试结束后紧接着的 IntLoop3 会重新给它们赋值,
|
||||
# 所以这样换是安全的),字数与地址不变。
|
||||
# 6) **ISR 不能破坏被打断代码的 ra**:三个 ISR 都保存 s1/a7/a0/s0/s3/s4/s5/s6,
|
||||
# 但都没有保存 ra,而它们要调用显示例程,"jal ra,DISP_DLY" 会把 ra 写成 ISR
|
||||
# 内部的返回地址。如果中断恰好落在被打断代码依赖 ra 的位置(典型就是主程序
|
||||
# 正停在 DISP_DLY 末尾的 jalr zero,0(ra) 那一条上),中断返回后就会跳进 ISR
|
||||
# 代码里,一路执行到 ISR 结尾的 csrrci zero,0x4,1,把 MIE 永久清零:表现
|
||||
# 就是“跑一阵子之后拨开关再也不响应,只能复位”。窗口只有 1~2 拍,平时很
|
||||
# 难遇到(仿真里把延时改到极小才会碰上)。适配脚本给 ISR 另加一份显示例程
|
||||
# DISP_DLY_ISR(0x458),链接寄存器改用 a0、显示值走 a7(这两个寄存器三个
|
||||
# ISR 都保存/恢复),于是 ISR 完全不碰 ra;ISR 的显示点由 "jal ra,DISP_DLY +
|
||||
# nop" 换成 "add a7,a0,zero + jal a0,DISP_DLY_ISR",仍是 2 字换 2 字,
|
||||
# 字数与地址不变。
|
||||
# =============================================================================
|
||||
|
||||
.text
|
||||
addi sp, zero, 2000 #内存堆栈初始化
|
||||
|
||||
#############################################################
|
||||
#走马灯测?,测试addi,andi,slli,srli,srai,or,ori,nor,ecall LED按走马灯方式来回显示数据
|
||||
#############################################################
|
||||
|
||||
.text
|
||||
START:
|
||||
|
||||
addi s0,zero,1
|
||||
slli s3, s0, 31 # s3=0x80000000
|
||||
srai s3, s3, 31 # s3=0xFFFFFFFF
|
||||
add s0,zero,zero # s0=0
|
||||
addi s2,zero,12
|
||||
|
||||
addi s6,zero,8 #走马灯计数
|
||||
zmd_loop:
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
andi s0, s0, 15
|
||||
|
||||
#######################################
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
left:
|
||||
|
||||
slli s3, s3, 4 #走马灯左移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,left
|
||||
#######################################
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
addi t6,zero,15
|
||||
and s0, s0, t6
|
||||
slli s0, s0, 28
|
||||
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
|
||||
zmd_right:
|
||||
|
||||
srli s3, s3, 4 #走马灯右移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,zmd_right
|
||||
srli s0, s0, 28
|
||||
#######################################
|
||||
|
||||
sub s6,s6,t1
|
||||
beq s6,zero, exit
|
||||
j zmd_loop
|
||||
|
||||
exit:
|
||||
|
||||
add t0,zero,zero
|
||||
xori t0,t0,-1 #test nor ori
|
||||
slli t0,t0,16
|
||||
ori t0,t0,-1
|
||||
|
||||
add a0,zero,t0 # display t0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
j START # loop forever
|
||||
|
||||
|
||||
#InteruptProgram1:
|
||||
#############################################################################################
|
||||
# exceptoin 1
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram1:
|
||||
sw t0, 0(sp) #保护t0现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# DIVU测试
|
||||
addi t0,zero,2 # /2
|
||||
addi s1,zero, 0x11
|
||||
slli s1,s1,8
|
||||
addi s1,s1,0x11
|
||||
slli s1,s1,16
|
||||
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t3,zero,28 #ѭ������
|
||||
|
||||
divu_branch:
|
||||
divu s1,s1,t0 #����ָ��
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,divu_branch #ѭ��
|
||||
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,1 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop1:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift1:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift1
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop1
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t0, 0(sp) #恢复t0现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram2:
|
||||
#############################################################################################
|
||||
# exceptoin 2
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram2:
|
||||
sw t1, 0(sp) #保护t1现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw s2, 0(sp) #保护s2现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# BLT测试
|
||||
addi s1,zero,-15 #初始值
|
||||
blt_branch:
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi s1,s1,1
|
||||
blt s1,zero,blt_branch #当前指令
|
||||
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
################# 中断服务
|
||||
|
||||
addi s6,zero,2 #中断?1,2,3 不同中断号显示值不一样
|
||||
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop2:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift2:
|
||||
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift2
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop2
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp) #恢复s0现场
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s2, 0(sp) #恢复s2现场
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t1, 0(sp) #恢复t1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram3:
|
||||
#############################################################################################
|
||||
# exceptoin 3
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram3:
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# SB测试
|
||||
# 注:ROM 镜像里下面这段的 t1/t3/s2 已改成 ISR3 自己保存的 s4/s5/s6
|
||||
# (t1 当地址 -> s4,t3 当计数 -> s5,s2 当增量 -> s6),避免抢占 ISR1
|
||||
# 时破坏 ISR1 的 DIVU 循环计数,详见文件头部第 5 项。
|
||||
addi t1,zero,0 #init_addr
|
||||
addi t3,zero,32 #counter
|
||||
|
||||
#sb写入 01,02,03,04
|
||||
addi s1,zero, 0x00 #
|
||||
addi s2,zero, 0x01 #
|
||||
|
||||
sb_store:
|
||||
sb s1,(t1)
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
add s1,s1,s2 #data +1
|
||||
addi t1,t1,1 # addr ++
|
||||
addi t3,t3,-1 #counter
|
||||
bne t3,zero,sb_store
|
||||
|
||||
addi t3,zero,8
|
||||
addi t1,zero,0 # addr
|
||||
sb_branch:
|
||||
lw s1,(t1) #读出数据
|
||||
add a0,zero,s1
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
addi t1,t1,4
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,sb_branch
|
||||
|
||||
nop # 原为 addi a7,zero,10 + ecall:这颗 CPU 会停机,改成空操作
|
||||
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,3 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop3:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift3:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
jal ra, DISP_DLY # 显示 a0(写 LED 寄存器 0x1000)并延时约 10 ms
|
||||
nop # 占位:保持每个显示点仍是 2 个字,指令地址不变
|
||||
|
||||
bne s0, zero, IntLeftShift3
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop3
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DISP_DLY —— 显示 + 延时(对应 ROM 末尾 0x438 起的 8 条指令;11 个显示点都用
|
||||
# "jal ra, DISP_DLY" 调用它)
|
||||
#
|
||||
# sw ra, 256(sp) # 保存返回地址(故意存在 sp+256,见下面的说明)
|
||||
# lui a7, 1 # a7 = 0x1000 = LED_ADDR
|
||||
# sw a0, 0(a7) # 把 a0 写进 LED 寄存器(数码管显示它的 8 位十六进制)
|
||||
# lui t2, 0xFFFC0 # 计数初值为负(-262144),向上数到 0 ≈ 10 ms @50 MHz
|
||||
# 1:addi t2, t2, 1
|
||||
# blt t2, zero, 1b
|
||||
# lw ra, 256(sp)
|
||||
# jalr zero, ra, 0
|
||||
#
|
||||
# 为什么返回地址存在 sp+256,而不是按常规压在 sp-8 的栈帧里:
|
||||
# 本程序的 ISR 用的是"先把 sp 加上去、再往里存"的向上增长栈,一层最多 48 字节,
|
||||
# 最多嵌套 4 层(IRQ_STACK_DEPTH=4)共 192 字节 —— 也就是说 [sp, sp+192) 是中断
|
||||
# 随时可能写到的区域。而显示调用既可能被中断打断,也可能从 ISR 内部发出,所以:
|
||||
# * 若压在 sp-8(常规做法),从 ISR 里调用时会正好盖掉 ISR 存好的 s6 / uepc;
|
||||
# uepc 一坏,uret 就返回到 ISR 内部地址,最后执行到 ISR 尾部的 csrrci 把 MIE
|
||||
# 永久清零,之后再也不响应中断(实测就是这个现象);
|
||||
# * 放在 sp+256 越过了中断可能压栈的 192 字节区域,嵌套中断打断延时也不会破坏
|
||||
# 它;每次调用各用自己的 sp+256,嵌套调用互不影响。
|
||||
#
|
||||
# 延时计数用"负数向上数到 0"而不是"递减到 0":万一 t2 被嵌套调用改掉,循环只会
|
||||
# 在 t2 >= 0 时立即退出,不会像递减写法那样回绕成 40 亿次而死等。
|
||||
# -----------------------------------------------------------------------------
|
||||
DISP_DLY:
|
||||
sw ra, 256(sp)
|
||||
lui a7, 1
|
||||
sw a0, 0(a7)
|
||||
lui t2, 0xFFFC0
|
||||
DISP_DLY_WAIT:
|
||||
addi t2, t2, 1
|
||||
blt t2, zero, DISP_DLY_WAIT
|
||||
lw ra, 256(sp)
|
||||
jalr zero, ra, 0
|
||||
+22
-22
@@ -12,8 +12,8 @@
|
||||
00499993
|
||||
0089e9b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
406282b3
|
||||
fe0294e3
|
||||
00140413
|
||||
@@ -25,8 +25,8 @@ fe0294e3
|
||||
0049d993
|
||||
0089e9b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
406282b3
|
||||
fe0294e3
|
||||
01c45413
|
||||
@@ -38,8 +38,8 @@ fff2c293
|
||||
01029293
|
||||
fff2e293
|
||||
00500533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
f5dff06f
|
||||
00512023
|
||||
00410113
|
||||
@@ -71,13 +71,13 @@ f5dff06f
|
||||
01148493
|
||||
01049493
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
01c00e13
|
||||
0254d4b3
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fffe0e13
|
||||
fe0e16e3
|
||||
00a00893
|
||||
@@ -89,8 +89,8 @@ fe0e16e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -147,8 +147,8 @@ ffc10113
|
||||
0040e073
|
||||
ff100493
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
00148493
|
||||
fe04c8e3
|
||||
00a00893
|
||||
@@ -160,8 +160,8 @@ fe04c8e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -218,8 +218,8 @@ ffc10113
|
||||
00100913
|
||||
00930023
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
012484b3
|
||||
00130313
|
||||
fffe0e13
|
||||
@@ -228,8 +228,8 @@ fe0e12e3
|
||||
00000313
|
||||
00032483
|
||||
00900533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
00430313
|
||||
fffe0e13
|
||||
fe0e14e3
|
||||
@@ -242,8 +242,8 @@ fe0e14e3
|
||||
00441413
|
||||
014469b3
|
||||
01300533
|
||||
02200893
|
||||
00000073
|
||||
000018B7
|
||||
00A8A023
|
||||
fe0416e3
|
||||
415a0a33
|
||||
fe0a10e3
|
||||
@@ -1,421 +0,0 @@
|
||||
.text
|
||||
addi sp, zero, 2000 #内存堆栈初始化
|
||||
|
||||
#############################################################
|
||||
#走马灯测?,测试addi,andi,slli,srli,srai,or,ori,nor,ecall LED按走马灯方式来回显示数据
|
||||
#############################################################
|
||||
|
||||
.text
|
||||
START:
|
||||
|
||||
addi s0,zero,1
|
||||
slli s3, s0, 31 # s3=0x80000000
|
||||
srai s3, s3, 31 # s3=0xFFFFFFFF
|
||||
add s0,zero,zero # s0=0
|
||||
addi s2,zero,12
|
||||
|
||||
addi s6,zero,8 #走马灯计数
|
||||
zmd_loop:
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
andi s0, s0, 15
|
||||
|
||||
#######################################
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
left:
|
||||
|
||||
slli s3, s3, 4 #走马灯左移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,left
|
||||
#######################################
|
||||
|
||||
addi s0, s0, 1 #计算下一个走马灯的数
|
||||
addi t6,zero,15
|
||||
and s0, s0, t6
|
||||
slli s0, s0, 28
|
||||
|
||||
addi t0,zero,8
|
||||
addi t1,zero,1
|
||||
|
||||
zmd_right:
|
||||
|
||||
srli s3, s3, 4 #走马灯右移
|
||||
or s3, s3, s0
|
||||
|
||||
add a0,zero,s3 # display s3
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
sub t0,t0,t1
|
||||
bne t0,zero,zmd_right
|
||||
srli s0, s0, 28
|
||||
#######################################
|
||||
|
||||
sub s6,s6,t1
|
||||
beq s6,zero, exit
|
||||
j zmd_loop
|
||||
|
||||
exit:
|
||||
|
||||
add t0,zero,zero
|
||||
xori t0,t0,-1 #test nor ori
|
||||
slli t0,t0,16
|
||||
ori t0,t0,-1
|
||||
|
||||
add a0,zero,t0 # display t0
|
||||
addi a7,zero,34 # system call for LED display
|
||||
ecall # display
|
||||
|
||||
j START # loop forever
|
||||
|
||||
|
||||
#InteruptProgram1:
|
||||
#############################################################################################
|
||||
# exceptoin 1
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram1:
|
||||
sw t0, 0(sp) #保护t0现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# DIVU测试
|
||||
addi t0,zero,2 # /2
|
||||
addi s1,zero, 0x11
|
||||
slli s1,s1,8
|
||||
addi s1,s1,0x11
|
||||
slli s1,s1,16
|
||||
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall
|
||||
addi t3,zero,28 #ѭ������
|
||||
|
||||
divu_branch:
|
||||
divu s1,s1,t0 #����ָ��
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall #�����ǰֵ
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,divu_branch #ѭ��
|
||||
|
||||
|
||||
addi a7,zero,10
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,1 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop1:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift1:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift1
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop1
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t0, 0(sp) #恢复t0现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram2:
|
||||
#############################################################################################
|
||||
# exceptoin 2
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram2:
|
||||
sw t1, 0(sp) #保护t1现场
|
||||
addi sp, sp, 4
|
||||
sw t3, 0(sp) #保护t3现场
|
||||
addi sp, sp, 4
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw s2, 0(sp) #保护s2现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# BLT测试
|
||||
addi s1,zero,-15 #初始值
|
||||
blt_branch:
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34
|
||||
ecall #输出当前值
|
||||
addi s1,s1,1
|
||||
blt s1,zero,blt_branch #当前指令
|
||||
|
||||
|
||||
addi a7,zero,10
|
||||
|
||||
################# 中断服务
|
||||
|
||||
addi s6,zero,2 #中断?1,2,3 不同中断号显示值不一样
|
||||
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop2:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift2:
|
||||
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift2
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop2
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp) #恢复s0现场
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s2, 0(sp) #恢复s2现场
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
addi sp, sp, -4
|
||||
lw t3, 0(sp) #恢复t3现场
|
||||
addi sp, sp, -4
|
||||
lw t1, 0(sp) #恢复t1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
|
||||
#InteruptProgram3:
|
||||
#############################################################################################
|
||||
# exceptoin 3
|
||||
# 使用?s6? s5?s4?s3?s0?a0?a7
|
||||
#############################################################################################
|
||||
|
||||
|
||||
################# 保护现场
|
||||
InteruptProgram3:
|
||||
sw s1, 0(sp) #保护s1现场
|
||||
addi sp, sp, 4
|
||||
sw a7, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw a0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s0, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s3, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s4, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s5, 0(sp)
|
||||
addi sp, sp, 4
|
||||
sw s6, 0(sp)
|
||||
addi sp, sp, 4
|
||||
|
||||
csrrsi s6,0x41,0 #s6=uepc
|
||||
sw s6, 0(sp) #uepc压栈保护
|
||||
addi sp, sp, 4
|
||||
|
||||
|
||||
################# 开中断
|
||||
csrrsi zero,0x4,1 #ustatus.MIE=1
|
||||
|
||||
################# SB测试
|
||||
addi t1,zero,0 #init_addr
|
||||
addi t3,zero,32 #counter
|
||||
|
||||
#sb写入 01,02,03,04
|
||||
addi s1,zero, 0x00 #
|
||||
addi s2,zero, 0x01 #
|
||||
|
||||
sb_store:
|
||||
sb s1,(t1)
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34 # system call for print
|
||||
ecall # print
|
||||
|
||||
add s1,s1,s2 #data +1
|
||||
addi t1,t1,1 # addr ++
|
||||
addi t3,t3,-1 #counter
|
||||
bne t3,zero,sb_store
|
||||
|
||||
addi t3,zero,8
|
||||
addi t1,zero,0 # addr
|
||||
sb_branch:
|
||||
lw s1,(t1) #读出数据
|
||||
add a0,zero,s1
|
||||
addi a7,zero,34 # system call for print
|
||||
ecall # print
|
||||
addi t1,t1,4
|
||||
addi t3,t3, -1
|
||||
bne t3,zero,sb_branch
|
||||
|
||||
addi a7,zero,10 # system call for exit
|
||||
|
||||
|
||||
################# 中断服务
|
||||
addi s6,zero,3 #中断?1,2,3 不同中断号显示值不一样
|
||||
addi s4,zero,3 #循环次数初始值
|
||||
addi s5,zero,1 #计数器累加值
|
||||
|
||||
IntLoop3:
|
||||
add s0,zero,s6
|
||||
|
||||
IntLeftShift3:
|
||||
|
||||
slli s0, s0, 4
|
||||
or s3,s0,s4
|
||||
add a0,zero,s3 #display s0
|
||||
addi a7,zero,34 # display hex
|
||||
ecall
|
||||
|
||||
bne s0, zero, IntLeftShift3
|
||||
sub s4,s4,s5 #循环次数递减
|
||||
bne s4, zero, IntLoop3
|
||||
|
||||
################# 关中断
|
||||
csrrci zero,0x4,1 #ustatus.MIE=0
|
||||
|
||||
|
||||
|
||||
################# 恢复现场
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
csrrw zero,0x41,s6 #uepc=s6
|
||||
|
||||
addi sp, sp, -4
|
||||
lw s6, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s5, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s4, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s3, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a0, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw a7, 0(sp)
|
||||
addi sp, sp, -4
|
||||
lw s1, 0(sp) #恢复s1现场
|
||||
|
||||
################ 中断返回
|
||||
uret #同步开中断,uepc-->pc
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
|
||||
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
|
||||
//
|
||||
// clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU)
|
||||
// clk100 (E3) -> CPU 时钟(100 MHz,经 CPU_DIV 分频后给 CPU;默认 50 MHz,
|
||||
// 跑中断综合测试程序时用更慢的 CLK_DIV_INTRUPT,
|
||||
// 让数码管上每个数值停留更久,便于观察)
|
||||
// rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器
|
||||
// go (N17) -> 按键 BTNC:ecall 停机后按下继续运行
|
||||
// irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源
|
||||
// led[15:0] -> 板载 LED[15:0]:显示 MMIO(LED_ADDR)写入值的低 16 位
|
||||
// led[15:0] -> 板载 LED[15:0]:LED[2:0] = 三路中断指示,从该路中断
|
||||
// 请求出现开始点亮,一直到它的 ISR 执行 uret 返回后才熄灭;
|
||||
// LED[15:3] 熄灭
|
||||
// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察
|
||||
// an/seg/dp -> 板载 8 位七段数码管:显示程序写入 LED_ADDR 的 32 位
|
||||
// 十六进制数据(每 4 位对应一个数码管)
|
||||
//
|
||||
// 片内存储器:
|
||||
// ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出
|
||||
@@ -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
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,11 @@ create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { c
|
||||
create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
|
||||
-divide_by 2 [get_pins { clk_div_reg/Q }]
|
||||
|
||||
## 这里固定按最坏情况 50 MHz(-divide_by 2)约束。跑中断综合测试程序时 RTL 里的
|
||||
## CPU_DIV 会变成 8(12.5 MHz,见 cpu21_board_top.v 的 CLK_DIV_INTRUPT),实际
|
||||
## 时钟比这里的约束更慢,因此仍然满足——约束偏严,不会掩盖时序违例。
|
||||
## (真要改分频比时,两处都要改;把这里的 -divide_by 调大反而会放松约束。)
|
||||
|
||||
## 复位按键(CPU_RESETN,低有效)与"继续运行"按键(BTNC)
|
||||
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rst_n }]
|
||||
set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { go }]
|
||||
@@ -22,7 +27,8 @@ set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { irq_sw
|
||||
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[1] }]
|
||||
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[2] }]
|
||||
|
||||
## 16 位 LED:显示 MMIO 写入值的低 16 位
|
||||
## 16 位 LED:LED[2:0] = 三路中断指示(请求出现即亮,ISR 执行 uret 返回后灭),
|
||||
## LED[15:3] 熄灭
|
||||
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]
|
||||
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]
|
||||
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]
|
||||
@@ -40,7 +46,7 @@ set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13
|
||||
set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }]
|
||||
set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }]
|
||||
|
||||
## LED16_B:MMIO 写入指示(拉长约 0.34 s @50 MHz)
|
||||
## LED16_B:MMIO 写入指示(约 0.34 s @50 MHz;中断程序的 12.5 MHz 下约 1.36 s)
|
||||
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }]
|
||||
|
||||
## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G,
|
||||
|
||||
@@ -8,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
|
||||
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env python3
|
||||
"""把中断测试程序适配到这块 CPU 上板运行(只改 ROM 内容,不改 CPU 语义)。
|
||||
|
||||
背景
|
||||
----
|
||||
programs/risc-v-intrupt-ccab.hex 是课程的中断/异常测试程序(270 字)。
|
||||
直接放进板上 ROM 会有两个问题:
|
||||
|
||||
1) ISR 里的 ecall 会停机
|
||||
三个中断服务程序里各有一条 "addi a7,zero,10" + "ecall"。这颗 CPU 的约定是
|
||||
a7==34 的 ecall 是历史"显示"调用(无操作),其余 a7 值一律让流水线停机并等待
|
||||
go_i。于是 ISR 跑到这里就卡住:后面的中断服务显示循环永远不执行,主程序的
|
||||
跑马灯也停在原地。
|
||||
-> 把 "addi a7,zero,10" + "ecall" 换成两条 NOP。
|
||||
|
||||
2) 显示刷新太快,数码管上看不出跑马灯
|
||||
原来的显示是 "lui a7,1" + "sw a0,0(a7)"(写 MMIO 的 LED 寄存器)。主循环里
|
||||
两次显示只隔几拍(50 MHz 下约 0.1 us),而数码管是按位动态扫描的,根本来不及
|
||||
分辨,只会看到一片亮。
|
||||
-> 把每条显示换成 "jal ra,DISP_DLY" + "nop",由追加在 ROM 末尾的 DISP_DLY
|
||||
完成"写 LED 寄存器 + 约 10 ms 延时"。
|
||||
(注意:早先把旧的 "addi a7,zero,34 + ecall" 换成 MMIO 存储时,rs1 域算错,
|
||||
把 sw 写成了 sb —— 只更新 LED 寄存器的低字节,高位一直是旧值,显示自然是
|
||||
错的。DISP_DLY 内部统一用正确的 sw 写整个字,这个脚本会顺带纠正。)
|
||||
|
||||
3) **修正 ISR2 / ISR3 的现场恢复顺序**
|
||||
原始 hex 是手工改过的,两个 ISR 的出栈顺序被写乱(ISR1 是对的):
|
||||
* ISR3:把 s1 从 s3 的槽位弹出,于是 s1 被写成 s3 保存的值 0xFFFFF111;
|
||||
ISR3 返回后,正在跑 BLT 测试的 ISR2 的循环计数 s1 就变成了 -3823,
|
||||
循环要跑 3800 多次(约 170 万周期)才退出 —— 看起来就是“二级中断返回
|
||||
之后卡住”;
|
||||
* ISR2:s0/s2/a0/a7/s1/t3/t1 全部错位,返回后调用者的这些寄存器被破坏。
|
||||
-> 按 asm 清单里正确的 LIFO 顺序,只改 12 条 `lw` 的目标寄存器(字数不变、
|
||||
地址不动),ISR3 改 5 条、ISR2 改 7 条。
|
||||
|
||||
5) **修正 ISR3 的 SB 测试用到的未保存寄存器**
|
||||
ISR3 的现场只保存了 s1/a7/a0/s0/s3/s4/s5/s6/uepc,但它的 SB 测试却用
|
||||
t1(地址)、t3(计数)、s2(增量)—— 这三个都没保存。后果:
|
||||
* 若 IRQ3 抢占正在跑 DIVU 测试的 ISR1(ISR1 用 t3 做递减计数),ISR1 的
|
||||
计数会被 ISR3 改成 0;ISR1 恢复后 `addi t3,t3,-1` 得到 -1,`bne t3,zero`
|
||||
永远成立 —— ISR1 的 DIVU 循环再也出不来,表现为“三级中断之后卡住”;
|
||||
* 主程序的跑马灯步长 t1 也会被改掉。
|
||||
-> 改用 ISR3 自己保存的 s4/s5/s6:SB 测试结束后紧接着的 IntLoop3
|
||||
(0x3B4 起)会重新给它们赋值,所以这样换是安全的;同样只改寄存器域,
|
||||
字数与地址不变。
|
||||
|
||||
6) **ISR 不能破坏被打断代码的 ra**
|
||||
三个 ISR 的现场里都保存了 s1/a7/a0/s0/s3/s4/s5/s6(外加各自的测试寄存器),
|
||||
但**都没有保存 ra**,而它们要调用显示例程,`jal ra,DISP_DLY` 会把 ra 写成
|
||||
ISR 内部的返回地址。后果:如果中断恰好落在被打断代码依赖 ra 的位置
|
||||
—— 典型就是主程序正停在 DISP_DLY 末尾的 `jalr zero,0(ra)`(第 0x454 字)
|
||||
—— 中断返回后 `jalr` 会跳到 ra 里的地址,也就是 ISR 内部的某条指令上,
|
||||
于是一路执行到 ISR 结尾的 `csrrci zero,0x4,1`,把 MIE 永久清零:
|
||||
“跑一阵子之后拨开关再也不响应,只能按复位”。窗口只有 1~2 拍,所以平时
|
||||
很难遇到(仿真里把延时改到极小才会碰上)。
|
||||
-> 给 ISR 另加一份显示例程 DISP_DLY_ISR(0x458),链接寄存器改用 **a0**,
|
||||
显示值改走 **a7**:这两个寄存器三个 ISR 都保存/恢复,所以
|
||||
* ISR 完全不碰 ra,被打断的代码停在 DISP_DLY 哪里都安全;
|
||||
* ISR 自己被内层中断打断时,内层 ISR 返回也会把 a0/a7 恢复成被打断时
|
||||
的值,所以 DISP_DLY_ISR 自己的 `jalr zero,0(a0)` 同样安全。
|
||||
主程序的 3 个显示点仍用原来的 DISP_DLY(链接在 ra,行为不变)。
|
||||
每个 ISR 显示点:`jal ra,DISP_DLY` + `nop` 换成
|
||||
`add a7,a0,zero` + `jal a0,DISP_DLY_ISR` —— 仍是 2 字换 2 字。
|
||||
|
||||
以上替换都是 **2 字换 2 字**,两个例程又追加在最后一条指令(uret)之后,所以
|
||||
**所有指令地址都保持不变**,三个中断入口仍然是 0x0AC / 0x1E4 / 0x308。
|
||||
|
||||
DISP_DLY(8 字,追加在程序末尾)
|
||||
--------------------------------
|
||||
sw ra, 256(sp) # 保存返回地址
|
||||
lui a7, 1 # a7 = 0x1000 = LED_ADDR
|
||||
sw a0, 0(a7) # 把 a0 写进 LED 寄存器(数码管显示它的 8 位十六进制)
|
||||
lui t2, DELAY # 计数初值为负,向上数到 0(约 10 ms @50 MHz)
|
||||
1: addi t2, t2, 1
|
||||
blt t2, zero, 1b
|
||||
lw ra, 256(sp)
|
||||
jalr zero, ra, 0
|
||||
|
||||
为什么返回地址存在 sp+256 而不是程序栈里:
|
||||
本程序的 ISR 是“先把 sp 加上去,再往里存”的向上增长栈,一层最多占 48 字节,
|
||||
最多嵌套 4 层(IRQ_STACK_DEPTH=4)共 192 字节,所以 [sp, sp+192) 是中断随时
|
||||
可能写到的区域。把返回地址放在 sp+256(越过了这个区域):
|
||||
* 从 ISR 里调用时,不会覆盖 ISR 存好的 s6 / uepc(早期用 sp-8 就是这么坏掉的:
|
||||
uepc 被写坏后 uret 会返回到 ISR 内部地址,最后执行到 csrrci 把 MIE 永久清零);
|
||||
* 中断(包括多层嵌套)随时打断延时也不会破坏它,因为嵌套 ISR 只会在
|
||||
[sp, sp+192) 里压栈;
|
||||
* 每次调用各用自己 sp+256 的位置,所以嵌套调用也互不影响。
|
||||
延时计数不用“递减到 0”而用“负数向上数到 0”:万一被嵌套调用改掉了 t2,循环也
|
||||
只会在 t2 >= 0 时立即退出,不会像递减写法那样绕成 40 亿次而死等。
|
||||
|
||||
用法
|
||||
----
|
||||
python tools/adapt_intrupt_ccab.py <输入.hex> <输出.vh> [输出_fast.hex]
|
||||
|
||||
<输入.hex> programs/risc-v-intrupt-ccab.hex(未适配的原始镜像)
|
||||
<输出.vh> srcs/board/risc-v-intrupt-ccab.vh(上板综合用)
|
||||
[输出_fast.hex] 可选:延时改成约 0.6 us 的仿真专用版本。上板一次中断服务要跑
|
||||
几十毫秒(几十万个时钟),仿真跑完整流程会很慢,因此仿真用这个
|
||||
快版来验证控制流(入口 / 中断返回 / 不停机),延时量本身不影响
|
||||
控制流。
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
NOP = 0x00000013
|
||||
|
||||
LED_ADDR_HI = 1 # lui a7, 1 -> a7 = 0x1000(LED_ADDR)
|
||||
# 延时计数:计数初值为负,向上数到 0。0xFFFC0000 -> 0x40000 = 262144 次
|
||||
# (每轮约 2 个时钟)= 52.4 万个时钟 ≈ 10 ms @50 MHz。
|
||||
DELAY_NEG_HI = 0xFFFC0
|
||||
DELAY_FAST_NEG = -16 # 仿真用:addi t2, zero, -16 ≈ 0.6 us
|
||||
|
||||
# DISP_DLY 保存返回地址的偏移:必须大于 ISR 最大嵌套栈用量(4 层 x 48B = 192B)
|
||||
LINK_SLOT = 256
|
||||
|
||||
# 寄存器号
|
||||
ZERO, RA, SP, GP, T2, A0, A7 = 0, 1, 2, 3, 7, 10, 17
|
||||
|
||||
|
||||
def enc_i(imm, rs1, f3, rd, op=0x13):
|
||||
return ((imm & 0xFFF) << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | op
|
||||
|
||||
|
||||
def enc_s(imm, rs2, rs1, f3):
|
||||
imm &= 0xFFF
|
||||
return (((imm >> 5) << 25) | (rs2 << 20) | (rs1 << 15) | (f3 << 12)
|
||||
| ((imm & 0x1F) << 7) | 0x23)
|
||||
|
||||
|
||||
def enc_b(imm, rs2, rs1, f3):
|
||||
imm &= 0x1FFF
|
||||
return ((((imm >> 12) & 1) << 31) | (((imm >> 5) & 0x3F) << 25)
|
||||
| (rs2 << 20) | (rs1 << 15) | (f3 << 12)
|
||||
| (((imm >> 1) & 0xF) << 8) | (((imm >> 11) & 1) << 7) | 0x63)
|
||||
|
||||
|
||||
def enc_u(imm, rd):
|
||||
return ((imm & 0xFFFFF) << 12) | (rd << 7) | 0x37
|
||||
|
||||
|
||||
def enc_j(imm, rd):
|
||||
imm &= 0x1FFFFF
|
||||
return ((((imm >> 20) & 1) << 31) | (((imm >> 1) & 0x3FF) << 21)
|
||||
| (((imm >> 11) & 1) << 20) | (((imm >> 12) & 0xFF) << 12)
|
||||
| (rd << 7) | 0x6F)
|
||||
|
||||
|
||||
HALT_LOAD = enc_i(10, ZERO, 0, A7) # addi a7, zero, 10
|
||||
ECALL = 0x00000073
|
||||
DISP_LUI = enc_u(LED_ADDR_HI, A7) # lui a7, 1
|
||||
DISP_SW = enc_s(0, A0, A7, 2) # sw a0, 0(a7) <- 正确:字写
|
||||
DISP_SB_BAD = enc_s(0, A0, A7, 0) # sb a0, 0(a7) <- 误写成字节写,一并修正
|
||||
# 说明:这两种都当成"显示点"。DISP_DLY 内部一律用正确的 sw 写整个字,因此即使
|
||||
# 输入镜像里是 sb(只写低字节,显示的高位会残留旧值),适配后也会恢复正常。
|
||||
|
||||
|
||||
def disp_dly(delay_word):
|
||||
"""DISP_DLY 的 8 条指令(delay_word 决定延时长短)。
|
||||
|
||||
返回地址存在 sp+LINK_SLOT,位于 ISR 可能压栈的区域之上,所以中断(含嵌套)
|
||||
打断延时也不会破坏它 —— 见文件头的说明。
|
||||
"""
|
||||
return [
|
||||
enc_s(LINK_SLOT, RA, SP, 2), # sw ra, 256(sp)
|
||||
DISP_LUI, # lui a7, 1
|
||||
DISP_SW, # sw a0, 0(a7)
|
||||
delay_word, # lui t2, 0xFFFC0 / addi t2, zero, -16
|
||||
enc_i(1, T2, 0, T2), # addi t2, t2, 1
|
||||
enc_b(-4, ZERO, T2, 4), # blt t2, zero, -4
|
||||
enc_i(LINK_SLOT, SP, 2, RA, 0x03), # lw ra, 256(sp)
|
||||
enc_i(0, RA, 0, ZERO, 0x67), # jalr zero, ra, 0
|
||||
]
|
||||
|
||||
|
||||
def disp_dly_isr(delay_word):
|
||||
"""ISR 专用的显示/延时例程(8 条)—— 链接寄存器用 a0,不用 ra。
|
||||
|
||||
三个 ISR 的现场都保存了 a7 与 a0(s1/a7/a0/s0/s3/s4/s5/s6),所以把链接放
|
||||
a0、把显示值放 a7 不会额外破坏任何东西;而 ra 完全不被触碰,被打断的代码
|
||||
即使停在主 DISP_DLY 的 `jalr zero,0(ra)` 上也不会被破坏。
|
||||
"""
|
||||
return [
|
||||
enc_s(LINK_SLOT, A0, SP, 2), # sw a0, 256(sp) 保存链接寄存器
|
||||
enc_u(LED_ADDR_HI, A0), # lui a0, 1 a0 = 0x1000
|
||||
enc_s(0, A7, A0, 2), # sw a7, 0(a0) 显示 a7
|
||||
delay_word, # lui t2, 0xFFFC0 / addi t2,zero,-16
|
||||
enc_i(1, T2, 0, T2), # addi t2, t2, 1
|
||||
enc_b(-4, ZERO, T2, 4), # blt t2, zero, -4
|
||||
enc_i(LINK_SLOT, SP, 2, A0, 0x03), # lw a0, 256(sp)
|
||||
enc_i(0, A0, 0, ZERO, 0x67), # jalr zero, a0, 0
|
||||
]
|
||||
|
||||
|
||||
# 三个中断入口的原指令(核对地址没有跑偏)
|
||||
ISR_ENTRY = {43: 0x00512023, 121: 0x00612023, 194: 0x00912023}
|
||||
# 三个 ISR 的代码从 ISR1 入口开始一直排到程序末尾(主程序在它之前)
|
||||
ISR1_WORD = min(ISR_ENTRY)
|
||||
|
||||
# 需要修正目标寄存器的 `lw X,0(sp)`:addr -> (当前错误的 rd, 正确的 rd)
|
||||
# ISR3(入口 0x308)恢复顺序应为:uepc,s6,s5,s4,s3,s0,a0,a7,s1
|
||||
# ISR2(入口 0x1E4)恢复顺序应为:uepc,s6,s5,s4,s3,s0,a0,a7,s2,s1,t3,t1
|
||||
FIX_POPS = {
|
||||
0x410: (9, 19), 0x418: (19, 8), 0x420: (8, 10),
|
||||
0x428: (10, 17), 0x430: (17, 9), # ISR3
|
||||
0x2D0: (18, 8), 0x2D8: (9, 10), 0x2E0: (28, 17),
|
||||
0x2E8: (6, 18), 0x2F0: (8, 9), 0x2F8: (10, 28),
|
||||
0x300: (17, 6), # ISR2
|
||||
}
|
||||
|
||||
# ISR3 的 SB 测试用了未保存的 t1/t3/s2 -> 改成 ISR3 自己保存的 s4/s5/s6
|
||||
# addr -> (要改的域, 原寄存器号, 新寄存器号)
|
||||
T1, T3, S2, S4, S5, S6 = 6, 28, 18, 20, 21, 22
|
||||
FIX_ISR3_SB = {
|
||||
0x358: ('rd', T1, S4), 0x35C: ('rd', T3, S5),
|
||||
0x364: ('rd', S2, S6), 0x368: ('rs1', T1, S4),
|
||||
0x378: ('rs2', S2, S6), 0x37C: ('rd_rs1', T1, S4),
|
||||
0x380: ('rd_rs1', T3, S5), 0x384: ('rs1', T3, S5),
|
||||
0x388: ('rd', T3, S5), 0x38C: ('rd', T1, S4),
|
||||
0x390: ('rs1', T1, S4), 0x3A0: ('rd_rs1', T1, S4),
|
||||
0x3A4: ('rd_rs1', T3, S5), 0x3A8: ('rs1', T3, S5),
|
||||
}
|
||||
|
||||
|
||||
def read_hex(path):
|
||||
words = []
|
||||
with open(path, encoding="ascii") as f:
|
||||
for line in f:
|
||||
line = line.split("//")[0].strip()
|
||||
if line:
|
||||
words.append(int(line, 16))
|
||||
return words
|
||||
|
||||
|
||||
def patch(words):
|
||||
"""返回 (适配后的字表, 适配后的快版字表, 统计信息)。"""
|
||||
halt_sites = [i for i in range(len(words) - 1)
|
||||
if words[i] == HALT_LOAD and words[i + 1] == ECALL]
|
||||
if len(halt_sites) != 3:
|
||||
raise SystemExit("预期 3 处 'addi a7,zero,10 + ecall'(ISR 停机点),"
|
||||
"实际找到 %d 处;输入文件不是预期的原始镜像?" % len(halt_sites))
|
||||
|
||||
disp_sites = [i for i in range(len(words) - 1)
|
||||
if words[i] == DISP_LUI
|
||||
and words[i + 1] in (DISP_SW, DISP_SB_BAD)]
|
||||
if len(disp_sites) != 11:
|
||||
raise SystemExit("预期 11 处 'lui a7,1 + sw/sb a0,0(a7)'(显示点),"
|
||||
"实际找到 %d 处;输入文件不是预期的原始镜像?" % len(disp_sites))
|
||||
|
||||
bad_store = [i for i in disp_sites if words[i + 1] == DISP_SB_BAD]
|
||||
|
||||
for i in halt_sites:
|
||||
words[i] = NOP
|
||||
words[i + 1] = NOP
|
||||
|
||||
# plain 变体:只去掉停机 ecall,显示保持原来的 "lui + sw" 两字写法
|
||||
# (必须在把显示点改成 jal 之前取副本,否则会生成“跳到没有函数体的地址”的镜像)
|
||||
plain = list(words)
|
||||
|
||||
# DISP_DLY 紧跟在原程序的最后一条指令之后(原程序长度不变)
|
||||
disp_pc = 4 * len(words)
|
||||
isr_pc = disp_pc + 32 # ISR 专用的 DISP_DLY_ISR 再占 8 字
|
||||
isr_sites = []
|
||||
for i in disp_sites:
|
||||
if i >= ISR1_WORD:
|
||||
# 三个 ISR 里的显示点:改用 a0 做链接寄存器、显示值走 a7
|
||||
# 注意 jal 落在显示点的后一条(i+1)上,偏移要相对它自己算
|
||||
isr_sites.append(i)
|
||||
words[i] = enc_i(0, A0, 0, A7) # add a7, a0, zero
|
||||
words[i + 1] = enc_j(isr_pc - 4 * (i + 1), A0) # jal a0, DISP_DLY_ISR
|
||||
else:
|
||||
# 主程序里的显示点:照旧用 ra 链接
|
||||
words[i] = enc_j(disp_pc - 4 * i, RA)
|
||||
words[i + 1] = NOP
|
||||
if not isr_sites:
|
||||
raise SystemExit("没有找到 ISR 里的显示点;中断入口地址对不上?")
|
||||
|
||||
# 自检:把刚写进去的 jal 偏移反解出来,确认真的跳到两个例程的入口
|
||||
def jal_target(w, pc):
|
||||
imm = (((w >> 31) & 1) << 20) | (((w >> 21) & 0x3FF) << 1) \
|
||||
| (((w >> 20) & 1) << 11) | (((w >> 12) & 0xFF) << 12)
|
||||
if imm & 0x100000:
|
||||
imm -= 0x200000
|
||||
return pc + imm
|
||||
|
||||
for i in disp_sites:
|
||||
if i >= ISR1_WORD:
|
||||
want, at = isr_pc, 4 * (i + 1) # ISR:jal 在显示点后一条
|
||||
else:
|
||||
want, at = disp_pc, 4 * i # 主程序:jal 就在显示点上
|
||||
got = jal_target(words[at // 4], at)
|
||||
if got != want:
|
||||
raise SystemExit("显示点 0x%03X 的跳转目标算错了:0x%03X != 0x%03X"
|
||||
% (4 * i, got, want))
|
||||
|
||||
for idx, want in ISR_ENTRY.items():
|
||||
if words[idx] != want:
|
||||
raise SystemExit("中断入口 0x%03X 处的指令被改动了(%08X != %08X)"
|
||||
% (4 * idx, words[idx], want))
|
||||
|
||||
# --- 4) 修正 ISR2 / ISR3 的现场恢复顺序(只改 lw 的 rd 域) ---
|
||||
fixed_pops = 0
|
||||
for pc, (bad_rd, rd) in FIX_POPS.items():
|
||||
w = words[pc // 4]
|
||||
op, f3 = w & 0x7F, (w >> 12) & 7
|
||||
rs1, imm = (w >> 15) & 0x1F, (w >> 20) & 0xFFF
|
||||
old_rd = (w >> 7) & 0x1F
|
||||
if not (op == 0x03 and f3 == 2 and rs1 == SP and imm == 0):
|
||||
raise SystemExit("0x%03X 不是预期的 `lw X,0(sp)`(%08X)" % (pc, w))
|
||||
if old_rd == rd:
|
||||
continue # 已经是对的(重复运行时跳过)
|
||||
if old_rd != bad_rd:
|
||||
raise SystemExit("0x%03X 的目标寄存器不是预期的 %d(实际 %d)"
|
||||
% (pc, bad_rd, old_rd))
|
||||
words[pc // 4] = (w & ~(0x1F << 7)) | (rd << 7)
|
||||
fixed_pops += 1
|
||||
|
||||
# --- 5) 把 ISR3 SB 测试里的 t1/t3/s2 换成 s4/s5/s6 ---
|
||||
def set_field(w, name, val):
|
||||
if name == 'rd':
|
||||
return (w & ~(0x1F << 7)) | (val << 7)
|
||||
if name == 'rs1':
|
||||
return (w & ~(0x1F << 15)) | (val << 15)
|
||||
if name == 'rs2':
|
||||
return (w & ~(0x1F << 20)) | (val << 20)
|
||||
if name == 'rd_rs1':
|
||||
return ((w & ~(0x1F << 7)) | (val << 7)) & ~(0x1F << 15) | (val << 15)
|
||||
raise SystemExit('未知域 ' + name)
|
||||
|
||||
fixed_sb = 0
|
||||
for pc, (field, old, new) in FIX_ISR3_SB.items():
|
||||
w = words[pc // 4]
|
||||
idx = {'rd': (w >> 7) & 0x1F, 'rs1': (w >> 15) & 0x1F,
|
||||
'rs2': (w >> 20) & 0x1F}
|
||||
if field == 'rd_rs1':
|
||||
cur = idx['rd']
|
||||
if idx['rs1'] != cur:
|
||||
raise SystemExit('0x%03X 的 rd/rs1 不一致' % pc)
|
||||
else:
|
||||
cur = idx[field]
|
||||
if cur == new:
|
||||
continue # 已经改过了
|
||||
if cur != old:
|
||||
raise SystemExit('0x%03X 的 %s 不是预期的 %d(实际 %d)'
|
||||
% (pc, field, old, cur))
|
||||
words[pc // 4] = set_field(w, field, new)
|
||||
fixed_sb += 1
|
||||
|
||||
slow = disp_dly(enc_u(DELAY_NEG_HI, T2)) + disp_dly_isr(enc_u(DELAY_NEG_HI, T2))
|
||||
fast = disp_dly(enc_i(DELAY_FAST_NEG, ZERO, 0, T2)) \
|
||||
+ disp_dly_isr(enc_i(DELAY_FAST_NEG, ZERO, 0, T2))
|
||||
return (words + slow, words + fast, plain,
|
||||
(halt_sites, disp_sites, disp_pc,
|
||||
[i for i in disp_sites if i < ISR1_WORD], isr_sites,
|
||||
bad_store, fixed_pops, fixed_sb))
|
||||
|
||||
|
||||
def write_hex(path, words):
|
||||
with open(path, "w", encoding="ascii", newline="\n") as f:
|
||||
for w in words:
|
||||
f.write("%08X\n" % w)
|
||||
|
||||
|
||||
def write_vh(path, words, src_name, total, note):
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(" // 由 tools/adapt_intrupt_ccab.py 生成 —— 不要手改\n")
|
||||
f.write(" // source: %s(%d 字,其余地址填 NOP)\n"
|
||||
% (src_name, len(words)))
|
||||
f.write(" // %s\n" % note)
|
||||
for addr in range(total):
|
||||
w = words[addr] if addr < len(words) else NOP
|
||||
f.write(" rom[%d] = 32'h%08X;\n" % (addr, w))
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
raise SystemExit(__doc__.strip().split("用法")[-1])
|
||||
src, dst_vh = sys.argv[1], sys.argv[2]
|
||||
dst_fast = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
dst_plain = sys.argv[4] if len(sys.argv) > 4 else None
|
||||
|
||||
words = read_hex(src)
|
||||
slow, fast, plain, (halt_sites, disp_sites, disp_pc, main_sites, isr_sites,
|
||||
bad_store, fixed_pops, fixed_sb) = patch(words)
|
||||
|
||||
total = 1024
|
||||
write_vh(dst_vh, slow, src, total,
|
||||
"已适配:去掉 ISR 停机 ecall、显示改为 MMIO+DISP_DLY(约 10 ms)")
|
||||
if dst_fast:
|
||||
write_hex(dst_fast, fast)
|
||||
if dst_plain:
|
||||
write_hex(dst_plain, plain)
|
||||
|
||||
print("适配完成:%s(原程序 %d 字 + DISP_DLY %d 字 = %d 字)"
|
||||
% (dst_vh, len(words), len(slow) - len(words), len(slow)))
|
||||
print(" 去掉的停机 ecall 位置:%s"
|
||||
% ", ".join("0x%03X" % (4 * i) for i in halt_sites))
|
||||
print(" 换成显示调用的点:主程序 %s|ISR %s"
|
||||
% (", ".join("0x%03X" % (4 * i) for i in main_sites),
|
||||
", ".join("0x%03X" % (4 * i) for i in isr_sites)))
|
||||
print(" DISP_DLY 本体:0x%03X - 0x%03X(主程序用,链接在 ra)"
|
||||
% (disp_pc, disp_pc + 28))
|
||||
print(" DISP_DLY_ISR 本体:0x%03X - 0x%03X(ISR 用,链接在 a0,不碰 ra)"
|
||||
% (disp_pc + 32, disp_pc + 60))
|
||||
print(" 修正的现场恢复顺序:%d 条" % fixed_pops)
|
||||
print(" ISR3 SB 测试改用的已保存寄存器:%d 条" % fixed_sb)
|
||||
if bad_store:
|
||||
print(" 顺带修正的字节写显示:%s"
|
||||
% ", ".join("0x%03X" % (4 * i) for i in bad_store))
|
||||
print(" 中断入口(不变):0x0AC / 0x1E4 / 0x308")
|
||||
if dst_fast:
|
||||
print(" 仿真快版:%s(延时约 0.6 us)" % dst_fast)
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user