# ============================================================================= # 注意:上板/仿真真正使用的镜像是 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