Initial commit
This commit is contained in:
186
tools/as.py
Normal file
186
tools/as.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import argparse
|
||||
import enum
|
||||
import lark
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import obj_pb2
|
||||
|
||||
GRAMMAR_FILE = '/home/paulmathieu/vhdl/bin/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{p0:x}{p1&0xff:02x}',
|
||||
'set' : lambda p0, p1: f'e{p0:x}{p1&0xff:02x}',
|
||||
'bneq' : lambda p0, p1: f'f{p0:x}{p1&0xff:02x}',
|
||||
'.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 - 2
|
||||
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()
|
||||
Reference in New Issue
Block a user