Skip to content

Commit

Permalink
Merge pull request #202 from dan-fritchman/dev
Browse files Browse the repository at this point in the history
#115, #198, Built-in Generators expansion, Pydantic BaseModel citizenship
  • Loading branch information
dan-fritchman authored Oct 25, 2023
2 parents 767115d + f97bf62 commit 9171dcd
Show file tree
Hide file tree
Showing 18 changed files with 388 additions and 373 deletions.
7 changes: 0 additions & 7 deletions hdl21/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@

__version__ = "4.0.0" # NOTE: VLSIR_VERSION


# Before any real importing, ensure we can instantiate non-pydantic types in type-checked dataclasses.
# This `Config` seems to be shared for *all* pydantic types, even when not applied to `BaseModel`.
from pydantic import BaseModel

BaseModel.Config.arbitrary_types_allowed = True

# Internal (python) module aliases, overridden by names such as the `module` decorator function.
from . import module as _module_module
from . import bundle as _bundle_module
Expand Down
57 changes: 42 additions & 15 deletions hdl21/datatype.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,67 @@
```
Notes:
* `@datatype` is designed solely to work *intra-package*.
* Attempts to import and use it after `__init__.py` completes will generally fail.
* Importing this function into other packages is therefore highly discouraged. (Copying it is quite easy though.)
* `@datatype` only works on modules which are imported before `_update_forward_refs()` is run.
* Generally this means modules which are imported as part of `__init__.py`
* `@datatype` is designed solely to work on `pydantic.dataclasses.dataclass`es.
* Notable exceptions include *union types* thereof, which do not have the necessary fields/ methods.
- `@datatype` is designed solely to work *intra-package*.
- Attempts to import and use it after `__init__.py` completes will generally fail.
- Importing this function into other packages is therefore highly discouraged. (Copying it is quite easy though.)
- `@datatype` only works on modules which are imported before `_update_forward_refs()` is run.
- Generally this means modules which are imported as part of `__init__.py`
- `@datatype` is designed solely to work on `pydantic.dataclasses.dataclass`es.
- Notable exceptions include *union types* thereof, which do not have the necessary fields/ methods.
"""

from pydantic import Extra
from pydantic.dataclasses import dataclass
from typing import TypeVar, Type, Any
from typing import TypeVar, Type, Optional


# The list of defined datatypes
datatypes = []

T = TypeVar("T")


class Config: # Pydantic Model Config
allow_extra = Extra.forbid
def _datatype(cls: Type[T], *, config: Optional[Type] = None, **kwargs) -> Type[T]:
"""# Inner implementation of `@datatype`"""


def datatype(cls: Type[T]) -> Type[T]:
"""Register a class as a datatype."""
# Get the default `Config` if none is provided
config = config or OurBaseConfig

# Convert `cls` to a `pydantic.dataclasses.dataclass`,
# and add it to the list of datatypes
cls = dataclass(cls, config=Config)
cls = dataclass(cls, config=config, **kwargs)

# And add it to the list of datatypes
datatypes.append(cls)
return cls


def datatype(cls: Optional[Type[T]] = None, **kwargs) -> Type[T]:
"""Register a class as a datatype."""

# NOTE: the return type here is really, like,
# `Union[Type[T], Callable[[Type[T]], Type[T]]`
# But do ya really want that, or just to know it works as a decorator.

inner = lambda c: _datatype(c, **kwargs)
if cls is None:
return inner # Called with parens, e.g. `@datatype()`
return inner(cls) # Called without parens


def _update_forward_refs():
"""Update all the forward type-references"""
for tp in datatypes:
tp.__pydantic_model__.update_forward_refs()


"""
# Define a few common pydantic model `Config`s
"""


class OurBaseConfig:
allow_extra = Extra.forbid


class AllowArbConfig(OurBaseConfig):
arbitrary_types_allowed = True
8 changes: 3 additions & 5 deletions hdl21/elab/passes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
from dataclasses import field
from typing import Dict, List, Set, Optional, Union

# PyPi Imports
from pydantic.dataclasses import dataclass

# Local imports
from ...datatype import datatype, AllowArbConfig
from ...module import Module
from ...external_module import ExternalModuleCall
from ...instance import _Instance, Instance, InstanceArray, InstanceBundle
Expand All @@ -26,9 +24,9 @@
ElabStackEntry = Union[Module, Instance, InstanceArray, InstanceBundle]


@dataclass
@datatype(config=AllowArbConfig)
class ClassLevelCache:
"""# Class-Level Cache for ElabPasss"""
"""# Class-Level Cache for ElabPasses"""

# Note Modules hash *by identity*, so each instance of `Module`,
# regardless of the similarity of their content, gets its own entry in these sets.
Expand Down
15 changes: 7 additions & 8 deletions hdl21/elab/passes/flatten_bundles.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
from dataclasses import field
from typing import Dict, List, Union, Optional

# PyPi
from pydantic.dataclasses import dataclass

# Local imports
from ...datatype import datatype, AllowArbConfig
from ...module import Module
from ...instance import Instance
from ... import Slice, Concat, NoConn, PortRef
Expand All @@ -27,7 +25,7 @@
from .base import ElabPass


@dataclass(frozen=True)
@datatype(frozen=True)
class Path:
"""
# Hierarchical String-Valued Path
Expand Down Expand Up @@ -60,7 +58,7 @@ def __eq__(self, other: "Path") -> bool:
return self.segs == other.segs


@dataclass
@datatype(config=AllowArbConfig)
class BundleScope:
"""Scope-worth of Signals for a flattened Bundle"""

Expand Down Expand Up @@ -95,7 +93,7 @@ def add_subscope(self, name: str, scope: "BundleScope"):
BundleScope.__pydantic_model__.update_forward_refs()


@dataclass
@datatype(config=AllowArbConfig)
class BundlePortEntry:
"""# Bundle-Port Entry in the Cache
Hashable combination of a Module and a portname."""
Expand All @@ -112,7 +110,7 @@ def __hash__(self) -> int:
return hash((id(self.module), self.portname))


@dataclass
@datatype(config=AllowArbConfig)
class Cache:
"""
The Bundle Flattening Cache
Expand Down Expand Up @@ -169,7 +167,8 @@ def elaborate_module(self, module: Module) -> Module:

def replace_bundle_inst(self, module: Module, bundle_inst: BundleInstance):
"""Replace a `BundleInstance`, flattening its Signals into `module`'s namespace,
and replacing all of its Instance connections with their flattened replacements."""
and replacing all of its Instance connections with their flattened replacements.
"""

# Check we haven't (somehow) already replaced it
if id(bundle_inst) in THE_CACHE.bundle_insts:
Expand Down
2 changes: 2 additions & 0 deletions hdl21/elab/passes/mark_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ def elaborate_module(self, module: Module) -> Module:
# and not just a boolean, in case we want to
# have differences between the two some day.
module._elaborated = module

return module
Loading

0 comments on commit 9171dcd

Please sign in to comment.