357 lines
11 KiB
Python
Executable File
357 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# RISC-V assembly to fox32 translator
|
|
import argparse
|
|
import re
|
|
|
|
class Emitter:
|
|
"""
|
|
The Emitter class emits fox32 assembly code.
|
|
"""
|
|
|
|
def __init__(self, filename):
|
|
self.filename = filename
|
|
self.f = open(filename, 'w')
|
|
|
|
def write(self, line):
|
|
self.f.write(line + '\n')
|
|
|
|
@staticmethod
|
|
def _adjust_label(label):
|
|
"""
|
|
Adjust a label to the requirements of fox32asm
|
|
"""
|
|
l = label.replace('.', '_')
|
|
if l.startswith('_'):
|
|
return 'U' + l[1:]
|
|
return l
|
|
|
|
@staticmethod
|
|
def _adjust_operand(op):
|
|
"""
|
|
Adjust an operand to the requirements of fox32asm
|
|
"""
|
|
if re.fullmatch(r'-?[0-9]x?[0-9a-fA-F]*', op):
|
|
return f'{int(op, 0) & 0xffffffff:#x}'
|
|
return op
|
|
|
|
def insn(self, operation, *operands):
|
|
"""
|
|
Emit an instruction
|
|
"""
|
|
line = '\t' + operation
|
|
if operands:
|
|
line += '\t' + ', '.join([self._adjust_operand(op) for op in operands])
|
|
self.write(line)
|
|
|
|
def include(self, filename):
|
|
"""
|
|
Emit an #include directive
|
|
"""
|
|
self.write(f'#include "{filename}"')
|
|
|
|
def comment(self, line):
|
|
"""
|
|
Emit a comment
|
|
"""
|
|
self.write(f'; {line}')
|
|
|
|
def label(self, label):
|
|
"""
|
|
Emit a label definition
|
|
"""
|
|
self.write(self._adjust_label(label) + ':')
|
|
|
|
def data(self, *data, size=32):
|
|
"""
|
|
Emit data
|
|
"""
|
|
for d in data:
|
|
self.write(f'data.{size} {d}')
|
|
|
|
def zero(self, *data):
|
|
"""
|
|
Emit zeros
|
|
"""
|
|
for d in data:
|
|
self.write(f'data.fill 0, {d}')
|
|
|
|
def strz(self, *data):
|
|
"""
|
|
Emit null-terminated string
|
|
"""
|
|
for d in data:
|
|
self.write(f'data.strz \"{d}\"')
|
|
|
|
def nop(self):
|
|
"""
|
|
Emit a nop instruction
|
|
"""
|
|
self.insn('nop')
|
|
|
|
def jump(self, label, condition=''):
|
|
"""
|
|
Emit a jump to a label, optionally with a condition code
|
|
"""
|
|
label = self._adjust_label(label)
|
|
if condition:
|
|
self.insn(f'{condition} rjmp', label)
|
|
else:
|
|
self.insn('rjmp', label)
|
|
|
|
def jump_reg(self, reg):
|
|
"""
|
|
Emit a jump to a register
|
|
"""
|
|
self.insn('jmp', reg)
|
|
|
|
def mov(self, dest, source, size=32):
|
|
"""
|
|
Emit a mov instruction
|
|
"""
|
|
self.insn(f'movz.{size}', dest, source)
|
|
|
|
def store(self, rmem, rval, size=32):
|
|
"""
|
|
Emit a mov instruction that performs a memory store
|
|
"""
|
|
self.mov(f'[{rmem}]', rval, size)
|
|
|
|
def load(self, rmem, rval, size=32):
|
|
"""
|
|
Emit a mov instruction that performs a memory load
|
|
"""
|
|
self.mov(rval, f'[{rmem}]', size)
|
|
|
|
def rta(self, dest, source):
|
|
"""
|
|
Emit an rta instruction
|
|
"""
|
|
source = self._adjust_label(source)
|
|
self.insn(f'rta', dest, source)
|
|
|
|
def halt(self):
|
|
"""
|
|
Emit an halt instruction
|
|
"""
|
|
self.insn('halt')
|
|
|
|
class Converter:
|
|
"""
|
|
This class converts RISC-V assembly code to fox32 assembly code
|
|
"""
|
|
def __init__(self, input, output, *, debug=False, start_file='start.asm', additional_includes=[]):
|
|
self.input_file = open(input, 'r')
|
|
self.e = Emitter(output)
|
|
|
|
self.debug = debug
|
|
self.start_file = start_file
|
|
self.additional_includes = additional_includes
|
|
|
|
self.xregs = [f'x{n}' for n in range(32)]
|
|
self.regs = ['zero', 'ra', 'sp', 'gp', 'tp']
|
|
self.regs += [f't{n}' for n in range(3)]
|
|
self.regs += [f's{n}' for n in range(2)]
|
|
self.regs += [f'a{n}' for n in range(8)]
|
|
self.regidx = { r: i for i, r in enumerate(self.regs) }
|
|
self.xregidx = { r: i for i, r in enumerate(self.xregs) }
|
|
|
|
self.tmp = 'r0' # temporary register
|
|
self.branches = ['bge', 'bgeu', 'blt', 'bltu', 'ble', 'bleu', 'bne', 'bneu']
|
|
|
|
self.label_gen = iter(range(1000000))
|
|
|
|
def unique_label(self):
|
|
return f'rv2fox_{(next(self.label_gen))}'
|
|
|
|
@staticmethod
|
|
def ldst_to_size(insn):
|
|
size_map = {
|
|
'lb': 8, 'lh': 16, 'lw': 32,
|
|
'lbu': 8, 'lhu': 16,
|
|
'sb': 8, 'sh': 16, 'sw': 32,
|
|
}
|
|
if insn in size_map:
|
|
return size_map[insn]
|
|
|
|
def ldst(self, insn, rval, rbase, offset):
|
|
size = self.ldst_to_size(insn)
|
|
if int(offset, 0) != 0:
|
|
self.e.mov(self.tmp, rbase)
|
|
self.e.insn('add', self.tmp, offset)
|
|
rmem = self.tmp
|
|
else:
|
|
rmem = rbase
|
|
if insn.startswith('s'):
|
|
self.e.store(rmem, rval, size)
|
|
else:
|
|
self.e.load(rmem, rval, size)
|
|
|
|
def convert_directive(self, line):
|
|
if line in ['text', 'data']:
|
|
pass # ignore
|
|
elif m := re.fullmatch(r'word\s+([^\s]+)', line):
|
|
self.e.data(*m.groups())
|
|
elif m := re.fullmatch(r'zero\s+([^\s]+)', line):
|
|
self.e.zero(*m.groups())
|
|
elif m := re.fullmatch(r'string\s+\"(.*)\"', line):
|
|
self.e.strz(*m.groups())
|
|
elif m := re.fullmatch(r'asciz\s+\"(.*)\"', line):
|
|
self.e.strz(*m.groups())
|
|
elif m := re.match(r'set\s([^,]*),', line):
|
|
self.e.label(*m.groups())
|
|
|
|
def reg(self, reg):
|
|
"""
|
|
Convert a RISC-V register to a fox32 register
|
|
"""
|
|
if reg == 'sp':
|
|
return 'rsp'
|
|
elif reg in self.regidx:
|
|
return f'r{self.regidx[reg]}'
|
|
elif reg in self.xregidx:
|
|
return f'r{self.xregidx[reg]}'
|
|
else:
|
|
raise Exception(f'Unknown register {reg}')
|
|
|
|
@staticmethod
|
|
def convert_arithmetic_op(op):
|
|
if op in ['add', 'addi']: return 'add'
|
|
if op in ['sub', 'subi']: return 'sub'
|
|
if op in ['mul']: return 'mul'
|
|
if op in ['div']: return 'idiv'
|
|
if op in ['divu']: return 'div'
|
|
if op in ['rem']: return 'irem'
|
|
if op in ['remu']: return 'rem'
|
|
if op in ['and', 'andi']: return 'and'
|
|
if op in ['or', 'ori']: return 'or'
|
|
if op in ['xor', 'xori']: return 'xor'
|
|
if op in ['sll', 'slli']: return 'sla'
|
|
if op in ['srl', 'srli']: return 'srl'
|
|
else:
|
|
raise Exception(f'Unknown op {op}')
|
|
|
|
@staticmethod
|
|
def convert_condition(op):
|
|
if op in ['blt', 'bltu']: return 'iflt', False
|
|
if op in ['bne', 'bnez']: return 'ifnz', False
|
|
if op in ['ble', 'bleu']: return 'ifgt', True
|
|
if op in ['bge', 'bgeu']: return 'ifgteq', False
|
|
|
|
def convert_branch(self, insn, rs1, rs2, label):
|
|
cond, reverse = self.convert_condition(insn)
|
|
if reverse:
|
|
rs1, rs2 = rs2, rs1
|
|
self.e.insn('cmp', self.reg(rs1), self.reg(rs2))
|
|
self.e.jump(label, cond)
|
|
|
|
def convert_branch_imm(self, insn, rs, label, imm):
|
|
cond, reverse = self.convert_condition(insn)
|
|
if reverse:
|
|
rs1, rs2 = rs2, rs1
|
|
self.e.insn('cmp', self.reg(rs), str(imm))
|
|
self.e.jump(label, cond)
|
|
|
|
@staticmethod
|
|
def iter_operands(operands):
|
|
if operands:
|
|
for op in operands.split(','):
|
|
op = op.strip()
|
|
if op != '':
|
|
yield op
|
|
|
|
def convert_insn(self, insn, operands=None):
|
|
tmp = self.tmp
|
|
operands = list(self.iter_operands(operands))
|
|
|
|
if insn == 'nop':
|
|
self.e.nop()
|
|
elif insn in ['addi', 'subi', 'div', 'rem', 'andi', 'ori', 'xori', 'slli', 'srli']:
|
|
rd, rs, imm = operands
|
|
op = self.convert_arithmetic_op(insn)
|
|
if rd == rs:
|
|
self.e.insn(op, self.reg(rd), imm)
|
|
else:
|
|
self.e.mov(tmp, self.reg(rs))
|
|
self.e.insn(op, tmp, imm)
|
|
self.e.mov(self.reg(rd), tmp)
|
|
elif insn in ['add', 'sub', 'mul', 'divu', 'remu', 'and', 'nor', 'xor', 'sll', 'srl']:
|
|
rd, rs1, rs2 = operands
|
|
if rd == rs1:
|
|
op = self.convert_arithmetic_op(insn)
|
|
self.e.insn(op, self.reg(rd), self.reg(rs2))
|
|
else:
|
|
op = self.convert_arithmetic_op(insn)
|
|
self.e.mov(tmp, self.reg(rs1))
|
|
self.e.insn(op, tmp, self.reg(rs2))
|
|
self.e.mov(self.reg(rd), tmp)
|
|
elif insn in ['sw', 'lw', 'sh', 'lh', 'lhu', 'sb', 'lb', 'lbu']:
|
|
rval, address = operands
|
|
offset, rbase = re.fullmatch(r'(-?[0-9]+)\(([a-z0-9]+)\)', address).groups()
|
|
self.ldst(insn, self.reg(rval), self.reg(rbase), offset)
|
|
elif insn in ['j']:
|
|
self.e.jump(*operands)
|
|
elif insn in ['jr']:
|
|
self.e.jump_reg(self.reg(*operands))
|
|
elif insn in ['ret']:
|
|
self.e.jump_reg(self.reg('ra'))
|
|
elif insn in ['lla']:
|
|
rd, label = operands
|
|
self.e.rta(self.reg(rd), label)
|
|
elif insn in ['mv']:
|
|
rd, rs = operands
|
|
self.e.mov(self.reg(rd), self.reg(rs))
|
|
elif insn in ['call']:
|
|
label = self.unique_label()
|
|
self.e.mov(self.reg('ra'), label)
|
|
self.e.jump(*operands)
|
|
self.e.label(label)
|
|
elif insn in self.branches:
|
|
self.convert_branch(insn, *operands)
|
|
elif insn in ['bnez']:
|
|
self.convert_branch_imm(insn, *operands, 0)
|
|
elif insn in ['li']:
|
|
rd, imm = operands
|
|
self.e.mov(self.reg(rd), imm)
|
|
elif insn in ['lui']:
|
|
rd, imm = operands
|
|
self.e.mov(self.reg(rd), str(int(imm, 0) << 12))
|
|
elif insn == 'wfi':
|
|
self.e.halt()
|
|
else:
|
|
raise Exception(f'Unknown instruction {insn} {operands}')
|
|
|
|
def convert(self):
|
|
self.e.comment(f'Generated with rv2fox')
|
|
|
|
self.e.include(self.start_file)
|
|
for file in self.additional_includes:
|
|
self.e.include(file)
|
|
|
|
for i, line in enumerate(self.input_file):
|
|
num = i + 1
|
|
line = line.strip()
|
|
|
|
if self.debug:
|
|
self.e.write(f'\t; {line}')
|
|
|
|
if line == '' or re.fullmatch(r'#.*', line):
|
|
pass
|
|
elif re.fullmatch(r'\.[a-z]+.*', line):
|
|
self.convert_directive(line[1:])
|
|
elif m := re.fullmatch(r'([a-zA-Z0-9_.]+):', line):
|
|
self.e.label(*m.groups())
|
|
elif m := re.fullmatch(r'([a-z]+)', line) or \
|
|
re.fullmatch(r'([a-z]+)\s+([^\s].*)', line):
|
|
self.convert_insn(*m.groups())
|
|
else:
|
|
raise Exception(f'Unknown line: {line}')
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description='Convert RISC-V assembly to fox32 assembly')
|
|
parser.add_argument('input', help='RISC-V input')
|
|
parser.add_argument('-o', '--output', help='fox32 output')
|
|
parser.add_argument('--debug', help='produce verbose output to allow debugging this tool', action='store_true')
|
|
args = parser.parse_args()
|
|
c = Converter(args.input, args.output, debug=args.debug)
|
|
c.convert()
|