Source code for hdl_block_design._block

"""Submodule defining blocks within a design."""

from __future__ import annotations

import dataclasses
import enum
from pathlib import Path
from typing import TYPE_CHECKING

from . import vhdl
from ._yamllable import ToFromYAML, ensure_existence_and_type

if TYPE_CHECKING:
    from typing import Any, Self

    from ._yamllable import PathLike, YAMLMapping


class DocEnum(enum.StrEnum):
    def __new__(cls, value: str, doc: str | None = None) -> Self:
        """Create a new string enum."""
        self = str.__new__(cls, value)
        self._value_ = value
        if doc is not None:
            self.__doc__ = doc
        return self


[docs] @enum.unique class Direction(DocEnum): """Class representing the direction of a pin.""" IN = "in", "The pin is an input." OUT = "out", "The pin is an output." INOUT = "inout", "The pin is an input and an output." @classmethod def _missing_(cls, value: Any) -> Self | None: # noqa: ANN401 """Interpret values that don't completely match an enum value.""" if not isinstance(value, str): return None value = value.lower() for member in cls: if member.value == value: return member return None def __repr__(self) -> str: return f"{self.__class__.__name__}.{self.value.upper()}"
@dataclasses.dataclass class Parameter(ToFromYAML): """Class representing a parameter on a block.""" name: str """The name of the parameter.""" type: str """The type of the parameter. This maps to the VHDL type.""" @classmethod def from_dict(cls, dct: YAMLMapping) -> Self: """Create a Parameter from a dictionary.""" name = ensure_existence_and_type(dct, "name", str) t = ensure_existence_and_type(dct, "type", str) return cls(name=name, type=t) def to_dict(self) -> YAMLMapping: """Convert a Parameter to a dictionary.""" return { "name": self.name, "type": self.type, } def to_vhdl_entity_generic_definition( self, prefix: str | None = None, prefix_separator: str = "_", ) -> str: """Return the VHDL entity generic definition for this parameter.""" if prefix is not None and len(prefix) != 0: return f"{prefix}{prefix_separator}{self.name}: {self.type}" return f"{self.name}: {self.type}"
[docs] @dataclasses.dataclass class Pin(ToFromYAML): """Class representing a pin on a block.""" name: str """The name of the pin.""" type: str """The type of the pin. This maps to the VHDL type.""" direction: Direction | str """The direction of the pin.""" def __post_init__(self) -> None: self.direction = Direction(self.direction)
[docs] @classmethod def from_dict(cls, dct: YAMLMapping) -> Self: """Create a Pin from a dictionary.""" name = ensure_existence_and_type(dct, "name", str) t = ensure_existence_and_type(dct, "type", str) direction = ensure_existence_and_type(dct, "direction", str) return cls(name=name, type=t, direction=direction)
[docs] def to_dict(self) -> YAMLMapping: """Convert a Pin to a dictionary.""" return { "name": self.name, "type": self.type, "direction": str(self.direction), }
[docs] def to_vhdl_entity_port_definition( self, prefix: str | None = None, prefix_separator: str = "_", ) -> str: """Return the VHDL entity port definition for this pin.""" if prefix is not None and len(prefix) != 0: return ( f"{prefix}{prefix_separator}{self.name}: {self.direction} {self.type}" ) return f"{self.name}: {self.direction} {self.type}"
[docs] @dataclasses.dataclass class Block(ToFromYAML): """Class representing an available block in a block design.""" name: str """The name of the block.""" parameters: dict[str, Parameter] """The parameters on the block.""" pins: dict[str, Pin] """The pins on the block."""
[docs] @classmethod def from_dict(cls, dct: YAMLMapping) -> Self: """Create a Block from a dictionary.""" name = ensure_existence_and_type(dct, "name", str) parameters = ensure_existence_and_type(dct, "parameters", dict) parameters_as_objects = { k: Parameter.from_dict(ensure_existence_and_type(parameters, k, dict)) for k in parameters } pins = ensure_existence_and_type(dct, "pins", dict) pins_as_objects = { k: Pin.from_dict(ensure_existence_and_type(pins, k, dict)) for k in pins } return cls(name=name, parameters=parameters_as_objects, pins=pins_as_objects)
[docs] @classmethod def from_vhdl_file(cls, path: PathLike) -> Self: """Create a Block from a VHDL file.""" return cls.from_vhdl_string(Path(path).read_bytes())
[docs] @classmethod def from_vhdl_string(cls, s: str | bytes) -> Self: """ Create a Block from a VHDL string or bytes string. Raises ------ RuntimeError If `s` contains 0 or > 1 entity declarations. """ if isinstance(s, str): s = bytes(s, "utf-8") tree = vhdl.PARSER.parse(s) captures = vhdl.ENTITY_NAME_QUERY.captures(tree.root_node) entity_name = [ e.text.decode("utf-8") for e in captures.get("entity.name", []) if e.text is not None ] if len(entity_name) != 1: message = ( f"'{s.decode('utf-8')}' doesn't contain exactly one entity declaration" ) raise RuntimeError(message) captures = vhdl.ENTITY_GENERICS_QUERY.captures(tree.root_node) names = [ n.text.decode("utf-8") for n in captures.get("generic.name", []) if n.text is not None ] types = [ t.text.decode("utf-8") for t in captures.get("generic.type", []) if t.text is not None ] generics = [] for name, generic_type in zip(names, types, strict=True): generics.append({"name": name, "type": generic_type}) parameters = { generic["name"]: Parameter( name=generic["name"], type=generic["type"], ) for generic in generics } captures = vhdl.ENTITY_PORTS_QUERY.captures(tree.root_node) names = [ n.text.decode("utf-8") for n in captures.get("port.name", []) if n.text is not None ] directions = [ d.text.decode("utf-8") for d in captures.get("port.direction", []) if d.text is not None ] types = [ t.text.decode("utf-8") for t in captures.get("port.type", []) if t.text is not None ] ports = [] for name, direction, port_type in zip(names, directions, types, strict=True): ports.append({"name": name, "direction": direction, "type": port_type}) pins = { port["name"]: Pin( name=port["name"], direction=port["direction"], type=port["type"], ) for port in ports } return cls(name=entity_name[0], parameters=parameters, pins=pins)
[docs] def to_dict(self) -> YAMLMapping: """Convert a Block to a dictionary.""" return { "name": self.name, "parameters": {k: v.to_dict() for k, v in self.parameters.items()}, "pins": {k: v.to_dict() for k, v in self.pins.items()}, }