#!/usr/bin/env python3 """把课程新版中断测试(EPC 内存堆栈保护 + 附加 SLL/SB/BLTU)适配到本 CPU 上板运行。 输入:programs/risc-v-intrupt-sllsb.hex 课程的原始镜像(344 字,已去掉 Logisim 的 "v2.0 raw" 头) 输出:<输出.vh> 供 cpu21_board_top.v 里 `include 的 ROM 初始化(RISC_V_INTRUPT_SLLSB_ROM) [输出_adapted.hex] 可选:适配后的镜像本身(方便人工对比/仿真) 只改 ROM 内容,不改 CPU 语义,**所有指令地址保持不变**,因此顶层的 IRQ1/2/3_VECTOR = 0x0AC / 0x240 / 0x3E8 不用动。 改了什么 -------- 课程程序用 "addi a7,zero,34" + "ecall" 显示(`ecall` 的服务号 34 是原 Logisim 电路的历史"显示"调用;本 CPU 把它保留为空操作,LED/数码管不会有任何反应)。 这里把每条显示点**原地换成一次调用**(仍然是 2 字换 2 字): * 主程序(字 0..42): "jal ra,DISP_DLY" + "nop" * 三个 ISR(字 >= 43): "add a7,a0,zero" + "jal a0,DISP_DLY_ISR" 两个显示例程(各 8 条)追加在程序末尾(最后一条 uret 之后),所以入口地址不变。 为什么 ISR 版不用 ra -------------------- 三个 ISR 的现场都保存/恢复 a7 与 a0,但**都没有保存 ra**(它们原来的显示是 ecall,不需要 ra)。若 ISR 里用 `jal ra,...`,ra 会被写成 ISR 内部地址;恰好 中断落在主程序 DISP_DLY 末尾的 `jalr zero,0(ra)` 上时,返回后会跳到 ISR 内部, 一路执行到 ISR 结尾的 `csrrci zero,0x4,1`,把 MIE 永久清零(表现就是"跑一阵子 之后拨开关再也不响应")。所以 ISR 版把链接寄存器放 a0、显示值放 a7。 为什么返回地址存在 sp+256 ------------------------ 本程序的 ISR 是"先加 sp 再存"的向上增长栈,一层最多 48 字节,最多嵌套 4 层共 192 字节,所以 [sp, sp+192) 是中断随时会写到的区域。返回地址放在 sp+256(越过 这个区域)后,中断(含嵌套)打断显示延时也破坏不了它。 用法 ---- py tools/adapt_intrupt_sllsb.py programs/risc-v-intrupt-sllsb.hex \ srcs/board/risc-v-intrupt-sllsb.vh [programs/risc-v-intrupt-sllsb-adapted.hex] """ import sys NOP = 0x00000013 ECALL = 0x00000073 ZERO, RA, SP, T2, A0, A7 = 0, 1, 2, 7, 10, 17 LED_ADDR_HI = 1 # lui a7, 1 -> a7 = 0x1000(LED_ADDR / 数码管) # 延时计数初值为负、向上数到 0(约 2 拍一轮): # 0xFFFC0000 -> 0x40000 = 262144 轮 ≈ 52 万拍 ≈ 10 ms @50 MHz # @3.125 MHz(CLK_DIV_INTRUPT=32)时约 160 ms,肉眼看得很清楚 DELAY_NEG_HI = 0xFFFC0 LINK_SLOT = 256 # 链接寄存器保存位置,必须 > ISR 最大栈用量(4x48=192) ISR1_WORD = 43 # 字 43 起是三个 ISR(主程序在它之前) ROM_WORDS = 1024 # 与 cpu21_board_top 的 hex2vh 约定一致(ROM 深度 8192 也够) # ---------------------------------------------------------------- 编码 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) def enc_r(f7, rs2, rs1, f3, rd): return ((f7 << 25) | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x33) DISP_LUI = enc_u(LED_ADDR_HI, A7) # lui a7, 1 DISP_SW = enc_s(0, A0, A7, 2) # sw a0, 0(a7) SHOW_SITE = enc_i(34, ZERO, 0, A7) # addi a7, zero, 34 <- 课程程序的"显示"点 SW_A7_0SP = enc_s(0, A7, SP, 2) # sw a7, 0(sp) <- ISR 入口第一条 def disp_dly(): """主程序用:显示 a0,延时约 10 ms,链接在 ra(8 条)。""" return [ enc_s(LINK_SLOT, RA, SP, 2), # sw ra, 256(sp) DISP_LUI, # lui a7, 1 DISP_SW, # sw a0, 0(a7) enc_u(DELAY_NEG_HI, T2), # lui t2, 0xFFFC0 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(): """ISR 用:显示 a7,链接在 a0,完全不碰 ra(8 条)。""" return [ enc_s(LINK_SLOT, A0, SP, 2), # sw a0, 256(sp) enc_u(LED_ADDR_HI, A0), # lui a0, 1 enc_s(0, A7, A0, 2), # sw a7, 0(a0) enc_u(DELAY_NEG_HI, T2), # lui t2, 0xFFFC0 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 ] def read_hex(path): words = [] with open(path, encoding="ascii") as f: for line in f: line = line.split("//")[0].strip() if not line or line.lower().startswith("v2.0"): continue # 容忍 Logisim 的 "v2.0 raw" 头 words.append(int(line, 16)) return words def main(): if len(sys.argv) < 3: raise SystemExit(__doc__) src, dst_vh = sys.argv[1], sys.argv[2] dst_hex = sys.argv[3] if len(sys.argv) > 3 else None w = read_hex(src) print("源程序 %d 字" % len(w)) # ---- 自检:三个 ISR 入口必须还是 sw a7,0(sp) ---- entries = [i for i, x in enumerate(w) if x == SW_A7_0SP] print("ISR 入口候选(sw a7,0(sp))字号 = %s" % entries) if entries != [43, 144, 250]: print("警告:ISR 入口与预期 [43, 144, 250] 不一致,请核对顶层 IRQ_VECTOR!") # ---- 例程地址(追加在程序末尾,入口地址因此不变)---- n = len(w) dly_addr = n * 4 dly_isr_addr = (n + 8) * 4 # ---- 逐条替换显示点 ---- patched_main = patched_isr = 0 i = 0 while i < n - 1: if w[i] == SHOW_SITE and w[i + 1] == ECALL: if i < ISR1_WORD: w[i] = enc_j(dly_addr - i * 4, RA) # jal ra, DISP_DLY w[i + 1] = NOP patched_main += 1 else: w[i] = enc_r(0, 0, A0, 0, A7) # add a7, a0, zero # 注意:jal 落在 w[i+1],所以偏移必须按 (i+1) 算,否则会跳过例程首条 w[i + 1] = enc_j(dly_isr_addr - (i + 1) * 4, A0) # jal a0, DISP_DLY_ISR patched_isr += 1 i += 2 else: i += 1 print("显示点替换:主程序 %d 处、ISR %d 处" % (patched_main, patched_isr)) # ---- 自检:每个显示调用的跳转目标必须正好落在例程首字 ---- def jal_target(word, addr): imm = (((word >> 31) & 1) << 20) | (((word >> 21) & 0x3FF) << 1) \ | (((word >> 20) & 1) << 11) | (((word >> 12) & 0xFF) << 12) if imm & (1 << 20): imm -= (1 << 21) return addr + imm bad = [] for i in range(n - 1): if w[i] == enc_r(0, 0, A0, 0, A7) and (w[i + 1] & 0x7F) == 0x6F: if jal_target(w[i + 1], (i + 1) * 4) != dly_isr_addr: bad.append(i) if (w[i] & 0x7F) == 0x6F and ((w[i] >> 7) & 0x1F) == RA and w[i + 1] == NOP: if jal_target(w[i], i * 4) != dly_addr: bad.append(i) if bad: raise SystemExit("自检失败:字 %s 的跳转目标不对" % bad) print("自检通过:所有显示调用都精确跳到例程首字(DISP_DLY 0x%X / ISR 0x%X)" % (dly_addr, dly_isr_addr)) w += disp_dly() + disp_dly_isr() print("追加 DISP_DLY @字 %d (0x%X)、DISP_DLY_ISR @字 %d (0x%X)" % (n, dly_addr, n + 8, dly_isr_addr)) # ---- 写 .vh(与 tools/hex2vh.py 的格式一致)---- if len(w) > ROM_WORDS: raise SystemExit("程序 %d 字,超过 %d 字上限" % (len(w), ROM_WORDS)) with open(dst_vh, "w", encoding="utf-8") as f: f.write(" // generated by tools/adapt_intrupt_sllsb.py - do not edit by hand\n") f.write(" // source: %s (%d words, remaining entries filled with NOP)\n" % (src, len(w))) for a in range(ROM_WORDS): f.write(" rom[%d] = 32'h%08X;\n" % (a, w[a] if a < len(w) else NOP)) print("已生成 %s(%d 个字)" % (dst_vh, ROM_WORDS)) if dst_hex: with open(dst_hex, "w", encoding="ascii") as f: for x in w: f.write("%08X\n" % x) print("已生成 %s(适配后的镜像)" % dst_hex) main()