From 6313c2aa5d2694c08431c26a42e9be9a78c09489 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Tue, 3 Oct 2023 12:41:03 -0500 Subject: [PATCH 01/10] Initial output generation from SVD --- .github/workflows/python-package.yml | 2 +- README.md | 4 ++-- config | 2 +- ifgen/__init__.py | 4 ++-- ifgen/commands/svd.py | 14 ++++++++++---- ifgen/svd/model/__init__.py | 14 +++++++++++++- ifgen/svd/task.py | 11 +++++++++++ local/variables/package.yaml | 2 +- pyproject.toml | 2 +- tests/commands/test_svd.py | 22 ++++++++++++++++------ 10 files changed, 58 insertions(+), 19 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d3c99d3..7742f5c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -76,7 +76,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=ifgen version=2.4.0 + repo=ifgen version=2.4.1 if: | matrix.python-version == '3.11' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index 0aa36ec..b9ce12c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.3 - hash=6f2705d0caa7d536cae7a9eb581f7d7d + hash=fd06f3c891302b90329a9edaf282bd31 ===================================== --> -# ifgen ([2.4.0](https://pypi.org/project/ifgen/)) +# ifgen ([2.4.1](https://pypi.org/project/ifgen/)) [![python](https://img.shields.io/pypi/pyversions/ifgen.svg)](https://pypi.org/project/ifgen/) ![Build Status](https://github.com/vkottler/ifgen/workflows/Python%20Package/badge.svg) diff --git a/config b/config index 06e1770..f8271b5 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 06e17704470a8049187e480ce313a853516449ee +Subproject commit f8271b54f578880060f0136fbc9ed9b7c2649902 diff --git a/ifgen/__init__.py b/ifgen/__init__.py index 2c958f3..6add4ec 100644 --- a/ifgen/__init__.py +++ b/ifgen/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.3 -# hash=50538057667f048e4e1c2e1232e77e1b +# hash=f049d3e8785fc691ce45835642aa24c8 # ===================================== """ @@ -10,4 +10,4 @@ DESCRIPTION = "An interface generator for distributed computing." PKG_NAME = "ifgen" -VERSION = "2.4.0" +VERSION = "2.4.1" diff --git a/ifgen/commands/svd.py b/ifgen/commands/svd.py index c884c4c..8b2868c 100644 --- a/ifgen/commands/svd.py +++ b/ifgen/commands/svd.py @@ -6,6 +6,7 @@ from argparse import ArgumentParser as _ArgumentParser from argparse import Namespace as _Namespace from logging import getLogger +from pathlib import Path # third-party from vcorelib.args import CommandFunction as _CommandFunction @@ -30,10 +31,7 @@ def svd_cmd(args: _Namespace) -> int: ) assert path is not None, args.svd_file - task = SvdProcessingTask.svd(path) - - # generate output files etc. ? - assert task + SvdProcessingTask.svd(path).generate_configs(args.output) return 0 @@ -41,6 +39,14 @@ def svd_cmd(args: _Namespace) -> int: def add_svd_cmd(parser: _ArgumentParser) -> _CommandFunction: """Add svd-command arguments to its parser.""" + parser.add_argument( + "-o", + "--output", + type=Path, + default=f"{PKG_NAME}-out", + help="output directory for configuration files", + ) + parser.add_argument( "svd_file", type=str, help="path/uri to a CMSIS-SVD file" ) diff --git a/ifgen/svd/model/__init__.py b/ifgen/svd/model/__init__.py index 7052123..99ff786 100644 --- a/ifgen/svd/model/__init__.py +++ b/ifgen/svd/model/__init__.py @@ -4,7 +4,7 @@ # built-in from dataclasses import dataclass -from typing import Optional +from typing import Any, Optional from xml.etree import ElementTree # internal @@ -21,6 +21,18 @@ class SvdModel: device: Optional[Device] = None cpu: Optional[Cpu] = None + def metadata(self) -> dict[str, Any]: + """Get device and CPU metadata.""" + + result = {} + + if self.device is not None: + result["device"] = self.device.raw_data + if self.cpu is not None: + result["cpu"] = self.cpu.raw_data + + return result + def assign_device(self, device: Device) -> None: """Assign a device instance.""" assert self.device is None, self.device diff --git a/ifgen/svd/task.py b/ifgen/svd/task.py index 78540b0..7945c3b 100644 --- a/ifgen/svd/task.py +++ b/ifgen/svd/task.py @@ -10,6 +10,7 @@ from xml.etree import ElementTree # third-party +from vcorelib.io import ARBITER from vcorelib.logging import LoggerType # internal @@ -39,3 +40,13 @@ def svd(path: Path) -> "SvdProcessingTask": task = SvdProcessingTask(SvdModel({})) task.process(ElementTree.parse(path).getroot()) return task + + def generate_configs(self, path: Path) -> None: + """Generate output configuration files.""" + + path.mkdir(exist_ok=True, parents=True) + + # Write metadata that doesn't currently get used for generation. + ARBITER.encode(path.joinpath("metadata.json"), self.model.metadata()) + + # generate outputs diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 05fe501..1c0b239 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 2 minor: 4 -patch: 0 +patch: 1 entry: ig diff --git a/pyproject.toml b/pyproject.toml index d0ab680..84aa6f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "ifgen" -version = "2.4.0" +version = "2.4.1" description = "An interface generator for distributed computing." readme = "README.md" requires-python = ">=3.11" diff --git a/tests/commands/test_svd.py b/tests/commands/test_svd.py index 576710f..e495c57 100644 --- a/tests/commands/test_svd.py +++ b/tests/commands/test_svd.py @@ -2,6 +2,9 @@ Test the 'commands.svd' module. """ +# built-in +from tempfile import TemporaryDirectory + # module under test from ifgen import PKG_NAME from ifgen.entry import main as ifgen_main @@ -10,10 +13,17 @@ def test_svd_command_basic(): """Test the 'svd' command.""" - for svd in ["XMC4700", "rp2040"]: - assert ( - ifgen_main( - [PKG_NAME, "svd", f"package://{PKG_NAME}/svd/{svd}.svd"] + with TemporaryDirectory() as tmpdir: + for svd in ["XMC4700", "rp2040"]: + assert ( + ifgen_main( + [ + PKG_NAME, + "svd", + "-o", + str(tmpdir), + f"package://{PKG_NAME}/svd/{svd}.svd", + ] + ) + == 0 ) - == 0 - ) From e49d6fe3495e75fb6837882becdd53b605301e0c Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Tue, 3 Oct 2023 12:46:33 -0500 Subject: [PATCH 02/10] Try to fix CI --- .isort.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.isort.cfg b/.isort.cfg index 3ae85b4..467f526 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,3 +1,3 @@ [settings] known_first_party=ifgen,vmklib -skip=tests/data +skip=tests/data,config From ab1cc159be13f2b801e0c201f53685ec76d11794 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 01:17:45 -0500 Subject: [PATCH 03/10] Initial YAML generation --- ifgen/requirements.txt | 2 +- ifgen/svd/group/__init__.py | 34 +++++++++++++++++++++++++ ifgen/svd/group/base.py | 47 +++++++++++++++++++++++++++++++++++ ifgen/svd/model/peripheral.py | 17 +++++++++++++ ifgen/svd/task.py | 18 +++++++++++--- local/configs/package.yaml | 2 +- test_svd.sh | 13 ++++++++++ 7 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 ifgen/svd/group/__init__.py create mode 100644 ifgen/svd/group/base.py create mode 100755 test_svd.sh diff --git a/ifgen/requirements.txt b/ifgen/requirements.txt index 3698011..43e085c 100644 --- a/ifgen/requirements.txt +++ b/ifgen/requirements.txt @@ -1,2 +1,2 @@ runtimepy>=2.1.1 -vcorelib>=2.6.1 +vcorelib>=3.0.0 diff --git a/ifgen/svd/group/__init__.py b/ifgen/svd/group/__init__.py new file mode 100644 index 0000000..8852e7f --- /dev/null +++ b/ifgen/svd/group/__init__.py @@ -0,0 +1,34 @@ +""" +A module implementing interfaces for processing a group of peripherals. +""" + +# built-in +from pathlib import Path + +# third-party +from vcorelib.io import ARBITER + +# internal +from ifgen.svd.group.base import PeripheralGroup, peripheral_groups +from ifgen.svd.model.peripheral import peripheral_name + +__all__ = ["PeripheralGroup", "peripheral_groups", "handle_group"] + + +def handle_group( + output_dir: Path, name: str, group: PeripheralGroup, includes: set[Path] +) -> None: + """Handle a peripheral group.""" + + output = output_dir.joinpath("include.yaml") + includes.add(output) + + ARBITER.encode( + output, + { + "name": peripheral_name(name), + "derivatives": [ + peripheral_name(x.name) for x in group.derivatives + ], + }, + ) diff --git a/ifgen/svd/group/base.py b/ifgen/svd/group/base.py new file mode 100644 index 0000000..d8cd63d --- /dev/null +++ b/ifgen/svd/group/base.py @@ -0,0 +1,47 @@ +""" +A module implementing base interfaces for processing a group of peripherals. +""" + +# built-in +from dataclasses import dataclass + +# internal +from ifgen.svd.model.peripheral import Peripheral + + +@dataclass +class PeripheralGroup: + """A container for peripherals that have the same register layout.""" + + root: Peripheral + derivatives: list[Peripheral] + + @property + def size(self) -> int: + """Get the size of this peripheral group.""" + return 1 + len(self.derivatives) + + +def peripheral_groups( + peripherals: dict[str, Peripheral] +) -> dict[str, PeripheralGroup]: + """Organize peripherals into groups.""" + + result: dict[str, PeripheralGroup] = {} + + for name, peripheral in peripherals.items(): + if name not in result and not peripheral.derived: + # Validate this later. + result[name] = PeripheralGroup(None, []) # type: ignore + + if peripheral.derived: + result[peripheral.derived_elem.name].derivatives.append(peripheral) + else: + assert result[name].root is None, result[name].root + result[name].root = peripheral + + # Validate groups. + for name, group in result.items(): + assert group.root is not None, (name, group) + + return result diff --git a/ifgen/svd/model/peripheral.py b/ifgen/svd/model/peripheral.py index 54bbeb8..b5c7af0 100644 --- a/ifgen/svd/model/peripheral.py +++ b/ifgen/svd/model/peripheral.py @@ -19,6 +19,18 @@ RegisterData = Tuple[ClusterMap, RegisterMap] +def peripheral_name(name: str, inst: bool = True) -> str: + """Get the name of a peripheral.""" + + name = name.lower() + + if not inst: + if name[-1].isdigit(): + name = name[:-1] + + return name + + @dataclass class Peripheral(DerivedMixin): """A container for peripheral information.""" @@ -28,6 +40,11 @@ class Peripheral(DerivedMixin): address_blocks: List[AddressBlock] registers: List[RegisterData] + @property + def base_name(self) -> str: + """Get the base peripheral name.""" + return peripheral_name(self.name, inst=False) + def handle_registers(self, registers: ElementTree.Element) -> None: """Handle the 'registers' element.""" diff --git a/ifgen/svd/task.py b/ifgen/svd/task.py index 7945c3b..0c5e211 100644 --- a/ifgen/svd/task.py +++ b/ifgen/svd/task.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from logging import getLogger from pathlib import Path -from typing import Callable, Dict +from typing import Callable from xml.etree import ElementTree # third-party @@ -14,12 +14,13 @@ from vcorelib.logging import LoggerType # internal +from ifgen.svd.group import handle_group, peripheral_groups from ifgen.svd.model import SvdModel TagProcessor = Callable[ [ElementTree.Element, "SvdProcessingTask", LoggerType], None ] -TagProcessorMap = Dict[str, TagProcessor] +TagProcessorMap = dict[str, TagProcessor] TAG_PROCESSORS: TagProcessorMap = {} @@ -49,4 +50,15 @@ def generate_configs(self, path: Path) -> None: # Write metadata that doesn't currently get used for generation. ARBITER.encode(path.joinpath("metadata.json"), self.model.metadata()) - # generate outputs + includes: set[Path] = set() + + # Organize peripherals into groups based on ones derived from others + # and process them. + for name, group in peripheral_groups(self.model.peripherals).items(): + output_dir = path.joinpath(group.root.base_name) + output_dir.mkdir(exist_ok=True) + handle_group(output_dir, name, group, includes) + + ARBITER.encode( + path.joinpath("all.yaml"), {"includes": [str(x) for x in includes]} + ) diff --git a/local/configs/package.yaml b/local/configs/package.yaml index b70969a..fc75e61 100644 --- a/local/configs/package.yaml +++ b/local/configs/package.yaml @@ -15,7 +15,7 @@ ci_local: requirements: - runtimepy>=2.1.1 - - vcorelib>=2.6.1 + - vcorelib>=3.0.0 dev_requirements: - pytest-cov diff --git a/test_svd.sh b/test_svd.sh new file mode 100755 index 0000000..38d0f4c --- /dev/null +++ b/test_svd.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +mkdir >/dev/null -p build + +pushd build || exit + +for CHIP in rp2040 XMC4700; do + ../venv/bin/ig svd -o $CHIP package://ifgen/svd/$CHIP.svd & +done + +wait + +popd >/dev/null || exit From 4cbd3ce1ac947bbe4d61dc533a8a3f4cb59b1fd9 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 01:33:16 -0500 Subject: [PATCH 04/10] First sign of life --- ifgen/svd/group/__init__.py | 30 +++++++++++++++++++++++------- ifgen/svd/group/base.py | 7 +++++++ ifgen/svd/task.py | 4 ++-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/ifgen/svd/group/__init__.py b/ifgen/svd/group/__init__.py index 8852e7f..bf59346 100644 --- a/ifgen/svd/group/__init__.py +++ b/ifgen/svd/group/__init__.py @@ -4,31 +4,47 @@ # built-in from pathlib import Path +from typing import Any # third-party from vcorelib.io import ARBITER # internal from ifgen.svd.group.base import PeripheralGroup, peripheral_groups -from ifgen.svd.model.peripheral import peripheral_name +from ifgen.svd.model.peripheral import Peripheral, peripheral_name __all__ = ["PeripheralGroup", "peripheral_groups", "handle_group"] +def struct_instance(peripheral: Peripheral) -> dict[str, Any]: + """Get struct instance data.""" + + return { + "name": peripheral_name(peripheral.name), + "address": peripheral.raw_data["baseAddress"], + } + + +def struct_data(group: PeripheralGroup) -> dict[str, Any]: + """Get struct data for a peripheral group.""" + + data = {"instances": [struct_instance(x) for x in group.peripherals]} + + return data + + def handle_group( - output_dir: Path, name: str, group: PeripheralGroup, includes: set[Path] + output_dir: Path, group: PeripheralGroup, includes: set[Path] ) -> None: """Handle a peripheral group.""" output = output_dir.joinpath("include.yaml") includes.add(output) - ARBITER.encode( output, { - "name": peripheral_name(name), - "derivatives": [ - peripheral_name(x.name) for x in group.derivatives - ], + "structs": { + group.root.base_name: struct_data(group), # type: ignore + } }, ) diff --git a/ifgen/svd/group/base.py b/ifgen/svd/group/base.py index d8cd63d..e2cd51c 100644 --- a/ifgen/svd/group/base.py +++ b/ifgen/svd/group/base.py @@ -4,6 +4,7 @@ # built-in from dataclasses import dataclass +from typing import Iterator # internal from ifgen.svd.model.peripheral import Peripheral @@ -21,6 +22,12 @@ def size(self) -> int: """Get the size of this peripheral group.""" return 1 + len(self.derivatives) + @property + def peripherals(self) -> Iterator[Peripheral]: + """Get all peripheral instances.""" + yield self.root + yield from self.derivatives + def peripheral_groups( peripherals: dict[str, Peripheral] diff --git a/ifgen/svd/task.py b/ifgen/svd/task.py index 0c5e211..5b4a45c 100644 --- a/ifgen/svd/task.py +++ b/ifgen/svd/task.py @@ -54,10 +54,10 @@ def generate_configs(self, path: Path) -> None: # Organize peripherals into groups based on ones derived from others # and process them. - for name, group in peripheral_groups(self.model.peripherals).items(): + for group in peripheral_groups(self.model.peripherals).values(): output_dir = path.joinpath(group.root.base_name) output_dir.mkdir(exist_ok=True) - handle_group(output_dir, name, group, includes) + handle_group(output_dir, group, includes) ARBITER.encode( path.joinpath("all.yaml"), {"includes": [str(x) for x in includes]} From db532b9ccadbdddc8ea0ba4135daba7ff699763a Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 01:37:37 -0500 Subject: [PATCH 05/10] Ready to generate data for fields --- ifgen/svd/group/__init__.py | 8 +++++++- ifgen/svd/group/fields.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 ifgen/svd/group/fields.py diff --git a/ifgen/svd/group/__init__.py b/ifgen/svd/group/__init__.py index bf59346..7a627ed 100644 --- a/ifgen/svd/group/__init__.py +++ b/ifgen/svd/group/__init__.py @@ -11,6 +11,7 @@ # internal from ifgen.svd.group.base import PeripheralGroup, peripheral_groups +from ifgen.svd.group.fields import struct_fields from ifgen.svd.model.peripheral import Peripheral, peripheral_name __all__ = ["PeripheralGroup", "peripheral_groups", "handle_group"] @@ -28,7 +29,12 @@ def struct_instance(peripheral: Peripheral) -> dict[str, Any]: def struct_data(group: PeripheralGroup) -> dict[str, Any]: """Get struct data for a peripheral group.""" - data = {"instances": [struct_instance(x) for x in group.peripherals]} + data = { + "instances": [struct_instance(x) for x in group.peripherals], + "fields": struct_fields(group.root), + "stream": False, + "codec": False, + } return data diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py new file mode 100644 index 0000000..5a98fe5 --- /dev/null +++ b/ifgen/svd/group/fields.py @@ -0,0 +1,19 @@ +""" +A module for generating configuration data for struct fields. +""" + +# built-in +from typing import Any + +# internal +from ifgen.svd.model.peripheral import Peripheral + + +def struct_fields(peripheral: Peripheral) -> list[dict[str, Any]]: + """Generate data for struct fields.""" + + result: list[dict[str, Any]] = [] + + del peripheral + + return result From 7de1620f88f397dfb18d504596797faa1467cfe3 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 02:44:29 -0500 Subject: [PATCH 06/10] More generation forward progress --- ifgen/svd/group/base.py | 5 --- ifgen/svd/group/fields.py | 9 ++++- ifgen/svd/model/__init__.py | 10 +++++- ifgen/svd/model/cluster.py | 62 +++++++++++++++++++++++++++-------- ifgen/svd/model/peripheral.py | 17 ++++------ ifgen/svd/model/register.py | 37 ++++++++++----------- 6 files changed, 90 insertions(+), 50 deletions(-) diff --git a/ifgen/svd/group/base.py b/ifgen/svd/group/base.py index e2cd51c..901a3d5 100644 --- a/ifgen/svd/group/base.py +++ b/ifgen/svd/group/base.py @@ -17,11 +17,6 @@ class PeripheralGroup: root: Peripheral derivatives: list[Peripheral] - @property - def size(self) -> int: - """Get the size of this peripheral group.""" - return 1 + len(self.derivatives) - @property def peripherals(self) -> Iterator[Peripheral]: """Get all peripheral instances.""" diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 5a98fe5..ea7b256 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -6,6 +6,7 @@ from typing import Any # internal +from ifgen.svd.model.cluster import Cluster from ifgen.svd.model.peripheral import Peripheral @@ -14,6 +15,12 @@ def struct_fields(peripheral: Peripheral) -> list[dict[str, Any]]: result: list[dict[str, Any]] = [] - del peripheral + for item in peripheral.registers: + # check if this is an array? + if isinstance(item, Cluster): + for child in item.children: + result.append({"name": child.name}) + else: + result.append({"name": item.name}) return result diff --git a/ifgen/svd/model/__init__.py b/ifgen/svd/model/__init__.py index 99ff786..5b559ba 100644 --- a/ifgen/svd/model/__init__.py +++ b/ifgen/svd/model/__init__.py @@ -24,13 +24,21 @@ class SvdModel: def metadata(self) -> dict[str, Any]: """Get device and CPU metadata.""" - result = {} + result: dict[str, Any] = {} if self.device is not None: result["device"] = self.device.raw_data if self.cpu is not None: result["cpu"] = self.cpu.raw_data + for name, peripheral in self.peripherals.items(): + result[name] = { + "interrupts": [x.raw_data for x in peripheral.interrupts], + "address_blocks": [ + x.raw_data for x in peripheral.address_blocks + ], + } + return result def assign_device(self, device: Device) -> None: diff --git a/ifgen/svd/model/cluster.py b/ifgen/svd/model/cluster.py index bb2460c..985570c 100644 --- a/ifgen/svd/model/cluster.py +++ b/ifgen/svd/model/cluster.py @@ -4,20 +4,25 @@ # built-in from dataclasses import dataclass -from typing import Iterable, Optional +from typing import Iterable, Optional, Union from xml.etree import ElementTree # internal -from ifgen.svd.model.derived import DerivedMixin, derived_from_stack +from ifgen.svd.model.derived import DerivedMixin from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES +from ifgen.svd.model.register import Register, RegisterMap, register from ifgen.svd.string import StringKeyVal +ClusterMap = dict[str, "Cluster"] +RegisterData = list[Union[Register, "Cluster"]] + @dataclass class Cluster(DerivedMixin): """A container for cluster information.""" derived_from: Optional["Cluster"] + children: RegisterData @classmethod def string_keys(cls) -> Iterable[StringKeyVal]: @@ -38,20 +43,49 @@ def string_keys(cls) -> Iterable[StringKeyVal]: ) -ClusterMap = dict[str, Cluster] - +def handle_registers( + registers: ElementTree.Element, + cluster_map: ClusterMap = None, + register_map: RegisterMap = None, +) -> RegisterData: + """Handle the 'registers' element.""" -def get_clusters(registers: ElementTree.Element) -> ClusterMap: - """Get register clusters.""" + result: RegisterData = [] - result: ClusterMap = {} - for cluster in derived_from_stack(registers.iterfind("cluster")): - derived_cluster = None - derived = cluster.attrib.get("derivedFrom") - if derived is not None: - derived_cluster = result[derived] # pragma: nocover + if cluster_map is None: + cluster_map = {} + if register_map is None: + register_map = {} - inst = Cluster.create(cluster, derived_cluster) - result[inst.name] = inst + for item in registers: + if item.tag == "cluster": + result.append( + cluster(item, cluster_map, register_map=register_map) + ) + elif item.tag == "register": + result.append(register(item, register_map)) return result + + +def cluster( + element: ElementTree.Element, + cluster_map: ClusterMap, + register_map: RegisterMap = None, +) -> Cluster: + """Create a Cluster instance from an SVD element.""" + + derived_cluster = None + derived = element.attrib.get("derivedFrom") + if derived is not None: + derived_cluster = cluster_map[derived] # pragma: nocover + + inst = Cluster.create( + element, + derived_cluster, + handle_registers( + element, cluster_map=cluster_map, register_map=register_map + ), + ) + cluster_map[inst.name] = inst + return inst diff --git a/ifgen/svd/model/peripheral.py b/ifgen/svd/model/peripheral.py index b5c7af0..6e5140d 100644 --- a/ifgen/svd/model/peripheral.py +++ b/ifgen/svd/model/peripheral.py @@ -4,20 +4,17 @@ # built-in from dataclasses import dataclass -from typing import Iterable, List, Optional, Tuple +from typing import Iterable, List, Optional from xml.etree import ElementTree # internal from ifgen.svd.model.address_block import AddressBlock -from ifgen.svd.model.cluster import ClusterMap, get_clusters +from ifgen.svd.model.cluster import RegisterData, handle_registers from ifgen.svd.model.derived import DerivedMixin from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES from ifgen.svd.model.interrupt import Interrupt -from ifgen.svd.model.register import RegisterMap, get_registers from ifgen.svd.string import StringKeyVal -RegisterData = Tuple[ClusterMap, RegisterMap] - def peripheral_name(name: str, inst: bool = True) -> str: """Get the name of a peripheral.""" @@ -36,9 +33,12 @@ class Peripheral(DerivedMixin): """A container for peripheral information.""" derived_from: Optional["Peripheral"] + + # Currently treated as metadata. interrupts: List[Interrupt] address_blocks: List[AddressBlock] - registers: List[RegisterData] + + registers: RegisterData @property def base_name(self) -> str: @@ -47,10 +47,7 @@ def base_name(self) -> str: def handle_registers(self, registers: ElementTree.Element) -> None: """Handle the 'registers' element.""" - - self.registers.append( - (get_clusters(registers), get_registers(registers)) - ) + self.registers = handle_registers(registers) def handle_address_block(self, address_block: ElementTree.Element) -> None: """Handle an 'address_block' element.""" diff --git a/ifgen/svd/model/register.py b/ifgen/svd/model/register.py index 9c25ded..c89988e 100644 --- a/ifgen/svd/model/register.py +++ b/ifgen/svd/model/register.py @@ -8,7 +8,7 @@ from xml.etree import ElementTree # internal -from ifgen.svd.model.derived import DerivedMixin, derived_from_stack +from ifgen.svd.model.derived import DerivedMixin from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES from ifgen.svd.model.field import FieldMap, get_fields from ifgen.svd.string import StringKeyVal @@ -50,25 +50,24 @@ def string_keys(cls) -> Iterable[StringKeyVal]: RegisterMap = dict[str, Register] -def get_registers(registers: ElementTree.Element) -> RegisterMap: - """Get register elements.""" +def register( + element: ElementTree.Element, register_map: RegisterMap +) -> Register: + """Create a Register instance from an SVD element.""" - result: RegisterMap = {} - for register in derived_from_stack(registers.iterfind("register")): - derived_register = None - derived = register.attrib.get("derivedFrom") - if derived is not None: - derived_register = result[derived] # pragma: nocover + derived_register = None + derived = element.attrib.get("derivedFrom") + if derived is not None: + derived_register = register_map[derived] # pragma: nocover - # Handle writeConstraint at some point? + # Handle writeConstraint at some point? - # Load fields. - fields = None - fields_elem = register.find("fields") - if fields_elem is not None: - fields = get_fields(fields_elem) + # Load fields. + fields = None + fields_elem = element.find("fields") + if fields_elem is not None: + fields = get_fields(fields_elem) - inst = Register.create(register, derived_register, fields) - result[inst.name] = inst - - return result + inst = Register.create(element, derived_register, fields) + register_map[inst.name] = inst + return inst From 4b5b1a8658147fcd1c20a6d72d67fa156d2b97e2 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 03:27:11 -0500 Subject: [PATCH 07/10] More SVD development --- ifgen/data/schemas/Struct.yaml | 3 ++ ifgen/struct/__init__.py | 13 ++++- ifgen/svd/group/__init__.py | 35 +++++++------- ifgen/svd/group/fields.py | 51 +++++++++++++++----- ifgen/svd/model/cluster.py | 2 - ifgen/svd/string.py | 16 +++++- tests/data/valid/scenarios/sample/ifgen.yaml | 1 + 7 files changed, 86 insertions(+), 35 deletions(-) diff --git a/ifgen/data/schemas/Struct.yaml b/ifgen/data/schemas/Struct.yaml index bbfe3db..449c0e0 100644 --- a/ifgen/data/schemas/Struct.yaml +++ b/ifgen/data/schemas/Struct.yaml @@ -15,6 +15,9 @@ properties: type: boolean default: true + expected_size: + type: integer + instances: type: array items: diff --git a/ifgen/struct/__init__.py b/ifgen/struct/__init__.py index d5c1686..e233f84 100644 --- a/ifgen/struct/__init__.py +++ b/ifgen/struct/__init__.py @@ -115,10 +115,19 @@ def create_struct(task: GenerateTask) -> None: f"{task.name}'s identifier.", ) ) + size = task.env.types.size(task.name) + + # If expected size is set, verify it. + if "expected_size" in task.instance: + assert task.instance["expected_size"] == size, ( + task.name, + task.instance["expected_size"], + size, + ) + lines.append( ( - f"static constexpr std::size_t size = " - f"{task.env.types.size(task.name)};", + f"static constexpr std::size_t size = {size};", f"{task.name}'s size in bytes.", ) ) diff --git a/ifgen/svd/group/__init__.py b/ifgen/svd/group/__init__.py index 7a627ed..0ee1d66 100644 --- a/ifgen/svd/group/__init__.py +++ b/ifgen/svd/group/__init__.py @@ -11,8 +11,8 @@ # internal from ifgen.svd.group.base import PeripheralGroup, peripheral_groups -from ifgen.svd.group.fields import struct_fields -from ifgen.svd.model.peripheral import Peripheral, peripheral_name +from ifgen.svd.group.fields import DEFAULT_STRUCT, StructMap, struct_fields +from ifgen.svd.model.peripheral import Peripheral __all__ = ["PeripheralGroup", "peripheral_groups", "handle_group"] @@ -21,21 +21,24 @@ def struct_instance(peripheral: Peripheral) -> dict[str, Any]: """Get struct instance data.""" return { - "name": peripheral_name(peripheral.name), + "name": peripheral.name, "address": peripheral.raw_data["baseAddress"], } -def struct_data(group: PeripheralGroup) -> dict[str, Any]: +def struct_data(group: PeripheralGroup, structs: StructMap) -> dict[str, Any]: """Get struct data for a peripheral group.""" - data = { - "instances": [struct_instance(x) for x in group.peripherals], - "fields": struct_fields(group.root), - "stream": False, - "codec": False, - } + data: dict[str, Any] = {} + peripheral = group.root + peripheral.handle_description(data) + + data["instances"] = [struct_instance(x) for x in group.peripherals] + data["fields"] = struct_fields(peripheral.registers, structs) + # let's compute expected size? + + data.update(DEFAULT_STRUCT) return data @@ -46,11 +49,7 @@ def handle_group( output = output_dir.joinpath("include.yaml") includes.add(output) - ARBITER.encode( - output, - { - "structs": { - group.root.base_name: struct_data(group), # type: ignore - } - }, - ) + + structs: StructMap = {} + structs[group.root.base_name] = struct_data(group, structs) + ARBITER.encode(output, {"structs": structs}) diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index ea7b256..6976ce3 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -6,21 +6,48 @@ from typing import Any # internal -from ifgen.svd.model.cluster import Cluster -from ifgen.svd.model.peripheral import Peripheral +from ifgen.svd.model.cluster import Cluster, RegisterData +from ifgen.svd.model.register import Register +StructMap = dict[str, Any] +StructField = dict[str, Any] +DEFAULT_STRUCT = {"stream": False, "codec": False} -def struct_fields(peripheral: Peripheral) -> list[dict[str, Any]]: - """Generate data for struct fields.""" - result: list[dict[str, Any]] = [] +def handle_cluster(cluster: Cluster, structs: StructMap) -> StructField: + """Handle a cluster element.""" + + # Register a struct for this cluster. Should we use a namespace for this? + cluster_struct: dict[str, Any] = cluster.handle_description() + cluster_struct["fields"] = struct_fields(cluster.children, structs) + cluster_struct.update(DEFAULT_STRUCT) + + # compute expected size? - for item in peripheral.registers: - # check if this is an array? - if isinstance(item, Cluster): - for child in item.children: - result.append({"name": child.name}) - else: - result.append({"name": item.name}) + structs[cluster.name] = cluster_struct + # This needs to be an array element somehow. Use a namespace? + result: StructField = {"name": cluster.name} + cluster.handle_description(result) return result + + +def handle_register(register: Register) -> StructField: + """Handle a register entry.""" + + data = {"name": register.name} + register.handle_description(data) + return data + + +def struct_fields( + registers: RegisterData, structs: StructMap +) -> list[StructField]: + """Generate data for struct fields.""" + + return [ + handle_cluster(item, structs) + if isinstance(item, Cluster) + else handle_register(item) + for item in registers + ] diff --git a/ifgen/svd/model/cluster.py b/ifgen/svd/model/cluster.py index 985570c..c4aee21 100644 --- a/ifgen/svd/model/cluster.py +++ b/ifgen/svd/model/cluster.py @@ -28,8 +28,6 @@ class Cluster(DerivedMixin): def string_keys(cls) -> Iterable[StringKeyVal]: """Get string keys for this instance type.""" - # Not currently handling nested registers or clusters. - return ( ARRAY_PROPERTIES + [ diff --git a/ifgen/svd/string.py b/ifgen/svd/string.py index 2196c92..3e9f5f1 100644 --- a/ifgen/svd/string.py +++ b/ifgen/svd/string.py @@ -5,7 +5,7 @@ # built-in from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import Iterable, Optional, Type, TypeVar +from typing import Any, Iterable, Optional, Type, TypeVar from xml.etree import ElementTree # third-party @@ -41,6 +41,20 @@ def create(cls: Type[T], elem: ElementTree.Element, *args, **kwargs) -> T: def string_keys(cls) -> Iterable[StringKeyVal]: """Get string keys for this instance type.""" + def handle_description( + self, data: dict[str, Any] = None + ) -> dict[str, Any]: + """Handle a possible description entry.""" + + if data is None: + data = {} + + description = self.raw_data.get("description") + if description: + data["description"] = description + + return data + def raw(self, elem: ElementTree.Element) -> dict[str, str]: """Get raw data for this instance based on string keys.""" diff --git a/tests/data/valid/scenarios/sample/ifgen.yaml b/tests/data/valid/scenarios/sample/ifgen.yaml index 03ab831..3314a44 100644 --- a/tests/data/valid/scenarios/sample/ifgen.yaml +++ b/tests/data/valid/scenarios/sample/ifgen.yaml @@ -7,6 +7,7 @@ structs: namespace: [C] description: "Sample struct 1." json_indent: 4 + expected_size: 11 fields: - name: field1 description: A field. From 5e0421d80e12953ddd318223f080ed15f1a2c375 Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 13:04:05 -0500 Subject: [PATCH 08/10] Add initial register size --- ifgen/svd/group/fields.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 6976ce3..558d460 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -35,7 +35,9 @@ def handle_cluster(cluster: Cluster, structs: StructMap) -> StructField: def handle_register(register: Register) -> StructField: """Handle a register entry.""" - data = {"name": register.name} + # handle register is array + size = register.raw_data["size"] + data = {"name": register.name, "type": f"uint{size}_t"} register.handle_description(data) return data From c968c16df7260dc363fe19ffc3db3045f04b02be Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 18:22:46 -0500 Subject: [PATCH 09/10] Some intermediate fixes --- ifgen/svd/group/fields.py | 12 +++-- ifgen/svd/model/cluster.py | 89 ------------------------------- ifgen/svd/model/peripheral.py | 70 ++++++++++++++++++++++--- ifgen/svd/model/register.py | 73 -------------------------- ifgen/svd/peripherals.py | 3 +- ifgen/svd/process.py | 99 +++++++++++++++++++++++++++++++++++ 6 files changed, 172 insertions(+), 174 deletions(-) delete mode 100644 ifgen/svd/model/cluster.py delete mode 100644 ifgen/svd/model/register.py create mode 100644 ifgen/svd/process.py diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 558d460..01dd98f 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -6,8 +6,7 @@ from typing import Any # internal -from ifgen.svd.model.cluster import Cluster, RegisterData -from ifgen.svd.model.register import Register +from ifgen.svd.model.peripheral import Cluster, Register, RegisterData StructMap = dict[str, Any] StructField = dict[str, Any] @@ -35,9 +34,12 @@ def handle_cluster(cluster: Cluster, structs: StructMap) -> StructField: def handle_register(register: Register) -> StructField: """Handle a register entry.""" - # handle register is array - size = register.raw_data["size"] - data = {"name": register.name, "type": f"uint{size}_t"} + # handle register is array + get size from peripheral if necessary + # assert "size" in register.raw_data, register.name + # size = register.raw_data["size"] + # data = {"name": register.name, "type": f"uint{size}_t"} + data = {"name": register.name} + register.handle_description(data) return data diff --git a/ifgen/svd/model/cluster.py b/ifgen/svd/model/cluster.py deleted file mode 100644 index c4aee21..0000000 --- a/ifgen/svd/model/cluster.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -A module implementing a data model for ARM CMSIS-SVD 'cluster' data. -""" - -# built-in -from dataclasses import dataclass -from typing import Iterable, Optional, Union -from xml.etree import ElementTree - -# internal -from ifgen.svd.model.derived import DerivedMixin -from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES -from ifgen.svd.model.register import Register, RegisterMap, register -from ifgen.svd.string import StringKeyVal - -ClusterMap = dict[str, "Cluster"] -RegisterData = list[Union[Register, "Cluster"]] - - -@dataclass -class Cluster(DerivedMixin): - """A container for cluster information.""" - - derived_from: Optional["Cluster"] - children: RegisterData - - @classmethod - def string_keys(cls) -> Iterable[StringKeyVal]: - """Get string keys for this instance type.""" - - return ( - ARRAY_PROPERTIES - + [ - StringKeyVal("name", True), - StringKeyVal("description", False), - StringKeyVal("alternateCluster", False), - StringKeyVal("headerStructName", False), - StringKeyVal("addressOffset", True), - ] - + REGISTER_PROPERTIES - ) - - -def handle_registers( - registers: ElementTree.Element, - cluster_map: ClusterMap = None, - register_map: RegisterMap = None, -) -> RegisterData: - """Handle the 'registers' element.""" - - result: RegisterData = [] - - if cluster_map is None: - cluster_map = {} - if register_map is None: - register_map = {} - - for item in registers: - if item.tag == "cluster": - result.append( - cluster(item, cluster_map, register_map=register_map) - ) - elif item.tag == "register": - result.append(register(item, register_map)) - - return result - - -def cluster( - element: ElementTree.Element, - cluster_map: ClusterMap, - register_map: RegisterMap = None, -) -> Cluster: - """Create a Cluster instance from an SVD element.""" - - derived_cluster = None - derived = element.attrib.get("derivedFrom") - if derived is not None: - derived_cluster = cluster_map[derived] # pragma: nocover - - inst = Cluster.create( - element, - derived_cluster, - handle_registers( - element, cluster_map=cluster_map, register_map=register_map - ), - ) - cluster_map[inst.name] = inst - return inst diff --git a/ifgen/svd/model/peripheral.py b/ifgen/svd/model/peripheral.py index 6e5140d..51ae0ed 100644 --- a/ifgen/svd/model/peripheral.py +++ b/ifgen/svd/model/peripheral.py @@ -4,14 +4,14 @@ # built-in from dataclasses import dataclass -from typing import Iterable, List, Optional +from typing import Iterable, List, Optional, Union from xml.etree import ElementTree # internal from ifgen.svd.model.address_block import AddressBlock -from ifgen.svd.model.cluster import RegisterData, handle_registers from ifgen.svd.model.derived import DerivedMixin from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES +from ifgen.svd.model.field import FieldMap from ifgen.svd.model.interrupt import Interrupt from ifgen.svd.string import StringKeyVal @@ -28,6 +28,68 @@ def peripheral_name(name: str, inst: bool = True) -> str: return name +RegisterData = list[Union["Register", "Cluster"]] + + +@dataclass +class Cluster(DerivedMixin): + """A container for cluster information.""" + + derived_from: Optional["Cluster"] + children: RegisterData + peripheral: "Peripheral" + + @classmethod + def string_keys(cls) -> Iterable[StringKeyVal]: + """Get string keys for this instance type.""" + + return ( + ARRAY_PROPERTIES + + [ + StringKeyVal("name", True), + StringKeyVal("description", False), + StringKeyVal("alternateCluster", False), + StringKeyVal("headerStructName", False), + StringKeyVal("addressOffset", True), + ] + + REGISTER_PROPERTIES + ) + + +@dataclass +class Register(DerivedMixin): + """A container for register information.""" + + derived_from: Optional["Register"] + fields: Optional[FieldMap] + peripheral: "Peripheral" + + @classmethod + def string_keys(cls) -> Iterable[StringKeyVal]: + """Get string keys for this instance type.""" + + return ( + ARRAY_PROPERTIES + + [ + StringKeyVal("name", True), + StringKeyVal("displayName", False), + StringKeyVal("description", False), + StringKeyVal("alternateGroup", False), + StringKeyVal("alternateRegister", False), + StringKeyVal("addressOffset", True), + ] + + REGISTER_PROPERTIES + + [ + # is enum + StringKeyVal("dataType", False), + # is enum + StringKeyVal("modifiedWriteValues", False), + # is enum + StringKeyVal("readAction", False), + ] + ) + + @dataclass class Peripheral(DerivedMixin): """A container for peripheral information.""" @@ -45,10 +107,6 @@ def base_name(self) -> str: """Get the base peripheral name.""" return peripheral_name(self.name, inst=False) - def handle_registers(self, registers: ElementTree.Element) -> None: - """Handle the 'registers' element.""" - self.registers = handle_registers(registers) - def handle_address_block(self, address_block: ElementTree.Element) -> None: """Handle an 'address_block' element.""" self.address_blocks.append(AddressBlock.create(address_block)) diff --git a/ifgen/svd/model/register.py b/ifgen/svd/model/register.py deleted file mode 100644 index c89988e..0000000 --- a/ifgen/svd/model/register.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -A module implementing a data model for ARM CMSIS-SVD 'register' data. -""" - -# built-in -from dataclasses import dataclass -from typing import Iterable, Optional -from xml.etree import ElementTree - -# internal -from ifgen.svd.model.derived import DerivedMixin -from ifgen.svd.model.device import ARRAY_PROPERTIES, REGISTER_PROPERTIES -from ifgen.svd.model.field import FieldMap, get_fields -from ifgen.svd.string import StringKeyVal - - -@dataclass -class Register(DerivedMixin): - """A container for register information.""" - - derived_from: Optional["Register"] - fields: Optional[FieldMap] - - @classmethod - def string_keys(cls) -> Iterable[StringKeyVal]: - """Get string keys for this instance type.""" - - return ( - ARRAY_PROPERTIES - + [ - StringKeyVal("name", True), - StringKeyVal("displayName", False), - StringKeyVal("description", False), - StringKeyVal("alternateGroup", False), - StringKeyVal("alternateRegister", False), - StringKeyVal("addressOffset", True), - ] - + REGISTER_PROPERTIES - + [ - # is enum - StringKeyVal("dataType", False), - # is enum - StringKeyVal("modifiedWriteValues", False), - # is enum - StringKeyVal("readAction", False), - ] - ) - - -RegisterMap = dict[str, Register] - - -def register( - element: ElementTree.Element, register_map: RegisterMap -) -> Register: - """Create a Register instance from an SVD element.""" - - derived_register = None - derived = element.attrib.get("derivedFrom") - if derived is not None: - derived_register = register_map[derived] # pragma: nocover - - # Handle writeConstraint at some point? - - # Load fields. - fields = None - fields_elem = element.find("fields") - if fields_elem is not None: - fields = get_fields(fields_elem) - - inst = Register.create(element, derived_register, fields) - register_map[inst.name] = inst - return inst diff --git a/ifgen/svd/peripherals.py b/ifgen/svd/peripherals.py index 5713c4b..95e34c5 100644 --- a/ifgen/svd/peripherals.py +++ b/ifgen/svd/peripherals.py @@ -11,6 +11,7 @@ # internal from ifgen.svd.model.derived import derived_from_stack from ifgen.svd.model.peripheral import Peripheral +from ifgen.svd.process import handle_registers from ifgen.svd.task import SvdProcessingTask @@ -30,7 +31,7 @@ def process_peripheral( # Handle registers. registers = elem.find("registers") if registers is not None: - peripheral.handle_registers(registers) + peripheral.registers = handle_registers(registers, peripheral) # Handle address blocks. for address_block in elem.iterfind("addressBlock"): diff --git a/ifgen/svd/process.py b/ifgen/svd/process.py new file mode 100644 index 0000000..f5a0f4f --- /dev/null +++ b/ifgen/svd/process.py @@ -0,0 +1,99 @@ +""" +A module implementing interfaces for processing registers and clusters. +""" + +# built-in +from xml.etree import ElementTree + +# internal +from ifgen.svd.model.field import get_fields +from ifgen.svd.model.peripheral import ( + Cluster, + Peripheral, + Register, + RegisterData, +) + +ClusterMap = dict[str, "Cluster"] +RegisterMap = dict[str, Register] + + +def handle_registers( + registers: ElementTree.Element, + peripheral: Peripheral, + cluster_map: ClusterMap = None, + register_map: RegisterMap = None, +) -> RegisterData: + """Handle the 'registers' element.""" + + result: RegisterData = [] + + if cluster_map is None: + cluster_map = {} + if register_map is None: + register_map = {} + + for item in registers: + if item.tag == "cluster": + result.append( + cluster( + item, cluster_map, peripheral, register_map=register_map + ) + ) + elif item.tag == "register": + result.append(register(item, register_map, peripheral)) + + return result + + +def cluster( + element: ElementTree.Element, + cluster_map: ClusterMap, + peripheral: Peripheral, + register_map: RegisterMap = None, +) -> Cluster: + """Create a Cluster instance from an SVD element.""" + + derived_cluster = None + derived = element.attrib.get("derivedFrom") + if derived is not None: + derived_cluster = cluster_map[derived] # pragma: nocover + + inst = Cluster.create( + element, + derived_cluster, + handle_registers( + element, + peripheral, + cluster_map=cluster_map, + register_map=register_map, + ), + peripheral, + ) + cluster_map[inst.name] = inst + return inst + + +def register( + element: ElementTree.Element, + register_map: RegisterMap, + peripheral: Peripheral, +) -> Register: + """Create a Register instance from an SVD element.""" + + derived_register = None + derived = element.attrib.get("derivedFrom") + if derived is not None: + derived_register = register_map[derived] # pragma: nocover + + # Handle writeConstraint at some point? + + # Load fields. + fields = None + fields_elem = element.find("fields") + if fields_elem is not None: + fields = get_fields(fields_elem) + + inst = Register.create(element, derived_register, fields, peripheral) + register_map[inst.name] = inst + return inst From 7b813220007d7912ca62d8c5efeea70c5a8f389d Mon Sep 17 00:00:00 2001 From: Vaughn Kottler Date: Wed, 4 Oct 2023 20:38:13 -0500 Subject: [PATCH 10/10] Finish preparation for array feature --- ifgen/data/schemas/Struct.yaml | 4 +- ifgen/data/schemas/StructField.yaml | 1 + ifgen/data/schemas/has_expected_size.yaml | 4 ++ ifgen/struct/__init__.py | 29 +++++++++---- ifgen/svd/group/__init__.py | 5 +-- ifgen/svd/group/fields.py | 51 ++++++++++++++--------- ifgen/svd/model/derived.py | 6 ++- ifgen/svd/model/peripheral.py | 24 +++++++++++ 8 files changed, 90 insertions(+), 34 deletions(-) create mode 100644 ifgen/data/schemas/has_expected_size.yaml diff --git a/ifgen/data/schemas/Struct.yaml b/ifgen/data/schemas/Struct.yaml index 449c0e0..391c900 100644 --- a/ifgen/data/schemas/Struct.yaml +++ b/ifgen/data/schemas/Struct.yaml @@ -3,6 +3,7 @@ includes: - has_description.yaml - has_namespace.yaml - has_json_indent.yaml + - has_expected_size.yaml required: [fields] @@ -15,9 +16,6 @@ properties: type: boolean default: true - expected_size: - type: integer - instances: type: array items: diff --git a/ifgen/data/schemas/StructField.yaml b/ifgen/data/schemas/StructField.yaml index 407fd11..5e1ec4a 100644 --- a/ifgen/data/schemas/StructField.yaml +++ b/ifgen/data/schemas/StructField.yaml @@ -2,6 +2,7 @@ includes: - has_description.yaml - has_volatile.yaml + - has_expected_size.yaml required: [name, type] diff --git a/ifgen/data/schemas/has_expected_size.yaml b/ifgen/data/schemas/has_expected_size.yaml new file mode 100644 index 0000000..f0b2951 --- /dev/null +++ b/ifgen/data/schemas/has_expected_size.yaml @@ -0,0 +1,4 @@ +--- +properties: + expected_size: + type: integer diff --git a/ifgen/struct/__init__.py b/ifgen/struct/__init__.py index e233f84..fde3663 100644 --- a/ifgen/struct/__init__.py +++ b/ifgen/struct/__init__.py @@ -66,6 +66,11 @@ def struct_fields(task: GenerateTask, writer: IndentedFileWriter) -> None: with writer.trailing_comment_lines(style=CommentStyle.C_DOXYGEN) as lines: # Fields. for field in task.instance["fields"]: + enforce_expected_size( + task.env.size(field["type"]), + field, + f"{task.name}.{field['name']}", + ) lines.append(struct_line(field["name"], field, field["volatile"])) lines.append(("", None)) @@ -94,6 +99,20 @@ def struct_instance( ) +def enforce_expected_size( + size: int, data: dict[str, Any], assert_msg: str +) -> None: + """Enforce an expected-size field.""" + + # If expected size is set, verify it. + if "expected_size" in data: + assert data["expected_size"] == size, ( + assert_msg, + data["expected_size"], + size, + ) + + def create_struct(task: GenerateTask) -> None: """Create a header file based on a struct definition.""" @@ -115,15 +134,9 @@ def create_struct(task: GenerateTask) -> None: f"{task.name}'s identifier.", ) ) - size = task.env.types.size(task.name) - # If expected size is set, verify it. - if "expected_size" in task.instance: - assert task.instance["expected_size"] == size, ( - task.name, - task.instance["expected_size"], - size, - ) + size = task.env.types.size(task.name) + enforce_expected_size(size, task.instance, task.name) lines.append( ( diff --git a/ifgen/svd/group/__init__.py b/ifgen/svd/group/__init__.py index 0ee1d66..cba373f 100644 --- a/ifgen/svd/group/__init__.py +++ b/ifgen/svd/group/__init__.py @@ -34,9 +34,8 @@ def struct_data(group: PeripheralGroup, structs: StructMap) -> dict[str, Any]: peripheral.handle_description(data) data["instances"] = [struct_instance(x) for x in group.peripherals] - data["fields"] = struct_fields(peripheral.registers, structs) - - # let's compute expected size? + size, data["fields"] = struct_fields(peripheral.registers, structs) + data["expected_size"] = size data.update(DEFAULT_STRUCT) return data diff --git a/ifgen/svd/group/fields.py b/ifgen/svd/group/fields.py index 01dd98f..1a136bd 100644 --- a/ifgen/svd/group/fields.py +++ b/ifgen/svd/group/fields.py @@ -13,12 +13,15 @@ DEFAULT_STRUCT = {"stream": False, "codec": False} -def handle_cluster(cluster: Cluster, structs: StructMap) -> StructField: +def handle_cluster( + cluster: Cluster, structs: StructMap +) -> tuple[int, StructField]: """Handle a cluster element.""" # Register a struct for this cluster. Should we use a namespace for this? cluster_struct: dict[str, Any] = cluster.handle_description() - cluster_struct["fields"] = struct_fields(cluster.children, structs) + size, cluster_struct["fields"] = struct_fields(cluster.children, structs) + cluster_struct["expected_size"] = size cluster_struct.update(DEFAULT_STRUCT) # compute expected size? @@ -26,32 +29,42 @@ def handle_cluster(cluster: Cluster, structs: StructMap) -> StructField: structs[cluster.name] = cluster_struct # This needs to be an array element somehow. Use a namespace? - result: StructField = {"name": cluster.name} + result: StructField = {"name": cluster.name, "expected_size": size} cluster.handle_description(result) - return result + return size, result -def handle_register(register: Register) -> StructField: +def handle_register(register: Register) -> tuple[int, StructField]: """Handle a register entry.""" # handle register is array + get size from peripheral if necessary - # assert "size" in register.raw_data, register.name - # size = register.raw_data["size"] - # data = {"name": register.name, "type": f"uint{size}_t"} - data = {"name": register.name} - + size = register.size + data = { + "name": register.name, + "type": register.c_type, + "expected_size": size, + } register.handle_description(data) - return data + return size, data def struct_fields( - registers: RegisterData, structs: StructMap -) -> list[StructField]: + registers: RegisterData, structs: StructMap, size: int = None +) -> tuple[int, list[StructField]]: """Generate data for struct fields.""" - return [ - handle_cluster(item, structs) - if isinstance(item, Cluster) - else handle_register(item) - for item in registers - ] + fields = [] + + if size is None: + size = 0 + + for item in registers: + inst_size, field = ( + handle_cluster(item, structs) + if isinstance(item, Cluster) + else handle_register(item) + ) + fields.append(field) + size += inst_size + + return size, fields diff --git a/ifgen/svd/model/derived.py b/ifgen/svd/model/derived.py index 9db096b..1766a41 100644 --- a/ifgen/svd/model/derived.py +++ b/ifgen/svd/model/derived.py @@ -22,7 +22,11 @@ class DerivedMixin(StringKeyValueMixin): @property def derived_elem(self: T) -> T: """Get the derived element.""" - return getattr(self, "derived_from") # type: ignore + + result = getattr(self, "derived_from", None) + if result is None: + result = self + return result @property def name(self) -> str: diff --git a/ifgen/svd/model/peripheral.py b/ifgen/svd/model/peripheral.py index 51ae0ed..31ded34 100644 --- a/ifgen/svd/model/peripheral.py +++ b/ifgen/svd/model/peripheral.py @@ -64,6 +64,23 @@ class Register(DerivedMixin): fields: Optional[FieldMap] peripheral: "Peripheral" + @property + def bits(self) -> int: + """Get the size of this register in bits.""" + result = self.raw_data.get("size", self.peripheral.bits) + assert result is not None + return int(result) + + @property + def size(self) -> int: + """Get the size of this register in bytes.""" + return self.bits // 8 + + @property + def c_type(self) -> str: + """Get the C type for this register.""" + return f"uint{self.bits}_t" + @classmethod def string_keys(cls) -> Iterable[StringKeyVal]: """Get string keys for this instance type.""" @@ -102,6 +119,13 @@ class Peripheral(DerivedMixin): registers: RegisterData + @property + def bits(self) -> Optional[int]: + """Get size for this peripheral in bits.""" + + result = self.derived_elem.raw_data.get("size") + return int(result) if result is not None else None + @property def base_name(self) -> str: """Get the base peripheral name."""