synth/tools/as.py

189 lines
5.5 KiB
Python

import argparse
import enum
import lark
import os
import struct
import sys
import obj_pb2
_HERE = os.path.dirname(__file__)
GRAMMAR_FILE = os.path.join(_HERE, 'as.ebnf')
opcodes = {
'nop' : lambda: '0000',
'load' : lambda p0, p1, p2: f'1{p0:x}{p1:x}{(p2 >> 1)&0xf:x}',
'store': lambda p0, p1, p2: f'2{p0:x}{p1:x}{(p2 >> 1)&0xf:x}',
'add' : lambda p0, p1, p2: f'3{p0:x}{p1:x}{p2:x}',
'sub' : lambda p0, p1, p2: f'4{p0:x}{p1:x}{p2:x}',
'or' : lambda p0, p1, p2: f'5{p0:x}{p1:x}{p2:x}',
'and' : lambda p0, p1, p2: f'6{p0:x}{p1:x}{p2:x}',
'not' : lambda p0, p1: f'7{p0:x}{p1:x}0',
'xor' : lambda p0, p1, p2: f'8{p0:x}{p1:x}{p2:x}',
'seth' : lambda p0, p1: f'9{p0:x}{p1&0xff:02x}',
'shr' : lambda p0, p1, p2: f'a{p0:x}{p1:x}{p2:x}',
'mul' : lambda p0, p1, p2: f'b{p0:x}{p1:x}{p2:x}',
'cmp' : lambda p0, p1: f'c{p0:x}{p1:x}0',
'beq' : lambda p0, p1: f'd{(p1 >> 1)&0xfff:03x}',
'set' : lambda p0, p1: f'e{p0:x}{p1&0xff:02x}',
'bneq' : lambda p0, p1: f'f{(p1 >> 1)&0xfff:03x}',
'.word': lambda p0: f'{p0:04x}',
}
class AsTransformer(lark.Transformer):
def __init__(self):
self.addr = 0
def statement(self, s):
opcode, *params = s
addr = self.addr
self.addr += 2
return ('OP', addr, opcode, *params)
def opcode(self, o):
(o, ) = o
return str(o)
def reg_offset(self, r):
if len(r) < 2:
return r[0], 0
return tuple(r[:])
LABEL = str
HEX_LITTERAL = lambda _, x: int(x[2:], 16)
def label(self, l):
(l,) = l
return ('LBL', l, self.addr)
def label_ref(self, l):
(l,) = l
return l
def raw_word(self, w):
(w,) = w
addr = self.addr
self.addr += 2
return ('OP', addr, '.word', w)
def register(self, r):
(r,) = r
if r == 'pc':
return 14
if r == 'lr':
return 13
if r == 'sp':
return 12
return int(r[1:])
def immediate(self, i):
(i,) = i
return int(i)
def symbol_def(self, s):
(sym,) = s
return ('SYM', s[0])
start = list
def generate_ops(ops, labels, relocs):
def filter_label(params, pc):
for p in params:
if isinstance(p, str): # label ref
if len(params) == 1: # branch
yield 14 # pc
yield labels[p] - pc - 4
else: # set, allow relocs here
relocs.append((pc, p))
yield 0xff
elif isinstance(p, tuple): # reg offset
yield p[0]
yield p[1]
else:
yield p
for op in ops:
addr = op[0]
fmt = opcodes[op[1]]
params = filter_label(op[2:], addr)
yield fmt(*params)
def larkparse(f, debug=False):
with open(GRAMMAR_FILE) as g:
asparser = lark.Lark(g.read())
tree = asparser.parse(f.read())
if debug:
print(tree.pretty())
lines = AsTransformer().transform(tree)
labels = {l[1]: l[2] for l in lines if l[0] == 'LBL'}
ops = [l[1:] for l in lines if l[0] == 'OP']
syms = [(l[1], labels[l[1]]) for l in lines if l[0] == 'SYM']
relocs = []
return [int(x, 16) for x in generate_ops(ops, labels, relocs)], syms, relocs
# any record: >BHs, type, length, data
# header: >cccc, "pol0"
# text record: >BHs, name, text_len, text
# reloc record: >pHip, txt_rec_name, offset, target
def write_obj(fout, ops, syms, relocs):
obj = obj_pb2.ObjFile(header=obj_pb2.Header(magic='pol0'))
ends = [s[1] for s in syms[1:]] + [2 * len(ops)]
data = b''.join(struct.pack('>H', op) for op in ops)
for (name, begin), end in zip(syms, ends):
section = obj_pb2.Section(name=name, text=data[begin:end])
obj.sections.append(section)
for offs, target in relocs:
if offs >= begin and offs < end:
section_off = offs - begin
reloc = obj_pb2.Reloc(section=name, offset=section_off, target=target)
obj.relocs.append(reloc)
fout.write(obj.SerializeToString())
def parse_args():
parser = argparse.ArgumentParser(description='Assemble.')
group = parser.add_mutually_exclusive_group()
group.add_argument('--vhdl', action='store_true',
help='output VHDL code')
group.add_argument('--coe', action='store_true',
help='output a Vivado COE file')
parser.add_argument('--debug', action='store_true',
help='print the AST')
parser.add_argument('--input', '-c', type=argparse.FileType('r'),
default=sys.stdin, help='input file (default: stdin)')
parser.add_argument('--output', '-o', type=argparse.FileType('wb'),
default=sys.stdout.buffer, help='output file')
return parser.parse_args()
def main():
args = parse_args()
ops, syms, relocs = larkparse(args.input, debug=args.debug)
if args.vhdl:
print(',\n'.join(f'x"{x:04x}"' for x in ops))
for addr, target in relocs:
print(f'-- reloc: {target} @{addr}')
elif args.coe:
assert not relocs, "relocs not supported with coe output"
print('memory_initialization_radix=16;')
print('memory_initialization_vector=')
code = ',\n'.join(f'{x:04x}' for x in ops)
print(f'{code};')
else:
write_obj(args.output, ops, syms, relocs)
if __name__ == "__main__":
main()