Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3.1.2 - Heuristics to improve output #53

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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