import re import sys import json ADDRESS_PATTERN = r'0x[0-9A-F]+' def parse_table_entries(fields): """Parse aligned table entries""" 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 entries = [] def flush(): nonlocal prev if prev is not None: entries.append(prev['data']) prev = None while True: line = yield if line == None: break line = line.rstrip() if len(line.strip()) == 0: flush() continue match = pattern.fullmatch(line) if match is not None: flush() starts = [match.span(v)[0] for v in fields] prev = { 'data': {v: [match.group(v)] for v in fields}, 'starts': starts } elif prev is not None: for i, field in enumerate(fields): if i + 1 == len(fields): v = line[prev['starts'][i]:].strip() else: v = line[prev['starts'][i]:prev['starts'][i + 1]].strip() if len(v) == 0: continue prev['data'][field].append(v) flush() return entries def parse_register_fields(): fields = [ r'name: \w+', r'bits: \d+(:\d+)?', r'access: \w+', f'reset: {ADDRESS_PATTERN}', r'description: .+' ] it = parse_table_entries(fields) next(it) return it def end_iterator(it): try: it.send(None) except StopIteration as e: return e.value def parse_registers(): def two_column(width): # only require the leftmost column starts with a \w, and the a space # separator between the two columns... fields = [ f'key: \\w.{{1,{width-2}}}\\s', r'value: \w.+' ] it = parse_table_entries(fields) next(it) return it def inner(): 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+') state = 0 it = None results = [] while True: line = yield if line == None: break 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]) it.send(line) 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) if it is not None: if state == 0: results[-1]['fields']= end_iterator(it) return results it = inner() next(it) return it 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 = {} result['name'] = ''.join(field['name']).strip() result['bits'] = [low, high] result['access'] = field['access'][0].strip() return result def interpret(reg): pattern = re.compile(r'(.+\w)\d+') width_pattern = re.compile(r'(\d+)\s?bits') result = {} name_pattern = None expected = [ ['Name', 'name', lambda v: ''.join(v).strip()], ['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))], ['Access Type', 'access', lambda v: v[0].strip()], ['Reset Value', 'reset', lambda v: v[0]], ['Description', 'description', lambda v: ' '.join(v)] ] for v in reg['def']: a = v['key'] b = v['value'] key = ''.join([p.strip() for p in a]).strip() if len(expected) > 0 and key == expected[0][0]: result[expected[0][1]] = expected[0][2](b) 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'] = [] result['similar'].append({'name': key, 'abs': int(b[0], 16)}) result['fields'] = [interpret_field(f) for f in reg['fields']] assert len(expected) == 0 return result def to_camel(name: str): result = [] 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('_') else: result.append(c.lower()) prev_upper = current_upper if result[-1] == '_': result.pop() return ''.join(reversed(result)) 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']) assert reg['width'] in [8, 16, 32] if fields == []: return (f'{access}', []) if len(fields) == 1: bits = fields[0]['bits'] if bits[1] - bits[0] + 1 == reg['width']: return (f'{access_to_type(reg["access"])}', []) namespace = to_snake(reg['name']) name = to_camel(reg['name']) 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: field_name = to_snake(f['name']) if field_name in ['na', 'reserved']: continue field_access = f['access'].upper() [low, high] = f['bits'] assert low <= high if field_access == 'WTC': has_wtc = True else: assert field_access in ['RO', 'RW', 'WO'] 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},' f' {value_range}, {low}, {high});') 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) def emit_rust(base_addr, ending_addr, registers): current_addr = base_addr reserved_id = 0 code = [] fields = [] def advance_to(addr, width): nonlocal current_addr, reserved_id, code padding = addr - current_addr assert padding >= 0 if padding > 0: if padding % 4 == 0: code.append(f' unused{reserved_id}: [u32; {padding // 4}],') else: code.append(f' unused{reserved_id}: [u8; {padding}],') reserved_id += 1 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 {r["name"].lower()}: {typename},') else: 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 return code 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)