Repository: dan-fritchman/Hdl21 (language: python)

Issue:
Sub-Bundle-Valued Connections
Reaching into sub-bundles to make instance connections fails elaboration. 

Minimal unit test: 

```python
def test_sub_bundle_conn():
    """ Test connecting via PortRef to a sub-Bundle """

    @h.bundle
    class B1:
        s = h.Signal()

    @h.bundle
    class B2:
        b1 = B1()

    @h.module
    class HasB1:
        b1 = B1(port=True)

    @h.module
    class HasB2:
        b2 = B2()
        h = HasB1(b1=b2.b1)

    h.elaborate(HasB2)
```

Generates: 

```
self = <hdl21.bundle.Bundle object at 0x7f637ddb2cd0>, key = 'bundle_ports'

    def __getattr__(self, key):
        ns = self.__getattribute__("namespace")
        if key in ns:
            return ns[key]
>       return object.__getattribute__(self, key)
E       AttributeError: 'Bundle' object has no attribute 'bundle_ports'

hdl21/bundle.py:129: AttributeError
```




Discussion / hints:

Fix patch:
diff --git a/hdl21/__init__.py b/hdl21/__init__.py
index ca835cb..2c31136 100644
--- a/hdl21/__init__.py
+++ b/hdl21/__init__.py
@@ -11,10 +11,17 @@
 
 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 instance as _instance_module
+from . import generator as _generator_module
 
 from .params import *
 from .instance import *
 from .signal import *
+from .slice import *
+from .concat import *
+from .noconn import *
 from .module import *
 from .generator import *
 from .generators import *
diff --git a/hdl21/bundle.py b/hdl21/bundle.py
index af69a0e..2b98279 100644
--- a/hdl21/bundle.py
+++ b/hdl21/bundle.py
@@ -5,11 +5,13 @@
 """
 
 from enum import Enum, EnumMeta
-from typing import Optional, Union, Any, get_args, Dict, Set, List
+from typing import Optional, Union, Any, get_args, Dict, Set, List, ClassVar
 
 # Local Imports
 from .attrmagic import init
-from .connect import connectable, track_connected_ports
+from .connect import connectable, is_connectable
+from .sliceable import sliceable
+from .concat import concatable
 from .signal import Signal
 
 
@@ -18,25 +20,12 @@ def getattr_bundle_refs(cls: type) -> type:
     Adds the `_bundle_ref` method and `__getattr__` access to it."""
 
     # First check and fail if any of the methods to be defined here are already defined elsewhere
-    defined_here = ["_bundle_ref", "__getattr__"]
+    defined_here = ["__getattr__"]
     for key in defined_here:
         if key in cls.__dict__:
             msg = f"Invalid modification of {cls} with `@getattr_bundle_refs`: {key} is already defined."
             raise RuntimeError(msg)
 
-    def _bundle_ref(self, key: str) -> "BundleRef":
-        """Return a reference to name `key`, creating it if necessary."""
-
-        # Check in our existing references
-        bundle_refs = self.__getattribute__("refs_to_me")
-        if key in bundle_refs:
-            return bundle_refs[key]
-
-        # New reference; create, add, and return it
-        bundle_ref = BundleRef(parent=self, attrname=key)
-        bundle_refs[key] = bundle_ref
-        return bundle_ref
-
     def __getattr__(self, key: str) -> Any:
         """BundleRef access by getattr"""
         if key.startswith("_") or not self.__getattribute__("_initialized"):
@@ -55,9 +44,8 @@ def __getattr__(self, key: str) -> Any:
 
         # Fell through all those cases. Fancy `BundleRef` generation time!
         # Return a `BundleRef`, creating one if necessary.
-        return self._bundle_ref(key)
+        return _bundle_ref(self, key)
 
-    cls._bundle_ref = _bundle_ref
     cls.__getattr__ = __getattr__
     cls.__getattr_bundle_refs__ = True
 
@@ -70,7 +58,6 @@ def has_getattr_bundle_refs(obj: Any) -> bool:
     return getattr(obj, "__getattr_bundle_refs__", False)
 
 
-@track_connected_ports
 @getattr_bundle_refs
 @connectable
 @init
@@ -87,7 +74,6 @@ class BundleInstance:
         "dest",
         "desc",
         "refs_to_me",
-        "connected_ports",
     ]
 
     def __init__(
@@ -111,7 +97,7 @@ def __init__(
         # References handed out to our children
         self.refs_to_me: Dict[str, "BundleRef"] = dict()
         # Connected port references
-        self.connected_ports: Set["BundleRef"] = set()
+        self._connected_ports: Set["PortRef"] = set()
         self._parent_module: Optional["Module"] = None
         self._elaborated = False
         self._initialized = True
@@ -285,7 +271,6 @@ class Roles(Enum):
     return bundle
 
 
-@track_connected_ports
 @connectable
 class AnonymousBundle:
     """# Anonymous Connection Bundle
@@ -294,70 +279,67 @@ class AnonymousBundle:
 
     def __init__(self, **kwargs: Dict[str, "Connectable"]):
         # Create our internal structures
-        self.signals: Dict[str, "Signal"] = dict()
-
-        # NotImplemented:
-        # self.bundles: Dict[str, "BundleInstance"] = dict()
-        # self.anons: Dict[str, "AnonymousBundle"] = dict()
-
-        # Bundle references to others, stored as members,
-        # e.g. AnonBundle(sig=OtherBundle.sig)
-        self.refs_to_others: Dict[str, "BundleRef"] = dict()
-
+        # The core namespace of Signals, other Bundles, and any other Connectables
+        self._namespace: Dict[str, "Signal"] = dict()
         # Connected port references
-        self.connected_ports: Set["PortRef"] = set()
+        self._connected_ports: Set["PortRef"] = set()
 
         # And add each keyword-arg
         for key, val in kwargs.items():
             self.add(key, val)
 
     def add(self, name: str, val: BundleAttr) -> BundleAttr:
-        """Add attribute `val`."""
+        """Add attribute `val` to our namespace."""
 
-        if isinstance(val, Signal):
-            self.signals[name] = val
-        elif isinstance(val, BundleRef):
-            self.refs_to_others[name] = val
-        elif isinstance(val, (BundleInstance, AnonymousBundle)):
-            raise NotImplementedError(f"Nested Anonymous Bundle")
-        else:
-            msg = f"Invalid Bundle attribute for `{self}`: `{val}`"
-            raise TypeError(msg)
+        if name in self._namespace:
+            raise RuntimeError(f"Duplicate attribute {name} in {self}")
+        if not is_connectable(val):
+            raise TypeError(f"Invalid Bundle attribute {val} for {self}")
+        self._namespace[name] = val
         return val
 
-    def get(self, name: str) -> Optional[Union[Signal, "BundleRef"]]:
+    def get(self, name: str) -> Optional["Connectable"]:
         """Get attribute `name`. Returns `None` if not present.
         Note unlike Python built-ins such as `getattr`, `get` returns solely
         from the HDL namespace-worth of attributes."""
-        if name in self.signals:
-            return self.signals[name]
-        if name in self.refs_to_others:
-            return self.refs_to_others[name]
-        return None
+        return self._namespace.get(name, None)
 
 
-@track_connected_ports
+@getattr_bundle_refs
+@concatable
+@sliceable
 @connectable
+@init
 class BundleRef:
     """Reference into a Bundle Instance"""
 
-    _specialcases = [
+    _specialcases: ClassVar[List[str]] = [
         "parent",
+        "attrname",
         "path",
         "root",
-        "connected_ports",
+        "refs_to_me",
+        "resolved",
     ]
 
     def __init__(
         self,
-        parent: Union["BundleInstance", "BundleRef"],
-        attrname: str,
+        parent: Union["BundleInstance", "BundleRef"],  # Parent Bundle
+        attrname: str,  # Attribute name
     ):
         self.parent = parent
         self.attrname = attrname
-        # Connected port references
-        self.connected_ports: Set["PortRef"] = set()
+
+        self.resolved: Union[None, "Signal", "BundleInstance"] = None
+        # References handed out to our children
+        self.refs_to_me: Dict[str, "BundleRef"] = dict()
+        self._width: Optional[int] = None  # FIXME: remove?
+        self._slices: Set["Slice"] = set()
+        self._concats: Set["Concat"] = set()
+        self._connected_ports: Set["PortRef"] = set()
+
         self._elaborated = False
+        self._initialized = True
 
     def __eq__(self, other) -> bool:
         """Port-reference equality requires *identity* between parents
@@ -386,3 +368,17 @@ def root(self) -> "BundleInstance":
 
     def __repr__(self):
         return f"{self.__class__.__name__}(root={self.root()} path={self.path()})"
+
+
+def _bundle_ref(self: Union[BundleRef, BundleInstance], key: str) -> BundleRef:
+    """Return a reference to name `key`, creating it if necessary."""
+
+    # Check in our existing references
+    bundle_refs = self.__getattribute__("refs_to_me")
+    if key in bundle_refs:
+        return bundle_refs[key]
+
+    # New reference; create, add, and return it
+    bundle_ref = BundleRef(parent=self, attrname=key)
+    bundle_refs[key] = bundle_ref
+    return bundle_ref
diff --git a/hdl21/call.py b/hdl21/call.py
new file mode 100644
index 0000000..3634107
--- /dev/null
+++ b/hdl21/call.py
@@ -0,0 +1,62 @@
+"""
+# Param-Class Argument Calls
+
+Enables the "no paramclass constructor" style invocation for Generators, ExternalModules, and Primitives.
+Creates an instance of the generator's parameter-class if keyword args were provided instead.
+If both were provided, fail.
+"""
+
+from typing import Union, Any, Dict
+from pydantic import ValidationError
+
+# Local Imports
+from .default import Default
+
+
+# Union of types designed to support `param_call`,
+# which more specifically must have a `Params` property, which is a type.
+Callee = Union["Generator", "ExternalModule", "Primitive"]
+
+
+def param_call(callee: Callee, arg: Any = Default, **kwargs: Dict) -> "Callee.Params":
+    """
+    # Param-Class Argument Calls
+
+    Enables the "no paramclass constructor" style invocation for Generators, ExternalModules, and Primitives.
+    Creates an instance of the generator's parameter-class if keyword args were provided instead.
+    If both were provided, fail.
+    """
+
+    if kwargs and arg is not Default:
+        msg = f"Invalid call to {callee}: either provide a single {callee.Params} instance or keyword arguments, not both."
+        raise RuntimeError(msg)
+
+    if arg is not Default:
+        # Already constructed. Return as-is.
+        # Note type-checking for instances of `callee.Params` *is not* done here.
+        return arg
+
+    try:
+        # Try to create and return an instance of the parameter-class
+        # As indicated by the `Params` property of the callee.
+        return callee.Params(**kwargs)
+
+    except (TypeError, ValidationError) as e:
+        # Type validation failed; add some of our info to the error message.
+
+        # A very common case is forgetting to apply parameters, and going straight to connections. 
+        # Check for this, and provide a specific error message when it happens.
+        from .connect import is_connectable
+
+        connectables = [key for key, val in kwargs.items() if is_connectable(val)]
+        if connectables:
+            msg = f"Invalid call to {callee} with connectables: ({', '.join(connectables)}). \n"
+            msg += f"Did you forget to pass this its `params` ({callee.Params.__name__}) first?"
+            raise RuntimeError(msg)
+
+        # FIXME: add `SourceInfo`
+        msg = f"Invalid call to `{callee}` with arguments `{kwargs}`: \n\n{e}"
+        raise RuntimeError(msg)
+
+    except:  # Any other Exception, raise along
+        raise
diff --git a/hdl21/concat.py b/hdl21/concat.py
new file mode 100644
index 0000000..4368cc9
--- /dev/null
+++ b/hdl21/concat.py
@@ -0,0 +1,59 @@
+"""
+# Hdl21 Concatenations 
+"Sequences" of Signals and other Connectable objects. 
+"""
+
+from typing import Optional, Set
+
+# Local imports
+from .connect import connectable
+from .sliceable import sliceable
+from .concatable import concatable, is_concatable
+
+
+@sliceable
+@concatable
+@connectable
+class Concat:
+    """Signal Concatenation
+    Uses *Python-convention* ordering, in which "LSBs", i.e. index 0, are specified first."""
+
+    def __init__(self, *parts):
+        invalid_parts = [p for p in parts if not is_concatable(p)]
+        if invalid_parts:
+            raise TypeError(f"Invalid `Concat` of {invalid_parts}")
+
+        for part in parts:
+            part._concats.add(self)
+
+        # Store the `parts` to a tuple
+        self.parts = tuple(parts)
+
+        # Inner management data
+        self._connected_ports: Set["PortRef"] = set()
+        self._width: Optional[int] = None
+        self._slices: Set["Slice"] = set()
+        self._concats: Set["Concat"] = set()
+
+    def __eq__(self, other) -> bool:
+        # Identity is equality
+        return id(self) == id(other)
+
+    def __hash__(self) -> bool:
+        # Identity is equality
+        return hash(id(self))
+
+    def __repr__(self):
+        return f"Concat(parts={len(self.parts)})"
+
+    @property
+    def width(self):
+        # FIXME: whether to keep me around as a property
+        # As is the elaboration mechanics do not use this property, but users may.
+        from .elab.elaborators.width import width
+
+        return width(self)
+
+
+__all__ = ["Concat"]
+
diff --git a/hdl21/concatable.py b/hdl21/concatable.py
new file mode 100644
index 0000000..dc26ff8
--- /dev/null
+++ b/hdl21/concatable.py
@@ -0,0 +1,22 @@
+"""
+# Concatenation-ability Decorator 
+
+Solely a marker used by `Concat` for validity checking. 
+"""
+
+# Local imports
+from .connect import is_connectable
+
+
+def concatable(cls: type) -> type:
+    """Decorator for `Concat`-compatible types."""
+    if not is_connectable(cls):
+        raise TypeError(f"{cls} is not connectable")
+    # Just adds a "marker trait" to the class
+    cls.__concatable__ = True
+    return cls
+
+
+def is_concatable(obj: object) -> type:
+    return getattr(obj, "__concatable__", False)
+
diff --git a/hdl21/connect.py b/hdl21/connect.py
index a77b3a4..2f9c93a 100644
--- a/hdl21/connect.py
+++ b/hdl21/connect.py
@@ -1,26 +1,11 @@
 """
-# hdl21 Connection Helpers 
+# Hdl21 Connectable Decorator & Type Union
 
-Decorators which add a number of connection-related facilities onto classes to which they are applied. 
-Several such decorators are defined here, and keeping them straight can be confusing. 
-From the most basic to most elaborate: 
-
-* `connectable` - think `Signal`. 
-  * Indicates that a type can serve as the *value* in a connections-dict. 
-  * No added functionality, just an annotation, checked by other methods here. 
-* `track_connected_ports`
-  * A `connectable` type which also tracks `PortRef`s it connects to. 
-  * Again, think `Signal` 
-* `getattr_port_refs`
-  * Adds "generate `PortRef`s via getattr" functionality. 
-* `call_and_setattr_connects` - think `Instance`. 
-  * The most elaborate of the bunch. 
-  * For types with a connections-dict. 
-  * Adds "connect by call" and "connect by setattr" functionality. 
+Applied to types that are "used as connections", e.g. Signal. 
 """
 
 # Std-Lib Imports
-from typing import Any, Union, Dict
+from typing import Any, Union
 
 
 def connectable(cls: type) -> type:
@@ -34,201 +19,16 @@ def is_connectable(obj: Any) -> bool:
     return getattr(obj, "__connectable__", False)
 
 
-Connectable = Union["Signal", "PortRef", "BundleInstance", "AnonymousBundle"]
-
-
-def track_connected_ports(cls: type) -> type:
-    """Add an annotation indicating `cls` will track `connected_ports`,
-    on a `List[PortRef]` valued field by that name."""
-    if not is_connectable(cls):
-        raise RuntimeError(f"Must be `@connectable`")
-    cls.__track_connected_ports__ = True
-    return cls
-
-
-def does_track_connected_ports(obj: Any) -> bool:
-    """Boolean indication of connected-port tracking"""
-    return getattr(obj, "__track_connected_ports__", False)
-
-
-def call_and_setattr_connects(cls: type) -> type:
-    """Decorator to add 'connect by call' and 'connect by setattr' semantics.
-    Applied to hdl21 internal types such as `Instance`, `InstArray` and `BundleInstance`.
-
-    `call_and_setattr_connects` classes have a few more subtle requirements, including that they
-    indicate when their constructors complete via an `_initialized` field,
-    include a `conns` connections-dict, and a `portrefs` dictionary of past port-references."""
-
-    if not has_getattr_port_refs(cls):
-        raise RuntimeError(f"Must be `@getattr_port_refs`")
-
-    # First check and fail if any of the methods to be defined here are already defined elsewhere
-    defined_here = ["__call__", "__setattr__", "connect", "disconnect"]
-    for key in defined_here:
-        if key in cls.__dict__:
-            msg = f"Invalid modification of {cls} with `@call_and_setattr_connects`: {key} is already defined."
-            raise RuntimeError(msg)
-
-    def __call__(self, **kwargs) -> "Self":
-        """Connect-by-call"""
-        for key, val in kwargs.items():
-            self.connect(key, val)
-        # Don't forget to retain ourselves at the call-site!
-        return self
-
-    def __setattr__(self, key: str, val: Any) -> None:
-        """Connect-by-setattr"""
-        if not getattr(self, "_initialized", False) or key.startswith("_"):
-            # Bootstrapping phase: do regular setattrs to get started
-            return object.__setattr__(self, key, val)
-        if key in self.__getattribute__("_specialcases"):  # Special case(s)
-            return object.__setattr__(self, key, val)
-        _ = self.connect(key, val)  # Discard the returned `self`
-        return None
-
-    def connect(self, portname: str, conn: Connectable) -> "Self":
-        """Connect `conn` to port (name) `portname`.
-        Called by both by-call and by-assignment convenience methods, and usable directly.
-        Direct calls to `connect` will generally be required for ports with otherwise illegal names,
-        e.g. Python language keywords (`in`, `from`, etc.),
-        or Hdl21 internal "keywords" (`name`, `ports`, `signals`, etc.).
-        Returns `self` to aid in method-chaining use-cases."""
-        from .bundle import AnonymousBundle
-        from .portref import PortRef
-
-        if isinstance(conn, Dict):
-            # Special-case dictionaries of connectables into Anon Bundles
-            conn = AnonymousBundle(**conn)
-        if not is_connectable(conn):
-            raise TypeError(f"{self} attempting to connect non-connectable {conn}")
-
-        # The main event: actually stick `conn` in the `conns` dict
-        if portname in self.conns:
-            # Replace and disconnect any prior connection
-            self.replace(portname, conn)
-        else:
-            self.conns[portname] = conn
-            # If `conn` tracks them, add a reference back to us
-            if does_track_connected_ports(conn):
-                conn.connected_ports.add(PortRef(self, portname))
-
-        # And return `self` to aid in method-chaining use-cases
-        return self
-
-    def disconnect(self, portname: str) -> Connectable:
-        """Disconnect the port named `portname`.
-        Returns the formerly-connected `Connectable`.
-        Raises a KeyError if the port is not connected."""
-        from .portref import PortRef
-
-        conn = self.conns.pop(portname)
-        if does_track_connected_ports(conn):
-            conn.connected_ports.remove(PortRef(self, portname))
-        return conn
-
-    def replace(self, portname: str, conn: Connectable) -> Connectable:
-        """
-        Replace the connection to `portname` with `conn`.
-        Returns the formerly-connected `Connectable`.
-        Raises a KeyError if the port is not connected.
-
-        The `replace` method is functionally identical to serial calls to `disconnect` and `connect`,
-        but allows for in-place modification of the `conns` dict, e.g. while iterating over its items.
-        """
-        from .portref import PortRef
-
-        # Get a reference to the old connection in the `conns` dict, without removing it
-        old = self.conns[portname]
-        if does_track_connected_ports(old):
-            old.connected_ports.remove(PortRef(self, portname))
-
-        # And replace it in the `conns` dict
-        self.conns[portname] = conn
-        # If `conn` tracks them, add a reference back to us
-        if does_track_connected_ports(conn):
-            conn.connected_ports.add(PortRef(self, portname))
-
-        return old
-
-    # Attach all of these to the class
-    cls.__call__ = __call__
-    cls.__setattr__ = __setattr__
-    cls.connect = connect
-    cls.replace = replace
-    cls.disconnect = disconnect
-    cls.__call_and_setattr_connects__ = True
-
-    # And don't forget to return it!
-    return cls
-
-
-def does_call_and_setattr_connect(obj: Any) -> bool:
-    """Boolean indication of "call and setattr connects" functionality"""
-    return getattr(obj, "__call_and_setattr_connects__", False)
-
-
-def getattr_port_refs(cls: type) -> type:
-    """Decorator to add the "__getattr__ generates PortRefs" functionality to `cls`.
-
-    Adds the `_port_ref` method and `__getattr__` access to it.
-
-    This method is *required* of `@call_and_setattr_connects` types (e.g. `Instance`s),
-    and is also included on `PortRef`s themselves, largely to support nested Bundled references."""
-
-    # First check and fail if any of the methods to be defined here are already defined elsewhere
-    defined_here = ["_port_ref", "__getattr__"]
-    for key in defined_here:
-        if key in cls.__dict__:
-            msg = f"Invalid modification of {cls} with `@getattr_port_refs`: {key} is already defined."
-            raise RuntimeError(msg)
-
-    def _port_ref(self, key: str) -> "PortRef":
-        """Return a port-reference to name `key`, creating it if necessary."""
-        from .portref import PortRef
-
-        # Check in our existing port-references
-        port_refs = self.__getattribute__("portrefs")
-        if key in port_refs:
-            return port_refs[key]
-
-        # New reference; create, add, and return it
-        port_ref = PortRef(inst=self, portname=key)
-        port_refs[key] = port_ref
-        return port_ref
-
-    def __getattr__(self, key: str) -> Any:
-        """Port access by getattr"""
-        if not self.__getattribute__("_initialized") or key.startswith("_"):
-            # Bootstrapping phase: do regular getattrs to get started
-            return object.__getattribute__(self, key)
-
-        if key in self.__getattribute__("_specialcases"):  # Special case(s)
-            return object.__getattribute__(self, key)
-
-        # After elaboration, the fancy PortRef creation below goes away. Only return ready-made connections.
-        if self.__getattribute__("_elaborated"):
-            # Types without Instance-style connections return "regular getattr"
-            if not does_call_and_setattr_connect(self):
-                return object.__getattribute__(self, key)
-
-            # Types with Instance-style connections pull from their `conns` dict.
-            conns = self.__getattribute__("conns")
-            if key in conns.keys():
-                return conns[key]
-            raise AttributeError(f"No attribute {key} for {self}")
-
-        # Fell through all those cases. Fancy `PortRef` generation time!
-        # Return a `PortRef`, creating one if necessary.
-        return self._port_ref(key)
-
-    cls._port_ref = _port_ref
-    cls.__getattr__ = __getattr__
-    cls.__getattr_port_refs__ = True
-
-    # And don't forget to return it!
-    return cls
-
+# Union of types using `connectable`
+# For checking, `is_connectable` is preferable, but this serves as a handy shorthand for many type annotations.
+Connectable = Union[
+    "Signal",
+    "Slice",
+    "Concat",
+    "NoConn",
+    "PortRef",
+    "BundleInstance",
+    "AnonymousBundle",
+    "BundleRef",
+]
 
-def has_getattr_port_refs(obj: Any) -> bool:
-    """Boolean indication of "getattr port refs" functionality"""
-    return getattr(obj, "__getattr_port_refs__", False)
diff --git a/hdl21/default.py b/hdl21/default.py
new file mode 100644
index 0000000..c399081
--- /dev/null
+++ b/hdl21/default.py
@@ -0,0 +1,24 @@
+"""
+# Default Value Sentinel 
+
+Used by a number of Hdl21 types to indicate a default value, 
+where all other "public" values could be valid non-default data. 
+"""
+
+from .datatype import datatype
+
+
+@datatype
+class Default:
+    """
+    # Default Value Sentinel
+
+    Used by a number of Hdl21 types to indicate a default value,
+    where all other "public" values could be valid non-default data.
+    """
+
+    ...  # Empty contents
+
+    def __new__(cls, *args, **kwargs):
+        # And we're a singleton. Call away, you get the same class-object back.
+        return Default
diff --git a/hdl21/elab/elab.py b/hdl21/elab/elab.py
index dd3f176..99e5349 100644
--- a/hdl21/elab/elab.py
+++ b/hdl21/elab/elab.py
@@ -37,18 +37,13 @@ def elaborate(
     passes = passes or ElabPass.default()
 
     # Check whether we are elaborating a single object or a list thereof
-    tops = top if isinstance(top, List) else [top]
+    tops: List[Elaboratable] = top if isinstance(top, List) else [top]
 
-    def run_passes(t: Elaboratable):
-        # Pass `t` through each of our passes, in order
-        for elabpass in passes:
-            t = elabpass.elaborate(top=t, ctx=ctx)
-        return t
-
-    # Pass each of our top-level objects through the passes
-    rv = list(map(run_passes, tops))
+    # Pass `tops` through each of our passes, in order
+    for elabpass in passes:
+        tops = elabpass.elaborate(tops=tops, ctx=ctx)
 
     # Extract the single-element case
     if not isinstance(top, List):
-        return rv[0]
-    return rv
+        return tops[0]
+    return tops
diff --git a/hdl21/elab/elaborators/arrays.py b/hdl21/elab/elaborators/arrays.py
index 86003cb..3374d3d 100644
--- a/hdl21/elab/elaborators/arrays.py
+++ b/hdl21/elab/elaborators/arrays.py
@@ -7,7 +7,9 @@
 from ...instance import Instance
 from ...portref import PortRef
 from ...bundle import BundleInstance
-from ...signal import Signal, Slice, Concat
+from ...signal import Signal
+from ...slice import Slice
+from ...concat import Concat
 
 # Import the base class
 from .base import Elaborator
@@ -15,7 +17,7 @@
 
 class ArrayFlattener(Elaborator):
     """
-    Elaboration Pass to Flatten `InstArray`s into `Instance`s, broadcast and remake their connections.
+    Elaboration Pass to Flatten `InstanceArray`s into `Instance`s, broadcast and remake their connections.
     """
 
     def elaborate_module(self, module: Module) -> Module:
@@ -27,12 +29,14 @@ def elaborate_module(self, module: Module) -> Module:
             name, array = module.instarrays.popitem()
             self.stack.append(array)
             module.namespace.pop(name)
+            
             # Visit the array's target
-            target = self.elaborate_instance_array(array)
+            target = self.elaborate_instance_base(array)
 
             # And do the real work: flattening it.
             if array.n < 1:
-                self.fail(f"Invalid InstArray {array} with size {array.n}")
+                self.fail(f"Invalid InstanceArray {array} with size {array.n}")
+
             # Create the new, flat Instances
             new_insts = []
             for k in range(array.n):
@@ -41,6 +45,7 @@ def elaborate_module(self, module: Module) -> Module:
                 )
                 inst = module.add(Instance(of=target, name=name))
                 new_insts.append(inst)
+            
             # And connect them
             for portname, conn in array.conns.items():
                 if isinstance(conn, BundleInstance):
@@ -51,10 +56,10 @@ def elaborate_module(self, module: Module) -> Module:
                     # Get the target-module port, particularly for its width
                     port = target.ports.get(portname, None)
                     if port is None:
-                        msg = f"Connection to invalid Port `{portname}` on InstArray `{array}` in Module `{module.name}`"
+                        msg = f"Connection to invalid Port `{portname}` on InstanceArray `{array}` in Module `{module.name}`"
                         self.fail(msg)
                     if not isinstance(port, Signal):
-                        msg = f"Invalid Port `{portname}` ({port}) on InstArray `{array}` in Module `{module.name}`"
+                        msg = f"Invalid Port `{portname}` ({port}) on InstanceArray `{array}` in Module `{module.name}`"
                         self.fail(msg)
 
                     if port.width == conn.width:
@@ -78,7 +83,7 @@ def elaborate_module(self, module: Module) -> Module:
                     msg += f"Connection {conn} has not been resolved to a `Signal`. "
                     raise RuntimeError
                 else:
-                    msg = f"Invalid connection to {conn} in InstArray {array}"
+                    msg = f"Invalid connection to {conn} in InstanceArray {array}"
                     self.fail(msg)
             self.stack.pop()
 
diff --git a/hdl21/elab/elaborators/base.py b/hdl21/elab/elaborators/base.py
index d26be1d..08d68f2 100644
--- a/hdl21/elab/elaborators/base.py
+++ b/hdl21/elab/elaborators/base.py
@@ -10,7 +10,7 @@
 
 # Local imports
 from ...module import Module, ExternalModuleCall
-from ...instance import InstArray, Instance, InstanceBundle
+from ...instance import _Instance, Instance, InstanceArray, InstanceBundle
 from ...primitives import PrimitiveCall
 from ...bundle import BundleInstance
 from ...generator import GeneratorCall
@@ -21,7 +21,7 @@
 
 
 # Union of entry-types in the elaboration stack
-ElabStackEntry = Union[GeneratorCall, Module, Instance, InstArray, InstanceBundle]
+ElabStackEntry = Union[GeneratorCall, Module, Instance, InstanceArray, InstanceBundle]
 
 
 class Elaborator:
@@ -43,21 +43,29 @@ class Elaborator:
     """
 
     @classmethod
-    def elaborate(cls, top: Elaboratable, ctx: Context) -> Module:
-        """Elaboration entry-point. Elaborate the top-level object."""
-        return cls(top, ctx).elaborate_top()
+    def elaborate(cls, tops: List[Elaboratable], ctx: Context) -> List[Elaboratable]:
+        """Elaboration entry-point. Elaborate the top-level objects."""
+        return cls(tops, ctx).elaborate_tops()
 
-    def __init__(self, top: Elaboratable, ctx: Context):
-        self.top = top
+    def __init__(self, tops: List[Elaboratable], ctx: Context):
+        self.tops = tops
         self.ctx = ctx
         self.stack: List[ElabStackEntry] = list()
         self.modules: Dict[int, Module] = dict()
 
-    def elaborate_top(self) -> Module:
-        """Elaborate our top node"""
-        if not isinstance(self.top, Module):
-            self.fail(f"Invalid Top for Elaboration: {self.top} must be a Module")
-        return self.elaborate_module_base(self.top)  # Note `_base` here!
+    def elaborate_tops(self) -> List[Elaboratable]:
+        """Elaborate our top nodes"""
+        if not isinstance(self.tops, List):
+            self.fail(f"Invalid Top for Elaboration: {self.tops} must be a list")
+
+        # The base-class Elaborator, and most sub-classes, return the `self.tops` list unmodified,
+        # while modifiying its elements inline.
+        # This differs from the `Generator` elaborator, which returns a new list,
+        # generally converting `GeneratorCall`s into `Module`s.
+
+        for t in self.tops:
+            self.elaborate_module_base(t)  # Note `_base` here!
+        return self.tops
 
     def elaborate_generator_call(self, call: GeneratorCall) -> Module:
         """Elaborate a GeneratorCall"""
@@ -91,9 +99,13 @@ def elaborate_module_base(self, module: Module) -> Module:
 
         # Depth-first traverse instances, ensuring their targets are defined
         for inst in module.instances.values():
-            self.elaborate_instance(inst)
+            self.elaborate_instance_base(inst)
         for arr in module.instarrays.values():
-            self.elaborate_instance_array(arr)
+            self.elaborate_instance_base(arr)
+        for instbundle in module.instbundles.values():
+            self.elaborate_instance_base(instbundle)
+
+        # Traverse Bundle instances
         for bundle in module.bundles.values():
             self.elaborate_bundle_instance(bundle)
 
@@ -123,20 +135,8 @@ def elaborate_bundle_instance(self, inst: BundleInstance) -> BundleInstance:
         inst._elaborated = True
         return inst
 
-    def elaborate_instance_array(self, array: InstArray) -> Instantiable:
-        """Elaborate an InstArray"""
-        self.stack.append(array)
-        # Turn off `PortRef` magic
-        array._elaborated = True
-        # And visit the Instance's target
-        rv = self.elaborate_instantiable(array._resolved)
-        self.stack.pop()
-        return rv
-
-    def elaborate_instance(self, inst: Instance) -> Instantiable:
-        """Elaborate a Module Instance."""
-        # This version of `elaborate_instantiable` is the "post-generators" version used by *most* passes.
-        # The Generator-elaborator is different, and overrides it.
+    def elaborate_instance_base(self, inst: _Instance) -> Instantiable:
+        """Elaborate a Module Instance, Array or Bundle thereof."""
 
         self.stack.append(inst)
         # Turn off `PortRef` magic
diff --git a/hdl21/elab/elaborators/conntypes.py b/hdl21/elab/elaborators/conntypes.py
index 3857f85..b67ac73 100644
--- a/hdl21/elab/elaborators/conntypes.py
+++ b/hdl21/elab/elaborators/conntypes.py
@@ -8,15 +8,17 @@
 
 # Local imports
 from ...connect import is_connectable, Connectable
+from ...portref import PortRef
 from ...module import Module
-from ...instance import InstArray, Instance
-from ...signal import Sliceable, Signal
+from ...instance import InstanceArray, Instance
+from ...noconn import NoConn
 from ...bundle import (
     AnonymousBundle,
     BundleInstance,
     BundleRef,
     Bundle,
 )
+from .width import HasWidth
 
 # Import the base class
 from .base import Elaborator
@@ -50,7 +52,9 @@ def elaborate_module(self, module: Module) -> Module:
         # No errors means it checked out, return the Module unchanged
         return module
 
-    def check_instance(self, module: Module, inst: Union[Instance, InstArray]) -> None:
+    def check_instance(
+        self, module: Module, inst: Union[Instance, InstanceArray]
+    ) -> None:
         """Check the connections of `inst` in parent `module`"""
         self.stack.append(inst)
 
@@ -134,13 +138,13 @@ def assert_bundles_compatible(
         msg = f"Invalid connection-compatibility check between {bundle} and {other}"
         self.fail(msg)
 
-    def assert_signals_compatible(self, sig: Sliceable, other: Any) -> None:
-        """Assert that `Sliceable`s (generally `Signal`s) a and b are compatible for connection."""
+    def assert_signals_compatible(self, sig: HasWidth, other: Any) -> None:
+        """Assert that `HasWidth`s (generally `Signal`s) a and b are compatible for connection."""
 
-        if not isinstance(other, get_args(Sliceable)):
+        if not isinstance(other, get_args(HasWidth)):
             self.fail(f"Invalid connection to non-Signal {other}")
 
-        if sig.width != other.width:
+        if self.get_width(sig) != self.get_width(other):
             signame = getattr(sig, "name", f"Anonymous(width={sig.width})")
             othername = getattr(other, "name", f"Anonymous(width={other.width})")
             msg = f"Signals `{signame}` and `{othername}` width mismatch: {sig.width} != {other.width}"
@@ -156,9 +160,12 @@ def assert_compatible(self, port: Connectable, conn: Connectable) -> None:
 
         if isinstance(conn, BundleRef):
             # Recursively call this function on the ref's resolved value
-            return self.assert_compatible(port, self.resolve_bundleref_type(conn))
+            from .resolve_ref_types import resolve_bundleref_type
 
-        if isinstance(port, get_args(Sliceable)):
+            referent = resolve_bundleref_type(conn, self.fail)
+            return self.assert_compatible(port, referent)
+
+        if isinstance(port, get_args(HasWidth)):
             return self.assert_signals_compatible(port, conn)
 
         if isinstance(port, BundleInstance):
@@ -166,29 +173,12 @@ def assert_compatible(self, port: Connectable, conn: Connectable) -> None:
 
         self.fail(f"Invalid Port {port}")
 
-    def resolve_bundleref_type(self, bref: BundleRef) -> Union[Signal, BundleInstance]:
-        """
-        Resolve a bundle-reference to either a `Signal` or sub-`Bundle` Instance.
+    def get_width(self, conn: Connectable) -> int:
+        """ Get the `width` of a conn. Fails for types which this pass is not designed to handle. """
+        from .width import width
 
-        NOTE this returns a *representative* signal or bundle instance,
-        i.e. one with the correct type and width - not *the signal* for a given instance.
-        In other words: this is fine for connection-validity checking,
-        but *not* for copying signals during flattening.
-        Hence the "type" name suffix, although what we return is not really a type.
-        """
+        if isinstance(conn, (NoConn, PortRef)):
+            msg = f"Internal error: {type(conn).__name__} remaining in connection-types check"
+            return self.fail(msg)
 
-        if isinstance(bref.parent, BundleInstance):  # Parent is a BundleInstance.
-            parent = bref.parent
-        elif isinstance(
-            bref.parent, BundleRef
-        ):  # Nested reference. Recursively resolve the parent.
-            parent = self.resolve_bundleref_type(bref.parent)
-        else:
-            self.fail(f"Invalid BundleRef parent for {bref}")
-
-        # Get the attribute from the parent namespace, or fail if not available.
-        attr = parent.of.get(bref.attrname)
-        if attr is None:
-            msg = f"Bundle `{bref.parent.of.name}` has no attribute `{bref.attrname}`"
-            self.fail(msg)
-        return attr
+        return width(conn, failer=self.fail)
diff --git a/hdl21/elab/elaborators/flatten_bundles.py b/hdl21/elab/elaborators/flatten_bundles.py
index c1902fb..510329b 100644
--- a/hdl21/elab/elaborators/flatten_bundles.py
+++ b/hdl21/elab/elaborators/flatten_bundles.py
@@ -4,7 +4,7 @@
 
 # Std-Lib Imports
 import copy
-from typing import Dict, List, Tuple, Union
+from typing import Dict, List, Tuple, Union, Optional
 
 # PyPi
 from pydantic.dataclasses import dataclass
@@ -12,6 +12,7 @@
 # Local imports
 from ...module import Module
 from ...instance import Instance
+from ... import Slice, Concat, NoConn, PortRef
 from ...bundle import (
     AnonymousBundle,
     Bundle,
@@ -19,6 +20,7 @@
     BundleRef,
 )
 from ...signal import PortDir, Signal, Visibility
+from .resolve_ref_types import update_ref_deps
 
 # Import the base class
 from .base import Elaborator
@@ -69,12 +71,36 @@ def from_list(cls, ls: List[str]) -> "PathStr":
         return cls.from_path(Path(ls))
 
 
+@dataclass
 class BundleScope:
     """Scope-worth of Signals for a flattened Bundle"""
 
     path: Path
     pathstr: PathStr
-    signals: Dict[PathStr, List[Signal]]
+    # Flattened signals-dict, keyed by Path
+    # Includes *all* Signals in *all* sub-scopes.
+    signals: Dict[PathStr, Signal]
+    # Hierarchical scopes
+    # All Signals inside sub-scopes are references to elements in `signals`.
+    scopes: Dict[PathStr, "BundleScope"]
+
+    @classmethod
+    def root(
+        cls, signals: Optional[dict] = None, scopes: Optional[dict] = None
+    ) -> "BundleScope":
+        """ Create a new root scope """
+        signals = signals or {}
+        scopes = scopes or {}
+        return cls(path=Path([]), pathstr=PathStr(""), signals=signals, scopes=scopes,)
+
+    def add_subscope(self, name: str, scope: "BundleScope"):
+        """ Add a sub `BundleScope`. Also add its signals to our parent dict. """
+        self.scopes[PathStr(name)] = scope
+        for k, v in scope.signals.items():
+            self.signals[PathStr.concat(name, k)] = v
+
+
+BundleScope.__pydantic_model__.update_forward_refs()
 
 
 @dataclass
@@ -84,7 +110,10 @@ class FlatBundleDef:
     # Source/ Original Bundle
     src: Bundle
     # Flattened signals-dict, keyed by Path
+    # Includes *all* Signals in *all* sub-scopes.
     signals: Dict[PathStr, Signal]
+    # Hierarchical scopes of flattened internal Bundles
+    scopes: Dict[PathStr, BundleScope]
 
 
 @dataclass
@@ -93,8 +122,33 @@ class FlatBundleInst:
 
     # Source/ Original Bundle
     src: BundleInstance
-    # Flattened signals-dict, keyed by Path
-    signals: Dict[PathStr, Signal]
+    # Root `BundleScope`
+    root_scope: BundleScope
+
+    @property
+    def signals(self):
+        return self.root_scope.signals
+
+
+def get(
+    flat: Union[FlatBundleDef, FlatBundleInst], path: Path
+) -> Union[Signal, BundleScope]:
+    if isinstance(flat, FlatBundleDef):
+        ns = flat
+    elif isinstance(flat, FlatBundleInst):
+        ns = flat.root_scope
+    else:
+        raise TypeError
+
+    for seg in path.segs:
+        seg = PathStr(seg)
+        if seg in ns.signals:
+            ns = ns.signals[seg]
+        elif seg in ns.scopes:
+            ns = ns.scopes[seg]
+        else:
+            raise ValueError(f"{flat} has no attribute {seg} in {ns}")
+    return ns
 
 
 class BundleFlattener(Elaborator):
@@ -110,10 +164,12 @@ def __init__(self, *args, **kwargs):
         self.bundle_defs: Dict[int, FlatBundleDef] = dict()
         # BundleInstance replacements, id(BundleInst) => FlatBundleInst
         self.bundle_insts: Dict[int, FlatBundleInst] = dict()
-        # AnonymousBundle replacements, id(AnonBundle) => Tbd
-        self.anon_bundles: Dict[int, "Tbd"] = dict()
-        # Replacement-values by Module and attribute-name. (id(Module), attrname) => FlatBundleInst
-        self.module_attrs: Dict[Tuple[int, str], FlatBundleInst] = dict()
+        # Replacement flattened Bundle-valued ports.
+        # Keyed by Module and attribute-name.
+        # (id(Module), attrname) => FlatBundleInst
+        self.flat_bundle_ports: Dict[Tuple[int, str], FlatBundleInst] = dict()
+        # AnonymousBundle replacements, id(AnonBundle) => BundleScope
+        self.anon_bundles: Dict[int, BundleScope] = dict()
 
     def elaborate_module(self, module: Module) -> Module:
         """Flatten Module `module`s Bundles, replacing them with newly-created Signals.
@@ -145,23 +201,20 @@ 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."""
 
-        # Check we haven't (somehow) already flattened it
+        # Check we haven't (somehow) already replaced it
         if id(bundle_inst) in self.bundle_insts:
             msg = f"Bundle Instance {bundle_inst} in Module {module} flattened more than once. Was it actually part of another Module?"
             self.fail(msg)
 
-        # Flatten it and store the result in our caches
+        # Flatten it
         flat = self.flatten_bundle_inst(bundle_inst)
-        self.bundle_insts[id(bundle_inst)] = flat
-        self.module_attrs[(id(module), bundle_inst.name)] = flat
 
         # Add each flattened Signal. Note flattened Signals are modified in-place.
         for pathstr, sig in flat.signals.items():
             # Rename the signal, prepending the bundle-instance's name
             # path = Path.concat(bundle_inst.name, pathstr.to_path())
             sig.name = self.flatname(
-                segments=[bundle_inst.name, pathstr.to_name()],
-                avoid=module.namespace,
+                segments=[bundle_inst.name, pathstr.to_name()], avoid=module.namespace,
             )
             # Sort out the new Signal's visibility and direction
             if bundle_inst.port:
@@ -183,59 +236,76 @@ def replace_bundle_inst(self, module: Module, bundle_inst: BundleInstance):
             # And add it to the Module namespace
             module.add(sig)
 
+        # Store the result in our caches
+        self.bundle_insts[id(bundle_inst)] = flat
+        if bundle_inst.port:
+            self.flat_bundle_ports[(id(module), bundle_inst.name)] = flat
+
         # Replace connections to any connected instances
-        for portref in list(bundle_inst.connected_ports):
+        for portref in list(bundle_inst._connected_ports):
             self.replace_bundle_conn(
                 inst=portref.inst, portname=portref.portname, flat=flat
             )
 
-        # Re-connect any BundleRefs that `bundle` has given out, as in the form:
-        # i = SomeBundle()       # Theoretical Bundle with signal-attribute `s`
-        # x = SomeModule(s=i.s)  # Connects `BundleRef` `i.s`
-        # FIXME: this needs to happen for hierarchical bundles too
-        for bref in bundle_inst.refs_to_me.values():
-            # Resolve to the flattened signal
-            flatsig = self.resolve_bundleref(bref)
-
-            # Walk through connected Instances, replacing any connections to this `BundleRef` with `flatsig`
-            for portref in list(bref.connected_ports):
-                portref.inst.replace(portref.portname, flatsig)
-
-    def replace_anon_bundle_conn(
-        self, inst: Instance, portname: str, anon: AnonymousBundle
-    ):
-        """Replace an `AnonymousBundle` connection with its (already) flattened referents."""
-
-        flat_bundle_port = self.module_attrs.get((id(inst._resolved), portname), None)
-        if flat_bundle_port is None:
-            msg = f"Invalid Port Connection to {portname} on Instance {inst}"
-            self.fail(msg)
-
-        # Replace the connection to each flattened Signal
-        for pathstr, flat_port in flat_bundle_port.signals.items():
-            # Note `flat_port.name` will be `inst`'s name, i.e. `_mybus_clk_`
-            # Whereas its `path` will line up to `flat`'s namespace, i.e. `['bus']`
-            attr = anon.get(pathstr.val)
-            if attr is None:
-                msg = f"Connection {pathstr.val} missing from AnonymousBundle connected to Port {portname} on Instance {inst}"
-                self.fail(msg)
-
-            if isinstance(attr, BundleRef):  # Resolve any references
-                attr = self.resolve_bundleref(attr)
-            elif isinstance(attr, Signal):
-                ...  # Nothing to do, carry along with the Module-owned `Signal` attribute
-            else:
-                raise TypeError(f"Invalid AnonymousBundle attribute {attr}")
-
-            inst.connect(flat_port.name, attr)
-
-        # And now we can remove the `AnonymousBundle` connection
-        inst.disconnect(portname)
-
-    def replace_bundle_conn(self, inst: Instance, portname: str, flat: FlatBundleInst):
-        """Replace a connection to a `BundleInstance` with a connections to the Signals of a `FlatBundleInst`"""
-
-        flat_bundle_port = self.module_attrs.get((id(inst._resolved), portname), None)
+        # Kick off recursive `Ref` replacement for any (potential nested)
+        # references `bundle_inst` has given out.
+        self.resolve_bundlerefs(bundle_inst)
+
+    def resolve_bundlerefs(self, hasrefs: Union[BundleInstance, BundleRef]) -> None:
+        """Resolve all BundleRefs that `hasrefs` has given out."""
+        for bref in hasrefs.refs_to_me.values():
+            # Recursively get references it has handed out
+            self.resolve_bundlerefs(bref)
+            # And resolve `bref` itself
+            self.resolve_bundleref(bref)
+
+    def replace_bundle_conn(self, inst: Instance, portname: str, flat: BundleScope):
+        """
+        Replace a connection to a `BundleInstance` with a connections to the Signals of a `FlatBundleInst`. 
+        At this point, if we started with a circuit like: 
+
+        ```python 
+        @h.bundle 
+        class B:
+            x, y, z = h.Signals(3)
+
+        @h.module
+        class Inner: 
+            inner_b = B(port=True)
+        
+        @h.module 
+        class Outer:
+            outer_b = B()
+            inner = Inner(b=outer_b)
+        ```
+        At the point of getting here to reconnect the port `inner.b`, 
+        the `Inner` module has been flattened, and looks more like
+
+        ```
+        @h.module
+        class Inner:
+            inner_b_x, inner_b_y, inner_b_z = h.Ports(3)
+        ```
+
+        The bundle instances in `Outer` have also been flattened. 
+        It now has signals named `outer_b_z`, `outer_b_y`, and `outer_b_z`, 
+        which are stored in a `FlatBundleInst` keyed by `x`, `y`, and `z`.
+        After this function it will (or should) look like: 
+
+        ```
+        @h.module 
+        class Outer:
+            outer_b_z, outer_b_y, outer_b_z  = h.Signals(3)
+            inner = Inner(inner_b_x=outer_b_x, inner_b_y=outer_b_y, inner_b_z=outer_b_z)
+        ```
+
+        The primary mental gymnastics here are in naming, 
+        particularly between the Instance and instantiating module. 
+        """
+
+        flat_bundle_port = self.flat_bundle_ports.get(
+            (id(inst._resolved), portname), None
+        )
         if flat_bundle_port is None:
             msg = f"Invalid Port Connection to {portname} on Instance {inst}"
             self.fail(msg)
@@ -245,14 +315,21 @@ def replace_bundle_conn(self, inst: Instance, portname: str, flat: FlatBundleIns
 
         # Replace the connection to each flattened Signal
         for pathstr, flat_port in flat_bundle_port.signals.items():
-            # Note `flat_port.name` will be `inst`'s name, i.e. `_mybus_clk_`
-            # Whereas its `path` will line up to `flat`'s namespace, i.e. `['bus']`
+            # Note in analogy to the commentary above -
+            # * `pathstr` will be relative to the Bundle definition
+            #   * In the example, its values will be `x`, `y`, and `z`.
+            # * `flat_port.name` will be the instance's new port names
+            #   * In the example, its values will be `inner_b_x`, `inner_b_y`, and `inner_b_z`.
+            # * Neither takes on the values `outer_b_x`, `outer_b_y`, and `outer_b_z`.
+            #   * These would be `flat.signals[pathstr].name`
             inst.connect(flat_port.name, flat.signals[pathstr])
 
     def flatten_bundle_def(self, bundle: Bundle) -> FlatBundleDef:
-        """Flatten a `Bundle` (definition).
+        """
+        Flatten a `Bundle` (definition).
         Note Signals *are not* copied upon `FlatBundleDef` creation,
-        but must be while unrolling to each `FlatBundleInst`."""
+        but must be while unrolling to each `FlatBundleInst`.
+        """
 
         if id(bundle) in self.bundle_defs:
             return self.bundle_defs[id(bundle)]  # Already done!
@@ -264,94 +341,167 @@ def flatten_bundle_def(self, bundle: Bundle) -> FlatBundleDef:
             signals={
                 PathStr.from_list([sig.name]): sig for sig in bundle.signals.values()
             },
+            scopes={},
         )
         # Depth-first walk any bundle-instance's definitions, flattening them
-        for i in bundle.bundles.values():
+        for instname, i in bundle.bundles.items():
+
+            # Create a new scope for the bundle-instance
+            scope_path = Path([instname])
+            scope_pathstr = PathStr.from_path(scope_path)
+            scope = flat.scopes[instname] = BundleScope(
+                path=scope_path, pathstr=scope_pathstr, signals={}, scopes={},
+            )
+
+            # Recursively flatten the bundle-instance's definition
             iflat = self.flatten_bundle_def(i.of)
+
+            scope.scopes = copy.copy(iflat.scopes)
+
+            # And add its contents to our flattened definition
             for path_suffix, sig in iflat.signals.items():
-                # Pre-pend the bundle name to its path, and add to our Signals-dict
-                pathstr = PathStr.concat(i.name, path_suffix)
-                if pathstr in flat.signals:
-                    msg = f"Error Flattening {bundle}: colliding flattened Signal names for {sig} and {flat.signals[pathstr]}"
+                # Add the Signal to the new scope
+                if path_suffix in scope.signals:
+                    msg = f"Error Flattening {bundle}: colliding flattened Signal names for {sig} and {scope.signals[path_suffix]}"
                     self.fail(msg)
-                flat.signals[pathstr] = sig
+                scope.signals[path_suffix] = sig
+
+                # And add them all to the root `signals` dict, with a prepended path
+                root_pathstr = PathStr.concat(i.name, path_suffix)
+                if root_pathstr in flat.signals:
+                    msg = f"Error Flattening {bundle}: colliding flattened Signal names for {sig} and {flat.signals[root_pathstr]}"
+                    self.fail(msg)
+                flat.signals[root_pathstr] = sig
 
         # Store it in our cache, and return
         self.bundle_defs[id(bundle)] = flat
         return flat
 
-    def flatten_bundle_inst(self, bundle: BundleInstance) -> FlatBundleInst:
+    def flatten_bundle_inst(self, bundle_inst: BundleInstance) -> FlatBundleInst:
         """Convert nested Bundle `bundle` into a flattened `FlatBundleInst` of scalar Signals.
         Signals are copied in the creation of each `BundleInstance`, so no further copying is required at the Module-level."""
 
         # Get the (flattened) Bundle-definition
-        flatdef = self.flatten_bundle_def(bundle.of)
+        flatdef = self.flatten_bundle_def(bundle_inst.of)
 
         # Copy and rename its signals
-        signals = dict()
-        for pathstr, sig in flatdef.signals.items():
-            sig = copy.deepcopy(sig)
+        signals: Dict[str, Signal] = dict()
+        replacement_signals: Dict[int, Signal] = dict()
+        for pathstr, oldsig in flatdef.signals.items():
+            sig = replacement_signals[id(oldsig)] = copy.deepcopy(oldsig)
             sig.name = pathstr.to_name()
             signals[pathstr] = sig
 
+        # Copy its sub-Bundle `Scope`s
+        scopes = {
+            PathStr(name): self.copy_bundle_scope(scope, replacement_signals)
+            for name, scope in flatdef.scopes.items()
+        }
+
         # And return a new `FlatBundleInst`
-        return FlatBundleInst(src=bundle, signals=signals)
+        scope = BundleScope.root(signals=signals, scopes=scopes)
+        return FlatBundleInst(src=bundle_inst, root_scope=scope)
+
+    def copy_bundle_scope(
+        self, scope: BundleScope, signal_replacements: Dict[int, Signal]
+    ) -> BundleScope:
+        """ Recursively copy a `BundleScope`, replacing its `signals` with those in `signal_replacements`."""
+        signals = {
+            name: signal_replacements[id(sig)] for name, sig in scope.signals.items()
+        }
+        scopes = {
+            PathStr(name): self.copy_bundle_scope(scope, signal_replacements)
+            for name, scope in scope.scopes.items()
+        }
+        return BundleScope(
+            path=scope.path, pathstr=scope.pathstr, signals=signals, scopes=scopes
+        )
 
-    def flatten_anonymous_bundle(self, anon: AnonymousBundle) -> FlatBundleInst:
+    def replace_anon_bundle_conn(
+        self, inst: Instance, portname: str, anon: AnonymousBundle
+    ):
+        """Replace an `AnonymousBundle` connection with its (already) flattened referents."""
+
+        anon_scope: BundleScope = self.flatten_anonymous_bundle(anon)
+        self.replace_bundle_conn(inst, portname, anon_scope)
+
+    def flatten_anonymous_bundle(self, anon: AnonymousBundle) -> BundleScope:
         """Flatten an `AnonymousBundle`.
         Differs from bundle-class instances in that each attribute of anonymous-bundles
         are generally "references", owned by something else, commonly a Module."""
 
-        raise NotImplementedError
-
         if id(anon) in self.anon_bundles:
             return self.anon_bundles[id(anon)]
 
-        # All scalar Signals are owned by the Module, and can be used in the flat bundle as-is.
-        # Note this copies the `signals` *dictionary* without copying the Signals themselves.
-        signals = copy.copy(anon.signals)
+        scope = BundleScope.root()
 
-        for name, bundleref in anon.refs_to_others.items():
-            msg = f"Unsupported: Bundle-valued Ref to {bundleref} in {anon}"
+        for name, attr in anon._namespace.items():
+            # First resolve any references
+            # Note at this point in elaboration, these Anon-Bundles are the sole remaining place `PortRef`s can hide.
+            # They are also the last place where `BundleRef`s will be resolved,
+            # although the others just have been, earlier in this elaborator pass.
+            if isinstance(attr, (BundleRef, PortRef)):
+                attr = self.resolve_bundleref(attr)
+
+            if isinstance(attr, NoConn):  # Invalid
+                self.fail(f"Invalid AnonymousBundle NoConn attribute {attr} in {anon}")
+
+            elif isinstance(attr, (Signal, Slice, Concat)):
+                # "Scalar" signals get added to the result scope
+                scope.signals[PathStr(name)] = attr
+
+            # Finally handle nested, Bundle-like types, which produce sub-scopes
+            elif isinstance(attr, BundleInstance):
+                # BundleInstances have all been visited by now, and hence better be in the cache
+                flat_inst = self.bundle_insts.get(id(attr), None)
+                if flat_inst is None:
+                    self.fail(f"Invalid AnonymousBundle attribute {attr}")
+                scope.add_subscope(name, flat_inst.root_scope)
+
+            elif isinstance(attr, AnonymousBundle):
+                subscope = self.flatten_anonymous_bundle(attr)
+                scope.add_subscope(name, subscope)
+
+            elif isinstance(attr, BundleScope):
+                scope.add_subscope(name, attr)
+            else:
+                raise TypeError  # Shouldn't be reachable
+
+        self.anon_bundles[id(anon)] = scope
+        return scope
+
+    def resolve_bundleref(self, bref: BundleRef) -> Union[Signal, BundleScope]:
+        """Resolve a bundle-reference to a Signal or Flattened Bundle thereof."""
+
+        if bref.resolved is not None:
+            return bref.resolved  # Already done
+
+        # Get its path and root BundleInstance
+        path: List[str] = bref.path()
+        root: BundleInstance = bref.root()
+
+        # Get the flattened version of the root BundleInstance
+        flat_root = self.bundle_insts.get(id(root), None)
+        if flat_root is None:
+            msg = f"Invalid BundleRef to {bref.parent}"
             self.fail(msg)
-            resolved = self.resolve_bundleref(bundleref)
-            if isinstance(resolved, Signal):
-                signals[name] = resolved
-            elif isinstance(resolved, BundleInstance):
-                raise NotImplementedError  # Yeah thats the hard part
-
-        # Cache and return ther new `FlatBundleInst`
-        flat = FlatBundleInst(src=anon, signals=signals)
-        self.anon_bundles[id(anon)] = flat
-        return flat
 
-    def resolve_bundleref(self, bref: BundleRef) -> Union[Signal, FlatBundleInst]:
-        """Resolve a bundle-reference to a Signal or Flattened Bundle thereof.
-        NOTE: currently only the scalar Signal resolution case is supported; nested Bundles are TBC."""
-
-        if isinstance(bref.parent, BundleInstance):
-            # Get the flattened version of the parent
-            flat_parent = self.bundle_insts.get(id(bref.parent), None)
-            if flat_parent is None:
-                msg = f"Invalid BundleRef to {bref.parent}"
-                self.fail(msg)
-
-            # And look up the Signal in the flattened version
-            flatsig = flat_parent.signals.get(PathStr(bref.attrname), None)
-            if flatsig is None:
-                msg = f"Port {bref.attrname} not found in Bundle {bref.parent.of.name}"
-                self.fail(msg)
-            if not isinstance(flatsig, Signal):
-                msg = f"Unsupported: BundleRef to non-Signal {flatsig}"
-                raise TypeError(msg)
-
-            # Success! Return the Signal
-            return flatsig
-
-        if isinstance(bref.parent, BundleRef):
-            raise NotImplementedError  # Nested bundle refs, TBC
-
-        raise TypeError(f"BundleRef parent for {bref}")
+        bref.resolved = resolved = get(flat_root, Path(path))
+
+        if isinstance(resolved, BundleScope):
+            for connected_port in list(bref._connected_ports):
+                self.replace_bundle_conn(
+                    inst=connected_port.inst,
+                    portname=connected_port.portname,
+                    flat=resolved,
+                )
+            return resolved
+
+        if isinstance(resolved, Signal):
+            update_ref_deps(bref, resolved)
+            return resolved
+
+        raise TypeError(f"BundleRef {bref} resolved to invalid {resolved}")
 
 
 def instances_and_arrays(module: Module) -> List[Instance]:
diff --git a/hdl21/elab/elaborators/generators.py b/hdl21/elab/elaborators/generators.py
index e0da569..08294cc 100644
--- a/hdl21/elab/elaborators/generators.py
+++ b/hdl21/elab/elaborators/generators.py
@@ -2,10 +2,12 @@
 # Generator Elaborator 
 """
 
+from typing import List, Dict, Union
+
 # Local imports
 from ...module import Module
-from ...instance import InstArray, Instance
-from ...generator import GeneratorCall, Default as GenDefault
+from ...instance import Instance
+from ...generator import GeneratorCall
 from ...params import HasNoParams, _unique_name
 from ...instantiable import Instantiable
 
@@ -32,38 +34,31 @@ class GeneratorElaborator(Elaborator):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
-        self.generator_calls = dict()  # GeneratorCalls to their (Module) results
+        # Cache GeneratorCalls to their (Module) results
+        # (Yes, GeneratorCalls can be hashed.)
+        self.generator_calls: Dict[GeneratorCall, Module] = dict()
+
+    def elaborate_tops(self) -> List[Module]:
+        """Elaborate our top nodes"""
+        if not isinstance(self.tops, List):
+            self.fail(f"Invalid Top for Elaboration: {self.tops} must be a list")
+        return [self.elaborate_a_top_node(t) for t in self.tops]
+
+    def elaborate_a_top_node(self, t: Union[Module, GeneratorCall]) -> Module:
+        """Elaborate a top-level node, which may be a Module or a GeneratorCall."""
+        if isinstance(t, Module):
+            return self.elaborate_module_base(t)  # Note `_base` here!
 
-    def elaborate_top(self):
-        """Elaborate our top node"""
-        if isinstance(self.top, Module):
-            return self.elaborate_module_base(self.top)
-        if isinstance(self.top, GeneratorCall):
-            return self.elaborate_generator_call(self.top)
-        msg = f"Invalid Elaboration top-level {self.top}, must be a Module or Generator"
-        self.fail(msg)
+        if isinstance(t, GeneratorCall):
+            return self.elaborate_generator_call(t)
+
+        self.fail(
+            f"Invalid Elaboration top-level {t}, must be a Module or call to Generator"
+        )
 
     def elaborate_generator_call(self, call: GeneratorCall) -> Module:
         """Elaborate Generator-function-call `call`. Returns the generated Module."""
-        self.stack.append(call)
-
-        # Support the "no paramclass constructor" invocation.
-        # Create an instance of the generator's parameter-class if keyword args were provided instead.
-        # If both were provided, fail.
-        if call.kwargs and call.arg is not GenDefault:
-            msg = f"Invalid Generator Call {call}: either provide a single {call.gen.Params} instance or keyword arguments, not both."
-            self.fail(msg)
-        elif call.arg is GenDefault:
-            # Create an instance of the generator's parameter-class, and wipe out the keyword-args dict.
-            call.arg = call.gen.Params(**call.kwargs)
-            call.kwargs = dict()
-
-        # After all that, check that the call has a valid instance of the generator's parameter-class
-        if not isinstance(call.arg, call.gen.Params):
-            msg = f"Invalid Generator Call {call}: {call.gen.Params} instance required, got {call.arg}"
-            self.fail(msg)
 
-        # At this point the `Call` has a valid `Generator` and `arg`.
         # Check our cache, see if we've already generated its module.
         if call in self.generator_calls:  # Already done!
             # Give the `call` a reference to its result.
@@ -72,11 +67,23 @@ def elaborate_generator_call(self, call: GeneratorCall) -> Module:
             call.result = result
             return result
 
+        # Add both the `Call` and `Generator` to our stack. 
+        self.stack.append(call)
+        self.stack.append(call.gen)
+
+        # Check that the call has a valid instance of the generator's parameter-class
+        if not isinstance(call.params, call.gen.Params):
+            msg = f"Invalid Generator Call {call}: {call.gen.Params} instance required, got {call.params}"
+            self.fail(msg)
+
         # The main event: Run the generator-function
-        if call.gen.usecontext:
-            m = call.gen.func(call.arg, self.ctx)
-        else:
-            m = call.gen.func(call.arg)
+        try:
+            if call.gen.usecontext:
+                m = call.gen.func(call.params, self.ctx)
+            else:
+                m = call.gen.func(call.params)
+        except Exception as e:
+            self.fail(f"{call.gen} raised an exception: \n{e}")
 
         # Type-check the result
         # Generators may return other (potentially nested) generator-calls; unwind any of them
@@ -98,18 +105,20 @@ def elaborate_generator_call(self, call: GeneratorCall) -> Module:
             m.name = call.gen.func.__name__
 
         # Then add a unique suffix per its parameter-values
-        if not isinstance(call.arg, HasNoParams):
-            m.name += "(" + _unique_name(call.arg) + ")"
-
-        # Update the Module's `pymodule`, which generally at this point is `hdl21.generator`
-        m._pymodule = call.gen.pymodule
+        if not isinstance(call.params, HasNoParams):
+            m.name += "(" + _unique_name(call.params) + ")"
 
         # And elaborate the module
         m = self.elaborate_module_base(m)  # Note the `_base` here!
+        
+        # Pop both the `Call` and `Generator` off the stack
+        self.stack.pop()
         self.stack.pop()
+
+        # And return the generated Module
         return m
 
-    def elaborate_instance(self, inst: Instance) -> Instantiable:
+    def elaborate_instance_base(self, inst: Instance) -> Instantiable:
         """Elaborate a Module Instance."""
         # This version differs from `Elaborator` in operating on the *unresolved* attribute `inst.of`,
         # instead of the resolved version `inst._resolved`.
@@ -122,19 +131,6 @@ def elaborate_instance(self, inst: Instance) -> Instantiable:
         self.stack.pop()
         return rv
 
-    def elaborate_instance_array(self, arr: InstArray) -> Instantiable:
-        """Elaborate an Instance Array."""
-        # This version differs from `Elaborator` in operating on the *unresolved* attribute `inst.of`,
-        # instead of the resolved version `inst._resolved`.
-
-        self.stack.append(arr)
-        # Turn off `PortRef` magic
-        arr._elaborated = True
-        # And visit the Instance's target
-        rv = self.elaborate_instantiable(arr.of)
-        self.stack.pop()
-        return rv
-
     def elaborate_instantiable(self, of: Instantiable) -> Instantiable:
         """Elaborate an Instance target. Adds the capacity to call `GeneratorCall`s to the more-common base-case."""
         if isinstance(of, GeneratorCall):
diff --git a/hdl21/elab/elaborators/inst_bundles.py b/hdl21/elab/elaborators/inst_bundles.py
index 3010078..71ea964 100644
--- a/hdl21/elab/elaborators/inst_bundles.py
+++ b/hdl21/elab/elaborators/inst_bundles.py
@@ -10,13 +10,12 @@
 FIXME: sort out the relationship between this pass and `ConnTypes`. 
        Currently this pass checks for "bundle type identity" (i.e. `conn.bundle IS inst.bundle`), 
        and `ConnTypes` checks for "structural equivalence". 
-FIXME: this is amid `Diff`/`Pair` specific code and more general `InstanceBundle`s
 """
 
-from typing import get_args
 
 # Local imports
-from ...portref import PortRef
+from ...connect import is_connectable
+from ...bundle import AnonymousBundle, BundleInstance, _bundle_ref
 from ...module import Module
 from ...instance import Instance, InstanceBundle
 
@@ -46,9 +45,6 @@ def elaborate_instance_bundle(
         """# Elaborate an Instance Bundle
         Replace it with "scalar" Instances and reconnect them."""
 
-        from ...bundle import AnonymousBundle, BundleInstance
-        from ...signal import Sliceable
-
         if len(instbundle.bundle.bundles):
             msg = f"Invalid Instance Bundle {instbundle} with nested Bundles"
             self.fail(msg)
@@ -71,26 +67,19 @@ def elaborate_instance_bundle(
 
             if isinstance(conn, BundleInstance):
                 # If the connection is a Bundle instance, connect each new instance to the paired-named Signals
-
                 if conn.of is not instbundle.bundle:
                     msg = f"Invalid Instance Bundle connection between {conn.of} and {instbundle.bundle}"
                     self.fail(msg)
 
                 for signame, new_inst in signal_names_to_instances.items():
-                    new_inst.connect(portname, conn._bundle_ref(signame))
+                    new_inst.connect(portname, _bundle_ref(conn, signame))
 
             elif isinstance(conn, AnonymousBundle):
                 for signame, new_inst in signal_names_to_instances.items():
                     new_inst.connect(portname, conn.get(signame))
 
-            elif isinstance(conn, get_args(Sliceable)) or isinstance(conn, PortRef):
+            elif is_connectable(conn):
                 # If the connection is a scalar, connect it to each new instance
-
-                # FIXME: check for unit width, when the `Ref` types can handle it
-                # if conn.width != 1:
-                #     msg = f"InstanceBundle {instbundle.name} connection {conn} is not a scalar, but has width {conn.width}"
-                #     self.fail(msg)
-
                 for new_inst in signal_names_to_instances.values():
                     new_inst.connect(portname, conn)
 
diff --git a/hdl21/elab/elaborators/orphanage.py b/hdl21/elab/elaborators/orphanage.py
index b376c19..ddfd9f6 100644
--- a/hdl21/elab/elaborators/orphanage.py
+++ b/hdl21/elab/elaborators/orphanage.py
@@ -78,9 +78,16 @@ def check_connectable(self, module: Module, conn: "Connectable") -> None:
         """Check a Connectable for orphanage.
         Dispatches across connectable types, and recursively follows `conn` back to its constituent and/or parent elements."""
 
-        from ...signal import Signal, Slice, Concat, NoConn
-        from ...bundle import BundleInstance, BundleRef, AnonymousBundle
-        from ...portref import PortRef
+        from ... import (
+            NoConn,
+            Signal,
+            Slice,
+            Concat,
+            BundleInstance,
+            BundleRef,
+            AnonymousBundle,
+            PortRef,
+        )
 
         # Check owned types first: Signals and Bundle Instances
         if isinstance(conn, (Signal, BundleInstance)):
@@ -88,7 +95,7 @@ def check_connectable(self, module: Module, conn: "Connectable") -> None:
 
         if isinstance(conn, Slice):
             # Recursively check the parent-signals of slices
-            return self.check_connectable(module, conn.signal)
+            return self.check_connectable(module, conn.parent)
 
         if isinstance(conn, Concat):
             # Check each of the concatenated signals, also recursively across inner types
@@ -109,9 +116,8 @@ def check_connectable(self, module: Module, conn: "Connectable") -> None:
             return self.assert_parentage(module, conn.root())
 
         if isinstance(conn, AnonymousBundle):
-            for sig in conn.signals.values():
+            for sig in conn._namespace.values():
                 self.check_connectable(module, sig)
-            # FIXME: add sub-bundles when available
             return
 
         raise TypeError(f"Orphanage: Unhandled Connectable `{conn}`")
diff --git a/hdl21/elab/elaborators/portrefs.py b/hdl21/elab/elaborators/portrefs.py
index d853539..92919f6 100644
--- a/hdl21/elab/elaborators/portrefs.py
+++ b/hdl21/elab/elaborators/portrefs.py
@@ -6,14 +6,17 @@
 
 # Std-Lib Imports
 import copy
-from typing import Union, Dict, List, Optional, Sequence, get_args
+from typing import Union, List, Optional, Set, get_args
 
 # Local imports
+from ...connect import Connectable
+from ...instance import _get_connref
 from ...module import Module
 from ...portref import PortRef
-from ...instance import Instance, InstArray
 from ...bundle import BundleInstance, BundleRef, AnonymousBundle
-from ...signal import PortDir, Signal, Visibility, NoConn
+from ...signal import PortDir, Signal, Visibility
+from ...noconn import NoConn
+from .resolve_ref_types import update_ref_deps
 
 # Import the base class
 from .base import Elaborator
@@ -48,55 +51,68 @@ class ResolvePortRefs(Elaborator):
     """
 
     def elaborate_module(self, module: Module) -> Module:
-        """Resolve and replace all Instance `PortRef`s in `module`."""
+        """
+        Resolve and replace all Instance `PortRef`s in `module`.
 
-        # Collect up every `PortRef` and `NoConn` that's been instantiated
-        portconns = self.collect_portconns(module)
-        # And replace them with concrete connectable types
-        self.replace_portconns(module, portconns)
+        In the process, all `Connectable`s are annotated with a set of `_connected_ports`. 
+        These `_connected_ports` are relied upon by later elaboration stages, 
+        and must be maintained hereafter, e.g. when making connection updates. 
+        """
 
-        return module
+        instancelike = (
+            list(module.instances.values())
+            + list(module.instarrays.values())
+            + list(module.instbundles.values())
+        )
 
-    def collect_portconns(
-        self, module: Module
-    ) -> Dict[PortRef, Union[PortRef, NoConn]]:
-        """Collect a dictionary of all Instance-ports connected to `PortRef`s or `NoConn`s."""
+        # Collect up all `PortRef`s for all instances in the module
+        # FIXME: move from SetList to a regular Set. Thus far breaks one test, somehow.
+        module_portrefs = SetList()
+        for inst in instancelike:
 
-        portconns: Dict[PortRef, Union[PortRef, NoConn]] = dict()
+            # Populate the module-level set of PortRefs
+            for portref in inst._refs.portrefs.values():
+                module_portrefs.add(portref)
 
-        # FIXME: a better word for "instances or arrays thereof"; we already used "connectable"
-        connectables = list(module.instances.values()) + list(
-            module.instarrays.values()
-        )
-        for inst in connectables:
-            for port, conn in inst.conns.items():
-                if isinstance(conn, (PortRef, NoConn)):
-                    internal_ref = PortRef(inst, port)
-                    portconns[internal_ref] = conn
-
-        return portconns
-
-    def replace_portconns(
-        self, module: Module, portconns: Dict[PortRef, Union[PortRef, NoConn]]
-    ) -> Module:
-        """Replace each port-to-port connection in `portconns` with concrete `Signal`s."""
-
-        # Now walk through all port-connections, assigning each set to a net
-        groups: List[List[Union[PortRef, NoConn]]] = list()
-
-        while portconns:
-            # Keep both a list for consistent ordering, and a set for quick membership tests
-            this_group = SetList()
-            # Grab a random pair
-            inner, outer = portconns.popitem()
-            this_group.add(inner)
-            this_group.add(outer)
-            # If it's connected to others, grab those
-            while outer in portconns:
-                outer = portconns.pop(outer)
-                this_group.add(outer)
-            # Once we find an object not in `portconns`, we've covered everything connected to the group.
-            groups.append(this_group.order)
+            # FIXME: add the `NoConn`s here, although it's not clear we *really* need these checks on them
+            for portname, conn in inst.conns.items():
+                if isinstance(conn, NoConn):
+                    module_portrefs.add(_get_connref(inst, portname))
+
+        def follow(pref: PortRef, group: SetList) -> None:
+            """ Closure to recursively follow `pref`, adding its outward and inward connections to `group`. 
+            Removes encountered entries from `module_portrefs` along the way."""
+
+            if pref in group:
+                return  # Already done
+            if pref in module_portrefs:
+                module_portrefs.remove(pref)
+
+            # Add the portref to the group
+            group.add(pref)
+
+            # Add, and if necessary recursively follow, its instance connection
+            conn = pref.inst.conns.get(pref.portname, None)
+            if isinstance(conn, PortRef):
+                follow(conn, group)
+            else:  # Add ultimate signal `Source`s to the group
+                group.add(conn)
+
+            # And recursively follow its connected ports
+            for connected_port in pref._connected_ports:
+                follow(connected_port, group)
+
+        # Collect groups of connected `PortRef`s
+        groups: List[List[Optional[Connectable]]] = list()
+
+        while module_portrefs:
+            # Create a new group, and recursively follow port refs to populate it
+            group = SetList()
+            follow(module_portrefs.pop(), group)
+
+            # Filter out the `None` entries before sticking in the `groups` list-of-lists.
+            group: List[Connectable] = [x for x in group.order if x is not None]
+            groups.append(group)
 
         # For each group, find and/or create a Signal to replace all the PortRefs with.
         for group in groups:
@@ -104,7 +120,7 @@ def replace_portconns(
 
         return module
 
-    def handle_group(self, module: Module, group: List[Union[PortRef, NoConn]]):
+    def handle_group(self, module: Module, group: List[Connectable]) -> None:
         """Handle a `group` worth of connected Ports and NoConns"""
 
         # First cover `NoConn` connections, checking for any invalid multiple-conns to them.
@@ -112,46 +128,29 @@ def handle_group(self, module: Module, group: List[Union[PortRef, NoConn]]):
             return self.handle_noconn(module, group)
         return self.handle_portconn(module, group)
 
-    def handle_portconn(self, module: Module, group: List[PortRef]):
+    def handle_portconn(self, module: Module, group: List[Connectable]):
         """Handle re-connecting a list of connected `PortRef`s.
         Creates and adds a fresh new `Signal` if one does not already exist."""
 
-        # Find any existing, declared `Signal` connected to `group`.
-        # And if we don't find any, go about naming and creating one.
-        sig: Source = self.find_source(group) or self.create_source(module, group)
-
-        # And re-connect it to each Instance
-        non_sources = list(filter(lambda p: self.source(p) is None, group))
-        for portref in non_sources:
-            portref.inst.connect(portref.portname, sig)
-
-    def source(self, portref: PortRef) -> Optional[Source]:
-        """
-        Find the "source signal" for `portref`, if one is connected.
-        If `portref` is not explicitly connected, or is connected to another `PortRef`, returns None.
-        """
-        if not isinstance(portref.inst, (Instance, InstArray)):
-            self.fail(f"Invalid reference to port on non-Instance `{portref}`")
+        group_port_refs = [x for x in group if isinstance(x, PortRef)]
 
-        portconn = portref.inst.conns.get(portref.portname, None)
-        if portconn is None:
-            return None  # Nothing explicitly connected *into* this port
-        if isinstance(portconn, PortRef):
-            return None  # Connected to another `PortRef`, not a `Source`
+        # Find any existing, declared `Source` connected to `group`.
+        # And if we don't find any, go about naming and creating one.
+        source: Optional[Source] = self.find_source(group)
+        if source is None:
+            source = self.create_source(module, group_port_refs)
 
-        # This check *should* always succeed, but type-check it nonetheless.
-        if isinstance(portconn, get_args(Source)):
-            return portconn
-        self.fail(f"Connection {portconn}")
+        # Resolve each PortRef with `source` as its referent,
+        # and reconnect it everywhere it's connected.
+        for portref in group_port_refs:
+            resolve_portref(portref, source)
 
-    def find_source(self, group: List[PortRef]) -> Optional[Source]:
+    def find_source(self, group: List[Connectable]) -> Optional[Source]:
         """Find any existing, declared `Source` connected to `group`.
-        And if there more than one, make sure they all connect to the same object.
         Returns None is no `Source`s are connected to any element in the group."""
 
         # Extract which of the `group` are connected to `Source`s
-        sources: Sequence[Optional[Source]] = map(self.source, group)
-        sources: List[Source] = [s for s in sources if s is not None]
+        sources: List[Source] = [s for s in group if isinstance(s, get_args(Source))]
 
         # Three relevant cases then emerge:
         # * (a) Zero sources. No problem, create one.
@@ -170,25 +169,57 @@ def find_source(self, group: List[PortRef]) -> Optional[Source]:
         msg += f"shorting Ports {[(p.inst.name, p.portname) for p in group]}"
         self.fail(msg)
 
+    def which_portref_to_name(self, group: List[PortRef]) -> PortRef:
+        """ Figure out which PortRef in `group` to use for its new signal name. 
+        This generally follows "the port the other ones refer to", i.e. for: 
+        ```
+        i1.p = i0.p
+        i2.p = i0.p
+        i3.p = i1.p
+        i4.p = i3.p
+        ```
+        The Instance `i0` has the sole None-connected Port `p`, and gets "naming rights". 
+        
+        In other cases there is no such instance, such as: 
+        ```
+        i0.p = i1.p
+        i1.p = i0.p # Connected back!
+        ```
+        In these cases, the first Instance name in alphabetical order is used.
+        """
+
+        # Sort out which PortRefs have no connection
+        conn = lambda p: p.inst.conns.get(p.portname, None)
+        connected_to_none = [p for p in group if conn(p) is None]
+
+        if len(connected_to_none) == 1:
+            return connected_to_none[0]
+
+        if len(connected_to_none) > 1:
+            self.fail(f"Invalid PortRef group: {group}")
+
+        # Nothing "unconnected". Find the instance one with the lowest (alphabetical) name.
+        ordered = sorted(group, key=lambda p: p.inst.name)
+        return ordered[0]
+
     def create_source(self, module: Module, group: List[PortRef]) -> PortType:
         """Create a new `Signal`, parametrized and named to connect to the `PortRef`s in `group`."""
 
-        # Find a unique name for the new Signal.
-        # The default name template is "_inst1_port1_inst2_port2_inst3_port3_"
+        # Sort out which PortRef to use for the new signal name.
+        portref = self.which_portref_to_name(group)
         signame = self.flatname(
-            segments=[f"{p.inst.name}_{p.portname}" for p in group],
+            segments=[f"{portref.inst.name}_{portref.portname}"],
             avoid=module.namespace,
         )
 
-        # Create the Signal, looking up all its properties from the last Instance's Module
-        # (If other instances are inconsistent, later stages will flag them)
-        portref = group[-1]
-        lastmod = portref.inst._resolved
+        # Get its Module for its IO
+        # Note if the other entries in `group` have incompatible IO, this will be flagged by a later pass.
+        portrefs_module = portref.inst._resolved
 
         # FIXME: make this `io` method Module-like-wide
-        io = copy.copy(lastmod.ports)
-        if hasattr(lastmod, "bundle_ports"):
-            io.update(copy.copy(lastmod.bundle_ports))
+        io = copy.copy(portrefs_module.ports)
+        if hasattr(portrefs_module, "bundle_ports"):
+            io.update(copy.copy(portrefs_module.bundle_ports))
 
         port = io.get(portref.portname, None)
         if port is None:  # Clone it, and remove any Port-attributes
@@ -219,7 +250,7 @@ def copy_port(self, port: PortType) -> PortType:
 
         self.fail(f"Invalid Port Type `{port}`")
 
-    def handle_noconn(self, module: Module, group: List[Union[PortRef, NoConn]]):
+    def handle_noconn(self, module: Module, group: List[Connectable]):
         """Handle a group with a `NoConn`."""
         # First check for validity of the group, i.e. that the `NoConn` only connects to *one* port.
         if len(group) > 2:
@@ -261,20 +292,61 @@ class SetList:
     """A common combination of a hash-set and ordered list of the same items.
     Used for keeping ordered items while maintaining quick membership testing.
 
-    FIXME: don't think the elaborator actually needs this any more, seems it could be a set only."""
+    FIXME: this is really using a LIST for now, slowing down membership tests, 
+    but should be set-based when some connectable types, particularly `Signal`, are ready for it. 
+    """
 
     def __init__(self):
-        self.set = set()
+        # self.set = set()
         self.list = list()
 
+    def __bool__(self):
+        return bool(self.list)
+
     def __contains__(self, item):
-        return item in self.set
+        # return item in self.set
+        return item in self.list
 
     def add(self, item):
-        if item not in self.set:
-            self.set.add(item)
+        # if item not in self.set:
+        if item not in self.list:
+            # self.set.add(item)
             self.list.append(item)
 
+    def remove(self, item):
+        # if item not in self.set:
+        if item not in self.list:
+            raise RuntimeError
+            # self.set.add(item)
+        self.list.remove(item)
+
+    def pop(self):
+        # if item not in self.set:
+        if not self.list:
+            raise RuntimeError
+            # self.set.add(item)
+        return self.list.pop(0)
+
     @property
     def order(self):
         return self.list
+
+
+def resolve_portref(pref: PortRef, to: Connectable) -> None:
+    """# Resolve a `PortRef` to its referent `Connectable`. """
+
+    if pref.resolved is to:
+        return  # Already resolved
+    if pref.resolved is not None:
+        raise ValueError(f"PortRef {pref} already resolved")
+
+    # Give the `PortRef` a reference to its referent
+    pref.resolved = to
+
+    # Connect the "primary" instance to the referent
+    # Note this differs between `PortRef` and `BundleRef`;
+    # the latter has no "primary instance" to connect to.
+    pref.inst.connect(pref.portname, to)
+
+    # Update all downstream dependencies, e.g. connected ports, slices
+    update_ref_deps(pref, to)
diff --git a/hdl21/elab/elaborators/resolve_ref_types.py b/hdl21/elab/elaborators/resolve_ref_types.py
new file mode 100644
index 0000000..114319d
--- /dev/null
+++ b/hdl21/elab/elaborators/resolve_ref_types.py
@@ -0,0 +1,118 @@
+"""
+# Helper for resolving the "types" of references. 
+
+Note "types" here does not mean Python-types, but something more like "connection types". 
+Resolving a reference "type" produces either a Signal or BundleInstance, 
+which is "compatible with", but not equal to, the ultimate referent. 
+
+For example: 
+
+```python
+@h.module
+class Inner:
+    p = h.Port()
+
+@h.module
+class Outer:
+    i = Inner() 
+
+# Create a `PortRef`
+ref = Outer.i.p
+
+# All of these succeed:
+assert ref is Outer.i.p
+assert resolve_portref_type(ref) is not Outer.i.p
+assert resolve_portref_type(ref) is not Inner.p
+```
+
+Here `ref` will ultimately refer to the a Signal within an `Instance` of `Inner`. 
+At the time of calling `resolve_portref_type`, no such Signal exists. 
+The result of `resolve_portref_type` is therefore the `Inner` *Module definition level* Signal object. 
+This is often required e.g. for connection-validity checking, or for copying to new Signals.
+
+The important thing is: don't take the results of `resolve_*_type` as the actual referents! 
+"""
+
+# Std-Lib Imports
+from typing import Union, Callable
+
+# Local imports
+from ...portref import PortRef
+from ...connect import Connectable
+from ...signal import Signal
+from ...bundle import (
+    BundleInstance,
+    BundleRef,
+)
+
+
+def fail(msg: str) -> None:
+    # The default failure handler
+    raise RuntimeError(msg)
+
+
+def resolve_bundleref_type(
+    bref: BundleRef, failer: Callable = fail
+) -> Union[Signal, BundleInstance]:
+    """
+    Resolve a bundle-reference to either a `Signal` or sub-`Bundle` Instance.
+
+    NOTE this returns a *representative* signal or bundle instance,
+    i.e. one with the correct type and width - not *the signal* for a given instance.
+    In other words: this is fine for connection-validity checking,
+    but *not* for copying signals during flattening.
+    Hence the "type" name suffix, although what we return is not really a type.
+    """
+
+    if isinstance(bref.parent, BundleInstance):  # Parent is a BundleInstance.
+        parent = bref.parent
+    elif isinstance(
+        bref.parent, BundleRef
+    ):  # Nested reference. Recursively resolve the parent.
+        parent = resolve_bundleref_type(bref.parent, failer)
+    else:
+        failer(f"Invalid BundleRef parent for {bref}")
+
+    # Get the attribute from the parent namespace, or fail if not available.
+    attr = parent.of.get(bref.attrname)
+    if attr is None:
+        msg = f"Bundle `{bref.parent.of.name}` has no attribute `{bref.attrname}`"
+        failer(msg)
+    return attr
+
+
+def resolve_portref_type(
+    pref: PortRef, failer: Callable = fail
+) -> Union[Signal, BundleInstance]:
+    """ Get the `Port` object referred to by `pref`."""
+
+    instantiable = pref.inst._resolved
+
+    if pref.portname in instantiable.ports:
+        return instantiable.ports[pref.portname]
+
+    if (
+        hasattr(instantiable, "bundle_ports")
+        and pref.portname in instantiable.bundle_ports
+    ):
+        return instantiable.bundle_ports[pref.portname]
+
+    return failer(f"Invalid PortRef {pref}")
+
+
+def update_ref_deps(ref: Union[PortRef, BundleRef], resolved: Connectable):
+    """ Update all downstream dependencies on a `Ref` after it has been resolved to `resolved`. """
+
+    # Reconnect all connected ports
+    for connected_port in list(ref._connected_ports):
+        connected_port.inst.replace(connected_port.portname, resolved)
+
+    # Update all dependent slices and concats
+    if hasattr(ref, "_slices"):
+        for slice_ in ref._slices:
+            slice_.parent = resolved
+    if hasattr(ref, "_concats"):
+        for concat in ref._concats:
+            parts = list(concat.parts)
+            parts = [resolved if p is ref else p for p in parts]
+            concat.parts = tuple(parts)
diff --git a/hdl21/elab/elaborators/slices.py b/hdl21/elab/elaborators/slices.py
index ba987a4..76498a2 100644
--- a/hdl21/elab/elaborators/slices.py
+++ b/hdl21/elab/elaborators/slices.py
@@ -3,11 +3,16 @@
 """
 
 # Std-Lib Imports
-from typing import List
+from typing import List, Union
 
 # Local imports
 from ...module import Module
-from ...signal import Signal, Slice, Concat, Sliceable
+from ...signal import Signal
+from ...slice import Slice
+from ...concat import Concat
+from ...portref import PortRef
+from ...bundle import BundleRef
+from .width import width, Sliceable
 
 # Import the base class
 from .base import Elaborator
@@ -47,6 +52,8 @@ def _resolve_sliceable(conn: Sliceable) -> Sliceable:
         return _resolve_slice(conn)
     if isinstance(conn, Concat):
         return _resolve_concat(conn)
+    if isinstance(conn, (PortRef, BundleRef)):
+        return _resolve_ref(conn)
     raise TypeError(f"Invalid attempt to resolve slicing on {conn}")
 
 
@@ -55,43 +62,42 @@ def _list_slice(slize: Slice) -> List[Slice]:
     Returns a list of Slices in which each element has a concrete Signal for its parent."""
 
     # Resolve "full-width" slices to their parent Signals
-    if slize.width == slize.signal.width:
+    if width(slize) == width(slize.parent):
         # Return a single-element list, after resolution
-        return [_resolve_sliceable(slize.signal)]
+        return [_resolve_sliceable(slize.parent)]
 
-    if isinstance(slize.signal, Signal):
+    if isinstance(slize.parent, Signal):
         return [slize]  # Already all good! Just make a one-element list.
 
     # Do some actual work. Recursively peel off a bit at a time.
-    if slize.width == 1:
+    if width(slize) == 1:
         # Base case: slice is one-bit wide. Reach into the parent signal and grab that bit.
 
-        if isinstance(slize.signal, Slice):
-            parent = slize.signal  # Note this is also a Slice
-            return _list_slice(parent.signal[parent.bot + slize.bot])
+        if isinstance(slize.parent, Slice):
+            parent = slize.parent  # Note this is also a Slice
+            return _list_slice(parent.parent[parent.bot + slize.bot])
 
-        if isinstance(slize.signal, Concat):
+        if isinstance(slize.parent, Concat):
             idx = 0  # Find the `part` including our index
-            for part in slize.signal.parts:
-                if part.width + idx > slize.bot:
+            for part in slize.parent.parts:
+                if width(part) + idx > slize.bot:
                     return _list_slice(part[slize.bot - idx])
-                idx += part.width
-            msg = f"Slice {slize} is out of bounds of Concat {slize.signal}"
+                idx += width(part)
+            msg = f"Slice {slize} is out of bounds of Concat {slize.parent}"
             raise RuntimeError(msg)
 
         raise TypeError(f"Invalid attempt to resolve slicing on {slize}")
 
     # Otherwise recurse in something like a "cons" pattern, splitting between the first bit and the rest.
-
-    if slize.step is not None and slize.step < 0:  # Negative step, begin from `top`
-        first = _list_slice(slize.signal[slize.top])
-        rest = slize.signal[slize.top + slize.step : slize.bot : slize.step]
+    step = slize.step
+    if step < 0:  # Negative step, begin from `top`
+        first = _list_slice(slize.parent[slize.top])
+        rest = slize.parent[slize.top + step : slize.bot : step]
         rest = _list_slice(rest)
 
     else:  # Positive step, begin from `bot`
-        step = slize.step if slize.step is not None else 1
-        first = _list_slice(slize.signal[slize.bot])
-        rest = _list_slice(slize.signal[slize.bot + step : slize.top : slize.step])
+        first = _list_slice(slize.parent[slize.bot])
+        rest = _list_slice(slize.parent[slize.bot + step : slize.top : step])
 
     return first + rest
 
@@ -162,6 +168,13 @@ def _resolve_concat(conc: Concat) -> Concat:
     raise RuntimeError("Unable to resolve concatenation")
 
 
+def _resolve_ref(ref: Union[PortRef, BundleRef]) -> Sliceable:
+    """Resolve a reference to a Port or Bundle"""
+    if ref.resolved is None:
+        raise RuntimeError(f"Unresolved reference {ref}")
+    return _resolve_sliceable(ref.resolved)
+
+
 def _flat_concatable(s: Sliceable) -> bool:
     """Boolean indication of whether `s` is suitable for flattened Concatenations.
     Such objects must be either:
@@ -169,5 +182,5 @@ def _flat_concatable(s: Sliceable) -> bool:
     * (b) A Slice into a Signal
     Notable exceptions include Concats and nested Slices of Concats and other Slices."""
     return isinstance(s, Signal) or (
-        isinstance(s, Slice) and isinstance(s.signal, Signal)
+        isinstance(s, Slice) and isinstance(s.parent, Signal)
     )
diff --git a/hdl21/elab/elaborators/width.py b/hdl21/elab/elaborators/width.py
new file mode 100644
index 0000000..6df31ed
--- /dev/null
+++ b/hdl21/elab/elaborators/width.py
@@ -0,0 +1,90 @@
+"""
+# Width Resolution Helper 
+
+Function to determine the `width` of connectable types. 
+"""
+
+# Std-Lib Imports
+from typing import Union, Callable
+
+# Local imports
+from ...connect import Connectable
+from ...portref import PortRef
+from ...signal import Signal
+from ...slice import Slice
+from ...concat import Concat
+from ...noconn import NoConn
+from ...bundle import (
+    AnonymousBundle,
+    BundleInstance,
+    BundleRef,
+)
+from .resolve_ref_types import resolve_bundleref_type, resolve_portref_type
+
+# HasWidth & Sliceable Type Uhions
+# Everything that has, or at least sometimes has, an integer Signal-style `width`. 
+# AND everything that can be `Slice`d. 
+# These happen to be the same set of types, but can be broken up 
+# if the two should ever differ. 
+Sliceable = HasWidth = Union[Signal, Slice, Concat, BundleRef, PortRef]
+
+
+def fail(msg: str) -> None:
+    # The default failure handler
+    raise RuntimeError(msg)
+
+
+def width(conn: Connectable, failer: Callable = fail) -> int:
+    """ Get the `width` of a conn. Largely dispatches across conn types. 
+    Optional function-valued argument `failer` is passed all errors. 
+    This is commonly used to pass failure information and control back to `Elaborator`s."""
+
+    # A reminder, as of this writing:
+    # Connectable = Union["Signal", "Slice", "Concat", "NoConn", "PortRef", "BundleInstance", "AnonymousBundle", "BundleRef"]
+    # (Man that Rust-style enum would be nice)
+
+    # Invalid cases
+    if isinstance(conn, (BundleInstance, AnonymousBundle, NoConn)):
+        return failer(f"Invalid `width` of {conn}")
+
+    # Typical "scalar" cases
+    if isinstance(conn, Signal):
+        return conn.width
+    if isinstance(conn, Slice):
+        return conn.width
+    if isinstance(conn, Concat):
+        return sum([width(p) for p in conn.parts])
+
+    # References
+    if isinstance(conn, (BundleRef, PortRef)):
+        return ref_width(conn, failer)
+
+    return failer(f"Invalid `width` of {conn}")
+
+
+def ref_width(ref: Union[PortRef, BundleRef], failer: Callable = fail) -> int:
+    """ Get a reference's width from its referent.
+    Fail if this resolves to a Bundle Instance.
+    And cache the result on the Ref for future use.
+    
+    Optional function-valued argument `failer` is passed all errors. 
+    This is commonly used to pass failure information and control back to `Elaborator`s."""
+
+    if ref._width is not None:
+        return ref._width
+
+    # Get the referent
+    if isinstance(ref, BundleRef):
+        referent = resolve_bundleref_type(ref, failer)
+    elif isinstance(ref, PortRef):
+        referent = resolve_portref_type(ref)
+    else:
+        return failer(f"Invalid arg to ref_width {ref}")
+
+    # And get its width
+    if isinstance(referent, Signal):
+        ref._width = width(referent, failer)
+        return ref._width
+    if isinstance(referent, BundleInstance):
+        return failer(f"Invalid `width` of Bundle {referent}")
+    return failer(f"Invalid `width` of {referent}")
diff --git a/hdl21/generator.py b/hdl21/generator.py
index 8365eb7..077b370 100644
--- a/hdl21/generator.py
+++ b/hdl21/generator.py
@@ -4,41 +4,36 @@
 
 import inspect
 import pickle
-from dataclasses import field
 from typing import Callable, Any, Optional, Dict
-from types import ModuleType
-from pydantic.dataclasses import dataclass
-from pydantic import ValidationError
 
 # Local imports
+from .default import Default
+from .call import param_call
+from .params import HasNoParams, _unique_name
+from .source_info import SourceInfo, source_info
 from .module import Module
 from .instance import calls_instantiate
 
 
-@dataclass
-class Default:
-    """Default value for generator arguments"""
-
-    ...  # Empty contents
-
-
-@dataclass
 class Generator:
-    """# Generator Object
+    """
+    # Generator Object
+
     Typically created by the `@hdl.generator` decorator.
-    Stores a function-object and parameters-type,
-    along with some auxiliary data.
+    Stores a function-object and parameters-type, along with some auxiliary data.
     """
 
-    func: Callable  # The generator function
-    paramtype: type  # Parameter type
-    usecontext: bool  # Boolean indication of `Context` usage
-    pymodule: ModuleType  # Python module where function defined
+    def __init__(self, func: Callable, paramtype: type, usecontext: bool):
+        self.func = func
+        self.paramtype = paramtype
+        self.usecontext = usecontext
+        self._source_info: Optional[SourceInfo] = source_info(get_pymodule=False)
 
-    def __call__(self, arg: Any = Default, **kwargs) -> "GeneratorCall":
+    def __call__(self, arg: Any = Default, **kwargs: Dict) -> "GeneratorCall":
         """Calls to Generators create GeneratorCall-objects
         to be expanded during elaboration."""
-        return GeneratorCall(gen=self, arg=arg, kwargs=kwargs)
+        params = param_call(callee=self, arg=arg, **kwargs)
+        return GeneratorCall(gen=self, params=params)
 
     @property
     def name(self) -> str:
@@ -56,30 +51,30 @@ def __repr__(self) -> str:
 
 
 @calls_instantiate
-@dataclass
 class GeneratorCall:
     """Generator 'Bare Calls'
     Stored for expansion during elaboration.
     Only single-argument calls with `Params` are supported.
     Any application of a `Context` is done during elaboration."""
 
-    gen: Generator
-    arg: Any
-    kwargs: Dict[str, Any]
-    result: Optional[Module] = field(init=False, default=None)
+    def __init__(self, gen: Generator, params: Any):
+        self.gen = gen
+        self.params = params
+        self.result: Optional[Module] = None
+        self._source_info: Optional[SourceInfo] = source_info(get_pymodule=False)
 
     def __eq__(self, other) -> bool:
         """Generator-Call equality requires:
         * *Identity* between generators, and
         * *Equality* between parameter-values."""
-        return self.gen is other.gen and self.arg == other.arg
+        return self.gen is other.gen and self.params == other.params
 
     def __hash__(self):
         """Generator-Call hashing, consistent with `__eq__` above, uses:
         * *Identity* of its generator, and
         * *Value* of its parameters.
         The two are joined for hashing as a two-element tuple."""
-        return hash((id(self.gen), pickle.dumps(self.arg)))
+        return hash((id(self.gen), pickle.dumps(self.params)))
 
     def __repr__(self) -> str:
         return f"GeneratorCall(gen={self.gen.name})"
@@ -90,8 +85,12 @@ def name(self) -> str:
         Once elaborated, returns the name of the generated Module.
         If not elaborated, raises a `RuntimeError`."""
         if self.result is None:
-            raise RuntimeError(f"Cannot name un-elaborated GeneratorCall {self}")
-        return self.result.name
+            name = self.gen.name
+            if not isinstance(self.params, HasNoParams):
+                name += "(" + _unique_name(self.params) + ")"
+            return name
+        return self.result.name  # FIXME: do we need this case?
+        # They are probably always the same already, but can be made 110% the same for sure.
 
 
 def generator(f: Callable) -> Generator:
@@ -129,9 +128,5 @@ def generator(f: Callable) -> Generator:
         msg = f"Generator {f.__name__} must return (and must be annotated to return) a Module."
         raise TypeError(msg)
 
-    # Grab a reference to the Python-module defining the function
-    # (For later name-unique-ifying)
-    pymodule = inspect.getmodule(f)
-    return Generator(
-        func=f, paramtype=paramtype, usecontext=usecontext, pymodule=pymodule
-    )
+    # And return the `Generator` object
+    return Generator(func=f, paramtype=paramtype, usecontext=usecontext)
diff --git a/hdl21/generators.py b/hdl21/generators.py
index 606b5bc..3658eff 100644
--- a/hdl21/generators.py
+++ b/hdl21/generators.py
@@ -149,7 +149,7 @@ def SeriesPar(params: SeriesParParams) -> Module:
         for iser in range(1, params.nser):
             prev_inst = inst
             inst = unit(**par_conns)
-            inst.connect(ser0.name, prev_inst._port_ref(ser1.name))
+            inst.connect(ser0.name, getattr(prev_inst, ser1.name))
             inst = m.add(name=f"unit_{ipar}_{iser}", val=inst)
         # Finally connect the last series-port to the last instance
         inst.connect(ser1.name, ser1)
diff --git a/hdl21/instance.py b/hdl21/instance.py
index 21999a8..3bf881a 100644
--- a/hdl21/instance.py
+++ b/hdl21/instance.py
@@ -6,30 +6,23 @@
 
 # Std-Lib Imports
 from typing import Optional, Any, Dict
+from dataclasses import dataclass, field
 from textwrap import dedent
 
 # Local imports
 from .source_info import source_info, SourceInfo
 from .attrmagic import init
-from .connect import (
-    call_and_setattr_connects,
-    getattr_port_refs,
-)
+from .connect import Connectable, is_connectable
 
 
-@call_and_setattr_connects
-@getattr_port_refs
 @init
 class _Instance:
-    """Shared base class for Instance-like types (Instance, InstArray)"""
+    """Shared base class for Instance-like types (Instance, InstanceArray)"""
 
     def __init__(
-        self,
-        of: "Instantiable",
-        *,
-        name: Optional[str] = None,
+        self, of: "Instantiable", *, name: Optional[str] = None,
     ):
-        from .instantiable import is_instantiable
+        from .instantiable import Instantiable, is_instantiable
 
         if not is_instantiable(of):
             raise RuntimeError(f"Invalid Instance of {of}")
@@ -37,18 +30,19 @@ def __init__(
         self.name: Optional[str] = name
         self.of: "Instantiable" = of
         self.conns: Dict[str, "Connectable"] = dict()
-        self.portrefs: Dict[str, "PortRef"] = dict()
-        self._parent_module = None  # Instantiating module
-        self._elaborated = False
-        self._source_info: Optional[SourceInfo] = source_info([__file__])
+
+        # References we give out, either for refering to ports or entries in out own `conns`
+        self._refs = Refs()
+
+        self._parent_module: Optional["Module"] = None  # Instantiating module
+        self._elaborated: bool = False
+        self._source_info: Optional[SourceInfo] = source_info(get_pymodule=False)
 
     def __repr__(self):
         return f"{self.__class__.__name__}(name={self.name} of={self.of})"
 
     @property
-    def _resolved(
-        self,
-    ) -> Optional["Instantiable"]:
+    def _resolved(self,) -> Optional["Instantiable"]:
         """Property to retrieve the Instance's resolved Module, if complete.
         Returns `None` if unresolved."""
         from .generator import GeneratorCall
@@ -57,24 +51,100 @@ def _resolved(
             return self.of.result
         return self.of
 
-
-def _to_array(inst: "Instance", num: int) -> "InstArray":
-    """Create an Instance Array from an Instance"""
-    # Several contraints asserted here which may eventually be relaxed.
-    # * No port-references (yet)
-    # * Not a member of a module (yet)
-    if len(inst.portrefs) > 0:
-        msg = f"Cannot convert Instance {inst} with outstanding port-references {inst.portrefs} to Array"
-        raise RuntimeError(msg)
-    if inst._parent_module is not None:
-        msg = f"Cannot convert Instance {inst} already inserted in Module {inst._parent_module} to Array"
-        raise RuntimeError(msg)
-
-    # Checks out. Create the array.
-    return InstArray(of=inst.of, n=num, name=inst.name)(**inst.conns)
-
-
-def _mult(inst: "Instance", other: int) -> "InstArray":
+    def __getattr__(self, key: str) -> Any:
+        """Port access by getattr"""
+        if not self.__getattribute__("_initialized") or key.startswith("_"):
+            # Bootstrapping phase: do regular getattrs to get started
+            return object.__getattribute__(self, key)
+
+        if key in self.__getattribute__("_specialcases"):  # Special case(s)
+            return object.__getattribute__(self, key)
+
+        # After elaboration, the fancy PortRef creation below goes away. Only return ready-made connections.
+        if self.__getattribute__("_elaborated"):
+            # Pull from the `conns` dict.
+            conns = self.__getattribute__("conns")
+            if key in conns.keys():
+                return conns[key]
+            raise AttributeError(f"No attribute {key} for {self}")
+
+        # Fell through all those cases. Fancy `PortRef` generation time!
+        # Return a `PortRef`, creating one if necessary.
+        return _get_portref(self, key)
+
+    def __call__(self, **kwargs) -> "_Instance":
+        """Connect-by-call"""
+        for key, val in kwargs.items():
+            self.connect(key, val)
+        # Don't forget to retain ourselves at the call-site!
+        return self
+
+    def __setattr__(self, key: str, val: Any) -> None:
+        """Connect-by-setattr"""
+        if not getattr(self, "_initialized", False) or key.startswith("_"):
+            # Bootstrapping phase: do regular setattrs to get started
+            return object.__setattr__(self, key, val)
+        if key in self.__getattribute__("_specialcases"):  # Special case(s)
+            return object.__setattr__(self, key, val)
+        _ = self.connect(key, val)  # Discard the returned `self`
+        return None
+
+    def connect(self, portname: str, conn: Connectable) -> "_Instance":
+        """Connect `conn` to port (name) `portname`.
+        Called by both by-call and by-assignment convenience methods, and usable directly.
+        Direct calls to `connect` will generally be required for ports with otherwise illegal names,
+        e.g. Python language keywords (`in`, `from`, etc.),
+        or Hdl21 internal "keywords" (`name`, `ports`, `signals`, etc.).
+        Returns `self` to aid in method-chaining use-cases."""
+        from .bundle import AnonymousBundle
+
+        if isinstance(conn, Dict):
+            # Special-case dictionaries of connectables into Anon Bundles
+            conn = AnonymousBundle(**conn)
+        if not is_connectable(conn):
+            raise TypeError(f"{self} attempting to connect non-connectable {conn}")
+
+        # The main event: actually stick `conn` in the `conns` dict
+        if portname in self.conns:
+            # Replace and disconnect any prior connection. Disregards the returned old connection.
+            self.replace(portname, conn)
+        else:
+            self.conns[portname] = conn
+            conn._connected_ports.add(_get_connref(self, portname))
+
+        # And return `self` to aid in method-chaining use-cases
+        return self
+
+    def disconnect(self, portname: str) -> Connectable:
+        """Disconnect the port named `portname`.
+        Returns the formerly-connected `Connectable`.
+        Raises a KeyError if the port is not connected."""
+
+        conn = self.conns.pop(portname)
+        conn._connected_ports.remove(_get_connref(self, portname))
+        return conn
+
+    def replace(self, portname: str, conn: Connectable) -> Connectable:
+        """
+        Replace the connection to `portname` with `conn`.
+        Returns the formerly-connected `Connectable`.
+        Raises a KeyError if the port is not connected.
+
+        The `replace` method is functionally identical to serial calls to `disconnect` and `connect`,
+        but allows for in-place modification of the `conns` dict, e.g. while iterating over its items.
+        """
+
+        connref = _get_connref(self, portname)
+        # Get a reference to the old connection in the `conns` dict, without removing it
+        old = self.conns[portname]
+        old._connected_ports.remove(connref)
+        # And replace it in the `conns` dict
+        self.conns[portname] = conn
+        conn._connected_ports.add(connref)
+        return old
+
+
+def _mult(inst: "Instance", other: int) -> "InstanceArray":
     """Instance by integer multiplication.
     Creates an Instance Array of size `other`."""
     if not isinstance(other, int):
@@ -91,14 +161,14 @@ class Instance(_Instance):
         "name",
         "of",
         "conns",
-        "portref",
-        "portrefs",
         "connect",
         "disconnect",
         "replace",
     ]
 
     def __init__(self, *_, **__):
+        # Just call the base-class constructor, and set the `_initialized` flag.
+        # The need to do so is just an enabler for other sub-classes to add constructor action.
         super().__init__(*_, **__)
         self._initialized = True
 
@@ -113,14 +183,14 @@ class InstanceBundle(_Instance):
         "name",
         "of",
         "conns",
-        "portref",
-        "portrefs",
         "connect",
         "disconnect",
         "replace",
     ]
 
     def __init__(self, *_, **__):
+        # Just call the base-class constructor, and set the `_initialized` flag.
+        # The need to do so is just an enabler for other sub-classes to add constructor action.
         super().__init__(*_, **__)
         self._initialized = True
 
@@ -143,7 +213,7 @@ def InstanceBundleType(name: str, bundle: "Bundle", doc: Optional[str] = None) -
     return type(name, (InstanceBundle,), {"bundle": bundle, "__doc__": doc})
 
 
-class InstArray(_Instance):
+class InstanceArray(_Instance):
     """Array of `n` Instances"""
 
     _specialcases = [
@@ -158,10 +228,7 @@ class InstArray(_Instance):
     ]
 
     def __init__(
-        self,
-        of: "Instantiable",
-        n: int,
-        name: Optional[str] = None,
+        self, of: "Instantiable", n: int, name: Optional[str] = None,
     ):
         super().__init__(of=of, name=name)
         self.n = n
@@ -174,8 +241,104 @@ def __setitem__(self, _idx: Any, _val: Any):
         return RuntimeError(f"Illegal indexing into Array {self}")
 
 
+""" 
+Module-level functions 
+These could be instance methods, but are kept here to minimally pollute their namespaces.
+"""
+
+
+def _to_array(inst: Instance, num: int) -> InstanceArray:
+    """Create an Instance Array from an Instance"""
+    # Several contraints asserted here which may eventually be relaxed.
+    # * No port-references (yet)
+    # * Not a member of a module (yet)
+    if len(inst._refs.portrefs) > 0:
+        msg = f"Cannot convert Instance {inst} with outstanding port-references {inst._portrefs} to Array"
+        raise RuntimeError(msg)
+    if inst._parent_module is not None:
+        msg = f"Cannot convert Instance {inst} already inserted in Module {inst._parent_module} to Array"
+        raise RuntimeError(msg)
+
+    # Checks out. Create the array.
+    return InstanceArray(of=inst.of, n=num, name=inst.name)(**inst.conns)
+
+
+@dataclass
+class Refs:
+    """ Tracking of references stored on each Instance. 
+    All references are of type `PortRef`, and are organized into two camps: 
+    
+    * The `portrefs` dict includes entries like `i.someport` below: 
+    ```
+    i = Instance(of=MyModule)
+    i2 = Instance(of=AnotherModule)(itsport=i.someport)
+    ``` 
+    These are ultimately resolved to Signals during elaboration. 
+
+    * The `conns` dict includes entries like `i.someport` below:
+    ```
+    s = Signal()
+    i = Instance(of=MyModule)(someport=s)
+    ```
+    Here the Signal `s` is given a port-reference to `i`, to help track and update later connections. 
+    These entries are members of the `connrefs` dict. 
+
+    The combination of the two is stored in the `all` dictionary. 
+    All items in both `portrefs` and `connrefs` are always also located in `all`. 
+    Attemptes to retrieve a reference come from `all`, and therefore generate the same 
+    objects for successive fetches of the same port name. 
+
+    Entries are never removed from `refs` over the course of an Instance's lifetime. 
+    For most Instances, by the end of their life, the `connrefs` and `all` dicts will contain 
+    an entry for each of its ports. 
+    """
+
+    all: Dict[str, "PortRef"] = field(default_factory=dict)
+    portrefs: Dict[str, "PortRef"] = field(default_factory=dict)
+    connrefs: Dict[str, "PortRef"] = field(default_factory=dict)
+
+
+def _get_portref(self: _Instance, key: str) -> "PortRef":
+    """Return a port-reference to name `key`, creating it if necessary."""
+    from .portref import PortRef
+
+    # Check in our existing references
+    refs = self.__getattribute__("_refs")
+    if key in refs.all:
+        refs.portrefs[key] = refs.all[key]
+        return refs.all[key]
+
+    # New reference; create, add, and return it
+    ref = PortRef(inst=self, portname=key)
+    refs.portrefs[key] = refs.all[key] = ref
+    return ref
+
+
+def _get_connref(self: _Instance, key: str) -> "PortRef":
+    """Return a connection-reference to name `key`, creating it if necessary."""
+    from .portref import PortRef
+
+    # Check in our existing references
+    refs = self.__getattribute__("_refs")
+    if key in refs.all:
+        refs.connrefs[key] = refs.all[key]
+        return refs.all[key]
+
+    # New reference; create, add, and return it
+    ref = PortRef(inst=self, portname=key)
+    refs.connrefs[key] = refs.all[key] = ref
+    return ref
+
+
+""" 
+Instantiation Decorator 
+"""
+
+
 def calls_instantiate(cls: type) -> type:
-    """Decorator which adds 'calls produce `hdl21.Instance`s' functionality."""
+    """# Calls Instantiate
+    Decorator which adds 'calls produce `hdl21.Instance`s' functionality.
+    Added to `Module`, `GeneratorCall`, and everything else that is `Instantiable`."""
 
     def __call__(self, **kwargs) -> Instance:
         """Calls Create `hdl21.Instances`,
@@ -192,9 +355,13 @@ def __call__(self, **kwargs) -> Instance:
     return cls
 
 
+"""
+Selected star-exports 
+Notably excludes the base class, and module-level functions
+"""
 __all__ = [
     "Instance",
-    "InstArray",
+    "InstanceArray",
     "InstanceBundle",
     "InstanceBundleType",
     "calls_instantiate",
diff --git a/hdl21/instantiable.py b/hdl21/instantiable.py
index 99f3d64..a1a4b74 100644
--- a/hdl21/instantiable.py
+++ b/hdl21/instantiable.py
@@ -20,6 +20,8 @@ def is_instantiable(val: Any) -> bool:
 
 
 def qualname(i: Instantiable) -> str:
+    """Path-qualified name of `i`"""
+
     if isinstance(i, Module):
         return i._qualname()
     if isinstance(i, GeneratorCall):
diff --git a/hdl21/module.py b/hdl21/module.py
index 6984cf7..80286e1 100644
--- a/hdl21/module.py
+++ b/hdl21/module.py
@@ -7,18 +7,17 @@
 * The `@module` (lower-case) decorator-function, for class-syntax creation of `Module`s
 """
 
-import inspect
-from types import ModuleType
 from typing import Any, Optional, List, Union, get_args, Type, Dict
 from pydantic.dataclasses import dataclass
-from dataclasses import field
 
 # Local imports
+from .default import Default
+from .call import param_call
 from .source_info import source_info, SourceInfo
 from .attrmagic import init
-from .params import NoParams, HasNoParams, isparamclass
+from .params import HasNoParams, isparamclass
 from .signal import Signal, Visibility
-from .instance import calls_instantiate, _Instance, Instance, InstArray, InstanceBundle
+from .instance import calls_instantiate, _Instance, Instance, InstanceArray, InstanceBundle
 from .bundle import BundleInstance
 
 
@@ -65,11 +64,7 @@ def __init__(self, *, name: Optional[str] = None):
         self.bundles = dict()
         self.namespace = dict()  # Combination of all these
 
-        self._source_info = source_info([__file__])
-        self._pymodule = (
-            self._source_info.pymodule
-        )  # FIXME: move to operating on `SourceInfo` instead
-
+        self._source_info: Optional[SourceInfo] = source_info(get_pymodule=True)
         self._importpath = None  # Optional field set by importers
         self._moduledata = None  # Optional[ModuleData]
         self._updated = True  # Flag indicating whether we've been updated since creating `_moduledata`
@@ -159,7 +154,7 @@ def _add(self, val: ModuleAttr) -> ModuleAttr:
         elif isinstance(val, Instance):
             self.instances[val.name] = val
             self.namespace[val.name] = val
-        elif isinstance(val, InstArray):
+        elif isinstance(val, InstanceArray):
             self.instarrays[val.name] = val
             self.namespace[val.name] = val
         elif isinstance(val, InstanceBundle):
@@ -231,24 +226,14 @@ def io(self) -> dict:
         return rv
 
     def _defpath(self) -> str:
-        """Helper for exporting.
-        Returns a string representing "where" this module was defined.
-        This is generally one of a few things:
-        * If "normally" defined via Python code, it's the Python module path
-        * If *imported*, it's the path inferred during import"""
-        if self._importpath:  # Imported. Return the period-separated import path.
-            return ".".join(self._importpath)
-        # Defined the old fashioned way. Use the Python module name.
-        return self._pymodule.__name__
+        return _defpath(self)
 
     def _qualname(self) -> Optional[str]:
-        """Helper for exporting. Returns the path-qualified name including
-        `_defpath` options above, and the `Module.name`."""
-        if self.name is None:
-            return None
-        return self._defpath() + "." + self.name
+        return _qualname(self)
 
     def __eq__(self, other: "Module") -> bool:
+        if not isinstance(other, Module):
+            return NotImplemented
         if self.name is None or other.name is None:
             raise RuntimeError(f"Cannot invoke equality on unnamed Module {self}")
         return self._qualname() == other._qualname()
@@ -342,20 +327,17 @@ class ExternalModule:
     paramtype: Type = HasNoParams  # Parameter-type `paramclass`
     desc: Optional[str] = None  # Description
     domain: Optional[str] = None  # Domain name, for references upon export
-    pymodule: Optional[ModuleType] = field(repr=False, init=False, default=None)
-    importpath: Optional[List[str]] = field(repr=False, init=False, default=None)
 
     def __post_init__(self):
         # Check for a valid parameter-type
         if not isparamclass(self.paramtype) and self.paramtype not in (dict, Dict):
-            msg = (
-                f"Invalid `ExternalModule` parameter type {self.paramtype} for {self}. "
-            )
+            msg = f"Invalid parameter type {self.paramtype} for {self}. "
             msg += "Param types must be either `@paramclass`es or `dict`."
             raise ValueError(msg)
+
         # Internal tracking data: defining module/import-path
-        self.pymodule = _caller_pymodule()
-        self.importpath = None
+        self._source_info: Optional[SourceInfo] = source_info(get_pymodule=True)
+        self._importpath = None
 
     def __post_init_post_parse__(self):
         """After type-checking, do some more checks on values"""
@@ -366,7 +348,8 @@ def __post_init_post_parse__(self):
                 msg = f"Invalid Primitive Port {p.name} on {self.name}; must have PORT visibility"
                 raise ValueError(msg)
 
-    def __call__(self, params: Any = NoParams) -> "ExternalModuleCall":
+    def __call__(self, arg: Any = Default, **kwargs) -> "ExternalModuleCall":
+        params = param_call(callee=self, arg=arg, **kwargs)
         return ExternalModuleCall(module=self, params=params)
 
     @property
@@ -374,24 +357,14 @@ def ports(self) -> dict:
         return {p.name: p for p in self.port_list}
 
     def _defpath(self) -> str:
-        """Helper for exporting.
-        Returns a string representing "where" this module was defined.
-        This is generally one of a few things:
-        * If "normally" defined via Python code, it's the Python module path
-        * If *imported*, it's the path inferred during import"""
-        if self.importpath:  # Imported. Return the period-separated import path.
-            return ".".join(self.importpath)
-        # Defined the old fashioned way. Use the Python module name.
-        return self.pymodule.__name__
+        return _defpath(self)
 
     def _qualname(self) -> Optional[str]:
-        """Helper for exporting. Returns the path-qualified name including
-        `_defpath` options above, and the `Module.name`."""
-        if self.name is None:
-            return None
-        return self._defpath() + "." + self.name
+        return _qualname(self)
 
     def __eq__(self, other: "ExternalModule") -> bool:
+        if not isinstance(other, ExternalModule):
+            return NotImplemented
         if self.name is None or other.name is None:
             raise RuntimeError(f"Cannot invoke equality on unnamed Module {self}")
         return self._qualname() == other._qualname()
@@ -406,13 +379,44 @@ def __getstate__(self):
             raise RuntimeError(f"Cannot invoke pickling on unnamed Module {self}")
         return self._qualname()
 
+    @property
+    def Params(self) -> Type:
+        return self.paramtype
+
+
+def _qualname(mod: Union[Module, ExternalModule]) -> Optional[str]:
+    """# Qualified Name
+    Helper for exporting. Returns a module or import path-qualified name."""
+
+    if mod.name is None:
+        return None
+    return mod._defpath() + "." + mod.name
+
+
+def _defpath(mod: Union[Module, ExternalModule]) -> str:
+    """# Definition Path
+    Helper for exporting.
+    Returns a string representing "where" this module was defined.
+    This is generally one of a few things:
+    * If "normally" defined via Python code, it's the Python module path
+    * If *imported*, it's the path inferred during import"""
+
+    if mod._importpath is not None:
+        # Imported. Return the period-separated import path.
+        return ".".join(mod._importpath)
+
+    # Defined the old fashioned way. Use the Python module name.
+    if mod._source_info.pymodule is None:
+        raise RuntimeError(f"{mod} is not defined in Python")
+    return mod._source_info.pymodule.__name__
+
 
 @calls_instantiate
 @dataclass
 class ExternalModuleCall:
-    """External Module Call
+    """# External Module Call
     A combination of an `ExternalModule` and its Parameter-values,
-    typically generated by calling the Module."""
+    typically generated by calling the `ExternalModule`."""
 
     module: ExternalModule
     params: Any
@@ -423,6 +427,10 @@ def __post_init_post_parse__(self):
             msg = f"Invalid parameter type {type(self.params)} for ExternalModule {self.module.name}. Must be {self.module.paramtype}"
             raise TypeError(msg)
 
+    @property
+    def name(self) -> Optional[str]:
+        return self.module.name
+
     @property
     def ports(self) -> dict:
         return self.module.ports
@@ -465,25 +473,4 @@ def _attr_type_error(m: Module, val: Any) -> None:
     raise TypeError(msg)
 
 
-def _caller_pymodule() -> Optional[ModuleType]:
-    """# Caller Py-Module
-    Find the (python) module of the first python-stack-frame not from *this* file.
-
-    Sometimes this will be `hdl21.generator`. That's OK, `Generator`s know how to figure it out.
-
-    Returns `None` if *no* python modules are in the call-stack.
-    This can happen, for example when running `python -c "import hdl21 as h; h.Module()"`,
-    or (probably) through other interactive interpreter setups.
-
-    These "(python) module-less Modules" will generally fail in later steps
-    such as exporting to ProtoBuf and netlists, but can be created in-memory.
-    """
-    for fr in inspect.stack():
-        # Note frames produce an `Optional[ModuleType]`.
-        # (It seems extension modules may not have one.)
-        # So be sure to check for `None`!
-        pymod = inspect.getmodule(fr[0])
-        if pymod is not None and pymod.__file__ != __file__:
-            return pymod
-    # No caller module found, anywhere in the call-stack.
-    return None
+__all__ = ["module", "Module", "ExternalModule", "ExternalModuleCall"]
diff --git a/hdl21/noconn.py b/hdl21/noconn.py
new file mode 100644
index 0000000..6fd9b16
--- /dev/null
+++ b/hdl21/noconn.py
@@ -0,0 +1,47 @@
+"""
+# No-Connect
+
+Special placeholder connectable-object which indicates "unconnected" Ports,
+typically unconnected outputs.
+
+An optional `name` field allows guidance for external netlisting,
+for cases in which consistent naming is desirable (e.g. for waveform probing).
+"""
+
+from typing import Optional, Set
+
+# Local imports
+from .datatype import datatype
+from .connect import connectable
+from .concat import concatable
+from .portref import PortRef
+
+
+@concatable
+@connectable
+@datatype
+class NoConn:
+    """
+    # No-Connect
+
+    Special placeholder connectable-object which indicates "unconnected" Ports,
+    typically unconnected outputs.
+
+    An optional `name` field allows guidance for external netlisting,
+    for cases in which consistent naming is desirable (e.g. for waveform probing).
+    """
+
+    name: Optional[str] = None
+
+    def __post_init_post_parse__(self) -> None:
+        # Internal management data
+        # Connected port references
+        self._connected_ports: Set[PortRef] = set()
+
+    def __eq__(self, other: "NoConn") -> bool:
+        """`NoConn`s are "equal" only if identical objects."""
+        return self is other
+
+    def __hash__(self) -> int:
+        """`NoConn`s are "equal" only if identical objects."""
+        return hash(id(self))
diff --git a/hdl21/params.py b/hdl21/params.py
index 7db9ab4..836db81 100644
--- a/hdl21/params.py
+++ b/hdl21/params.py
@@ -2,42 +2,15 @@
 Hdl21 Parameters and Param-Classes 
 """
 
-from typing import Optional, Any
+from typing import Optional, Union, Any, Dict
 import dataclasses
 import json
 import pickle
 import hashlib
 import pydantic
 
-
-# Import a few likely common Exception-types,
-# Not used directly here, but likely by users and tests
-from pydantic import ValidationError
-from dataclasses import FrozenInstanceError
-
-
-class _Default:
-    """The cardinal value for unspecified parameter-declaration defaults.
-    Normally the class-object itself would work for this, but `pydantic.dataclasses`
-    seems to have some problems accepting class-objects.
-    https://github.com/samuelcolvin/pydantic/issues/1537
-    So we create this singleton instance `_default`, AKA `_Default._the_one`.
-
-    FIXME: this pydantic issue was updated and closed at some point,
-    potentially enabling removint this singleton stuff.
-    Check out the pydantic updates along with https://github.com/dan-fritchman/Hdl21/issues/15
-    """
-
-    _the_one = None
-
-    def __new__(cls, *_, **__):
-        if cls._the_one is None:
-            cls._the_one = super().__new__(cls)
-        return cls._the_one
-
-
-# Create that singleton
-_default = _Default()
+# Local Imports
+from .default import Default
 
 
 def paramclass(cls: type) -> type:
@@ -72,12 +45,17 @@ class C:
     * *All* non-Python-internal fields must be of type `Param`
     * Inheritance is not supported
     """
+
     if cls.__bases__ != (object,):
         raise RuntimeError(f"Invalid @hdl21.paramclass inheriting from {cls.__bases__}")
+    
     protected_names = ["descriptions", "defaults"]
     dunders = dict()
     params = dict()
+
     # Take a lap through the class dictionary, type-check everything and grab Params
+    # FIXME: look for, and alert users about, the error writing type annotations rather than equality. 
+    # (Or, we could move to type annotations...)
     for key, val in cls.__dict__.items():
         if key in protected_names:
             raise RuntimeError(f"Invalid field name {key} in paramclass {cls}")
@@ -93,9 +71,9 @@ class C:
     fields = list()
     for name, par in params.items():
         field = [name, par.dtype]
-        if par.default is not _default:
+        if par.default is not Default:
             field.append(dataclasses.field(default=par.default))
-        elif par.default_factory is not _default:
+        elif par.default_factory is not Default:
             raise NotImplementedError(f"Param.default_factory for {cls}")
             field.append(dataclasses.field(default_factory=par.default_factory))
         fields.append(tuple(field))
@@ -110,7 +88,7 @@ class C:
             lambda cls: {
                 k: v.default
                 for k, v in cls.__params__.items()
-                if v.default is not _default
+                if v.default is not Default
             }
         ),
     )
@@ -139,8 +117,8 @@ class Param:
 
     dtype: Any  # Datatype. Required
     desc: str  # Description. Required
-    default: Optional[Any] = _default  # Default Value. Optional
-    default_factory: Optional[Any] = _default  # Default Call-Factory. Optional
+    default: Optional[Any] = Default  # Default Value. Optional
+    default_factory: Optional[Any] = Default  # Default Call-Factory. Optional
 
 
 def _unique_name(params: Any) -> str:
@@ -232,8 +210,15 @@ def hdl21_naming_encoder(obj: Any) -> Any:
 
 # Shortcut for parameter-less generators.
 # Defines the empty-value `@paramclass`.
-HasNoParams = paramclass(
-    dataclasses.make_dataclass("NoParams", fields=[], namespace={}, frozen=True)
-)
+@paramclass
+class HasNoParams:
+    """# HasNoParams
+    A built-in `hdl21.paramclass` for generators that require no parameters."""
+
+    ...  # Empty Contents
+
+
 # And an instance of it
 NoParams = HasNoParams()
+
+__all__ = ["paramclass", "Param", "HasNoParams", "NoParams"]
diff --git a/hdl21/portref.py b/hdl21/portref.py
index 778bc5d..c09ded5 100644
--- a/hdl21/portref.py
+++ b/hdl21/portref.py
@@ -1,33 +1,45 @@
-from dataclasses import field
-from typing import Set, Dict
+from copy import copy
+from typing import Set, Union, Optional
 
 # Local imports
 from .datatype import datatype
-from .connect import connectable, track_connected_ports
+from .connect import connectable
+from .sliceable import sliceable
+from .concat import concatable
 from .instance import _Instance
 
 
-@track_connected_ports
+@concatable
+@sliceable
 @connectable
 @datatype
 class PortRef:
-    """# PortRef
+    """
+    # PortRef
     Reference to a Port
-    Created from a combination of a parent `inst` and a port-name."""
+    Created from a combination of a parent `Instance` and a port-name.
+    """
 
     # Core fields: the Instance and port name
     inst: _Instance
     portname: str
 
-    # Internal, ideally private, fields
-    portrefs: Dict[str, "PortRef"] = field(default_factory=dict, init=False, repr=False)
-    connected_ports: Set["PortRef"] = field(default_factory=set, init=False, repr=False)
+    def __post_init_post_parse__(self):
+        # Inner management data
+        self._connected_ports: Set[PortRef] = set()
+        self.resolved: Union[None, "Signal", "BundleInstance"] = None
+        self._slices: Set["Slice"] = set()
+        self._concats: Set["Concat"] = set()
+        self._width: Optional[int] = None
 
     def __eq__(self, other) -> bool:
         """Port-reference equality requires *identity* between instances
         (and of course equality of port-name)."""
+        if not isinstance(other, PortRef):
+            return False
         return self.inst is other.inst and self.portname == other.portname
 
     def __hash__(self):
         """Hash references as the tuple of their instance-address and name"""
         return hash((id(self.inst), self.portname))
+
diff --git a/hdl21/primitives.py b/hdl21/primitives.py
index 7adb106..86ebc02 100644
--- a/hdl21/primitives.py
+++ b/hdl21/primitives.py
@@ -40,10 +40,12 @@
 import copy
 from dataclasses import replace
 from enum import Enum
-from typing import Optional, Any, List, Type, Union
+from typing import Optional, Any, List, Type, Union, Dict
 from pydantic.dataclasses import dataclass
 
 # Local imports
+from .default import Default
+from .call import param_call
 from .params import paramclass, Param, isparamclass, NoParams
 from .signal import Port, Signal, Visibility
 from .instance import calls_instantiate
@@ -88,7 +90,8 @@ def __post_init_post_parse__(self):
                 msg = f"Invalid Primitive Port {p.name} on {self.name}; must have PORT visibility"
                 raise ValueError(msg)
 
-    def __call__(self, params: Any = NoParams) -> "PrimitiveCall":
+    def __call__(self, arg: Any = Default, **kwargs) -> "PrimitiveCall":
+        params = param_call(callee=self, arg=arg, **kwargs)
         return PrimitiveCall(prim=self, params=params)
 
     @property
@@ -96,7 +99,7 @@ def Params(self) -> Type:
         return self.paramtype
 
     @property
-    def ports(self) -> dict:
+    def ports(self) -> Dict[str, Signal]:
         return {p.name: p for p in self.port_list}
 
 
diff --git a/hdl21/proto/from_proto.py b/hdl21/proto/from_proto.py
index 90ecd3e..ba8de2b 100644
--- a/hdl21/proto/from_proto.py
+++ b/hdl21/proto/from_proto.py
@@ -13,7 +13,9 @@
 from ..prefix import Prefix, Prefixed
 from ..module import Module, ExternalModule
 from ..instance import Instance
-from ..signal import Signal, PortDir, Slice, Concat, Visibility
+from ..signal import Signal, PortDir, Visibility
+from ..slice import Slice
+from ..concat import Concat
 from .. import primitives
 from ..primitives import Primitive
 
@@ -84,7 +86,7 @@ def import_external_module(self, pmod: vckt.ExternalModule) -> ExternalModule:
             paramtype=dict,  # FIXME: should these be stored in the serialization schema?
         )
         # Give it a (non-initializer) value for its `importpath`
-        emod.importpath = [pmod.name.domain]
+        emod._importpath = [pmod.name.domain]
         # Cache and return it
         self.ext_modules[key] = emod
         return emod
@@ -154,10 +156,7 @@ def import_instance(self, pinst: vckt.Instance) -> Instance:
                 target = import_vlsir_primitive(ref.external)
                 params = target.Params(**params)
 
-            elif ref.external.domain in (
-                "hdl21.primitives",
-                "hdl21.ideal",
-            ):
+            elif ref.external.domain in ("hdl21.primitives", "hdl21.ideal",):
                 # Retrieve the Primitive from `hdl21.primitives`, and convert its parameters
                 target = import_hdl21_primitive(ref.external)
                 params = target.Params(**params)
@@ -288,9 +287,9 @@ def import_connection_target(
         raise RuntimeError(f"Invalid Signal {sname} in Module {module.name}")
     # Now chop this up if it's a Slice
     if stype == "slice":
-        start = bot = pconn.slice.bot
-        stop = top = pconn.slice.top + 1  # Move to Python-style exclusive indexing
-        sig = Slice(signal=sig, top=top, bot=bot, start=start, stop=stop, step=None)
+        start = pconn.slice.bot
+        stop = pconn.slice.top + 1  # Move to Python-style exclusive indexing
+        sig = Slice(parent=sig, index=slice(start, stop))
     return sig
 
 
diff --git a/hdl21/proto/to_proto.py b/hdl21/proto/to_proto.py
index 0fc45bd..c15f3f6 100644
--- a/hdl21/proto/to_proto.py
+++ b/hdl21/proto/to_proto.py
@@ -28,7 +28,9 @@
 from ..elab import Elaboratables, elaborate
 from ..module import Module, ExternalModule, ExternalModuleCall
 from ..instance import Instance
-from .. import signal
+from ..signal import Signal, Port, PortDir
+from ..slice import Slice
+from ..concat import Concat
 from ..primitives import (
     PrimitiveCall,
     PrimitiveType,
@@ -38,9 +40,7 @@
 
 
 def to_proto(
-    top: Elaboratables,
-    domain: Optional[str] = None,
-    **kwargs,
+    top: Elaboratables, domain: Optional[str] = None, **kwargs,
 ) -> vckt.Package:
     """Convert Elaborate-able Module or Generator `top` and its dependencies to a Proto-format `Package`."""
     # Elaborate all the top-level Modules
@@ -230,7 +230,7 @@ def export_instance(self, inst: Instance) -> vckt.Instance:
         return pinst
 
 
-def export_port(port: signal.Port) -> vckt.Port:
+def export_port(port: Port) -> vckt.Port:
     """Export a `Port`"""
     pport = vckt.Port()
     pport.direction = export_port_dir(port)
@@ -238,31 +238,31 @@ def export_port(port: signal.Port) -> vckt.Port:
     return pport
 
 
-def export_port_dir(port: signal.Port) -> vckt.Port.Direction:
+def export_port_dir(port: Port) -> vckt.Port.Direction:
     """Convert between Port-Direction Enumerations"""
-    if port.direction == signal.PortDir.INPUT:
+    if port.direction == PortDir.INPUT:
         return vckt.Port.Direction.INPUT
-    if port.direction == signal.PortDir.OUTPUT:
+    if port.direction == PortDir.OUTPUT:
         return vckt.Port.Direction.OUTPUT
-    if port.direction == signal.PortDir.INOUT:
+    if port.direction == PortDir.INOUT:
         return vckt.Port.Direction.INOUT
-    if port.direction == signal.PortDir.NONE:
+    if port.direction == PortDir.NONE:
         return vckt.Port.Direction.NONE
     raise ValueError(f"Invalid PortDir {port.direction}")
 
 
 def export_connection_target(
-    sig: Union[signal.Signal, signal.Slice, signal.Concat]
+    sig: Union[Signal, Slice, Concat]
 ) -> vckt.ConnectionTarget:
     """Export a proto `ConnectionTarget`"""
 
     pconn = vckt.ConnectionTarget()  # Create a proto-Connection
-    if isinstance(sig, signal.Signal):
+    if isinstance(sig, Signal):
         pconn.sig = sig.name
-    elif isinstance(sig, signal.Slice):
+    elif isinstance(sig, Slice):
         pslice = export_slice(sig)
         pconn.slice.CopyFrom(pslice)
-    elif isinstance(sig, signal.Concat):
+    elif isinstance(sig, Concat):
         pconc = export_concat(sig)
         pconn.concat.CopyFrom(pconc)
     else:
@@ -271,22 +271,22 @@ def export_connection_target(
     return pconn
 
 
-def export_slice(slize: signal.Slice) -> vckt.Slice:
+def export_slice(slize: Slice) -> vckt.Slice:
     """Export a signal-`Slice`.
     Fails if the parent is not a concrete `Signal`, i.e. it is a `Concat` or another `Slice`.
     Fails for non-unit step-sizes, which should be converted to `Concat`s upstream."""
-    if not isinstance(slize.signal, signal.Signal):
-        msg = f"Export error: {slize} has a parent {slize.signal} which is not a concrete Signal"
+    if not isinstance(slize.parent, Signal):
+        msg = f"Export error: {slize} has a parent {slize.parent} which is not a concrete Signal"
         raise RuntimeError(msg)
-    if slize.step is not None and slize.step != 1:
+    if slize.step != 1:
         msg = f"Export error: {slize} has non-unit step"
         raise RuntimeError(msg)
 
     # Move to HDL-style indexing, with inclusive `top` index.
-    return vckt.Slice(signal=slize.signal.name, top=slize.top - 1, bot=slize.bot)
+    return vckt.Slice(signal=slize.parent.name, top=slize.top - 1, bot=slize.bot)
 
 
-def export_concat(concat: signal.Concat) -> vckt.Concat:
+def export_concat(concat: Concat) -> vckt.Concat:
     """Export (potentially recursive) Signal Concatenations"""
     pconc = vckt.Concat()
     for part in concat.parts:
diff --git a/hdl21/signal.py b/hdl21/signal.py
index 9dd4a1f..30c0b7c 100644
--- a/hdl21/signal.py
+++ b/hdl21/signal.py
@@ -23,99 +23,13 @@
 from copy import copy
 from enum import Enum
 from dataclasses import field
-from typing import Callable, Optional, Any, List, Union, Set
-from pydantic.dataclasses import dataclass
+from typing import Callable, Optional, List, Set
 
 # Local imports
-from .connect import connectable, is_connectable
-from .portref import PortRef
-
-
-def _slice_(*, parent: "Sliceable", key: Union[int, slice]) -> "Slice":
-    """Square-bracket slicing into Signals, Concatenations, and Slices.
-    Assuming valid inputs, returns a signal-`Slice`.
-
-    Signal slices are indexed "Python style", in the senses that:
-    * Negative indices are supported, and count from the "end" of the Signal.
-    * Slice-ranges such as `sig[0:2]` are supported, and *inclusive* of the start, while *exclusive* of the end index.
-    * Negative-range slices such as `sig[2:0:-1]`, again *inclusive* of the start, *exclusive* of the end index, and *reversed*.
-    Popular HDLs commonly use different signal-indexing conventions.
-    Hdl21's own primary exchange format (in ProtoBuf) does as well,
-    eschewing adopting inclusive-endpoints and negative-indexing.
-    """
-
-    if isinstance(key, int):
-        if key >= parent.width:
-            raise ValueError(f"Out-of-bounds index {key} into {parent}")
-        if key < 0:
-            key += parent.width
-        return Slice(
-            signal=parent, top=key + 1, bot=key, start=key, stop=None, step=None
-        )
-
-    if isinstance(key, slice):
-        # Note these `slice` attributes are descriptor-things, and they get weird, fast.
-        # Extracting their three key fields the most-hardest way via `__getattribute__` seems to work cleanest.
-        start = slice.__getattribute__(key, "start")
-        stop = slice.__getattribute__(key, "stop")
-        step = slice.__getattribute__(key, "step")
-
-        stepsz = 1 if step is None else step
-        if stepsz == 0:
-            raise ValueError(f"slice step cannot be zero")
-        elif stepsz < 0:
-            # Here `top` gets a "+1" since `start` is *inclusive*, while `bot` gets "+1" as `stop` is *exclusive*.
-            top = (
-                parent.width
-                if start is None
-                else start + 1
-                if start >= 0
-                else parent.width + start + 1
-            )
-            bot = (
-                0
-                if stop is None
-                else stop + 1
-                if stop >= 0
-                else parent.width + stop + 1
-            )
-            # Align bot with the step
-            bot += (top - bot) % abs(stepsz)
-        else:
-            # Here `start` and `stop` match `top` and `bot`'s inclsive/ exclusivity.
-            # No need to add any offsets.
-            top = (
-                parent.width
-                if stop is None
-                else stop
-                if stop >= 0
-                else parent.width + stop
-            )
-            bot = 0 if start is None else start if start >= 0 else parent.width + start
-            # Align top with the step
-            top -= (top - bot) % stepsz
-
-        # Create and return our Slice. More checks are done in its constructor.
-        return Slice(signal=parent, top=top, bot=bot, step=step, start=start, stop=stop)
-
-    raise TypeError(f"Invalid slice-type {key} into {parent}")
-
-
-def slices(cls: type) -> type:
-    """Decorator to add the 'square-bracket indexing produces `Slice`s' behavior."""
-
-    if getattr(cls, "__getitem__", None) is not None:
-        msg = f"Internal hdl21 Error: invavlid `slices`-decoration of {cls} with existing __getitem__"
-        raise RuntimeError(msg)
-
-    def __getitem__(self, key: Any) -> "Slice":
-        return _slice_(parent=self, key=key)
-
-    # Add the new behavior to the class
-    cls.__getitem__ = __getitem__
-    cls.__getitem__.__doc__ = _slice_.__doc__
-    # And don't forget to return that class!
-    return cls
+from .datatype import datatype
+from .connect import connectable
+from .sliceable import sliceable
+from .concat import concatable
 
 
 class PortDir(Enum):
@@ -134,9 +48,10 @@ class Visibility(Enum):
     PORT = 1  # Exposed as a Port
 
 
-@slices
+@sliceable
+@concatable
 @connectable
-@dataclass
+@datatype
 class Signal:
     """
     # hdl21 Signal
@@ -151,14 +66,21 @@ class Signal:
     src: Optional[Enum] = field(repr=False, default=None)
     dest: Optional[Enum] = field(repr=False, default=None)
 
-    # Internal, ideally private fields
-    # Connected port references
-    connected_ports: Set[PortRef] = field(init=False, repr=False, default_factory=set)
-
     def __post_init_post_parse__(self):
-        self._parent_module: Optional["Module"] = None
         if self.width < 1:
             raise ValueError(f"Signal {self.name} width must be positive")
+        self._parent_module: Optional["Module"] = None
+        self._slices: Set["Slice"] = set()
+        self._concats: Set["Concat"] = set()
+        self._connected_ports: Set["PortRef"] = set()
+
+    def __eq__(self, other) -> bool:
+        # Identity is equality
+        return id(self) == id(other)
+
+    def __hash__(self) -> bool:
+        # Identity is equality
+        return hash(id(self))
 
 
 def _copy_to_internal(sig: Signal) -> Signal:
@@ -268,111 +190,3 @@ def _plural(*, fn: Callable, num: int, **kwargs) -> List[Signal]:
     for _ in range(num):
         rv.append(fn(**kwargs))
     return rv
-
-
-@slices
-@connectable
-class Concat:
-    """Signal Concatenation
-    Uses *Python-convention* ordering, in which "LSBs", i.e. index 0, are specified first."""
-
-    def __init__(self, *parts):
-        for p in parts:
-            if not is_connectable(p):
-                raise TypeError(f"Signal-concatenating unconnectable object {p}")
-        self.parts = tuple(parts)
-        if self.width < 1:
-            raise ValueError(f"Invalid Zero-Width Concat of {self.parts}")
-
-    @property
-    def width(self):
-        return sum([s.width for s in self.parts])
-
-    def __eq__(self, _other) -> bool:
-        # Concat-equality can be implemented, but has plenty of edge cases
-        # not yet worked through. For example slicing and re-concatenation:
-        # ```python
-        # s = h.Signal(width=2)      # Create a 2-bit signal
-        # s == h.Concat(s[0], s[1])  # Slice and re-concatenate it
-        # # Should that be True of False?
-        # ```
-        return NotImplemented
-
-    def __repr__(self):
-        return f"Concat(width={self.width}, parts={len(self.parts)})"
-
-
-@slices
-@connectable
-@dataclass
-class Slice:
-    """Signal Slice, comprising a subset of its width"""
-
-    signal: "Sliceable"  # Parent Signal
-    top: int  # Top index (exclusive)
-    bot: int  # Bottom index (inclusive)
-    start: Optional[int]  # Python-convention start index
-    stop: Optional[int]  # Python-convention stop index
-    step: Optional[int]  # Python-convention step size
-
-    index: Optional[Union[int, slice]] = None  # Python-convention index
-
-    def __post_init_post_parse__(self):
-        if self.step is not None and self.step == 0:
-            raise ValueError(f"Invalid Slice into {self.signal}: step==0")
-        if self.width < 1:
-            raise ValueError(f"Invalid Slice into {self.signal}: width={self.width}")
-        if self.top <= self.bot:
-            msg = f"Invalid Slice into {self.signal}: top={self.top} <= bot={self.bot}"
-            raise ValueError(msg)
-
-    @property
-    def width(self) -> int:
-        """Slice Width Accessor"""
-        step = 1 if self.step is None else abs(self.step)
-        return (self.top - self.bot) // step
-
-    def __eq__(self, other: "Slice") -> bool:
-        """Slice equality requires *identity* between parent Signals"""
-        if not isinstance(other, Slice):
-            return NotImplemented
-        return (
-            self.signal is other.signal
-            and self.top == other.top
-            and self.bot == other.bot
-            and self.step == other.step
-        )
-
-
-# Slice-compatible type aliases
-Sliceable = Union[Signal, Concat, Slice]
-
-Slice.__pydantic_model__.update_forward_refs()
-
-
-@connectable
-@dataclass
-class NoConn:
-    """
-    # No-Connect
-
-    Special placeholder connectable-object which indicates "unconnected" Ports,
-    typically unconnected outputs.
-
-    An optional `name` field allows guidance for external netlisting,
-    for cases in which consistent naming is desirable (e.g. for waveform probing).
-    """
-
-    name: Optional[str] = None
-
-    # Internal, ideally private fields
-    # Connected port references
-    connected_ports: Set[PortRef] = field(init=False, repr=False, default_factory=set)
-
-    def __eq__(self, other: "NoConn") -> bool:
-        """`NoConn`s are "equal" only if identical objects."""
-        return self is other
-
-    def __hash__(self) -> int:
-        """`NoConn`s are "equal" only if identical objects."""
-        return hash(id(self))
diff --git a/hdl21/slice.py b/hdl21/slice.py
new file mode 100644
index 0000000..513775c
--- /dev/null
+++ b/hdl21/slice.py
@@ -0,0 +1,161 @@
+"""
+# Hdl21 Slices
+
+By-index references into Connectable types. 
+"""
+
+from typing import Optional, Union, Any, Set
+from weakref import WeakSet
+
+# Local imports
+from .datatype import datatype
+from .connect import connectable
+from .sliceable import sliceable, is_sliceable
+from .concat import concatable
+
+
+@sliceable
+@concatable
+@connectable
+@datatype
+class Slice:
+    """
+    # Slice
+
+    Subset of the indices of a parent `Connectable`, 
+    commonly Signals, Concatenations, and other Slices. 
+    Typically produced via square-bracket indexing into said `Connectable`s.
+    
+    Hdl21 slices are indexed "Python style", in the senses that:
+    * Negative indices are supported, and count from the "end" of the Signal.
+    * Slice-ranges such as `sig[0:2]` are supported, and *inclusive* of the start, while *exclusive* of the end index.
+    * Negative-range slices such as `sig[2:0:-1]`, again *inclusive* of the start, *exclusive* of the end index, and *reversed*.
+    
+    Popular HDLs commonly use different signal-indexing conventions.
+    Hdl21's own primary exchange format (in ProtoBuf) does as well,
+    eschewing adopting inclusive-endpoints and negative-indexing.
+    """
+
+    # Parent Connectable.
+    # Really of union-type `Sliceable`, which is more painful to type-check statically,
+    # although the constructor does it procedurally.
+    parent: Any
+    # Python index, i.e. that passed to square brackets
+    index: Union[int, slice]
+
+    def __post_init_post_parse__(self):
+        if not is_sliceable(self.parent):
+            raise TypeError(f"{self.parent} is not Sliceable")
+        self._connected_ports: Set["PortRef"] = set()
+        self._inner: Optional[SliceInner] = None
+        self._slices: WeakSet[Slice] = set()
+        self._concats: WeakSet["Concat"] = set()
+
+    @property
+    def top(self) -> int:
+        return _get_inner(self).top
+
+    @property
+    def bot(self) -> int:
+        return _get_inner(self).bot
+
+    @property
+    def step(self) -> int:
+        return _get_inner(self).step
+
+    @property
+    def width(self) -> int:
+        return _get_inner(self).width
+
+    def __repr__(self):
+        return f"Slice(parent={self.parent}, index={self.index})"
+
+    def __eq__(self, other) -> bool:
+        # Identity is equality
+        return id(self) == id(other)
+
+    def __hash__(self) -> bool:
+        # Identity is equality
+        return hash(id(self))
+
+
+@datatype
+class SliceInner:
+    """ Inner, private, resolved attributes of a `Slice`. 
+    Designed solely to be created by `_slice_inner` and stored as the `Slice._inner` field."""
+
+    top: int  # Top index (exclusive)
+    bot: int  # Bottom index (inclusive)
+    step: int  # Python-convention step size
+    width: int
+
+
+def _slice_inner(slize: Slice) -> SliceInner:
+    """Calculate the inner resolved fields for `slize`"""
+
+    parent = slize.parent
+    index = slize.index
+
+    if isinstance(index, int):
+        if index >= parent.width:
+            raise ValueError(f"Out-of-bounds index {index} into {parent}")
+        if index < 0:
+            index += parent.width
+        return SliceInner(top=index + 1, bot=index, step=1, width=1)
+
+    if isinstance(index, slice):
+        # Note these `slice` attributes are descriptor-things, and they get weird, fast.
+        # Extracting their three index fields the most-hardest way via `__getattribute__` seems to work cleanest.
+        start = slice.__getattribute__(index, "start")
+        stop = slice.__getattribute__(index, "stop")
+        step = slice.__getattribute__(index, "step")
+
+        step = 1 if step is None else step
+        if step == 0:
+            raise ValueError(f"slice step cannot be zero")
+        elif step < 0:
+            # Here `top` gets a "+1" since `start` is *inclusive*, while `bot` gets "+1" as `stop` is *exclusive*.
+            top = (
+                parent.width
+                if start is None
+                else start + 1
+                if start >= 0
+                else parent.width + start + 1
+            )
+            bot = (
+                0
+                if stop is None
+                else stop + 1
+                if stop >= 0
+                else parent.width + stop + 1
+            )
+            # Align bot with the step
+            bot += (top - bot) % abs(step)
+        else:
+            # Here `start` and `stop` match `top` and `bot`'s inclsive/ exclusivity.
+            # No need to add any offsets.
+            top = (
+                parent.width
+                if stop is None
+                else stop
+                if stop >= 0
+                else parent.width + stop
+            )
+            bot = 0 if start is None else start if start >= 0 else parent.width + start
+            # Align top with the step
+            top -= (top - bot) % step
+
+        width = (top - bot) // step
+
+        # Create and return our Slice. More checks are done in its constructor.
+        return SliceInner(top=top, bot=bot, step=step, width=width)
+
+    # Shouldn't be reachable, but blow up if we (somehow) get here.
+    raise TypeError("Internal Error: Slice index should be an int or (python) slice")
+
+
+def _get_inner(self: Slice) -> SliceInner:
+    """ Get a slice's `SliceInner`, calculating it inline if necessary"""
+    if self._inner is None:
+        self._inner = _slice_inner(self)
+    return self._inner
diff --git a/hdl21/sliceable.py b/hdl21/sliceable.py
new file mode 100644
index 0000000..13954e0
--- /dev/null
+++ b/hdl21/sliceable.py
@@ -0,0 +1,60 @@
+"""
+# Slice-Enabling Decorator
+"""
+
+from typing import Union
+
+from .connect import is_connectable
+
+
+def sliceable(cls: type) -> type:
+    """Decorator to add the 'square-bracket indexing produces `Slice`s' behavior."""
+
+    if getattr(cls, "__getitem__", None) is not None:
+        msg = f"Internal hdl21 Error: invavlid `slices`-decoration of {cls} with existing __getitem__"
+        raise RuntimeError(msg)
+
+    if not is_connectable(cls):
+        msg = f"Internal hdl21 Error: invavlid `slices`-decoration of {cls} which is not `connectable`"
+        raise RuntimeError(msg)
+
+    def __getitem__(self, index: Union[int, slice]) -> "Slice":
+        return _slice(parent=self, index=index)
+
+    # Add the new behavior to the class
+    cls.__getitem__ = __getitem__
+    cls.__getitem__.__doc__ = _slice.__doc__
+    # And a marker attribute
+    cls.__slices__ = True
+    # And don't forget to return that class!
+    return cls
+
+
+def is_sliceable(obj: object) -> bool:
+    """Returns True if the class has the `slices` decorator."""
+    return getattr(obj, "__slices__", False)
+
+
+def _slice(*, parent: "Sliceable", index: Union[int, slice]) -> "Slice":
+    """
+    Square-bracket slicing into Signals, Concatenations, and other Slices.
+    Assuming valid inputs, returns a signal-`Slice`.
+
+    Hdl21 slices are indexed "Python style", in the senses that:
+    * Negative indices are supported, and count from the "end" of the Signal.
+    * Slice-ranges such as `sig[0:2]` are supported, and *inclusive* of the start, while *exclusive* of the end index.
+    * Negative-range slices such as `sig[2:0:-1]`, again *inclusive* of the start, *exclusive* of the end index, and *reversed*.
+    
+    Popular HDLs commonly use different signal-indexing conventions.
+    Hdl21's own primary exchange format (in ProtoBuf) does as well,
+    eschewing adopting inclusive-endpoints and negative-indexing.
+    """
+
+    from .slice import Slice
+
+    if not isinstance(index, (int, slice)):
+        raise TypeError
+
+    slize = Slice(parent=parent, index=index)
+    parent._slices.add(slize)
+    return slize
diff --git a/hdl21/source_info.py b/hdl21/source_info.py
index 17b0a6e..ee1c9e1 100644
--- a/hdl21/source_info.py
+++ b/hdl21/source_info.py
@@ -2,47 +2,104 @@
 # Python Source Info
 
 Track the source file and line number of user calls. 
+
+This source-level tracking is invaluable for hierarchical hardware designs, 
+especially since the Hdl21 elaboration process means default debugger errors 
+do not produce a stack-trace through the *hardware*, but through the *elaborators*. 
+
+Most of the logic to produce nice error messages using `SourceInfo`s 
+is in `Elaborator`s, particularly the base-class `Elaborator`'s `fail` method. 
+
+The mechanisms for tracking this source info have been the subject of some experimentation. 
+Past methods, and associated lessons include: 
+
+* `inspect.stack()` and `inspect.getmodule()` are REALLY slow. 
+  * Versions of Hdl21 overusing these methods have observed them taking as much as 90% of elaboration time. 
+  * `stack()` in particular does not abide the Python3 "everything is a generator" rule, and computes all stack-frames into a list upfront
+  * We still used `getmodule()` as a basis for (Hdl21) Module naming. But beware to limit its calls, and get rid of it if we can. 
+* Storing the `frame` object returned by `inspect.currentframe()` at creation time and referring back to it later, just doesn't work. 
+  * The `frame` objects are mutated directly by the interpreter; the stack is lost essentially immediately. 
+
 """
 
 import inspect
 from pathlib import Path
-from types import ModuleType
-from typing import Optional, List
+from types import ModuleType, FrameType
+from typing import Optional, Set
 
 from pydantic.dataclasses import dataclass
 
 
 @dataclass
 class SourceInfo:
+    """# Python Source Info"""
+
     filepath: Path
     linenum: int
-    pymodule: Optional[ModuleType]
+    pymodule: Optional[ModuleType] = None
 
 
-def source_info(skipfiles: List[str]) -> Optional[SourceInfo]:
+def source_info(
+    get_pymodule: bool = False,
+) -> Optional[SourceInfo]:
     """
     # Get Source Info
 
-    Find the (python) source of the first python-stack-frame from outside `skipfiles` (plus *this* file).
+    Find the source details of the first python-stack-frame from outside `files_to_skip`.
     Returns `None` if *no* such modules are in the call-stack.
+
+    Returns a `SourceInfo` with `filepath` and `linenum` fields,
+    and additionally finds the `pymodule` python module field if requested
+    via the `get_pymodule` boolean argument.
     """
 
-    if not isinstance(skipfiles, list):
-        raise RuntimeError(
-            "Internal Error - source_info() called with non-list skipfiles"
-        )
+    # Get or create the files-to-skip set
+    global files_to_skip
+    if files_to_skip is None:
+        files_to_skip = get_files_to_skip()
+    # Note: this file is in the `files_to_skip` list, so this function's call-stack entry will never show up.
+
+    frame: Optional[FrameType] = inspect.currentframe()
+    MAX_DEPTH = 1000  # Avoid infinite recursion
 
-    skipfiles = set(skipfiles)
-    skipfiles.add(__file__)
+    for _ in range(MAX_DEPTH):
+        if frame is None:
+            return None
+        if frame.f_code.co_filename not in files_to_skip:
+            # We've got a hit! Return a `SourceInfo` object.
+
+            # If requested via the `get_pymodule` flag, return the Python module.
+            # Note this is generally the slow part.
+            pymodule = None if not get_pymodule else inspect.getmodule(frame)
 
-    for frame in inspect.stack():
-        if frame.filename not in skipfiles:
-            # Note frames produce an `Optional[ModuleType]`.
-            # (It seems extension modules may not have one.)
-            pymodule = inspect.getmodule(frame[0])
             return SourceInfo(
-                filepath=Path(frame.filename), linenum=frame.lineno, pymodule=pymodule
+                filepath=Path(frame.f_code.co_filename),
+                linenum=frame.f_lineno,
+                pymodule=pymodule,
             )
+        # And back up one frame
+        frame = frame.f_back
+
+    # If we got here without returning, we failed.
+    raise RecursionError("Error finding `SourceDetail`")
+
+
+# Set of files to skip
+# Calculated once, after import-time, so those modules can import this one.
+files_to_skip: Optional[Set[str]] = None
+
+
+def get_files_to_skip():
+    """Create the list of files to skip.
+    Should only be run once, shortly after import-time."""
+
+    from . import _module_module, _generator_module, _instance_module
 
-    # No caller outside `skipfiles` found anywhere in the call-stack.
-    return None
+    return set(
+        [
+            __file__,
+            _module_module.__file__,
+            _generator_module.__file__,
+            _instance_module.__file__,
+        ]
+    )
diff --git a/hdl21/walker.py b/hdl21/walker.py
index 9f77203..5164b59 100644
--- a/hdl21/walker.py
+++ b/hdl21/walker.py
@@ -38,7 +38,7 @@ def visit_elaboratables(self, src: Elaboratables) -> None:
 
     def visit_generator_call(self, call: GeneratorCall) -> None:
         """Visit a `GeneratorCall` object, primarily by visiting its resultant `Module`."""
-        return self.visit_module(call.gen._resolved)
+        return self.visit_module(call.gen._resolved) # FIXME! think we broke this one. 
 
     def visit_module(self, module: Module) -> None:
         """Visit a `Module`.


Test patch:
diff --git a/hdl21/tests/test_builtin_generators.py b/hdl21/tests/test_builtin_generators.py
new file mode 100644
index 0000000..676b953
--- /dev/null
+++ b/hdl21/tests/test_builtin_generators.py
@@ -0,0 +1,64 @@
+""" 
+# Hdl21 Built-In Generators 
+# Unit Tests 
+"""
+
+import hdl21 as h
+
+
+def test_mos_generator():
+    """Initial test of built-in series-Mos generator"""
+
+    Mos = h.generators.Mos
+    m = Mos(Mos.Params(nser=2))
+
+    assert isinstance(m, h.GeneratorCall)
+    assert isinstance(m.params, Mos.Params)
+
+    m = h.elaborate(m)
+
+    assert isinstance(m, h.Module)
+    assert len(m.ports) == 4
+    assert m.ports.keys() == h.primitives.Mos.ports.keys()
+    assert len(m.instances) == 2
+    assert "unit0" in m.instances
+    assert "unit1" in m.instances
+    assert isinstance(m.instances["unit0"].of, h.PrimitiveCall)
+    assert isinstance(m.instances["unit1"].of, h.PrimitiveCall)
+    assert m.instances["unit0"].of.params == h.primitives.MosParams()
+    assert m.instances["unit1"].of.params == h.primitives.MosParams()
+    assert len(m.signals) == 1
+    assert "unit0_d" in m.signals
+
+
+def test_series_parallel_generator():
+    """Initial test of the general-purpose series-parallel generator"""
+
+    from hdl21.generators import SeriesPar
+
+    @h.module
+    class M:  # Unit cell
+        a, b, c, d, e, f, g = h.Ports(7)
+
+    params = SeriesPar.Params(unit=M, npar=2, nser=2, series_conns=["a", "b"])
+    m = SeriesPar(params)
+
+    assert isinstance(m, h.GeneratorCall)
+    assert isinstance(m.params, SeriesPar.Params)
+
+    m = h.elaborate(m)
+
+    assert isinstance(m, h.Module)
+    assert len(m.ports) == 7
+    assert m.ports.keys() == M.ports.keys()
+    assert len(m.instances) == 4
+    assert "unit_0_0" in m.instances
+    assert "unit_0_1" in m.instances
+    assert "unit_1_0" in m.instances
+    assert "unit_1_1" in m.instances
+
+    for inst in m.instances.values():
+        assert inst.of is M
+    assert len(m.signals) == 2
+    assert "unit_0_0_b" in m.signals
+    assert "unit_1_0_b" in m.signals
diff --git a/hdl21/tests/test_bundles.py b/hdl21/tests/test_bundles.py
new file mode 100644
index 0000000..414a279
--- /dev/null
+++ b/hdl21/tests/test_bundles.py
@@ -0,0 +1,501 @@
+"""
+# Hdl21 Unit Tests
+
+Not necessarily exclusive to `Bundle`s, but focusing on them.
+"""
+import copy, pytest
+from enum import Enum, EnumMeta, auto
+
+import hdl21 as h
+
+
+def test_bundle1():
+    # Create an bundle
+
+    i1 = h.Bundle(name="MyFirstBundle")
+    i1.s1 = h.Signal()
+    i1.s2 = h.Signal()
+
+    assert isinstance(i1, h.Bundle)
+    assert isinstance(i1.s1, h.Signal)
+    assert isinstance(i1.s2, h.Signal)
+
+    ii1 = i1()
+    assert isinstance(ii1, h.BundleInstance)
+    assert ii1.role is None
+    assert ii1.port == False
+
+
+def test_bundle2():
+    # Wire up a few Modules via bundles
+
+    MySecondBundle = h.Bundle(name="MySecondBundle")
+    MySecondBundle.s = h.Signal()
+
+    m1 = h.Module(name="M1")
+    m1.i = MySecondBundle(port=True)
+    m2 = h.Module(name="M2")
+    m2.i = MySecondBundle(port=True)
+
+    # Now create a parent Module connecting the two
+    m3 = h.Module(name="M3")
+    m3.i1 = m1()
+    m3.i2 = m2(i=m3.i1.i)
+    assert "i1_i" not in m3.namespace
+
+    # First run the "implicit bundles" pass, and see that an explicit one is created
+    from hdl21.elab import ElabPass
+
+    m3 = h.elaborate(m3, passes=[ElabPass.RESOLVE_PORT_REFS])
+    assert isinstance(m3, h.Module)
+    assert isinstance(m3.i1, h.Instance)
+    assert isinstance(m3.i2, h.Instance)
+    assert "i1_i" in m3.namespace
+
+    # Now elaborate it the rest of the way, to scalar signals
+    m3 = h.elaborate(m3)
+    assert "i1_i" not in m3.namespace
+    assert "i1_i_s" in m3.namespace
+    assert isinstance(m3.get("i1_i_s"), h.Signal)
+    assert m3.get("i1_i_s") in m3.i1.conns.values()
+
+
+def test_bundle3():
+    # Test the bundle-definition decorator
+
+    @h.bundle
+    class Diff:  # Differential Signal Bundle
+        p = h.Signal()
+        n = h.Signal()
+
+    @h.bundle
+    class DisplayPort:  # DisplayPort, kinda
+        main_link = Diff()
+        aux = h.Signal()
+
+    assert isinstance(DisplayPort, h.Bundle)
+    assert isinstance(DisplayPort(), h.BundleInstance)
+    assert isinstance(DisplayPort.main_link, h.BundleInstance)
+    assert isinstance(DisplayPort.main_link.p, h.BundleRef)
+    assert isinstance(DisplayPort.main_link.n, h.BundleRef)
+    assert isinstance(DisplayPort.aux, h.Signal)
+    assert isinstance(Diff, h.Bundle)
+    assert isinstance(Diff(), h.BundleInstance)
+    assert isinstance(Diff.p, h.Signal)
+    assert isinstance(Diff.n, h.Signal)
+
+    # Instantiate one in a Module
+    m = h.Module(name="M")
+    m.dp = DisplayPort()
+    assert isinstance(m.dp, h.BundleInstance)
+    assert len(m.bundles) == 1
+    assert len(m.signals) == 0
+
+    # And elaborate it
+    h.elaborate(m)
+
+    assert not hasattr(m, "dp")
+    assert len(m.bundles) == 0
+    assert len(m.signals) == 3
+    assert isinstance(m.get("dp_aux"), h.Signal)
+    assert isinstance(m.get("dp_main_link_p"), h.Signal)
+    assert isinstance(m.get("dp_main_link_n"), h.Signal)
+
+
+def test_bundle4():
+    # Test bundle roles
+
+    @h.bundle
+    class Diff:  # Differential Signal Bundle
+        p = h.Signal()
+        n = h.Signal()
+
+    @h.bundle
+    class HasRoles:  # An Bundle with Roles
+        class Roles(Enum):  # USB-Style Role Nomenclature
+            HOST = auto()
+            DEVICE = auto()
+
+        # Create signals going in either direction
+        tx = h.Signal(src=Roles.HOST, dest=Roles.DEVICE)
+        rx = h.Signal(src=Roles.DEVICE, dest=Roles.HOST)
+
+        # And create differential versions thereof
+        txd = Diff(src=Roles.HOST, dest=Roles.DEVICE)
+        rxd = Diff(src=Roles.DEVICE, dest=Roles.HOST)
+
+    hr = HasRoles()
+    assert isinstance(HasRoles, h.Bundle)
+    assert isinstance(HasRoles.roles, EnumMeta)
+    assert isinstance(HasRoles.Roles, EnumMeta)
+    assert isinstance(hr, h.BundleInstance)
+    assert isinstance(HasRoles.tx, h.Signal)
+    assert isinstance(HasRoles.rx, h.Signal)
+    assert isinstance(HasRoles.txd, h.BundleInstance)
+    assert isinstance(HasRoles.rxd, h.BundleInstance)
+
+    @h.module
+    class Host:
+        # A thing with a HOST-roled bundle-port
+        hr = HasRoles(port=True, role=HasRoles.Roles.HOST)
+
+    @h.module
+    class Device:
+        # A thing with a DEVICE-roled bundle-port
+        hr = HasRoles(port=True, role=HasRoles.Roles.DEVICE)
+
+    @h.module
+    class System:
+        # Parent system-module including a host and device
+        host = Host()
+        devc = Device(hr=host.hr)
+
+    assert isinstance(System, h.Module)
+    assert isinstance(System.host, h.Instance)
+    assert isinstance(System.devc, h.Instance)
+    assert "host_hr" not in System.namespace
+
+    # First run the "implicit bundles" pass, and see that an explicit one is created
+    from hdl21.elab import ElabPass
+
+    sys = h.elaborate(System, passes=[ElabPass.RESOLVE_PORT_REFS])
+    assert "host_hr" in sys.namespace
+
+    # Now expand the rest of the way, down to scalar signals
+    # Check that bundle went away, and its constituent signals replaced it
+    sys = h.elaborate(sys)
+    assert "host_hr" not in sys.namespace
+    assert "host_hr_tx" in sys.namespace
+    assert "host_hr_rx" in sys.namespace
+    assert "host_hr_txd_p" in sys.namespace
+    assert "host_hr_txd_n" in sys.namespace
+    assert "host_hr_rxd_p" in sys.namespace
+    assert "host_hr_rxd_n" in sys.namespace
+
+
+def test_bigger_bundles():
+    """Test a slightly more elaborate Bundle-based system"""
+
+    class HostDevice(Enum):
+        HOST = auto()
+        DEVICE = auto()
+
+    @h.bundle
+    class Jtag:
+        # Jtag Bundle
+
+        roles = HostDevice
+        tck, tdi, tms = h.Signals(3, src=roles.HOST, dest=roles.DEVICE)
+        tdo = h.Signal(src=roles.DEVICE, dest=roles.HOST)
+
+    @h.bundle
+    class Uart:
+        # Uart Bundle
+        class Roles(Enum):
+            # Uart roles are essentially peers, here named `ME` and `YOU`.
+            # Essentially everything will use the role `ME`,
+            # except for interconnect which swaps between the two.
+            ME = auto()
+            YOU = auto()
+
+        tx = h.Signal(src=Roles.ME, dest=Roles.YOU)
+        rx = h.Signal(src=Roles.YOU, dest=Roles.ME)
+
+    @h.bundle
+    class Spi:
+        # Spi Bundle
+        roles = HostDevice
+        sck, cs = h.Signals(2, src=roles.HOST, dest=roles.DEVICE)
+        dq = h.Signal(src=roles.DEVICE, dest=roles.HOST, width=4)
+
+    @h.module
+    class Chip:
+        spi = Spi(role=HostDevice.HOST, port=True)
+        jtag = Jtag(role=HostDevice.DEVICE, port=True)
+        uart = Uart(role=Uart.Roles.ME, port=True)
+        ...  # Actual internal content, which likely connects these down *many* levels of hierarchy
+
+    @h.module
+    class SpiFlash:
+        # A typical flash memory with a SPI port
+        spi = Spi(role=HostDevice.DEVICE, port=True)
+
+    @h.module
+    class Board:
+        # A typical embedded board, featuring a custom chip, SPI-connected flash, and JTAG port
+        jtag = Jtag(role=HostDevice.DEVICE, port=True)
+        uart = Uart(role=Uart.Roles.ME, port=True)
+
+        chip = Chip(jtag=jtag, uart=uart)
+        flash = SpiFlash(spi=chip.spi)
+
+    @h.module
+    class Tester:
+        # A typical test-widget with a JTAG port
+        jtag = Jtag(role=HostDevice.HOST, port=True)
+        uart = Uart(role=Uart.Roles.ME, port=True)
+
+    @h.module
+    class TestSystem:
+        # A system in which `Tester` can test `Board`
+        jtag = Jtag()
+
+        tester = Tester(jtag=jtag)
+        board = Board(jtag=jtag)
+
+        # Connect UART, swapping `rx` and `tx`
+        u0, u1 = h.Signals(2)
+        board.uart = h.AnonymousBundle(tx=u0, rx=u1)
+        tester.uart = h.AnonymousBundle(rx=u0, tx=u1)
+
+    assert isinstance(TestSystem.jtag, h.BundleInstance)
+    assert isinstance(TestSystem.tester, h.Instance)
+    assert isinstance(TestSystem.board, h.Instance)
+
+    assert isinstance(TestSystem.tester.uart, h.PortRef)
+    assert isinstance(TestSystem.board.uart, h.PortRef)
+
+    # Run this through elaboration
+    h.elaborate(TestSystem)
+
+    # Post-elab checks
+    # TestSystem
+    assert isinstance(TestSystem.tester, h.Instance)
+    assert isinstance(TestSystem.board, h.Instance)
+    assert TestSystem.tester.of is Tester
+    assert TestSystem.board.of is Board
+    assert not hasattr(TestSystem, "jtag")
+    assert not hasattr(TestSystem, "uart")
+    assert "u0" in TestSystem.namespace
+    assert "u1" in TestSystem.namespace
+    assert len(TestSystem.ports) == 0
+    assert len(TestSystem.signals) == 6
+    assert len(TestSystem.instances) == 2
+    assert isinstance(TestSystem.get("u0"), h.Signal)
+    assert isinstance(TestSystem.get("u1"), h.Signal)
+    assert isinstance(TestSystem.get("jtag_tck"), h.Signal)
+    assert isinstance(TestSystem.get("jtag_tdi"), h.Signal)
+    assert isinstance(TestSystem.get("jtag_tdo"), h.Signal)
+    assert isinstance(TestSystem.get("jtag_tms"), h.Signal)
+
+    # Tester
+    assert len(Tester.ports) == 6
+    assert len(Tester.signals) == 0
+    assert len(Tester.instances) == 0
+    assert isinstance(Tester.get("jtag_tck"), h.Signal)
+    assert Tester.get("jtag_tck").vis == h.signal.Visibility.PORT
+    assert Tester.get("jtag_tdo").vis == h.signal.Visibility.PORT
+    assert Tester.get("jtag_tdi").vis == h.signal.Visibility.PORT
+    assert Tester.get("jtag_tms").vis == h.signal.Visibility.PORT
+    assert Tester.get("uart_tx").vis == h.signal.Visibility.PORT
+    assert Tester.get("uart_rx").vis == h.signal.Visibility.PORT
+
+    # Board
+    assert len(Board.ports) == 6
+    assert len(Board.signals) == 3  # SPI signals
+    assert len(Board.instances) == 2
+    assert Board.chip.of is Chip
+    assert Board.flash.of is SpiFlash
+    assert Board.get("jtag_tck").vis == h.signal.Visibility.PORT
+    assert Board.get("jtag_tdo").vis == h.signal.Visibility.PORT
+    assert Board.get("jtag_tdi").vis == h.signal.Visibility.PORT
+    assert Board.get("jtag_tms").vis == h.signal.Visibility.PORT
+    assert Board.get("uart_tx").vis == h.signal.Visibility.PORT
+    assert Board.get("uart_rx").vis == h.signal.Visibility.PORT
+    assert Board.get("chip_spi_sck").vis == h.signal.Visibility.INTERNAL
+    assert Board.get("chip_spi_cs").vis == h.signal.Visibility.INTERNAL
+    assert Board.get("chip_spi_dq").vis == h.signal.Visibility.INTERNAL
+
+    # Chip
+    assert len(Chip.ports) == 9
+    assert len(Chip.signals) == 0
+    assert len(Chip.instances) == 0
+    assert Chip.get("jtag_tck").vis == h.signal.Visibility.PORT
+    assert Chip.get("jtag_tdo").vis == h.signal.Visibility.PORT
+    assert Chip.get("jtag_tdi").vis == h.signal.Visibility.PORT
+    assert Chip.get("jtag_tms").vis == h.signal.Visibility.PORT
+    assert Chip.get("spi_sck").vis == h.signal.Visibility.PORT
+    assert Chip.get("spi_cs").vis == h.signal.Visibility.PORT
+    assert Chip.get("spi_dq").vis == h.signal.Visibility.PORT
+    assert Chip.get("uart_tx").vis == h.signal.Visibility.PORT
+    assert Chip.get("uart_rx").vis == h.signal.Visibility.PORT
+
+    # SpiFlash
+    assert len(SpiFlash.ports) == 3
+    assert len(SpiFlash.signals) == 0
+    assert len(SpiFlash.instances) == 0
+    assert SpiFlash.get("spi_sck").vis == h.signal.Visibility.PORT
+    assert SpiFlash.get("spi_cs").vis == h.signal.Visibility.PORT
+    assert SpiFlash.get("spi_dq").vis == h.signal.Visibility.PORT
+
+
+def test_bad_bundle_conn():
+    """Test invalid Bundle connections"""
+
+    @h.bundle
+    class P:
+        p = h.Signal(width=3)
+
+    @h.bundle
+    class R:
+        z = h.Signal(width=11)
+
+    @h.module
+    class C:
+        p = P(port=True)  # `P`-type Bundle
+
+    @h.module
+    class Q:
+        r = R()  # `R`-type Bundle
+        c = C(p=r)  # <= Bad connection here
+
+    with pytest.raises(RuntimeError):
+        h.elaborate(Q)
+
+
+def test_copy_bundle_instance():
+    """Copying a BundleInstance"""
+    # This generally fails when run in the debugger, but seems alright stand-alone (?)
+    copy.copy(h.BundleInstance(name="inst", of=h.Bundle()))
+
+
+def test_bundle_destructure():
+    """Test de-structuring bundles to individual Signals"""
+
+    @h.bundle
+    class B:
+        w1 = h.Signal(width=1)
+        w3 = h.Signal(width=3)
+
+    @h.module
+    class Child:
+        w1 = h.Port(width=1)
+        w3 = h.Port(width=3)
+
+    @h.module
+    class Parent:
+        b = B()  # `B`-type Bundle instance
+        c = Child(w1=b.w1, w3=b.w3)  # `Child`-type instance, connected to `b`
+
+    h.elaborate(Parent)
+
+    assert len(Parent.instances) == 1
+    assert len(Parent.signals) == 2
+    assert "b_w1" in Parent.signals
+    assert "b_w3" in Parent.signals
+    assert Parent.c.conns["w1"] is Parent.signals["b_w1"]
+    assert Parent.c.conns["w3"] is Parent.signals["b_w3"]
+
+
+def test_sub_bundle_conn():
+    """Test connecting via BundleRef to a sub-Bundle"""
+
+    @h.bundle
+    class B1:
+        s = h.Signal()
+
+    @h.bundle
+    class B2:
+        b1 = B1()
+
+    @h.module
+    class HasB1:
+        b1 = B1(port=True)
+
+    @h.module
+    class HasB2:
+        b2 = B2()
+        hasb1 = HasB1(b1=b2.b1)
+
+    h.elaborate(HasB2)
+
+
+def test_nested_bundle_conn():
+    """ Test connecting to a nested bundle ref """
+
+    @h.bundle
+    class B1:
+        s = h.Signal()
+
+    @h.bundle
+    class B2:
+        b1 = B1()
+
+    @h.bundle
+    class B3:
+        b2 = B2()
+
+    @h.bundle
+    class B4:
+        b3 = B3()
+
+    @h.module
+    class HasB1:
+        b1 = B1(port=True)
+
+    @h.module
+    class HasB4:
+        b4 = B4()
+        hasb1 = HasB1(b1=b4.b3.b2.b1)
+
+    h.elaborate(HasB4)
+
+
+def test_anon_bundle_port_conn():
+    """Test connecting via PortRef to an AnonymousBundle"""
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class HasB:
+        b = B(port=True)
+
+    @h.module
+    class Top:
+        s = h.Signal()
+        h1 = HasB(b=h.AnonymousBundle(s=s))
+        h2 = HasB(b=h1.b)
+        h3 = HasB(b=h2.b)
+
+    # Elaborate to flesh this out
+    h.elaborate(Top)
+
+    # Check this resolved to a single Signal in Top
+    assert len(Top.signals) == 1
+    assert len(Top.bundles) == 0
+    assert len(Top.instances) == 3
+
+    # And check that Signal is connected to all three Instances
+    assert Top.h1.b_s is Top.s
+    assert Top.h2.b_s is Top.s
+    assert Top.h3.b_s is Top.s
+
+
+def test_anon_bundle_refs():
+    """Test adding `BundleRef`s to `AnonymousBundle`s."""
+
+    @h.bundle
+    class Diff:
+        p, n = h.Signals(2)
+
+    @h.module
+    class HasDiff:
+        # Module with a `Diff` Port
+        d = Diff(port=True)
+
+    @h.module
+    class HasHasDiff:
+        # Module instantiating a few `HasDiff`
+        d = Diff()
+        # Instance connected to the Bundle Instance
+        h1 = HasDiff(d=d)
+
+        # THE POINT OF THE TEST:
+        # Instance with an Anon Bundle, "equal to" the Bundle Instance
+        h2 = HasDiff(d=h.AnonymousBundle(p=d.p, n=d.n))
+        # Instance with (p, n) flipped
+        h3 = HasDiff(d=h.AnonymousBundle(p=d.n, n=d.p))
+
+    h.elaborate(HasHasDiff)
diff --git a/hdl21/tests/test_conns.py b/hdl21/tests/test_conns.py
new file mode 100644
index 0000000..0522fbd
--- /dev/null
+++ b/hdl21/tests/test_conns.py
@@ -0,0 +1,301 @@
+import hdl21 as h
+
+
+def test_resolve_portref_type():
+    """ Test resolution of PortRef "types" to their Module-level referents """
+    from hdl21.elab.elaborators.resolve_ref_types import resolve_portref_type
+
+    @h.module
+    class Inner:
+        p = h.Port()
+
+    @h.module
+    class Outer:
+        i = Inner()
+
+    # Create a `PortRef`
+    ref = Outer.i.p
+
+    # All of these succeed:
+    assert ref is Outer.i.p
+    assert resolve_portref_type(ref) is not Outer.i.p
+    assert resolve_portref_type(ref) is Inner.p
+
+
+def test_portref_concat():
+    """ Test concatenating Port References """
+
+    @h.module
+    class A:
+        p = h.Port(width=1)
+
+    @h.module
+    class B:
+        p = h.Port(width=3)
+
+    @h.module
+    class Outer:
+        a = A()
+        b = B(p=h.Concat(a.p, a.p, a.p))
+
+    h.elaborate(Outer)
+
+
+def test_portref_slice():
+    """ Test slicing Port References """
+
+    @h.module
+    class A:
+        p = h.Port(width=1)
+
+    @h.module
+    class B:
+        p = h.Port(width=3)
+
+    @h.module
+    class Outer:
+        b = B()
+        a = A(p=b.p[2])
+
+    h.elaborate(Outer)
+
+
+def test_resolve_bundleref_type():
+    """ Test resolution of BundleRef "types" to their Bundle-level referents """
+    from hdl21.elab.elaborators.resolve_ref_types import resolve_bundleref_type
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class Outer:
+        b = B()
+
+    # Create a `BundleRef`
+    ref = Outer.b.s
+
+    # All of these succeed:
+    assert ref is Outer.b.s
+    assert resolve_bundleref_type(ref) is not Outer.b.s
+    assert resolve_bundleref_type(ref) is B.s
+
+
+def test_bundleref_concat():
+    """ Test concatenating Bundle References """
+
+    @h.module
+    class Inner:
+        p = h.Port(width=3)
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class Outer:
+        b = B()
+        i = Inner(p=h.Concat(b.s, b.s, b.s))
+
+    h.elaborate(Outer)
+
+
+def test_bundleref_slice():
+    """ Test slicing Bundle References """
+
+    @h.module
+    class Inner:
+        p = h.Port(width=2)
+
+    @h.bundle
+    class B:
+        s = h.Signal(width=8)
+
+    @h.module
+    class Outer:
+        b = B()
+        i = Inner(p=b.s[1:3])
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_signal():
+    """ Test an AnonymousBundle with a Signal"""
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        s = h.Signal()
+        i = Inner(b=h.AnonymousBundle(s=s))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_concat():
+    """ Test an AnonymousBundle with a concatenation"""
+
+    @h.bundle
+    class B:
+        s = h.Signal(width=2)
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        s = h.Signal(width=1)
+        i = Inner(b=h.AnonymousBundle(s=h.Concat(s, s)))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_slice():
+    """ Test an AnonymousBundle with a slice"""
+
+    @h.bundle
+    class B:
+        s = h.Signal(width=2)
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        s = h.Signal(width=4)
+        i = Inner(b=h.AnonymousBundle(s=s[0:2]))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_bundle_inst():
+    """ Test an AnonymousBundle referring to a BundleInstance"""
+
+    @h.bundle
+    class BI:
+        x = h.Signal()
+
+    @h.bundle
+    class B:
+        y = h.Signal()
+        bi = BI()
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        z = h.Signal()
+        bi = BI()
+        i = Inner(b=h.AnonymousBundle(y=z, bi=bi))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_bundleref_signal():
+    """ Test an AnonymousBundle referring to a sub-bundle reference"""
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        b = B()
+        i = Inner(b=h.AnonymousBundle(s=b.s))
+
+    h.elaborate(Outer)
+
+    assert len(Outer.signals) == 1
+    assert len(Outer.ports) == 0
+    assert len(Outer.bundles) == 0
+    assert len(Inner.signals) == 0
+    assert len(Inner.ports) == 1
+    assert len(Inner.bundles) == 0
+    assert Inner.ports["b_s"]
+    assert Outer.i.conns["b_s"] is Outer.signals["b_s"]
+
+
+def test_anon_with_subbundle_ref():
+    """ Test an AnonymousBundle referring to a sub-bundle reference"""
+
+    @h.bundle
+    class BI:
+        x = h.Signal()
+
+    @h.bundle
+    class B:
+        y = h.Signal()
+        bi = BI()
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        z = h.Signal()
+        b = B()
+        i = Inner(b=h.AnonymousBundle(y=z, bi=b.bi))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_portref():
+    """ Test an AnonymousBundle referring to a PortRef"""
+
+    @h.bundle
+    class B:
+        s = h.Signal()
+
+    @h.module
+    class HasBPort:
+        b = B(port=True)
+
+    @h.module
+    class HasSignalPort:
+        s = h.Port()
+
+    @h.module
+    class Outer:
+        hass = HasSignalPort()
+        hasb = HasBPort(b=h.AnonymousBundle(s=hass.s))
+
+    h.elaborate(Outer)
+
+
+def test_anon_with_anon():
+    """ Test an AnonymousBundle with a nested AnonymousBundle"""
+
+    @h.bundle
+    class BI:
+        x = h.Signal()
+
+    @h.bundle
+    class B:
+        y = h.Signal()
+        bi = BI()
+
+    @h.module
+    class Inner:
+        b = B(port=True)
+
+    @h.module
+    class Outer:
+        a, b = h.Signals(2)
+        i = Inner(b=h.AnonymousBundle(y=a, bi=h.AnonymousBundle(x=b)))
+
+    h.elaborate(Outer)
diff --git a/hdl21/tests/test_exports.py b/hdl21/tests/test_exports.py
index cce86c0..fced7aa 100644
--- a/hdl21/tests/test_exports.py
+++ b/hdl21/tests/test_exports.py
@@ -202,18 +202,18 @@ def test_proto3():
     assert "s" in M2.signals
     assert "i" in M2.instances
     inst = M2.i
-    assert isinstance(inst.conns["p1"], h.signal.Slice)
-    assert inst.conns["p1"].signal is M2.s
+    assert isinstance(inst.conns["p1"], h.Slice)
+    assert inst.conns["p1"].parent is M2.s
     assert inst.conns["p1"].top == 1
     assert inst.conns["p1"].bot == 0
     assert inst.conns["p1"].width == 1
-    assert isinstance(inst.conns["p8"], h.signal.Concat)
+    assert isinstance(inst.conns["p8"], h.Concat)
     assert len(inst.conns["p8"].parts) == 4
     assert inst.conns["p8"].parts[0] is M2.s
-    assert isinstance(inst.conns["p8"].parts[0], h.signal.Signal)
-    assert isinstance(inst.conns["p8"].parts[1], h.signal.Slice)
-    assert isinstance(inst.conns["p8"].parts[2], h.signal.Slice)
-    assert isinstance(inst.conns["p8"].parts[3], h.signal.Slice)
+    assert isinstance(inst.conns["p8"].parts[0], h.Signal)
+    assert isinstance(inst.conns["p8"].parts[1], h.Slice)
+    assert isinstance(inst.conns["p8"].parts[2], h.Slice)
+    assert isinstance(inst.conns["p8"].parts[3], h.Slice)
 
 
 def test_proto_roundtrip():
@@ -289,7 +289,7 @@ def test_proto_roundtrip2():
     assert len(M2.ports) == 3
     assert len(M2.signals) == 2
     assert "s" in M2.signals
-    assert "i1_i_i0_o" in M2.signals
+    assert "i0_o" in M2.signals
 
 
 def test_netlist_fmts():
diff --git a/hdl21/tests/test_hdl21.py b/hdl21/tests/test_hdl21.py
index 54f8caf..5d5353d 100644
--- a/hdl21/tests/test_hdl21.py
+++ b/hdl21/tests/test_hdl21.py
@@ -5,9 +5,6 @@
 import copy, pytest
 from enum import Enum, EnumMeta, auto
 
-from pydantic import ValidationError
-
-# Import the PUT (package under test)
 import hdl21 as h
 
 
@@ -136,7 +133,7 @@ class HasGen:
     assert isinstance(HasGen.a, h.Instance)
     assert isinstance(HasGen.a.of, h.GeneratorCall)
     assert HasGen.a.of.gen is g3a
-    assert HasGen.a.of.arg == P3()
+    assert HasGen.a.of.params == P3()
     assert isinstance(HasGen.a.of.result, h.Module)
     assert HasGen.a.of.result.name == "g3a(width=1)"
     assert isinstance(HasGen.b, h.Instance)
@@ -144,7 +141,7 @@ class HasGen:
     assert isinstance(HasGen.b.of.result, h.Module)
     assert HasGen.b.of.result.name == "g3b(width=5)"
     assert HasGen.b.of.gen is g3b
-    assert HasGen.b.of.arg == P3(width=5)
+    assert HasGen.b.of.params == P3(width=5)
     assert isinstance(HasGen.c, h.Instance)
     assert isinstance(HasGen.c.of, h.Module)
     assert HasGen.c.of is M
@@ -159,7 +156,7 @@ class InArray:
     m = h.Module(name="HasArray")
     m.s1 = h.Signal(width=8)
     m.s2 = h.Signal(width=1)
-    m.arr = h.InstArray(InArray, 8)
+    m.arr = h.InstanceArray(InArray, 8)
     m.arr.inp = m.s1
     m.arr.out = m.s2
 
@@ -176,7 +173,7 @@ def test_array2():
     class HasArray2:
         s1 = h.Signal(width=8)
         s2 = h.Signal(width=1)
-        arr = h.InstArray(a, 8)(inp=s1, out=s2)
+        arr = h.InstanceArray(a, 8)(inp=s1, out=s2)
 
     assert len(HasArray2.instances) == 0
     assert len(HasArray2.instarrays) == 1
@@ -202,6 +199,11 @@ class BackToBack:
         t1 = Thing()
         t2 = Thing(inp=t1.out, out=t1.inp)
 
+    assert isinstance(BackToBack.t1.inp, h.PortRef)
+    assert isinstance(BackToBack.t1.out, h.PortRef)
+    assert isinstance(BackToBack.t2.inp, h.PortRef)
+    assert isinstance(BackToBack.t2.out, h.PortRef)
+
     b = h.elaborate(BackToBack)
 
     assert isinstance(b.t1.inp, h.Signal)
@@ -209,7 +211,15 @@ class BackToBack:
     assert isinstance(b.t2.inp, h.Signal)
     assert isinstance(b.t2.out, h.Signal)
 
-    # Doing the same thing in procedural code
+
+def test_cycle2():
+    """# Doing the same thing in procedural code"""
+
+    @h.module
+    class Thing:
+        inp = h.Input()
+        out = h.Output()
+
     b2 = h.Module(name="BackToBack2")
     b2.t1 = Thing()
     b2.t2 = Thing()
@@ -228,8 +238,8 @@ class BackToBack:
     assert len(b2.bundles) == 0
     assert len(b2.ports) == 0
     assert len(b2.signals) == 2
-    assert "t2_inp_t1_out" in b2.signals
-    assert "t2_out_t1_inp" in b2.signals
+    assert "t1_out" in b2.signals
+    assert "t1_inp" in b2.signals
 
 
 def test_gen3():
@@ -294,353 +304,33 @@ class HasPrims:
     # FIXME: post-elab checks
 
 
-def test_bundle1():
-    # Create an bundle
-
-    i1 = h.Bundle(name="MyFirstBundle")
-    i1.s1 = h.Signal()
-    i1.s2 = h.Signal()
-
-    assert isinstance(i1, h.Bundle)
-    assert isinstance(i1.s1, h.Signal)
-    assert isinstance(i1.s2, h.Signal)
-
-    ii1 = i1()
-    assert isinstance(ii1, h.BundleInstance)
-    assert ii1.role is None
-    assert ii1.port == False
-
-
-def test_bundle2():
-    # Wire up a few Modules via bundles
-
-    MySecondBundle = h.Bundle(name="MySecondBundle")
-    MySecondBundle.s = h.Signal()
-
-    m1 = h.Module(name="M1")
-    m1.i = MySecondBundle(port=True)
-    m2 = h.Module(name="M2")
-    m2.i = MySecondBundle(port=True)
-
-    # Now create a parent Module connecting the two
-    m3 = h.Module(name="M3")
-    m3.i1 = m1()
-    m3.i2 = m2(i=m3.i1.i)
-    assert "i2_i_i1_i" not in m3.namespace
-
-    # First run the "implicit bundles" pass, and see that an explicit one is created
-    from hdl21.elab import ElabPass
-
-    m3 = h.elaborate(m3, passes=[ElabPass.RESOLVE_PORT_REFS])
-    assert isinstance(m3, h.Module)
-    assert isinstance(m3.i1, h.Instance)
-    assert isinstance(m3.i2, h.Instance)
-    assert "i2_i_i1_i" in m3.namespace
-
-    # Now elaborate it the rest of the way, to scalar signals
-    m3 = h.elaborate(m3)
-    assert "i2_i_i1_i" not in m3.namespace
-    assert "i2_i_i1_i_s" in m3.namespace
-    assert isinstance(m3.get("i2_i_i1_i_s"), h.Signal)
-    assert m3.get("i2_i_i1_i_s") in m3.i1.conns.values()
-
-
-def test_bundle3():
-    # Test the bundle-definition decorator
-
-    @h.bundle
-    class Diff:  # Differential Signal Bundle
-        p = h.Signal()
-        n = h.Signal()
-
-    @h.bundle
-    class DisplayPort:  # DisplayPort, kinda
-        main_link = Diff()
-        aux = h.Signal()
-
-    assert isinstance(DisplayPort, h.Bundle)
-    assert isinstance(DisplayPort(), h.BundleInstance)
-    assert isinstance(DisplayPort.main_link, h.BundleInstance)
-    assert isinstance(DisplayPort.main_link.p, h.BundleRef)
-    assert isinstance(DisplayPort.main_link.n, h.BundleRef)
-    assert isinstance(DisplayPort.aux, h.Signal)
-    assert isinstance(Diff, h.Bundle)
-    assert isinstance(Diff(), h.BundleInstance)
-    assert isinstance(Diff.p, h.Signal)
-    assert isinstance(Diff.n, h.Signal)
-
-    # Instantiate one in a Module
-    m = h.Module(name="M")
-    m.dp = DisplayPort()
-    assert isinstance(m.dp, h.BundleInstance)
-    assert len(m.bundles) == 1
-    assert len(m.signals) == 0
-
-    # And elaborate it
-    h.elaborate(m)
-
-    assert not hasattr(m, "dp")
-    assert len(m.bundles) == 0
-    assert len(m.signals) == 3
-    assert isinstance(m.get("dp_aux"), h.Signal)
-    assert isinstance(m.get("dp_main_link_p"), h.Signal)
-    assert isinstance(m.get("dp_main_link_n"), h.Signal)
-
-
-def test_bundle4():
-    # Test bundle roles
-
-    @h.bundle
-    class Diff:  # Differential Signal Bundle
-        p = h.Signal()
-        n = h.Signal()
-
-    @h.bundle
-    class HasRoles:  # An Bundle with Roles
-        class Roles(Enum):  # USB-Style Role Nomenclature
-            HOST = auto()
-            DEVICE = auto()
-
-        # Create signals going in either direction
-        tx = h.Signal(src=Roles.HOST, dest=Roles.DEVICE)
-        rx = h.Signal(src=Roles.DEVICE, dest=Roles.HOST)
-
-        # And create differential versions thereof
-        txd = Diff(src=Roles.HOST, dest=Roles.DEVICE)
-        rxd = Diff(src=Roles.DEVICE, dest=Roles.HOST)
-
-    hr = HasRoles()
-    assert isinstance(HasRoles, h.Bundle)
-    assert isinstance(HasRoles.roles, EnumMeta)
-    assert isinstance(HasRoles.Roles, EnumMeta)
-    assert isinstance(hr, h.BundleInstance)
-    assert isinstance(HasRoles.tx, h.Signal)
-    assert isinstance(HasRoles.rx, h.Signal)
-    assert isinstance(HasRoles.txd, h.BundleInstance)
-    assert isinstance(HasRoles.rxd, h.BundleInstance)
-
-    @h.module
-    class Host:
-        # A thing with a HOST-roled bundle-port
-        hr = HasRoles(port=True, role=HasRoles.Roles.HOST)
-
-    @h.module
-    class Device:
-        # A thing with a DEVICE-roled bundle-port
-        hr = HasRoles(port=True, role=HasRoles.Roles.DEVICE)
-
-    @h.module
-    class System:
-        # Parent system-module including a host and device
-        host = Host()
-        devc = Device(hr=host.hr)
-
-    assert isinstance(System, h.Module)
-    assert isinstance(System.host, h.Instance)
-    assert isinstance(System.devc, h.Instance)
-    assert "devc_hr_host_hr" not in System.namespace
-
-    # First run the "implicit bundles" pass, and see that an explicit one is created
-    from hdl21.elab import ElabPass
-
-    sys = h.elaborate(System, passes=[ElabPass.RESOLVE_PORT_REFS])
-    assert "devc_hr_host_hr" in sys.namespace
-
-    # Now expand the rest of the way, down to scalar signals
-    # Check that bundle went away, and its constituent signals replaced it
-    sys = h.elaborate(sys)
-    assert "devc_hr_host_hr" not in sys.namespace
-    assert "devc_hr_host_hr_tx" in sys.namespace
-    assert "devc_hr_host_hr_rx" in sys.namespace
-    assert "devc_hr_host_hr_txd_p" in sys.namespace
-    assert "devc_hr_host_hr_txd_n" in sys.namespace
-    assert "devc_hr_host_hr_rxd_p" in sys.namespace
-    assert "devc_hr_host_hr_rxd_n" in sys.namespace
-
-
-def test_bigger_bundles():
-    """Test a slightly more elaborate Bundle-based system"""
-
-    class HostDevice(Enum):
-        HOST = auto()
-        DEVICE = auto()
-
-    @h.bundle
-    class Jtag:
-        # Jtag Bundle
-
-        roles = HostDevice
-        tck, tdi, tms = h.Signals(3, src=roles.HOST, dest=roles.DEVICE)
-        tdo = h.Signal(src=roles.DEVICE, dest=roles.HOST)
-
-    @h.bundle
-    class Uart:
-        # Uart Bundle
-        class Roles(Enum):
-            # Uart roles are essentially peers, here named `ME` and `YOU`.
-            # Essentially everything will use the role `ME`,
-            # except for interconnect which swaps between the two.
-            ME = auto()
-            YOU = auto()
-
-        tx = h.Signal(src=Roles.ME, dest=Roles.YOU)
-        rx = h.Signal(src=Roles.YOU, dest=Roles.ME)
-
-    @h.bundle
-    class Spi:
-        # Spi Bundle
-        roles = HostDevice
-        sck, cs = h.Signals(2, src=roles.HOST, dest=roles.DEVICE)
-        dq = h.Signal(src=roles.DEVICE, dest=roles.HOST, width=4)
-
-    @h.module
-    class Chip:
-        spi = Spi(role=HostDevice.HOST, port=True)
-        jtag = Jtag(role=HostDevice.DEVICE, port=True)
-        uart = Uart(role=Uart.Roles.ME, port=True)
-        ...  # Actual internal content, which likely connects these down *many* levels of hierarchy
-
-    @h.module
-    class SpiFlash:
-        # A typical flash memory with a SPI port
-        spi = Spi(role=HostDevice.DEVICE, port=True)
-
-    @h.module
-    class Board:
-        # A typical embedded board, featuring a custom chip, SPI-connected flash, and JTAG port
-        jtag = Jtag(role=HostDevice.DEVICE, port=True)
-        uart = Uart(role=Uart.Roles.ME, port=True)
-
-        chip = Chip(jtag=jtag, uart=uart)
-        flash = SpiFlash(spi=chip.spi)
-
-    @h.module
-    class Tester:
-        # A typical test-widget with a JTAG port
-        jtag = Jtag(role=HostDevice.HOST, port=True)
-        uart = Uart(role=Uart.Roles.ME, port=True)
-
-    @h.module
-    class TestSystem:
-        # A system in which `Tester` can test `Board`
-        jtag = Jtag()
-
-        tester = Tester(jtag=jtag)
-        board = Board(jtag=jtag)
-
-        # Connect UART, swapping `rx` and `tx`
-        u0, u1 = h.Signals(2)
-        board.uart = h.AnonymousBundle(tx=u0, rx=u1)
-        tester.uart = h.AnonymousBundle(rx=u0, tx=u1)
-
-    assert isinstance(TestSystem.jtag, h.BundleInstance)
-    assert isinstance(TestSystem.tester, h.Instance)
-    assert isinstance(TestSystem.board, h.Instance)
-
-    assert isinstance(TestSystem.tester.uart, h.PortRef)
-    assert isinstance(TestSystem.board.uart, h.PortRef)
-
-    # Run this through elaboration
-    h.elaborate(TestSystem)
-
-    # Post-elab checks
-    # TestSystem
-    assert isinstance(TestSystem.tester, h.Instance)
-    assert isinstance(TestSystem.board, h.Instance)
-    assert TestSystem.tester.of is Tester
-    assert TestSystem.board.of is Board
-    assert not hasattr(TestSystem, "jtag")
-    assert not hasattr(TestSystem, "uart")
-    assert "u0" in TestSystem.namespace
-    assert "u1" in TestSystem.namespace
-    assert len(TestSystem.ports) == 0
-    assert len(TestSystem.signals) == 6
-    assert len(TestSystem.instances) == 2
-    assert isinstance(TestSystem.get("u0"), h.Signal)
-    assert isinstance(TestSystem.get("u1"), h.Signal)
-    assert isinstance(TestSystem.get("jtag_tck"), h.Signal)
-    assert isinstance(TestSystem.get("jtag_tdi"), h.Signal)
-    assert isinstance(TestSystem.get("jtag_tdo"), h.Signal)
-    assert isinstance(TestSystem.get("jtag_tms"), h.Signal)
-
-    # Tester
-    assert len(Tester.ports) == 6
-    assert len(Tester.signals) == 0
-    assert len(Tester.instances) == 0
-    assert isinstance(Tester.get("jtag_tck"), h.Signal)
-    assert Tester.get("jtag_tck").vis == h.signal.Visibility.PORT
-    assert Tester.get("jtag_tdo").vis == h.signal.Visibility.PORT
-    assert Tester.get("jtag_tdi").vis == h.signal.Visibility.PORT
-    assert Tester.get("jtag_tms").vis == h.signal.Visibility.PORT
-    assert Tester.get("uart_tx").vis == h.signal.Visibility.PORT
-    assert Tester.get("uart_rx").vis == h.signal.Visibility.PORT
-
-    # Board
-    assert len(Board.ports) == 6
-    assert len(Board.signals) == 3  # SPI signals
-    assert len(Board.instances) == 2
-    assert Board.chip.of is Chip
-    assert Board.flash.of is SpiFlash
-    assert Board.get("jtag_tck").vis == h.signal.Visibility.PORT
-    assert Board.get("jtag_tdo").vis == h.signal.Visibility.PORT
-    assert Board.get("jtag_tdi").vis == h.signal.Visibility.PORT
-    assert Board.get("jtag_tms").vis == h.signal.Visibility.PORT
-    assert Board.get("uart_tx").vis == h.signal.Visibility.PORT
-    assert Board.get("uart_rx").vis == h.signal.Visibility.PORT
-    assert Board.get("flash_spi_chip_spi_sck").vis == h.signal.Visibility.INTERNAL
-    assert Board.get("flash_spi_chip_spi_cs").vis == h.signal.Visibility.INTERNAL
-    assert Board.get("flash_spi_chip_spi_dq").vis == h.signal.Visibility.INTERNAL
-
-    # Chip
-    assert len(Chip.ports) == 9
-    assert len(Chip.signals) == 0
-    assert len(Chip.instances) == 0
-    assert Chip.get("jtag_tck").vis == h.signal.Visibility.PORT
-    assert Chip.get("jtag_tdo").vis == h.signal.Visibility.PORT
-    assert Chip.get("jtag_tdi").vis == h.signal.Visibility.PORT
-    assert Chip.get("jtag_tms").vis == h.signal.Visibility.PORT
-    assert Chip.get("spi_sck").vis == h.signal.Visibility.PORT
-    assert Chip.get("spi_cs").vis == h.signal.Visibility.PORT
-    assert Chip.get("spi_dq").vis == h.signal.Visibility.PORT
-    assert Chip.get("uart_tx").vis == h.signal.Visibility.PORT
-    assert Chip.get("uart_rx").vis == h.signal.Visibility.PORT
-
-    # SpiFlash
-    assert len(SpiFlash.ports) == 3
-    assert len(SpiFlash.signals) == 0
-    assert len(SpiFlash.instances) == 0
-    assert SpiFlash.get("spi_sck").vis == h.signal.Visibility.PORT
-    assert SpiFlash.get("spi_cs").vis == h.signal.Visibility.PORT
-    assert SpiFlash.get("spi_dq").vis == h.signal.Visibility.PORT
-
-
 def test_signal_slice1():
     # Initial test of signal slicing
 
     sig = h.Signal(width=10)
     sl = sig[0]
-    assert isinstance(sl, h.signal.Slice)
+    assert isinstance(sl, h.Slice)
     assert sl.top == 1
     assert sl.bot == 0
     assert sl.width == 1
-    assert sl.step is None
-    assert sl.signal is sig
+    assert sl.step == 1
+    assert sl.parent is sig
 
     sl = sig[0:5]
-    assert isinstance(sl, h.signal.Slice)
+    assert isinstance(sl, h.Slice)
     assert sl.top == 5
     assert sl.bot == 0
     assert sl.width == 5
-    assert sl.step is None
-    assert sl.signal is sig
+    assert sl.step == 1
+    assert sl.parent is sig
 
     sl = sig[:]
-    assert isinstance(sl, h.signal.Slice)
+    assert isinstance(sl, h.Slice)
     assert sl.top == 10
     assert sl.bot == 0
     assert sl.width == 10
-    assert sl.step is None
-    assert sl.signal is sig
+    assert sl.step == 1
+    assert sl.parent is sig
 
 
 def test_signal_slice2():
@@ -648,22 +338,23 @@ def test_signal_slice2():
     sl = h.Signal(width=11)[-1]
     assert sl.top == 11
     assert sl.bot == 10
-    assert sl.step is None
+    assert sl.step == 1
     assert sl.width == 1
 
     sl = h.Signal(width=11)[:-1]
     assert sl.top == 10
     assert sl.bot == 0
-    assert sl.step is None
+    assert sl.step == 1
     assert sl.width == 10
 
     sl = h.Signal(width=11)[-2:]
     assert sl.top == 11
     assert sl.bot == 9
-    assert sl.step is None
+    assert sl.step == 1
     assert sl.width == 2
 
 
+@pytest.mark.xfail(reason="#21 https://github.com/dan-fritchman/Hdl21/issues/21")
 def test_bad_slice1():
     # Test slicing error-cases
     with pytest.raises(TypeError):
@@ -685,23 +376,23 @@ def test_signal_concat1():
     # Initial Signal concatenation test
 
     c = h.Concat(h.Signal(width=1), h.Signal(width=2), h.Signal(width=3))
-    assert isinstance(c, h.signal.Concat)
+    assert isinstance(c, h.Concat)
     assert len(c.parts) == 3
     for p in c.parts:
-        assert isinstance(p, h.signal.Signal)
+        assert isinstance(p, h.Signal)
     assert c.width == 6
 
     c = h.Concat(h.Signal(width=1)[0], h.Signal(width=2)[0], h.Signal(width=3)[0])
-    assert isinstance(c, h.signal.Concat)
+    assert isinstance(c, h.Concat)
     assert len(c.parts) == 3
     for p in c.parts:
-        assert isinstance(p, h.signal.Slice)
+        assert isinstance(p, h.Slice)
     assert c.width == 3
 
     c = h.Concat(
         h.Concat(h.Signal(), h.Signal()), h.Signal(width=2), h.Signal(width=2)[:]
     )
-    assert isinstance(c, h.signal.Concat)
+    assert isinstance(c, h.Concat)
     assert len(c.parts) == 3
     assert c.width == 6
 
@@ -740,64 +431,6 @@ def UsesModuleParam(params: HasModuleParam) -> h.Module:
     assert m == Empty
 
 
-def test_mos_generator():
-    """Initial test of built-in series-Mos generator"""
-
-    Mos = h.generators.Mos
-    m = Mos(Mos.Params(nser=2))
-
-    assert isinstance(m, h.GeneratorCall)
-    assert isinstance(m.arg, Mos.Params)
-
-    m = h.elaborate(m)
-
-    assert isinstance(m, h.Module)
-    assert len(m.ports) == 4
-    assert m.ports == h.primitives.Mos.ports
-    assert len(m.instances) == 2
-    assert "unit0" in m.instances
-    assert "unit1" in m.instances
-    assert isinstance(m.instances["unit0"].of, h.PrimitiveCall)
-    assert isinstance(m.instances["unit1"].of, h.PrimitiveCall)
-    assert m.instances["unit0"].of.params == h.primitives.MosParams()
-    assert m.instances["unit1"].of.params == h.primitives.MosParams()
-    assert len(m.signals) == 1
-    assert "unit1_s_unit0_d" in m.signals
-
-
-def test_series_parallel_generator():
-    """Initial test of the general-purpose series-parallel generator"""
-
-    from hdl21.generators import SeriesPar
-
-    @h.module
-    class M:  # Unit cell
-        a, b, c, d, e, f, g = h.Ports(7)
-
-    params = SeriesPar.Params(unit=M, npar=2, nser=2, series_conns=["a", "b"])
-    m = SeriesPar(params)
-
-    assert isinstance(m, h.GeneratorCall)
-    assert isinstance(m.arg, SeriesPar.Params)
-
-    m = h.elaborate(m)
-
-    assert isinstance(m, h.Module)
-    assert len(m.ports) == 7
-    assert m.ports == M.ports
-    assert len(m.instances) == 4
-    assert "unit_0_0" in m.instances
-    assert "unit_0_1" in m.instances
-    assert "unit_1_0" in m.instances
-    assert "unit_1_1" in m.instances
-
-    for inst in m.instances.values():
-        assert inst.of is M
-    assert len(m.signals) == 2
-    assert "unit_0_1_a_unit_0_0_b" in m.signals
-    assert "unit_1_1_a_unit_1_0_b" in m.signals
-
-
 def test_instance_mult():
     """Initial tests of instance-array-generation by multiplication"""
 
@@ -808,7 +441,7 @@ def test_instance_mult():
     Parent.child = Child() * 5  # Lotta kids here
 
     # Check that the array is created correctly
-    assert isinstance(Parent.child, h.InstArray)
+    assert isinstance(Parent.child, h.InstanceArray)
     assert Parent.child.n == 5
     assert Parent.child.of is Child
 
@@ -826,7 +459,7 @@ def test_instance_mult():
     Parent.child = 11 * Child()  # Getting even more kids
 
     # Check that the array is created correctly
-    assert isinstance(Parent.child, h.InstArray)
+    assert isinstance(Parent.child, h.InstanceArray)
     assert Parent.child.n == 11
     assert Parent.child.of is Child
 
@@ -841,7 +474,7 @@ def test_instance_mult():
 
 
 def test_instance_mult2():
-    """Test connecting to multiplication-generated InstArrays"""
+    """Test connecting to multiplication-generated InstanceArrays"""
 
     @h.module
     class Child:
@@ -863,9 +496,11 @@ class Parent:
     assert Parent.get("child_0").of is Child
     assert Parent.get("child_1").of is Child
     assert Parent.get("child_2").of is Child
-    assert Parent.get("child_0").conns["p"] == Parent.a[0]
-    assert Parent.get("child_1").conns["p"] == Parent.a[1]
-    assert Parent.get("child_2").conns["p"] == Parent.a[2]
+
+    # FIXME: slice equality changes break these tests
+    # assert Parent.get("child_0").conns["p"] == Parent.a[0]
+    # assert Parent.get("child_1").conns["p"] == Parent.a[1]
+    # assert Parent.get("child_2").conns["p"] == Parent.a[2]
 
 
 def test_instance_mult3():
@@ -888,9 +523,11 @@ class Parent:
     assert Parent.get("child_0").of is Child
     assert Parent.get("child_1").of is Child
     assert Parent.get("child_2").of is Child
-    assert Parent.get("child_0").conns["p"] == Parent.a[0]
-    assert Parent.get("child_1").conns["p"] == Parent.a[1]
-    assert Parent.get("child_2").conns["p"] == Parent.a[2]
+
+    # FIXME: slice equality changes break these tests
+    # assert Parent.get("child_0").conns["p"] == Parent.a[0]
+    # assert Parent.get("child_1").conns["p"] == Parent.a[1]
+    # assert Parent.get("child_2").conns["p"] == Parent.a[2]
 
 
 def test_instance_mult4():
@@ -918,9 +555,11 @@ class Parent:
     assert Parent.get("child_0").conns["a"] == Parent.a
     assert Parent.get("child_1").conns["a"] == Parent.a
     assert Parent.get("child_2").conns["a"] == Parent.a
-    assert Parent.get("child_0").conns["b"] == Parent.b[0:16]
-    assert Parent.get("child_1").conns["b"] == Parent.b[16:32]
-    assert Parent.get("child_2").conns["b"] == Parent.b[32:48]
+
+    # FIXME: we probably want a new Signal/ Slice "equality" method for these
+    # assert Parent.get("child_0").conns["b"] == Parent.b[0:16]
+    # assert Parent.get("child_1").conns["b"] == Parent.b[16:32]
+    # assert Parent.get("child_2").conns["b"] == Parent.b[32:48]
 
 
 def test_bad_width_conn():
@@ -935,30 +574,6 @@ def test_bad_width_conn():
         h.elaborate(q)
 
 
-def test_bad_bundle_conn():
-    """Test invalid Bundle connections"""
-
-    @h.bundle
-    class P:
-        p = h.Signal(width=3)
-
-    @h.bundle
-    class R:
-        z = h.Signal(width=11)
-
-    @h.module
-    class C:
-        p = P(port=True)  # `P`-type Bundle
-
-    @h.module
-    class Q:
-        r = R()  # `R`-type Bundle
-        c = C(p=r)  # <= Bad connection here
-
-    with pytest.raises(RuntimeError):
-        h.elaborate(Q)
-
-
 def test_illegal_module_attrs():
     """Test attempting to add illegal attributes"""
 
@@ -1005,40 +620,6 @@ def test_copy_signal():
     copy.copy(h.Signal())
 
 
-def test_copy_bundle_instance():
-    """Copying a BundleInstance"""
-    # This generally fails when run in the debugger, but seems alright stand-alone (?)
-    copy.copy(h.BundleInstance(name="inst", of=h.Bundle()))
-
-
-def test_bundle_destructure():
-    """Test de-structuring bundles to individual Signals"""
-
-    @h.bundle
-    class B:
-        w1 = h.Signal(width=1)
-        w3 = h.Signal(width=3)
-
-    @h.module
-    class Child:
-        w1 = h.Port(width=1)
-        w3 = h.Port(width=3)
-
-    @h.module
-    class Parent:
-        b = B()  # `B`-type Bundle instance
-        c = Child(w1=b.w1, w3=b.w3)  # `Child`-type instance, connected to `b`
-
-    h.elaborate(Parent)
-
-    assert len(Parent.instances) == 1
-    assert len(Parent.signals) == 2
-    assert "b_w1" in Parent.signals
-    assert "b_w3" in Parent.signals
-    assert Parent.c.conns["w1"] is Parent.signals["b_w1"]
-    assert Parent.c.conns["w3"] is Parent.signals["b_w3"]
-
-
 def test_orphanage():
     """Test that orphaned Module-attributes fail at elaboration"""
 
@@ -1167,7 +748,7 @@ class Bad:
 
 
 def test_array_concat_conn():
-    """Test connecting a `Concat` to an `InstArray`"""
+    """Test connecting a `Concat` to an `InstanceArray`"""
 
     Child = h.Module(name="Child")
     Child.p3 = h.Input(width=3)
@@ -1181,6 +762,7 @@ def test_array_concat_conn():
     Parent.c.p5 = h.Concat(Parent.s5[3], Parent.s9)
 
 
+@pytest.mark.xfail(reason="FIXME: structural Slice/ Concat equality ")
 def test_slice_resolution():
     """Test resolutions of slice combinations"""
 
@@ -1286,7 +868,7 @@ class P:
         c = h.Param(dtype=str, desc="c")
 
     @h.generator
-    def M(p: P) -> h.Module:
+    def M(_: P) -> h.Module:
         return h.Module()
 
     # Call without constructing a `P`
@@ -1297,27 +879,21 @@ def M(p: P) -> h.Module:
     assert isinstance(m, h.Module)
 
     # Check that type-checking continue
-    m = M(a=TabError, b=TabError, c=TabError)
-    assert isinstance(m, h.GeneratorCall)
-    with pytest.raises(ValidationError):
-        m = h.elaborate(m)
+    with pytest.raises(RuntimeError):
+        m = M(a=TabError, b=TabError, c=TabError)
 
 
 def test_instance_array_portrefs():
     """Test Instance Arrays connected by port-references"""
 
     Inv = h.ExternalModule(
-        name="Inv",
-        port_list=[
-            h.Input(name="i"),
-            h.Output(name="z"),
-        ],
+        name="Inv", port_list=[h.Input(name="i"), h.Output(name="z"),],
     )
 
     m = h.Module(name="TestArrayPortRef")
     m.a, m.b = h.Signals(2, width=4)
 
-    # Create an InstArray
+    # Create an InstanceArray
     m.inva = 4 * Inv()(i=m.a)
     # And another which connects to it via PortRef
     m.invb = 4 * Inv()(i=m.inva.z, z=m.b)
@@ -1354,62 +930,6 @@ class HasArr:
         assert inst.conns.get("b_s", None) is HasArr.b_s
 
 
-@pytest.mark.xfail(reason="#19 https://github.com/dan-fritchman/Hdl21/issues/19")
-def test_sub_bundle_conn():
-    """Test connecting via BundleRef to a sub-Bundle"""
-
-    @h.bundle
-    class B1:
-        s = h.Signal()
-
-    @h.bundle
-    class B2:
-        b1 = B1()
-
-    @h.module
-    class HasB1:
-        b1 = B1(port=True)
-
-    @h.module
-    class HasB2:
-        b2 = B2()
-        h = HasB1(b1=b2.b1)
-
-    h.elaborate(HasB2)
-
-
-def test_anon_bundle_port_conn():
-    """Test connecting via PortRef to an AnonymousBundle"""
-
-    @h.bundle
-    class B:
-        s = h.Signal()
-
-    @h.module
-    class HasB:
-        b = B(port=True)
-
-    @h.module
-    class Top:
-        s = h.Signal()
-        h1 = HasB(b=h.AnonymousBundle(s=s))
-        h2 = HasB(b=h1.b)
-        h3 = HasB(b=h2.b)
-
-    # Elaborate to flesh this out
-    h.elaborate(Top)
-
-    # Check this resolved to a single Signal in Top
-    assert len(Top.signals) == 1
-    assert len(Top.bundles) == 0
-    assert len(Top.instances) == 3
-
-    # And check that Signal is connected to all three Instances
-    assert Top.h1.b_s is Top.s
-    assert Top.h2.b_s is Top.s
-    assert Top.h3.b_s is Top.s
-
-
 def test_multiple_signals_in_port_group():
     """Test, or at least try to test, jamming more than one Signal
     into the `group` concept used by the `ResolvePortRefs` elaborator pass.
@@ -1486,41 +1006,12 @@ class O:
     assert "Missing connection to" in str(e.value)
 
 
-def test_anon_bundle_refs():
-    """Test adding `BundleRef`s to `AnonymousBundle`s."""
-
-    @h.bundle
-    class Diff:
-        p, n = h.Signals(2)
-
-    @h.module
-    class HasDiff:
-        # Module with a `Diff` Port
-        d = Diff(port=True)
-
-    @h.module
-    class HasHasDiff:
-        # Module instantiating a few `HasDiff`
-        d = Diff()
-        # Instance connected to the Bundle Instance
-        h1 = HasDiff(d=d)
-
-        # THE POINT OF THE TEST:
-        # Instance with an Anon Bundle, "equal to" the Bundle Instance
-        h2 = HasDiff(d=h.AnonymousBundle(p=d.p, n=d.n))
-        # Instance with (p, n) flipped
-        h3 = HasDiff(d=h.AnonymousBundle(p=d.n, n=d.p))
-
-    h.elaborate(HasHasDiff)
-
-
-@pytest.mark.xfail(reason="#21 https://github.com/dan-fritchman/Hdl21/issues/21")
 def test_generator_port_slice():
     """Test taking a slice from a Generator `PortRef`."""
 
     @h.paramclass
     class Width:
-        width: h.Param(dtype=int, desc="Width")
+        width = h.Param(dtype=int, desc="Width")
 
     @h.generator
     def SliceMyP(params: Width) -> h.Module:
@@ -1615,3 +1106,66 @@ def test_deep_hierarchy():
     M1.i.not_a_real_port = M1.p
     with pytest.raises(RuntimeError):
         h.elaborate(m)
+
+
+def test_generator_eq():
+    """Test equality and hashing of Generator calls using `Default`."""
+
+    @h.paramclass
+    class Params:
+        p = h.Param(dtype=int, desc="p", default=111)
+
+    @h.generator
+    def Gen(_: Params) -> h.Module:
+        return h.Module()
+
+    assert Gen() == Gen()
+    assert Gen() == Gen(Params(p=111))
+    assert Gen() == Gen(p=111)
+    assert Gen().params == Params()
+    assert Gen().params == Params(p=111)
+
+    assert hash(Gen()) == hash(Gen(Params(p=111)))
+    assert hash(Gen()) == hash(Gen(p=111))
+
+
+def test_param_calls():
+    """ " Test creation of `Primitive`s and `ExternalModule` calls with inline construction of their parameters."""
+
+    Ext = h.ExternalModule(name="Ext", port_list=[], paramtype=dict)
+    call = Ext(a=1, b="c", d=TabError)
+    assert isinstance(call, h.ExternalModuleCall)
+    assert call.params == dict(a=1, b="c", d=TabError)
+
+    with pytest.raises(TypeError):
+        Ext("not_a_dict_as_positional_arg")
+    with pytest.raises(RuntimeError):
+        Ext(dict(), b="d")
+
+    @h.paramclass
+    class P:
+        reqd = h.Param(dtype=float, desc="reqd")
+        a = h.Param(dtype=int, desc="a", default=111)
+        b = h.Param(dtype=str, desc="b", default="c")
+
+    Ext = h.ExternalModule(name="Ext", port_list=[], paramtype=P)
+    call = Ext(reqd=3.14159, a=1, b="q")
+    assert isinstance(call, h.ExternalModuleCall)
+    assert call.params == P(reqd=3.14159, a=1, b="q")
+
+    with pytest.raises(TypeError):
+        Ext("not_a_P_as_positional_arg")
+    with pytest.raises(RuntimeError):
+        Ext(P(reqd=3.14159), a=12)
+
+    from hdl21.primitives import R, ResistorParams
+
+    call = R(r=11)
+    assert isinstance(call, h.PrimitiveCall)
+    assert call == R(R.Params(r=11))
+    assert call == R(ResistorParams(r=11))
+
+    with pytest.raises(TypeError):
+        R("not_a_ResistorParams_as_positional_arg")
+    with pytest.raises(RuntimeError):
+        R(R.Params(r=11), z=12)
diff --git a/hdl21/tests/test_params.py b/hdl21/tests/test_params.py
index 63bb359..76db3e2 100644
--- a/hdl21/tests/test_params.py
+++ b/hdl21/tests/test_params.py
@@ -3,9 +3,8 @@
 # Unit Tests 
 """
 
-import copy, pytest
-from dataclasses import field
-from enum import Enum, EnumMeta, auto
+import pytest
+from dataclasses import field, FrozenInstanceError
 
 from pydantic import ValidationError
 from pydantic.dataclasses import dataclass
@@ -101,25 +100,19 @@ class Outer:
 def test_bad_params1():
     # Test a handful of errors Params and paramclasses should raise.
 
-    from hdl21 import (
-        paramclass,
-        Param,
-        FrozenInstanceError,
-    )
-
     with pytest.raises(RuntimeError):
-        # Test that creating a paramclass with parent-class(es) fails
+        # Test that creating a h.paramclass with parent-class(es) fails
 
-        @paramclass
+        @h.paramclass
         class C(TabError):  # Of course this is a sub-class of the best built-in class
             ...
 
-    @paramclass
+    @h.paramclass
     class C:
-        a = Param(dtype=int, desc="Gonna Fail!")
+        a = h.Param(dtype=int, desc="Gonna Fail!")
 
     with pytest.raises(RuntimeError):
-        # Test that sub-classing a paramclass fails
+        # Test that sub-classing a h.paramclass fails
 
         class D(C):
             ...
@@ -134,7 +127,7 @@ class D(C):
         c = C(a=TabError)
 
     with pytest.raises(FrozenInstanceError):
-        # Test that attempts at mutating paramclasses fail
+        # Test that attempts at mutating h.paramclasses fail
         c = C(a=3)
         c.a = 4
 
diff --git a/hdl21/tests/test_source_info.py b/hdl21/tests/test_source_info.py
new file mode 100644
index 0000000..5da1a3d
--- /dev/null
+++ b/hdl21/tests/test_source_info.py
@@ -0,0 +1,24 @@
+import importlib
+from pathlib import Path
+from types import FrameType, ModuleType
+from hdl21.source_info import source_info, SourceInfo
+
+
+def test_source_info():
+    """ Test getting `SourceInfo` for the current call-stack. 
+    Note the `linenum` assertions here generally change if this file changes. 
+    This file is kept short on purpose! """
+
+    call = source_info(get_pymodule=True)
+
+    assert isinstance(call, SourceInfo)
+    assert call.filepath == Path(__file__)
+    assert call.linenum == 12
+    assert isinstance(call.pymodule, ModuleType)
+
+    call = source_info(get_pymodule=False)
+
+    assert isinstance(call, SourceInfo)
+    assert call.filepath == Path(__file__)
+    assert call.linenum == 19
+    assert call.pymodule is None