Files
risc-v-cpu/pipelined-cpu/tools/adapt_intrupt_ccab.py
Anccend f7844b7b1c 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。
2026-09-19 15:44:47 +08:00

415 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()