Files
risc-v-cpu/tools/rv_asm.py
T
Anccend ccd1b917d0 新增 ISA 验证资产:迷你汇编器 + 指令测试程序
tools/rv_asm.py
- 极简 RISC-V 汇编器(Python 3,无第三方依赖),支持
  add/sub/and/or/xor/sll/srl/sra/slt/sltu、mul/mulh/mulhsu/mulhu、
  addi/slti/sltiu/xori/ori/andi、slli/srli/srai、lw/sw、
  beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签与 # 注释;
- 提供 print / halt 两条双字伪指令(分别等价于 addi a7,zero,34 + ecall、
  addi a7,zero,10 + ecall);
- I 型与访存立即数做 12 位有符号范围检查,越界直接报错,避免静默截断;
- 除 hex 外同时输出 .lst 编码清单,便于与 .asm 逐行核对。

testbench/programs/isa_test.asm + isa_test.expected
- 指令扩展验证程序:sltiu(两个方向)、xor/sll/srl/sra、mul/mulh/mulhu/mulhsu、
  bltu/bge/bgeu 的有符号与无符号区分,以及 bge/bgeu/bltu 三个循环,
  共 24 项 LED 输出,期望值逐行保存在 isa_test.expected。

testbench/programs/lui_test.asm
- LUI 验证程序:0x12345 / 0xfffff / 0x80000 与 lui+addi 组合,共 4 项输出。

另外包含编辑器/格式化工具对 cpu21_riscv_alu.v、cpu21_riscv_branch_unit.v、
cpu21_riscv_decoder.v 的重排(纯格式,语义等价)。

验证(xsim + testbench/tb_no_intr.v,按提交后的正式路径生成 hex)
- isa_test:24 项 LED 输出与 isa_test.expected 逐项一致,143 周期后 halt 停机;
- lui_test:12345000 / fffff000 / 80000000 / 12345678,21 周期后停机;
- 重新编译无 error / warning。
2026-09-12 19:10:24 +08:00

221 lines
5.9 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
"""极简 RISC-V 汇编器:仅支持本项目测试所需的指令子集。
用法: python tools/rv_asm.py <input.asm> <output.hex>
同时生成 <output.hex>.lst 反汇编/编码清单,便于核对。
支持:add/sub/and/or/xor/sll/srl/sra/slt/sltu、
mul/mulh/mulhsu/mulhu、
addi/slti/sltiu/xori/ori/andi、slli/srli/srai、
lw/sw、beq/bne/blt/bge/bltu/bgeu、lui、jal/jalr、ecall、标签、# 注释
伪指令:print(= addi a7,zero,34 + ecall)、halt(= addi a7,zero,10 + ecall),各占两个字。
示例:python tools/rv_asm.py testbench/programs/isa_test.asm out.hex
"""
import re
import sys
REGS = {
"zero": 0,
"ra": 1,
"sp": 2,
"gp": 3,
"tp": 4,
"t0": 5,
"t1": 6,
"t2": 7,
"s0": 8,
"s1": 9,
"a0": 10,
"a1": 11,
"a2": 12,
"a3": 13,
"a4": 14,
"a5": 15,
"a6": 16,
"a7": 17,
"s2": 18,
"s3": 19,
"s4": 20,
"s5": 21,
"s6": 22,
"s7": 23,
"s8": 24,
"s9": 25,
"s10": 26,
"s11": 27,
"t3": 28,
"t4": 29,
"t5": 30,
"t6": 31,
}
def reg(t):
t = t.strip()
if t in REGS:
return REGS[t]
m = re.fullmatch(r"x(\d+)", t)
if m:
return int(m.group(1))
raise SystemExit("未知寄存器: " + t)
def num(t):
return int(t.strip(), 0)
def enc_r(f7, rs2, rs1, f3, rd):
return (f7 << 25) | (rs2 << 20) | (rs1 << 15) | (f3 << 12) | (rd << 7) | 0x33
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
)
RTYPE = {
"add": (0x00, 0),
"sub": (0x20, 0),
"sll": (0x00, 1),
"slt": (0x00, 2),
"sltu": (0x00, 3),
"xor": (0x00, 4),
"srl": (0x00, 5),
"sra": (0x20, 5),
"or": (0x00, 6),
"and": (0x00, 7),
"mul": (0x01, 0),
"mulh": (0x01, 1),
"mulhsu": (0x01, 2),
"mulhu": (0x01, 3),
}
ITYPE = {"addi": 0, "slti": 2, "sltiu": 3, "xori": 4, "ori": 6, "andi": 7}
SHIFT = {"slli": 1, "srli": 5, "srai": 5}
BTYPE = {"beq": 0, "bne": 1, "blt": 4, "bge": 5, "bltu": 6, "bgeu": 7}
# 双字伪指令:print = addi a7,zero,34 + ecall;halt = addi a7,zero,10 + ecall
PSEUDO_SIZE = {"print": 2, "halt": 2}
def assemble(lines):
labels, items, addr = {}, [], 0
for raw in lines:
line = raw.split("#")[0].strip()
if not line:
continue
while True:
m = re.match(r"^([A-Za-z_.$][\w.$]*)\s*:\s*(.*)$", line)
if not m:
break
labels[m.group(1)] = addr
line = m.group(2).strip()
if not line:
break
if not line:
continue
items.append((addr, line))
addr += 4 * PSEUDO_SIZE.get(line.split()[0].lower(), 1)
out = []
for addr, line in items:
parts = re.split(r"[\s,]+", line)
mn, a = parts[0].lower(), parts[1:]
if mn in RTYPE:
f7, f3 = RTYPE[mn]
w = enc_r(f7, reg(a[2]), reg(a[1]), f3, reg(a[0]))
elif mn in ITYPE:
im = num(a[2])
if not (-2048 <= im <= 2047):
raise SystemExit("I 型立即数超出 12 位有符号范围: " + line)
w = enc_i(im, reg(a[1]), ITYPE[mn], reg(a[0]))
elif mn in SHIFT:
w = enc_i(num(a[2]) & 0x1F, reg(a[1]), SHIFT[mn], reg(a[0]))
elif mn == "lui":
w = enc_u(num(a[1]), reg(a[0]))
elif mn in ("lw", "sw"):
m = re.fullmatch(r"(-?\w+)\((\w+)\)", a[1])
if not m:
raise SystemExit("内存操作数格式应为 imm(rs1): " + line)
im = num(m.group(1))
if not (-2048 <= im <= 2047):
raise SystemExit("访存偏移超出 12 位有符号范围: " + line)
if mn == "lw":
w = enc_i(im, reg(m.group(2)), 2, reg(a[0]), 0x03)
else:
w = enc_s(im, reg(a[0]), reg(m.group(2)), 2)
elif mn in BTYPE:
tgt = labels[a[2]] if a[2] in labels else num(a[2])
w = enc_b(tgt - addr, reg(a[1]), reg(a[0]), BTYPE[mn])
elif mn == "jal":
tgt = labels[a[1]] if a[1] in labels else num(a[1])
w = enc_j(tgt - addr, reg(a[0]))
elif mn == "jalr":
w = enc_i(num(a[2]), reg(a[1]), 0, reg(a[0]), 0x67)
elif mn == "ecall":
w = 0x00000073
elif mn == "print":
w = [enc_i(34, 0, 0, 17), 0x00000073]
elif mn == "halt":
w = [enc_i(10, 0, 0, 17), 0x00000073]
else:
raise SystemExit("未知助记符: " + mn)
out.append((addr, line, w if isinstance(w, list) else [w]))
return out
def main():
src, dst = sys.argv[1], sys.argv[2]
with open(src, encoding="utf-8") as f:
prog = assemble(f.readlines())
with open(dst, "w", encoding="ascii") as f:
for _, _, ws in prog:
for w in ws:
f.write("%08X\n" % w)
with open(dst + ".lst", "w", encoding="utf-8") as f:
for addr, line, ws in prog:
f.write("%04X: %s %s\n" % (addr, " ".join("%08X" % w for w in ws), line))
print("已生成 %s(%d 个字)" % (dst, sum(len(ws) for _, _, ws in prog)))
main()