#!/usr/bin/env python3 """check_isa.py —— 校验反汇编清单里没有这颗 CPU 未实现的指令。 用法: python tools/check_isa.py 为什么需要它:本 CPU(srcs/rtl/cpu21_riscv_decoder.v)只实现了 RV32I 的一个 子集,最容易踩的三个坑是: * 没有 auipc -> 编译器一旦生成它,整段代码就废了(多为 PIC/rodata 引起) * 没有 lb/lh/lbu/lhu -> 只要碰 char/short 类型的读就会出现 * 没有有符号 div/rem(只有 divu/remu) -> 有符号除法/取余会被静默算错 所以这里用"白名单"方式检查:出现名单外的助记符就报错并给出修复提示。 退出码 = 违规条数(>255 时截断),0 表示通过。 """ import sys # 这颗 CPU 真正实现了的指令(见 cpu21_riscv_decoder.v) ALLOWED = { # R 型 "add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and", # M 扩展(只有这几个) "mul", "mulh", "mulhsu", "mulhu", "divu", "remu", # I 型 "addi", "slti", "sltiu", "xori", "ori", "andi", "slli", "srli", "srai", # 访存:只有 lw / sw / sb(没有 lb/lh/lbu/lhu/sh) "lw", "sw", "sb", # 控制流 "beq", "bne", "blt", "bge", "bltu", "bgeu", "jal", "jalr", # 高位立即数 "lui", # 系统 "ecall", "uret", "csrrw", "csrrsi", "csrrci", } # objdump 有时仍打印伪指令;这些展开后都是上面支持的指令 ALIAS_OK = { "nop", "mv", "li", "j", "jr", "ret", "neg", "not", "beqz", "bnez", "blez", "bgez", "bltz", "bgtz", "seqz", "snez", "sltz", "sgtz", } # 违规时给出针对性提示 HINTS = { "auipc": "出现了 auipc:说明编译带了 PIC/PIE 或访问了只读数据。" "请用 -fno-pic -mcmodel=medlow,并去掉字符串/常量表(.rodata)", "lb": "没有 lb:不要用 char/short 类型读内存,改用 32 位字", "lh": "没有 lh:同上", "lbu": "没有 lbu:不要用 unsigned char/short 类型读内存(写显存可以用 sb,但别读回来)", "lhu": "没有 lhu:同上", "sh": "没有 sh:只用 sw / sb", "div": "没有有符号 div:改用 unsigned 运算(unsigned 除法会编成 divu)", "rem": "没有有符号 rem:改用 unsigned 运算(unsigned 取余会编成 remu)", "divw": "不支持 64 位运算", "remw": "不支持 64 位运算", "call": "call 会展开成 auipc+jalr:请直接用普通函数调用(jal)", "tail": "tail 会展开成 auipc:请用普通跳转", "fence": "没有 fence/fence.i", "ebreak": "没有 ebreak", "mret": "这颗核的中断返回是 uret,不是 mret", "sret": "这颗核的中断返回是 uret", "wfi": "没有 wfi:主循环只能用轮询", "unimp": "出现了 unimp(非法指令/内联汇编占位)", } def main(): if len(sys.argv) < 2: print("用法: python tools/check_isa.py ") return 2 path = sys.argv[1] total = 0 violations = [] with open(path, encoding="utf-8", errors="replace") as f: for line in f: if "\t" not in line: continue parts = line.split("\t") if len(parts) < 3 or not parts[0].strip().endswith(":"): continue mnemonic = parts[2].strip().split()[0] if not mnemonic: continue total += 1 if (mnemonic not in ALLOWED) and (mnemonic not in ALIAS_OK): violations.append((parts[0].strip(), mnemonic, line.rstrip())) print("检查了 %d 条指令。" % total) if not violations: print("ISA 检查通过:全部指令都在本 CPU 的实现范围内。") return 0 print("") print("!! ISA 检查失败:有 %d 条指令这颗 CPU 没有实现:" % len(violations)) seen = set() for addr, mnemonic, raw in violations[:40]: print(" %s %s" % (addr, raw.strip())) seen.add(mnemonic) if len(violations) > 40: print(" ...(只列出前 40 条)") print("") print("修复提示:") for m in sorted(seen): print(" * %s: %s" % (m, HINTS.get(m, "不在本 CPU 的指令白名单里"))) return min(len(violations), 255) if __name__ == "__main__": sys.exit(main())