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]
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)
header: str
defines: int = 0

Number of bit field macros defined in the device header.

unassigned: list[str]

Bit field macros that are not assigned to any register.

empty: list[str]

Registers without any bit field macros.

overlapping: list[tuple[str, str, str]]

(register, removed bit field, remaining bit field) with overlapping bits.

renamed: list[tuple[str, str]]

(peripheral, register) that were renamed due to name collisions.

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.

alternates: list[str]

Registers that were split into alternate registers due to overlapping bit fields.

interrupts: list[str]

Interrupts that could not be assigned to a peripheral.

enumerations: int = 0

Number of bit fields with enumerated values.

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, cm4 or cm7.
Returns

the memory map as SVD device tree and a report of discrepancies.

def compare_svd( header: modm_data.svd.Device, svd: modm_data.svd.Device) -> list[str]:
 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.