modm_data.header2svd.stmicro.header

  1# Copyright 2022, Niklas Hauser
  2# SPDX-License-Identifier: MPL-2.0
  3
  4import re
  5import logging
  6import subprocess
  7
  8from collections import defaultdict
  9
 10from ..header import Header as CmsisHeader
 11from ...utils import ext_path, cache_path
 12import modm_data.svd as svd
 13
 14
 15LOGGER = logging.getLogger(__file__)
 16HEADER_TEMPLATE = r"""
 17#include <iostream>
 18#include <{{header}}>
 19
 20template<typename T>
 21void __modm_dump_f(char const *symbol, T value) {
 22    std::cout << "\"" << symbol << "\": " << uint64_t(value) << "," << std::endl;
 23}
 24#define __modm_dump(def) __modm_dump_f(#def, (def))
 25int main() {
 26    std::cout << "cpp_defines = {";{% for define in defines %}
 27#ifdef {{define}}
 28    __modm_dump({{define}});
 29#endif{% endfor %}
 30    std::cout << "}";
 31    return 0;
 32}
 33"""
 34
 35
 36def getDefineForDevice(device_id, familyDefines):
 37    # get all defines for this device name
 38    devName = "STM32{}{}".format(device_id.family.upper(), device_id.name.upper())
 39
 40    # Map STM32F7x8 -> STM32F7x7
 41    if device_id.family == "f7" and devName[8] == "8":
 42        devName = devName[:8] + "7"
 43
 44    deviceDefines = sorted([define for define in familyDefines if define.startswith(devName)])
 45    # if there is only one define thats the one
 46    if len(deviceDefines) == 1:
 47        return deviceDefines[0]
 48
 49    # sort with respecting variants
 50    minlen = min(len(d) for d in deviceDefines)
 51    deviceDefines.sort(key=lambda d: (d[:minlen], d[minlen:]))
 52
 53    # now we match for the size-id (and variant-id if applicable).
 54    if device_id.family == "h7":
 55        devNameMatch = devName + "xx"
 56    else:
 57        devNameMatch = devName + "x{}".format(device_id.size.upper())
 58    if device_id.family == "l1":
 59        # Map STM32L1xxQC and STM32L1xxZC -> STM32L162QCxA variants
 60        if device_id.pin in ["q", "z"] and device_id.size == "c":
 61            devNameMatch += "A"
 62        else:
 63            devNameMatch += device_id.variant.upper()
 64    elif device_id.family == "h7":
 65        if device_id.variant:
 66            devNameMatch += device_id.variant.upper()
 67    for define in deviceDefines:
 68        if devNameMatch <= define:
 69            return define
 70
 71    # now we match for the pin-id.
 72    devNameMatch = devName + "{}x".format(device_id.pin.upper())
 73    for define in deviceDefines:
 74        if devNameMatch <= define:
 75            return define
 76
 77    return None
 78
 79
 80class Header(CmsisHeader):
 81    HEADER_PATH = ext_path("stmicro/header")
 82    CACHE_PATH =  cache_path("cmsis/stm32")
 83    CACHE_FAMILY = defaultdict(dict)
 84    BUILTINS = {
 85        "const uint32_t": 4,
 86        "const uint16_t": 2,
 87        "const uint8_t": 1,
 88        "uint32_t": 4,
 89        "uint16_t": 2,
 90        "uint8_t":  1,
 91    }
 92
 93    def __init__(self, did):
 94        self.did = did
 95        self.family_folder = "stm32{}xx".format(self.did.family)
 96        self.cmsis_folder = Header.HEADER_PATH / self.family_folder / "Include"
 97        self.family_header_file = "{}.h".format(self.family_folder)
 98
 99        self.family_defines = self._get_family_defines()
100        self.define = getDefineForDevice(self.did, self.family_defines)
101        assert self.define is not None
102        self.is_valid = self.define is not None
103        if not self.is_valid: return
104
105        self.header_file = "{}.h".format(self.define.lower())
106        self.device_map = None
107        substitutions = {
108            # r"/\* +?(Legacy defines|Legacy aliases|Old .*? legacy purpose|Aliases for .*?) +?\*/.*?\n\n": "",
109            r"/\* +?Legacy (aliases|defines|registers naming) +?\*/.*?\n\n": "",
110            r"/\* +?Old .*? legacy purpose +?\*/.*?\n\n": "",
111            r"/\* +?Aliases for .*? +?\*/.*?\n\n": "",
112            # r"( 0x[0-9A-F]+)\)                 ": r"\1U",
113            # r"#define.*?/\*!<.*? Legacy .*?\*/\n": "",
114        }
115        super().__init__(self.cmsis_folder / self.header_file, substitutions)
116
117    def get_defines(self):
118        key = "defines" + self.did.get("core", "")
119        if key not in self._cache:
120            self._cache[key] = self._get_defines()
121        return self._cache[key]
122
123    @property
124    def _memory_map_key(self):
125        return "memmap" + self.did.get("core", "")
126
127    @property
128    def memory_map_tree(self):
129        if self._memory_map_key not in self._cache:
130            self._cache[self._memory_map_key] = self._get_memmap()
131        return self._cache[self._memory_map_key]
132
133    @property
134    def interrupt_table(self):
135        if "vectors" not in self._cache:
136            interrupt_enum = [i["values"] for i in self.header.enums if i["name"] == "IRQn_Type"][0]
137            vectors = [{"position": int(str(i["value"]).replace(" ", "")),
138                        "name": i["name"][:-5]} for i in interrupt_enum]
139            self._cache["vectors"] = vectors
140        return self._cache["vectors"]
141
142    def _get_family_defines(self):
143        if self.did.family not in Header.CACHE_FAMILY:
144            defines = []
145            match = re.findall(r"if defined\((?P<define>STM32[A-Z].....)\)", (self.cmsis_folder / self.family_header_file).read_text(encoding="utf-8", errors="replace"))
146            if match: defines = match;
147            else: LOGGER.error("Cannot find family defines for {}!".format(self.did.string));
148            Header.CACHE_FAMILY[self.did.family]["family_defines"] = defines
149        return Header.CACHE_FAMILY[self.did.family]["family_defines"]
150
151    def _get_filtered_defines(self):
152        defines = {}
153        # get all the non-empty defines
154        for define in self.header.defines:
155            if comment := re.search(r"/\* *(.*?) *\*/", define):
156                if "legacy" in comment.group(1): continue
157            define = re.sub(r"/\*.*?\*/", "", define).strip()
158            name, *parts = define.split(" ")
159            if not len(parts): continue
160            if any(i in name for i in ["("]): continue;
161            if any(name.endswith(i) for i in ["_IRQn", "_IRQHandler", "_SUPPORT", "_TypeDef"]): continue;
162            if any(name.startswith(i) for i in ["IS_"]): continue;
163            if name in ["FLASH_SIZE", "FLASH_BANK_SIZE", "COMP1_COMMON", "COMP12_COMMON_BASE", "OPAMP12_COMMON_BASE", "BL_ID"]: continue;
164            defines[name] = "".join(parts[1:]).strip()
165        return defines
166
167    def _get_defines(self):
168        from jinja2 import Environment
169        # create the destination directory
170        destination = (Header.CACHE_PATH / self.family_folder / self.header_file).with_suffix(".cpp").absolute()
171        core = self.did.get("core", "")
172        executable = destination.with_suffix("."+core if core else "")
173        defines = self._get_filtered_defines()
174        if not executable.exists():
175            # generate the cpp file from the template
176            LOGGER.info("Generating {} ...".format(destination.name))
177            substitutions = {"header": self.header_file, "defines": sorted(defines)}
178            content = Environment().from_string(HEADER_TEMPLATE).render(substitutions)
179            # write the cpp file into the cache
180            destination.parent.mkdir(exist_ok=True, parents=True)
181            destination.write_text(content)
182            header_defines = [self.define]
183            if core: header_defines.append(f"CORE_C{core.upper()}=1")
184            # compile file into an executable
185            includes = [str(Header.CMSIS_PATH.absolute()), str(self.cmsis_folder.absolute())]
186            gcc_command = ["g++", "-Wno-narrowing", "-fms-extensions",
187                " ".join(f"-I{incl}" for incl in includes),
188                " ".join(f"-D{define}" for define in header_defines),
189                "-o {}".format(executable),
190                str(destination)
191            ]
192            LOGGER.info("Compiling {} ...".format(destination.name))
193            retval = subprocess.run(" ".join(gcc_command), shell=True)
194            if retval.returncode:
195                LOGGER.error("Header compilation failed! {}".format(retval));
196                return None
197        # execute the file
198        LOGGER.info("Running {} ...".format(executable.name))
199        retval = subprocess.run([str(executable)], stdout=subprocess.PIPE)
200        if retval.returncode:
201            LOGGER.error("Header execution failed! {}".format(retval));
202            return None
203        # parse the printed values
204        localv = {}
205        exec(retval.stdout, globals(), localv)
206        undefined = [d for d in defines if d not in localv["cpp_defines"]]
207        if len(undefined):
208            LOGGER.warning("Undefined macros: {}".format(undefined))
209        return localv["cpp_defines"]
210
211    def _get_memmap(self):
212        # get the values of the definitions in this file
213        defines = self.get_defines()
214
215        # get the mapping of peripheral to its type
216        peripheral_map = {}
217        seen_defines = []
218        for name, value in self._get_filtered_defines().items():
219            if "*)" in value:
220                values = value.split("*)")
221                typedef = values[0].strip()[2:].strip()
222                peripheral_map[name] = (typedef, defines[name])
223                LOGGER.debug(f"Found peripheral ({typedef} *) {name} @ 0x{defines[name]:x}")
224
225        # build the array containing the peripheral types
226        raw_types = {typedef: [(v["type"], v["name"], int(v["array_size"], 16 if v["array_size"].startswith("0x") else 10) if v["array"] else 0)
227                               for v in values["properties"]["public"]]
228                     for typedef, values in self.header.classes.items()}
229
230        # function to recursively flatten the types
231        def _flatten_type(typedef, result, prefix=""):
232            for (t, n, s) in raw_types[typedef]:
233                if t in Header.BUILTINS:
234                    size = Header.BUILTINS[t]
235                    name = None if n.upper().startswith("RESERVED") else n
236                    if s == 0:
237                        result.append( (size, (prefix + name) if name else name) )
238                    else:
239                        if not name:
240                            result.append( (size * s, name) )
241                        else:
242                            result.extend([(size, f"{prefix}{name}.{ii}") for ii in range(s)])
243                elif t in raw_types.keys():
244                    if s == 0:
245                        _flatten_type(t, result, prefix)
246                    else:
247                        for ii in range(s):
248                            _flatten_type(t, result, prefix + "{}.{}.".format(n, ii))
249                else:
250                    LOGGER.error("Unknown type: {} ({} {})".format(t, n, s))
251                    exit(1)
252
253        # flatten all types
254        flat_types = defaultdict(list)
255        for typedef in raw_types:
256            _flatten_type(typedef, flat_types[typedef])
257
258        # match the macro definitions to the type structures
259        matched_types = defaultdict(list)
260        for typedef, pregs in flat_types.items():
261            # print(typedef, pregs)
262            peri = "_".join([t for t in typedef.split("_") if t.isupper()])
263            position = 0
264            for reg in pregs:
265                if reg[1] is None:
266                    position += reg[0]
267                    continue
268                sreg = [r for r in reg[1].split(".") if r.isupper() or r.isdigit()]
269                prefix = ["{}_{}_".format(peri, "".join([r for r in sreg if r.isupper()]))]
270                if len(sreg) > 1:
271                    if sreg[0].isdigit():
272                        parts = sreg[1].split("R")
273                        parts[-2] += sreg[0]
274                        prefix.append("{}_{}_".format(peri, "R".join(parts)))
275                    elif sreg[1].isdigit():
276                        sreg[1] = str(int(sreg[1]) + 1)
277                        prefix.append("{}_{}_".format(peri, "".join([r for r in sreg if r.isupper()]) + sreg[1]))
278                        prefix.append("{}_{}x_".format(peri, "".join([r for r in sreg if r.isupper()])))
279                # A bunch of aliases
280                if "FSMC_BTCR_" in prefix: prefix.extend(["FSMC_BCRx_", "FSMC_BTRx_"])
281                if "ADC_TR_" in prefix: prefix.append("ADC_TR1_")
282                if "DBGMCU_APB1FZ_" in prefix: prefix.append("DBGMCU_APB1_FZ_")
283                if "DBGMCU_APB2FZ_" in prefix: prefix.append("DBGMCU_APB2_FZ_")
284                # if "FLASH_KEYR_" in prefix: prefix.extend(["FLASH_KEY1_", "FLASH_KEY2_"])
285                # if "FLASH_OPTKEYR_" in prefix: prefix.extend(["FLASH_OPTKEY1_", "FLASH_OPTKEY2_"])
286                if "GPIO_AFR1_" in prefix: prefix.append("GPIO_AFRL_")
287                if "GPIO_AFR2_" in prefix: prefix.append("GPIO_AFRH_")
288                if "SAI_Block" in typedef: prefix = [p.replace("SAI_", "SAI_x") for p in prefix]
289
290                regmap = {}
291                for p in prefix:
292                    keys = [d for d in defines.keys() if d.startswith(p)]
293                    seen_defines.extend(keys)
294                    regmap.update({k.replace(p, ""):defines[k] for k in keys})
295                if not len(regmap):
296                    LOGGER.info("Empty: {:30} {}->{} ({} >> {})".format(typedef, peri, prefix, reg[1], sreg))
297
298                # convert macro names to positional arguments
299                fields = sorted(list(set([r[:-4] for r in regmap if r.endswith("_Pos")])))
300                registers = {}
301                for field in fields:
302                    regs = {k:v for k,v in regmap.items() if k == field or k.startswith(field + "_")}
303                    val = regs.pop(field, None)
304                    pos = regs.pop(field + "_Pos", None)
305                    msk = regs.pop(field + "_Msk", None)
306                    if val is None:
307                        LOGGER.warning("{} not found: {}".format(field, regs))
308                        continue
309                    if pos is None:
310                        LOGGER.warning("{}_Pos not found: {}".format(field, regs))
311                        continue
312                    if msk is None:
313                        LOGGER.warning("{}_Msk not found: {}".format(field, regs))
314                        continue
315
316                    rem = {k.replace(field + "_", ""):v for k,v in regs.items()}
317                    mask = msk >> pos
318                    width = 0
319                    while(mask):
320                        width += 1
321                        mask >>= 1
322                    registers[pos] = (field, width, msk, val, rem)
323
324                # print(registers)
325                # Store in map
326                matched_types[typedef].append( (position, reg[0], reg[1], registers) )
327                position += reg[0]
328
329        # print the remaining
330        remaining_defines = [d for d in defines if d not in seen_defines and not d.endswith("_BASE")]
331        for typedef in matched_types:
332            peri = "_".join([t for t in typedef.split("_") if t.isupper()]) + "_"
333            rem = [d for d in remaining_defines if d.startswith(peri)]
334            if len(rem):
335                LOGGER.warning("Unassigned defines for ({} *) {}: {}".format(typedef, peri, len(rem)))
336                for d in rem:
337                    LOGGER.info("{}: {}".format(d, defines[d]))
338
339        # for typedef, registers in matched_types.items():
340        #     print(typedef)
341        #     for reg in registers:
342        #         print("    {:03x}: {}".format(reg[0], reg[2]))
343
344
345        device = svd.Device(self.did.string)
346        for name, (typedef, address) in peripheral_map.items():
347            svd.Peripheral(name, typedef, defines[name], parent=device)
348
349        for name, registers in matched_types.items():
350            peripheral = svd.PeripheralType(name, parent=device)
351            for offset, width, name, bitfields in registers:
352                register = svd.Register(name, offset, width, parent=peripheral)
353                for pos, (name, width, mask, value, _) in bitfields.items():
354                    svd.BitField(name, pos, width, parent=register)
355
356        return device
357        # return (peripheral_map, matched_types)
LOGGER = <Logger /opt/hostedtoolcache/Python/3.12.2/x64/lib/python3.12/site-packages/modm_data/header2svd/stmicro/header.py (WARNING)>
HEADER_TEMPLATE = '\n#include <iostream>\n#include <{{header}}>\n\ntemplate<typename T>\nvoid __modm_dump_f(char const *symbol, T value) {\n std::cout << "\\"" << symbol << "\\": " << uint64_t(value) << "," << std::endl;\n}\n#define __modm_dump(def) __modm_dump_f(#def, (def))\nint main() {\n std::cout << "cpp_defines = {";{% for define in defines %}\n#ifdef {{define}}\n __modm_dump({{define}});\n#endif{% endfor %}\n std::cout << "}";\n return 0;\n}\n'
def getDefineForDevice(device_id, familyDefines):
37def getDefineForDevice(device_id, familyDefines):
38    # get all defines for this device name
39    devName = "STM32{}{}".format(device_id.family.upper(), device_id.name.upper())
40
41    # Map STM32F7x8 -> STM32F7x7
42    if device_id.family == "f7" and devName[8] == "8":
43        devName = devName[:8] + "7"
44
45    deviceDefines = sorted([define for define in familyDefines if define.startswith(devName)])
46    # if there is only one define thats the one
47    if len(deviceDefines) == 1:
48        return deviceDefines[0]
49
50    # sort with respecting variants
51    minlen = min(len(d) for d in deviceDefines)
52    deviceDefines.sort(key=lambda d: (d[:minlen], d[minlen:]))
53
54    # now we match for the size-id (and variant-id if applicable).
55    if device_id.family == "h7":
56        devNameMatch = devName + "xx"
57    else:
58        devNameMatch = devName + "x{}".format(device_id.size.upper())
59    if device_id.family == "l1":
60        # Map STM32L1xxQC and STM32L1xxZC -> STM32L162QCxA variants
61        if device_id.pin in ["q", "z"] and device_id.size == "c":
62            devNameMatch += "A"
63        else:
64            devNameMatch += device_id.variant.upper()
65    elif device_id.family == "h7":
66        if device_id.variant:
67            devNameMatch += device_id.variant.upper()
68    for define in deviceDefines:
69        if devNameMatch <= define:
70            return define
71
72    # now we match for the pin-id.
73    devNameMatch = devName + "{}x".format(device_id.pin.upper())
74    for define in deviceDefines:
75        if devNameMatch <= define:
76            return define
77
78    return None