#!/usr/bin/env python3 """elf2hex.py —— 把 objcopy 出来的裸二进制转成 ROM 需要的两份文件。 用法: python tools/elf2hex.py [ROM_WORDS] [output.vh] 产出: 1) 每行一个 32 位字($readmemh 格式,供 srcs/rom/ 下的 测试平台加载,例如拷成 srcs/rom/saolei.hex) 2) (可选) rom[i] = 32'h...; 语句,供 pipelined-cpu/srcs/board/cpu21_board_top.v 里 `include 固化进片内 ROM(未用到的地址填 NOP 0x00000013) (未用到的地址填 NOP 0x00000013) 字序:输入的 .bin 是按字节小端排列的机器码,这里按小端拼成 32 位字。 """ import sys NOP = 0x00000013 def main(): if len(sys.argv) < 3: print("用法: python tools/elf2hex.py [ROM_WORDS] [output.vh]") return 2 src = sys.argv[1] dst = sys.argv[2] words_total = int(sys.argv[3]) if len(sys.argv) > 3 else 8192 vh_path = sys.argv[4] if len(sys.argv) > 4 else None with open(src, "rb") as f: data = f.read() if len(data) % 4 != 0: print("警告:二进制长度 %d 不是 4 的倍数,尾部会被补 0" % len(data)) data += b"\x00" * (4 - len(data) % 4) words = [] for i in range(0, len(data), 4): words.append(int.from_bytes(data[i:i + 4], "little")) # 段间空洞填 NOP:objcopy 出来的 .bin 在 .text.isr1 / .text.isr2 / .text.isr3 # 这些"绝对地址段"之间会留下 0x00000000,而本 CPU 把 0x00000000 译成自跳指令 # (beq x0,x0,0 形式),PC 一旦落进去就永远出不来(仿真里已复现:PC 热点全在 # 0x194-0x1FF / 0x218-0x2FF 这些空洞地址上)。这里把"最后一条非零字之前"的 # 0 字统一换成 NOP,让 PC 即使误入也能顺势走过。 last_nz = -1 for i, w in enumerate(words): if w != 0: last_nz = i filled = 0 for i in range(last_nz): if words[i] == 0: words[i] = NOP filled += 1 if len(words) > words_total: print("错误:程序 %d 个字,超过 ROM 容量 %d 个字(改大 ROM_WORDS 或精简程序)" % (len(words), words_total)) return 1 with open(dst, "w", encoding="ascii") as f: for w in words: f.write("%08X\n" % w) if vh_path: with open(vh_path, "w", encoding="utf-8") as f: f.write(" // 由 tools/elf2hex.py 生成,请勿手工编辑\n") f.write(" // 源文件:%s(%d 个字,其余填 NOP)\n" % (src, len(words))) for addr in range(words_total): w = words[addr] if addr < len(words) else NOP f.write(" rom[%d] = 32'h%08X;\n" % (addr, w)) print("已生成 %s(%d 个字)%s" % (dst, len(words), ("、%s(%d 个字,NOP 填充)" % (vh_path, words_total)) if vh_path else "")) return 0 if __name__ == "__main__": sys.exit(main())