modm_data.header2svd.stmicro
1# Copyright 2022, Niklas Hauser 2# SPDX-License-Identifier: MPL-2.0 3 4from .header import Header, getDefineForDevice 5from .tree import normalize_memory_map 6from .memory_map import Report, device_headers, header_defines, memory_map_from_header 7from .compare import compare_svd, svd_for_header 8 9__all__ = [ 10 "Header", 11 "getDefineForDevice", 12 "normalize_memory_map", 13 "Report", 14 "device_headers", 15 "header_defines", 16 "memory_map_from_header", 17 "compare_svd", 18 "svd_for_header", 19]
class
Header(modm_data.header2svd.header.Header):
48class Header(CmsisHeader): 49 _HEADER_PATH = ext_path("stmicro/header") 50 _CACHE_FAMILY = defaultdict(dict) 51 52 def __init__(self, did, family_header_file, define): 53 self.did = did 54 self.family_folder = family_header_file 55 if "xx" not in self.family_folder: 56 self.family_folder += "x" 57 self.cmsis_folder = Header._HEADER_PATH / self.family_folder / "Include" 58 self.family_header_file = "{}.h".format(family_header_file) 59 60 self.family_defines = self._get_family_defines() 61 self.define = define[:9].upper() + define[9:] 62 if self.define not in self.family_defines: 63 self.define = getDefineForDevice(self.did, self.family_defines) 64 self.is_valid = self.define is not None 65 if not self.is_valid: 66 return 67 68 self.header_file = "{}.h".format(self.define.lower()) 69 substitutions = { 70 # r"/\* +?(Legacy defines|Legacy aliases|Old .*? legacy purpose|Aliases for .*?) +?\*/.*?\n\n": "", 71 r"/\* +?Legacy (aliases|defines|registers naming) +?\*/.*?\n\n": "", 72 r"/\* +?Old .*? legacy purpose +?\*/.*?\n\n": "", 73 r"/\* +?Aliases for .*? +?\*/.*?\n\n": "", 74 # r"( 0x[0-9A-F]+)\) ": r"\1U", 75 # r"#define.*?/\*!<.*? Legacy .*?\*/\n": "", 76 } 77 super().__init__(self.cmsis_folder / self.header_file, substitutions) 78 79 @property 80 def interrupt_table(self): 81 if "vectors" not in self._cache: 82 interrupt_enum = [i["values"] for i in self.header.enums if i["name"] == "IRQn_Type"][0] 83 vectors = [ 84 {"position": int(str(i["value"]).replace(" ", "")), "name": i["name"][:-5]} for i in interrupt_enum 85 ] 86 self._cache["vectors"] = vectors 87 return self._cache["vectors"] 88 89 def _get_family_defines(self): 90 if self.family_folder not in Header._CACHE_FAMILY: 91 content = (self.cmsis_folder / self.family_header_file).read_text(encoding="utf-8", errors="replace") 92 defines = [] 93 for include in re.findall(r'#include +"(stm32.*?(?<!_hal))\.h"', content): 94 define = re.search(rf"defined *\( *({include}) *\)", content, flags=re.IGNORECASE) 95 defines.append(define.group(1)) 96 Header._CACHE_FAMILY[self.family_folder]["family_defines"] = defines 97 return Header._CACHE_FAMILY[self.family_folder]["family_defines"]
Header(did, family_header_file, define)
52 def __init__(self, did, family_header_file, define): 53 self.did = did 54 self.family_folder = family_header_file 55 if "xx" not in self.family_folder: 56 self.family_folder += "x" 57 self.cmsis_folder = Header._HEADER_PATH / self.family_folder / "Include" 58 self.family_header_file = "{}.h".format(family_header_file) 59 60 self.family_defines = self._get_family_defines() 61 self.define = define[:9].upper() + define[9:] 62 if self.define not in self.family_defines: 63 self.define = getDefineForDevice(self.did, self.family_defines) 64 self.is_valid = self.define is not None 65 if not self.is_valid: 66 return 67 68 self.header_file = "{}.h".format(self.define.lower()) 69 substitutions = { 70 # r"/\* +?(Legacy defines|Legacy aliases|Old .*? legacy purpose|Aliases for .*?) +?\*/.*?\n\n": "", 71 r"/\* +?Legacy (aliases|defines|registers naming) +?\*/.*?\n\n": "", 72 r"/\* +?Old .*? legacy purpose +?\*/.*?\n\n": "", 73 r"/\* +?Aliases for .*? +?\*/.*?\n\n": "", 74 # r"( 0x[0-9A-F]+)\) ": r"\1U", 75 # r"#define.*?/\*!<.*? Legacy .*?\*/\n": "", 76 } 77 super().__init__(self.cmsis_folder / self.header_file, substitutions)
interrupt_table
79 @property 80 def interrupt_table(self): 81 if "vectors" not in self._cache: 82 interrupt_enum = [i["values"] for i in self.header.enums if i["name"] == "IRQn_Type"][0] 83 vectors = [ 84 {"position": int(str(i["value"]).replace(" ", "")), "name": i["name"][:-5]} for i in interrupt_enum 85 ] 86 self._cache["vectors"] = vectors 87 return self._cache["vectors"]
Inherited Members
- modm_data.header2svd.header.Header
- CMSIS_PATH
- filename
- substitutions
- macros
- typedefs
- peripherals
- is_defined
- define_value
- evaluate
- instances
- header
def
getDefineForDevice(device_id, familyDefines):
17def getDefineForDevice(device_id, familyDefines): 18 if len(familyDefines) == 1: 19 return familyDefines[0] 20 21 # get all defines for this device name 22 devName = "STM32{}{}".format(device_id.family.upper(), device_id.name.upper()) 23 24 # Map STM32WL33 -> STM32WL3X 25 if device_id.family == "wl" and devName[7:9] in ["30", "31", "33"]: 26 devName = devName[:-1] + "X" 27 28 deviceDefines = sorted([define for define in familyDefines if define.startswith(devName)]) 29 # if there is only one define thats the one 30 if len(deviceDefines) == 1: 31 return deviceDefines[0] 32 33 # now we match for the size-id. 34 devNameMatch = devName + "x{}".format(device_id.size.upper()) 35 for define in deviceDefines: 36 if devNameMatch <= define: 37 return define 38 39 # now we match for the pin-id. 40 devNameMatch = devName + "{}x".format(device_id.pin.upper()) 41 for define in deviceDefines: 42 if devNameMatch <= define: 43 return define 44 45 return None
def
normalize_memory_map(memtree):
164def normalize_memory_map(memtree): 165 # print(RenderTree(memtree, maxlevel=2)) 166 memtree = _normalize_subtypes(memtree, "DMA_TypeDef", "DMA_Channel_TypeDef", "DMA_Stream_TypeDef") 167 memtree = _normalize_subtypes(memtree, "MDMA_TypeDef", "MDMA_Channel_TypeDef") 168 memtree = _normalize_subtypes(memtree, "BDMA_TypeDef", "BDMA_Channel_TypeDef") 169 memtree = _normalize_subtypes(memtree, "LTDC_TypeDef", "LTDC_Layer_TypeDef") 170 memtree = _normalize_subtypes(memtree, "SAI_TypeDef", "SAI_Block_TypeDef") 171 memtree = _normalize_subtypes(memtree, "RAMECC_TypeDef", "RAMECC_MonitorTypeDef") 172 173 memtree = _normalize_dfsdm(memtree) 174 memtree = _normalize_dmamux(memtree) 175 memtree = _normalize_adc_common(memtree) 176 177 memtree = _normalize_duplicates(memtree, lambda n: "_COMMON" in n.name, lambda n: "_COMMON" not in n.name) 178 memtree = _normalize_duplicates(memtree, lambda n: "OPAMP" == n.name, lambda n: re.match(r"OPAMP\d$", n.name)) 179 180 memtree = _normalize_instances(memtree) 181 memtree = _normalize_order(memtree) 182 return memtree
@dataclass
class
Report:
88@dataclass 89class Report: 90 """Discrepancies found in the header during the reconstruction.""" 91 92 header: str 93 defines: int = 0 94 """Number of bit field macros defined in the device header.""" 95 unassigned: list[str] = field(default_factory=list) 96 """Bit field macros that are not assigned to any register.""" 97 empty: list[str] = field(default_factory=list) 98 """Registers without any bit field macros.""" 99 overlapping: list[tuple[str, str, str]] = field(default_factory=list) 100 """(register, removed bit field, remaining bit field) with overlapping bits.""" 101 renamed: list[tuple[str, str]] = field(default_factory=list) 102 """(peripheral, register) that were renamed due to name collisions.""" 103 hinted: list[tuple[str, str]] = field(default_factory=list) 104 """(register, bit field macro prefix) that were paired by the CubeHAL source code.""" 105 restricted: list[tuple[str, str]] = field(default_factory=list) 106 """(peripheral, register.field) that are not supported by the instance.""" 107 alternates: list[str] = field(default_factory=list) 108 """Registers that were split into alternate registers due to overlapping bit fields.""" 109 interrupts: list[str] = field(default_factory=list) 110 """Interrupts that could not be assigned to a peripheral.""" 111 enumerations: int = 0 112 """Number of bit fields with enumerated values."""
Discrepancies found in the header during the reconstruction.
Report( header: str, defines: int = 0, unassigned: list[str] = <factory>, empty: list[str] = <factory>, overlapping: list[tuple[str, str, str]] = <factory>, renamed: list[tuple[str, str]] = <factory>, hinted: list[tuple[str, str]] = <factory>, restricted: list[tuple[str, str]] = <factory>, alternates: list[str] = <factory>, interrupts: list[str] = <factory>, enumerations: int = 0)
overlapping: list[tuple[str, str, str]]
(register, removed bit field, remaining bit field) with overlapping bits.
hinted: list[tuple[str, str]]
(register, bit field macro prefix) that were paired by the CubeHAL source code.
restricted: list[tuple[str, str]]
(peripheral, register.field) that are not supported by the instance.
def
device_headers() -> list[pathlib.Path]:
129def device_headers() -> list[Path]: 130 """:return: all STM32 device headers in the CMSIS header repository.""" 131 headers = [] 132 for header in sorted(_HEADER_PATH.glob("stm32*xx/Include/stm32*.h")): 133 content = header.read_text(encoding="utf-8", errors="replace") 134 if "_TypeDef" in content and re.search(r'#include +"core_cm', content): 135 headers.append(header) 136 return headers
Returns
all STM32 device headers in the CMSIS header repository.
def
header_defines(header: pathlib.Path, core: str = None) -> list[str]:
139def header_defines(header: Path, core: str = None) -> list[str]: 140 """:return: the device define from the family header and the core define for dual-core devices.""" 141 define = None 142 for family in header.parent.glob("stm32*.h"): 143 content = family.read_text(encoding="utf-8", errors="replace") 144 if match := re.search(rf'#include +"{re.escape(header.name)}"', content): 145 # The define is checked right before the include 146 lines = content[: match.start()].splitlines()[-3:] 147 if defines := re.findall(r"defined *\( *(STM32\w+) *\)", "\n".join(lines)): 148 define = defines[-1] 149 break 150 defines = [define or (header.stem[:9].upper() + header.stem[9:])] 151 if "CORE_CM4 or CORE_CM7" in header.read_text(encoding="utf-8", errors="replace"): 152 defines.append(f"CORE_{(core or 'cm7').upper()}") 153 return defines
Returns
the device define from the family header and the core define for dual-core devices.
def
memory_map_from_header( header: pathlib.Path, core: str = None) -> tuple[modm_data.svd.Device, Report]:
825def memory_map_from_header(header: Path, core: str = None) -> tuple[Device, Report]: 826 """ 827 Extracts the header data and reconstructs the memory map of a CMSIS header. 828 829 :param header: path to the CMSIS device header. 830 :param core: the core of dual-core devices, `cm4` or `cm7`. 831 :return: the memory map as SVD device tree and a report of discrepancies. 832 """ 833 data = extract_header(header, header_defines(header, core)) 834 name = header.stem + (f"_{core}" if core else "") 835 return memory_map(data, name, cubehal_folder(header.parent.parent.name))
Extracts the header data and reconstructs the memory map of a CMSIS header.
Parameters
- header: path to the CMSIS device header.
- core: the core of dual-core devices,
cm4orcm7.
Returns
the memory map as SVD device tree and a report of discrepancies.
49def compare_svd(header: Device, svd: Device) -> list[str]: 50 """ 51 :param header: the memory map reconstructed from the CMSIS header. 52 :param svd: the memory map read from the ST SVD file. 53 :return: a list of differences. 54 """ 55 lines = [] 56 derived = {p.name: p for p in header.children} 57 58 def registers(peripheral): 59 while not peripheral.children and getattr(peripheral, "derived_from", None) in derived: 60 peripheral = derived[peripheral.derived_from] 61 return peripheral.children 62 63 def normalize(name): 64 """Ignores the position of the instance number, e.g. GTZC1_TZIC and GTZC_TZIC1""" 65 secure = bool(re.search(r"(_S$|^SEC_)", name)) 66 name = re.sub(r"(_S$|^SEC_)", "", name) 67 return re.sub(r"[\d_]", "", name).upper(), "".join(re.findall(r"\d", name)), secure 68 69 # ST SVDs call the secure instances SEC_* 70 hperipherals = {re.sub(r"^(.*)_S$", r"SEC_\1", p.name): p for p in header.children} 71 hnormalized = defaultdict(list) 72 for p in header.children: 73 hnormalized[normalize(p.name)].append(p) 74 haddresses = {p.address: p for p in header.children} 75 matched = set() 76 for speripheral in svd.children: 77 if speripheral.address >= 0xE0000000: 78 continue 79 hperipheral = hperipherals.get(speripheral.name) 80 if hperipheral is None and len(candidates := hnormalized[normalize(speripheral.name)]) == 1: 81 hperipheral = candidates[0] 82 if hperipheral is None: 83 hperipheral = haddresses.get(speripheral.address) 84 if hperipheral is None: 85 lines.append(f"{speripheral.name} @ 0x{speripheral.address:08x}: not defined in header") 86 continue 87 matched.add(hperipheral.name) 88 prefix = f"{speripheral.name}" 89 if hperipheral.name != speripheral.name: 90 prefix += f" ({hperipheral.name})" 91 if hperipheral.address != speripheral.address: 92 lines.append(f"{prefix}: address 0x{hperipheral.address:08x} != 0x{speripheral.address:08x}") 93 94 # Alternate registers are merged, e.g. TIM_CCMR1 and TIM_CCMR1_ALT 95 halternates = defaultdict(list) 96 for hregister in registers(hperipheral): 97 halternates[hregister.offset].append(hregister) 98 # Alternate registers are merged, e.g. TIM_CCMR1_Output and TIM_CCMR1_Input 99 sregisters = defaultdict(list) 100 for sregister in speripheral.children: 101 sregisters[sregister.offset].append(sregister) 102 for offset, alternates in sregisters.items(): 103 snames = {_normalize_register(speripheral.name, r.name) for r in alternates} 104 sname = "/".join(sorted(snames)) 105 if not (hregisters := halternates.get(offset)): 106 lines.append(f"{prefix}.{sname} @ 0x{offset:03x}: not defined in header") 107 continue 108 hregister = hregisters[0] 109 rprefix = f"{prefix}.{hregister.name}" 110 if hregister.name.replace("[%s]", "") not in snames: 111 lines.append(f"{rprefix}: named {sname} in SVD") 112 if hregister.width not in {r.width for r in alternates}: 113 lines.append(f"{rprefix}: size {hregister.width} != {alternates[0].width} in SVD") 114 hfields = {(f.position, f.width): f.name for r in reversed(hregisters) for f in r.children} 115 sfields = defaultdict(set) 116 for sregister in alternates: 117 for f in sregister.children: 118 sfields[(f.position, f.width)].add(f.name) 119 hnames = {name: bits for bits, name in hfields.items()} 120 snames = {name: bits for bits, names in sfields.items() for name in names} 121 for bits, names in sorted(sfields.items()): 122 for name in sorted(names): 123 if bits in hfields: 124 if hfields[bits] not in names: 125 lines.append(f"{rprefix}.{hfields[bits]}: named {name} in SVD") 126 break 127 elif name in hnames and hnames[name] not in sfields: 128 lines.append(f"{rprefix}.{name}[{_range(hnames[name])}]: located at [{_range(bits)}] in SVD") 129 else: 130 lines.append(f"{rprefix}.{name}[{_range(bits)}]: not defined in header") 131 for bits, name in sorted(hfields.items()): 132 if bits not in sfields and not (name in snames and snames[name] not in hfields): 133 lines.append(f"{rprefix}.{name}[{_range(bits)}]: not defined in SVD") 134 if speripheral.children: 135 for hregister in registers(hperipheral): 136 if hregister.offset not in sregisters and not getattr(hregister, "alternate", None): 137 lines.append(f"{prefix}.{hregister.name} @ 0x{hregister.offset:03x}: not defined in SVD") 138 139 for hperipheral in header.children: 140 if hperipheral.name not in matched and hperipheral.address < 0xE0000000: 141 lines.append(f"{hperipheral.name} @ 0x{hperipheral.address:08x}: not defined in SVD") 142 return lines
Parameters
- header: the memory map reconstructed from the CMSIS header.
- svd: the memory map read from the ST SVD file.
Returns
a list of differences.
def
svd_for_header(header: pathlib.Path) -> pathlib.Path | None:
24def svd_for_header(header: Path) -> Path | None: 25 """:return: the ST SVD file with the longest name pattern matching the header.""" 26 stem = header.stem.lower() 27 best = None 28 for svd in _SVD_PATH.glob("*/*.svd"): 29 pattern = re.sub(r"_cm\d+$", "", svd.stem.lower()).replace("x", ".") 30 if re.match(pattern, stem) and (best is None or len(svd.stem) > len(best.stem)): 31 best = svd 32 return best
Returns
the ST SVD file with the longest name pattern matching the header.