""" svdpatch.py Copyright 2017-2019 Adam Greig. Licensed under the MIT and Apache 2.0 licenses. See LICENSE files for details. This script was adapted from stm32-rs (https://github.com/stm32-rs/stm32-rs/blob/master/scripts/svdpatch.py). To be integrated into the build system, support for an input / output svd argument is added. Additional changes: - Support for _replace_enum to overwrite existing - Fix field modifications not being able to add new elements - Add _write_constraint modifier for changing the """ import copy import yaml import os.path import argparse import xml.etree.ElementTree as ET from fnmatch import fnmatch from collections import OrderedDict DEVICE_CHILDREN = [ "vendor", "vendorID", "name", "series", "version", "description", "licenseText", "headerSystemFilename", "headerDefinitionsPrefix", "addressUnitBits", "width", "size", "access", "protection", "resetValue", "resetMask"] # Set up pyyaml to use ordered dicts so we generate the same # XML output each time def dict_constructor(loader, node): return OrderedDict(loader.construct_pairs(node)) _mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG yaml.add_constructor(_mapping_tag, dict_constructor, yaml.SafeLoader) def parseargs(): """Parse our command line arguments, returns a Namespace of results.""" parser = argparse.ArgumentParser() parser.add_argument("yaml", help="Path to YAML file to load") parser.add_argument("svd_in", help="Path to the input SVD file") parser.add_argument("svd_out", help="Path to write the patched SVD file") args = parser.parse_args() return args def matchname(name, spec): """Check if name matches against a specification.""" return ( (not spec.startswith("_")) and any(fnmatch(name, subspec) for subspec in spec.split(","))) def abspath(frompath, relpath): """Gets the absolute path of relpath from the point of view of frompath.""" basepath = os.path.realpath( os.path.join(os.path.abspath(frompath), os.pardir)) return os.path.normpath(os.path.join(basepath, relpath)) def update_dict(parent, child): """ Recursively merge child.key into parent.key, with parent overriding. """ for key in child: if key == "_path" or key == "_include": continue elif key in parent: if isinstance(parent[key], list): parent[key] += child[key] elif isinstance(parent[key], dict): update_dict(parent[key], child[key]) else: parent[key] = child[key] def yaml_includes(parent): """Recursively loads any included YAML files.""" included = [] for relpath in parent.get("_include", []): path = abspath(parent["_path"], relpath) if path in included: continue with open(path) as f: child = yaml.safe_load(f) child["_path"] = path included.append(path) # Process any peripheral-level includes in child for pspec in child: if not pspec.startswith("_") and "_include" in child[pspec]: child[pspec]["_path"] = path included += yaml_includes(child[pspec]) # Process any top-level includes in child included += yaml_includes(child) update_dict(parent, child) return included def make_write_constraint(wc_range): """Given a (min, max), returns a writeConstraint Element.""" wc = ET.Element('writeConstraint') r = ET.SubElement(wc, 'range') minimum = ET.SubElement(r, 'minimum') minimum.text = str(wc_range[0]) maximum = ET.SubElement(r, 'maximum') maximum.text = str(wc_range[1]) wc.tail = "\n " return wc def make_enumerated_values(name, values, usage="read-write"): """ Given a name and a dict of values which maps variant names to (value, description), returns an enumeratedValues Element. """ ev = ET.Element('enumeratedValues') usagekey = { "read": "R", "write": "W", }.get(usage, "") ET.SubElement(ev, 'name').text = name + usagekey ET.SubElement(ev, 'usage').text = usage if len(set(v[0] for v in values.values())) != len(values): raise ValueError("enumeratedValue {}: can't have duplicate values" .format(name)) if name[0] in "0123456789": raise ValueError("enumeratedValue {}: can't start with a number" .format(name)) for vname in values: if vname.startswith("_"): continue if vname[0] in "0123456789": raise ValueError("enumeratedValue {}.{}: can't start with a number" .format(name, vname)) value, description = values[vname] if not description: raise ValueError("enumeratedValue {}: can't have empty description" " for value {}".format(name, value)) el = ET.SubElement(ev, 'enumeratedValue') ET.SubElement(el, 'name').text = vname ET.SubElement(el, 'description').text = description ET.SubElement(el, 'value').text = str(value) ev.tail = "\n " return ev def make_derived_enumerated_values(name): """Returns an enumeratedValues Element which is derivedFrom name.""" evd = ET.Element('enumeratedValues', {"derivedFrom": name}) evd.tail = "\n " return evd def iter_peripherals(tree, pspec, check_derived=True): """Iterates over all peripherals that match pspec.""" for ptag in tree.iter('peripheral'): name = ptag.find('name').text if matchname(name, pspec): if check_derived and "derivedFrom" in ptag.attrib: continue yield ptag def iter_registers(ptag, rspec): """ Iterates over all registers that match rspec and live inside ptag. """ for rtag in ptag.iter('register'): name = rtag.find('name').text if matchname(name, rspec): yield rtag def iter_fields(rtag, fspec): """ Iterates over all fields that match fspec and live inside rtag. """ for ftag in rtag.find('fields').iter('field'): name = ftag.find('name').text if matchname(name, fspec): yield ftag def process_device_peripheral_modify(device, pspec, pmod): """Modify pspec inside device according to pmod.""" for ptag in iter_peripherals(device, pspec): for (key, value) in pmod.items(): ptag.find(key).text = str(value) def process_device_child_modify(device, key, val): """Modify key inside device and set it to val.""" for child in device.findall(key): child.text = str(val) def process_device_cpu_modify(device, mod): """Modify the `cpu` node inside `device` according to `mod`.""" cpu = device.find('cpu') for key, val in mod.items(): field = cpu.find(key) if field is not None: field.text = str(val) else: field = ET.SubElement(cpu, key) field.text = str(val) def process_peripheral_modify(ptag, rspec, rmod): """Modify rspec inside ptag according to rmod.""" for rtag in iter_registers(ptag, rspec): for (key, value) in rmod.items(): rtag.find(key).text = value def process_register_modify(rtag, fspec, fmod): """Modify fspec inside rtag according to fmod.""" for ftag in iter_fields(rtag, fspec): for (key, value) in fmod.items(): if key == "_write_constraint": key = "writeConstraint" tag = ftag.find(key) if tag is None: tag = ET.SubElement(ftag, key) if key == "writeConstraint": # Remove existing constraint contents for child in list(tag): tag.remove(child) if value == "none": # Completely remove the existing writeConstraint ftag.remove(tag) elif value == "enum": # Only allow enumerated values enum_tag = ET.SubElement(tag, "useEnumeratedValues") enum_tag.text = "true" elif isinstance(value, list): # Allow a certain range range_tag = make_write_constraint(value).find("range") tag.append(range_tag) else: raise SvdPatchError('Unknown writeConstraint type {}' .format(repr(value))) else: # For all other tags, just set the value tag.text = str(value) def process_device_add(device, pname, padd): """Add pname given by padd to device.""" parent = device.find('peripherals') for ptag in parent.iter('peripheral'): if ptag.find('name').text == pname: raise SvdPatchError('device already has a peripheral {}' .format(pname)) if "derivedFrom" in padd: derived = padd["derivedFrom"] pnew = ET.SubElement(parent, 'peripheral', {"derivedFrom": derived}) else: pnew = ET.SubElement(parent, 'peripheral') ET.SubElement(pnew, 'name').text = pname for (key, value) in padd.items(): if key == "registers": ET.SubElement(pnew, 'registers') for rname in value: process_peripheral_add_reg(pnew, rname, value[rname]) elif key == "interrupts": for iname in value: process_peripheral_add_int(pnew, iname, value[iname]) elif key == "addressBlock": ab = ET.SubElement(pnew, 'addressBlock') for (ab_key, ab_value) in value.items(): ET.SubElement(ab, ab_key).text = str(ab_value) elif key != "derivedFrom": ET.SubElement(pnew, key).text = str(value) pnew.tail = "\n " def process_device_derive(device, pname, pderive): """ Remove registers from pname and mark it as derivedFrom pderive. Update all derivedFrom referencing pname. """ parent = device.find('peripherals') ptag = parent.find('./peripheral[name=\'{}\']'.format(pname)) derived = parent.find('./peripheral[name=\'{}\']'.format(pderive)) if ptag is None: raise SvdPatchError('peripheral {} not found'.format(pname)) if derived is None: raise SvdPatchError('peripheral {} not found'.format(pderive)) for (value) in list(ptag): if value.tag in ('name', 'baseAddress', 'interrupt'): continue ptag.remove(value) for value in ptag: last = value last.tail = "\n " ptag.set('derivedFrom', pderive) for p in parent.findall('./peripheral[@derivedFrom=\'{}\']'.format(pname)): p.set('derivedFrom', pderive) def process_device_rebase(device, pnew, pold): """ Move registers from pold to pnew. Update all derivedFrom referencing pold. """ parent = device.find('peripherals') old = parent.find('./peripheral[name=\'{}\']'.format(pold)) new = parent.find('./peripheral[name=\'{}\']'.format(pnew)) if old is None: raise SvdPatchError('peripheral {} not found'.format(pold)) if new is None: raise SvdPatchError('peripheral {} not found'.format(pnew)) for value in new: last = value last.tail = "\n " for (value) in list(old): if value.tag in ('name', 'baseAddress', 'interrupt'): continue old.remove(value) new.append(value) for value in old: last = value last.tail = "\n " del new.attrib['derivedFrom'] old.set('derivedFrom', pnew) for p in parent.findall('./peripheral[@derivedFrom=\'{}\']'.format(pold)): p.set('derivedFrom', pnew) def process_peripheral_add_reg(ptag, rname, radd): """Add rname given by radd to ptag.""" parent = ptag.find('registers') for rtag in parent.iter('register'): if rtag.find('name').text == rname: raise SvdPatchError('peripheral {} already has a register {}' .format(ptag.find('name').text, rname)) rnew = ET.SubElement(parent, 'register') ET.SubElement(rnew, 'name').text = rname ET.SubElement(rnew, 'fields') for (key, value) in radd.items(): if key == "fields": for fname in value: process_register_add(rnew, fname, value[fname]) else: ET.SubElement(rnew, key).text = str(value) rnew.tail = "\n " def process_peripheral_add_int(ptag, iname, iadd): """Add iname given by iadd to ptag.""" for itag in ptag.iter('interrupt'): if itag.find('name').text == iname: raise SvdPatchError('peripheral {} already has an interrupt {}' .format(ptag.find('name').text, iname)) inew = ET.SubElement(ptag, 'interrupt') ET.SubElement(inew, 'name').text = iname for key, val in iadd.items(): ET.SubElement(inew, key).text = str(val) inew.tail = "\n " def process_register_add(rtag, fname, fadd): """Add fname given by fadd to rtag.""" parent = rtag.find('fields') for ftag in parent.iter('field'): if ftag.find('name').text == fname: raise SvdPatchError('register {} already has a field {}' .format(rtag.find('name').text, fname)) fnew = ET.SubElement(parent, 'field') ET.SubElement(fnew, 'name').text = fname for (key, value) in fadd.items(): ET.SubElement(fnew, key).text = str(value) fnew.tail = "\n " def process_device_delete(device, pspec): """Delete registers matched by rspec inside ptag.""" for ptag in list(iter_peripherals(device, pspec, check_derived=False)): device.find('peripherals').remove(ptag) def process_peripheral_delete(ptag, rspec): """Delete registers matched by rspec inside ptag.""" for rtag in list(iter_registers(ptag, rspec)): ptag.find('registers').remove(rtag) def process_register_delete(rtag, fspec): """Delete fields matched by fspec inside rtag.""" for ftag in list(iter_fields(rtag, fspec)): rtag.find('fields').remove(ftag) def process_peripheral_strip(ptag, prefix): """Delete prefix in register names inside ptag.""" for rtag in ptag.iter('register'): nametag = rtag.find('name') name = nametag.text if name.startswith(prefix): nametag.text = name[len(prefix):] dnametag = rtag.find('displayName') dname = dnametag.text if dname.startswith(prefix): dnametag.text = dname[len(prefix):] def spec_ind(spec): """ Find left and right indices of enumeration token in specification string. """ li1 = spec.find("*") li2 = spec.find("?") li3 = spec.find("[") li = li1 if li1 > -1 else li2 if li2 > -1 else li3 if li3 > -1 else None ri1 = spec[::-1].find("*") ri2 = spec[::-1].find("?") ri3 = spec[::-1].find("]") ri = ri1 if ri1 > -1 else ri2 if ri2 > -1 else ri3 if ri3 > -1 else None return li, ri def check_offsets(offsets, dimIncrement): for o1, o2 in zip(offsets[:-1], offsets[1:]): if o2-o1 != dimIncrement: return False return True def get_bitmask(rtag): """Calculate filling of register""" mask = 0x0 for ftag in iter_fields(rtag, "*"): foffset = int(ftag.findtext("bitOffset"), 0) fwidth = int(ftag.findtext("bitWidth"), 0) mask |= (0xffffffff >> (32-fwidth)) << foffset return mask def check_bitmasks(masks, mask): for m in masks: if m != mask: return False return True def process_peripheral_regs_array(ptag, rspec, rmod): """Collect same registers in peripheral into register array.""" registers = [] li, ri = spec_ind(rspec) for rtag in list(iter_registers(ptag, rspec)): rname = rtag.findtext('name') registers.append([rtag, rname[li:len(rname)-ri], int(rtag.findtext('addressOffset'), 0)]) dim = len(registers) if dim == 0: raise SvdPatchError("{}: registers {} not found" .format(ptag.findtext('name'), rspec)) registers = sorted(registers, key=lambda r: r[2]) dimIndex = "{0}-{0}".format(registers[0][1]) if dim == 1 else ",".join([r[1] for r in registers]) offsets = [r[2] for r in registers] bitmasks = [get_bitmask(r[0]) for r in registers] dimIncrement = 0 if dim > 1: dimIncrement = offsets[1]-offsets[0] if not (check_offsets(offsets, dimIncrement) and check_bitmasks(bitmasks, bitmasks[0])): raise SvdPatchError("{}: registers cannot be collected into {} array" .format(ptag.findtext('name'), rspec)) for rtag, _, _ in registers[1:]: ptag.find('registers').remove(rtag) rtag = registers[0][0] if 'name' in rmod: name = rmod['name'] else: name = rspec[:li]+"%s"+rspec[len(rspec)-ri:] rtag.find('name').text = name process_peripheral_register(ptag, name, rmod) ET.SubElement(rtag, 'dim').text = str(dim) ET.SubElement(rtag, 'dimIndex').text = dimIndex ET.SubElement(rtag, 'dimIncrement').text = hex(dimIncrement) def process_peripheral_cluster(ptag, cname, cmod): """Collect registers in peripheral into clusters.""" rdict = {} first = True check = True rspecs = [r for r in cmod if r != "description"] for rspec in rspecs: registers = [] li, ri = spec_ind(rspec) for rtag in list(iter_registers(ptag, rspec)): rname = rtag.findtext('name') registers.append([rtag, rname[li:len(rname)-ri], int(rtag.findtext('addressOffset'), 0)]) registers = sorted(registers, key=lambda r: r[2]) rdict[rspec] = registers bitmasks = [get_bitmask(r[0]) for r in registers] if first: dim = len(registers) if dim == 0: check = False break dimIndex = ",".join([r[1] for r in registers]) offsets = [r[2] for r in registers] dimIncrement = 0 if dim > 1: dimIncrement = offsets[1]-offsets[0] if not (check_offsets(offsets, dimIncrement) and check_bitmasks(bitmasks, bitmasks[0])): check = False break else: if ( (dim != len(registers)) or (dimIndex != ",".join([r[1] for r in registers])) or (not check_offsets(offsets, dimIncrement)) or (not check_bitmasks(bitmasks, bitmasks[0])) ): check = False break first = False if not check: raise SvdPatchError("{}: registers cannot be collected into {} cluster" .format(ptag.findtext('name'), cname)) ctag = ET.SubElement(ptag.find('registers'), 'cluster') addressOffset = min([registers[0][2] for _, registers in rdict.items()]) ET.SubElement(ctag, 'name').text = cname if 'description' in cmod: description = cmod['description'] else: description = ("Cluster {}, containing {}" .format(cname, ", ".join(rspecs))) ET.SubElement(ctag, 'description').text = description ET.SubElement(ctag, 'addressOffset').text = hex(addressOffset) for rspec, registers in rdict.items(): for rtag, _, _ in registers[1:]: ptag.find('registers').remove(rtag) rtag = registers[0][0] rmod = cmod[rspec] process_peripheral_register(ptag, rspec, rmod) new_rtag = copy.deepcopy(rtag) ptag.find('registers').remove(rtag) if 'name' in rmod: name = rmod['name'] else: li, ri = spec_ind(rspec) name = rspec[:li]+rspec[len(rspec)-ri:] new_rtag.find('name').text = name offset = new_rtag.find('addressOffset') offset.text = hex(int(offset.text, 0)-addressOffset) ctag.append(new_rtag) ET.SubElement(ctag, 'dim').text = str(dim) ET.SubElement(ctag, 'dimIndex').text = dimIndex ET.SubElement(ctag, 'dimIncrement').text = hex(dimIncrement) class SvdPatchError(ValueError): pass class RegisterMergeError(SvdPatchError): pass class MissingFieldError(SvdPatchError): pass class MissingRegisterError(SvdPatchError): pass class MissingPeripheralError(SvdPatchError): pass def process_register_merge(rtag, fspec): """Merge all fspec in rtag.""" fields = list(iter_fields(rtag, fspec)) if len(fields) == 0: rname = rtag.find('name').text raise RegisterMergeError("Could not find any fields to merge {}.{}" .format(rname, fspec)) parent = rtag.find('fields') name = os.path.commonprefix([f.find('name').text for f in fields]) desc = fields[0].find('description').text bitwidth = sum(int(f.find('bitWidth').text) for f in fields) bitoffset = min(int(f.find('bitOffset').text) for f in fields) for field in fields: parent.remove(field) fnew = ET.SubElement(parent, 'field') ET.SubElement(fnew, 'name').text = name ET.SubElement(fnew, 'description').text = desc ET.SubElement(fnew, 'bitOffset').text = str(bitoffset) ET.SubElement(fnew, 'bitWidth').text = str(bitwidth) def process_register_split(rtag, fspec): """split all fspec in rtag.""" fields = list(iter_fields(rtag, fspec)) if len(fields) == 0: rname = rtag.find('name').text raise RegisterMergeError("Could not find any fields to split {}.{}" .format(rname, fspec)) parent = rtag.find('fields') name = os.path.commonprefix([f.find('name').text for f in fields]) desc = fields[0].find('description').text bitwidth = sum(int(f.find('bitWidth').text) for f in fields) parent.remove(fields[0]) for i in range(bitwidth): fnew = ET.SubElement(parent, 'field') ET.SubElement(fnew, 'name').text = name + str(i) ET.SubElement(fnew, 'description').text = desc ET.SubElement(fnew, 'bitOffset').text = str(i) ET.SubElement(fnew, 'bitWidth').text = str(1) def process_field_enum(pname, rtag, fspec, field, usage="read-write"): """Add an enumeratedValues given by field to all fspec in rtag.""" replace_if_exists = False if "_replace_enum" in field: field = field["_replace_enum"] replace_if_exists = True derived = None for ftag in iter_fields(rtag, fspec): name = ftag.find('name').text if derived is None: enum = make_enumerated_values(name, field, usage=usage) enum_name = enum.find('name').text enum_usage = enum.find('usage').text for ev in ftag.iter('enumeratedValues'): ev_usage_tag = ev.find('usage') ev_usage = ev_usage_tag.text if ev_usage_tag is not None else 'read-write' if ev_usage == enum_usage or ev_usage == "read-write": if replace_if_exists: ftag.remove(ev) else: print(pname, fspec, field) raise SvdPatchError( "{}: field {} already has enumeratedValues for {}. Use '_replace_enum' to overwrite." .format(pname, name, ev_usage)) ftag.append(enum) derived = make_derived_enumerated_values(enum_name) else: ftag.append(derived) if derived is None: rname = rtag.find('name').text raise MissingFieldError("Could not find {}:{}.{}" .format(pname, rname, fspec)) def process_field_range(pname, rtag, fspec, field): """Add a writeConstraint range given by field to all fspec in rtag.""" set_any = False for ftag in iter_fields(rtag, fspec): ftag.append(make_write_constraint(field)) set_any = True if not set_any: rname = rtag.find('name').text raise MissingFieldError("Could not find {}:{}.{}" .format(pname, rname, fspec)) def process_register_field(pname, rtag, fspec, field): """Work through a field, handling either an enum or a range.""" if isinstance(field, dict): usages = ("_read", "_write") if not any(u in field for u in usages): process_field_enum(pname, rtag, fspec, field) for usage in (u for u in usages if u in field): process_field_enum(pname, rtag, fspec, field[usage], usage=usage.replace("_", "")) elif isinstance(field, list) and len(field) == 2: process_field_range(pname, rtag, fspec, field) def process_peripheral_register(ptag, rspec, register, update_fields=True): """Work through a register, handling all fields.""" # Find all registers that match the spec pname = ptag.find('name').text rcount = 0 for rtag in iter_registers(ptag, rspec): rcount += 1 # Handle deletions for fspec in register.get("_delete", []): process_register_delete(rtag, fspec) # Handle modifications for fspec in register.get("_modify", []): fmod = register["_modify"][fspec] process_register_modify(rtag, fspec, fmod) # Handle additions for fname in register.get("_add", []): fadd = register["_add"][fname] process_register_add(rtag, fname, fadd) # Handle merges for fspec in register.get("_merge", []): process_register_merge(rtag, fspec) # Handle splits for fspec in register.get("_split", []): process_register_split(rtag, fspec) # Handle fields if update_fields: for fspec in register: if not fspec.startswith("_"): field = register[fspec] process_register_field(pname, rtag, fspec, field) if rcount == 0: raise MissingRegisterError("Could not find {}:{}" .format(pname, rspec)) def process_peripheral(svd, pspec, peripheral, update_fields=True): """Work through a peripheral, handling all registers.""" # Find all peripherals that match the spec pcount = 0 for ptag in iter_peripherals(svd, pspec): pcount += 1 # Handle deletions for rspec in peripheral.get("_delete", []): process_peripheral_delete(ptag, rspec) # Handle modifications for rspec in peripheral.get("_modify", {}): rmod = peripheral["_modify"][rspec] process_peripheral_modify(ptag, rspec, rmod) # Handle strips for rspec in peripheral.get("_strip", []): process_peripheral_strip(ptag, rspec) # Handle additions for rname in peripheral.get("_add", {}): radd = peripheral["_add"][rname] if rname == "_registers": for rname in radd: process_peripheral_add_reg(ptag, rname, radd[rname]) elif rname == "_interrupts": for iname in radd: process_peripheral_add_int(ptag, iname, radd[iname]) else: process_peripheral_add_reg(ptag, rname, radd) # Handle registers for rspec in peripheral: if not rspec.startswith("_"): register = peripheral[rspec] process_peripheral_register(ptag, rspec, register, update_fields) # Handle register arrays for rspec in peripheral.get("_array", {}): rmod = peripheral["_array"][rspec] process_peripheral_regs_array(ptag, rspec, rmod) # Handle clusters for cname in peripheral.get("_cluster", {}): cmod = peripheral["_cluster"][cname] process_peripheral_cluster(ptag, cname, cmod) if pcount == 0: raise MissingPeripheralError("Could not find {}".format(pspec)) def process_device(svd, device, update_fields=True): """Work through a device, handling all peripherals""" # Handle any deletions for pspec in device.get("_delete", []): process_device_delete(svd, pspec) # Handle any modifications for key in device.get("_modify", {}): val = device["_modify"][key] if key == "cpu": process_device_cpu_modify(svd, val) elif key == "_peripherals": for pspec in val: pmod = device['_modify']['_peripherals'][pspec] process_device_peripheral_modify(svd, pspec, pmod) elif key in DEVICE_CHILDREN: process_device_child_modify(svd, key, val) else: process_device_peripheral_modify(svd, key, val) # Handle any new peripherals (!) for pname in device.get("_add", []): padd = device["_add"][pname] process_device_add(svd, pname, padd) # Handle any derived peripherals for pname in device.get("_derive", []): pderive = device["_derive"][pname] process_device_derive(svd, pname, pderive) # Handle any rebased peripherals for pname in device.get("_rebase", []): pold = device["_rebase"][pname] process_device_rebase(svd, pname, pold) # Now process all peripherals for periphspec in device: if not periphspec.startswith("_"): device[periphspec]["_path"] = device["_path"] process_peripheral(svd, periphspec, device[periphspec], update_fields) def main(): # Load the specified YAML root file args = parseargs() with open(args.yaml) as f: root = yaml.safe_load(f) root["_path"] = args.yaml # Load the specified SVD file svdpath = args.svd_in if not os.path.exists(svdpath): raise FileNotFoundError("The input SVD file does not exist") svdpath_out = args.svd_out svd = ET.parse(svdpath) # Load all included YAML files yaml_includes(root) # Process device process_device(svd, root) # SVD should now be updated, write it out svd.write(svdpath_out) if __name__ == "__main__": main()