Skip to content

Commit

Permalink
Merge pull request #53 from vkottler/dev/3.1.2
Browse files Browse the repository at this point in the history
3.1.2 - Heuristics to improve output
  • Loading branch information
vkottler authored Oct 17, 2023
2 parents 16a53c1 + 76b9406 commit e254170
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
- run: |
mk python-release owner=vkottler \
repo=ifgen version=3.1.1
repo=ifgen version=3.1.2
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
=====================================
generator=datazen
version=3.1.4
hash=9fcb7464a9f8a23b45f51e6f91e5dcfd
hash=975b48a74c4be1ac5e11b915ab1b99fd
=====================================
-->

# ifgen ([3.1.1](https://pypi.org/project/ifgen/))
# ifgen ([3.1.2](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)
Expand Down
4 changes: 2 additions & 2 deletions ifgen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.4
# hash=45c636153116b5681132452943461260
# hash=206c822433faa199cbbeaf11941043ba
# =====================================

"""
Expand All @@ -10,4 +10,4 @@

DESCRIPTION = "An interface generator for distributed computing."
PKG_NAME = "ifgen"
VERSION = "3.1.1"
VERSION = "3.1.2"
44 changes: 36 additions & 8 deletions ifgen/svd/group/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# built-in
from dataclasses import dataclass
from typing import Iterator
from typing import Iterator, Optional

# internal
from ifgen.svd.model.peripheral import Peripheral
Expand All @@ -24,17 +24,46 @@ def peripherals(self) -> Iterator[Peripheral]:
yield from self.derivatives


def get_derived(
peripheral: Peripheral, peripherals: list[Peripheral]
) -> Optional[Peripheral]:
"""Determine if this peripheral is derived from any other peripheral."""

result = None

if peripheral.derived:
result = peripheral.derived_elem

# Check if this peripheral is equivalent to some other peripheral.
else:
for other in peripherals:
# Always return None if you get far enough to see yourself in the
# list. That way this peripheral becomes the effective 'root'.
if other is peripheral:
break

if not other.is_alternate() and not other.derived:
if other == peripheral:
result = other
break

return result


def peripheral_groups(
peripherals: dict[str, Peripheral]
) -> dict[str, PeripheralGroup]:
"""Organize peripherals into groups."""

result: dict[str, PeripheralGroup] = {}

for peripheral in peripherals.values():
peripherals_list = list(peripherals[x] for x in sorted(peripherals))
for peripheral in peripherals_list:
name = peripheral.base_name()
if peripheral.derived:
name = peripheral.derived_elem.base_name()

derived = get_derived(peripheral, peripherals_list)
if derived is not None:
name = derived.base_name()

if name not in result:
# Validate this later.
Expand All @@ -43,11 +72,10 @@ def peripheral_groups(
group = result[name]

if group.root is None:
group.root = peripheral
group.root = derived if derived is not None else peripheral
else:
result[peripheral.derived_elem.base_name()].derivatives.append(
peripheral
)
assert derived is not None
result[derived.base_name()].derivatives.append(peripheral)

# Validate groups.
for name, group in result.items():
Expand Down
130 changes: 130 additions & 0 deletions ifgen/svd/group/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""
A module for handling SVD bit-field enumerations.
"""

# built-in
from typing import Any

# internal
from ifgen.svd.model.enum import EnumeratedValues

EnumValues = dict[str, Any]
ENUM_DEFAULTS: dict[str, Any] = {
"unit_test": False,
"json": False,
"use_map": False,
"identifier": False,
}

BY_HASH: dict[str, dict[int, str]] = {}


def get_enum_name(name: str, peripheral: str, raw_mapping: EnumValues) -> str:
"""Get the name of an enumeration."""

hashed = hash(
",".join(
name + f"={val['value']}" for name, val in raw_mapping.items()
)
)

BY_HASH.setdefault(peripheral, {})

for_periph = BY_HASH[peripheral]
for_periph.setdefault(hashed, name)

return for_periph[hashed]


IGNORE_WORDS = {
"the",
"as",
"a",
"is",
"will",
"but",
"are",
"yet",
"that",
"to",
"and",
"in",
"of",
"on",
"for",
"from",
"its",
"it",
}


def is_name_part(value: str) -> bool:
"""Determine if a word should be part of an enumeration value name."""
return bool(value) and value not in IGNORE_WORDS


def as_alnum(word: str) -> str:
"""Get a word's alpha-numeric contents only."""

result = ""
for char in word:
if char.isalnum() or char == "_":
result += char

return result


def handle_enum_name(name: str, description: str = None) -> str:
"""Attempt to generate more useful enumeration names."""

if name.startswith("value") and description:
new_name = description.replace("-", "_")

alnum_parts = [as_alnum(x.strip().lower()) for x in new_name.split()]

# Prune some words if the description is very long.
if len(alnum_parts) > 1:
alnum_parts = list(filter(is_name_part, alnum_parts))

assert alnum_parts, (name, description)

new_name = "_".join(alnum_parts)

assert new_name, (name, description)
name = new_name

return name


def translate_enums(enum: EnumeratedValues) -> EnumValues:
"""Generate an enumeration definition."""

result: dict[str, Any] = {}
enum.handle_description(result)

for name, value in enum.derived_elem.enum.items():
enum_data: dict[str, Any] = {}
value.handle_description(enum_data)

value_str: str = value.raw_data["value"]

prefix = ""
for possible_prefix in ("#", "0b", "0x"):
if value_str.startswith(possible_prefix):
prefix = possible_prefix
break

if prefix in ("#", "0b"):
enum_data["value"] = int(
value_str[len(prefix) :].replace("X", "1"), 2
)
elif prefix == "0x":
enum_data["value"] = int(value_str[len(prefix) :], 16)
else:
enum_data["value"] = int(value_str)

result[
handle_enum_name(name, value.raw_data.get("description"))
] = enum_data

return result
89 changes: 13 additions & 76 deletions ifgen/svd/group/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from typing import Any, Iterable

# internal
from ifgen.svd.model.enum import EnumeratedValues
from ifgen.svd.model.field import Field
from ifgen.svd.group.enums import ENUM_DEFAULTS, get_enum_name, translate_enums
from ifgen.svd.model.peripheral import Cluster, Register, RegisterData

StructMap = dict[str, Any]
Expand Down Expand Up @@ -87,72 +86,6 @@ def handle_cluster(
RegisterMap = dict[str, Register]


def bit_field_data(field: Field, output: dict[str, Any]) -> None:
"""Populate bit-field data."""

field.handle_description(output)

# We don't currently handle arrays of bit-fields.
assert "dim" not in field.raw_data

if "bitRange" in field.raw_data:
msb_str, lsb_str = field.raw_data["bitRange"].split(":")
lsb = int(lsb_str.replace("]", ""))
msb = int(msb_str.replace("[", ""))
elif "lsb" in field.raw_data:
lsb = int(field.raw_data["lsb"])
msb = int(field.raw_data["msb"])

output["index"] = lsb

width = (msb - lsb) + 1
assert width >= 1, (msb, lsb, field.name)
output["width"] = width

output["read"] = "read" in field.access
output["write"] = "write" in field.access


def translate_enums(enum: EnumeratedValues) -> dict[str, Any]:
"""Generate an enumeration definition."""

result: dict[str, Any] = {}
enum.handle_description(result)

for name, value in enum.derived_elem.enum.items():
enum_data: dict[str, Any] = {}
value.handle_description(enum_data)

value_str: str = value.raw_data["value"]

prefix = ""
for possible_prefix in ("#", "0b", "0x"):
if value_str.startswith(possible_prefix):
prefix = possible_prefix
break

if prefix in ("#", "0b"):
enum_data["value"] = int(
value_str[len(prefix) :].replace("X", "1"), 2
)
elif prefix == "0x":
enum_data["value"] = int(value_str[len(prefix) :], 16)
else:
enum_data["value"] = int(value_str)

result[name] = enum_data

return result


ENUM_DEFAULTS: dict[str, Any] = {
"unit_test": False,
"json": False,
"use_map": False,
"identifier": False,
}


def process_bit_fields(
register: Register,
output: dict[str, Any],
Expand All @@ -169,25 +102,29 @@ def process_bit_fields(
# Process fields.
for name, field in register.fields.items():
field_data: dict[str, Any] = {"name": name}
bit_field_data(field, field_data)
field_data.update(field.ifgen_data)
result.append(field_data)

# Handle creating an enumeration.
if field.enum is not None:
enum_name = f"{peripheral}_{register.name}_{name}".replace(
"[%s]", ""
)
field_data["type"] = enum_name

# Register enumeration.
new_enum: dict[str, Any] = {"enum": translate_enums(field.enum)}
raw = translate_enums(field.enum)
new_enum: dict[str, Any] = {"enum": raw}
new_enum.update(ENUM_DEFAULTS)

# Increase size of underlying if necessary.
if field_data["width"] > 8:
new_enum["underlying"] = "uint16_t"

enums[enum_name] = new_enum
# Check if enum is unique.
enum_name = get_enum_name(
f"{peripheral}_{register.name}_{name}".replace("[%s]", ""),
peripheral,
raw,
)
field_data["type"] = enum_name
if enum_name not in enums:
enums[enum_name] = new_enum

if result:
output["fields"] = result
Expand Down
Loading

0 comments on commit e254170

Please sign in to comment.