2020-07-28 16:09:24 +08:00
|
|
|
import re
|
|
|
|
import sys
|
2020-07-29 10:15:58 +08:00
|
|
|
import json
|
2020-07-28 16:09:24 +08:00
|
|
|
|
2020-07-29 10:49:41 +08:00
|
|
|
ADDRESS_PATTERN = r'0x[0-9A-F]+'
|
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
def parse_table_entries(fields):
|
2020-07-28 16:34:06 +08:00
|
|
|
"""Parse aligned table entries"""
|
2020-07-28 17:33:59 +08:00
|
|
|
pattern = r'\s+'.join([f"(?P<{v[:v.find(':')]}>{v[v.find(':') + 1:].strip()})"
|
|
|
|
for v in fields])
|
|
|
|
pattern = re.compile(pattern)
|
|
|
|
fields = [v[:v.find(':')] for v in fields]
|
|
|
|
prev = None
|
2020-07-28 16:34:06 +08:00
|
|
|
entries = []
|
|
|
|
def flush():
|
|
|
|
nonlocal prev
|
|
|
|
if prev is not None:
|
|
|
|
entries.append(prev['data'])
|
|
|
|
prev = None
|
2020-07-28 17:33:59 +08:00
|
|
|
|
|
|
|
while True:
|
|
|
|
line = yield
|
|
|
|
if line == None:
|
|
|
|
break
|
2020-07-29 10:15:58 +08:00
|
|
|
line = line.rstrip()
|
2020-07-28 16:09:24 +08:00
|
|
|
if len(line.strip()) == 0:
|
2020-07-28 16:34:06 +08:00
|
|
|
flush()
|
2020-07-28 16:09:24 +08:00
|
|
|
continue
|
2020-07-29 10:15:58 +08:00
|
|
|
match = pattern.fullmatch(line)
|
2020-07-28 16:09:24 +08:00
|
|
|
if match is not None:
|
2020-07-28 16:34:06 +08:00
|
|
|
flush()
|
2020-07-28 17:33:59 +08:00
|
|
|
starts = [match.span(v)[0] for v in fields]
|
2020-07-28 16:09:24 +08:00
|
|
|
prev = {
|
2020-07-28 17:33:59 +08:00
|
|
|
'data': {v: [match.group(v)] for v in fields},
|
2020-07-28 16:09:24 +08:00
|
|
|
'starts': starts
|
|
|
|
}
|
|
|
|
elif prev is not None:
|
|
|
|
for i, field in enumerate(fields):
|
2020-07-28 17:33:59 +08:00
|
|
|
if i + 1 == len(fields):
|
|
|
|
v = line[prev['starts'][i]:].strip()
|
|
|
|
else:
|
|
|
|
v = line[prev['starts'][i]:prev['starts'][i + 1]].strip()
|
2020-07-28 16:09:24 +08:00
|
|
|
if len(v) == 0:
|
|
|
|
continue
|
2020-07-28 17:33:59 +08:00
|
|
|
prev['data'][field].append(v)
|
2020-07-28 16:34:06 +08:00
|
|
|
flush()
|
|
|
|
return entries
|
2020-07-28 16:09:24 +08:00
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
def parse_register_fields():
|
|
|
|
fields = [
|
|
|
|
r'name: \w+',
|
|
|
|
r'bits: \d+(:\d+)?',
|
2020-07-29 11:58:56 +08:00
|
|
|
r'access: \w+',
|
2020-07-29 10:49:41 +08:00
|
|
|
f'reset: {ADDRESS_PATTERN}',
|
2020-07-28 17:33:59 +08:00
|
|
|
r'description: .+'
|
|
|
|
]
|
|
|
|
it = parse_table_entries(fields)
|
|
|
|
next(it)
|
|
|
|
return it
|
2020-07-28 16:34:06 +08:00
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
def end_iterator(it):
|
|
|
|
try:
|
|
|
|
it.send(None)
|
|
|
|
except StopIteration as e:
|
|
|
|
return e.value
|
|
|
|
|
|
|
|
def parse_registers():
|
2020-07-29 10:15:58 +08:00
|
|
|
def two_column(width):
|
2020-07-29 10:49:41 +08:00
|
|
|
# only require the leftmost column starts with a \w, and the a space
|
|
|
|
# separator between the two columns...
|
2020-07-29 10:15:58 +08:00
|
|
|
fields = [
|
|
|
|
f'key: \\w.{{1,{width-2}}}\\s',
|
2020-07-29 10:49:41 +08:00
|
|
|
r'value: \w.+'
|
2020-07-29 10:15:58 +08:00
|
|
|
]
|
|
|
|
it = parse_table_entries(fields)
|
|
|
|
next(it)
|
|
|
|
return it
|
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
def inner():
|
2020-07-29 10:15:58 +08:00
|
|
|
def_start = re.compile(r'Name\s+(\w+)\s+')
|
|
|
|
field_start = re.compile(r'\s+Field Name\s+Bits\s+Type\s+Reset '
|
|
|
|
r'Value\s+Description\s+')
|
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
state = 0
|
|
|
|
it = None
|
2020-07-29 10:15:58 +08:00
|
|
|
results = []
|
2020-07-28 17:33:59 +08:00
|
|
|
while True:
|
|
|
|
line = yield
|
|
|
|
if line == None:
|
|
|
|
break
|
2020-07-29 10:15:58 +08:00
|
|
|
line = line
|
|
|
|
if state == 0:
|
|
|
|
m = def_start.fullmatch(line)
|
|
|
|
if m is not None:
|
|
|
|
if it is not None:
|
|
|
|
results[-1]['fields']= end_iterator(it)
|
|
|
|
it = two_column(m.span(1)[0])
|
2020-07-28 17:33:59 +08:00
|
|
|
it.send(line)
|
2020-07-29 10:15:58 +08:00
|
|
|
state = 1
|
|
|
|
else:
|
|
|
|
if it is not None:
|
|
|
|
it.send(line)
|
|
|
|
elif state == 1:
|
|
|
|
m = field_start.fullmatch(line)
|
|
|
|
if m is not None:
|
|
|
|
if it is not None:
|
|
|
|
results.append({'def': end_iterator(it)})
|
|
|
|
it = parse_register_fields()
|
|
|
|
state = 0
|
|
|
|
else:
|
|
|
|
if it is not None:
|
|
|
|
it.send(line)
|
|
|
|
|
2020-07-28 17:33:59 +08:00
|
|
|
if it is not None:
|
2020-07-29 10:15:58 +08:00
|
|
|
if state == 0:
|
|
|
|
results[-1]['fields']= end_iterator(it)
|
2020-07-28 17:33:59 +08:00
|
|
|
return results
|
|
|
|
it = inner()
|
|
|
|
next(it)
|
|
|
|
return it
|
2020-07-28 16:09:24 +08:00
|
|
|
|
2020-07-29 11:27:11 +08:00
|
|
|
def interpret_field(field):
|
|
|
|
bit_pattern = re.compile(r'(\d+)(:(\d+))?')
|
|
|
|
m = bit_pattern.fullmatch(field['bits'][0])
|
|
|
|
if m is None:
|
|
|
|
raise ValueError(field['bits'])
|
|
|
|
high = int(m.group(1))
|
|
|
|
low = high
|
|
|
|
if m.group(3) is not None:
|
|
|
|
low = int(m.group(3))
|
|
|
|
result = {}
|
2020-07-29 11:58:56 +08:00
|
|
|
result['name'] = field['name'][0].strip()
|
2020-07-29 11:27:11 +08:00
|
|
|
result['bits'] = [low, high]
|
2020-07-29 11:58:56 +08:00
|
|
|
result['access'] = field['access'][0].strip()
|
2020-07-29 11:27:11 +08:00
|
|
|
return result
|
|
|
|
|
2020-07-29 10:49:41 +08:00
|
|
|
def interpret(reg):
|
|
|
|
pattern = re.compile(r'(.+\w)\d+')
|
2020-07-29 11:27:11 +08:00
|
|
|
width_pattern = re.compile(r'(\d+)\s?bits')
|
2020-07-29 10:49:41 +08:00
|
|
|
result = {}
|
|
|
|
name_pattern = None
|
2020-07-29 11:27:11 +08:00
|
|
|
expected = [
|
2020-07-29 11:58:56 +08:00
|
|
|
['Name', 'name', lambda v: ''.join(v).strip()],
|
2020-07-29 11:27:11 +08:00
|
|
|
['Relative Address', 'rel', lambda v: int(v[0], 16)],
|
|
|
|
['Absolute Address', 'abs', lambda v: int(v[0], 16)],
|
|
|
|
['Width', 'width', lambda v: int(width_pattern.fullmatch(v[0]).group(1))],
|
2020-07-29 11:58:56 +08:00
|
|
|
['Access Type', 'access', lambda v: v[0].strip()],
|
2020-07-29 11:27:11 +08:00
|
|
|
['Reset Value', 'reset', lambda v: v[0]],
|
|
|
|
['Description', 'description', lambda v: ' '.join(v)]
|
|
|
|
]
|
2020-07-29 10:49:41 +08:00
|
|
|
for v in reg['def']:
|
|
|
|
a = v['key']
|
|
|
|
b = v['value']
|
|
|
|
key = a[0].strip()
|
2020-07-29 11:27:11 +08:00
|
|
|
if len(expected) > 0 and key == expected[0][0]:
|
|
|
|
result[expected[0][1]] = expected[0][2](b)
|
2020-07-29 10:49:41 +08:00
|
|
|
expected = expected[1:]
|
|
|
|
if key == 'Name':
|
|
|
|
m = pattern.fullmatch(b[0].strip())
|
|
|
|
if m is not None:
|
|
|
|
name_pattern = m.group(1)
|
|
|
|
elif name_pattern != None and key.startswith(name_pattern):
|
|
|
|
if 'similar' not in result:
|
|
|
|
result['similar'] = []
|
2020-07-29 11:27:11 +08:00
|
|
|
result['similar'].append({'name': key, 'abs': int(b[0], 16)})
|
|
|
|
result['fields'] = [interpret_field(f) for f in reg['fields']]
|
|
|
|
assert len(expected) == 0
|
2020-07-29 10:49:41 +08:00
|
|
|
return result
|
|
|
|
|
2020-07-29 14:28:02 +08:00
|
|
|
def to_camel(name: str):
|
2020-07-29 11:27:11 +08:00
|
|
|
result = []
|
2020-07-29 14:28:02 +08:00
|
|
|
parts = name.split('_')
|
|
|
|
for p in parts:
|
|
|
|
if len(p) == 0:
|
|
|
|
continue
|
|
|
|
p = p[0].upper() + p[1:]
|
|
|
|
result.append(p)
|
|
|
|
return ''.join(result)
|
|
|
|
|
|
|
|
def to_snake(text: str):
|
|
|
|
if len(text) == 0:
|
|
|
|
return ''
|
|
|
|
if '_' in text:
|
|
|
|
return text.lower()
|
|
|
|
|
|
|
|
prev_upper = text[-1].isupper()
|
|
|
|
result = []
|
|
|
|
for c in reversed(text):
|
|
|
|
current_upper = c.isupper()
|
|
|
|
if prev_upper and not current_upper:
|
|
|
|
if len(result) > 0 and result[-1] != '_':
|
|
|
|
result.append('_')
|
|
|
|
result.append(c.lower())
|
|
|
|
elif not prev_upper and current_upper:
|
|
|
|
result.append(c.lower())
|
|
|
|
result.append('_')
|
2020-07-29 11:27:11 +08:00
|
|
|
else:
|
|
|
|
result.append(c.lower())
|
2020-07-29 14:28:02 +08:00
|
|
|
prev_upper = current_upper
|
|
|
|
if result[-1] == '_':
|
|
|
|
result.pop()
|
|
|
|
return ''.join(reversed(result))
|
2020-07-29 11:27:11 +08:00
|
|
|
|
2020-07-29 11:58:56 +08:00
|
|
|
def access_to_type(access: str):
|
|
|
|
access = access.upper()
|
|
|
|
if access in ['RO', 'RW', 'WO']:
|
|
|
|
return access
|
|
|
|
elif access in ['MIXED', 'WTC']:
|
|
|
|
return 'RW'
|
|
|
|
raise ValueError(access)
|
|
|
|
|
|
|
|
def fields_to_rust(reg):
|
|
|
|
fields = reg['fields']
|
|
|
|
name_pattern = re.compile(r'(.+\w)\d+')
|
|
|
|
access = access_to_type(reg['access'])
|
2020-07-29 14:28:02 +08:00
|
|
|
assert reg['width'] in [8, 16, 32]
|
2020-07-29 11:58:56 +08:00
|
|
|
if fields == []:
|
|
|
|
return (f'{access}<u{reg["width"]}>', [])
|
|
|
|
if len(fields) == 1:
|
|
|
|
bits = fields[0]['bits']
|
|
|
|
if bits[1] - bits[0] + 1 == reg['width']:
|
|
|
|
return (f'{access_to_type(reg["access"])}<u{reg["width"]}>', [])
|
2020-07-29 14:28:02 +08:00
|
|
|
namespace = to_snake(reg['name'])
|
|
|
|
name = to_camel(reg['name'])
|
2020-07-29 11:58:56 +08:00
|
|
|
if 'similar' in reg:
|
|
|
|
# remove the trailing digits
|
|
|
|
name = name_pattern.fullmatch(name).group(1)
|
|
|
|
namespace = name_pattern.fullmatch(namespace).group(1)
|
|
|
|
bitmask = 0
|
|
|
|
has_wtc = False
|
|
|
|
lines = []
|
|
|
|
for f in fields:
|
2020-07-29 14:28:02 +08:00
|
|
|
field_name = to_snake(f['name'])
|
|
|
|
if field_name in ['na', 'reserved']:
|
|
|
|
continue
|
2020-07-29 11:58:56 +08:00
|
|
|
field_access = f['access'].upper()
|
|
|
|
[low, high] = f['bits']
|
|
|
|
assert low <= high
|
|
|
|
if field_access == 'WTC':
|
|
|
|
has_wtc = True
|
|
|
|
else:
|
2020-07-29 14:28:02 +08:00
|
|
|
assert field_access in ['RO', 'RW', 'WO']
|
2020-07-29 11:58:56 +08:00
|
|
|
for i in range(high - low + 1):
|
|
|
|
bitmask |= 1 << (i + low)
|
|
|
|
if low == high:
|
|
|
|
wtc = ', WTC' if field_access == 'WTC' else ''
|
|
|
|
lines.append(f'register_bit!({namespace}, {field_name}, {low}{wtc});')
|
|
|
|
else:
|
|
|
|
value_range = ''
|
|
|
|
if high - low < 8:
|
|
|
|
value_range = 'u8'
|
|
|
|
elif high - low < 32:
|
|
|
|
value_range = 'u32'
|
|
|
|
else:
|
|
|
|
raise ValueError([low, high])
|
|
|
|
if field_access == 'WTC':
|
|
|
|
# we did not implement WTC for multiple bits, but could be done
|
|
|
|
raise ValueError()
|
|
|
|
lines.append(f'register_bits!({namespace}, {field_name},'
|
2020-07-29 14:28:02 +08:00
|
|
|
f' {value_range}, {low}, {high});')
|
2020-07-29 11:58:56 +08:00
|
|
|
if has_wtc:
|
|
|
|
lines.insert(0, f'register!({namespace}, {name}, {access},'
|
|
|
|
f' u{reg["width"]}, {bitmask});')
|
|
|
|
else:
|
|
|
|
lines.insert(0, f'register!({namespace}, {name}, {access},'
|
|
|
|
f' u{reg["width"]});')
|
|
|
|
return (name, lines)
|
|
|
|
|
2020-07-28 16:09:24 +08:00
|
|
|
def emit_rust(base_addr, ending_addr, registers):
|
|
|
|
current_addr = base_addr
|
|
|
|
reserved_id = 0
|
|
|
|
code = []
|
2020-07-29 14:28:02 +08:00
|
|
|
fields = []
|
|
|
|
def advance_to(addr, width):
|
|
|
|
nonlocal current_addr, reserved_id, code
|
2020-07-28 16:09:24 +08:00
|
|
|
padding = addr - current_addr
|
2020-07-29 14:28:02 +08:00
|
|
|
assert padding >= 0
|
2020-07-28 16:09:24 +08:00
|
|
|
if padding > 0:
|
|
|
|
if padding % 4 == 0:
|
2020-07-29 14:28:02 +08:00
|
|
|
code.append(f' unused{reserved_id}: [u32; {padding // 4}],')
|
2020-07-28 16:09:24 +08:00
|
|
|
else:
|
2020-07-29 14:28:02 +08:00
|
|
|
code.append(f' unused{reserved_id}: [u8; {padding}],')
|
2020-07-28 16:09:24 +08:00
|
|
|
reserved_id += 1
|
2020-07-29 14:28:02 +08:00
|
|
|
assert width in [8, 16, 32]
|
|
|
|
current_addr += padding + width // 8
|
|
|
|
|
|
|
|
for reg in registers:
|
|
|
|
reg = interpret(reg)
|
|
|
|
(typename, lines) = fields_to_rust(reg)
|
|
|
|
description = reg['description']
|
|
|
|
addr = reg['abs']
|
|
|
|
if addr > ending_addr:
|
|
|
|
break
|
|
|
|
if 'similar' in reg:
|
|
|
|
for r in reg['similar']:
|
|
|
|
addr = r['abs']
|
|
|
|
if addr > ending_addr:
|
|
|
|
break
|
|
|
|
if addr < base_addr:
|
|
|
|
continue
|
|
|
|
advance_to(addr, reg['width'])
|
|
|
|
# add description for the first one
|
|
|
|
if description is not None:
|
|
|
|
code.append(f' /// {description}')
|
|
|
|
description = None
|
|
|
|
if len(lines) > 0:
|
|
|
|
fields.append('')
|
|
|
|
fields += lines
|
|
|
|
lines = []
|
|
|
|
code.append(f' pub {reg["name"].lower()}: {typename},')
|
2020-07-28 16:09:24 +08:00
|
|
|
else:
|
2020-07-29 14:28:02 +08:00
|
|
|
addr = reg['abs']
|
|
|
|
if addr > ending_addr:
|
|
|
|
break
|
|
|
|
if addr < base_addr:
|
|
|
|
continue
|
|
|
|
advance_to(addr, reg['width'])
|
|
|
|
code.append(f' /// {description}')
|
|
|
|
code.append(f' pub {reg["name"].lower()}: {typename},')
|
|
|
|
if len(lines) > 0:
|
|
|
|
fields.append('')
|
|
|
|
fields += lines
|
|
|
|
code.insert(0, '#[repr(C)]')
|
|
|
|
code.insert(1, 'pub struct RegisterBlock {')
|
|
|
|
code.append('}')
|
|
|
|
code += fields
|
2020-07-28 16:09:24 +08:00
|
|
|
return code
|
|
|
|
|
2020-07-29 14:28:02 +08:00
|
|
|
if __name__ == '__main__':
|
|
|
|
if len(sys.argv) != 3:
|
|
|
|
print("Please read the README")
|
|
|
|
exit(0)
|
|
|
|
|
|
|
|
parser = parse_registers()
|
|
|
|
for line in sys.stdin:
|
|
|
|
parser.send(line)
|
|
|
|
v = end_iterator(parser)
|
|
|
|
for line in emit_rust(int(sys.argv[1], 0), int(sys.argv[2], 0), v):
|
|
|
|
print(line)
|
|
|
|
|