diff --git a/pysvd/classes.py b/pysvd/classes.py index 3c12432..96f0750 100644 --- a/pysvd/classes.py +++ b/pysvd/classes.py @@ -116,6 +116,9 @@ def parse(self, node): super().parse(node) self.name = pysvd.parser.Text(pysvd.node.Element(node, 'name', True)) + displayName = pysvd.parser.Text(pysvd.node.Element(node, 'displayName')) + if displayName is not None: + self.displayName = displayName description = pysvd.parser.Text(pysvd.node.Element(node, 'description')) if description is not None: self.description = description @@ -125,6 +128,8 @@ def parse(self, node): def set_index(self, value): value = str(value) self.name = self.name.replace('%s', value) + if hasattr(self, 'displayName') and self.displayName is not None: + self.displayName = self.displayName.replace('%s', value) if hasattr(self, 'description') and self.description is not None: self.description = self.description.replace('%s', value) diff --git a/pysvd/element.py b/pysvd/element.py index 54965bc..ec8eb72 100644 --- a/pysvd/element.py +++ b/pysvd/element.py @@ -4,6 +4,17 @@ import pysvd +def compare_attribute(lhs, rhs, attibute): + """Compare attibute of objects. + """ + if hasattr(lhs, attibute) != hasattr(rhs, attibute): + return False + + if hasattr(lhs, attibute): + return getattr(lhs, attibute) == getattr(rhs, attibute) + + return True + # /device # http://www.keil.com/pack/doc/cmsis/svd/html/elem_device.html class Device(pysvd.classes.Base): @@ -190,6 +201,49 @@ def __init__(self, parent, node): super().__init__(parent, node) + def __eq__(self, other): + """Compare element and all attributes. + """ + if not isinstance(other, Peripheral): + return NotImplemented + + if not self.equal_struct(other): + return False + + return compare_attribute(self, other, 'name') and \ + compare_attribute(self, other, 'description') and \ + compare_attribute(self, other, 'version') and \ + compare_attribute(self, other, 'alternatePeripheral') and \ + compare_attribute(self, other, 'groupName') and \ + compare_attribute(self, other, 'prependToName') and \ + compare_attribute(self, other, 'appendToName') and \ + compare_attribute(self, other, 'headerStructName') and \ + compare_attribute(self, other, 'disableCondition') and \ + compare_attribute(self, other, 'baseAddress') and \ + compare_attribute(self, other, 'addressBlock') and \ + compare_attribute(self, other, 'interrupt') + + def equal_struct(self, other): + """Compare only child elements. + """ + if not isinstance(other, Peripheral): + return NotImplemented + + if len(self.registers) != len(other.registers): + return False + + for (lhs, rhs) in zip(self.registers, other.registers): + if lhs != rhs: + return False + + if len(self.clusters) != len(other.clusters): + return False + + for (lhs, rhs) in zip(self.clusters, other.clusters): + if lhs != rhs: + return False + return True + def set_offset(self, value): self.baseAddress += value @@ -348,6 +402,44 @@ def __init__(self, parent, node): super().__init__(parent, node) + def __eq__(self, other): + """Compare element and all attributes. + """ + if not isinstance(other, Register): + return NotImplemented + + if not self.equal_struct(other): + return False + + return compare_attribute(self, other, 'name') and \ + compare_attribute(self, other, 'displayName') and \ + compare_attribute(self, other, 'description') and \ + compare_attribute(self, other, 'alternateGroup') and \ + compare_attribute(self, other, 'alternateRegister') and \ + compare_attribute(self, other, 'addressOffset') and \ + compare_attribute(self, other, 'size') and \ + compare_attribute(self, other, 'access') and \ + compare_attribute(self, other, 'protection') and \ + compare_attribute(self, other, 'resetValue') and \ + compare_attribute(self, other, 'resetMask') and \ + compare_attribute(self, other, 'dataType') and \ + compare_attribute(self, other, 'modifiedWriteValues') and \ + compare_attribute(self, other, 'readAction') + + def equal_struct(self, other): + """Compare only child elements. + """ + if not isinstance(other, Register): + return NotImplemented + + if len(self.fields) != len(other.fields): + return False + + for (lhs, rhs) in zip(self.fields, other.fields): + if lhs != rhs: + return False + return True + def set_offset(self, value): self.addressOffset += value @@ -447,6 +539,41 @@ class Field(pysvd.classes.Dim): def __init__(self, parent, node): super().__init__(parent, node) + def __eq__(self, other): + if not isinstance(other, Field): + return NotImplemented + + if not self.equal_struct(other): + return False + + return compare_attribute(self, other, 'name') and \ + compare_attribute(self, other, 'description') and \ + compare_attribute(self, other, 'bitOffset') and \ + compare_attribute(self, other, 'bitWidth') and \ + compare_attribute(self, other, 'access') and \ + compare_attribute(self, other, 'modifiedWriteValues') and \ + compare_attribute(self, other, 'readAction') and \ + compare_attribute(self, other, 'writeConstraint') + + def equal_struct(self, other): + """Check structure elements, but not overridable attributes. + """ + if not isinstance(other, Field): + return NotImplemented + + return compare_attribute(self, other, 'enumeratedValues') + + x = """ + if len(self.enumeratedValues) != len(other.enumeratedValues): + return False + + for (lhs, rhs) in zip(self.enumeratedValues, other.enumeratedValues): + if lhs != rhs: + return False + return True + """ + + def set_offset(self, value): self.bitOffset += value @@ -521,6 +648,29 @@ def __init__(self, parent, node): super().__init__(parent, node) + def __eq__(self, other): + if not isinstance(other, EnumeratedValues): + return NotImplemented + + if not self.equal_struct(other): + return False + + return compare_attribute(self, other, 'name') and \ + compare_attribute(self, other, 'headerEnumName') and \ + compare_attribute(self, other, 'usage') + + def equal_struct(self, other): + if not isinstance(other, EnumeratedValues): + return NotImplemented + + if len(self.enumeratedValues) != len(other.enumeratedValues): + return False + + for (lhs, rhs) in zip(self.enumeratedValues, other.enumeratedValues): + if lhs != rhs: + return False + return True + def parse(self, node): super().parse(node) @@ -545,6 +695,15 @@ class EnumeratedValue(pysvd.classes.Parent): def __init__(self, parent, node): super().__init__(parent, node) + def __eq__(self, other): + if not isinstance(other, EnumeratedValue): + return NotImplemented + + return compare_attribute(self, other, 'name') and \ + compare_attribute(self, other, 'value') and \ + compare_attribute(self, other, 'description') and \ + compare_attribute(self, other, 'isDefault') + def parse(self, node): super().parse(node) diff --git a/requirements.txt b/requirements.txt index 8e2fbdc..d9fe426 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ pytest-codestyle pytest-flakes pytest-cov coveralls +naturalsort +colorama diff --git a/scripts/svd2register.py b/scripts/svd2register.py new file mode 100644 index 0000000..3308b0a --- /dev/null +++ b/scripts/svd2register.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# coding: utf-8 +"""pysvd example project. + +Read SVD file and generate C-style register access structs. +""" + +import argparse +import xml.etree.ElementTree as ET +import pysvd +from enum import Enum + +access = { + pysvd.type.access.read_write: '__IO', + pysvd.type.access.read_only: '__I', + pysvd.type.access.write_only: '__O', +} + +data_type = { + 8: 'uint8_t', + 16: 'uint16_t', + 32: 'uint32_t', +} + +def format_registers(output, prefix, registers): + data = [] + for register in registers: + data.append(('`{1} <{0}.{1}_>`_'.format(prefix, register.name), '0x{:02X}'.format(register.addressOffset))) + if len(data): + output.write(table(('Register', 'Offset'), data)) + + for register in registers: + output.write('.. _{}.{}:\n\n'.format(prefix, register.name)) + + output.write(underline(register.description, section.subsubsection)) + + output.write(rst_list_name.format('Name', register.name)) + output.write(rst_list_name.format('Size', register.size)) + output.write(rst_list_name.format('Offset', '0x{:02X}'.format(register.addressOffset))) + output.write(rst_list_name.format('Reset', '0x{:{fill}{width}X}'.format(register.resetValue, fill='0', width=register.size // 4))) + output.write(rst_list_name.format('Access', register.access)) + output.write('\n') + + # Table + + # Bit description + for field in register.fields: + if field.bitWidth == 1: + output.write('- Bit {} ({}) - {}\n'.format(field.bitOffset, field.access, field.name)) + else: + output.write('- Bits {}:{} ({}) - {}\n'.format( + field.bitOffset + field.bitWidth - 1, field.bitOffset, field.access, field.name)) + output.write(' {}\n\n'.format(field.description)) + + if hasattr(field, 'enumeratedValues'): + for enumeratedValue in field.enumeratedValues.enumeratedValues: + output.write(' - {} - {}\n'.format(enumeratedValue.value, enumeratedValue.name)) + if hasattr(enumeratedValue, 'description'): + output.write(' {}\n'.format(enumeratedValue.description)) + output.write('\n') + + +def write_register(register, output): + output.write(" union\n {\n") + output.write(" {1} {2} {0.name};\n".format(register, access[register.access], data_type[register.size])) + + +def write_peripheral(peripheral, output): + output.write("/* {0.name} - {0.description} */\n\n".format(peripheral)) + + index = 1; + offset = 0 + output.write("struct __attribute__((packed)) {0.name}_t\n{{\n".format(peripheral)) + for register in peripheral.registers: + if register.addressOffset != offset: + output.write(" __I {0} _{1};\n".format(data_type[register.addressOffset - offset]), index) + index += 1 + write_register(register, output) + offset = register.addressOffset + register.size // 8 + output.write("};\n") + output.write("static_assert(sizeof({0}_t) == {1}, \"Size of {0}_t mismatch\");\n\n".format(peripheral.name, offset)) + +def main(): + parser = argparse.ArgumentParser(description='SVD to C-style register access structs') + parser.add_argument('--svd', metavar='FILE', type=str, help='System view description (SVD) file', required=True) + parser.add_argument('--output', '-o', metavar='FILE', type=str, help='C output file', required=True) + parser.add_argument('--version', action='version', version=pysvd.__version__) + args = parser.parse_args() + + node = ET.parse(args.svd).getroot() + device = pysvd.element.Device(node) + + output = open(args.output, "w") + + # Header + output.write("/**\n" \ + " * @file\n" \ + " * @version {0.version}\n" \ + " * @brief Register access structs for {0.vendor} {0.name}\n" \ + " * @note This file is autogenerated using pysvd {1.__version__}\n" \ + " */\n\n".format(device, pysvd) + ) + output.write("#pragma once\n\n") + + output.write("#define __I volatile const /*!< Read only permission */\n" \ + "#define __O volatile /*!< Write only permission */\n" \ + "#define __IO volatile /*!< Read/write permission */\n\n" + ) + + # Interrupts + interrupts = [] + for peripheral in device.peripherals: + interrupts += peripheral.interrupts + + interrupt_count = 0 + interrupt_name = "" + output.write("enum IRQn\n{\n") + for interrupt in sorted(interrupts, key=lambda interrupt: interrupt.value): + if interrupt_name != interrupt.name: + output.write(" {0.name:20}= {0.value},\n".format(interrupt)) + interrupt_name = interrupt.name + interrupt_count += 1 + output.write("};\n\n") + + # Peripherals + data = [] + for peripheral in sorted(device.peripherals, key=lambda peripheral: peripheral.name): + if peripheral.name.startswith('ADC'): + write_peripheral(peripheral, output) + output.close() + +if __name__ == '__main__': + main() diff --git a/scripts/svd_duplicates.py b/scripts/svd_duplicates.py new file mode 100644 index 0000000..e0853c9 --- /dev/null +++ b/scripts/svd_duplicates.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# coding: utf-8 +"""Read SVD file, order elements, check for valid elements to generate register access structs and displays possible substitutions. +""" + +import sys +import argparse +import xml.etree.ElementTree as ET +from enum import IntEnum +import itertools +from natsort import natsort +from colorama import Fore, Back, Style + +import pysvd + +class Level(IntEnum): + """Output level.""" + warning = 0 + hint = 1 + all = 2 + +class Depth(IntEnum): + """Analysis depth.""" + peripherals = 0 + registers = 1 + fields = 2 + enumeratedValues = 3 + +def integer(value): + """Convert binary, hex, octal and decimal strings to integer.""" + if value.startswith('#'): + value = value.replace('#', '0b') + + if value.startswith('0b'): + value = value.replace('x', '0') + return int(value, 0) + +def compare_peripherals(level, peripherals): + """Compare peripherals.""" + + print("{0.CYAN}Peripherals{0.RESET}".format(Fore)) + + peripherals_base = [] + peripherals_derived = [] + peripherals_not_derived = [] + for a, b in itertools.combinations(peripherals, 2): + # Check if item is already in list + if a in peripherals_derived + peripherals_not_derived: + continue + + if a.equal_struct(b): + if a not in peripherals_base: + peripherals_base.append(a) + + # Already derived peripheral + if b.derivedFrom: + if level >= Level.all: + print("[{0.GREEN}OK{0.RESET}] Peripheral '{2.name}' is derived from '{1.name}'".format(Fore, b.derivedFrom, b)) + peripherals_derived.append(b) + # Derivable peripheral + else: + if level >= Level.warning: + print("[{0.RED}WARNING{0.RESET}] Peripheral '{2.name}' can be derived from '{1.name}'".format(Fore, a, b)) + peripherals_not_derived.append(b) + + peripherals_none_derivable = [peripheral for peripheral in peripherals \ + if peripheral not in peripherals_base and \ + peripheral not in peripherals_derived and \ + peripheral not in peripherals_not_derived] + + if level >= Level.hint: + for peripheral in peripherals_none_derivable: + print("[{0.YELLOW}HINT{0.RESET}] Peripheral '{1.name}' can not be derived".format(Fore, peripheral)) + print() + + return (peripherals_base, peripherals_none_derivable) + +def compare_registers(level, peripheral, registers): + print("{0.CYAN}Registers of peripheral '{1}'{0.RESET}".format(Fore, peripheral.name)) + + registers_base = [] + registers_derived = [] + registers_not_derived = [] + + for a, b in itertools.combinations(registers, 2): + # Check if item is already in list + if a in registers_derived + registers_not_derived: + continue + + if a.equal_struct(b): + if a not in registers_base: + registers_base.append(a) + + # Already derived register + if b.derivedFrom: + if level >= Level.all: + print("[{0.GREEN}OK{0.RESET}] Register '{2.name}' is derived from '{1.name}'".format(Fore, b.derivedFrom, b)) + registers_derived.append(b) + # Derivable register + else: + if level >= Level.warning: + print("[{0.RED}WARNING{0.RESET}] Register '{2.name}' can be derived from '{1.name}'".format(Fore, a, b)) + registers_not_derived.append(b) + + registers_none_derivable = [register for register in registers \ + if register not in registers_base and \ + register not in registers_derived and \ + register not in registers_not_derived] + + if level >= Level.hint: + for register in registers_none_derivable: + print("[{0.YELLOW}HINT{0.RESET}] Register '{1.name}' can not be derived".format(Fore, register)) + print() + + return (registers_base, registers_none_derivable) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Read SVD file, order elements, check for' \ + 'valid elements to generate register access structs and displays possible substitutions.') + parser.add_argument('--svd', metavar='FILE', type=str, help='System view description (SVD) file', required=True) + parser.add_argument('--output', '-o', metavar='FILE', type=str, help='Save ordered SVD output file') + parser.add_argument('--level', '-l', choices=['all', 'hint', 'warning'], help='Select level of output messages', default='all') + parser.add_argument('--depth', '-d', choices=['peripherals', 'registers', 'fields', 'enumeratedValues'], help='Select depth of analysis', default='enumeratedValues') + args = parser.parse_args() + level = Level[args.level] + depth = Depth[args.depth] + + xml = ET.parse(args.svd) + + print('Sort peripherals by name') + for peripherals in xml.findall('.//peripherals'): + peripherals[:] = natsort(peripherals, key=lambda x: x.find('name').text) + + print('Sort registers by addressOffset') + for registers in xml.findall('.//registers'): + registers[:] = sorted(registers, key=lambda x: integer(x.find('addressOffset').text)) + + print('Sort fields by bitOffset') + for fields in xml.findall('.//fields'): + fields[:] = sorted(fields, key=lambda x: integer(x.find('bitOffset').text)) + + print('Sort enumeratedValues by value') + for enumeratedValues in xml.findall('.//enumeratedValues'): + enumeratedValues[:] = sorted(enumeratedValues, key=lambda x: integer(x.find('value').text)) + + print('Remove linebreaks from description tags') + for item in xml.findall('.//description'): + text = item.text.replace('\n', ' ').strip() + item.text = ' '.join(text.split()) + + if args.output: + xml.write(args.output, encoding="utf-8", xml_declaration=True, method="xml", short_empty_elements=True) + print() + + # Load ordered SVD file + try: + device = pysvd.element.Device(xml.getroot()) + except Exception as e: + print("Error parsing SVD file: {}".format(str(e))) + sys.exit(2) + + (peripherals_base, peripherals_none_derivable) = compare_peripherals(level, device.peripherals) + if depth >= Depth.registers: + peripherals = natsort(peripherals_base + peripherals_none_derivable, key=lambda peripheral: peripheral.name) + for peripheral in peripherals: + compare_registers(level, peripheral, peripheral.registers) diff --git a/setup.py b/setup.py index 88c5055..c5db26f 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ entry_points={ 'console_scripts': [ 'svd2rst = scripts.svd2rst:main', + 'svd2register = scripts.svd2register:main', ], }, setup_requires=["pytest-runner"],