Skip to content

Commit

Permalink
feat: implement HwMode class to represent bitfield hardware modes
Browse files Browse the repository at this point in the history
  • Loading branch information
esynr3z committed Oct 31, 2024
1 parent 164ca10 commit d16e70f
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 6 deletions.
3 changes: 3 additions & 0 deletions corsair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
__description__ = "Control and status register (CSR) map generator for HDL projects."


from .core import HwMode
from .input import (
AnyTarget,
BaseTarget,
Expand Down Expand Up @@ -46,4 +47,6 @@
"ForceNameCase",
"RegisterReset",
"GlobalConfig",
# core
"HwMode",
)
7 changes: 7 additions & 0 deletions corsair/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Internal representation of a register map."""

from __future__ import annotations

from .hwmode import HwMode

__all__ = ("HwMode",)
209 changes: 209 additions & 0 deletions corsair/core/hwmode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"""Enumeration for hardare modes of a bitfield."""

from __future__ import annotations

from enum import Flag
from typing import TYPE_CHECKING, Iterable, Iterator

if TYPE_CHECKING:
from enum import Enum

from typing_extensions import Self


class HwMode(str, Flag):
"""Hardware mode for a bitfield.
Mode reflects hardware possibilities and interfaces to observe and modify bitfield value.
"""

INPUT = "i"
"""Use input value from hardware to update the field."""

I = INPUT
"""Shorthand for `INPUT`."""

OUTPUT = "o"
"""Enable output value from the field to be accessed by hardware."""

O = OUTPUT
"""Shorthand for `OUTPUT`."""

CLEAR = "c"
"""Add signal to clear the field (fill with all zeros)."""

C = CLEAR
"""Shorthand for `CLEAR`."""

SET = "s"
"""Add signal to set the field (fill with all ones)."""

S = SET
"""Shorthand for `SET`."""

ENABLE = "e"
"""Add signal to enable the field to capture input value (must be used with `INPUT`)."""

E = ENABLE

LOCK = "l"
"""Add signal to lock the field (to prevent any changes)."""

L = LOCK
"""Shorthand for `LOCK`."""

ACCESS = "a"
"""Add signals to notify when bus access to the field is performed (at the same cycle)."""

A = ACCESS
"""Shorthand for `ACCESS`."""

QUEUE = "q"
"""Add simple interface to external queue (LIFO, FIFO)."""

Q = QUEUE
"""Shorthand for `QUEUE`."""

FIXED = "f"
"""Enable fixed mode (field is a constant)."""

F = FIXED
"""Shorthand for `FIXED`."""

NA = "n"
"""Not accessible by hardware."""

N = NA
"""Shorthand for `NA`"""

# Override original type hints to match actually used type
value: str # pyright: ignore [reportIncompatibleMethodOverride]
_value_: str # pyright: ignore [reportIncompatibleVariableOverride]

@classmethod
def _missing_(cls, value: object) -> Enum:
"""Return member if one can be found for value, otherwise create a composite member.
Composite member is created only iff value contains only members, else `ValueError` is raised.
Based on `enum.Flag._create_pseudo_member_()` and `enum._decompose()`.
"""
if not isinstance(value, str):
raise TypeError(f"Member value has to be 'str', but {type(value)} is provided")
if value == "":
value = "n" # Empty literal considered as 'n'
value = value.lower()

# Lookup for already created members (all members are singletons)
member = cls._value2member_map_.get(value, None)
if member is not None:
return member

# Create composite member
flags = tuple(cls._split_flags(value)) # Can raise ValueError for unknown flags
composite = str.__new__(cls)
# Name style is the same as in `enum.Flag.__str__`
composite._name_ = "|".join(
[
member._name_
for member in cls # Use iteration to follow order of declaration, rather than provided flags order
if member._value_ in flags and member._name_
]
)
composite._value_ = cls._join_flags(flags)

# Use setdefault in case another thread already created a composite with this value
return cls._value2member_map_.setdefault(value, composite)

@classmethod
def _split_flags(cls, value: str) -> Iterator[str]:
"""Split string into flag values."""
# For legacy reasons there could be a string without separators, where all flags are single chars.
# Code below allows "ioe" and "i|o|e" as well.
raw_flags = set(value.split("|") if "|" in value else value)

# Collect all known flags in order of declaration
self_flags = [member._value_ for member in cls]

# Check that all raw flags are valid values
for flag in raw_flags:
if flag not in self_flags:
raise ValueError(f"Unknown hardware mode {flag!r}")

# Then generate flags in delaration order
for member in cls:
if member._value_ in raw_flags:
yield member._value_

@classmethod
def _join_flags(cls, flags: Iterable[str]) -> str:
"""Concatenate all flag values into single string."""
# For input strings flags without separators are allowed (refer to `_split_flags`),
# but all other representations always use separators.
return "|".join(flags)

def __repr__(self) -> str:
"""Represent flags as a string in the same style as in `enum.Flag.__repr__()`."""
return f"<{self.__class__.__name__}.{self._name_}: {self._value_!r}>"

def __str__(self) -> str:
"""Represent flags as a compact string."""
return self._value_

def __or__(self, other: Self) -> Self:
"""Override `|` operator to combine flags into composite one."""
cls = self.__class__
if not isinstance(other, cls):
raise TypeError(f"Can't OR {type(self)} with {type(other)}")
return cls(cls._join_flags((self._value_, other._value_)))

def __contains__(self, item: object) -> bool:
"""Overload `in` operator to check flag inclusions."""
cls = self.__class__
if isinstance(item, str):
item = cls(item)
if not isinstance(item, cls):
raise TypeError(f"Can't use `in` for {type(self)} with {type(item)}")
self_flags = tuple(cls._split_flags(self._value_))
return all(flag in self_flags for flag in cls._split_flags(item._value_))

def __iter__(self) -> Iterator[Self]:
"""Iterate over combination of flags."""
cls = self.__class__
for flag in cls._split_flags(self._value_):
yield cls(flag)

def __le__(self, other: object) -> bool:
"""Overload `<=` operator to check if current flags are the same or the subset of other."""
cls = self.__class__
if isinstance(other, str):
other = cls(other)
if not isinstance(other, cls):
raise TypeError(f"Can't compare {type(self)} with {type(other)}")
return self in other

def __ge__(self, other: object) -> bool:
"""Overload `>=` operator to check if current flags are the same or the superset of other."""
cls = self.__class__
if isinstance(other, str):
other = cls(other)
if not isinstance(other, cls):
raise TypeError(f"Can't compare {type(self)} with {type(other)}")
return other in self

def __lt__(self, other: object) -> bool:
"""Overload `<` operator to check if current flags are the subset of other."""
cls = self.__class__
if isinstance(other, str):
other = cls(other)
if not isinstance(other, cls):
raise TypeError(f"Can't compare {type(self)} with {type(other)}")
return self._value_ != other._value_ and self.__le__(other)

def __gt__(self, other: object) -> bool:
"""Overload `>` operator to check if current flags are the superset of other."""
cls = self.__class__
if isinstance(other, str):
other = cls(other)
if not isinstance(other, cls):
raise TypeError(f"Can't compare {type(self)} with {type(other)}")
return self._value_ != other._value_ and self.__ge__(other)
12 changes: 6 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,10 @@ ignore = [
"TD003", # missing-todo-link
"FIX", # flake8-fixme
"EM101", # raw-string-in-exception
"EM102", # f-string-in-exception
"FBT", # flake8-boolean-trap
"TRY003", # raise-vanilla-args
"E741", # ambiguous-variable-name
]

[tool.ruff.lint.per-file-ignores]
Expand Down
1 change: 1 addition & 0 deletions tests/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for register map internal representation."""
Loading

0 comments on commit d16e70f

Please sign in to comment.