Files
risc-v-cpu/pipelined-cpu/tools/adapt_intrupt_sllsb.py
T
Alpha e44680a114 feat(board): 新增课程新版中断测试上板(多级中断 + 附加 SLL/SB/BLTU)
- programs/risc-v-intrupt-sllsb.asm/.hex: 课程原始程序(344 字,hex 已去掉 Logisim
  的 v2.0 raw 头),保留原文件以便追溯
- tools/adapt_intrupt_sllsb.py: 把它适配到本 CPU 上板(只改 ROM 内容,地址不变)
  - 课程用 addi a7,zero,34 + ecall 显示,本 CPU 里那是空操作 -> 每条显示点原地换成
    一次调用:主程序 jal ra,DISP_DLY;ISR 用 add a7,a0,zero + jal a0,DISP_DLY_ISR
    (三个 ISR 都没保存 ra,所以 ISR 版把链接寄存器放 a0、显示值放 a7)
  - DISP_DLY / DISP_DLY_ISR 各 8 条,追加在 uret 之后,中断入口 0x0AC/0x240/0x3E8 不变
  - 返回地址存 sp+256(越过 ISR 最多 192 字节的栈区),嵌套中断也破坏不了
  - 脚本自带跳转目标自检(曾发现 ISR 侧 jal 偏移少算一条指令的问题)
- srcs/board/cpu21_board_top.v: 新增 RISC_V_INTRUPT_SLLSB_ROM 分支(ROM 选择、
  CPU 分频用 CLK_DIV_INTRUPT、中断向量 0x0AC/0x240/0x3E8)
- srcs/board/risc-v-intrupt-sllsb.vh: 生成的 ROM 初始化(1024 字,程序 360 字)
- create_project.tcl: 新增 use_intrupt_sllsb 开关(默认 1,已把 ccab 置 0)

实测:Vivado 2020.2 建工程 + 综合通过(sources_1 verilog_define =
RISC_V_INTRUPT_SLLSB_ROM,synth_design Complete! 100%)
2026-09-19 16:10:19 +08:00

223 lines
9.1 KiB
Python
Raw 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
"""把课程新版中断测试(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()