diff --git a/.github/workflows/test-kg.yml b/.github/workflows/test-kg.yml new file mode 100644 index 0000000..ed362df --- /dev/null +++ b/.github/workflows/test-kg.yml @@ -0,0 +1,88 @@ +name: Test Generate Knowledge Graphs + +on: + workflow_dispatch: + pull_request: + +jobs: + test-kg-stmicro: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + family_group: + - name: 'L1_4_U0' + families: [STM32L1, STM32L4, STM32U0] + - name: 'F0_4_H5_WL' + families: [STM32F0, STM32F4, STM32H5, STM32WL] + - name: 'G4_F7' + families: [STM32G4, STM32F7] + - name: 'G0_L0_U5' + families: [STM32G0, STM32L0, STM32U5] + - name: 'C0_F1_2_3_L5_U3_WB' + families: [STM32C0, STM32F1, STM32F2, STM32F3, STM32L5, STM32U3, STM32WB] + - name: 'H7' + families: [STM32H7] + + name: Update KG ${{ matrix.family_group.name }} + + steps: + - name: Check out Repository + uses: actions/checkout@v4 + + - name: Setup Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: "3.12" + + - name: Install Python Dependencies + run: | + pip3 install -r tools/requirements.txt -e . + + - name: Clone STMicro CubeMX Archive + uses: actions/checkout@v4 + with: + repository: modm-ext/archive-stmicro-cubemx + path: ext/stmicro/cubemx + ssh-key: ${{ secrets.SSH_KEY_STMICRO_CUBEMX }} + + - name: Clone STMicro Sources + run: | + make ext/arm/cmsis/ ext/stmicro/cubehal/ ext/stmicro/header/ + + - name: Generate Database for ${{ matrix.family_group.name }} + run: | + FAMILY_LIST='${{ toJson(matrix.family_group.families) }}' + OVERALL_STATUS=0 + set +e + + for FAMILY in $(echo "$FAMILY_LIST" | jq -r '.[]') + do + echo "::group::Generating database for $FAMILY" + + python3 -m modm_data.cube2kg --family $FAMILY + if [ $? -ne 0 ]; then + echo "::error::Database generation for $FAMILY FAILED." + OVERALL_STATUS=1 + fi + + echo "::endgroup::" + done + + exit $OVERALL_STATUS + + - name: Upload KG Logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: logs-${{ matrix.family_group.name }} + path: log/stmicro/kg + retention-days: 7 + + - name: Upload KGs + if: always() + uses: actions/upload-artifact@v4 + with: + name: kg-${{ matrix.family_group.name }} + path: ext/stmicro/kg-archive + retention-days: 7 diff --git a/pyproject.toml b/pyproject.toml index 039fa95..b0f0af7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "CppHeaderParser>=2.7.4,<3", "jinja2>=3.1.3,<4", "lxml>=5.2.0,<6", - "owlready2==0.45", + "kuzu>=0.11.3,<0.12", "pypdfium2==4.30.0", "tqdm>=4.66.2,<5", ] diff --git a/src/modm_data/__init__.py b/src/modm_data/__init__.py index 0c0cd49..7fef54f 100644 --- a/src/modm_data/__init__.py +++ b/src/modm_data/__init__.py @@ -21,7 +21,7 @@ "html", "html2owl", "html2svd", - "owl", + "kg", "pdf", "pdf2html", "svd", diff --git a/src/modm_data/cube2owl/__init__.py b/src/modm_data/cube2kg/__init__.py similarity index 100% rename from src/modm_data/cube2owl/__init__.py rename to src/modm_data/cube2kg/__init__.py diff --git a/src/modm_data/cube2owl/__main__.py b/src/modm_data/cube2kg/__main__.py similarity index 50% rename from src/modm_data/cube2owl/__main__.py rename to src/modm_data/cube2kg/__main__.py index 9177916..9fa32a0 100644 --- a/src/modm_data/cube2owl/__main__.py +++ b/src/modm_data/cube2kg/__main__.py @@ -8,10 +8,7 @@ from multiprocessing.pool import ThreadPool from modm_data.cubemx import devices_from_prefix, devices_from_partname -from modm_data.header2svd.stmicro import Header -from modm_data.html.stmicro import datasheet_for_device, reference_manual_for_device -from modm_data.owl.stmicro import create_ontology -from modm_data.py2owl.stmicro import owl_from_did, owl_from_cubemx, owl_from_header, owl_from_doc +from modm_data.kg.stmicro import kg_create, kg_from_cubemx def main(): @@ -22,13 +19,13 @@ def main(): if args.family: partnames = devices_from_prefix(args.family.lower()) - print(partnames) # cluster the parallel jobs only around family+name partnames = sorted(list(set(p[:9] for p in partnames))) + print(" ".join(partnames)) - Path("log/stmicro/owl").mkdir(exist_ok=True, parents=True) + Path("log/stmicro/kg").mkdir(exist_ok=True, parents=True) calls = [ - f"python3 -m modm_data.cube2owl --prefix {partname} > log/stmicro/owl/cubemx_{partname}.txt 2>&1" + f"python3 -m modm_data.cube2kg --prefix {partname} > log/stmicro/kg/cube2kg_{partname}.txt 2>&1" for partname in partnames ] with ThreadPool() as pool: @@ -39,29 +36,17 @@ def main(): return all(r.returncode == 0 for r in retvals) for partname in devices_from_prefix(args.prefix.lower()): - ds, rm = None, None for device in devices_from_partname(partname): did = device["id"] - # Only change the documentation object if necessary to preserve caching - if ds != (nds := datasheet_for_device(did)): - ds = nds - if rm != (nrm := reference_manual_for_device(did)): - rm = nrm - print(did, ds, rm) - if ds is None or rm is None: - print(f"Ignoring {did} due to lack of documents") - continue - cmsis_header = Header(did) - onto = create_ontology(did) + db = kg_create(did) + kg_from_cubemx(db, device) - owl_from_did(onto) - owl_from_cubemx(onto, device) - owl_from_header(onto, cmsis_header) - owl_from_doc(onto, ds) - owl_from_doc(onto, rm) + # header = read_header(did) + # memory_map_from_header(header) + # kg_from_header(db, header) - onto.store.save(did.string) + db.save() return True diff --git a/src/modm_data/cubehal/__init__.py b/src/modm_data/cubehal/__init__.py index 456886f..ae06081 100644 --- a/src/modm_data/cubehal/__init__.py +++ b/src/modm_data/cubehal/__init__.py @@ -10,6 +10,6 @@ - Determine the map of register bit field values to names. """ -from .dmamux_requests import read_request_map +from .dmamux_requests import read_request_map, read_bdma_request_map -__all__ = ["read_request_map"] +__all__ = ["read_request_map", "read_bdma_request_map"] diff --git a/src/modm_data/cubehal/dmamux_requests.py b/src/modm_data/cubehal/dmamux_requests.py index d631107..eb52c67 100644 --- a/src/modm_data/cubehal/dmamux_requests.py +++ b/src/modm_data/cubehal/dmamux_requests.py @@ -4,11 +4,12 @@ import re from pathlib import Path from ..utils import ext_path -from ..owl import DeviceIdentifier +from ..kg import DeviceIdentifier _CUBE_PATH = ext_path("stmicro/cubehal") _DMAMUX_PATTERN = re.compile(r"^\s*#define\s+(?P(LL_DMAMUX_REQ_\w+))\s+(?P(0x[0-9A-Fa-f]+))U") _REQUEST_PATTERN = re.compile(r"^\s*#define\s+(?P(DMA_REQUEST_\w+))\s+(?P([0-9]+))U") +_BDMA_REQUEST_PATTERN = re.compile(r"^\s*#define\s+(?P(BDMA_REQUEST_\w+))\s+(?P([0-9]+))U") def read_request_map(did: DeviceIdentifier) -> dict[str, int]: @@ -18,27 +19,34 @@ def read_request_map(did: DeviceIdentifier) -> dict[str, int]: :param did: Device to query for. :return: A dictionary of DMA trigger name to trigger position. """ - dma_header = _get_hal_dma_header_path(did.family) - dmamux_header = _get_ll_dmamux_header_path(did.family) + dma_header = _get_hal_dma_header_path(did) + dmamux_header = _get_ll_dmamux_header_path(did) request_map = None - if did.family in ["g4", "h7", "l5"]: - request_map = _read_requests(dma_header) - elif did.family in ["g0", "wb", "wl"]: + if did.family in ["c0", "g4", "h7", "l5"]: + request_map = _read_requests(dma_header, _REQUEST_PATTERN) + elif did.family in ["g0", "u0", "wb", "wl"]: request_map = _read_requests_from_ll_dmamux(dma_header, dmamux_header) elif did.family == "l4" and did.name[0] in ["p", "q", "r", "s"]: - request_map = _read_requests_l4(did.name in ["p5", "q5"]) + request_map = _read_requests_l4(did) else: - raise RuntimeError(f"No DMAMUX request data available for {did}") - _fix_request_data(request_map) + raise RuntimeError("No DMAMUX request data available for {}".format(did)) + _fix_request_data(request_map, "DMA") return request_map -def _fix_request_data(request_map): +def read_bdma_request_map(did): + dma_header = _get_hal_dma_header_path(did) + request_map = _read_requests(dma_header, _BDMA_REQUEST_PATTERN) + _fix_request_data(request_map, "BDMA") + return request_map + + +def _fix_request_data(request_map, prefix): fix_requests = {} dac_pattern = re.compile(r"(?P(DAC[0-9]))_CHANNEL(?P[0-9])") for name, number in request_map.items(): if name.startswith("GENERATOR"): - fix_requests["DMA_" + name] = number + fix_requests[prefix + "_" + name] = number elif name == "FMAC_READ": fix_requests["FMAC_RD"] = number elif name == "FMAC_WRITE": @@ -61,6 +69,16 @@ def _fix_request_data(request_map): fix_requests["SUBGHZ_RX"] = number elif name == "SUBGHZSPI_TX": fix_requests["SUBGHZ_TX"] = number + elif name == "DAC": + fix_requests["DAC_OUT1"] = number + elif name == "USART_TX": + fix_requests["USART1_TX"] = number + elif name == "USART_RX": + fix_requests["USART1_RX"] = number + elif name == "LPUART_TX": + fix_requests["LPUART1_TX"] = number + elif name == "LPUART_RX": + fix_requests["LPUART1_RX"] = number else: m = dac_pattern.match(name) if m: @@ -69,40 +87,59 @@ def _fix_request_data(request_map): request_map.update(fix_requests) +def _get_family(did): + family = f"{did.family}xx" + if did.string[5:8] in ["h7r", "h7s"]: + family = "h7rsxx" + elif did.string[5:8] == "wb0": + family = "wb0x" + elif did.string[5:8] == "wba": + family = "wbax" + elif did.string[5:8] == "wl3": + family = "wl3x" + return family + + def _get_include_path(family): - return _CUBE_PATH / Path(f"stm32{family}xx/Inc") + return _CUBE_PATH / Path("stm32{}/Inc".format(family)) -def _get_hal_dma_header_path(family): - return _get_include_path(family) / Path(f"stm32{family}xx_hal_dma.h") +def _get_hal_dma_header_path(did): + family = _get_family(did) + return _get_include_path(family) / Path("stm32{}_hal_dma.h".format(family)) -def _get_ll_dmamux_header_path(family): - return _get_include_path(family) / Path(f"stm32{family}xx_ll_dmamux.h") +def _get_ll_dmamux_header_path(did): + family = _get_family(did) + return _get_include_path(family) / Path("stm32{}_ll_dmamux.h".format(family)) # For G4, H7 and L5 -def _read_requests(hal_dma_file): - requests_map = _read_map(hal_dma_file, _REQUEST_PATTERN) +def _read_requests(hal_dma_file, request_pattern): + requests_map = _read_map(hal_dma_file, request_pattern) out_map = {} for r in requests_map.keys(): - out_map[r.replace("DMA_REQUEST_", "", 1)] = int(requests_map[r]) + prefix = "BDMA" if "BDMA" in r else "DMA" + out_map[r.replace(prefix + "_REQUEST_", "", 1)] = int(requests_map[r]) return out_map # For G0, WB and WL def _read_requests_from_ll_dmamux(hal_dma_file, ll_dmamux_file): dmamux_map = _read_map(ll_dmamux_file, _DMAMUX_PATTERN) - request_pattern = re.compile(r"^\s*#define\s+(?P(DMA_REQUEST_\w+))\s+(?P(LL_DMAMUX?_REQ_\w+))\s*") + request_pattern = re.compile( + r"^\s*#define\s+(?P(DMAM?U?X?_REQU?E?S?T?_\w+))\s+(?P(LL_DMAMUX?_REQ_\w+))\s*" + ) requests_map = _read_map(hal_dma_file, request_pattern) out_map = {} for r in requests_map.keys(): - out_map[r.replace("DMA_REQUEST_", "", 1)] = int(dmamux_map[requests_map[r]], 16) + out_map[r.replace("DMA_REQUEST_", "", 1).replace("DMAMUX_REQ_", "", 1)] = int(dmamux_map[requests_map[r]], 16) return out_map # For L4+ -def _read_requests_l4(read_for_p5_q5): +def _read_requests_l4(did): + read_for_p5_q5 = did.name in ["p5", "q5"] out_map = {} p5_q5_if = "#if defined (STM32L4P5xx) || defined (STM32L4Q5xx)" if_pattern = re.compile(r"^\s*#\s*if\s+") @@ -110,7 +147,7 @@ def _read_requests_l4(read_for_p5_q5): endif_pattern = re.compile(r"^\s*#\s*endif") in_p5_q5_section = False ignore = False - with open(_get_hal_dma_header_path("l4"), "r") as header_file: + with open(_get_hal_dma_header_path(did), "r") as header_file: if_counter = 0 for line in header_file.readlines(): if p5_q5_if in line: diff --git a/src/modm_data/cubemx/device_data.py b/src/modm_data/cubemx/device_data.py index 8d12e79..8c3d418 100644 --- a/src/modm_data/cubemx/device_data.py +++ b/src/modm_data/cubemx/device_data.py @@ -7,11 +7,12 @@ import logging from collections import defaultdict -from ..owl.stmicro import did_from_string -from ..owl import DeviceIdentifier +from ..kg.stmicro import did_from_string +from ..kg import DeviceIdentifier from ..utils import ext_path, XmlReader from . import stm32_data from ..cubehal import read_request_map as dmamux_request_map +from ..cubehal import read_bdma_request_map as dmamux_bdma_request_map from . import peripherals LOGGER = logging.getLogger(__file__) @@ -105,25 +106,22 @@ def devices_from_partname(partname: str) -> list[dict[str]]: def _properties_from_id(comboDeviceName, device_file, did, core): if core.endswith("m4") or core.endswith("m7") or core.endswith("m33"): core += "f" - if did.family in ["h7"] or (did.family in ["f7"] and did.name not in ["45", "46", "56"]): + if did.family in ["h7"] or (did.family in ["f7"] and (did.name[0] in ("6", "7"))): core += "d" if "@" in did.naming_schema: did.set("core", core[7:9]) p = {"id": did, "core": core} # Maximum operating frequency - max_frequency = float(device_file.query("//Frequency")[0].text) + if max_frequency := device_file.query("//Frequency"): + max_frequency = float(max_frequency[0].text) + else: + max_frequency = stm32_data.getMaxFrequencyForDevice(did) # H7 dual-core devices run the M4 core at half the frequency as the M7 core if did.get("core", "") == "m4": max_frequency /= 2.0 p["max_frequency"] = int(max_frequency * 1e6) - # Information from the CMSIS headers - # stm_header = Header(did) - # if not stm_header.is_valid: - # LOGGER.error("CMSIS Header invalid for %s", did.string) - # return None - # flash and ram sizes # The and can occur multiple times. # they are "ordered" in the same way as the `(S-I-Z-E)` ids in the device combo name @@ -155,7 +153,7 @@ def _properties_from_id(comboDeviceName, device_file, did, core): access = "rw" if "flash" in mem_name: access = "rx" - memories.append({"name": mem_name, "access": access, "size": str(mem_size), "start": f"0x{mem_start:02X}"}) + memories.append({"name": mem_name, "access": access, "size": mem_size, "start": mem_start}) p["memories"] = memories @@ -175,6 +173,8 @@ def clean_up_version(version): modules = [] dmaFile = None + bdmaFile = None + hasFlashModule = False for ip in device_file.query("//IP"): # These IPs are all software modules, NOT hardware modules. Their version string is weird too. software_ips = { @@ -199,12 +199,17 @@ def clean_up_version(version): "USBX", "LINKEDLIST", "NETXDUO", + "BOOTPATH", + "MEMORYMAP", + "OPENAMP", } if any(ip.get("Name").upper().startswith(p) for p in software_ips): continue rversion = ip.get("Version") module = (ip.get("Name"), ip.get("InstanceName"), clean_up_version(rversion)) + if "flash" in module[0].lower(): + hasFlashModule = True if module[0] == "DMA": # lets load additional information about the DMA @@ -213,22 +218,29 @@ def clean_up_version(version): for dma in rdma.split(","): modules.append((module[0].lower(), dma.strip().lower(), module[2].lower())) continue - if module[0].startswith("TIM"): + elif module[0].startswith("BDMA"): + module = ("BDMA",) + module[1:] + # Ignore BDMA1 data + # If two instances exist the first one is hard-wired to DFSDM channels and has no associated DMAMUX data + if module[1] != "BDMA1": + bdmaFile = XmlReader(os.path.join(_MCU_PATH, "IP", module[1] + "-" + rversion + "_Modes.xml")) + elif module[0].startswith("TIM"): module = ("TIM",) + module[1:] + elif module[0] == "MDF" and module[1].startswith("ADF"): + module = ("ADF",) + module[1:] modules.append(tuple([m.lower() for m in module])) - modules.append(("flash", "flash", "v1.0")) + if not hasFlashModule: + modules.append(("flash", "flash", "v1.0")) modules = [m + peripherals.getPeripheralData(did, m) for m in modules] p["modules"] = modules LOGGER.debug("Available Modules are:\n" + _modulesToString(modules)) # print("\n".join(str(m) for m in modules)) - # p["stm_header"] = stm_header - # p["interrupts"] = stm_header.interrupt_table # Flash latency table - # p["flash_latency"] = stm32_data.getFlashLatencyForDevice(did) + p["flash_latency"] = stm32_data.getFlashLatencyForDevice(did) # lets load additional information about the GPIO IP ip_file = device_file.query('//IP[@Name="GPIO"]')[0].get("Version") @@ -248,8 +260,6 @@ def raw_pin_sort(p): # Remove package remaps from GPIO data (but not from package) pins.sort(key=lambda p: "PINREMAP" not in p.get("Variant", "")) - gpios = [] - def pin_name(name): name = name[:4] if len(name) > 3 and not name[3].isdigit(): @@ -293,7 +303,7 @@ def split_af(af): minst = [m for m in modules if af.startswith(m[1] + "_")] # print(af, mdriv, minst) if len(minst) > 1: - LOGGER.warning(f"Ambiguos driver: {af} {minst}") + LOGGER.warning(f"Ambiguous driver: {af} {minst}") exit(1) minst = minst[0] if len(minst) else None @@ -439,7 +449,7 @@ def deduplicate_list(dl): # a child node filtering by the STM32 die id # Try to match a node with condition first, if nothing matches choose the default one die_id = device_file.query("//Die")[0].text - q = '//RefParameter[@Name="Instance"]/Condition[@Expression="%s"]/../PossibleValue/@Value' % die_id + q = f'//RefParameter[@Name="Instance"]/Condition[@Expression="{die_id}"]/../PossibleValue/@Value' channels = dmaFile.query(q) if len(channels) == 0: # match channels from node without child node @@ -460,10 +470,75 @@ def deduplicate_list(dl): ) p["dma_mux_channels"] = mux_channels + if bdmaFile is not None: + bdma_channels = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) + p["bdma_naming"] = (None, "request", "signal") + bdma_request_map = None + for sig in bdmaFile.query('//ModeLogicOperator[@Name="XOR"]/Mode'): + name = rname = sig.get("Name") + + request = bdmaFile.query('//RefMode[@Name="{}"]'.format(name))[0] + name = name.lower().split(":")[0] + if name == "memtomem": + continue + + if name.startswith("dac") and "_" not in name: + name = "dac_{}".format(name) + if len(name.split("_")) < 2: + name = "{}_default".format(name) + driver, inst, name = split_af(name) + + if bdma_request_map is None: + bdma_request_map = dmamux_bdma_request_map(did) + channel = bdma_request_map[rname] + + if driver is None: # peripheral is not part of this device + continue + + mode = [v[4:].lower() for v in rv("Mode")] + for sname in [None] if name == "default" else name.split("/"): + signal = { + "driver": driver, + "name": sname, + "direction": [ + v[4:].replace("PERIPH", "p").replace("MEMORY", "m").replace("_TO_", "2") + for v in rv("Direction") + ], + "mode": mode, + "increase": "ENABLE" in rv("PeriphInc", ["DMA_PINC_ENABLE"])[0], + } + if inst: + signal["instance"] = inst + bdma_channels[0][0][channel].append(signal) + # print(channel) + # print(signal) + + p["bdma"] = bdma_channels + + mux_channels = [] + mux_channel_regex = re.compile(r"BDMA(?P2)?_Channel(?P([0-9]+))") + channel_names = bdmaFile.query('//RefParameter[@Name="Instance" and not(Condition)]/PossibleValue/@Value') + for mux_ch_position, channel in enumerate(channel_names): + m = mux_channel_regex.match(channel) + assert m is not None + if m.group("instance"): + mux_channels.append( + { + "position": mux_ch_position, + "dma-instance": int(m.group("instance")), + "dma-channel": int(m.group("channel")), + } + ) + else: + mux_channels.append({"position": mux_ch_position, "dma-channel": int(m.group("channel"))}) + p["bdma_mux_channels"] = mux_channels + if did.family == "f1": grouped_f1_signals = gpioFile.compactQuery("//GPIO_Pin/PinSignal/@Name") _seen_gpio = set() + gpios = [] + signals = set() for pin in pins: rname = pin.get("Name") name = pin_name(rname) @@ -490,9 +565,8 @@ def deduplicate_list(dl): naf = {} naf["driver"], naf["instance"], naf["name"] = raf naf["af"] = af[1] if int(af[1]) >= 0 else None - if "exti" in naf["name"]: - continue afs.append(naf) + signals.add(naf["name"]) gpio = (name[0], name[1], afs) if name not in _seen_gpio: @@ -518,18 +592,16 @@ def deduplicate_list(dl): if driver is None: continue mmm["name"] = name + signals.add(name) mpins.append(mmm) if module not in remaps: - driver, instance, _ = split_af(module + "_lol") - if not driver: + if not split_af(module + "_lol")[0]: continue remaps[module] = { "mask": mapping["mask"], "position": mapping["position"], "groups": {}, - "driver": driver, - "instance": instance, } if len(mpins) > 0: remaps[module]["groups"][mapping["mapping"]] = mpins @@ -542,6 +614,7 @@ def deduplicate_list(dl): p["remaps"] = remaps p["gpios"] = gpios + p["signals"] = signals return p diff --git a/src/modm_data/cubemx/peripherals.py b/src/modm_data/cubemx/peripherals.py index a964507..ca06eee 100644 --- a/src/modm_data/cubemx/peripherals.py +++ b/src/modm_data/cubemx/peripherals.py @@ -6,6 +6,12 @@ { "instances": "*", "groups": [ + { + "hardware": "stm32-c0", + "features": ["oversampler", "calfact", "prescaler"], + "protocols": ["analog-in"], + "devices": [{"family": ["c0"]}], + }, {"hardware": "stm32-f0", "features": [], "protocols": ["analog-in"], "devices": [{"family": ["f0"]}]}, { "hardware": "stm32-l0", @@ -54,7 +60,7 @@ "hardware": "stm32", "features": ["filter-14"], "protocols": ["can-v2.0a", "can-v2.0b"], - "devices": [{"family": ["f0", "g0", "f1"]}], + "devices": [{"family": ["c0", "f0", "g0", "f1"]}], }, { # 28 shared filters @@ -112,7 +118,7 @@ "features": [], "protocols": ["mem2mem", "mem2per", "per2per"], "devices": [ - {"family": ["g0", "g4", "l5", "wb", "wl"]}, + {"family": ["c0", "g0", "g4", "l5", "wb", "wl"]}, { "family": ["l4"], "name": ["p5", "p7", "p9", "q5", "q7", "q9", "r5", "r7", "r9", "s5", "s7", "s9"], @@ -123,7 +129,7 @@ "hardware": "stm32-mux-stream", "features": [], "protocols": ["mem2mem", "mem2per", "per2per"], - "devices": [{"family": ["h7"]}], + "devices": [{"family": ["h5", "h7"]}], }, { "hardware": "stm32-stream-channel", @@ -150,6 +156,19 @@ ], } ], + "bdma": [ + { + "instances": "*", + "groups": [ + { + "hardware": "stm32h7", + "features": [], + "protocols": ["mem2mem", "mem2per", "per2per"], + "devices": [{"family": ["h5", "h7"]}], + } + ], + } + ], "iwdg": [ { "instances": "*", @@ -172,9 +191,14 @@ "hardware": "stm32", "features": ["data-size", "nss-pulse", "fifo"], "protocols": [], - "devices": [{"family": ["f0", "g0", "f3", "f7", "l4", "l5", "g4", "wb"]}], + "devices": [{"family": ["c0", "f0", "g0", "f3", "f7", "l4", "l5", "g4", "wb"]}], + }, + { + "hardware": "stm32-extended", + "features": [], + "protocols": [], + "devices": [{"family": ["h5", "h7", "u5"]}], }, - {"hardware": "stm32-extended", "features": [], "protocols": [], "devices": [{"family": ["h7", "u5"]}]}, {"hardware": "stm32", "features": [], "protocols": [], "devices": "*"}, ], } @@ -197,15 +221,15 @@ "groups": [{"hardware": "stm32-advanced", "features": [], "protocols": [], "devices": "*"}], }, { - "instances": ["2", "3", "4", "5"], + "instances": ["2", "3", "4", "5", "19", "23", "24"], "groups": [{"hardware": "stm32-general-purpose", "features": [], "protocols": [], "devices": "*"}], }, { - "instances": ["9", "10", "11", "12", "13", "14", "15", "16", "17"], + "instances": ["9", "10", "11", "12", "13", "14", "15", "16", "17", "21", "22"], "groups": [{"hardware": "stm32-general-purpose", "features": [], "protocols": [], "devices": "*"}], }, { - "instances": ["6", "7"], + "instances": ["6", "7", "18"], "groups": [{"hardware": "stm32-basic", "features": [], "protocols": [], "devices": "*"}], }, ], @@ -236,7 +260,7 @@ "hardware": "stm32", "features": ["exti", "cfgr2", "itline"], "protocols": [], - "devices": [{"family": ["f0"], "name": ["91", "98"]}, {"family": ["g0"]}], + "devices": [{"family": ["f0"], "name": ["91", "98"]}, {"family": ["c0", "g0"]}], }, {"hardware": "stm32", "features": ["exti", "cfgr2"], "protocols": [], "devices": [{"family": ["f0"]}]}, {"hardware": "stm32", "features": ["exti"], "protocols": [], "devices": "*"}, @@ -289,14 +313,14 @@ "hardware": "stm32-extended", "features": ["dnf", "fmp"], "protocols": ["i2c-v3.0"], - "devices": [{"family": ["f0", "g0", "l0"]}], + "devices": [{"family": ["f0", "g0", "l0", "u0"]}], }, { # This hardware supports FM+ (1 Mhz) and SMBus "hardware": "stm32-extended", "features": ["dnf", "fmp"], "protocols": ["i2c-v3.0", "smb-v2.0", "pmb-v1.1"], - "devices": [{"family": ["f3", "f7", "l4", "l5", "h7", "g4", "u5", "wb"]}], + "devices": [{"family": ["c0", "f3", "f7", "l4", "l5", "h5", "h7", "g4", "u5", "wb"]}], }, ], }, @@ -309,7 +333,9 @@ "hardware": "stm32-extended", "features": ["dnf", "fmp"], "protocols": ["i2c-v3.0", "smb-v2.0", "pmb-v1.1"], - "devices": [{"family": ["f0", "g0", "f3", "f7", "l0", "l4", "l5", "h7", "g4", "u5", "wb"]}], + "devices": [ + {"family": ["c0", "f0", "g0", "f3", "f7", "l0", "l4", "l5", "h5", "h7", "g4", "u0", "u5", "wb"]} + ], } ], }, @@ -333,7 +359,7 @@ "family": ["l4"], "name": ["p5", "p7", "p9", "q5", "q7", "q9", "r5", "r7", "r9", "s5", "s7", "s9"], }, - {"family": ["g0", "g4", "wb", "h7", "l5", "u5"]}, + {"family": ["c0", "g0", "g4", "wb", "h7", "l5", "u5"]}, ], }, { @@ -371,7 +397,7 @@ "family": ["l4"], "name": ["p5", "p7", "p9", "q5", "q7", "q9", "r5", "r7", "r9", "s5", "s7", "s9"], }, - {"family": ["g0", "g4", "wb", "h7", "l5", "u5"]}, + {"family": ["c0", "g0", "g4", "wb", "h5", "h7", "l5", "u5"]}, ], }, { diff --git a/src/modm_data/cubemx/stm32_data.py b/src/modm_data/cubemx/stm32_data.py index 62c9c00..0f57079 100644 --- a/src/modm_data/cubemx/stm32_data.py +++ b/src/modm_data/cubemx/stm32_data.py @@ -6,8 +6,12 @@ LOGGER = logging.getLogger("dfg.stm.data") ignored_devices = [ - "STM32U59", - "STM32U5A", + "STM32G411", + "STM32G414", + "STM32WL5M", + "STM32WB1M", + "STM32WB5M", + "STM32WBA", ] @@ -75,6 +79,68 @@ def getGpioRemapForModuleConfig(module, config): return mmm +stm32_max_frequency = { + "c0": 48, + "f0": 48, + "f1": [ + {"name": ["00"], "f": 24}, + {"name": ["01"], "f": 36}, + {"name": ["02"], "f": 48}, + 72, + ], + "f2": 120, + "f3": 72, + "f4": [ + {"name": ["01"], "f": 84}, + {"name": ["10", "11", "12", "13", "23"], "f": 100}, + {"name": ["05", "07", "15", "17"], "f": 168}, + 180, + ], + "f7": 216, + "g0": 64, + "g4": 170, + "h5": 250, + "h7": [ + {"name": ["a3", "b0", "b3"], "f": 280}, + {"name": ["23", "25", "30", "33", "35"], "f": 550}, + {"name": ["r3", "s3", "r7", "s7"], "f": 600}, + 480, + ], + "l0": 32, + "l1": 32, + "l4": [ + {"name": ["s5", "s7", "s9", "q5", "p5", "r5", "r7", "r9"], "f": 120}, + 80, + ], + "l5": 110, + "u0": 56, + "u3": 96, + "u5": 160, + "wb": 64, + "wl": 48, +} + + +def getMaxFrequencyForDevice(did): + freq = stm32_max_frequency.get(did.family) + assert freq, f"No max frequency defined for family {did.family}" + if isinstance(freq, int): + return freq + + # Convert MHz to Hz and filter out string keys + def lconv(lt): + if isinstance(lt, int): + return lt + return lt["f"] + + for lt in freq: + if isinstance(lt, int): + return lt + # check if all conditions match + if all(did[k] in v for k, v in lt.items() if not isinstance(v, int)): + return lconv(lt) # return filtered table + + stm32_flash_latency = { "f0": {1800: [24, 48]}, "f1": [{"name": ["00"], 1800: [24]}, {1800: [24, 48, 72]}], @@ -138,8 +204,7 @@ def getGpioRemapForModuleConfig(module, config): "l4": [ { # L4+ devices "name": ["r5", "r7", "r9", "s5", "s7", "s9", "p5", "q5"], - 1280: [20, 40, 60, 80, 100, 120], - 1200: [20, 40, 60, 80], + 1200: [20, 40, 60, 80, 120], 1000: [8, 16, 26], }, { # L4 devices @@ -152,8 +217,17 @@ def getGpioRemapForModuleConfig(module, config): 1200: [20, 40, 60, 80], # Vcore range 1 1000: [8, 16, 26], # Vcore range 2 }, + "c0": { + 1200: [24, 48], + }, "g0": {1200: [24, 48, 64], 1000: [8, 16]}, "g4": {1280: [20, 40, 60, 80, 100, 120, 140, 160, 170], 1000: [8, 16, 26]}, + "h5": { + 1260: [42, 84, 126, 168, 210, 250], + 1150: [34, 68, 102, 136, 170, 200], + 1050: [30, 60, 90, 120, 150], + 950: [20, 40, 60, 80, 100], + }, "h7": [ { "name": ["23", "25", "30", "33", "35"], @@ -178,6 +252,8 @@ def getGpioRemapForModuleConfig(module, config): ], "wb": {1200: [18, 36, 54, 64], 1000: [6, 12, 16]}, "wl": {1200: [18, 36, 48], 1000: [6, 12, 16]}, + "u0": {1200: [24, 48, 56], 1000: [8, 16, 16]}, + "u3": {900: [32, 64, 96], 750: [16, 32, 48]}, "u5": { 1200: [32, 64, 96, 128, 160], 1100: [30, 60, 90, 110], @@ -369,6 +445,15 @@ def getDmaRemap(did, dma, channel, driver, inst, signal): } ], }, + "c0": { + "start": {"flash": 0x08000000, "sram": 0x20000000}, + "model": [ + {"name": ["11", "31", "71"], "memories": {"flash": 0, "sram1": 0}}, + {"name": ["51"], "memories": {"flash": 0, "sram1": 12 * 1024}}, + {"name": ["91"], "memories": {"flash": 0, "sram1": 36 * 1024}}, + {"name": ["92"], "memories": {"flash": 0, "sram1": 30 * 1024}}, + ], + }, "g0": { "start": {"flash": 0x08000000, "sram": 0x20000000}, "model": [ @@ -477,6 +562,32 @@ def getDmaRemap(did, dma, channel, driver, inst, signal): }, ], }, + "h5": { + "start": {"flash": 0x08000000, "itcm": 0x00000000, "sram": 0x20000000, "backup": 0x40036400}, + "model": [ + {"name": ["03"], "memories": {"flash": 0, "sram1": 0, "sram2": 16 * 1024, "backup": 2 * 1024}}, + { + "name": ["23", "33"], + "memories": { + "flash": 0, + "sram1": 128 * 1024, + "sram2": 80 * 1024, + "sram3": 64 * 1024, + "backup": 2 * 1024, + }, + }, + { + "name": ["62", "63", "73"], + "memories": { + "flash": 0, + "sram1": 256 * 1024, + "sram2": 64 * 1024, + "sram3": 320 * 1024, + "backup": 4 * 1024, + }, + }, + ], + }, "h7": { "start": { "flash": 0x08000000, @@ -571,6 +682,21 @@ def getDmaRemap(did, dma, channel, driver, inst, signal): "d3_sram": 32 * 1024, }, }, + { + "name": ["r3", "r7", "s3", "s7"], + "memories": { + "flash": 0, + "itcm": 64 * 1024, + "dtcm": 64 * 1024, + "backup": 4 * 1024, + "d1_sram1": 128 * 1024, + "d1_sram2": 128 * 1024, + "d1_sram3": 128 * 1024, + "d1_sram4": 72 * 1024, + "d2_sram1": 16 * 1024, + "d2_sram2": 16 * 1024, + }, + }, ], }, "l0": { @@ -654,24 +780,74 @@ def getDmaRemap(did, dma, channel, driver, inst, signal): "wb": { "start": {"flash": 0x08000000, "sram": 0x20000000}, "model": [ + {"name": ["05"], "memories": {"flash": 0, "sram0": 12 * 1024, "sram1": 0}}, + { + "name": ["06", "07", "09"], + "memories": { + "flash": 0, + "sram0": 16 * 1024, + "sram1": 16 * 1024, + "sram2": 16 * 1024, + "sram3": 16 * 1024, + }, + }, {"name": ["10", "15", "1m"], "memories": {"flash": 0, "sram1": 0, "sram2": 36 * 1024}}, {"name": ["30", "35", "50", "55", "5m"], "memories": {"flash": 0, "sram1": 0, "sram2": 64 * 1024}}, ], }, "wl": { "start": {"flash": 0x08000000, "sram": 0x20000000}, - "model": [{"name": ["54", "55", "e4", "e5"], "memories": {"flash": 0, "sram1": 0, "sram2": 32 * 1024}}], + "model": [ + {"name": ["30", "31"], "memories": {"flash": 0, "sram0": 0}}, + {"name": ["33"], "memories": {"flash": 0, "sram0": 16 * 1024, "sram1": 0}}, + {"name": ["54", "55", "e4", "e5"], "memories": {"flash": 0, "sram1": 32 * 1024, "sram2": 0}}, + ], + }, + "u0": { + "start": { + "flash": 0x08000000, + "sram2": 0x10000000, + "sram1": 0x20000000, + }, + "model": [ + {"name": ["31", "73", "83"], "memories": {"flash": 0, "sram1": 2 * 1024, "sram2": 1 * 1024}}, + ], + }, + "u3": { + "start": { + "flash": 0x08000000, + "sram": 0x20000000, + }, + "model": [ + {"name": ["75", "85"], "memories": {"flash": 0, "sram1": 0, "sram2": 64 * 1024}}, + ], }, "u5": { "start": { "flash": 0x08000000, "sram1": 0x20000000, - "sram2": 0x20030000, - "sram3": 0x20040000, + "sram2": 0x200C0000, + "sram3": 0x200D0000, + "sram5": 0x201A0000, + "sram6": 0x20270000, "sram4": 0x28000000, "bkpsram": 0x40036400, }, "model": [ + { + "name": ["35", "45"], + "memories": { + "flash": 0, + "sram1": 192 * 1024, + "sram2": 64 * 1024, + "sram4": 16 * 1024, + "bkpsram": 2 * 1024, + }, + "start": { # overwrite due to smaller sram1/3 sizes + "sram2": 0x20030000, + "sram3": 0x20040000, + }, + }, { "name": ["75", "85"], "memories": { @@ -682,11 +858,36 @@ def getDmaRemap(did, dma, channel, driver, inst, signal): "sram4": 16 * 1024, "bkpsram": 2 * 1024, }, - } - # ,{ - # 'name': ['95', '99', 'a5', 'a9'], # This devices are not published yet... - # 'memories': # TODO... - # } + "start": { # overwrite due to smaller sram1/3 sizes + "sram2": 0x20030000, + "sram3": 0x20040000, + }, + }, + { + "name": ["95", "99", "a5", "a9"], + "memories": { + "flash": 0, + "sram1": 768 * 1024, + "sram2": 64 * 1024, + "sram3": 832 * 1024, + "sram4": 16 * 1024, + "sram5": 832 * 1024, + "bkpsram": 2 * 1024, + }, + }, + { + "name": ["f5", "g5", "f7", "g7", "f9", "g9"], + "memories": { + "flash": 0, + "sram1": 768 * 1024, + "sram2": 64 * 1024, + "sram3": 832 * 1024, + "sram4": 16 * 1024, + "sram5": 832 * 1024, + "sram6": 512 * 1024, + "bkpsram": 2 * 1024, + }, + }, ], }, } @@ -731,6 +932,8 @@ def getMemoryForDevice(device_id, total_flash, total_ram): # Assemble flattened memories memories = [] for name, size in mem_model.items(): + if size <= 0: + continue sram_name = next(ram for ram in mem_start.keys() if name.startswith(ram)) index = int(name.split("sram")[-1]) if name[-1].isdigit() else 0 start = mem_start[sram_name] diff --git a/src/modm_data/header2svd/stmicro/header.py b/src/modm_data/header2svd/stmicro/header.py index bd16241..ff7cd48 100644 --- a/src/modm_data/header2svd/stmicro/header.py +++ b/src/modm_data/header2svd/stmicro/header.py @@ -34,8 +34,11 @@ def getDefineForDevice(device_id, familyDefines): + if len(familyDefines) == 1: + return familyDefines[0] + # get all defines for this device name - devName = f"STM32{device_id.family.upper()}{device_id.name.upper()}" + devName = "STM32{}{}".format(device_id.family.upper(), device_id.name.upper()) # Map STM32F7x8 -> STM32F7x7 if device_id.family == "f7" and devName[8] == "8": @@ -46,30 +49,14 @@ def getDefineForDevice(device_id, familyDefines): if len(deviceDefines) == 1: return deviceDefines[0] - # sort with respecting variants - minlen = min(len(d) for d in deviceDefines) - deviceDefines.sort(key=lambda d: (d[:minlen], d[minlen:])) - - # now we match for the size-id (and variant-id if applicable). - if device_id.family == "h7": - devNameMatch = devName + "xx" - else: - devNameMatch = devName + f"x{device_id.size.upper()}" - if device_id.family == "l1": - # Map STM32L1xxQC and STM32L1xxZC -> STM32L162QCxA variants - if device_id.pin in ["q", "z"] and device_id.size == "c": - devNameMatch += "A" - else: - devNameMatch += device_id.variant.upper() - elif device_id.family == "h7": - if device_id.variant: - devNameMatch += device_id.variant.upper() + # now we match for the size-id. + devNameMatch = devName + "x{}".format(device_id.size.upper()) for define in deviceDefines: if devNameMatch <= define: return define # now we match for the pin-id. - devNameMatch = devName + f"{device_id.pin.upper()}x" + devNameMatch = devName + "{}x".format(device_id.pin.upper()) for define in deviceDefines: if devNameMatch <= define: return define @@ -92,9 +79,21 @@ class Header(CmsisHeader): def __init__(self, did): self.did = did - self.family_folder = f"stm32{self.did.family}xx" + self.family_folder = "stm32{}xx".format(self.did.family) + if self.did.string[5:8] in ["h7r", "h7s"]: + self.family_folder = "stm32h7rsxx" + elif self.did.string[5:8] == "wb0": + self.family_folder = "stm32wb0xx" + elif self.did.string[5:8] == "wba": + self.family_folder = "stm32wbaxx" + elif self.did.string[5:8] == "wl3": + self.family_folder = "stm32wl3xx" self.cmsis_folder = Header._HEADER_PATH / self.family_folder / "Include" self.family_header_file = f"{self.family_folder}.h" + if self.did.string[5:8] == "wb0": + self.family_header_file = "stm32wb0x.h" + elif self.did.string[5:8] == "wl3": + self.family_header_file = "stm32wl3x.h" self.family_defines = self._get_family_defines() self.define = getDefineForDevice(self.did, self.family_defines) diff --git a/src/modm_data/html/stmicro/datasheet_stm32.py b/src/modm_data/html/stmicro/datasheet_stm32.py index 52967ad..b3646bb 100644 --- a/src/modm_data/html/stmicro/datasheet_stm32.py +++ b/src/modm_data/html/stmicro/datasheet_stm32.py @@ -7,8 +7,8 @@ from .helper import split_device_filter, split_package from ...html.text import ReDict, listify as html_listify, replace as html_replace -from ...owl import DeviceIdentifier -from ...owl.stmicro import did_from_string +from ...kg import DeviceIdentifier +from ...kg.stmicro import did_from_string from ..document import Document diff --git a/src/modm_data/html/stmicro/document.py b/src/modm_data/html/stmicro/document.py index 9f29b3d..b29dc0e 100644 --- a/src/modm_data/html/stmicro/document.py +++ b/src/modm_data/html/stmicro/document.py @@ -8,8 +8,8 @@ from .datasheet_stm32 import DatasheetStm32 from .datasheet_sensor import DatasheetSensor from .reference import ReferenceManual -from ...owl import DeviceIdentifier -from ...owl.stmicro import did_from_string +from ...kg import DeviceIdentifier +from ...kg.stmicro import did_from_string MAP_DEVICE_DOC_FILE = cache_path("stmicro-did-doc.json") diff --git a/src/modm_data/owl/__init__.py b/src/modm_data/kg/__init__.py similarity index 100% rename from src/modm_data/owl/__init__.py rename to src/modm_data/kg/__init__.py diff --git a/src/modm_data/owl/identifier.py b/src/modm_data/kg/identifier.py similarity index 100% rename from src/modm_data/owl/identifier.py rename to src/modm_data/kg/identifier.py diff --git a/src/modm_data/kg/stmicro/__init__.py b/src/modm_data/kg/stmicro/__init__.py new file mode 100644 index 0000000..5013524 --- /dev/null +++ b/src/modm_data/kg/stmicro/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2022, Niklas Hauser +# SPDX-License-Identifier: MPL-2.0 + +from .identifier import did_from_string +from .schema import kg_create +from .model import ( + kg_from_cubemx, + kg_from_header, +) + +__all__ = [ + "did_from_string", + "kg_create", + "kg_from_cubemx", + "kg_from_header", +] diff --git a/src/modm_data/owl/stmicro/identifier.py b/src/modm_data/kg/stmicro/identifier.py similarity index 100% rename from src/modm_data/owl/stmicro/identifier.py rename to src/modm_data/kg/stmicro/identifier.py diff --git a/src/modm_data/kg/stmicro/model.py b/src/modm_data/kg/stmicro/model.py new file mode 100644 index 0000000..020a642 --- /dev/null +++ b/src/modm_data/kg/stmicro/model.py @@ -0,0 +1,139 @@ +# Copyright 2022, Niklas Hauser +# SPDX-License-Identifier: MPL-2.0 + +import re + + +def kg_from_cubemx(db, data): + did = data["id"] + print(did.string) + # core = f", core: '{did.core}'" if did.get("core", False) else "" + variant = f", variant: '{did.variant}'" if did.get("variant", False) else "" + db.execute(f"CREATE (:DeviceIdentifier {{full: '{did.string.lower()}'}});") + # db.execute(f""" + # CREATE (:DeviceIdentifier {{ + # full: '{did.string.lower()}', + # schema: '{did.naming_schema}', + # platform: '{did.platform}', + # family: '{did.family}', + # name: '{did.name}', + # pin: '{did.pin}', + # size: '{did.size}', + # package: '{did.package}', + # temperature: '{did.temperature}' + # {variant} + # {core} + # }}); + # """) + + # Add internal memories + for memory in sorted(data["memories"], key=lambda m: m["start"]): + db.execute(f""" + CREATE (:Memory {{ + name: '{memory["name"].upper()}', + address: {memory["start"]}, + size: {memory["size"]}, + access: '{memory["access"]}' + }}); + """) + + # Add the peripherals and their type + for pbase, name, version, ptype, features, stype in sorted(data["modules"]): + instance = "NULL" + if pbase != name: + try: + instance = int(name.replace(pbase, "")) + except ValueError: + pass + db.execute(f""" + CREATE (:Peripheral {{ + name: '{name.upper()}', + type: '{pbase.upper()}', + instance: {instance}, + version: '{ptype}' + }}); + """) + + # Add package + db.execute(f""" + CREATE (:Package {{ + name: '{data["package"]}', + pins: {int(data["pin-count"])} + }}); + """) + + # Add pinout for package + for pin in sorted(data["pinout"], key=lambda p: p["name"]): + variant, port, number = "", "", "" + if pin["type"] == "I/O" and (pnumber := re.search(r"P\w(\d+)", pin["name"])): + port = f", port: '{pin['name'][1]}'" + number = f", pin: {int(pnumber.group(1))}" + if var := pin.get("variant"): + variant = f", variant: '{var}'" + db.execute(f""" + MERGE (:Pin {{ + name: '{pin["name"]}', + type: '{pin["type"].lower()}' + {variant} + {port} + {number} + }}); + MATCH (pa:Package), (pi:Pin {{name: '{pin["name"]}'}}) + CREATE (pa)-[:hasPin {{location: '{pin["position"]}'}}]->(pi); + """) + + # Add signals + for signal in sorted(data["signals"]): + db.execute(f"CREATE (:Signal {{name: '{signal.upper()}'}});") + + # Add alternate and additional functions to pins + for port, number, signals in sorted(data["gpios"]): + for signal in sorted(signals, key=lambda s: s["name"]): + peripheral = (signal["driver"] or "").upper() + (signal["instance"] or "") + name = signal["name"].upper() + db.execute(f""" + MATCH (p:Peripheral {{name: '{peripheral}'}}), (s:Signal {{name: '{name}'}}) + MERGE (p)-[:hasSignal]->(s); + """) + index = "" + if af := signal["af"]: + index = f", index: {af}" + db.execute(f""" + MATCH (p:Pin {{port: '{port.upper()}', pin: {number}}}), (s:Signal {{name: '{name}'}}) + MERGE (s)-[:hasSignalPin {{peripheral: '{peripheral}'{index}}}]->(p); + """) + + for peripheral, remap in data["remaps"].items(): + for index, groups in remap["groups"].items(): + for signal in groups: + sname = signal["name"].upper() + pname = peripheral.upper() + db.execute(f""" + MATCH (p:Peripheral {{name: '{pname}'}}), (s:Signal {{name: '{sname}'}}) + MERGE (p)-[:hasSignal {{shift: {remap["position"]}, mask: {remap["mask"]}}}]->(s); + + MATCH (p:Pin {{port: '{signal["port"].upper()}', pin: {signal["pin"]}}}), (s:Signal {{name: '{sname}'}}) + MERGE (s)-[:hasSignalPin {{peripheral: '{pname}', index: {index}}}]->(p); + """) + + for mV, freqs in data["flash_latency"].items(): + db.execute(f""" + CREATE (:FlashWaitState {{ + minVoltage: {mV}, + maxFrequency: [{", ".join(map(str, freqs))}] + }}); + """) + + +def kg_from_header(db, header): + # Add interrupt vector table + for position, name in sorted(header["irqs"]): + db.execute(f""" + CREATE (:InterruptVector {{ + name: '{name}', + position: {position} + }}); + MATCH (p:Peripheral), (i:InterruptVector {{name: '{name}'}}) + WHERE '{name}' CONTAINS p.name + CREATE (p)-[:hasInterruptVector]->(i); + """) diff --git a/src/modm_data/kg/stmicro/schema.py b/src/modm_data/kg/stmicro/schema.py new file mode 100644 index 0000000..eecb47f --- /dev/null +++ b/src/modm_data/kg/stmicro/schema.py @@ -0,0 +1,47 @@ +# Copyright 2022, Niklas Hauser +# SPDX-License-Identifier: MPL-2.0 + +import re +from .. import Store + + +def kg_create(did): + db = Store("stmicro", did) + schema = "" + + # --------------------------- DEVICE IDENTIFIER --------------------------- + schema += "CREATE NODE TABLE DeviceIdentifier(full STRING PRIMARY KEY);\n" + + # ------------------------------ PERIPHERALS ------------------------------ + schema += "CREATE NODE TABLE Peripheral(name STRING PRIMARY KEY, type STRING, instance UINT8, version STRING);\n" + + # --------------------------------- PINS ---------------------------------- + schema += "CREATE NODE TABLE Package(name STRING PRIMARY KEY, pins UINT16);\n" + schema += "CREATE NODE TABLE Pin(name STRING PRIMARY KEY, type STRING, variant STRING, port STRING, pin UINT8);\n" + schema += "CREATE REL TABLE hasPin(FROM Package TO Pin, location STRING);\n" + + # -------------------------------- SIGNALS -------------------------------- + schema += "CREATE NODE TABLE Signal(name STRING PRIMARY KEY);\n" + schema += "CREATE REL TABLE hasSignal(FROM Peripheral TO Signal, shift UINT8, mask UINT8);\n" + schema += "CREATE REL TABLE hasSignalPin(FROM Signal TO Pin, peripheral STRING, index UINT8);\n" + + # ------------------------------- MEMORIES -------------------------------- + schema += "CREATE NODE TABLE Memory(name STRING PRIMARY KEY, address UINT32, size UINT32, access STRING);\n" + + # ------------------------------ INTERRUPTS ------------------------------- + schema += "CREATE NODE TABLE InterruptVector(name STRING PRIMARY KEY, position INT16);\n" + schema += "CREATE REL TABLE hasInterruptVector(FROM Peripheral TO InterruptVector);\n" + + # ----------------------------- FLASH LATENCY ----------------------------- + schema += "CREATE NODE TABLE FlashWaitState(minVoltage UINT16 PRIMARY KEY, maxFrequency UINT32[]);\n" + + # Enforce unique name+type combinations due to Kuzu limitations + members = {} + for name, typename in re.findall(r"[\(,] ?([a-z]\w+) ([A-Z0-9\[\]]+)", schema): + assert members.get(name, typename) == typename, ( + f"Member '{name} {typename}' has different type than '{name} {members[name]}'!" + ) + members[name] = typename + + db.execute(schema) + return db diff --git a/src/modm_data/kg/store.py b/src/modm_data/kg/store.py new file mode 100644 index 0000000..d636c2a --- /dev/null +++ b/src/modm_data/kg/store.py @@ -0,0 +1,34 @@ +# Copyright 2025, Niklas Hauser +# SPDX-License-Identifier: MPL-2.0 + +from ..utils import ext_path +import kuzu +import shutil + + +class Store: + def __init__(self, vendor, device): + self.vendor = vendor + self.device = device + self._path = ext_path(f"{vendor}/kg-archive/{device.string}") + self.db = kuzu.Database(":memory:") + self.conn = kuzu.Connection(self.db) + + def execute(self, command): + # from textwrap import dedent + # print(dedent(command)) + self.conn.execute(command) + + def load(self): + self.conn.execute(f"IMPORT DATABASE '{self._path}';") + + def save(self, overwrite=True) -> bool: + if self._path.exists(): + if not overwrite: + return False + shutil.rmtree(self._path) + self.conn.execute(f"EXPORT DATABASE '{self._path}' (format='csv');") + return True + + def __repr__(self) -> str: + return f"Store({self.vendor}/{self.device})" diff --git a/src/modm_data/owl/stmicro/__init__.py b/src/modm_data/owl/stmicro/__init__.py deleted file mode 100644 index 8ee4f5c..0000000 --- a/src/modm_data/owl/stmicro/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022, Niklas Hauser -# SPDX-License-Identifier: MPL-2.0 - -from .device import owls, owl_devices, owl_device, load_owl_device -from .identifier import did_from_string -from .ontology import create_ontology -from .model import ( - owl_from_datasheet, - owl_from_reference_manual, - owl_from_doc, - owl_from_did, - owl_from_cubemx, - owl_from_header, -) - -__all__ = [ - "owls", - "owl_devices", - "owl_device", - "load_owl_device", - "did_from_string", - "create_ontology", - "owl_from_datasheet", - "owl_from_reference_manual", - "owl_from_doc", - "owl_from_did", - "owl_from_cubemx", - "owl_from_header", -] diff --git a/src/modm_data/owl/stmicro/device.py b/src/modm_data/owl/stmicro/device.py deleted file mode 100644 index 6c8f715..0000000 --- a/src/modm_data/owl/stmicro/device.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2022, Niklas Hauser -# SPDX-License-Identifier: MPL-2.0 - -from ...utils import cache_path -import modm_data.owl.stmicro as owl -import json - - -_OWL_FILES = None -_OWL_MAPPING = None -_OWL_MAPPING_FILE = cache_path("stmicro-owl.json") - - -def owls(): - global _OWL_FILES - if _OWL_FILES is None: - _OWL_FILES = cache_path("stmicro-owl/").glob("*.owl") - _OWL_FILES = [ds.stem for ds in _OWL_FILES] - return _OWL_FILES - - -def owl_devices(): - global _OWL_MAPPING, _OWL_MAPPING_FILE - if _OWL_MAPPING is None: - if not _OWL_MAPPING_FILE.exists(): - _OWL_MAPPING = {} - for ds in owls(): - if ds.startswith("DS"): - owl.store.load(ds) - for name in set(i.name for i in owl.Device.instances()): - _OWL_MAPPING[name] = ds - - _OWL_MAPPING_FILE.parent.mkdir(parents=True, exist_ok=True) - with _OWL_MAPPING_FILE.open("w", encoding="utf-8") as fh: - json.dump(_OWL_MAPPING, fh, indent=4) - else: - with _OWL_MAPPING_FILE.open("r", encoding="utf-8") as fh: - _OWL_MAPPING = json.load(fh) - return _OWL_MAPPING - - -def owl_device(device): - return owl_devices().get(device.string) - - -def load_owl_device(device) -> bool: - if (dev := owl_devices().get(device.string)) is not None: - owl.store.load(dev) - return True - return False diff --git a/src/modm_data/owl/stmicro/model.py b/src/modm_data/owl/stmicro/model.py deleted file mode 100644 index 7c54c7b..0000000 --- a/src/modm_data/owl/stmicro/model.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright 2022, Niklas Hauser -# SPDX-License-Identifier: MPL-2.0 - -import re -from ...html.stmicro import find_device_filter - - -def owl_from_datasheet(onto, ds): - pass - - -def owl_from_reference_manual(onto, rm): - odid = onto.DeviceIdentifier(onto.did.string.lower()) - - dfilter = find_device_filter(onto.did, rm.flash_latencies) - table_data = rm.flash_latencies[dfilter] - print(dfilter, table_data) - for min_voltage, frequencies in table_data.items(): - # print(min_voltage, frequencies) - for wait_state, max_frequency in enumerate(frequencies): - ofreq = onto.FlashWaitState(f"WaitState_{wait_state}_{min_voltage}mV") - ofreq.hasWaitState = wait_state - ofreq.hasMaxFrequency = int(max_frequency * 1e6) - ofreq.hasMinOperatingVoltage = min_voltage / 1000.0 - odid.hasFlashWaitState.append(ofreq) - - -def owl_from_doc(onto, doc): - if doc.name.startswith("DS"): - owl_from_datasheet(onto, doc) - elif doc.name.startswith("RM"): - owl_from_reference_manual(onto, doc) - - -def owl_from_did(onto): - # Add device identifiers - odid = onto.DeviceIdentifier(onto.did.string.lower()) - odid.hasDeviceSchema = onto.did.naming_schema - odid.hasDevicePlatform = onto.did.platform - odid.hasDeviceFamily = onto.did.family - odid.hasDeviceName = onto.did.name - odid.hasDevicePin = onto.did.pin - odid.hasDeviceSize = onto.did.size - odid.hasDevicePackage = onto.did.package - odid.hasDeviceTemperature = onto.did.temperature - odid.hasDeviceVariant = onto.did.variant - if onto.did.get("core", False): - odid.hasDeviceCore = onto.did.core - - -def owl_from_cubemx(onto, data): - odid = onto.DeviceIdentifier(onto.did.string.lower()) - - # Add internal memories - for memory in data["memories"]: - omem = onto.Memory("Memory_" + memory["name"].upper()) - odid.hasMemory.append(omem) - omem.hasName = memory["name"].upper() - omem.hasMemoryStartAddress = int(memory["start"], 16) - omem.hasMemorySize = int(memory["size"]) - omem.hasMemoryAccess = memory["access"] - - # Add the peripherals and their type - for pbase, name, version, ptype, features, stype in data["modules"]: - oper = onto.Peripheral("Peripheral_" + name.upper()) - odid.hasPeripheral.append(oper) - oper.hasName = name.upper() - if pbase != name: - oper.hasPeripheralInstance = int(name.replace(pbase, "")) - oper.hasPeripheralType = pbase + ptype.replace("stm32", "") - - # Add package - opack = onto.Package("Package_" + data["package"]) - opack.hasName = str(data["package"]) - odid.hasPackage.append(opack) - opack.hasPackagePinCount = int(data["pin-count"]) - - # Add pinout for package - io_pins = {} - for pin in data["pinout"]: - opin = onto.Pin("Pin_" + pin["name"]) - opin.hasName = pin["name"] - opin.hasPinType = pin["type"] - if pin["type"] == "I/O" and (number := re.search(r"P\w(\d+)", pin["name"])): - opin.hasPort = pin["name"][1] - opin.hasPinNumber = int(number.group(1)) - io_pins[(opin.hasPort.lower(), opin.hasPinNumber)] = opin - opack.hasPin.append(opin) - onto.pinPosition[opack, onto.hasPin, opin].append(pin["position"]) - - # Add alternate and additional functions to pins - for port, number, signals in data["gpios"]: - opin = io_pins[(port, int(number))] - for signal in signals: - peripheral = (signal["driver"] or "").upper() + (signal["instance"] or "") - name = signal["name"].upper() - af = signal["af"] - signame = "Signal_" + peripheral + "_" + name - osig = onto.AlternateFunction(signame) if af else onto.AdditionalFunction(signame) - osig.hasPeripheral.append(onto.Peripheral("Peripheral_" + peripheral)) - osig.hasName = name - opin.hasSignal.append(osig) - if af: - onto.alternateFunction[opin, onto.hasSignal, osig].append(int(af)) - - -def owl_from_header(onto, header): - odid = onto.DeviceIdentifier(onto.did.string.lower()) - - # Add interrupt vector table - for interrupt in header.interrupt_table: - if interrupt["position"] >= 0: - oint = onto.InterruptVector("InterruptVector_" + interrupt["name"]) - oint.hasInterruptVectorPosition = interrupt["position"] - odid.hasInterruptVector.append(oint) diff --git a/src/modm_data/owl/stmicro/ontology.py b/src/modm_data/owl/stmicro/ontology.py deleted file mode 100644 index b6c97a3..0000000 --- a/src/modm_data/owl/stmicro/ontology.py +++ /dev/null @@ -1,232 +0,0 @@ -# Copyright 2022, Niklas Hauser -# SPDX-License-Identifier: MPL-2.0 - -import owlready2 as owl -from .. import Store - - -class KeyDict: - def __init__(self, values): - self.values = values - - def __getattr__(self, attr): - val = self.values.get(attr, None) - if val is None: - raise AttributeError(f"'{self!r}' has no property '{attr}'") - return val - - -def create_ontology(did): - store = Store("stmicro", did.string) - ontology = store.ontology - - # --------------------------- DEVICE IDENTIFIER --------------------------- - class DeviceIdentifier(owl.Thing): - namespace = ontology - comment = "The unique identifier (part number) of the device." - - class hasDeviceSchema(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - comment = "How to format the device identifier." - domain = [DeviceIdentifier] - range = [str] - - class hasDevicePlatform(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceFamily(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceName(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDevicePin(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceSize(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDevicePackage(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceTemperature(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceVariant(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - class hasDeviceCore(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [str] - - # ------------------------------- MEMORIES -------------------------------- - class Memory(owl.Thing): - namespace = ontology - comment = "Internal memory." - - class hasMemoryStartAddress(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Memory] - range = [int] - - class hasMemorySize(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Memory] - range = [int] - - class hasMemoryAccess(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Memory] - range = [str] - - class hasMemory(DeviceIdentifier >> Memory): - namespace = ontology - - # ------------------------------ INTERRUPTS ------------------------------- - class InterruptVector(owl.Thing): - namespace = ontology - comment = "Interrupt vector in the table." - - class hasInterruptVectorPosition(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [InterruptVector] - range = [int] - - class hasInterruptVector(owl.ObjectProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [InterruptVector] - - # --------------------------------- PINS ---------------------------------- - class Package(owl.Thing): - namespace = ontology - comment = "A device package identifier" - domain = [DeviceIdentifier] - - class hasPackagePinCount(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Package] - range = [int] - - class hasPackage(DeviceIdentifier >> Package): - namespace = ontology - - class Pin(owl.Thing): - namespace = ontology - comment = "A pin on a package." - - class hasPinType(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Pin] - range = [str] - - class hasPinNumber(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Pin] - range = [int] - - class hasPort(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Pin] - range = [str] - - class hasPin(owl.ObjectProperty): - namespace = ontology - domain = [Package] - range = [Pin] - - class pinPosition(owl.AnnotationProperty): - namespace = ontology - comment = "The pin position attached to the [Package, hasPin, Pin] relation." - - # -------------------------------- SIGNALS -------------------------------- - class Signal(owl.Thing): - namespace = ontology - comment = "Connects a pin with a peripheral function." - - class AlternateFunction(Signal): - namespace = ontology - comment = "Connects to a digital peripheral function via multiplexer." - - class AdditionalFunction(Signal): - namespace = ontology - comment = "Connects to an analog/special peripheral function." - - class hasSignal(owl.ObjectProperty): - namespace = ontology - domain = [Pin] - range = [Signal] - - class alternateFunction(owl.AnnotationProperty): - namespace = ontology - comment = "The AF number attached to the [Pin, hasSignal, AlternateFunction] relation." - - # ------------------------------ PERIPHERALS ------------------------------ - class Peripheral(owl.Thing): - namespace = ontology - comment = "Internal peripheral." - - class hasPeripheralInstance(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Peripheral] - range = [int] - - class hasPeripheralType(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Peripheral] - range = [str] - - class hasPeripheral(owl.ObjectProperty): - namespace = ontology - domain = [DeviceIdentifier, Signal] - range = [Peripheral] - - # ----------------------------- FLASH LATENCY ----------------------------- - class FlashWaitState(owl.Thing): - namespace = ontology - comment = "Flash Latency for minimum frequency." - - class hasWaitState(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [FlashWaitState] - range = [int] - - class hasMaxFrequency(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [FlashWaitState] - range = [int] - - class hasMinOperatingVoltage(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [FlashWaitState] - range = [float] - - class hasFlashWaitState(owl.ObjectProperty): - namespace = ontology - domain = [DeviceIdentifier] - range = [FlashWaitState] - - # -------------------------------- GENERAL -------------------------------- - class hasName(owl.DataProperty, owl.FunctionalProperty): - namespace = ontology - domain = [Memory, Signal, Peripheral, Package, Pin] - range = [str] - - return KeyDict(locals()) diff --git a/src/modm_data/owl/store.py b/src/modm_data/owl/store.py deleted file mode 100644 index a1990e8..0000000 --- a/src/modm_data/owl/store.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2022, Niklas Hauser -# SPDX-License-Identifier: MPL-2.0 - -from ..utils import ext_path -import owlready2 as owl - - -XSLT_SORT = r""" - - - - - - - - - - - - - - - - - - - - - - -""" - - -class Store: - def __init__(self, vendor, device): - self.base_url = "https://data.modm.io/kg" - self.vendor = vendor - self.device = device - self._path = ext_path(f"{vendor}/owl") - # Add the directory to the search path - owl.onto_path.append(self._path) - self.ontology = owl.get_ontology(f"{self.base_url}/{vendor}/{device}") - - # def set_device_iri(self, device): - # self.ontology.set_base_iri(f"{self.base_url}/{self.vendor}/{device}", rename_entities=False) - - def namespace(self, name): - return self.ontology.get_namespace(f"{self.vendor}/{name}") - - def load(self, name=None): - if name is None: - name = "ontology" - fileobj = open(self._path / f"{name}.owl", "rb") - self.ontology.load(only_local=True, fileobj=fileobj, reload=True) - - def save(self, name=None): - self._path.mkdir(exist_ok=True, parents=True) - if name is None: - name = "ontology" - file = str(self._path / f"{name}.owl") - self.ontology.save(file=file) - - # dom = ET.parse(file) - # xslt = ET.XML(XSLT_SORT) - # transform = ET.XSLT(xslt) - # newdom = transform(dom) - # Path(file).write_bytes(ET.tostring(newdom, pretty_print=True)) - - # owl.default_world.set_backend(filename=str(self._path / f"{name}.sqlite3")) - # owl.default_world.save() - - def clear(self): - # self.ontology._destroy_cached_entities() - self.ontology.world._destroy_cached_entities() - # self.ontology.destroy() - # self.ontology = owl.get_ontology(f"{self.base_url}/{self.vendor}") - - def __repr__(self) -> str: - return f"Store({self.vendor}/{self.device})" diff --git a/src/modm_data/utils/path.py b/src/modm_data/utils/path.py index 36378fc..64a7249 100644 --- a/src/modm_data/utils/path.py +++ b/src/modm_data/utils/path.py @@ -5,7 +5,7 @@ def root_path(path) -> Path: - return Path(__file__).parents[2] / path + return Path(__file__).parents[3] / path def ext_path(path) -> Path: diff --git a/tools/make/arm.mk b/tools/make/arm.mk index f8afce9..c594755 100644 --- a/tools/make/arm.mk +++ b/tools/make/arm.mk @@ -5,7 +5,7 @@ # =============================== Input Sources =============================== ext/arm/cmsis/: - @git clone --depth=1 git@github.com:modm-ext/cmsis-5-partial.git $@ + @git clone --depth=1 https://github.com/modm-ext/cmsis-core-partial.git $@ .PHONY: clone-sources-arm ## Clone all ARM related repositories into /ext/arm. diff --git a/tools/make/common.mk b/tools/make/common.mk index 7417445..7281f9d 100644 --- a/tools/make/common.mk +++ b/tools/make/common.mk @@ -66,7 +66,7 @@ serve-api-docs: ### @Tests Testing \1009 # ================================== Testing ================================== ext/test/regression/: - @git clone --depth=1 git@github.com:modm-ext/modm-data-test-docs.git $@ + @git clone --depth=1 https://github.com/modm-ext/modm-data-test-docs.git $@ .PHONY: run-regression-tests ## @Tests Convert some PDF pages and check against their known HTML. diff --git a/tools/make/stmicro.mk b/tools/make/stmicro.mk index f96e2fa..36ae68c 100644 --- a/tools/make/stmicro.mk +++ b/tools/make/stmicro.mk @@ -5,19 +5,19 @@ # =============================== Input Sources =============================== ext/stmicro/cubehal/: - @git clone --depth=1 git@github.com:modm-ext/stm32-cube-hal-drivers.git $@ + @git clone --depth=1 https://github.com/modm-ext/stm32-cube-hal-drivers.git $@ ext/stmicro/header/: - @git clone --depth=1 git@github.com:modm-io/cmsis-header-stm32.git $@ + @git clone --depth=1 https://github.com/modm-io/cmsis-header-stm32.git $@ ext/stmicro/svd/: - @git clone --depth=1 git@github.com:modm-io/cmsis-svd-stm32.git $@ + @git clone --depth=1 https://github.com/modm-io/cmsis-svd-stm32.git $@ ext/stmicro/owl-archive/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-owl.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-owl.git $@ ext/stmicro/svd-archive/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-svd.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-svd.git $@ .PHONY: clone-sources-stmicro ## Clone all STMicro related repositories into /ext/stmicro. @@ -58,22 +58,22 @@ download-stmicro-cubeprog: # Note: The STMicro CubeMX database archive repo is private and used for our CI. # Please download the database via 'make download-stmicro-cubemx'! ext/stmicro/cubemx/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-cubemx.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-cubemx.git $@ # Note: The STMicro CubeProg database archive repo is private and used for our CI. # Please download the database via 'make download-stmicro-cubeprog'! ext/stmicro/cubeprog/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-cubeprog.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-cubeprog.git $@ # Note: The STMicro HTML archive repo is private and used for our CI. # Please convert the HTMLs via 'make convert-stmicro-html'. ext/stmicro/html-archive/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-html.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-html.git $@ # Note: The STMicro PDF archive repo is private and used for our CI. # Please download the PDFs via 'make download-stmicro-pdfs'. ext/stmicro/pdf/: - @git clone --depth=1 git@github.com:modm-ext/archive-stmicro-pdf.git $@ + @git clone --depth=1 https://github.com/modm-ext/archive-stmicro-pdf.git $@ .PHONY: clone-sources-stmicro-private clone-sources-stmicro-private: clone-sources-stmicro ext/stmicro/cubemx/ ext/stmicro/cubeprog/ \ diff --git a/tools/requirements.txt b/tools/requirements.txt index a67a1b8..4616ce1 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,37 +1,37 @@ anytree==2.12.1 -babel==2.16.0 -certifi==2024.12.14 -charset-normalizer==3.4.1 -click==8.1.8 +babel==2.17.0 +backrefs==5.9 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.3.0 colorama==0.4.6 CppHeaderParser==2.7.4 ghp-import==2.1.0 -idna==3.10 -Jinja2==3.1.5 -lxml==5.3.0 -Markdown==3.7 -MarkupSafe==3.0.2 +idna==3.11 +Jinja2==3.1.6 +kuzu==0.11.3 +lxml==5.4.0 +Markdown==3.9 +MarkupSafe==3.0.3 mergedeep==1.3.4 mkdocs==1.6.1 mkdocs-get-deps==0.2.0 -mkdocs-material==9.5.49 +mkdocs-material==9.6.22 mkdocs-material-extensions==1.3.1 -owlready2==0.45 -packaging==24.2 +packaging==25.0 paginate==0.5.7 pathspec==0.12.1 pdoc==14.7.0 -platformdirs==4.3.6 +platformdirs==4.5.0 ply==3.11 -Pygments==2.18.0 -pymdown-extensions==10.13 +Pygments==2.19.2 +pymdown-extensions==10.16.1 pypdfium2==4.30.0 python-dateutil==2.9.0.post0 -PyYAML==6.0.2 -pyyaml_env_tag==0.1 -regex==2024.11.6 -requests==2.32.3 +PyYAML==6.0.3 +pyyaml_env_tag==1.1 +requests==2.32.5 six==1.17.0 tqdm==4.67.1 -urllib3==2.3.0 +urllib3==2.5.0 watchdog==6.0.0