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

Issue:
`NoConn` should work for *any* port
Hdl21 requires each port on each instance be connected.  
For intentionally unused ports, Hdl21 then has a `NoConn` special-Signal type which indicates "don't worry about it".  

Currently this is only supported for scalar, width-one Signals.  
We should make it work for a broader range of cases, including: 

* Non unit width Signals
* Bundle-valued ports 

Example use case: 

```python 
    @h.module
    class Inner():
        # Inner module with a handful of port-types
        s = h.Port()
        b = h.Port(width=11)
        d = h.Diff(port=True)

    @h.module 
    class Outer:
        i = Inner(s=h.NoConn(), b=h.NoConn(), d=h.NoConn())
    
    h.elaborate(Outer)
```
"Orphaned" Instance Connections
Hdl21 elaboration includes an "orphanage" checker that prevents Modules from "stealing" each other's attributes. 

For example, from its docs: 

```python
    m1 = h.Module(name='m1')
    m1.s = h.Signal() # Signal `s` is now "parented" by `m1`

    m2 = h.Module(name='m2')
    m2.y = m1.s # Now `s` has been "orphaned" (or perhaps "cradle-robbed") by `m2`
```

This probably doesn't go quite far enough. It inspects everything in the Module `namespace`, which includes all its Signals, Ports, Bundles, Instances, and the like. But it does not dig into: 

* Instance connections 
* References to ports or bundles

For example this "orphan" signal survives elaboration: 

```python
    m1 = h.Module(name="m1")
    m1.p = h.Port() 

    # The "orphan signal" will not be owned by any module
    the_orphan_signal = h.Signal()
    m2 = h.Module(name="m2")
    m2.i = m1(p=the_orphan_signal) # <= Problem's here

    h.elaborate(m2) # Should fail, doesn't currently. 
```

This generally fails when we attempt to export or netlist such a Module instead, pretty confusingly. 
Netlisting: convert inline
The signature for `hdl21.netlist` is: 

```python 
def netlist(
    pkg: vlsir.circuit.Package, dest: IO, fmt: NetlistFormatSpec = "spectre"
) -> None:
```

This means in order to netlist, one must first convert an `hdl21.Module` (or container thereof) to `vlsir.Package`, generally via `hdl21.to_proto`. Calls then generally look something like: 

```python 
h.netlist(h.to_proto(MyModule), dest=sys.stdout)
```

Where `MyModule` is an `hdl21.Module`. 

The issue: do this inline. Enable calls like: 

```python 
h.netlist(MyModule, dest=sys.stdout)
```

And: 

```python 
h.netlist([MyModule1, MyModule2], dest=sys.stdout)
```


Pydantic v1.9
Updating to `pydantic` v1.9 generates a handful of errors along these lines: 

```
_____________________________________ test_slice_resolution _____________________________________

    def test_slice_resolution():
        """ Test resolutions of slice combinations """
    
        from hdl21.elab import _resolve_slice
    
        # Slice of a Signal
        s = h.Signal(width=5)
        assert _resolve_slice(s[0]) == s[0]
    
        # Slice of a Concat
        s1 = h.Signal(name="s1", width=5)
        s2 = h.Signal(name="s2", width=5)
        c = h.Concat(s1, s2)
        assert _resolve_slice(c[0]) == s1[0]
        assert _resolve_slice(c[1]) == s1[1]
        assert _resolve_slice(c[2]) == s1[2]
        assert _resolve_slice(c[3]) == s1[3]
        assert _resolve_slice(c[4]) == s1[4]
        assert _resolve_slice(c[5]) == s2[0]
        assert _resolve_slice(c[6]) == s2[1]
        assert _resolve_slice(c[7]) == s2[2]
        assert _resolve_slice(c[8]) == s2[3]
        assert _resolve_slice(c[9]) == s2[4]
    
        # Slice of a Slice
        sa = h.Signal(name="sa", width=5)
>       assert _resolve_slice(sa[2:5][0]) == sa[2]

hdl21/tests/test_hdl21.py:1777: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
hdl21/signal.py:110: in __getitem__
    return _slice_(parent=self, key=key)
hdl21/signal.py:50: in _slice_
    return Slice(
<string>:9: in __init__
    ???
pydantic/dataclasses.py:98: in pydantic.dataclasses._generate_pydantic_post_init._pydantic_post_init
    ???
pydantic/main.py:1022: in pydantic.main.validate_model
    ???
pydantic/fields.py:854: in pydantic.fields.ModelField.validate
    ???
pydantic/fields.py:1064: in pydantic.fields.ModelField._validate_singleton
    ???
pydantic/fields.py:854: in pydantic.fields.ModelField.validate
    ???
E   RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)
==================================== short test summary info ====================================
FAILED hdl21/tests/test_hdl21.py::test_array_concat_conn - RecursionError: maximum recursion d...
FAILED hdl21/tests/test_hdl21.py::test_slice_resolution - RecursionError: maximum recursion de...
```

This seems to be part of what was observed in #14. 

For now the fix is to change from: 

```toml
[tool.poetry.dependencies]
pydantic = "^1.8.2"
```

("Compatible with 1.8.2"),  to: 

```toml
[tool.poetry.dependencies]
pydantic = "1.8.2"
```

("Exactly 1.8.2")


Add `default_factory` to `Param`s
Hdl21 `Param`s have had the commented `default_factory` attribute here, essentially from day one: 

```python
@pydantic.dataclasses.dataclass
class Param:
    """ Parameter Declaration """

    dtype: Any  # Datatype. Required
    desc: str  # Description. Required
    default: Optional[Any] = _default  # Default Value. Optional

    # Default factories are supported in std-lib dataclasses, but "in beta" in `pydantic.dataclasses`.
    # default_factory: Optional[Any] = _default  # Default Call-Factory
```

This should be fixed in Pydantic by now, and should be enable-able for Hdl21.  
Note such updates sit behind #15. 


Discussion / hints:

Fix patch:
diff --git a/SampleSitePdks/setup.py b/SampleSitePdks/setup.py
index 871601d..4bb4032 100644
--- a/SampleSitePdks/setup.py
+++ b/SampleSitePdks/setup.py
@@ -33,6 +33,6 @@
         f"asap7-hdl21=={_VLSIR_VERSION}",
     ],
     extras_require={
-        "dev": ["pytest==5.2", "coverage", "pytest-cov", "black==19.10b0", "twine"]
+        "dev": ["pytest==7.1", "coverage", "pytest-cov", "black==22.6", "twine"]
     },
 )
diff --git a/SampleSitePdks/sitepdks.py b/SampleSitePdks/sitepdks.py
index dd2a816..2fc06ca 100644
--- a/SampleSitePdks/sitepdks.py
+++ b/SampleSitePdks/sitepdks.py
@@ -34,7 +34,4 @@
 # ASAP7
 import asap7
 
-asap7.install = asap7.Install(
-    model_lib=Path("pdks") / "asap7" / ... / "7nm_TT.pm"
-)
-
+asap7.install = asap7.Install(model_lib=Path("pdks") / "asap7" / ... / "7nm_TT.pm")
diff --git a/examples/pi.py b/examples/pi.py
index 85d7eed..69da906 100644
--- a/examples/pi.py
+++ b/examples/pi.py
@@ -9,8 +9,8 @@
 
 @h.bundle
 class QuadClock:
-    """ # Quadrature Clock Bundle 
-    Includes four 90-degree-separated phases. """
+    """# Quadrature Clock Bundle
+    Includes four 90-degree-separated phases."""
 
     class Roles(Enum):
         # Clock roles: source or sink
@@ -23,22 +23,37 @@ class Roles(Enum):
 
 Inv = h.ExternalModule(
     name="Inv",
-    port_list=[h.Input(name="i"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="i"),
+        h.Output(name="z"),
+    ],
     desc="Generic Inverter",
 )
 TriInv = h.ExternalModule(
     name="TriInv",
-    port_list=[h.Input(name="i"), h.Input(name="en"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="i"),
+        h.Input(name="en"),
+        h.Output(name="z"),
+    ],
     desc="Generic Tri-State Inverter",
 )
 Flop = h.ExternalModule(
     name="Flop",
-    port_list=[h.Input(name="d"), h.Input(name="clk"), h.Output(name="q"),],
+    port_list=[
+        h.Input(name="d"),
+        h.Input(name="clk"),
+        h.Output(name="q"),
+    ],
     desc="Generic Rising-Edge D Flip Flop",
 )
 Latch = h.ExternalModule(
     name="Latch",
-    port_list=[h.Input(name="d"), h.Input(name="clk"), h.Output(name="q"),],
+    port_list=[
+        h.Input(name="d"),
+        h.Input(name="clk"),
+        h.Output(name="q"),
+    ],
     desc="Generic Active High Level-Sensitive Latch",
 )
 
@@ -50,7 +65,10 @@ class WeightInvParams:
 
 WeightInv = h.ExternalModule(
     name="WeightInv",
-    port_list=[h.Input(name="i"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="i"),
+        h.Output(name="z"),
+    ],
     desc="Weighting Inverter",
     paramtype=WeightInvParams,
 )
@@ -64,9 +82,9 @@ class PhaseWeighterParams:
 
 @h.generator
 def PhaseWeighter(p: PhaseWeighterParams) -> h.Module:
-    """ # Phase-Weighter
-    Drives a single output with two out-of-phase inputs `a` and `b`, 
-    with weights dictates by params `wta` and `wtb`. """
+    """# Phase-Weighter
+    Drives a single output with two out-of-phase inputs `a` and `b`,
+    with weights dictates by params `wta` and `wtb`."""
 
     @h.module
     class PhaseWeighter:
@@ -91,24 +109,24 @@ class PhaseWeighter:
 
 @h.paramclass
 class PiParams:
-    """ Phase Interpolator Parameters """
+    """Phase Interpolator Parameters"""
 
     nbits = h.Param(dtype=int, default=5, desc="Resolution, or width of select-input.")
 
 
 @h.generator
 def PhaseGenerator(p: PiParams) -> h.Module:
-    """ # Phase Generator (Generator) (Get it?) 
-    
-    Takes a primary input `QuadClock` and interpolates to produce 
-    an array of equally-spaced output phases. """
+    """# Phase Generator (Generator) (Get it?)
+
+    Takes a primary input `QuadClock` and interpolates to produce
+    an array of equally-spaced output phases."""
 
     PhaseGen = h.Module()
     ckq = PhaseGen.ckq = QuadClock(
         role=QuadClock.Roles.SINK, port=True, desc="Quadrature input"
     )
     phases = PhaseGen.phases = h.Output(
-        width=2 ** p.nbits, desc="Array of equally-spaced phases"
+        width=2**p.nbits, desc="Array of equally-spaced phases"
     )
 
     if p.nbits != 5:
@@ -150,7 +168,7 @@ def PhaseGenerator(p: PiParams) -> h.Module:
 
 @h.generator
 def OneHotEncode5to32(_: h.HasNoParams) -> h.Module:
-    """ 5 to 32b One-Hot Encoder """
+    """5 to 32b One-Hot Encoder"""
     m = h.Module()
     m.inp = h.Input(width=5, desc="Binary-Encoded Input")
     m.out = h.Output(width=32, desc="One-Hot-Encoded Output")
@@ -160,12 +178,12 @@ def OneHotEncode5to32(_: h.HasNoParams) -> h.Module:
 
 @h.generator
 def PhaseSelector(p: PiParams) -> h.Module:
-    """ # Phase Selector Mux """
+    """# Phase Selector Mux"""
 
     @h.module
     class PhaseSelector:
         # IO Interface
-        phases = h.Input(width=2 ** p.nbits, desc="Array of equally-spaced phases")
+        phases = h.Input(width=2**p.nbits, desc="Array of equally-spaced phases")
         sel = h.Input(width=p.nbits, desc="Selection input")
         out = h.Output(width=1, desc="Clock output")
 
@@ -178,7 +196,7 @@ class PhaseSelector:
 
 @h.generator
 def PhaseInterp(p: PiParams) -> h.Module:
-    """ Phase Interpolator Generator """
+    """Phase Interpolator Generator"""
 
     @h.module
     class PhaseInterp:
@@ -188,7 +206,7 @@ class PhaseInterp:
         out = h.Output(width=1, desc="Clock output")
 
         # Internal Signals
-        phases = h.Signal(width=2 ** p.nbits, desc="Array of equally-spaced phases")
+        phases = h.Signal(width=2**p.nbits, desc="Array of equally-spaced phases")
 
         # Instantiate the phase-generator and phase-selector
         phgen = PhaseGenerator(p)(ckq=ckq, phases=phases)
@@ -201,4 +219,3 @@ class PhaseInterp:
 import sys
 
 h.netlist(h.to_proto(PhaseInterp()), dest=sys.stdout)
-
diff --git a/examples/rdac.py b/examples/rdac.py
index 13b34e7..2ba41da 100644
--- a/examples/rdac.py
+++ b/examples/rdac.py
@@ -164,9 +164,9 @@ class MuxTreeParams:
 
 @h.generator
 def mux_tree(params: MuxTreeParams) -> h.Module:
-    """ Binary Mux Tree Generator """
+    """Binary Mux Tree Generator"""
 
-    n_inputs = 2 ** params.nbit
+    n_inputs = 2**params.nbit
     p_ctrl = params.mux_params.nmos is not None
     n_ctrl = params.mux_params.pmos is not None
 
@@ -194,11 +194,11 @@ class MuxTree:
         layer_mux_conns["ctrl_b"] = MuxTree.ctrl_b[layer]
         if layer != 0:
             curr_output = MuxTree.add(
-                name=f"sig_{layer}", val=h.Signal(width=2 ** layer)
+                name=f"sig_{layer}", val=h.Signal(width=2**layer)
             )
         else:
             curr_output = MuxTree.out
-        for mux_idx in range(2 ** layer):
+        for mux_idx in range(2**layer):
             mux_conns = layer_mux_conns.copy()
             mux_conns["sourceA"] = curr_input[2 * mux_idx]
             mux_conns["sourceB"] = curr_input[2 * mux_idx + 1]
@@ -238,7 +238,7 @@ def external_factory(
 
 
 def main():
-    """ Main function, generating an `rladder` and `mux_tree` and netlisting each. """
+    """Main function, generating an `rladder` and `mux_tree` and netlisting each."""
 
     rparams = RLadderParams(
         nseg=15,
@@ -260,10 +260,10 @@ def main():
     proto = h.to_proto([rladder(rparams), mux_tree(mparams)], domain="rdac")
 
     # And netlist in a handful of formats
-    h.netlist(proto, sys.stdout, "verilog")
-    h.netlist(proto, sys.stdout, "spectre")
-    h.netlist(proto, sys.stdout, "spice")
-    h.netlist(proto, sys.stdout, "xyce")
+    h.netlist(proto, sys.stdout, fmt="verilog")
+    h.netlist(proto, sys.stdout, fmt="spectre")
+    h.netlist(proto, sys.stdout, fmt="spice")
+    h.netlist(proto, sys.stdout, fmt="xyce")
 
 
 if __name__ == "__main__":
diff --git a/examples/ro.py b/examples/ro.py
index 55d6fa6..3ef9156 100644
--- a/examples/ro.py
+++ b/examples/ro.py
@@ -26,7 +26,7 @@
 
 @h.module
 class GatedInvWrapper:
-    """ Thin wrapper over `GatedInv` """
+    """Thin wrapper over `GatedInv`"""
 
     inp, enb, VDD, VSS = h.Inputs(4)
     out = h.Output()
@@ -57,7 +57,7 @@ def __post_init_post_parse__(self):
 
 @h.generator
 def Ro(params: RoParams) -> h.Module:
-    """ Create a parametric oscillator """
+    """Create a parametric oscillator"""
     from hdl21.primitives import IdealCapacitor as C
 
     ro = h.Module()
@@ -87,16 +87,16 @@ def Ro(params: RoParams) -> h.Module:
 
 @h.generator
 def RoTb(params: RoParams) -> h.Module:
-    """ Ring Osc Testbench
-    Primarily instantiates the RO and a thermometer-encoder for its control code. 
-
-    This module includes a bit of hacking into the SPICE-domain world. 
-    It has no ports and is meant to be "the entirety" of a sim, save for analysis and control statements. 
-    Don't try it on its own! At minimum, it relies on: 
-    * SPICE/ Spectre's concept of "node zero", which it instantiates, and 
-    * A SPICE-level parameter `code` which sets the integer control value. 
-    This serves as an early case-study for how hdl21 modules and 
-    these SPICE-native things can play together (and perhaps how they shouldn't). 
+    """Ring Osc Testbench
+    Primarily instantiates the RO and a thermometer-encoder for its control code.
+
+    This module includes a bit of hacking into the SPICE-domain world.
+    It has no ports and is meant to be "the entirety" of a sim, save for analysis and control statements.
+    Don't try it on its own! At minimum, it relies on:
+    * SPICE/ Spectre's concept of "node zero", which it instantiates, and
+    * A SPICE-level parameter `code` which sets the integer control value.
+    This serves as an early case-study for how hdl21 modules and
+    these SPICE-native things can play together (and perhaps how they shouldn't).
     """
     from hdl21.primitives import V
 
diff --git a/examples/serdes.py b/examples/serdes.py
index 19494e9..860d633 100644
--- a/examples/serdes.py
+++ b/examples/serdes.py
@@ -10,12 +10,19 @@
 
 Inv = h.ExternalModule(
     name="Inv",
-    port_list=[h.Input(name="i"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="i"),
+        h.Output(name="z"),
+    ],
     desc="Generic Inverter",
 )
 And2 = h.ExternalModule(
     name="And2",
-    port_list=[h.Input(name="a"), h.Input(name="b"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="a"),
+        h.Input(name="b"),
+        h.Output(name="z"),
+    ],
     desc="Generic 3-Input And Gate",
 )
 And3 = h.ExternalModule(
@@ -30,12 +37,20 @@
 )
 TriInv = h.ExternalModule(
     name="TriInv",
-    port_list=[h.Input(name="i"), h.Input(name="en"), h.Output(name="z"),],
+    port_list=[
+        h.Input(name="i"),
+        h.Input(name="en"),
+        h.Output(name="z"),
+    ],
     desc="Generic Tri-State Inverter",
 )
 Flop = h.ExternalModule(
     name="Flop",
-    port_list=[h.Input(name="d"), h.Input(name="clk"), h.Output(name="q"),],
+    port_list=[
+        h.Input(name="d"),
+        h.Input(name="clk"),
+        h.Output(name="q"),
+    ],
     desc="Generic Rising-Edge D Flip Flop",
 )
 FlopResetLow = h.ExternalModule(
@@ -60,14 +75,18 @@
 )
 Latch = h.ExternalModule(
     name="Latch",
-    port_list=[h.Input(name="d"), h.Input(name="clk"), h.Output(name="q"),],
+    port_list=[
+        h.Input(name="d"),
+        h.Input(name="clk"),
+        h.Output(name="q"),
+    ],
     desc="Generic Active High Level-Sensitive Latch",
 )
 
 
 @h.bundle
 class Diff:
-    """ Differential Bundle """
+    """Differential Bundle"""
 
     class Roles(Enum):
         SOURCE = auto()
@@ -78,18 +97,18 @@ class Roles(Enum):
 
 @h.paramclass
 class Width:
-    """ Parameter class for Generators with a single integer-valued `width` parameter. """
+    """Parameter class for Generators with a single integer-valued `width` parameter."""
 
     width = h.Param(dtype=int, desc="Parametric Width", default=1)
 
 
 @h.module
 class OneHotEncoder2to4:
-    """ 
+    """
     # One-Hot Encoder
-    2b to 4b with enable. 
-    Also serves as the base-case for the recursive `OneHotEncoder` generator. 
-    All outputs are low if enable-input `en` is low. 
+    2b to 4b with enable.
+    Also serves as the base-case for the recursive `OneHotEncoder` generator.
+    All outputs are low if enable-input `en` is low.
     """
 
     # IO Interface
@@ -113,11 +132,11 @@ class OneHotEncoder2to4:
 
 @h.generator
 def OneHotEncoder(p: Width) -> h.Module:
-    """ 
-    # One-Hot Encoder Generator 
-    Recursively creates a `p.width`-bit one-hot encoder Module comprised of `OneHotEncoder2to4`s. 
-    Also generates `OneHotEncoder` Modules for `p.width-2`, `p.width-4`, et al, down to 
-    the base case two to four bit Module. 
+    """
+    # One-Hot Encoder Generator
+    Recursively creates a `p.width`-bit one-hot encoder Module comprised of `OneHotEncoder2to4`s.
+    Also generates `OneHotEncoder` Modules for `p.width-2`, `p.width-4`, et al, down to
+    the base case two to four bit Module.
     """
 
     if p.width < 2:
@@ -132,7 +151,7 @@ def OneHotEncoder(p: Width) -> h.Module:
     m = h.Module()
     m.en = h.Input(width=1, desc="Enable input. Active high.")
     m.bin = h.Input(width=p.width, desc="Binary valued input")
-    m.th = h.Output(width=2 ** p.width, desc="Thermometer encoded output")
+    m.th = h.Output(width=2**p.width, desc="Thermometer encoded output")
 
     # Thermo-encode the two MSBs, creating select signals for the LSBs
     m.lsb_sel = h.Signal(width=4)
@@ -148,7 +167,7 @@ def OneHotEncoder(p: Width) -> h.Module:
 
 @h.generator
 def OneHotMux(p: Width) -> h.Module:
-    """ # One-Hot Selected Mux """
+    """# One-Hot Selected Mux"""
 
     m = h.Module()
 
@@ -165,7 +184,7 @@ def OneHotMux(p: Width) -> h.Module:
 
 @h.generator
 def Counter(p: Width) -> h.Module:
-    """ # Binary Counter Generator """
+    """# Binary Counter Generator"""
 
     m = h.Module()
     m.clk = h.Input(desc="Primary input. Increments state on each rising edge.")
@@ -179,9 +198,9 @@ def Counter(p: Width) -> h.Module:
 
 @h.generator
 def OneHotRotator(p: Width) -> h.Module:
-    """ # One Hot Rotator 
-    A set of `p.width` flops with a one-hot state, 
-    which rotates by one bit on each clock cycle. """
+    """# One Hot Rotator
+    A set of `p.width` flops with a one-hot state,
+    which rotates by one bit on each clock cycle."""
 
     m = h.Module()
     # IO Interface: Clock, Reset, and a `width`-bit One-Hot output
@@ -207,8 +226,8 @@ def OneHotRotator(p: Width) -> h.Module:
 
 @h.generator
 def TxSerializer(_: h.HasNoParams) -> h.Module:
-    """ Transmit Serializer 
-    Includes parallel-clock generation divider """
+    """Transmit Serializer
+    Includes parallel-clock generation divider"""
 
     m = h.Module()
     m.pdata = h.Input(width=16, desc="Parallel Input Data")
@@ -231,8 +250,8 @@ def TxSerializer(_: h.HasNoParams) -> h.Module:
 
 @h.generator
 def RxDeSerializer(_: h.HasNoParams) -> h.Module:
-    """ RX De-Serializer 
-    Includes parallel-clock generation divider """
+    """RX De-Serializer
+    Includes parallel-clock generation divider"""
 
     m = h.Module()
     m.pdata = h.Output(width=16, desc="Parallel Output Data")
@@ -260,7 +279,7 @@ def RxDeSerializer(_: h.HasNoParams) -> h.Module:
 
 @h.generator
 def TxDriver(_: h.HasNoParams) -> h.Module:
-    """ Transmit Driver """
+    """Transmit Driver"""
 
     m = h.Module()
 
@@ -276,15 +295,15 @@ def TxDriver(_: h.HasNoParams) -> h.Module:
 
 @h.module
 class SerdesShared:
-    """ Serdes 'Shared' Module 
-    Central, re-used elements amortized across lanes """
+    """Serdes 'Shared' Module
+    Central, re-used elements amortized across lanes"""
 
     ...  # So far, empty
 
 
 @h.bundle
 class TxData:
-    """ Transmit Data Bundle """
+    """Transmit Data Bundle"""
 
     pdata = h.Input(width=10, desc="Parallel Data Input")
     pclk = h.Output(desc="Output Parallel-Domain Clock")
@@ -292,14 +311,14 @@ class TxData:
 
 @h.bundle
 class TxConfig:
-    """ Transmit Config Bundle """
+    """Transmit Config Bundle"""
 
     ...  # FIXME: contents!
 
 
 @h.bundle
 class TxIo:
-    """ Transmit Lane IO """
+    """Transmit Lane IO"""
 
     pads = Diff(desc="Differential Transmit Pads", role=Diff.Roles.SOURCE)
     data = TxData(desc="Data IO from Core")
@@ -308,7 +327,7 @@ class TxIo:
 
 @h.module
 class SerdesTxLane:
-    """ Transmit Lane """
+    """Transmit Lane"""
 
     # IO
     # io = TxIo(port=True) # FIXME: combined bundle
@@ -329,7 +348,7 @@ class SerdesTxLane:
 
 @h.module
 class SerdesRxLane:
-    """ Receive Lane """
+    """Receive Lane"""
 
     # IO
     # io = RxIo(port=True) # FIXME: combined bundle
@@ -355,14 +374,14 @@ class SerdesRxLane:
 
 @h.bundle
 class SerdesCoreIf:
-    """ # Serdes to Core Interface """
+    """# Serdes to Core Interface"""
 
     ...  #
 
 
 @h.module
 class SerdesLane:
-    """ TX + RX Lane """
+    """TX + RX Lane"""
 
     # IO Interface
     pads = Diff(desc="Differential Pads", port=True, role=None)
@@ -379,7 +398,7 @@ class SerdesParams:
 
 @h.generator
 def Serdes(p: SerdesParams) -> h.Module:
-    """ Serdes Generator """
+    """Serdes Generator"""
     s = h.Module()
     s.lanes = p.lanes * SerdesLane()
     s.shared = SerdesShared()
@@ -397,12 +416,24 @@ def rotator_tb() -> h.Module:
 
     tb.vsclk = h.primitives.Vpulse(
         Vpulse.Params(
-            delay=0, v1=0, v2=1800 * m, period=2, rise=1 * p, fall=1 * p, width=1,
+            delay=0,
+            v1=0,
+            v2=1800 * m,
+            period=2,
+            rise=1 * p,
+            fall=1 * p,
+            width=1,
         )
     )(p=tb.dut.sclk, n=tb.VSS)
     tb.vrstn = h.primitives.Vpulse(
         Vpulse.Params(
-            delay=0, v1=0, v2=1800 * m, period=2, rise=1 * p, fall=1 * p, width=1,
+            delay=0,
+            v1=0,
+            v2=1800 * m,
+            period=2,
+            rise=1 * p,
+            fall=1 * p,
+            width=1,
         )
     )(p=tb.dut.rstn, n=tb.VSS)
 
@@ -444,4 +475,3 @@ def rotator_sim() -> h.sim.Sim:
 )
 
 print(results)
-
diff --git a/hdl21/__init__.py b/hdl21/__init__.py
index 1554e1f..ca835cb 100644
--- a/hdl21/__init__.py
+++ b/hdl21/__init__.py
@@ -24,14 +24,14 @@
 from .elab import *
 from .walker import HierarchyWalker
 from .instantiable import *
+from .diff_pair import *
 from . import prefix
 from .prefix import Prefix, Prefixed
 from . import sim
+from . import proto
+from .proto import to_proto, from_proto
 
 # Update all the forward type-references throughout our many `@datatype`s
 from .datatype import _update_forward_refs
 
 _update_forward_refs()
-
-from . import proto
-from .proto import to_proto, from_proto
diff --git a/hdl21/attrmagic.py b/hdl21/attrmagic.py
new file mode 100644
index 0000000..9150a4d
--- /dev/null
+++ b/hdl21/attrmagic.py
@@ -0,0 +1,45 @@
+"""
+# Hdl21 Attribute Magic 
+
+Facilities for all of the `getattr` and `setattr` "magic" used by Hdl21 types. 
+
+There is fairly little "magic" (ahem) being performed here; 
+this module serves as more of a written reminder of how Hdl21's `getattr` and `setattr` overrides work. 
+
+Getting them completely straight can be a pain, including a few complications: 
+* During object initialization, and for setting "built-ins" e.g. the "dunder" methods 
+* Many failures tend to occur only while *debugging*, e.g. with `pdb` or an IDE plugin. 
+  * This makes tracking down problems with this scheme aways more difficult, as unit-tests fail to catch them. 
+
+The scheme adopted here is what we've empirically observed to be the most reliable. 
+* A `_initialized` boolean field is set to `False` as the VERY FIRST thing in object construction.
+* All "magic methods" check the value of this field, and run "regular" `setattr` and `getattr` if it is `False`.
+* The `_initialized` field is then set to `True` at the end of object construction.
+
+The methods here primarily serve as a central reminder of and reusable source for this scheme. 
+"""
+
+
+def init(cls: type) -> type:
+    """
+    Initialization decorator for "attr magic" types.
+
+    Sets an `_initialized` field to `False` as part of a `__new__` method, so that `__init__` need not remember to do so.
+    Constructors are required to set `_initialized` to `True` when *all* construction has completed.
+    For inherited types, this generally includes both parent and subclass initialization.
+
+    NOTE: this is a very important part of the `setattr` and `getattr` methods,
+    particularly for their "bootstrapping phase" during object creation.
+    """
+
+    if "__new__" in cls.__dict__:
+        msg = f"Internal Error: `__new__` doubly defined for {cls}"
+        raise RuntimeError(msg)
+
+    def __new__(cls, *_, **__):
+        inst = object.__new__(cls)
+        object.__setattr__(inst, "_initialized", False)
+        return inst
+
+    cls.__new__ = __new__
+    return cls
diff --git a/hdl21/bundle.py b/hdl21/bundle.py
index 1351737..af69a0e 100644
--- a/hdl21/bundle.py
+++ b/hdl21/bundle.py
@@ -8,13 +8,14 @@
 from typing import Optional, Union, Any, get_args, Dict, Set, List
 
 # Local Imports
+from .attrmagic import init
 from .connect import connectable, track_connected_ports
 from .signal import Signal
 
 
 def getattr_bundle_refs(cls: type) -> type:
-    """ Decorator to add the "__getattr__ generates BundleRefs" functionality to `cls`. 
-    Adds the `_bundle_ref` method and `__getattr__` access to it. """
+    """Decorator to add the "__getattr__ generates BundleRefs" functionality to `cls`.
+    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__"]
@@ -24,7 +25,7 @@ def getattr_bundle_refs(cls: type) -> type:
             raise RuntimeError(msg)
 
     def _bundle_ref(self, key: str) -> "BundleRef":
-        """ Return a reference to name `key`, creating it if necessary. """
+        """Return a reference to name `key`, creating it if necessary."""
 
         # Check in our existing references
         bundle_refs = self.__getattribute__("refs_to_me")
@@ -37,7 +38,7 @@ def _bundle_ref(self, key: str) -> "BundleRef":
         return bundle_ref
 
     def __getattr__(self, key: str) -> Any:
-        """ BundleRef access by getattr """
+        """BundleRef access by getattr"""
         if key.startswith("_") or not self.__getattribute__("_initialized"):
             # Bootstrapping phase: do regular getattrs to get started
             return object.__getattribute__(self, key)
@@ -65,16 +66,17 @@ def __getattr__(self, key: str) -> Any:
 
 
 def has_getattr_bundle_refs(obj: Any) -> bool:
-    """ Boolean indication of "getattr bundle refs" functionality """
+    """Boolean indication of "getattr bundle refs" functionality"""
     return getattr(obj, "__getattr_bundle_refs__", False)
 
 
-@getattr_bundle_refs
 @track_connected_ports
+@getattr_bundle_refs
 @connectable
+@init
 class BundleInstance:
-    """ # Instance of a `Bundle` 
-    Generally in a `Module` or another `Bundle` """
+    """# Instance of a `Bundle`
+    Generally in a `Module` or another `Bundle`"""
 
     _specialcases = [
         "name",
@@ -86,9 +88,6 @@ class BundleInstance:
         "desc",
         "refs_to_me",
         "connected_ports",
-        "_bundle_ref",
-        "_elaborated",
-        "_initialized",
     ]
 
     def __init__(
@@ -113,6 +112,7 @@ def __init__(
         self.refs_to_me: Dict[str, "BundleRef"] = dict()
         # Connected port references
         self.connected_ports: Set["BundleRef"] = set()
+        self._parent_module: Optional["Module"] = None
         self._elaborated = False
         self._initialized = True
 
@@ -129,15 +129,16 @@ def __repr__(self):
 
 
 def is_bundle_attr(val: Any) -> bool:
-    """ Boolean indication of whether `val` is a valid `hdl21.Bundle` attribute. """
+    """Boolean indication of whether `val` is a valid `hdl21.Bundle` attribute."""
     return isinstance(val, get_args(BundleAttr))
 
 
+@init
 class Bundle:
-    """ 
+    """
     # hdl21 Bundle
-    
-    Bundles are structured hierarchical connection objects which include Signals and other Bundles. 
+
+    Bundles are structured hierarchical connection objects which include Signals and other Bundles.
     """
 
     def __init__(self, *, name=None):
@@ -150,7 +151,7 @@ def __init__(self, *, name=None):
         self._initialized = True
 
     def __setattr__(self, key: str, val: object):
-        """ Set-attribute over-ride, organizing into type-based containers """
+        """Set-attribute over-ride, organizing into type-based containers"""
         from .signal import Signal
 
         if not getattr(self, "_initialized", False) or key.startswith("_"):
@@ -188,27 +189,27 @@ def __getattr__(self, key):
         return object.__getattribute__(self, key)
 
     def __call__(self, **kwargs):
-        """ Calls to Bundles return Bundle Instances """
+        """Calls to Bundles return Bundle Instances"""
         return BundleInstance(of=self, **kwargs)
 
     def __init_subclass__(cls, *_args, **_kwargs):
-        """ Sub-Classing Disable-ization """
+        """Sub-Classing Disable-ization"""
         msg = f"Error attempting to create {cls.__name__}. Sub-Typing hdl21.Bundle is not supported."
         raise RuntimeError(msg)
 
     def add(self, val: BundleAttr, *, name: Optional[str] = None) -> BundleAttr:
-        raise NotImplementedError
+        raise NotImplementedError  # FIXME!
 
     def get(self, name: str) -> Optional[BundleAttr]:
-        """ Get module-attribute `name`. Returns `None` if not present. 
-        Note unlike Python built-ins such as `getattr`, `get` returns solely 
-        from the HDL namespace-worth of `BundleAttr`s. """
+        """Get module-attribute `name`. Returns `None` if not present.
+        Note unlike Python built-ins such as `getattr`, `get` returns solely
+        from the HDL namespace-worth of `BundleAttr`s."""
         ns = self.__getattribute__("namespace")
         return ns.get(name, None)
 
     @property
     def Roles(self):
-        """ Roles-Enumeration Accessor Property """
+        """Roles-Enumeration Accessor Property"""
         # Roles often look like a class, so they have a class-style name-accessor
         return self.roles
 
@@ -219,10 +220,10 @@ def __repr__(self) -> str:
 
 
 def bundle(cls: type) -> Bundle:
-    """ # Bundle Definition Decorator 
-    
-    Converts a class-body full of Bundle-storable attributes (Signals, other Bundles) to an `hdl21.Bundle`. 
-    Example Usage: 
+    """# Bundle Definition Decorator
+
+    Converts a class-body full of Bundle-storable attributes (Signals, other Bundles) to an `hdl21.Bundle`.
+    Example Usage:
 
     ```python
     import hdl21 as h
@@ -238,10 +239,10 @@ class DisplayPort:
         aux = h.Signal()
     ```
 
-    Bundles may also define a `Roles` enumeration, inline within this class-body. 
-    `Roles` are optional pieces of enumerated endpoint-labels which aid in dictating `Signal` directions. 
-    Each `Signal` accepts optional source (`src`) and destination (`dst`) fields which (if set) must be one of these roles. 
-    
+    Bundles may also define a `Roles` enumeration, inline within this class-body.
+    `Roles` are optional pieces of enumerated endpoint-labels which aid in dictating `Signal` directions.
+    Each `Signal` accepts optional source (`src`) and destination (`dst`) fields which (if set) must be one of these roles.
+
     ```python
     import hdl21 as h
     from enum import Enum, auto
@@ -287,9 +288,9 @@ class Roles(Enum):
 @track_connected_ports
 @connectable
 class AnonymousBundle:
-    """ # Anonymous Connection Bundle 
-    Commonly used for "collecting" Signals into `h.Bundle`s, 
-    or for re-jiggering connections between `h.Bundle`s. """
+    """# Anonymous Connection Bundle
+    Commonly used for "collecting" Signals into `h.Bundle`s,
+    or for re-jiggering connections between `h.Bundle`s."""
 
     def __init__(self, **kwargs: Dict[str, "Connectable"]):
         # Create our internal structures
@@ -311,7 +312,7 @@ def __init__(self, **kwargs: Dict[str, "Connectable"]):
             self.add(key, val)
 
     def add(self, name: str, val: BundleAttr) -> BundleAttr:
-        """ Add attribute `val`. """
+        """Add attribute `val`."""
 
         if isinstance(val, Signal):
             self.signals[name] = val
@@ -325,9 +326,9 @@ def add(self, name: str, val: BundleAttr) -> BundleAttr:
         return val
 
     def get(self, name: str) -> Optional[Union[Signal, "BundleRef"]]:
-        """ 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. """
+        """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:
@@ -338,41 +339,37 @@ def get(self, name: str) -> Optional[Union[Signal, "BundleRef"]]:
 @track_connected_ports
 @connectable
 class BundleRef:
-    """ Reference into a Bundle Instance """
+    """Reference into a Bundle Instance"""
 
     _specialcases = [
         "parent",
         "path",
         "root",
         "connected_ports",
-        "_bundle_ref",
-        "_module",
-        "_resolved",
-        "_elaborated",
-        "_initialized",
     ]
 
     def __init__(
-        self, parent: Union["BundleInstance", "BundleRef"], attrname: str,
+        self,
+        parent: Union["BundleInstance", "BundleRef"],
+        attrname: str,
     ):
         self.parent = parent
         self.attrname = attrname
         # Connected port references
         self.connected_ports: Set["PortRef"] = set()
         self._elaborated = False
-        self._initialized = True
 
     def __eq__(self, other) -> bool:
-        """ Port-reference equality requires *identity* between parents 
-        (and of course equality of attribute-name). """
+        """Port-reference equality requires *identity* between parents
+        (and of course equality of attribute-name)."""
         return self.inst is other.inst and self.attrname == other.attrname
 
     def __hash__(self):
-        """ Hash references as the tuple of their instance-address and name """
+        """Hash references as the tuple of their instance-address and name"""
         return hash((id(self.inst), self.attrname))
 
     def path(self) -> List[str]:
-        """ Get the path to this potentially nested reference. """
+        """Get the path to this potentially nested reference."""
         if isinstance(self.parent, BundleRef):
             return self.parent.path() + [self.attrname]
         if isinstance(self.parent, BundleInstance):
@@ -380,7 +377,7 @@ def path(self) -> List[str]:
         raise TypeError
 
     def root(self) -> "BundleInstance":
-        """ Get the root `BundleInstance` of this potentially nested reference. """
+        """Get the root `BundleInstance` of this potentially nested reference."""
         if isinstance(self.parent, BundleRef):
             return self.parent.root()
         if isinstance(self.parent, BundleInstance):
diff --git a/hdl21/connect.py b/hdl21/connect.py
index e405a02..a77b3a4 100644
--- a/hdl21/connect.py
+++ b/hdl21/connect.py
@@ -19,17 +19,18 @@
   * Adds "connect by call" and "connect by setattr" functionality. 
 """
 
+# Std-Lib Imports
 from typing import Any, Union, Dict
 
 
 def connectable(cls: type) -> type:
-    """ Decorator for connectable types """
+    """Decorator for connectable types"""
     cls.__connectable__ = True
     return cls
 
 
 def is_connectable(obj: Any) -> bool:
-    """ Boolean indication of connect-ability """
+    """Boolean indication of connect-ability"""
     return getattr(obj, "__connectable__", False)
 
 
@@ -37,8 +38,8 @@ def is_connectable(obj: Any) -> bool:
 
 
 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. """
+    """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
@@ -46,17 +47,17 @@ def track_connected_ports(cls: type) -> type:
 
 
 def does_track_connected_ports(obj: Any) -> bool:
-    """ Boolean indication of connected-port tracking """
+    """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. """
+    """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`")
@@ -69,14 +70,14 @@ def call_and_setattr_connects(cls: type) -> type:
             raise RuntimeError(msg)
 
     def __call__(self, **kwargs) -> "Self":
-        """ Connect-by-call """
+        """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 """
+        """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)
@@ -86,14 +87,14 @@ def __setattr__(self, key: str, val: Any) -> None:
         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. """
+        """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 .instance import PortRef
+        from .portref import PortRef
 
         if isinstance(conn, Dict):
             # Special-case dictionaries of connectables into Anon Bundles
@@ -115,10 +116,10 @@ def connect(self, portname: str, conn: Connectable) -> "Self":
         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 .instance import PortRef
+        """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):
@@ -126,21 +127,21 @@ def disconnect(self, portname: str) -> Connectable:
         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 .instance import PortRef
+        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
@@ -162,17 +163,17 @@ def replace(self, portname: str, conn: Connectable) -> Connectable:
 
 
 def does_call_and_setattr_connect(obj: Any) -> bool:
-    """ Boolean indication of "call and setattr connects" functionality """
+    """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`. 
+    """Decorator to add the "__getattr__ generates PortRefs" functionality to `cls`.
 
-    Adds the `_port_ref` method and `__getattr__` access to it. 
+    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. """
+    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__"]
@@ -182,8 +183,8 @@ def getattr_port_refs(cls: type) -> type:
             raise RuntimeError(msg)
 
     def _port_ref(self, key: str) -> "PortRef":
-        """ Return a port-reference to name `key`, creating it if necessary. """
-        from .instance import 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")
@@ -196,7 +197,7 @@ def _port_ref(self, key: str) -> "PortRef":
         return port_ref
 
     def __getattr__(self, key: str) -> Any:
-        """ Port access by getattr """
+        """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)
@@ -229,5 +230,5 @@ def __getattr__(self, key: str) -> Any:
 
 
 def has_getattr_port_refs(obj: Any) -> bool:
-    """ Boolean indication of "getattr port refs" functionality """
+    """Boolean indication of "getattr port refs" functionality"""
     return getattr(obj, "__getattr_port_refs__", False)
diff --git a/hdl21/datatype.py b/hdl21/datatype.py
index d862206..2d7dd92 100644
--- a/hdl21/datatype.py
+++ b/hdl21/datatype.py
@@ -34,7 +34,7 @@
 
 
 def datatype(cls: type) -> type:
-    """ Register a class as a datatype. """
+    """Register a class as a datatype."""
 
     # Convert `cls` to a `pydantic.dataclasses.dataclass`,
     # and add it to the list of datatypes
@@ -44,6 +44,6 @@ def datatype(cls: type) -> type:
 
 
 def _update_forward_refs():
-    """ Update all the forward type-references """
+    """Update all the forward type-references"""
     for tp in datatypes:
         tp.__pydantic_model__.update_forward_refs()
diff --git a/hdl21/diff_pair.py b/hdl21/diff_pair.py
new file mode 100644
index 0000000..c32f7bc
--- /dev/null
+++ b/hdl21/diff_pair.py
@@ -0,0 +1,61 @@
+"""
+# `Diff` Differential Bundle and `Pair` Instance Pair 
+"""
+
+# Std-Lib Imports
+from enum import Enum, auto
+
+# Local imports
+from .signal import Signals
+from .instance import InstanceBundleType
+from .bundle import bundle, AnonymousBundle
+
+
+@bundle
+class Diff:
+    """Differential Bundle"""
+
+    class Roles(Enum):
+        SOURCE = auto()
+        SINK = auto()
+
+    p, n = Signals(2, src=Roles.SOURCE, dest=Roles.SINK)
+
+
+def inverse(d: Diff) -> AnonymousBundle:
+    """Create a Bundle with the same signals as `d`, but with `p` and `n` reversed."""
+    return AnonymousBundle(p=d.n, n=d.p)
+
+
+# Instance Pair Class
+Pair = InstanceBundleType(
+    name="Pair",
+    bundle=Diff,
+    doc=f"""
+        # Differential Instance Pair 
+
+        Two identical instances of the same `Module` which can be connected to either `Diff` signal bundles or scalar `Signal`s.  
+        The rules for connecting to the ports of `Pair`s are: 
+
+        * When connecting to a `Diff`, the `p` and `n` signals are connected to the `p` and `n` instances, respectively. 
+        * When connecting to a `Signal`, the `p` and `n` instances are connected in parallel to that signal. 
+        * All other connections are disallowed. 
+
+        Example: 
+    
+        ```python 
+        import hdl21 as h
+
+        @h.module 
+        class DiffResistors:
+            # Two identical, differential resistors to VSS
+            VSS = h.Port()
+            diff = h.Diff()
+            # The `Pair` instance
+            rs = h.Pair(MyResistor)(p=diff, n=VSS)
+        ```
+    """,
+)
+
+
+__all__ = ["Diff", "inverse", "Pair"]
diff --git a/hdl21/elab/__init__.py b/hdl21/elab/__init__.py
index 0c54583..4ac49c4 100644
--- a/hdl21/elab/__init__.py
+++ b/hdl21/elab/__init__.py
@@ -2,4 +2,4 @@
 # Hdl21 Elaboration Package
 """
 
-from .elab import elaborate, elab_all, Context, Elabable, Elabables, ElabPass
+from .elab import elaborate, Context, Elaboratable, Elaboratables, ElabPass
diff --git a/hdl21/elab/context.py b/hdl21/elab/context.py
index c1934cc..37f5cf0 100644
--- a/hdl21/elab/context.py
+++ b/hdl21/elab/context.py
@@ -32,7 +32,7 @@ def HasClk(_: h.HasNoParams, ctx: Context) -> h.Module:
 
 # Std-Lib Imports
 from enum import Enum, auto
-from typing import Optional, List, Tuple 
+from typing import Optional, List, Tuple
 from dataclasses import dataclass, field
 
 # Local Imports
@@ -40,7 +40,7 @@ def HasClk(_: h.HasNoParams, ctx: Context) -> h.Module:
 
 
 class ContextUseType(Enum):
-    """ Enumerated Types of Context Usages """
+    """Enumerated Types of Context Usages"""
 
     PWR = auto()
     GND = auto()
@@ -49,7 +49,7 @@ class ContextUseType(Enum):
 
 @dataclass
 class ContextUsage:
-    """ Context Usage """
+    """Context Usage"""
 
     call: "GeneratorCall"
     tp: ContextUseType
@@ -58,7 +58,7 @@ class ContextUsage:
 
 @dataclass
 class ElabSession:
-    """ # Elaboration Session """
+    """# Elaboration Session"""
 
     current: Optional["GeneratorCall"] = None
     elaborator: Optional["Elaborator"] = None
@@ -66,7 +66,7 @@ class ElabSession:
 
 
 class Context:
-    """ # Elaboration Context """
+    """# Elaboration Context"""
 
     session: Optional[ElabSession] = None
 
@@ -74,8 +74,8 @@ class Context:
     grounds: List[Signal] = field(default_factory=lambda _: [Signal(name="VSS")])
     clocks: List[Signal] = field(default_factory=lambda _: [Signal(name="clk")])
 
-    def mark(self, tp: ContextUseType, signal: Signal)-> None:
-        """ Mark a usage of the Context """
+    def mark(self, tp: ContextUseType, signal: Signal) -> None:
+        """Mark a usage of the Context"""
         if self.session.current is None:
             msg = "Invalid `Context.use` outside of `Generator`!"
             self.session.elaborator.fail(msg)
@@ -84,34 +84,33 @@ def mark(self, tp: ContextUseType, signal: Signal)-> None:
         self.session.usages.append(usage)
 
     def pwrgnd(self) -> Tuple[Signal, Signal]:
-        """ Get Power and Ground `Signal`s. 
-        Raises an Exception if the Context is configured for more than one Power or Ground. 
-        Equivalent to back-to-back calls to `pwr()` and `gnd()`. """ 
+        """Get Power and Ground `Signal`s.
+        Raises an Exception if the Context is configured for more than one Power or Ground.
+        Equivalent to back-to-back calls to `pwr()` and `gnd()`."""
         return self.pwr(), self.gnd()
 
     def pwr(self) -> Signal:
-        """ Get Power `Signal` """
+        """Get Power `Signal`"""
         if len(self.supplies) != 1:
             msg = "Invalid `Context.pwr`: multiple supplies"
             self.session.elaborator.fail(msg)
         self.mark(ContextUseType.PWR, self.supplies[0])
         return self.supplies[0]
-    
+
     def gnd(self) -> Signal:
-        """ Get Ground `Signal` """
+        """Get Ground `Signal`"""
         if len(self.grounds) != 1:
             msg = "Invalid `Context.gnd`: multiple grounds"
             self.session.elaborator.fail(msg)
 
         self.mark(ContextUseType.GND, self.grounds[0])
         return self.grounds[0]
-    
+
     def clk(self) -> Signal:
-        """ Get Clock `Signal` """
+        """Get Clock `Signal`"""
         if len(self.clks) != 1:
             msg = "Invalid `Context.clk`: multiple clocks"
             self.session.elaborator.fail(msg)
 
         self.mark(ContextUseType.CLK, self.clocks[0])
         return self.grounds[0]
-    
diff --git a/hdl21/elab/elab.py b/hdl21/elab/elab.py
index 40a4342..dd3f176 100644
--- a/hdl21/elab/elab.py
+++ b/hdl21/elab/elab.py
@@ -7,66 +7,48 @@
 """
 
 # Std-Lib Imports
-from types import SimpleNamespace
-from typing import Any, List, Optional
+from typing import List, Optional
 
 # Local imports
-from ..module import Module
-from .elaboratable import Elabable, Elabables, is_elabable
+from .elaboratable import Elaboratable, Elaboratables
 from .context import Context
 from .elabpass import ElabPass
 
 
-def elab_all(top: Elabables, **kwargs) -> List[Elabable]:
-    """ Elaborate everything we can find - potentially recursively - in `Elabables` `top`. 
-
-    Results are returned in a list, not necessarily reproducing the structure of `top`. 
-    Note the *attributes* of `top` are also generally modified in-place, allowing access to their elaboration results. """
-    # Recursively create a list of all elab-able types in `obj`
-    ls = []
-    _list_elabables_helper(top, ls)
-    # Elaborate each, and return them as a list
-    return [elaborate(top=t, **kwargs) for t in ls]
-
-
-def _list_elabables_helper(obj: Any, accum: list) -> None:
-    """ Recursive helper for hierarchically finding elaborate-able things in `obj`. 
-    Newly-found items are appended to accumulation-list `accum`. """
-    if is_elabable(obj):
-        accum.append(obj)
-    elif isinstance(obj, list):
-        [_list_elabables_helper(i, accum) for i in obj]
-    elif isinstance(obj, SimpleNamespace):
-        # Note this skips over non-elaboratable items (e.g. names), where the list demands all be suitable.
-        for i in obj.__dict__.values():
-            if isinstance(i, (SimpleNamespace, list)) or is_elabable(i):
-                _list_elabables_helper(i, accum)
-    else:
-        raise TypeError(f"Attempting Invalid Elaboration of {obj}")
-
-
 def elaborate(
-    top: Elabable,
+    top: Elaboratables,
     *,
     ctx: Optional[Context] = None,
     passes: Optional[List[ElabPass]] = None,
-) -> Module:
-    """ In-Memory Elaboration of Generator or Module `top`. 
-    
-    Optional `passes` lists the ordered `ElabPass`es to run. By default it runs the order specified by `ElabPass.default`. 
-    Note the order of passes is important; many depend upon others to have completed before they can successfully run. 
+) -> Elaboratables:
+    """
+    # Hdl21 Elaboration
 
-    Optional `Context` field `ctx` is not yet supported. 
+    In-memory elaborates of `Module`s, calls to `Generator`s, and lists thereof.
 
-    `elaborate` executes elaboration of a *single* `top` object. 
-    For (plural) combinations of `Elabable` objects, use `elab_all`. 
+    Optional `passes` lists the ordered `ElabPass`es to run. By default it runs the order specified by `ElabPass.default`.
+    Note the order of passes is important; many depend upon others to have completed before they can successfully run.
+
+    Optional `Context` field `ctx` is not yet supported.
     """
+
     # Expand default values
     ctx = ctx or Context()
     passes = passes or ElabPass.default()
 
-    # Pass `top` through each of our passes, in order
-    res = top
-    for pass_ in passes:
-        res = pass_.value.elaborate(top=res, ctx=ctx)
-    return res
+    # Check whether we are elaborating a single object or a list thereof
+    tops = 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))
+
+    # Extract the single-element case
+    if not isinstance(top, List):
+        return rv[0]
+    return rv
diff --git a/hdl21/elab/elaboratable.py b/hdl21/elab/elaboratable.py
index 3270d5d..29e81b9 100644
--- a/hdl21/elab/elaboratable.py
+++ b/hdl21/elab/elaboratable.py
@@ -3,19 +3,18 @@
 """
 
 from typing import Any, List, Union, get_args
-from types import SimpleNamespace
 
 # Local imports
 from ..module import Module
-from ..generator import Generator, GeneratorCall
+from ..generator import GeneratorCall
 
 
 # Type short-hand for elaborate-able types
-Elabable = Union[Module, Generator, GeneratorCall]
+Elaboratable = Union[Module, GeneratorCall]
 # (Plural Version)
-Elabables = Union[Elabable, List[Elabable], SimpleNamespace]
+Elaboratables = Union[Elaboratable, List[Elaboratable]]
 
 
-def is_elabable(obj: Any) -> bool:
+def is_elaboratable(obj: Any) -> bool:
     # Function to test this, since `isinstance` doesn't work for `Union`.
-    return isinstance(obj, get_args(Elabable))
+    return isinstance(obj, get_args(Elaboratable))
diff --git a/hdl21/elab/elaborators/__init__.py b/hdl21/elab/elaborators/__init__.py
index 0004b0a..31b7636 100644
--- a/hdl21/elab/elaborators/__init__.py
+++ b/hdl21/elab/elaborators/__init__.py
@@ -4,8 +4,9 @@
 Recursive walkers of Hdl21 hierarchies which perform the mechanics of elaboration. 
 """
 
-from .slices import SliceResolver
 from .generators import GeneratorElaborator
+from .inst_bundles import InstBundleElaborator
+from .slices import SliceResolver
 from .orphanage import Orphanage
 from .arrays import ArrayFlattener
 from .portrefs import ResolvePortRefs
diff --git a/hdl21/elab/elaborators/arrays.py b/hdl21/elab/elaborators/arrays.py
index 0da4797..86003cb 100644
--- a/hdl21/elab/elaborators/arrays.py
+++ b/hdl21/elab/elaborators/arrays.py
@@ -4,27 +4,28 @@
 
 # Local imports
 from ...module import Module
-from ...instance import Instance, PortRef
+from ...instance import Instance
+from ...portref import PortRef
 from ...bundle import BundleInstance
 from ...signal import Signal, Slice, Concat
 
 # Import the base class
-from .base import Elaborator, ElabStackEnum
+from .base import Elaborator
 
 
 class ArrayFlattener(Elaborator):
-    """ 
-    Elaboration Pass to Flatten `InstArray`s into `Instance`s, broadcast and remake their connections. 
+    """
+    Elaboration Pass to Flatten `InstArray`s into `Instance`s, broadcast and remake their connections.
     """
 
     def elaborate_module(self, module: Module) -> Module:
-        """ Elaborate Module `module`. 
-        Primarily performs flattening of Instance Arrays, and re-connecting to the resultant flattened instances. """
+        """Elaborate Module `module`.
+        Primarily performs flattening of Instance Arrays, and re-connecting to the resultant flattened instances."""
 
         # Flatten Instance arrays
         while module.instarrays:
             name, array = module.instarrays.popitem()
-            self.stack_push(ElabStackEnum.ARRAY, name)
+            self.stack.append(array)
             module.namespace.pop(name)
             # Visit the array's target
             target = self.elaborate_instance_array(array)
@@ -79,6 +80,6 @@ def elaborate_module(self, module: Module) -> Module:
                 else:
                     msg = f"Invalid connection to {conn} in InstArray {array}"
                     self.fail(msg)
-            self.stack_pop()
+            self.stack.pop()
 
         return module
diff --git a/hdl21/elab/elaborators/base.py b/hdl21/elab/elaborators/base.py
index b3e53d8..d26be1d 100644
--- a/hdl21/elab/elaborators/base.py
+++ b/hdl21/elab/elaborators/base.py
@@ -4,100 +4,80 @@
 
 
 # Std-Lib Imports
-from typing import Dict, List, Optional
-from enum import Enum
-
-# PyPi Imports
-from pydantic.dataclasses import dataclass
+import sys
+from pathlib import Path
+from typing import Dict, List, Optional, Union
 
 # Local imports
 from ...module import Module, ExternalModuleCall
-from ...instance import InstArray, Instance
+from ...instance import InstArray, Instance, InstanceBundle
 from ...primitives import PrimitiveCall
 from ...bundle import BundleInstance
 from ...generator import GeneratorCall
 from ...instantiable import Instantiable
 
-from ..elaboratable import Elabable
+from ..elaboratable import Elaboratable
 from ..context import Context
 
 
-class ElabStackEnum(Enum):
-    """ Enumerated Types of Entries in the Elaboration Stack """
-
-    GENERATOR = "Generator"
-    MODULE = "Module"
-    BUNDLE = "Bundle"
-    INSTANCE = "Instance"
-    ARRAY = "Array"
-
-
-@dataclass
-class ElabStackEntry:
-    """ Entry in the Elaboration Stack """
+# Union of entry-types in the elaboration stack
+ElabStackEntry = Union[GeneratorCall, Module, Instance, InstArray, InstanceBundle]
 
-    enum: ElabStackEnum
-    name: str
 
-    def __repr__(self):
-        """ Formatting: `Module ModuleName`, with second column aligned to print in a stack. """
-        return f"{self.enum.value.ljust(14)} {self.name}"
+class Elaborator:
+    """
+    # Base Elaborator Class
 
+    Defines the hierarchy-traversing methods used by each "elaborator pass" sub-class.
 
-class Elaborator:
-    """ 
-    # Base Elaborator Class 
-    
-    Defines the hierarchy-traversing methods used by each "elaborator pass" sub-class. 
-
-    For most passes, elaborating a `Module` at a time is the primary activity.     
-    To offload the need for each sub-class to cache `Module` definitions, 
-    and to traverse their internal contents, the base class defines `elaborate_module_base`, which: 
+    For most passes, elaborating a `Module` at a time is the primary activity.
+    To offload the need for each sub-class to cache `Module` definitions,
+    and to traverse their internal contents, the base class defines `elaborate_module_base`, which:
     * Checks for the module result being pre-cached
-    * Pre-order traverses its instances, arrays, and bundle instances 
-    * Calls the per-pass `elaborate_module` 
-    * Adds the result to the `modules` cache. 
-    This requires that sub-classes also carefully audit when they call their own 
-    `elaborate_module` method. Generally, they should not, and should always call 
-    `elaborate_module_base` instead. 
+    * Pre-order traverses its instances, arrays, and bundle instances
+    * Calls the per-pass `elaborate_module`
+    * Adds the result to the `modules` cache.
+    This requires that sub-classes also carefully audit when they call their own
+    `elaborate_module` method. Generally, they should not, and should always call
+    `elaborate_module_base` instead.
     """
 
     @classmethod
-    def elaborate(cls, top: Elabable, ctx: Context) -> Module:
-        """ Elaboration entry-point. Elaborate the top-level object. """
+    def elaborate(cls, top: Elaboratable, ctx: Context) -> Module:
+        """Elaboration entry-point. Elaborate the top-level object."""
         return cls(top, ctx).elaborate_top()
 
-    def __init__(self, top: Elabable, ctx: Context):
+    def __init__(self, top: Elaboratable, ctx: Context):
         self.top = top
         self.ctx = ctx
         self.stack: List[ElabStackEntry] = list()
         self.modules: Dict[int, Module] = dict()
 
     def elaborate_top(self) -> Module:
-        """ Elaborate our top node """
+        """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_generator_call(self, call: GeneratorCall) -> Module:
-        """ Elaborate a GeneratorCall """
+        """Elaborate a GeneratorCall"""
         # Only the generator-elaborator can handle generator calls.
         # By default it generates errors for all other elaboration passes.
         self.fail(f"Invalid call to elaborate GeneratorCall by {self}")
 
     def elaborate_module_base(self, module: Module) -> Module:
-        """ Base-Case `Module` Elaboration 
-        
-        For most passes, elaborating a `Module` at a time is the primary activity.     
-        To offload the need for each sub-class to cache `Module` definitions, 
-        and to traverse their internal contents, the base class defines `elaborate_module_base`, which: 
+        """Base-Case `Module` Elaboration
+
+        For most passes, elaborating a `Module` at a time is the primary activity.
+        To offload the need for each sub-class to cache `Module` definitions,
+        and to traverse their internal contents, the base class defines `elaborate_module_base`, which:
         * Checks for the module result being pre-cached
-        * Pre-order traverses its instances, arrays, and bundle instances 
-        * Calls the per-pass `elaborate_module` 
-        * Adds the result to the `modules` cache. 
-        This requires that sub-classes also carefully audit when they call their own 
-        `elaborate_module` method. Generally, they should not, and should always call 
-        `elaborate_module_base` instead. 
+        * Pre-order traverses its instances, arrays, and bundle instances
+        * Calls the per-pass `elaborate_module`
+        * Adds the result to the `modules` cache.
+        This requires that sub-classes also carefully audit when they call their own
+        `elaborate_module` method. Generally, they should not, and should always call
+        `elaborate_module_base` instead.
         """
 
         if id(module) in self.modules:  # Check our cache
@@ -107,7 +87,7 @@ def elaborate_module_base(self, module: Module) -> Module:
             msg = f"Anonymous Module {module} cannot be elaborated (did you forget to name it?)"
             self.fail(msg)
 
-        self.stack_push(ElabStackEnum.MODULE, module.name)
+        self.stack.append(module)
 
         # Depth-first traverse instances, ensuring their targets are defined
         for inst in module.instances.values():
@@ -122,48 +102,48 @@ def elaborate_module_base(self, module: Module) -> Module:
 
         # Store a reference to the now-elaborated Module in our cache, and return it
         self.modules[id(module)] = result
-        self.stack_pop()
+        self.stack.pop()
         return result
 
     def elaborate_module(self, module: Module) -> Module:
-        """ Elaborate a Module. Returns the Module unmodified by default. """
+        """Elaborate a Module. Returns the Module unmodified by default."""
         return module
 
     def elaborate_external_module(self, call: ExternalModuleCall) -> ExternalModuleCall:
-        """ Elaborate an ExternalModuleCall. Returns the Call unmodified by default. """
+        """Elaborate an ExternalModuleCall. Returns the Call unmodified by default."""
         return call
 
     def elaborate_primitive_call(self, call: PrimitiveCall) -> PrimitiveCall:
-        """ Elaborate a PrimitiveCall. Returns the Call unmodified by default.  """
+        """Elaborate a PrimitiveCall. Returns the Call unmodified by default."""
         return call
 
     def elaborate_bundle_instance(self, inst: BundleInstance) -> BundleInstance:
-        """ Elaborate an BundleInstance """
+        """Elaborate an BundleInstance"""
         # Annotate each BundleInstance so that its pre-elaboration `PortRef` magic is disabled.
         inst._elaborated = True
         return inst
 
     def elaborate_instance_array(self, array: InstArray) -> Instantiable:
-        """ Elaborate an InstArray """
-        self.stack_push(ElabStackEnum.ARRAY, array.name)
+        """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()
+        self.stack.pop()
         return rv
 
     def elaborate_instance(self, inst: Instance) -> Instantiable:
-        """ Elaborate a Module Instance. """
+        """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.
 
-        self.stack_push(ElabStackEnum.INSTANCE, inst.name)
+        self.stack.append(inst)
         # Turn off `PortRef` magic
         inst._elaborated = True
         # And visit the Instance's target
         rv = self.elaborate_instantiable(inst._resolved)
-        self.stack_pop()
+        self.stack.pop()
         return rv
 
     def elaborate_instantiable(self, of: Instantiable) -> Instantiable:
@@ -182,10 +162,10 @@ def elaborate_instantiable(self, of: Instantiable) -> Instantiable:
     def flatname(
         self, segments: List[str], *, avoid: Optional[Dict] = None, maxlen: int = 511
     ) -> str:
-        """ Create a attribute-name merging string-list `segments`, while avoiding all keys in dictionary `avoid`.
-        Commonly re-used while flattening  nested objects and while creating explicit attributes from implicit ones. 
-        Raises a `RunTimeError` if no such name can be found of length less than `maxlen`. 
-        The default max-length is 511 characters, a value representative of typical limits in target EDA formats. """
+        """Create a attribute-name merging string-list `segments`, while avoiding all keys in dictionary `avoid`.
+        Commonly re-used while flattening  nested objects and while creating explicit attributes from implicit ones.
+        Raises a `RunTimeError` if no such name can be found of length less than `maxlen`.
+        The default max-length is 511 characters, a value representative of typical limits in target EDA formats."""
 
         if avoid is None:
             avoid = {}
@@ -202,18 +182,37 @@ def flatname(
             name += "_"  # Collision; append underscore
         return name
 
-    def stack_push(self, enum: ElabStackEnum, name: str):
-        """ Stack helper, pushing the combination of `enum` and `name` """
-        self.stack.append(ElabStackEntry(enum, name))
+    def fail(self, msg: str):
+        """Error helper, adding stack and state info to an error"""
 
-    def stack_pop(self) -> ElabStackEntry:
-        """ Stack helper, popping the top entry """
-        return self.stack.pop()
+        lines = []
+        for s in self.stack:
+            # Format: `  Module          MyModule      `
+            line = "  " + type(s).__name__.ljust(14) + str(s.name).ljust(28)
 
-    def fail(self, msg: str):
-        """ Error helper, adding stack and state info to an error """
-        # This also serves as a very helpful place for a debugger breakpoint.
-        state = ["\t" + str(s) + " \n" for s in self.stack]
-        state = "Elaboration Error at hierarchical path: \n" + "".join(state)
-        raise RuntimeError(state + msg)
+            source_info = getattr(s, "_source_info", None)
+            if source_info is not None:
+                # The entry has source/line info. Add it to the error line.
+
+                # For source-paths in the run-directory, print the (presumably shorter) relative-path only
+                filepath = source_info.filepath
 
+                # Path.is_relative_to() was added in Python 3.9. And writing a replacement seems harder than you'd think.
+                # Use it for Python versions where it works, and skip it for 3.8.
+                if (
+                    sys.version_info.major > 3 or sys.version_info.minor > 8
+                ) and filepath.is_relative_to(Path.cwd()):
+                    filepath = filepath.relative_to(Path.cwd())
+                else:  # Otherwise print the full absolute path
+                    filepath = filepath.resolve()
+
+                # Format: /path/to/file.py:123
+                # Serves as a clickable link in popular IDEs.
+                line += f"{str(filepath)}:{source_info.linenum}"
+
+            line += "\n"
+            lines.append(line)
+
+        msg = "Elaboration Error at hierarchical path: \n" + "".join(lines) + msg
+        # This also serves as a very helpful place for a debugger breakpoint.
+        raise RuntimeError(msg)
diff --git a/hdl21/elab/elaborators/conntypes.py b/hdl21/elab/elaborators/conntypes.py
index 53b7cad..3857f85 100644
--- a/hdl21/elab/elaborators/conntypes.py
+++ b/hdl21/elab/elaborators/conntypes.py
@@ -19,23 +19,23 @@
 )
 
 # Import the base class
-from .base import Elaborator, ElabStackEnum
+from .base import Elaborator
 
 
 class ConnTypes(Elaborator):
-    """ 
-    Check for connection-type-validity on each Instance connection. 
-    
-    "Connection type validity" includes: 
+    """
+    Check for connection-type-validity on each Instance connection.
+
+    "Connection type validity" includes:
     * All connections must be Signal-like or Bundle-like
     * Signal-valued ports must be connected to Signals of the same width
-    * Bundle-valued ports must be connected to Bundles of compatible type 
+    * Bundle-valued ports must be connected to Bundles of compatible type
 
-    Note this stage *does not* perform port-direction checking or modification. 
+    Note this stage *does not* perform port-direction checking or modification.
     """
 
     def elaborate_module(self, module: Module) -> Module:
-        """ Check each Instance's connections in `module` """
+        """Check each Instance's connections in `module`"""
 
         # Check each Instance
         for inst in module.instances.values():
@@ -51,8 +51,8 @@ def elaborate_module(self, module: Module) -> Module:
         return module
 
     def check_instance(self, module: Module, inst: Union[Instance, InstArray]) -> None:
-        """ Check the connections of `inst` in parent `module` """
-        self.stack_push(ElabStackEnum.INSTANCE, inst.name)
+        """Check the connections of `inst` in parent `module`"""
+        self.stack.append(inst)
 
         # Get copies of both the instance's ports and connections.
         # These will be two {str: Signal-like} dictionaries, who should have the same keys,
@@ -92,14 +92,14 @@ def check_instance(self, module: Module, inst: Union[Instance, InstArray]) -> No
             msg = f"Connection to invalid Port `{portname}` on Instance `{inst.name}` in Module `{module.name}`"
             self.fail(msg)
 
-        self.stack_pop()  # Checks out, we good, pop this Instance from our elab-stack.
+        self.stack.pop()  # Checks out, we good, pop this Instance from our elab-stack.
 
     def assert_bundles_compatible(
         self, bundle: Bundle, other: Union[BundleInstance, AnonymousBundle]
     ):
-        """ Assert that `bundle` is compatible for connection with `other`. 
-        Raises a `RuntimeError` if not compatible. 
-        This includes key-matching and Signal-width matching, but *does not* examine Signal directions. 
+        """Assert that `bundle` is compatible for connection with `other`.
+        Raises a `RuntimeError` if not compatible.
+        This includes key-matching and Signal-width matching, but *does not* examine Signal directions.
         """
 
         if isinstance(other, BundleInstance):
@@ -135,7 +135,7 @@ def assert_bundles_compatible(
         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. """
+        """Assert that `Sliceable`s (generally `Signal`s) a and b are compatible for connection."""
 
         if not isinstance(other, get_args(Sliceable)):
             self.fail(f"Invalid connection to non-Signal {other}")
@@ -149,7 +149,7 @@ def assert_signals_compatible(self, sig: Sliceable, other: Any) -> None:
         return None  # Checks out.
 
     def assert_compatible(self, port: Connectable, conn: Connectable) -> None:
-        """ Assert that `port` and `conn` are compatible for connection. """
+        """Assert that `port` and `conn` are compatible for connection."""
 
         if not is_connectable(conn):
             self.fail(f"Invalid Connection {conn}")
@@ -166,21 +166,22 @@ 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. 
-
-        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. 
+        """
+        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. 
+        if isinstance(bref.parent, BundleInstance):  # Parent is a BundleInstance.
             parent = bref.parent
-        elif isinstance(bref.parent, BundleRef):  # Nested reference. Recursively resolve the 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}")
diff --git a/hdl21/elab/elaborators/flatten_bundles.py b/hdl21/elab/elaborators/flatten_bundles.py
index 5b40536..c1902fb 100644
--- a/hdl21/elab/elaborators/flatten_bundles.py
+++ b/hdl21/elab/elaborators/flatten_bundles.py
@@ -26,7 +26,7 @@
 
 @dataclass
 class Path:
-    """ Hierarchical String-Valued Path """
+    """Hierarchical String-Valued Path"""
 
     segs: List[str]  # List of path segments
 
@@ -45,7 +45,7 @@ def to_name(self) -> str:
 
 @dataclass(frozen=True)
 class PathStr:
-    """ Single-String Representation of `Path` """
+    """Single-String Representation of `Path`"""
 
     val: str
 
@@ -70,7 +70,7 @@ def from_list(cls, ls: List[str]) -> "PathStr":
 
 
 class BundleScope:
-    """ Scope-worth of Signals for a flattened Bundle """
+    """Scope-worth of Signals for a flattened Bundle"""
 
     path: Path
     pathstr: PathStr
@@ -79,7 +79,7 @@ class BundleScope:
 
 @dataclass
 class FlatBundleDef:
-    """ Flattened Bundle Definition """
+    """Flattened Bundle Definition"""
 
     # Source/ Original Bundle
     src: Bundle
@@ -89,7 +89,7 @@ class FlatBundleDef:
 
 @dataclass
 class FlatBundleInst:
-    """ Flattened Bundle Instance """
+    """Flattened Bundle Instance"""
 
     # Source/ Original Bundle
     src: BundleInstance
@@ -98,7 +98,7 @@ class FlatBundleInst:
 
 
 class BundleFlattener(Elaborator):
-    """ Bundle-Flattening Elaborator Pass """
+    """Bundle-Flattening Elaborator Pass"""
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -116,28 +116,34 @@ def __init__(self, *args, **kwargs):
         self.module_attrs: Dict[Tuple[int, str], FlatBundleInst] = dict()
 
     def elaborate_module(self, module: Module) -> Module:
-        """ Flatten Module `module`s Bundles, replacing them with newly-created Signals.
-        Reconnect the flattened Signals to any Instances connected to said Bundles. """
+        """Flatten Module `module`s Bundles, replacing them with newly-created Signals.
+        Reconnect the flattened Signals to any Instances connected to said Bundles."""
 
         # Remove and replace each `BundleInstance` from the Module
         while module.bundles:
             name, bundle_inst = module.bundles.popitem()
             module.namespace.pop(name)
             self.replace_bundle_inst(module, bundle_inst)
-        
+
         # Go through each Instance, replacing `AnonymousBundle`s with their referents
-        # Note `Module`s do not store `AnonymousBundle`s directly, so we don't have a quicker way 
-        # to find them all than traversing the connections to each instance and array. 
+        # Note `Module`s do not store `AnonymousBundle`s directly, so we don't have a quicker way
+        # to find them all than traversing the connections to each instance and array.
         for inst in instances_and_arrays(module):
-            anon_conns = {portname: conn for portname, conn in inst.conns.items() if isinstance(conn, AnonymousBundle)}
+            anon_conns = {
+                portname: conn
+                for portname, conn in inst.conns.items()
+                if isinstance(conn, AnonymousBundle)
+            }
             for portname, anon_bundle in anon_conns.items():
-                self.replace_anon_bundle_conn(inst=inst, portname=portname, anon=anon_bundle)
+                self.replace_anon_bundle_conn(
+                    inst=inst, portname=portname, anon=anon_bundle
+                )
 
         return module
-    
+
     def replace_bundle_inst(self, module: Module, bundle_inst: BundleInstance):
-        """ Replace a `BundleInstance`, flattening its Signals into `module`'s namespace, 
-        and replacing all of its Instance connections with their flattened replacements. """
+        """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
         if id(bundle_inst) in self.bundle_insts:
@@ -179,7 +185,9 @@ def replace_bundle_inst(self, module: Module, bundle_inst: BundleInstance):
 
         # Replace connections to any connected instances
         for portref in list(bundle_inst.connected_ports):
-            self.replace_bundle_conn(inst=portref.inst, portname=portref.portname, flat=flat)
+            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`
@@ -193,8 +201,10 @@ def replace_bundle_inst(self, module: Module, bundle_inst: BundleInstance):
             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. """
+    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:
@@ -210,7 +220,7 @@ def replace_anon_bundle_conn(self, inst: Instance, portname: str, anon: Anonymou
                 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
+            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
@@ -218,13 +228,12 @@ def replace_anon_bundle_conn(self, inst: Instance, portname: str, anon: Anonymou
                 raise TypeError(f"Invalid AnonymousBundle attribute {attr}")
 
             inst.connect(flat_port.name, attr)
-        
-        # And now we can remove the `AnonymousBundle` connection 
-        inst.disconnect(portname)
 
+        # 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` """
+        """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)
         if flat_bundle_port is None:
@@ -241,9 +250,9 @@ def replace_bundle_conn(self, inst: Instance, portname: str, flat: FlatBundleIns
             inst.connect(flat_port.name, flat.signals[pathstr])
 
     def flatten_bundle_def(self, bundle: Bundle) -> FlatBundleDef:
-        """ Flatten a `Bundle` (definition). 
-        Note Signals *are not* copied upon `FlatBundleDef` creation, 
-        but must be while unrolling to each `FlatBundleInst`. """
+        """Flatten a `Bundle` (definition).
+        Note Signals *are not* copied upon `FlatBundleDef` creation,
+        but must be while unrolling to each `FlatBundleInst`."""
 
         if id(bundle) in self.bundle_defs:
             return self.bundle_defs[id(bundle)]  # Already done!
@@ -272,8 +281,8 @@ def flatten_bundle_def(self, bundle: Bundle) -> FlatBundleDef:
         return flat
 
     def flatten_bundle_inst(self, bundle: 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. """
+        """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)
@@ -289,9 +298,9 @@ def flatten_bundle_inst(self, bundle: BundleInstance) -> FlatBundleInst:
         return FlatBundleInst(src=bundle, signals=signals)
 
     def flatten_anonymous_bundle(self, anon: AnonymousBundle) -> FlatBundleInst:
-        """ 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. """
+        """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
 
@@ -316,18 +325,17 @@ def flatten_anonymous_bundle(self, anon: AnonymousBundle) -> FlatBundleInst:
         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. """
+        """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 
+            # 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:
@@ -336,15 +344,16 @@ def resolve_bundleref(self, bref: BundleRef) -> Union[Signal, FlatBundleInst]:
             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 NotImplementedError  # Nested bundle refs, TBC
 
         raise TypeError(f"BundleRef parent for {bref}")
 
+
 def instances_and_arrays(module: Module) -> List[Instance]:
-    """ Get a list of `module`'s instances and instance arrays. """
+    """Get a list of `module`'s instances and instance arrays."""
     return list(module.instances.values()) + list(module.instarrays.values())
diff --git a/hdl21/elab/elaborators/generators.py b/hdl21/elab/elaborators/generators.py
index 5970b7c..e0da569 100644
--- a/hdl21/elab/elaborators/generators.py
+++ b/hdl21/elab/elaborators/generators.py
@@ -10,24 +10,24 @@
 from ...instantiable import Instantiable
 
 # Import the base class
-from .base import Elaborator, ElabStackEnum
+from .base import Elaborator
 
 
 class GeneratorElaborator(Elaborator):
-    """ 
+    """
     # Generator Elaborator
-    
-    Walks a hierarchy from `top` calling Generators. 
-    This pass generally needs to run first among elaboration passes, 
-    or else often times, if `top` is a GeneratorCall, 
-    all other passes really don't have anything to do. 
-    
-    This fact is embedded in a few places throughout `GeneratorElaborator` 
-    and how it inherits behavior from the base `Elaborator`. 
-    Most (probably all) other passes require there be no `GeneratorCall`s 
-    remaining as they operate, and instead act on those Call's `result` properties. 
-    For reuse, this fact is embedded into the base-class in a few places, 
-    and `GeneratorElaborator`'s special-ish case is left to over-ride it. 
+
+    Walks a hierarchy from `top` calling Generators.
+    This pass generally needs to run first among elaboration passes,
+    or else often times, if `top` is a GeneratorCall,
+    all other passes really don't have anything to do.
+
+    This fact is embedded in a few places throughout `GeneratorElaborator`
+    and how it inherits behavior from the base `Elaborator`.
+    Most (probably all) other passes require there be no `GeneratorCall`s
+    remaining as they operate, and instead act on those Call's `result` properties.
+    For reuse, this fact is embedded into the base-class in a few places,
+    and `GeneratorElaborator`'s special-ish case is left to over-ride it.
     """
 
     def __init__(self, *args, **kwargs):
@@ -35,7 +35,7 @@ def __init__(self, *args, **kwargs):
         self.generator_calls = dict()  # GeneratorCalls to their (Module) results
 
     def elaborate_top(self):
-        """ Elaborate our top node """
+        """Elaborate our top node"""
         if isinstance(self.top, Module):
             return self.elaborate_module_base(self.top)
         if isinstance(self.top, GeneratorCall):
@@ -44,8 +44,8 @@ def elaborate_top(self):
         self.fail(msg)
 
     def elaborate_generator_call(self, call: GeneratorCall) -> Module:
-        """ Elaborate Generator-function-call `call`. Returns the generated Module. """
-        self.stack_push(ElabStackEnum.GENERATOR, call.gen.name)
+        """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.
@@ -106,37 +106,37 @@ def elaborate_generator_call(self, call: GeneratorCall) -> Module:
 
         # And elaborate the module
         m = self.elaborate_module_base(m)  # Note the `_base` here!
-        self.stack_pop()
+        self.stack.pop()
         return m
 
     def elaborate_instance(self, inst: Instance) -> Instantiable:
-        """ Elaborate a Module Instance. """
+        """Elaborate a Module Instance."""
         # This version differs from `Elaborator` in operating on the *unresolved* attribute `inst.of`,
         # instead of the resolved version `inst._resolved`.
 
-        self.stack_push(ElabStackEnum.INSTANCE, inst.name)
+        self.stack.append(inst)
         # Turn off `PortRef` magic
         inst._elaborated = True
         # And visit the Instance's target
         rv = self.elaborate_instantiable(inst.of)
-        self.stack_pop()
+        self.stack.pop()
         return rv
 
     def elaborate_instance_array(self, arr: InstArray) -> Instantiable:
-        """ Elaborate an Instance Array. """
+        """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_push(ElabStackEnum.INSTANCE, arr.name)
+        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()
+        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. """
+        """Elaborate an Instance target. Adds the capacity to call `GeneratorCall`s to the more-common base-case."""
         if isinstance(of, GeneratorCall):
             return self.elaborate_generator_call(call=of)
         return super().elaborate_instantiable(of)
diff --git a/hdl21/elab/elaborators/inst_bundles.py b/hdl21/elab/elaborators/inst_bundles.py
new file mode 100644
index 0000000..3010078
--- /dev/null
+++ b/hdl21/elab/elaborators/inst_bundles.py
@@ -0,0 +1,102 @@
+""" 
+# `InstanceBundle`s Elaborator Pass
+
+Transform each `InstanceBundle` into a set of "scalar" `Instance`s, 
+and reconnect them to the signals of the original `Bundle`. 
+
+This pass is designed to execute relatively early, and relies on `BundleRef`s in particular to 
+be resolved *after* it completes. 
+
+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 ...module import Module
+from ...instance import Instance, InstanceBundle
+
+# Import the base class
+from .base import Elaborator
+
+
+class InstBundleElaborator(Elaborator):
+    """
+    # Instance Bundle Elaborator Pass
+    """
+
+    def elaborate_module(self, module: Module) -> Module:
+        """Elaborate a Module
+        Remove each Instance Bundle, and replace it with "scalar" Instances."""
+
+        while module.instbundles:
+            name, inst = module.instbundles.popitem()
+            module.namespace.pop(name)
+            self.elaborate_instance_bundle(module, inst)
+
+        return module
+
+    def elaborate_instance_bundle(
+        self, module: Module, instbundle: InstanceBundle
+    ) -> InstanceBundle:
+        """# 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)
+
+        # Create a replacement instance for each Signal in the Bundle
+        # Keep a mapping from signal-names to those instances for forthcoming connections
+        signal_names_to_instances = {
+            signame: module.add(
+                name=self.flatname(
+                    segments=[instbundle.name, signame], avoid=module.namespace
+                ),
+                val=Instance(of=instbundle.of),
+            )
+            for signame in instbundle.bundle.signals
+        }
+
+        # Disconnect each port from the `InstanceBundle`, and map it into the new scalar `Instance`s
+        for portname in list(instbundle.conns.keys()):
+            conn = instbundle.disconnect(portname)
+
+            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))
+
+            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):
+                # 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)
+
+            else:
+                self.fail(
+                    f"Invalid connection {conn} for `InstanceBundle` {instbundle.name}"
+                )
+
+        return instbundle
diff --git a/hdl21/elab/elaborators/orphanage.py b/hdl21/elab/elaborators/orphanage.py
index d160eea..b376c19 100644
--- a/hdl21/elab/elaborators/orphanage.py
+++ b/hdl21/elab/elaborators/orphanage.py
@@ -3,18 +3,19 @@
 """
 
 # Local imports
-from ...module import Module
+from ...module import Module, ModuleAttr
+from ...instance import _Instance
 
 # Import the base class
 from .base import Elaborator
 
 
 class Orphanage(Elaborator):
-    """ # Orphan-Checking Elaborator Pass 
+    """# Orphan-Checking Elaborator Pass
+
+    Ensures each Module-attribute is "parented" by the `Module` which holds it.
+    Errant cases can come up for code such as:
 
-    Ensures each Module-attribute is "parented" by the `Module` which holds it. 
-    Errant cases can come up for code such as: 
-    
     ```
     m1 = h.Module(name='m1')
     m1.s = h.Signal() # Signal `s` is now "parented" by `m1`
@@ -23,9 +24,9 @@ class Orphanage(Elaborator):
     m2.y = m1.s # Now `s` has been "orphaned" (or perhaps "cradle-robbed") by `m2`
     ```
 
-    This essentially boils down to the difference between Python's native reference-semantics, 
-    and `hdl21.Module`'s notion of "owning" its attributes. 
-    Note other *references* to Module attributes are allowed, such as: 
+    This essentially boils down to the difference between Python's native reference-semantics,
+    and `hdl21.Module`'s notion of "owning" its attributes.
+    Note other *references* to Module attributes are allowed, such as:
 
     ```
     m1 = h.Module(name='m1')
@@ -34,22 +35,95 @@ class Orphanage(Elaborator):
     my_favorite_signals = { "from_m1" : m1.s }
     ```
 
-    Here the dictionary `my_favorite_signals` retains a reference to Signal `s`. 
-    This does not generate an orphan-error complaint, so long as `Module`-parent is unique and unambiguous. 
+    Here the dictionary `my_favorite_signals` retains a reference to Signal `s`.
+    This does not generate an orphan-error complaint, so long as `Module`-parent is unique and unambiguous.
 
-    The orphan-test is very simple: each Module-attribute is annotated with a `_parent_module` member 
-    upon insertion into the Module namespace. 
-    Orphan-testing simply requires that for each attribute, this member is identical to the parent Module. 
-    A `RuntimeError` is raised if orphaned attributes are detected. 
+    The orphan-test is very simple: each Module-attribute is annotated with a `_parent_module` member
+    upon insertion into the Module namespace.
+    Orphan-testing simply requires that for each attribute, this member is identical to the parent Module.
+    A `RuntimeError` is raised if orphaned attributes are detected.
     Otherwise each Module is returned unchanged.
     """
 
     def elaborate_module(self, module: Module) -> Module:
-        """ Elaborate a Module """
+        """Elaborate a Module"""
+
         # Check each attribute in the module namespace for orphanage.
         for attr in module.namespace.values():
-            if attr._parent_module is not module:
-                msg = f"Orphanage: Module {module} attribute {attr} is actually owned by another Module {attr._parent_module}!"
-                self.fail(msg)
+            self.assert_parentage(module, attr)
+
+        # Check instance connections, which are not in the module namespace.
+        instlike = (
+            list(module.instances.values())
+            + list(module.instarrays.values())
+            + list(module.instbundles.values())
+        )
+        for inst in instlike:
+            self.check_instance(module, inst)
+
         # Checks out! Return the module unchanged.
         return module
+
+    def check_instance(self, module: Module, inst: _Instance) -> None:
+        """Check the connections of `inst` in parent `module`"""
+        self.stack.append(inst)
+
+        # Check each of the instance's connections
+        for conn in inst.conns.values():
+            self.check_connectable(module, conn)
+
+        self.stack.pop()
+
+    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
+
+        # Check owned types first: Signals and Bundle Instances
+        if isinstance(conn, (Signal, BundleInstance)):
+            return self.assert_parentage(module, conn)
+
+        if isinstance(conn, Slice):
+            # Recursively check the parent-signals of slices
+            return self.check_connectable(module, conn.signal)
+
+        if isinstance(conn, Concat):
+            # Check each of the concatenated signals, also recursively across inner types
+            for part in conn.parts:
+                self.check_connectable(module, part)
+            return
+
+        if isinstance(conn, NoConn):
+            # `NoConn`s are not "parented" by anything; they are essentially exempt from this check.
+            return
+
+        if isinstance(conn, PortRef):
+            # For Port references, check that `module` owns their target Instance
+            return self.assert_parentage(module, conn.inst)
+
+        if isinstance(conn, BundleRef):
+            # For Bundle references, check that `module` owns their root Bundle Instance
+            return self.assert_parentage(module, conn.root())
+
+        if isinstance(conn, AnonymousBundle):
+            for sig in conn.signals.values():
+                self.check_connectable(module, sig)
+            # FIXME: add sub-bundles when available
+            return
+
+        raise TypeError(f"Orphanage: Unhandled Connectable `{conn}`")
+
+    def assert_parentage(self, module: Module, attr: ModuleAttr) -> None:
+        """Assert that `attr` is parented by `module`, or fail."""
+
+        if attr._parent_module is None:
+            msg = f"Orphanage! Module `{module.name}` depends on orphan attribute `{attr}`! "
+            msg += "Did you forget to `add()` or assign it into `{module.name}`? "
+            self.fail(msg)
+
+        if attr._parent_module is not module:
+            msg = f"Orphanage: Module {module} attribute {attr} is actually owned by another Module {attr._parent_module}!"
+            self.fail(msg)
diff --git a/hdl21/elab/elaborators/portrefs.py b/hdl21/elab/elaborators/portrefs.py
index f0d9a75..d853539 100644
--- a/hdl21/elab/elaborators/portrefs.py
+++ b/hdl21/elab/elaborators/portrefs.py
@@ -10,7 +10,8 @@
 
 # Local imports
 from ...module import Module
-from ...instance import PortRef, Instance, InstArray
+from ...portref import PortRef
+from ...instance import Instance, InstArray
 from ...bundle import BundleInstance, BundleRef, AnonymousBundle
 from ...signal import PortDir, Signal, Visibility, NoConn
 
@@ -29,25 +30,25 @@
 
 
 class ResolvePortRefs(Elaborator):
-    """ Resolve all `PortRef`s on all `Instance`s. 
-    
+    """Resolve all `PortRef`s on all `Instance`s.
+
     This pass resolves "Instance to Instance" connections such as:
     ```
     inst1 = Module1()
     inst2 = Module2(port2=inst1.port1)
     ```
 
-    After this pass, all such connections resolve to concrete 
-    connectable objects, all of which are members of the `Source` type-union 
-    defined above. 
+    After this pass, all such connections resolve to concrete
+    connectable objects, all of which are members of the `Source` type-union
+    defined above.
 
-    While not terribly consistent with its name, this pass also resolves 
-    all `NoConn` connections into concrete `Signal`s, or fails if any `NoConn`s 
-    are invalidly connected between one another. 
+    While not terribly consistent with its name, this pass also resolves
+    all `NoConn` connections into concrete `Signal`s, or fails if any `NoConn`s
+    are invalidly connected between one another.
     """
 
     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)
@@ -59,7 +60,7 @@ def elaborate_module(self, module: Module) -> Module:
     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 a dictionary of all Instance-ports connected to `PortRef`s or `NoConn`s."""
 
         portconns: Dict[PortRef, Union[PortRef, NoConn]] = dict()
 
@@ -78,7 +79,7 @@ def collect_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. """
+        """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()
@@ -104,7 +105,7 @@ def replace_portconns(
         return module
 
     def handle_group(self, module: Module, group: List[Union[PortRef, NoConn]]):
-        """ Handle a `group` worth of connected Ports and NoConns """
+        """Handle a `group` worth of connected Ports and NoConns"""
 
         # First cover `NoConn` connections, checking for any invalid multiple-conns to them.
         if any([isinstance(n, NoConn) for n in group]):
@@ -112,25 +113,22 @@ def handle_group(self, module: Module, group: List[Union[PortRef, NoConn]]):
         return self.handle_portconn(module, group)
 
     def handle_portconn(self, module: Module, group: List[PortRef]):
-        """ Handle re-connecting a list of connected `PortRef`s. 
-        Creates and adds a fresh new `Signal` if one does not already exist. """
+        """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)
 
-        # if sig is None:
-        #     sig = 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. 
+        """
+        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}`")
@@ -147,9 +145,9 @@ def source(self, portref: PortRef) -> Optional[Source]:
         self.fail(f"Connection {portconn}")
 
     def find_source(self, group: List[PortRef]) -> 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. """
+        """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)
@@ -173,7 +171,7 @@ def find_source(self, group: List[PortRef]) -> Optional[Source]:
         self.fail(msg)
 
     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`. """
+        """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_"
@@ -206,7 +204,7 @@ def create_source(self, module: Module, group: List[PortRef]) -> PortType:
         return sig
 
     def copy_port(self, port: PortType) -> PortType:
-        """ Copy a port into an internal Signal or BundleInstance """
+        """Copy a port into an internal Signal or BundleInstance"""
 
         if isinstance(port, Signal):
             # Make a copy, and update its port-level visibility to internal
@@ -222,7 +220,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]]):
-        """ Handle a group with a `NoConn`. """
+        """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:
             msg = f"Invalid multiply-connected `NoConn`, including {group} in {module}"
@@ -233,7 +231,7 @@ def handle_noconn(self, module: Module, group: List[Union[PortRef, NoConn]]):
         return self.replace_noconn(module, portref=group[0], noconn=group[1])
 
     def replace_noconn(self, module: Module, portref: PortRef, noconn: NoConn):
-        """ Replace `noconn` with a newly minted `Signal`. """
+        """Replace `noconn` with a newly minted `Signal`."""
 
         # Get the target Module's port-object corresponding to `portref`
         mod = portref.inst._resolved
@@ -260,10 +258,10 @@ def replace_noconn(self, module: Module, portref: PortRef, noconn: NoConn):
 
 
 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. """
+    """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."""
 
     def __init__(self):
         self.set = set()
diff --git a/hdl21/elab/elaborators/slices.py b/hdl21/elab/elaborators/slices.py
index efad414..ba987a4 100644
--- a/hdl21/elab/elaborators/slices.py
+++ b/hdl21/elab/elaborators/slices.py
@@ -14,11 +14,11 @@
 
 
 class SliceResolver(Elaborator):
-    """ Elaboration pass to resolve slices and concatenations to concrete signals. 
-    Modifies connections to any nested slices, nested concatenations, or combinations thereof. 
+    """Elaboration pass to resolve slices and concatenations to concrete signals.
+    Modifies connections to any nested slices, nested concatenations, or combinations thereof.
     "Full-width" `Slice`s e.g. `sig[:]` are replaced with their parent `Signal`s.
 
-    TODO: `Slice`s with non-unit `step` are converted to `Concat`s. """
+    TODO: `Slice`s with non-unit `step` are converted to `Concat`s."""
 
     def elaborate_module(self, module: Module) -> Module:
         # All arrays must be flattened before getting here, or fail
@@ -40,7 +40,7 @@ def elaborate_module(self, module: Module) -> Module:
 
 
 def _resolve_sliceable(conn: Sliceable) -> Sliceable:
-    """ Resolve a `Sliceable` to flat-concatenation-amenable elements. """
+    """Resolve a `Sliceable` to flat-concatenation-amenable elements."""
     if isinstance(conn, Signal):
         return conn  # Nothing to do
     if isinstance(conn, Slice):
@@ -51,8 +51,8 @@ def _resolve_sliceable(conn: Sliceable) -> Sliceable:
 
 
 def _list_slice(slize: Slice) -> List[Slice]:
-    """ Internal recursive helper for `resolve_slice`. 
-    Returns a list of Slices in which each element has a concrete Signal for its parent. """
+    """Internal recursive helper for `resolve_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:
@@ -97,23 +97,23 @@ def _list_slice(slize: Slice) -> List[Slice]:
 
 
 def _resolve_slice(slize: Slice) -> Sliceable:
-    """ Resolve a `Slice` to one or more with "concrete" `Signal`s as parents. 
-    
-    Slices of other Slices and Slices of Concats are both valid design-time constructions. 
-    For example: 
+    """Resolve a `Slice` to one or more with "concrete" `Signal`s as parents.
+
+    Slices of other Slices and Slices of Concats are both valid design-time constructions.
+    For example:
     ```python
     h.Concat(sig1, sig2, sig3)[1] # Slice of a Concat
     sig4[0:2][1] # Slice of a Slice
     ```
 
-    While these may not frequently be created by designers, they are (at least) often created by array broadcasting. 
-    As some point their parents must be resolved to their original Signals, at minimum before export-level name resolution. 
+    While these may not frequently be created by designers, they are (at least) often created by array broadcasting.
+    As some point their parents must be resolved to their original Signals, at minimum before export-level name resolution.
 
-    Resolving Concatenations can generally resolve to more than one Slice, as in: 
+    Resolving Concatenations can generally resolve to more than one Slice, as in:
     ```python
     h.Concat(sig1[0], sig2[0], sig3[0])[0:1] # Requires slices of `sig1` and `sig2`
-    ``` 
-    Such cases create and return a Concatenation. """
+    ```
+    Such cases create and return a Concatenation."""
 
     # Break out the slice elements in a list
     ls = _list_slice(slize)
@@ -127,8 +127,8 @@ def _resolve_slice(slize: Slice) -> Sliceable:
 
 
 def _resolve_concat(conc: Concat) -> Concat:
-    """ Resolve a Concatenation into (a) concrete Signals and (b) Slices of concrete Signals. 
-    Removes nested concatenations and resolves slices along the way. """
+    """Resolve a Concatenation into (a) concrete Signals and (b) Slices of concrete Signals.
+    Removes nested concatenations and resolves slices along the way."""
 
     if not len(conc.parts):
         raise RuntimeError("Concatenation with no parts")
@@ -163,11 +163,11 @@ def _resolve_concat(conc: Concat) -> Concat:
 
 
 def _flat_concatable(s: Sliceable) -> bool:
-    """ Boolean indication of whether `s` is suitable for flattened Concatenations. 
-    Such objects must be either: 
+    """Boolean indication of whether `s` is suitable for flattened Concatenations.
+    Such objects must be either:
     * (a) A Signal, or
     * (b) A Slice into a Signal
-    Notable exceptions include Concats and nested Slices of Concats and other Slices. """
+    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)
     )
diff --git a/hdl21/elab/elabpass.py b/hdl21/elab/elabpass.py
index 04a3434..41de2d4 100644
--- a/hdl21/elab/elabpass.py
+++ b/hdl21/elab/elabpass.py
@@ -8,6 +8,7 @@
 # Import all the defined passes
 from .elaborators import (
     GeneratorElaborator,
+    InstBundleElaborator,
     Orphanage,
     ConnTypes,
     BundleFlattener,
@@ -18,19 +19,20 @@
 
 
 class ElabPass(Enum):
-    """ 
+    """
     # Enumerated Elaborator Passes
 
-    Each has a `value` attribute which is an `Elaborator` class, 
-    and a `name` attribute which is a (Python-enum-style) capitalized name. 
+    Each has a `value` attribute which is an `Elaborator` class,
+    and a `name` attribute which is a (Python-enum-style) capitalized name.
 
-    Typical usage involves arranging several `ElabPass` in a list 
-    to be performed in-order. 
-    The `default` class-method produces the default such list. 
+    Typical usage involves arranging several `ElabPass` in a list
+    to be performed in-order.
+    The `default` class-method produces the default such list.
     """
 
     RUN_GENERATORS = GeneratorElaborator
     ORPHANAGE = Orphanage
+    INSTANCE_BUNDLES = InstBundleElaborator
     RESOLVE_PORT_REFS = ResolvePortRefs
     CONN_TYPES = ConnTypes
     FLATTEN_BUNDLES = BundleFlattener
@@ -39,6 +41,10 @@ class ElabPass(Enum):
 
     @classmethod
     def default(cls) -> List["ElabPass"]:
-        """ Return the default ordered Elaborator Passes. """
+        """Return the default ordered Elaborator Passes."""
         # Returns each in definition order, then a final few tests.
         return list(ElabPass) + [ElabPass.CONN_TYPES, ElabPass.ORPHANAGE]
+
+    @property
+    def elaborate(self):
+        return self.value.elaborate
diff --git a/hdl21/generator.py b/hdl21/generator.py
index c69f526..8365eb7 100644
--- a/hdl21/generator.py
+++ b/hdl21/generator.py
@@ -17,17 +17,17 @@
 
 @dataclass
 class Default:
-    """ Default value for generator arguments """
+    """Default value for generator arguments"""
 
     ...  # Empty contents
 
 
 @dataclass
 class Generator:
-    """ # Generator Object 
-    Typically created by the `@hdl.generator` decorator. 
-    Stores a function-object and parameters-type, 
-    along with some auxiliary data. 
+    """# Generator Object
+    Typically created by the `@hdl.generator` decorator.
+    Stores a function-object and parameters-type,
+    along with some auxiliary data.
     """
 
     func: Callable  # The generator function
@@ -36,29 +36,32 @@ class Generator:
     pymodule: ModuleType  # Python module where function defined
 
     def __call__(self, arg: Any = Default, **kwargs) -> "GeneratorCall":
-        """ Calls to Generators create GeneratorCall-objects
-        to be expanded during elaboration. """
+        """Calls to Generators create GeneratorCall-objects
+        to be expanded during elaboration."""
         return GeneratorCall(gen=self, arg=arg, kwargs=kwargs)
 
     @property
     def name(self) -> str:
-        """ Generator Name
-        Equal to its callable-function's name. """
+        """Generator Name
+        Equal to its callable-function's name."""
         return self.func.__name__
 
     @property
     def Params(self) -> type:
-        """ Parameter-Type Property """
+        """Parameter-Type Property"""
         return self.paramtype
 
+    def __repr__(self) -> str:
+        return f"Generator(name={self.name})"
+
 
 @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.  """
+    """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
@@ -66,30 +69,33 @@ class GeneratorCall:
     result: Optional[Module] = field(init=False, default=None)
 
     def __eq__(self, other) -> bool:
-        """ Generator-Call equality requires:
-        * *Identity* between generators, and 
-        * *Equality* between parameter-values. """
+        """Generator-Call equality requires:
+        * *Identity* between generators, and
+        * *Equality* between parameter-values."""
         return self.gen is other.gen and self.arg == other.arg
 
     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. """
+        """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)))
 
+    def __repr__(self) -> str:
+        return f"GeneratorCall(gen={self.gen.name})"
+
     @property
     def name(self) -> str:
-        """ GeneratorCall Naming 
-        Once elaborated, returns the name of the generated Module. 
-        If not elaborated, raises a `RuntimeError`. """
+        """GeneratorCall Naming
+        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
 
 
 def generator(f: Callable) -> Generator:
-    """ Decorator for Generator Functions """
+    """Decorator for Generator Functions"""
     from .params import isparamclass
     from .elab import Context
 
diff --git a/hdl21/generators.py b/hdl21/generators.py
index 19350c3..606b5bc 100644
--- a/hdl21/generators.py
+++ b/hdl21/generators.py
@@ -2,7 +2,7 @@
 # Hdl21 Built-In Generators Library 
 """
 
-import copy
+from copy import copy
 from dataclasses import asdict, replace
 from typing import Optional, Tuple, Union
 
@@ -17,7 +17,7 @@
 
 @paramclass
 class MosParams:
-    """ Mos Series-Stack Generator Parameters """
+    """Mos Series-Stack Generator Parameters"""
 
     w = Param(dtype=Optional[int], desc="Width in resolution units", default=None)
     l = Param(dtype=Optional[int], desc="Length in resolution units", default=None)
@@ -27,7 +27,7 @@ class MosParams:
     vth = Param(dtype=MosVth, desc="Threshold voltage specifier", default=MosVth.STD)
 
     def __post_init_post_parse__(self):
-        """ Value Checks """
+        """Value Checks"""
         if self.w <= 0:
             raise ValueError(f"MosParams with invalid width {self.w}")
         if self.l <= 0:
@@ -44,9 +44,9 @@ def __post_init_post_parse__(self):
 
 @generator
 def Mos(params: MosParams) -> Module:
-    """ Mos Series-Stack Generator 
-    Generates a `Module` including `nser` identical series instances of unit-Mos `primitives.Mos`.  
-    Unit-Mos gate and bulk ports are connected in parallel. """
+    """Mos Series-Stack Generator
+    Generates a `Module` including `nser` identical series instances of unit-Mos `primitives.Mos`.
+    Unit-Mos gate and bulk ports are connected in parallel."""
 
     # Extract the number of series fingers
     nser = params.nser
@@ -61,7 +61,7 @@ def Mos(params: MosParams) -> Module:
     m = Module()
     # Copy the unit-cell ports
     for p in primitives.Mos.port_list:
-        m.add(copy.copy(p))
+        m.add(copy(p))
 
     # Add instances, starting at the source-side
     inst = m.add(name="unit0", val=unit_xtor(s=m.s, g=m.g, b=m.b))
@@ -76,19 +76,19 @@ def Mos(params: MosParams) -> Module:
 
 @generator
 def Nmos(params: MosParams) -> Module:
-    """ Nmos Generator. A thin wrapper around `hdl21.generators.Mos` """
+    """Nmos Generator. A thin wrapper around `hdl21.generators.Mos`"""
     return Mos(replace(params, tp=MosType.NMOS))
 
 
 @generator
 def Pmos(params: MosParams) -> Module:
-    """ Pmos Constructor. A thin wrapper around `hdl21.generators.Mos` """
+    """Pmos Constructor. A thin wrapper around `hdl21.generators.Mos`"""
     return Mos(replace(params, tp=MosType.PMOS))
 
 
 @paramclass
 class SeriesParParams:
-    """ Series-Parallel Generator Parameters """
+    """Series-Parallel Generator Parameters"""
 
     # Required
     unit = Param(dtype=Instantiable, desc="Unit cell")
@@ -103,11 +103,14 @@ class SeriesParParams:
 
 @generator
 def SeriesPar(params: SeriesParParams) -> Module:
-    """ Series-Parallel Generator 
-    Arrays `params.npar` copies of `params.nser` series-stacked Instances of unit-cell `params.unit`. 
-    The generated `Module` includes the same ports as `unit`. 
-    The two series-connected ports of `unit` are specified by parameter two-tuple `series_conns`. 
-    All other ports of `unit` are wired in parallel, and exposed as ports of the generated `Module`. """
+    """
+    # Series-Parallel Generator
+
+    Arrays `params.npar` copies of `params.nser` series-stacked Instances of unit-cell `params.unit`.
+    The generated `Module` includes the same ports as `unit`.
+    The two series-connected ports of `unit` are specified by parameter two-tuple `series_conns`.
+    All other ports of `unit` are wired in parallel, and exposed as ports of the generated `Module`.
+    """
 
     unit = params.unit
 
@@ -115,7 +118,7 @@ def SeriesPar(params: SeriesParParams) -> Module:
     m = Module()
     # Copy the unit-cell ports
     for p in unit.ports.values():
-        m.add(copy.copy(p))
+        m.add(copy(p))
 
     # Check for validity of the series-ports
     if isinstance(params.series_conns[0], str):
@@ -133,12 +136,11 @@ def SeriesPar(params: SeriesParParams) -> Module:
     if ser0 is None or ser1 is None:
         raise ValueError(f"SeriesPar: unit does not have ports {params.series_conns}")
 
-    # Extract all the parallel-connected ports
-    par_conns = {
-        port.name: port
-        for port in unit.ports.values()
-        if port.name not in params.series_conns
-    }
+    # Extract all the parallel-connected ports, and
+    par_ports = [
+        port for port in unit.ports.values() if port.name not in params.series_conns
+    ]
+    par_conns = {port.name: m.add(copy(port)) for port in par_ports}
 
     for ipar in range(params.npar):
         # Add instances, starting at the `series_conns[0]`-side
@@ -156,16 +158,16 @@ def SeriesPar(params: SeriesParParams) -> Module:
 
 
 def Wrapper(m: Module) -> Module:
-    """ 
+    """
     # Module Wrapper Creator
 
-    Adds a `Module` hierarchy layer around argument-`Module` `m`. 
-    Creates an `Instance` of `m` and clones its ports, connecting each. 
-    
-    Note: `Wrapper` is generally aways more helpful when the returned `Module` is modified after the fact. 
-    Hence while `Wrapper` is a *function that returns a `Module`*, it is *not* an `hdl21.Generator`, 
-    which cache and unique-name their results. 
-    Callers of `Wrapper` are therefore responsible for considerations such as unique naming. 
+    Adds a `Module` hierarchy layer around argument-`Module` `m`.
+    Creates an `Instance` of `m` and clones its ports, connecting each.
+
+    Note: `Wrapper` is generally aways more helpful when the returned `Module` is modified after the fact.
+    Hence while `Wrapper` is a *function that returns a `Module`*, it is *not* an `hdl21.Generator`,
+    which cache and unique-name their results.
+    Callers of `Wrapper` are therefore responsible for considerations such as unique naming.
     """
 
     # FIXME: find this function a home with a less-confusing name!
@@ -175,7 +177,7 @@ def Wrapper(m: Module) -> Module:
 
     # Copy the unit-cell ports
     for p in m.io.values():
-        wrapper.add(copy.copy(p))
+        wrapper.add(copy(p))
 
     # Create a connections-dict mirroring them
     conns = {port.name: port for port in wrapper.io.values()}
diff --git a/hdl21/instance.py b/hdl21/instance.py
index 4e5341f..21999a8 100644
--- a/hdl21/instance.py
+++ b/hdl21/instance.py
@@ -4,83 +4,53 @@
 Create instances of Modules, Generators, and Primitives in a hierarchy
 """
 
-from typing import Optional, Union, Set, Any, Dict
+# Std-Lib Imports
+from typing import Optional, Any, Dict
+from textwrap import dedent
 
 # Local imports
+from .source_info import source_info, SourceInfo
+from .attrmagic import init
 from .connect import (
-    connectable,
-    track_connected_ports,
     call_and_setattr_connects,
     getattr_port_refs,
 )
 
 
-@track_connected_ports
-@connectable
-class PortRef:
-    """ Reference to a Port 
-    Created from a combination of a parent `inst` and a port-name. """
-
-    _specialcases = [
-        "inst",
-        "portrefs",
-        "connect",
-        "disconnect",
-        "connected_ports",
-        "_port_ref",
-        "_module",
-        "_resolved",
-        "_elaborated",
-        "_initialized",
-    ]
-
-    def __init__(
-        self, inst: Union["Instance", "InstArray"], portname: str,
-    ):
-        self.inst = inst
-        self.portname = portname
-        self.portrefs: Dict[str, "PortRef"] = dict()
-        # Connected port references
-        self.connected_ports: Set["PortRef"] = set()
-        self._elaborated = False
-        self._initialized = True
-
-    def __eq__(self, other) -> bool:
-        """ Port-reference equality requires *identity* between instances 
-        (and of course equality of port-name). """
-        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))
-
-
+@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, InstArray)"""
 
     def __init__(
-        self, of: "Instantiable", *, name: Optional[str] = None,
+        self,
+        of: "Instantiable",
+        *,
+        name: Optional[str] = None,
     ):
         from .instantiable import is_instantiable
 
         if not is_instantiable(of):
             raise RuntimeError(f"Invalid Instance of {of}")
 
-        self.name = name
-        self.of = of
-        self.conns = dict()
-        self.portrefs = dict()
+        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._initialized = True
+        self._source_info: Optional[SourceInfo] = source_info([__file__])
 
     def __repr__(self):
         return f"{self.__class__.__name__}(name={self.name} of={self.of})"
 
     @property
-    def _resolved(self,) -> Optional["Instantiable"]:
-        """ Property to retrieve the Instance's resolved Module, if complete. 
-        Returns `None` if unresolved. """
+    def _resolved(
+        self,
+    ) -> Optional["Instantiable"]:
+        """Property to retrieve the Instance's resolved Module, if complete.
+        Returns `None` if unresolved."""
         from .generator import GeneratorCall
 
         if isinstance(self.of, GeneratorCall):
@@ -88,10 +58,34 @@ def _resolved(self,) -> Optional["Instantiable"]:
         return self.of
 
 
-@call_and_setattr_connects
-@getattr_port_refs
+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":
+    """Instance by integer multiplication.
+    Creates an Instance Array of size `other`."""
+    if not isinstance(other, int):
+        return NotImplemented
+    return _to_array(inst=inst, num=other)
+
+
 class Instance(_Instance):
-    """ Hierarchical Instance of another Module or Generator """
+    """Hierarchical Instance of another Module or Generator"""
+
+    __mul__ = __rmul__ = _mult  # Apply `_mult` on both left and right
 
     _specialcases = [
         "name",
@@ -101,42 +95,56 @@ class Instance(_Instance):
         "portrefs",
         "connect",
         "disconnect",
-        "_port_ref",
-        "_module",
-        "_resolved",
-        "_elaborated",
-        "_initialized",
+        "replace",
     ]
 
-    def _mult(self, other: int) -> "InstArray":
-        """ Instance by integer multiplication. 
-        Creates an Instance Array of size `other`. """
-        if not isinstance(other, int):
-            return NotImplemented
-        return self._to_array(num=other)
+    def __init__(self, *_, **__):
+        super().__init__(*_, **__)
+        self._initialized = True
 
-    __mul__ = __rmul__ = _mult  # Apply `_mult` on both left and right
 
-    def _to_array(self, 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(self.portrefs) > 0:
-            msg = f"Cannot convert Instance {self} with outstanding port-references {self.portrefs} to Array"
-            raise RuntimeError(msg)
-        if self._parent_module is not None:
-            msg = f"Cannot convert Instance {self} already inserted in Module {self._parent_module} to Array"
-            raise RuntimeError(msg)
+class InstanceBundle(_Instance):
+    """Named set of Instances, paired with a Signal Bundle."""
 
-        # Checks out. Create the array.
-        return InstArray(of=self.of, n=num, name=self.name)(**self.conns)
+    # The paired signal-Bundle type. Note this is a class-level attribute.
+    bundle: Optional["Bundle"] = None
+
+    _specialcases = [
+        "name",
+        "of",
+        "conns",
+        "portref",
+        "portrefs",
+        "connect",
+        "disconnect",
+        "replace",
+    ]
+
+    def __init__(self, *_, **__):
+        super().__init__(*_, **__)
+        self._initialized = True
+
+
+def InstanceBundleType(name: str, bundle: "Bundle", doc: Optional[str] = None) -> type:
+    """Create a new sub-class of `InstanceBundle`, tied to Bundle-type `bundle`."""
+
+    from .bundle import Bundle
+
+    if not isinstance(name, str):
+        raise TypeError
+    if not isinstance(bundle, Bundle):
+        raise TypeError
+
+    # Set a default doc-string
+    doc = doc or f"`InstanceBundle` type `{name}` of `Bundle` `{bundle.name}`"
+    doc = dedent(doc)
+
+    # Create and return the new type
+    return type(name, (InstanceBundle,), {"bundle": bundle, "__doc__": doc})
 
 
-@call_and_setattr_connects
-@getattr_port_refs
 class InstArray(_Instance):
-    """ Array of `n` Instances """
+    """Array of `n` Instances"""
 
     _specialcases = [
         "name",
@@ -144,21 +152,20 @@ class InstArray(_Instance):
         "conns",
         "portref",
         "portrefs",
-        "_port_ref",
-        "instrefs",
         "connect",
         "disconnect",
-        "_elaborated",
-        "_initialized",
-        "_module",
+        "replace",
     ]
 
     def __init__(
-        self, of: "Instantiable", n: int, name: Optional[str] = None,
+        self,
+        of: "Instantiable",
+        n: int,
+        name: Optional[str] = None,
     ):
-        self.instrefs = dict()
-        self.n = n
         super().__init__(of=of, name=name)
+        self.n = n
+        self._initialized = True
 
     def __getitem__(self, idx: int):
         return RuntimeError(f"Illegal indexing into Array {self}")
@@ -168,12 +175,12 @@ def __setitem__(self, _idx: Any, _val: Any):
 
 
 def calls_instantiate(cls: type) -> type:
-    """ Decorator which adds 'calls produce `hdl21.Instance`s' functionality. """
+    """Decorator which adds 'calls produce `hdl21.Instance`s' functionality."""
 
     def __call__(self, **kwargs) -> Instance:
-        """ Calls Create `hdl21.Instances`, 
-        and pass any (keyword-only) arguments to said `Instances`, 
-        generally to connect-by-call. """
+        """Calls Create `hdl21.Instances`,
+        and pass any (keyword-only) arguments to said `Instances`,
+        generally to connect-by-call."""
         return Instance(of=self)(**kwargs)
 
     # Check for an existing __call__ method, and if there is one, bail
@@ -183,3 +190,12 @@ def __call__(self, **kwargs) -> Instance:
 
     cls.__call__ = __call__
     return cls
+
+
+__all__ = [
+    "Instance",
+    "InstArray",
+    "InstanceBundle",
+    "InstanceBundleType",
+    "calls_instantiate",
+]
diff --git a/hdl21/instantiable.py b/hdl21/instantiable.py
index cf41cf7..99f3d64 100644
--- a/hdl21/instantiable.py
+++ b/hdl21/instantiable.py
@@ -15,7 +15,7 @@
 
 
 def is_instantiable(val: Any) -> bool:
-    """ Boolean indication of whether `val` is an `Instantiable` type. """
+    """Boolean indication of whether `val` is an `Instantiable` type."""
     return isinstance(val, get_args(Instantiable))
 
 
diff --git a/hdl21/module.py b/hdl21/module.py
index 879d757..6984cf7 100644
--- a/hdl21/module.py
+++ b/hdl21/module.py
@@ -14,24 +14,28 @@
 from dataclasses import field
 
 # Local imports
+from .source_info import source_info, SourceInfo
+from .attrmagic import init
 from .params import NoParams, HasNoParams, isparamclass
 from .signal import Signal, Visibility
-from .instance import calls_instantiate, Instance, InstArray
+from .instance import calls_instantiate, _Instance, Instance, InstArray, InstanceBundle
 from .bundle import BundleInstance
 
+
 # Type-alias for HDL objects storable as `Module` attributes
-ModuleAttr = Union[Signal, Instance, InstArray, BundleInstance]
+ModuleAttr = Union[Signal, _Instance, BundleInstance]
 
 
 @calls_instantiate
+@init
 class Module:
-    """ # Module
-    
+    """# Module
+
     The central element of hardware re-use.
-    Hdl21 `Module`s are static combinations of HDL objects such as `Signal`s, `Instance`s of other `Module`s, and `Bundle`s. 
+    Hdl21 `Module`s are static combinations of HDL objects such as `Signal`s, `Instance`s of other `Module`s, and `Bundle`s.
 
-    Two primary methods (directly) create `Module`s. The `Module` (capital M) class-constructor accepts a single optional `name` field. 
-    This serves as the name of an initially-empty Module-definition. Attributes can then be added programmatically. 
+    Two primary methods (directly) create `Module`s. The `Module` (capital M) class-constructor accepts a single optional `name` field.
+    This serves as the name of an initially-empty Module-definition. Attributes can then be added programmatically.
     ```
     m = Module(name="MyModule")
     m.inp = h.Input(width=5)
@@ -41,14 +45,14 @@ class Module:
     The `hdl21.module` (lower-case m) decorator-function is a convenient way to create a `Module` from a Python class-definition.
     Each attribute in the class-body is converted to a module attribute, and the class name is used as the module's name.
     ```
-    @h.module 
+    @h.module
     class MyModule:
         inp = h.Input(width=5)
         out = h.Output(width=4)
     ```
 
-    Hdl21 Modules *do not* contain behavior in the sense of procedural HDLs. Nor do they contain parameters. 
-    Parametric hardware is produced through Hdl21's `generator` facility, which defines python functions which create and return `Module`s. 
+    Hdl21 Modules *do not* contain behavior in the sense of procedural HDLs. Nor do they contain parameters.
+    Parametric hardware is produced through Hdl21's `generator` facility, which defines python functions which create and return `Module`s.
     """
 
     def __init__(self, *, name: Optional[str] = None):
@@ -57,17 +61,22 @@ def __init__(self, *, name: Optional[str] = None):
         self.signals = dict()
         self.instances = dict()
         self.instarrays = dict()
+        self.instbundles = dict()
         self.bundles = dict()
         self.namespace = dict()  # Combination of all these
 
-        self._pymodule = _caller_pymodule()  #  (Python) module where called
+        self._source_info = source_info([__file__])
+        self._pymodule = (
+            self._source_info.pymodule
+        )  # FIXME: move to operating on `SourceInfo` instead
+
         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`
         self._initialized = True
 
     def __setattr__(self, key: str, val: Any) -> None:
-        """ Set-attribute over-ride, organizing into type-based containers """
+        """Set-attribute over-ride, organizing into type-based containers"""
 
         if key.startswith("_") or not getattr(self, "_initialized", False):
             # Bootstrapping phase. Pass along to "regular" setattr.
@@ -79,6 +88,7 @@ def __setattr__(self, key: str, val: Any) -> None:
             "signals",
             "instances",
             "instarrays",
+            "instbundles",
             "bundles",
             "namespace",
             "add",
@@ -100,7 +110,7 @@ def __setattr__(self, key: str, val: Any) -> None:
         return None
 
     def add(self, val: ModuleAttr, *, name: Optional[str] = None) -> ModuleAttr:
-        """ Add a Module attribute.
+        """Add a Module attribute.
 
         `Module.add` allows for programmatic insertion of attributes whose names are not legal Python identifiers,
         such as keywords ('in', 'from') and those including invalid characters.
@@ -108,13 +118,13 @@ def add(self, val: ModuleAttr, *, name: Optional[str] = None) -> ModuleAttr:
         The added object `val` is also provided as the return value, enabling chaining-style usages such as
         ```python
         instance.inp = MyModule.add(h.Input(name="in", width=5))
-        ``` 
-        and similar. 
+        ```
+        and similar.
 
-        Attribute naming comes from one of either: 
-        * `val`'s `name` attribute, if it has one, or 
+        Attribute naming comes from one of either:
+        * `val`'s `name` attribute, if it has one, or
         * The optional `name` argument.
-        Only one of the two name-sources is allowed per call. 
+        Only one of the two name-sources is allowed per call.
         """
 
         # Check it's a valid attribute-type
@@ -137,8 +147,8 @@ def add(self, val: ModuleAttr, *, name: Optional[str] = None) -> ModuleAttr:
         return self._add(val)
 
     def _add(self, val: ModuleAttr) -> ModuleAttr:
-        """ Internal `add` and `setattr` implementation. Primarily sort `val` into one of our type-based containers. 
-        Layers above `_add` must ensure that `val` has its `name` attribute before calling this method. """
+        """Internal `add` and `setattr` implementation. Primarily sort `val` into one of our type-based containers.
+        Layers above `_add` must ensure that `val` has its `name` attribute before calling this method."""
 
         if isinstance(val, Signal):
             self.namespace[val.name] = val
@@ -152,6 +162,9 @@ def _add(self, val: ModuleAttr) -> ModuleAttr:
         elif isinstance(val, InstArray):
             self.instarrays[val.name] = val
             self.namespace[val.name] = val
+        elif isinstance(val, InstanceBundle):
+            self.instbundles[val.name] = val
+            self.namespace[val.name] = val
         elif isinstance(val, BundleInstance):
             self.bundles[val.name] = val
             self.namespace[val.name] = val
@@ -180,14 +193,14 @@ def _add(self, val: ModuleAttr) -> ModuleAttr:
         return val
 
     def get(self, name: str) -> Optional[ModuleAttr]:
-        """ Get module-attribute `name`. Returns `None` if not present. 
-        Note unlike Python built-ins such as `getattr`, `get` returns solely 
-        from the HDL namespace-worth of `ModuleAttr`s. """
+        """Get module-attribute `name`. Returns `None` if not present.
+        Note unlike Python built-ins such as `getattr`, `get` returns solely
+        from the HDL namespace-worth of `ModuleAttr`s."""
         ns = self.__getattribute__("namespace")
         return ns.get(name, None)
 
     def __getattr__(self, key: str) -> Any:
-        """ Include our namespace-worth of HDL objects in dot-access retrievals """
+        """Include our namespace-worth of HDL objects in dot-access retrievals"""
         if key.startswith("_"):
             return object.__getattribute__(self, key)
         ns = self.__getattribute__("namespace")
@@ -196,7 +209,7 @@ def __getattr__(self, key: str) -> Any:
         return object.__getattribute__(self, key)
 
     def __init_subclass__(cls, *_, **__):
-        """ Sub-Classing Disable-ization """
+        """Sub-Classing Disable-ization"""
         msg = f"Error attempting to create {cls.__name__}. Sub-Typing {cls} is not supported."
         raise RuntimeError(msg)
 
@@ -207,30 +220,30 @@ def __repr__(self) -> str:
 
     @property
     def bundle_ports(self) -> dict:
-        """ Port-Exposed Bundle Instances """
+        """Port-Exposed Bundle Instances"""
         return {name: bundle for name, bundle in self.bundles.items() if bundle.port}
 
     @property
     def io(self) -> dict:
-        """ Combined Dictionary of `Signal`-valued and `Bundle`-valued ports """
+        """Combined Dictionary of `Signal`-valued and `Bundle`-valued ports"""
         rv = self.bundle_ports
         rv.update(self.ports)
         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 """
+        """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__
 
     def _qualname(self) -> Optional[str]:
-        """ Helper for exporting. Returns the path-qualified name including 
-        `_defpath` options above, and the `Module.name`. """
+        """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
@@ -319,9 +332,9 @@ class ExternalModule:
 
     Unlike `Modules`, `ExternalModules` include parameters to support legacy HDLs.
     Said parameters may only take on a limited number of datatypes, and may not be nested.
-    Each `ExternalModule` stores a parameter-type field `paramtype`. 
-    Parameter types may be either `hdl21.paramclass`es or the built-in `dict`. 
-    Parameter-values are checked to be instances of `paramtype` at creation time. 
+    Each `ExternalModule` stores a parameter-type field `paramtype`.
+    Parameter types may be either `hdl21.paramclass`es or the built-in `dict`.
+    Parameter-values are checked to be instances of `paramtype` at creation time.
     """
 
     name: str  # Module name. Used *directly* when exporting.
@@ -335,7 +348,9 @@ class ExternalModule:
     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 `ExternalModule` 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
@@ -359,19 +374,19 @@ 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 """
+        """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__
 
     def _qualname(self) -> Optional[str]:
-        """ Helper for exporting. Returns the path-qualified name including 
-        `_defpath` options above, and the `Module.name`. """
+        """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
@@ -395,9 +410,9 @@ def __getstate__(self):
 @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 Module."""
 
     module: ExternalModule
     params: Any
@@ -405,7 +420,7 @@ class ExternalModuleCall:
     def __post_init_post_parse__(self):
         # Type-validate our parameters
         if not isinstance(self.params, self.module.paramtype):
-            msg = f"Invalid parameters {self.params} for ExternalModule {self.module}. Must be {self.module.paramtype}"
+            msg = f"Invalid parameter type {type(self.params)} for ExternalModule {self.module.name}. Must be {self.module.paramtype}"
             raise TypeError(msg)
 
     @property
@@ -421,18 +436,18 @@ def ports(self) -> dict:
 
 
 def _is_module_attr(val: Any) -> bool:
-    """ Boolean indication of whether `val` is a valid `hdl21.Module` attribute. """
+    """Boolean indication of whether `val` is a valid `hdl21.Module` attribute."""
     return isinstance(val, get_args(ModuleAttr))
 
 
 def _assert_module_attr(m: Module, val: Any) -> None:
-    """ Raise a TypeError if `val` is not a valid Module attribute. """
+    """Raise a TypeError if `val` is not a valid Module attribute."""
     if not _is_module_attr(val):
         _attr_type_error(m, val)
 
 
 def _attr_type_error(m: Module, val: Any) -> None:
-    """ Raise a `TypeError` with debug info for invalid attribute `val`. """
+    """Raise a `TypeError` with debug info for invalid attribute `val`."""
 
     # Give more specific error-messages for our common types.
     # Especially those that are easily confused, e.g. all the `Call` types, `Module`s, and `Instance`s thereof.
@@ -451,17 +466,17 @@ def _attr_type_error(m: Module, val: Any) -> None:
 
 
 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. 
+    """# 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. 
+    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. 
+    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]`.
diff --git a/hdl21/netlist.py b/hdl21/netlist.py
index 34f3814..d3a34f2 100644
--- a/hdl21/netlist.py
+++ b/hdl21/netlist.py
@@ -2,5 +2,53 @@
 # Hdl21 Netlisting 
 """
 
+from typing import IO, Union, Optional
+
 # Import the core netlisting from `vlsirtools`
-from vlsirtools.netlist import netlist, NetlistFormat, NetlistFormatSpec
+import vlsir
+from vlsirtools.netlist import (
+    netlist as vlisr_netlist,
+    NetlistFormat,
+    NetlistFormatSpec,
+    NetlistOptions,
+)
+
+from .elab import Elaboratables
+from .proto.to_proto import to_proto
+
+
+def netlist(
+    src: Union[Elaboratables, vlsir.circuit.Package],
+    dest: IO,
+    *,
+    domain: Optional[str] = None,
+    **kwargs
+) -> None:
+    """
+    # Hdl21 Netlisting
+
+    Netlist one or more Hdl21 `Elaboratable`s - typically `Module`s - or a VLSIR `Package` to destination `dest`.
+    All other options are forwarded to the underlying VLSIR netlister.
+
+    Example usages:
+    ```python
+    h.netlist(MyModule, dest=open('mynetlist.v', 'w'), fmt='verilog')
+    ```
+    ```python
+    s = StringIO()
+    h.netlist(MyGenerator(Params()), dest=s, fmt='spectre')
+    ```
+    ```python
+    import sys
+    h.netlist(h.to_proto([MyModule, MyGenerator(Params())]), dest=sys.stdout, fmt='spice')
+    ```
+    """
+
+    # Convert to the Vlsir `Package` if necessary
+    if not isinstance(src, vlsir.circuit.Package):
+        pkg = to_proto(top=src, domain=domain)
+    else:
+        pkg = src
+
+    # And invoke the VLSIR netlister
+    return vlisr_netlist(pkg=pkg, dest=dest, **kwargs)
diff --git a/hdl21/params.py b/hdl21/params.py
index 3e16f6a..7db9ab4 100644
--- a/hdl21/params.py
+++ b/hdl21/params.py
@@ -17,14 +17,14 @@
 
 
 class _Default:
-    """ The cardinal value for unspecified parameter-declaration defaults.
+    """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. 
+    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`. 
+    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. 
+    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
     """
 
@@ -41,33 +41,33 @@ def __new__(cls, *_, **__):
 
 
 def paramclass(cls: type) -> type:
-    """ Parameter-Class Creation Decorator 
+    """Parameter-Class Creation Decorator
 
-    Transforms a class-definition full of Params into a type-validated dataclass, 
-    with methods for default value and description-dictionary retrieval. 
+    Transforms a class-definition full of Params into a type-validated dataclass,
+    with methods for default value and description-dictionary retrieval.
 
-    Hdl21's `paramclass`es are immutable, strongly-typed data-storage structures. 
-    They are defined through a syntax similar to `@dataclass`, but using the `Param` 
-    constructor, and assignment rather than type annotation. 
+    Hdl21's `paramclass`es are immutable, strongly-typed data-storage structures.
+    They are defined through a syntax similar to `@dataclass`, but using the `Param`
+    constructor, and assignment rather than type annotation.
 
     @paramclass
     class C:
         reqd = Param(dtype=int, desc="A Required Parameter")
         optn = Param(dtype=int, desc="An Optional Parameter", default=11)
 
-    `Param`s each have required datatype (`dtype`) and description (`desc`) fields, 
-    and optional default values. 
-    Each `paramclass` constructor can be called with ordered arguments, 
-    in the order defined in the `paramclass`, or with named arguments. 
-    Named arguments are highly recommended for more than a single parameter. 
-    Note Python's function-argument ordering requirements also dictate 
-    that all `paramclass` required-arguments be declared *before* any optional arguments. 
+    `Param`s each have required datatype (`dtype`) and description (`desc`) fields,
+    and optional default values.
+    Each `paramclass` constructor can be called with ordered arguments,
+    in the order defined in the `paramclass`, or with named arguments.
+    Named arguments are highly recommended for more than a single parameter.
+    Note Python's function-argument ordering requirements also dictate
+    that all `paramclass` required-arguments be declared *before* any optional arguments.
     This also reinforces good practice for communicating which parameters are required.
 
-    Each `paramclass` comes with class-methods `descriptions` and `defaults`, 
-    which return dictionaries of the parameter names to descriptions and 
-    names to default values (for those with defaults), respectively. 
-    
+    Each `paramclass` comes with class-methods `descriptions` and `defaults`,
+    which return dictionaries of the parameter names to descriptions and
+    names to default values (for those with defaults), respectively.
+
     Requirements of the input `cls`:
     * *All* non-Python-internal fields must be of type `Param`
     * Inheritance is not supported
@@ -86,18 +86,18 @@ class C:
         elif isinstance(val, Param):
             params[key] = val
         else:
-            raise RuntimeError(
-                f"Invalid class-attribute {key} in paramclass {cls}. All attributes should be `hdl21.Param`s."
-            )
+            msg = f"Invalid class-attribute {key} in paramclass {cls}. All attributes should be `hdl21.Param`s."
+            raise RuntimeError(msg)
+
     # Translate the Params into dataclass.field-compatible tuples
     fields = list()
     for name, par in params.items():
         field = [name, par.dtype]
         if par.default is not _default:
             field.append(dataclasses.field(default=par.default))
-        # Default factories: not supported, yet. See `Param` below.
-        # elif par.default_factory is not _default:
-        #     field.append(dataclasses.field(default_factory=par.default_factory))
+        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))
     # Add a few helpers to the class namespace
     ns = dict(
@@ -129,24 +129,22 @@ def _brick_subclassing_(cls, *_, **__):
 
 
 def isparamclass(cls: type) -> bool:
-    """ Boolean indication of whether `cls` has been `@paramclass`-decorated """
+    """Boolean indication of whether `cls` has been `@paramclass`-decorated"""
     return getattr(cls, "__paramclass__", False)
 
 
 @pydantic.dataclasses.dataclass
 class Param:
-    """ Parameter Declaration """
+    """Parameter Declaration"""
 
     dtype: Any  # Datatype. Required
     desc: str  # Description. Required
     default: Optional[Any] = _default  # Default Value. Optional
-
-    # Default factories are supported in std-lib dataclasses, but "in beta" in `pydantic.dataclasses`.
-    # default_factory: Optional[Any] = _default  # Default Call-Factory
+    default_factory: Optional[Any] = _default  # Default Call-Factory. Optional
 
 
 def _unique_name(params: Any) -> str:
-    """ Create a unique name for parameter-class instance `params` """
+    """Create a unique name for parameter-class instance `params`"""
     if not isparamclass(params):
         raise RuntimeError(f"Invalid parameter-class instance {params}")
 
@@ -168,7 +166,7 @@ def _unique_name(params: Any) -> str:
         # Format: `pname1=pval1 pname2=pval2 pname3=pval3`
         keys = params.__params__.keys()
         name = " ".join(f"{k}={str(getattr(params, k))}" for k in keys)
-        
+
         # These names must also be limited in length, for sake of our favorite output formats.
         # If the generated name is too long, use the hashing method below instead
         if len(name) < 128:  # Probably(?) a reasonable length limit
@@ -195,15 +193,15 @@ def _unique_name(params: Any) -> str:
 
 
 def hdl21_naming_encoder(obj: Any) -> Any:
-    """ JSON encoder for naming of Hdl21 parameter-values. 
+    """JSON encoder for naming of Hdl21 parameter-values.
 
-    "Extends" `pydantic.json.pydantic_encoder` by first checking for 
-    each of the non-serializable Hdl21 types (`Module`, `Instance`, `Generator`, etc.), 
-    then hands everything else off to `pydantic.json.pydantic_encoder`. 
+    "Extends" `pydantic.json.pydantic_encoder` by first checking for
+    each of the non-serializable Hdl21 types (`Module`, `Instance`, `Generator`, etc.),
+    then hands everything else off to `pydantic.json.pydantic_encoder`.
 
-    Note this *does not fully serialize `Module`s and the like - 
-    see `hdl21.to_proto` for this. This JSON-ization is just good enough 
-    to enable unique naming of Hdl-object-value parameters. """
+    Note this *does not fully serialize `Module`s and the like -
+    see `hdl21.to_proto` for this. This JSON-ization is just good enough
+    to enable unique naming of Hdl-object-value parameters."""
 
     from .module import Module, ExternalModule, ExternalModuleCall
     from .instance import Instance
diff --git a/hdl21/pdk/corner.py b/hdl21/pdk/corner.py
index 002d58e..5e8a9ae 100644
--- a/hdl21/pdk/corner.py
+++ b/hdl21/pdk/corner.py
@@ -7,42 +7,48 @@
 
 
 class Corner(Enum):
-    """ # Enumerated Generic Corners 
-    
-    Values of the `Corner` enum can mean either the variations in a particular device, 
-    e.g. the "slow" versus "fast" variations of a poly resistor, 
-    or can just as oftern refer to a set of such variations within a given technology. 
-    
-    In the latter case `Corner` values are often expanded by PDK-level code to include each 
-    constituent device variation. For example `my.pdk.corner(Corner.FAST)` might expand to 
-    definitions of "fast" Cmos transistors, resistors, and capacitors. """
+    """# Enumerated Generic Corners
+
+    Values of the `Corner` enum can mean either the variations in a particular device,
+    e.g. the "slow" versus "fast" variations of a poly resistor,
+    or can just as oftern refer to a set of such variations within a given technology.
+
+    In the latter case `Corner` values are often expanded by PDK-level code to include each
+    constituent device variation. For example `my.pdk.corner(Corner.FAST)` might expand to
+    definitions of "fast" Cmos transistors, resistors, and capacitors."""
 
     TYP = auto()
     FAST = auto()
     SLOW = auto()
 
+    def __repr__(self) -> str:
+        return self.name
+
 
 class CornerType(Enum):
-    """ Enumerated Types of PDK Content to be Corner-Varied """
+    """Enumerated Types of PDK Content to be Corner-Varied"""
 
     MOS = auto()  # A single transistor type, e.g. NMOS
     CMOS = auto()  # A pair of transistors, e.g. NMOS/PMOS
     RES = auto()  # Resistors
     CAP = auto()  # Capacitors
 
+    def __repr__(self) -> str:
+        return self.name
+
 
 @dataclass
 class CmosCornerPair:
-    """ Cmos Corner Pair """
+    """Cmos Corner Pair"""
 
     nmos: Corner
     pmos: Corner
 
 
 class CmosCorner(Enum):
-    """ # Cmos Corner 
-    All values are `CmosCornerPair` objects. 
-    Naming convention is (Nmos, Pmos), e.g. "FS" is fast-N, slow-P. """
+    """# Cmos Corner
+    All values are `CmosCornerPair` objects.
+    Naming convention is (Nmos, Pmos), e.g. "FS" is fast-N, slow-P."""
 
     TT = CmosCornerPair(Corner.TYP, Corner.TYP)
     FF = CmosCornerPair(Corner.FAST, Corner.FAST)
@@ -50,3 +56,5 @@ class CmosCorner(Enum):
     FS = CmosCornerPair(Corner.FAST, Corner.SLOW)
     SF = CmosCornerPair(Corner.SLOW, Corner.FAST)
 
+    def __repr__(self) -> str:
+        return self.name
diff --git a/hdl21/pdk/installation.py b/hdl21/pdk/installation.py
index ae2a695..1a079da 100644
--- a/hdl21/pdk/installation.py
+++ b/hdl21/pdk/installation.py
@@ -57,59 +57,59 @@
 
 @dataclass
 class PdkInstallation:
-    """ 
-    # PDK Installation 
-
-    A named set of PDK-related data, typically including details of 
-    a particular "site installation" of external files and settings. 
-    
-    While often comprised of file-system `Path`s, the content of a `PdkInstallation` 
-    can in principle be anything related to the installation, 
+    """
+    # PDK Installation
+
+    A named set of PDK-related data, typically including details of
+    a particular "site installation" of external files and settings.
+
+    While often comprised of file-system `Path`s, the content of a `PdkInstallation`
+    can in principle be anything related to the installation,
     e.g. compilation settings for a particular tool, or metadata indicating
-    relevant tool versions. 
+    relevant tool versions.
 
-    Most PDK modules should generally create a `pydantic.dataclass` which is also 
-    a sub-class of `PdkInstallation`, e.g. 
+    Most PDK modules should generally create a `pydantic.dataclass` which is also
+    a sub-class of `PdkInstallation`, e.g.
 
     ```python
-    # mypdk.py 
+    # mypdk.py
 
     from pathlib import Path
-    from pydantic.dataclasses import dataclass 
-    from hdl21.pdk import PdkInstallation 
+    from pydantic.dataclasses import dataclass
+    from hdl21.pdk import PdkInstallation
 
-    @dataclass 
+    @dataclass
     class Install(PdkInstallation):
         model_file: Path
         options: Dict[str, str]
     ```
-    
+
     ```python
     # site.py
     import mypdk
 
     mypdk.install = mypdk.Install(
-        model_file = "/my/pdk/models", 
+        model_file = "/my/pdk/models",
         options = {"opt" : "max"}
     )
     ```
 
-    ```python 
+    ```python
     # Application-Level Code
     # Running an example simulation with `mypdk`
 
     import site   # Creates the site-specific `PdkInstallation`s
-    import mypdk  # Import the target PDK 
+    import mypdk  # Import the target PDK
 
     sim = Sim(tb=tb)
     sim.lib(mypdk.install.models, "ss")
     sim.run()
     ```
 
-    This "installation data" can in principle be store anywhere, or in unstructured types. 
-    The primary goal of the `PdkInstallation` type is to centralize this data, 
-    and to enable shorthand registration and recall, particularly for 
-    the common case of a single in-memory `PdkInstallation`. 
+    This "installation data" can in principle be store anywhere, or in unstructured types.
+    The primary goal of the `PdkInstallation` type is to centralize this data,
+    and to enable shorthand registration and recall, particularly for
+    the common case of a single in-memory `PdkInstallation`.
     """
 
     ...  # No content
diff --git a/hdl21/pdk/pdk.py b/hdl21/pdk/pdk.py
index 49ee82b..bab879e 100644
--- a/hdl21/pdk/pdk.py
+++ b/hdl21/pdk/pdk.py
@@ -8,18 +8,34 @@
 * (b) A transformation method for converting genertic `Primitive` elements to the technology-specific `ExternalModule`s.
 
 The latter manifests as a "compiler pass" over the Hdl21 circuit-proto tree. 
-An `vlsir.circuit.Package` is hierarchically traversed, and instances of `hdl21.Primitive`s 
+An Hdl21 design hierarchically traversed and instances of `hdl21.Primitive`s 
 are transformed, typically to technology-specific `ExternalModule`s. 
 
 The PDK API consists of one required method `compile`: 
 
 ```python
-def compile(src: vlsir.circuit.Package) -> vlsir.circuit.Package
+def compile(src: Elaboratables) -> None
 ```
 
-The core method `compile` transforms a process-generic `vlsir.circuit.Package` into PDK-specific content. 
+The core method `compile` transforms a process-generic design hierarchy into PDK-specific content. 
 This will commonly manifest as replacement of `hdl21.Primitive` instances with PDK-specific `ExternalModule`s. 
-Mutation of the `src` package is allowed, although not recommended (or often terribly helpful).  
+
+The `Elaboratables` union type which serves as the input of `compile` can be any of: 
+
+* An `hdl21.Module`
+* A call to an `hdl21.Generator`
+* Lists thereof
+
+A typical implementation of `compile`, shown in the `hdl21.pdk.sample_pdk` package, is to call the base class `visit_elaboratables` 
+method on a `Walker` subclass which implements the desired PDK transformations. For example: 
+
+```python
+def compile(src: h.Elaboratables) -> None:
+    # Compile `src` to the Sample technology 
+    return SamplePdkWalker().visit_elaboratables(src)
+```
+
+## Plug-in System 
 
 Plug-in-style registration of `hdl21.PDK`s makes them available to `hdl21.Generator`s, 
 and generally allows for a full program-worth of comprehension of the target process.  
@@ -45,7 +61,7 @@ def compile(src: vlsir.circuit.Package) -> vlsir.circuit.Package
 MyMos = h.ExternalModule(name='MyMos', ...) 
 
 # Compilation method 
-def compile(src: vlsir.circuit.Package) -> vlsir.circuit.Package:
+def compile(src: h.Elaboratables) -> h.Elaboratables:
     ...
 
 """
@@ -54,12 +70,11 @@ def compile(src: vlsir.circuit.Package) -> vlsir.circuit.Package:
 from typing import Optional, Union
 from types import ModuleType
 
-# Local Imports
-import vlsir
+from ..elab import Elaboratables
 
 
 class _PdkManager:
-    """ The "private" singleton manager of available PDK modules """
+    """The "private" singleton manager of available PDK modules"""
 
     _the_one = None
 
@@ -84,7 +99,7 @@ def __init__(self):
 
 
 def register(module: ModuleType) -> None:
-    """ Register the (Python) Module `module` as a PDK """
+    """Register the (Python) Module `module` as a PDK"""
     if module in _mgr.modules:
         return
 
@@ -103,14 +118,14 @@ def register(module: ModuleType) -> None:
 
     # Extract the parameters-argument type
     paramtype = args[0].annotation
-    if paramtype is not vlsir.circuit.Package:
-        msg = f"Invalid call signature for {module}.compile. Argument type must be `vlsir.circuit.circuit.Package`, not {paramtype}"
+    if paramtype is not Elaboratables:
+        msg = f"Invalid call signature for {module}.compile. Argument type must be `hdl21.Elaboratables`, not {paramtype}"
         raise RuntimeError(msg)
 
     # Validate the return type is also `Package`
     rt = sig.return_annotation
-    if rt is not vlsir.circuit.Package:
-        msg = f"Invalid call signature for {module}.compile. Return type must be `vlsir.circuit.circuit.Package`, not {paramtype}"
+    if rt not in (None, type(None)):
+        msg = f"Invalid call signature for {module}.compile. Return type must be `None`, not {paramtype}"
         raise RuntimeError(msg)
 
     # Checks out. Add it.
@@ -119,14 +134,14 @@ def register(module: ModuleType) -> None:
 
 
 def compile(
-    src: vlsir.circuit.Package, pdk: Optional[Union[str, ModuleType]] = None
-) -> vlsir.circuit.Package:
-    """ 
-    Compile to a target PDK. 
-
-    Uses the optional `pdk` argument as a target, if provided and valid. 
-    Otherwise uses the default PDK module. 
-    Raises a `RuntimeError` if there is no unambigous default. 
+    src: Elaboratables, pdk: Optional[Union[str, ModuleType]] = None
+) -> Elaboratables:
+    """
+    Compile to a target PDK.
+
+    Uses the optional `pdk` argument as a target, if provided and valid.
+    Otherwise uses the default PDK module.
+    Raises a `RuntimeError` if there is no unambigous default.
     """
     if pdk is None:
         pdk = default()
@@ -151,7 +166,7 @@ def compile(
 
 
 def set_default(to: Union[ModuleType, str]) -> None:
-    """ Set the default PDK to use when no PDK is specified in a `hdl21.Generator` """
+    """Set the default PDK to use when no PDK is specified in a `hdl21.Generator`"""
 
     if isinstance(to, str):
         to = _mgr.names.get(to, None)
@@ -168,7 +183,7 @@ def set_default(to: Union[ModuleType, str]) -> None:
 
 
 def default() -> Optional[ModuleType]:
-    """ Retrieve the default PDK plug-in, if there is an unambiguous one set up. """
+    """Retrieve the default PDK plug-in, if there is an unambiguous one set up."""
 
     if _mgr.default is not None:
         return _mgr.default
diff --git a/hdl21/pdk/sample_pdk/sample_pdk.py b/hdl21/pdk/sample_pdk/sample_pdk.py
index 666348b..912e118 100644
--- a/hdl21/pdk/sample_pdk/sample_pdk.py
+++ b/hdl21/pdk/sample_pdk/sample_pdk.py
@@ -13,7 +13,6 @@
 import hdl21 as h
 from hdl21.primitives import Mos, MosType, MosParams
 from hdl21.pdk import PdkInstallation
-from vlsir import circuit as vckt
 
 
 @dataclass
@@ -28,14 +27,14 @@ class Install(PdkInstallation):
 
 @h.paramclass
 class SamplePdkMosParams:
-    """ Sample PDK MOS Transistor Parameters """
+    """Sample PDK MOS Transistor Parameters"""
 
     w = h.Param(dtype=Optional[int], desc="Width in resolution units", default=None)
     l = h.Param(dtype=Optional[int], desc="Length in resolution units", default=None)
     npar = h.Param(dtype=int, desc="Number of parallel fingers", default=1)
 
     def __post_init_post_parse__(self):
-        """ Value Checks """
+        """Value Checks"""
         if self.w <= 0:
             raise ValueError(f"MosParams with invalid width {self.w}")
         if self.l <= 0:
@@ -63,13 +62,13 @@ def __post_init_post_parse__(self):
 
 
 class SamplePdkWalker(h.HierarchyWalker):
-    """ Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s. """
+    """Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s."""
 
     def __init__(self):
         self.mos_modcalls = dict()
 
     def visit_instance(self, inst: h.Instance):
-        """ Replace instances of `h.Primitive` with our `ExternalModule`s """
+        """Replace instances of `h.Primitive` with our `ExternalModule`s"""
         if isinstance(inst.of, h.PrimitiveCall):
             inst.of = self.replace_primitive(inst.of)
             return
@@ -86,17 +85,17 @@ def replace_primitive(
         return primcall
 
     def mos_module(self, params: MosParams) -> h.ExternalModule:
-        """ Retrieve or create an `ExternalModule` for a MOS of parameters `params`. """
+        """Retrieve or create an `ExternalModule` for a MOS of parameters `params`."""
         if params.tp == MosType.PMOS:
             return Pmos
         return Nmos
 
     def mos_params(self, params: MosParams) -> SamplePdkMosParams:
-        """ Convert generic primitive `MosParams` into PDK-specific `SamplePdkMosParams` """
+        """Convert generic primitive `MosParams` into PDK-specific `SamplePdkMosParams`"""
         return SamplePdkMosParams(w=params.w, l=params.l, npar=params.npar)
 
     def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
-        """ Retrieve or create a `Call` for MOS parameters `params`."""
+        """Retrieve or create a `Call` for MOS parameters `params`."""
         # First check our cache
         if params in self.mos_modcalls:
             return self.mos_modcalls[params]
@@ -114,8 +113,6 @@ def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
         return modcall
 
 
-def compile(src: vckt.Package) -> vckt.Package:
-    """ Compile proto-Package `src` to the Sample technology """
-    ns = h.from_proto(src)
-    SamplePdkWalker().visit_namespace(ns)
-    return h.to_proto(ns)
+def compile(src: h.Elaboratables) -> None:
+    """Compile `src` to the Sample technology"""
+    return SamplePdkWalker().visit_elaboratables(src)
diff --git a/hdl21/portref.py b/hdl21/portref.py
new file mode 100644
index 0000000..778bc5d
--- /dev/null
+++ b/hdl21/portref.py
@@ -0,0 +1,33 @@
+from dataclasses import field
+from typing import Set, Dict
+
+# Local imports
+from .datatype import datatype
+from .connect import connectable, track_connected_ports
+from .instance import _Instance
+
+
+@track_connected_ports
+@connectable
+@datatype
+class PortRef:
+    """# PortRef
+    Reference to a Port
+    Created from a combination of a parent `inst` 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 __eq__(self, other) -> bool:
+        """Port-reference equality requires *identity* between instances
+        (and of course equality of port-name)."""
+        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/prefix.py b/hdl21/prefix.py
index 5f358ca..d75e4b0 100644
--- a/hdl21/prefix.py
+++ b/hdl21/prefix.py
@@ -21,8 +21,8 @@
 
 
 class Prefix(Enum):
-    """ Enumerated Unit Prefixes
-    Values are equal to the associated power-of-ten exponent. """
+    """Enumerated Unit Prefixes
+    Values are equal to the associated power-of-ten exponent."""
 
     YOCTO = -24
     ZEPTO = -21
@@ -47,13 +47,14 @@ class Prefix(Enum):
 
     @classmethod
     def from_exp(cls, exp: int) -> Optional["Prefix"]:
-        """ Get the prefix from the exponent.
-        Returns None if the exponent does not correspond to a valid prefix. """
+        """Get the prefix from the exponent.
+        Returns None if the exponent does not correspond to a valid prefix.
+        FIXME: apply scaling here for non first class exponents."""
         inverted = {v.value: v for v in cls.__members__.values()}
         return inverted.get(exp, None)
 
     def __rmul__(self, other: Any):
-        """ Right-hand-side multiplication operator, e.g. `5 * µ`. """
+        """Right-hand-side multiplication operator, e.g. `5 * µ`."""
 
         if isinstance(other, (Decimal, float, int, str)):
             # The usual use-case, e.g. `5 * µ`
@@ -116,20 +117,20 @@ def __rmul__(self, other: Any):
 
 @dataclass
 class Prefixed:
-    """ 
-    # Prefixed 
-    
-    Combination of a literal value and a unit-indicating prefix. 
-    Colloquially, the numbers in expressions like "5ns", "11MV", and "1µA" 
-    are represented as `Prefixed`. 
+    """
+    # Prefixed
+
+    Combination of a literal value and a unit-indicating prefix.
+    Colloquially, the numbers in expressions like "5ns", "11MV", and "1µA"
+    are represented as `Prefixed`.
     """
 
     number: Decimal  # Numeric Portion. See the long note above.
     prefix: Prefix  # Enumerated SI Prefix
 
     def __float__(self) -> float:
-        """ Convert to float """
-        return float(self.number) * 10 ** self.prefix.value
+        """Convert to float"""
+        return float(self.number) * 10**self.prefix.value
 
     def __mul__(self, other) -> "Prefixed":
         if not isinstance(other, (int, float, Decimal)):
@@ -159,13 +160,18 @@ def __rsub__(self, other: "Prefixed") -> "Prefixed":
         return _subtract(lhs=other, rhs=self)
 
     def scaleto(self, prefix: Prefix) -> "Prefixed":
-        """ Scale to a new `Prefix` """
+        """Scale to a new `Prefix`"""
         newnum = self.number * 10 ** (self.prefix.value - prefix.value)
         return Prefixed(newnum, prefix)
 
+    def __repr__(self) -> str:
+        return f"{self.number}*{self.prefix.name}"
+
+    # FIXME: add comparison operations
+
 
 def _add(lhs: Prefixed, rhs: Prefixed) -> Prefixed:
-    """ `Prefixed` Addition """
+    """`Prefixed` Addition"""
     if not isinstance(lhs, Prefixed) or not isinstance(rhs, Prefixed):
         return NotImplemented
 
@@ -179,7 +185,7 @@ def _add(lhs: Prefixed, rhs: Prefixed) -> Prefixed:
 
 
 def _subtract(lhs: Prefixed, rhs: Prefixed) -> Prefixed:
-    """ `Prefixed` Subtraction """
+    """`Prefixed` Subtraction"""
     if not isinstance(lhs, Prefixed) or not isinstance(rhs, Prefixed):
         return NotImplemented
 
@@ -206,19 +212,19 @@ def _subtract(lhs: Prefixed, rhs: Prefixed) -> Prefixed:
 
 
 def e(exp: int) -> Optional[Prefix]:
-    """ # Exponential `Prefix` Creation 
-    
-    Returns a `Prefix` for power-of-ten exponent `exp`. 
-
-    In many cases HDL parameters must be non-integer values, 
-    e.g. `1nm`, but using `float` can prove undesirable 
-    as rounding errors can turn them into subtly different values. 
-    The `e()` function is most commonly useful with multiplication, 
-    to create "floating point" values such as `11 * e(-9)`. 
+    """# Exponential `Prefix` Creation
+
+    Returns a `Prefix` for power-of-ten exponent `exp`.
+
+    In many cases HDL parameters must be non-integer values,
+    e.g. `1nm`, but using `float` can prove undesirable
+    as rounding errors can turn them into subtly different values.
+    The `e()` function is most commonly useful with multiplication,
+    to create "floating point" values such as `11 * e(-9)`.
     """
     return Prefix.from_exp(exp)
 
 
 # Star-imports *do not* include the single-character names `µ`, `e`, et al.
-# They can be explicityle imported from `hdl21.units` instead.
+# They can be explicityle imported from `hdl21.prefix` instead.
 __all__ = ["Prefix", "Prefixed"]
diff --git a/hdl21/primitives.py b/hdl21/primitives.py
index b0cd31b..7adb106 100644
--- a/hdl21/primitives.py
+++ b/hdl21/primitives.py
@@ -55,7 +55,7 @@
 
 
 class PrimitiveType(Enum):
-    """ Enumerated Primitive-Types """
+    """Enumerated Primitive-Types"""
 
     IDEAL = "IDEAL"
     PHYSICAL = "PHYSICAL"
@@ -63,7 +63,7 @@ class PrimitiveType(Enum):
 
 @dataclass
 class Primitive:
-    """ # Hdl21 Primitive Component
+    """# Hdl21 Primitive Component
 
     Primitives are leaf-level Modules typically defined not by users,
     but by simulation tools or device fabricators.
@@ -77,7 +77,7 @@ class Primitive:
     primtype: PrimitiveType  # Ideal vs Physical Primitive-Type
 
     def __post_init_post_parse__(self):
-        """ After type-checking, do plenty more checks on values """
+        """After type-checking, do plenty more checks on values"""
         if not isparamclass(self.paramtype):
             msg = f"Invalid Primitive param-type {self.paramtype} for {self.name}, must be an `hdl21.paramclass`"
             raise TypeError(msg)
@@ -103,9 +103,9 @@ def ports(self) -> dict:
 @calls_instantiate
 @dataclass
 class PrimitiveCall:
-    """ Primitive Call
+    """Primitive Call
     A combination of a Primitive and its Parameter-values,
-    typically generated by calling the Primitive. """
+    typically generated by calling the Primitive."""
 
     prim: Primitive
     params: Any = NoParams
@@ -127,14 +127,14 @@ def ports(self) -> dict:
 
 
 class MosType(Enum):
-    """ NMOS/PMOS Type Enumeration """
+    """NMOS/PMOS Type Enumeration"""
 
     NMOS = "NMOS"
     PMOS = "PMOS"
 
 
 class MosVth(Enum):
-    """ MOS Threshold Enumeration """
+    """MOS Threshold Enumeration"""
 
     STD = "STD"
     LOW = "LOW"
@@ -143,7 +143,7 @@ class MosVth(Enum):
 
 @paramclass
 class MosParams:
-    """ MOS Transistor Parameters """
+    """MOS Transistor Parameters"""
 
     w = Param(dtype=Optional[int], desc="Width in resolution units", default=None)
     l = Param(dtype=Optional[int], desc="Length in resolution units", default=None)
@@ -155,7 +155,7 @@ class MosParams:
     )  # FIXME: whether to include
 
     def __post_init_post_parse__(self):
-        """ Value Checks """
+        """Value Checks"""
         if self.w <= 0:
             raise ValueError(f"MosParams with invalid width {self.w}")
         if self.l <= 0:
@@ -177,12 +177,12 @@ def __post_init_post_parse__(self):
 
 
 def Nmos(params: MosParams) -> Primitive:
-    """ Nmos Constructor. A thin wrapper around `hdl21.primitives.Mos` """
+    """Nmos Constructor. A thin wrapper around `hdl21.primitives.Mos`"""
     return Mos(replace(params, tp=MosType.NMOS))
 
 
 def Pmos(params: MosParams) -> Primitive:
-    """ Pmos Constructor. A thin wrapper around `hdl21.primitives.Mos` """
+    """Pmos Constructor. A thin wrapper around `hdl21.primitives.Mos`"""
     return Mos(replace(params, tp=MosType.PMOS))
 
 
@@ -336,7 +336,7 @@ class PhysicalShortParams:
 
 @paramclass
 class DcVoltageSourceParams:
-    """ `DcVoltageSource` Parameters """
+    """`DcVoltageSource` Parameters"""
 
     dc = Param(dtype=ScalarOption, default=0, desc="DC Value (V)")
     ac = Param(dtype=ScalarOption, default=None, desc="AC Amplitude (V)")
@@ -356,7 +356,7 @@ class DcVoltageSourceParams:
 
 @paramclass
 class PulseVoltageSourceParams:
-    """ `PulseVoltageSource` Parameters """
+    """`PulseVoltageSource` Parameters"""
 
     delay = Param(dtype=ScalarOption, default=None, desc="Time Delay (s)")
     v1 = Param(dtype=ScalarOption, default=None, desc="One Value (V)")
@@ -402,7 +402,7 @@ class CurrentSourceParams:
 
 
 class BipolarType(Enum):
-    """ Bipolar Junction Transistor NPN/PNP Type Enumeration """
+    """Bipolar Junction Transistor NPN/PNP Type Enumeration"""
 
     NPN = "NPN"
     PNP = "PNP"
@@ -410,7 +410,7 @@ class BipolarType(Enum):
 
 @paramclass
 class BipolarParams:
-    """ Bipolar Transistor Parameters """
+    """Bipolar Transistor Parameters"""
 
     w = Param(dtype=Optional[int], desc="Width in resolution units", default=None)
     l = Param(dtype=Optional[int], desc="Length in resolution units", default=None)
@@ -419,7 +419,7 @@ class BipolarParams:
     )
 
     def __post_init_post_parse__(self):
-        """ Value Checks """
+        """Value Checks"""
         if self.w <= 0:
             raise ValueError(f"BipolarParams with invalid width {self.w}")
         if self.l <= 0:
@@ -439,10 +439,10 @@ def __post_init_post_parse__(self):
 
 
 def Npn(params: BipolarParams) -> Primitive:
-    """ Npn Constructor. A thin wrapper around `hdl21.primitives.Bipolar` """
+    """Npn Constructor. A thin wrapper around `hdl21.primitives.Bipolar`"""
     return Bipolar(replace(params, tp=BipolarType.NPN))
 
 
 def Pnp(params: BipolarParams) -> Primitive:
-    """ Pnp Constructor. A thin wrapper around `hdl21.primitives.Bipolar` """
+    """Pnp Constructor. A thin wrapper around `hdl21.primitives.Bipolar`"""
     return Bipolar(replace(params, tp=BipolarType.PNP))
diff --git a/hdl21/proto/from_proto.py b/hdl21/proto/from_proto.py
index 4805601..90ecd3e 100644
--- a/hdl21/proto/from_proto.py
+++ b/hdl21/proto/from_proto.py
@@ -2,32 +2,33 @@
 hdl21 ProtoBuf Import 
 """
 from types import SimpleNamespace
-from typing import Union, Any, Dict, List, Type
+from typing import Union, Any, Dict, List
 
 # Local imports
 # Proto-definitions
 import vlsir
+import vlsir.circuit_pb2 as vckt
 
 # HDL
+from ..prefix import Prefix, Prefixed
 from ..module import Module, ExternalModule
 from ..instance import Instance
-from ..signal import Signal, Port, PortDir, Slice, Concat
+from ..signal import Signal, PortDir, Slice, Concat, Visibility
 from .. import primitives
 from ..primitives import Primitive
-from ..prefix import Prefix, Prefixed
 
 
-def from_proto(pkg: vlsir.circuit.Package) -> SimpleNamespace:
-    """ Convert Proto-defined Package `pkg` to a namespace-full of Modules. """
+def from_proto(pkg: vckt.Package) -> SimpleNamespace:
+    """Convert Proto-defined Package `pkg` to a namespace-full of Modules."""
     importer = ProtoImporter(pkg)
     return importer.import_()
 
 
 class ProtoImporter:
-    """ Protobuf Package Importer. 
-    Collects all `Modules` defined in Protobuf-sourced primary-argument `pkg` into a Python `types.SimpleNamespace`. """
+    """Protobuf Package Importer.
+    Collects all `Modules` defined in Protobuf-sourced primary-argument `pkg` into a Python `types.SimpleNamespace`."""
 
-    def __init__(self, pkg: vlsir.circuit.Package):
+    def __init__(self, pkg: vckt.Package):
         self.pkg = pkg
         self.modules = dict()  # Dict of names to Modules
         self.ext_modules = dict()  # Dict of qual-names to ExternalModules
@@ -35,7 +36,7 @@ def __init__(self, pkg: vlsir.circuit.Package):
         self.ns.name = pkg.domain
 
     def import_(self) -> SimpleNamespace:
-        """ Import the top-level `Package` to a Python namespace """
+        """Import the top-level `Package` to a Python namespace"""
         # Walk through each proto-defined Module
         # External modules first, as we know they have no dependencies
         for emod in self.pkg.ext_modules:
@@ -46,7 +47,7 @@ def import_(self) -> SimpleNamespace:
         return self.ns
 
     def get_namespace(self, path: List[str]) -> SimpleNamespace:
-        """ Get a (potentially nested) namespace at `path`, creating levels along the way if necessary ."""
+        """Get a (potentially nested) namespace at `path`, creating levels along the way if necessary ."""
         ns = self.ns
         for part in path:
             attr = getattr(ns, part, None)
@@ -61,10 +62,8 @@ def get_namespace(self, path: List[str]) -> SimpleNamespace:
                 raise RuntimeError(f"Invalid namespace path {path} overwriting {attr}")
         return ns
 
-    def import_external_module(
-        self, pmod: vlsir.circuit.ExternalModule
-    ) -> ExternalModule:
-        """ Convert Proto-Module `emod` to an `hdl21.ExternalModule` """
+    def import_external_module(self, pmod: vckt.ExternalModule) -> ExternalModule:
+        """Convert Proto-Module `emod` to an `hdl21.ExternalModule`"""
 
         # Check our cache for whether a module by the same name has been imported
         key = (pmod.name.domain, pmod.name.name)
@@ -73,12 +72,15 @@ def import_external_module(
             msg = f"Cannot import conflicting definitions of {pmod} and {conflict}"
             raise RuntimeError(msg)
 
+        # Import the port list
+        port_list = import_ports_and_signals(pmod)
+
         # Create the ExternalModule
         emod = ExternalModule(
             name=pmod.name.name,
             domain=pmod.name.domain,
             desc=pmod.desc,
-            port_list=self.import_ports(pmod.ports),
+            port_list=port_list,
             paramtype=dict,  # FIXME: should these be stored in the serialization schema?
         )
         # Give it a (non-initializer) value for its `importpath`
@@ -87,8 +89,8 @@ def import_external_module(
         self.ext_modules[key] = emod
         return emod
 
-    def import_module(self, pmod: vlsir.circuit.Module) -> Module:
-        """ Convert Proto-Module `pmod` to an `hdl21.Module` """
+    def import_module(self, pmod: vckt.Module) -> Module:
+        """Convert Proto-Module `pmod` to an `hdl21.Module`"""
         if pmod.name in self.modules:
             raise RuntimeError(f"Proto Import Error: Redefined Module {pmod.name}")
 
@@ -101,12 +103,8 @@ def import_module(self, pmod: vlsir.circuit.Module) -> Module:
         module._importpath = path[:-1]
         module.name = path[-1]
 
-        # Import its ports
-        [module.add(port) for port in self.import_ports(pmod.ports)]
-
-        # Import its signals
-        for psig in pmod.signals:
-            sig = Signal(name=psig.name, width=psig.width)
+        # Import its signals and ports
+        for sig in import_ports_and_signals(pmod):
             module.add(sig)
 
         # Lap through instances, connecting them, creating internal Signals if necessary (bleh)
@@ -115,25 +113,24 @@ def import_module(self, pmod: vlsir.circuit.Module) -> Module:
             module.add(inst)
 
             # Make the instance's connections
-            for pname, pconn in pinst.connections.items():
-                if pname not in inst._resolved.ports:
-                    raise RuntimeError(
-                        f"Invalid Port {pname} on {inst} in Module {module.name}"
-                    )
+            for pconn in pinst.connections:
+                if pconn.portname not in inst._resolved.ports:
+                    msg = f"Invalid Port {pconn.portname} on {inst} in Module {module.name}"
+                    raise RuntimeError(msg)
                 # Import the Signal-object
-                sig = self.import_connection(pconn, module)
+                conn = import_connection_target(pconn.target, module)
                 # And connect it to the Instance
-                setattr(inst, pname, sig)
+                inst.connect(pconn.portname, conn)
 
         # Add the Module to our cache and return-namespace, and return it
         self.modules[pmod.name] = module
         setattr(ns, module.name, module)
         return module
 
-    def import_instance(self, pinst: vlsir.circuit.Instance) -> Instance:
-        """ Convert Proto-Instance `pinst` to an `hdl21.Instance`. 
-        Requires an available Module-definition to be referenced. 
-        Connections are *not* performed inside this method. """
+    def import_instance(self, pinst: vckt.Instance) -> Instance:
+        """Convert Proto-Instance `pinst` to an `hdl21.Instance`.
+        Requires an available Module-definition to be referenced.
+        Connections are *not* performed inside this method."""
 
         # Also a small piece of proof that Google hates Python.
         ref = pinst.module
@@ -149,17 +146,20 @@ def import_instance(self, pinst: vlsir.circuit.Instance) -> Instance:
 
         elif ref.WhichOneof("to") == "external":  # Defined outside package
             # Import all of its instance parameters to a dict
-            params = self.import_parameters(pinst.parameters)
+            params = import_parameters(pinst.parameters)
 
             # First check the priviledged/ internally-defined domains
             if ref.external.domain == "vlsir.primitives":
                 # Import a VLSIR primitive to an ideal element, and convert its parameters
-                target = self.import_vlsir_primitive(ref.external)
+                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 = self.import_hdl21_primitive(ref.external)
+                target = import_hdl21_primitive(ref.external)
                 params = target.Params(**params)
 
             else:  # Externally-defined `ExternalModule`
@@ -173,134 +173,151 @@ def import_instance(self, pinst: vlsir.circuit.Instance) -> Instance:
             # Call the `target` ExternalModule/ Primitive it with the parameters, making an instantiable `Call`-object.
             target = target(params)
 
+        else:
+            raise ValueError
+
         return Instance(name=pinst.name, of=target)
 
-    @classmethod
-    def import_hdl21_primitive(cls, pref: vlsir.utils.QualifiedName) -> Primitive:
-        """ Convert an `hdl21.primitives` or `hdl21.ideal` qualified name to a Primitive. """
-        if pref.domain not in ["hdl21.primitives", "hdl21.ideal"]:
-            raise ValueError(f"Invalid Primitive Domain: {pref.domain}")
 
-        # Get the Primitive from `hdl21.primitives`, or fail
-        prim = getattr(primitives, pref.name, None)
-        if not isinstance(prim, Primitive):
-            msg = f"Attempt to import invalid `hdl21.primitive` {pref.external.name}"
-            raise RuntimeError(msg)
-        return prim
-
-    @classmethod
-    def import_vlsir_primitive(cls, pref: vlsir.utils.QualifiedName) -> Primitive:
-        """ Import a VLSIR-defined Primitive """
-        if pref.domain != "vlsir.primitives":
-            raise ValueError(f"Invalid Primitive Domain: {pref.domain}")
-
-        # Mapping from `vlsir.primitives` to Hdl21's ideal elements
-        # FIXME: specialized importing of their parameters!
-        prim_map = {
-            "vdc": "DcVoltageSource",
-            "vpluse": "PulseVoltageSource",
-            "isource": "CurrentSource",
-            "resistor": "IdealResistor",
-            "capacitor": "IdealCapacitor",
-            "inductor": "IdealInductor",
-        }
-        if pref.name not in prim_map:
-            msg = f"Invalid or unsupported VLSIR Primitive: {pref.name}"
-            raise RuntimeError(msg)
+def import_ports_and_signals(
+    pmod: Union[vckt.Module, vckt.ExternalModule]
+) -> List[Signal]:
+    """Import all Ports and Signals from Proto-Module `pmod`.
+    Returns them as a single list, which can serve as arguments to `Module.add` or `ExternalModule.port_list`."""
 
-        # Get the Hdl21 Primitive class
-        prim = getattr(primitives, prim_map[pref.name], None)
-        if not isinstance(prim, Primitive):
-            msg = f"Attempt to import invalid `hdl21.primitive` {pref.external.name}"
+    # Keep a dictionary from name: Signal imported
+    signals: Dict[str, Signal] = {}
+    for psig in pmod.signals:
+        signals[psig.name] = Signal(name=psig.name, width=psig.width)
+
+    # Convert the entries of `signals` that are ports
+    for pport in pmod.ports:
+        dir_ = import_port_dir(pport)
+        if pport.signal not in signals:
+            msg = f"Port {pport} missing Signal in {signals}"
             raise RuntimeError(msg)
-        return prim
-
-    @classmethod
-    def import_parameters(cls, pparams: Dict[str, vlsir.Param]) -> dict:
-        pdict = {}
-        for pname, pparam in pparams.items():
-            pdict[pname] = cls.import_parameter(pparam)
-        return pdict
-
-    @classmethod
-    def import_parameter(cls, pparam: vlsir.Param) -> Any:
-        """ Import a `Param` """
-        ptype = pparam.WhichOneof("value")
-        if ptype == "integer":
-            return int(pparam.integer)
-        if ptype == "double":
-            return float(pparam.double)
-        if ptype == "string":
-            return str(pparam.string)
-        if ptype == "prefixed":
-            return import_prefixed(pparam.prefixed)
-        if ptype == "literal":
-            raise NotImplementedError
-        raise ValueError(f"Invalid Parameter Type: `{ptype}`")
+        signals[pport.signal].direction = dir_
+        signals[pport.signal].vis = Visibility.PORT
+
+    return list(signals.values())
+
+
+def import_hdl21_primitive(pref: vlsir.utils.QualifiedName) -> Primitive:
+    """Convert an `hdl21.primitives` or `hdl21.ideal` qualified name to a Primitive."""
+    if pref.domain not in ["hdl21.primitives", "hdl21.ideal"]:
+        raise ValueError(f"Invalid Primitive Domain: {pref.domain}")
+
+    # Get the Primitive from `hdl21.primitives`, or fail
+    prim = getattr(primitives, pref.name, None)
+    if not isinstance(prim, Primitive):
+        msg = f"Attempt to import invalid `hdl21.primitive` {pref.external.name}"
+        raise RuntimeError(msg)
+    return prim
+
+
+def import_vlsir_primitive(pref: vlsir.utils.QualifiedName) -> Primitive:
+    """Import a VLSIR-defined Primitive"""
+    if pref.domain != "vlsir.primitives":
+        raise ValueError(f"Invalid Primitive Domain: {pref.domain}")
+
+    # Mapping from `vlsir.primitives` to Hdl21's ideal elements
+    # FIXME: specialized importing of their parameters!
+    prim_map = {
+        "vdc": "DcVoltageSource",
+        "vpluse": "PulseVoltageSource",
+        "isource": "CurrentSource",
+        "resistor": "IdealResistor",
+        "capacitor": "IdealCapacitor",
+        "inductor": "IdealInductor",
+    }
+    if pref.name not in prim_map:
+        msg = f"Invalid or unsupported VLSIR Primitive: {pref.name}"
+        raise RuntimeError(msg)
+
+    # Get the Hdl21 Primitive class
+    prim = getattr(primitives, prim_map[pref.name], None)
+    if not isinstance(prim, Primitive):
+        msg = f"Attempt to import invalid `hdl21.primitive` {pref.external.name}"
+        raise RuntimeError(msg)
+    return prim
 
-    def import_connection(
-        self, pconn: vlsir.circuit.Connection, module: Module
-    ) -> Union[Signal, Slice, Concat]:
-        """ Import a Proto-defined `Connection` into a Signal, Slice, or Concatenation """
-        # Connections are a proto `oneof` union; figure out which to import
-        stype = pconn.WhichOneof("stype")
-        # Concatenations are more complicated and need their own method
-        if stype == "concat":
-            return self.import_concat(pconn.concat, module)
-        # For signals & slices, first sort out the signal-name, so we can grab the object from `module.namespace`
-        if stype == "sig":
-            sname = pconn.sig.name
-        elif stype == "slice":
-            sname = pconn.slice.signal
-        else:
-            raise ValueError(f"Invalid Connection Type: {pconn}")
-        # Grab this Signal, if it exists
-        sig = module.namespace.get(sname, None)
-        if sig is None:
-            # This block has held, at some points in code-history,
-            # the SPICE-style "create nets from thin air" behavior.
-            # That's outta here; undeclared signals produce errors instead.
-            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)
-        return sig
-
-    def import_concat(self, pconc: vlsir.circuit.Concat, module: Module) -> Concat:
-        """ Import a (potentially nested) Concatenation """
-        parts = []
-        for ppart in pconc.parts:
-            part = self.import_connection(ppart, module)
-            parts.append(part)
-        return Concat(*parts)
-
-    def import_ports(self, pports: List[vlsir.circuit.Port]) -> List[Signal]:
-        """ Import a list of proto-ports """
-        ports = []
-        for pport in pports:
-            dir_ = self.import_port_dir(pport)
-            ports.append(
-                Port(name=pport.signal.name, width=pport.signal.width, direction=dir_,)
-            )
-        return ports
-
-    def import_port_dir(self, pport: vlsir.circuit.Port) -> PortDir:
-        """ Convert between Port-Direction Enumerations """
-        if pport.direction == vlsir.circuit.Port.Direction.INPUT:
-            return PortDir.INPUT
-        if pport.direction == vlsir.circuit.Port.Direction.OUTPUT:
-            return PortDir.OUTPUT
-        if pport.direction == vlsir.circuit.Port.Direction.INOUT:
-            return PortDir.INOUT
-        if pport.direction == vlsir.circuit.Port.Direction.NONE:
-            return PortDir.NONE
-        raise ValueError
+
+def import_parameters(pparams: List[vlsir.Param]) -> Dict[str, Any]:
+    """Import a list of Vlsir parameters to a Hdl21-style {name: value} dict."""
+    return {pparam.name: import_parameter_value(pparam.value) for pparam in pparams}
+
+
+def import_parameter_value(pparam: vlsir.ParamValue) -> Any:
+    """Import a `ParamValue`"""
+    ptype = pparam.WhichOneof("value")
+    if ptype == "integer":
+        return int(pparam.integer)
+    if ptype == "double":
+        return float(pparam.double)
+    if ptype == "string":
+        return str(pparam.string)
+    if ptype == "prefixed":
+        return import_prefixed(pparam.prefixed)
+    if ptype == "literal":
+        raise NotImplementedError
+    raise ValueError(f"Invalid Parameter Type: `{ptype}`")
+
+
+def import_connection_target(
+    pconn: vckt.ConnectionTarget, module: Module
+) -> Union[Signal, Slice, Concat]:
+    """Import a Proto-defined `ConnectionTarget` into a Signal, Slice, or Concatenation"""
+    # Connections are a proto `oneof` union; figure out which to import
+    stype = pconn.WhichOneof("stype")
+    # Concatenations are more complicated and need their own method
+    if stype == "concat":
+        return import_concat(pconn.concat, module)
+    # For signals & slices, first sort out the signal-name, so we can grab the object from `module.namespace`
+    if stype == "sig":
+        sname = pconn.sig
+    elif stype == "slice":
+        sname = pconn.slice.signal
+    else:
+        raise ValueError(f"Invalid Connection Type: {pconn}")
+    # Grab this Signal, if it exists
+    sig = module.namespace.get(sname, None)
+    if sig is None:
+        # This block has held, at some points in code-history,
+        # the SPICE-style "create nets from thin air" behavior.
+        # That's outta here; undeclared signals produce errors instead.
+        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)
+    return sig
+
+
+def import_concat(pconc: vckt.Concat, module: Module) -> Concat:
+    """Import a (potentially nested) Concatenation"""
+    parts = []
+    for ppart in pconc.parts:
+        part = import_connection_target(ppart, module)
+        parts.append(part)
+    return Concat(*parts)
+
+
+def import_port_dir(pport: vckt.Port) -> PortDir:
+    """Convert between Port-Direction Enumerations"""
+    if pport.direction == vckt.Port.Direction.INPUT:
+        return PortDir.INPUT
+    if pport.direction == vckt.Port.Direction.OUTPUT:
+        return PortDir.OUTPUT
+    if pport.direction == vckt.Port.Direction.INOUT:
+        return PortDir.INOUT
+    if pport.direction == vckt.Port.Direction.NONE:
+        return PortDir.NONE
+    raise ValueError
 
 
 def import_prefix(vpre: vlsir.SIPrefix) -> Prefix:
-    """ Import an enumerated `Prefix` """
+    """Import an enumerated `Prefix`"""
     map = {
         vlsir.SIPrefix.YOCTO: Prefix.YOCTO,
         vlsir.SIPrefix.ZEPTO: Prefix.ZEPTO,
@@ -329,7 +346,7 @@ def import_prefix(vpre: vlsir.SIPrefix) -> Prefix:
 
 
 def import_prefixed(vpref: vlsir.Prefixed) -> Prefixed:
-    """ Import a `Prefixed` number """
+    """Import a `Prefixed` number"""
 
     # Import the metric prefix
     prefix = import_prefix(vpref.prefix)
@@ -337,11 +354,12 @@ def import_prefixed(vpref: vlsir.Prefixed) -> Prefixed:
     # And import the numeric part, dispatched across its type.
     ptype = vpref.WhichOneof("number")
     if ptype == "integer":
-        return int(vpref.integer)
-    if ptype == "double":
-        return float(vpref.double)
-    if ptype == "string":
-        return str(vpref.string)
-
-    raise ValueError(f"Invalid Parameter Type: `{ptype}`")
+        number = vpref.integer
+    elif ptype == "double":
+        number = vpref.double
+    elif ptype == "string":
+        number = vpref.string
+    else:
+        raise ValueError(f"Invalid Parameter Type: `{ptype}`")
 
+    return Prefixed(prefix=prefix, number=number)
diff --git a/hdl21/proto/to_proto.py b/hdl21/proto/to_proto.py
index 14ca9cf..0fc45bd 100644
--- a/hdl21/proto/to_proto.py
+++ b/hdl21/proto/to_proto.py
@@ -1,7 +1,7 @@
 """
 # VLSIR ProtoBuf Schema Export
 
-Primary entrypoint `to_proto` turns a set of elaborate-able `Elabables`, 
+Primary entrypoint `to_proto` turns a set of elaborate-able `Elaboratables`, 
 and turns them into a `vlsir.circuit.Package`. 
 
 Most export-machinery is accomplished by primary implementation-class `ProtoExporter`, 
@@ -25,27 +25,39 @@
 # HDL
 from ..params import isparamclass
 from ..prefix import Prefix, Prefixed
-from ..elab import Elabables, elab_all
+from ..elab import Elaboratables, elaborate
 from ..module import Module, ExternalModule, ExternalModuleCall
-from ..primitives import PrimitiveCall, PrimitiveType, ScalarOption
 from ..instance import Instance
 from .. import signal
-from hdl21 import primitives
-
-
-def to_proto(top: Elabables, domain: Optional[str] = None, **kwargs,) -> vckt.Package:
-    """Convert Elaborate-able Module or Generator `top` and its dependencies to a Proto-format `Package`"""
+from ..primitives import (
+    PrimitiveCall,
+    PrimitiveType,
+    ScalarOption,
+    PulseVoltageSourceParams,
+)
+
+
+def to_proto(
+    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
-    tops = elab_all(top)
+    tops = elaborate(top)
+    if not isinstance(tops, list):
+        tops = [tops]
     exporter = ProtoExporter(tops=tops, domain=domain)
     return exporter.export()
 
 
 class ProtoExporter:
-    """Hierarchical Protobuf Exporter.
+    """
+    Hierarchical Protobuf Exporter
     Walks a Module hierarchy from Module `self.top`, defining each required Module along the way.
     Modules are defined in `self.pkg` in dependency order.
-    Upon round-tripping, all dependent child-modules will be encountered before their parent instantiators."""
+    Upon round-tripping, all dependent child-modules will be encountered before their parent instantiators.
+    """
 
     def __init__(self, tops: List[Module], domain: Optional[str] = None):
         self.tops = tops
@@ -84,6 +96,8 @@ def export_module_name(self, module: Module) -> vlsir.utils.QualifiedName:
         return mname
 
     def export_module(self, module: Module) -> vckt.Module:
+        """Export a Module `module` and all its dependencies."""
+
         if id(module) in self.modules:  # Already done
             return self.modules[id(module)]
 
@@ -97,15 +111,15 @@ def export_module(self, module: Module) -> vckt.Module:
         # Create its serialized name
         pmod.name = self.export_module_name(module)
 
+        # Create its Signal-objects, which include the hdl21.Module's Ports
+        for sig in list(module.signals.values()) + list(module.ports.values()):
+            psig = vckt.Signal(name=sig.name, width=sig.width)
+            pmod.signals.append(psig)
+
         # Create its Port-objects
         for port in module.ports.values():
             pmod.ports.append(export_port(port))
 
-        # Create its Signal-objects
-        for sig in module.signals.values():
-            psig = vckt.Signal(name=sig.name, width=sig.width)
-            pmod.signals.append(psig)
-
         # Create each Proto-Instance
         for inst in module.instances.values():
             if not inst._resolved:
@@ -121,7 +135,7 @@ def export_module(self, module: Module) -> vckt.Module:
         return pmod
 
     def export_external_module(self, emod: ExternalModule) -> vckt.ExternalModule:
-        """ Export an `ExternalModule` """
+        """Export an `ExternalModule`"""
         if id(emod) in self.ext_modules:  # Already done
             return self.ext_modules[id(emod)]
 
@@ -129,8 +143,10 @@ def export_external_module(self, emod: ExternalModule) -> vckt.ExternalModule:
         qname = vlsir.utils.QualifiedName(name=emod.name, domain=emod.domain)
         pmod = vckt.ExternalModule(name=qname)
 
-        # Create its Port-objects
-        for port in emod.ports.values():
+        # Create its Port-objects, which also require Vlsir Signal objects
+        for port in emod.port_list:
+            psig = vckt.Signal(name=port.name, width=port.width)
+            pmod.signals.append(psig)
             pmod.ports.append(export_port(port))
 
         # Store references to the result, and return it
@@ -139,9 +155,9 @@ def export_external_module(self, emod: ExternalModule) -> vckt.ExternalModule:
         return pmod
 
     def export_instance(self, inst: Instance) -> vckt.Instance:
-        """ Convert an hdl21.Instance into a Proto-Instance
+        """Convert an hdl21.Instance into a Proto-Instance
         Depth-first retrieves a Module definition first,
-        using its generated `name` field as the Instance's `module` pointer. """
+        using its generated `name` field as the Instance's `module` pointer."""
 
         # Create the Proto-Instance
         pinst = vckt.Instance(name=inst.name)
@@ -196,31 +212,34 @@ def export_instance(self, inst: Instance) -> vckt.Instance:
                 if val is None:
                     continue  # None-valued parameters go un-set
                 # Otherwise export and copy it into place
-                pinst.parameters[key].CopyFrom(export_param_value(val))
+                vparam = vlsir.Param(name=key, value=export_param_value(val))
+                pinst.parameters.append(vparam)
 
         else:
             raise TypeError(f"Un-exportable Instance {inst} of {inst._resolved}")
 
         # Create its connections mapping
-        for pname, sig in inst.conns.items():
+        for pname, conn in inst.conns.items():
             # Assign each item into the connections dict.
             # The proto interface requires copying it along the way
-            pinst.connections[pname].CopyFrom(export_conn(sig))
+            pconn = vckt.Connection(
+                portname=pname, target=export_connection_target(conn)
+            )
+            pinst.connections.append(pconn)
 
         return pinst
 
 
 def export_port(port: signal.Port) -> vckt.Port:
-    """ Export a `Port` """
+    """Export a `Port`"""
     pport = vckt.Port()
     pport.direction = export_port_dir(port)
-    pport.signal.name = port.name
-    pport.signal.width = port.width
+    pport.signal = port.name
     return pport
 
 
 def export_port_dir(port: signal.Port) -> vckt.Port.Direction:
-    """ Convert between Port-Direction Enumerations """
+    """Convert between Port-Direction Enumerations"""
     if port.direction == signal.PortDir.INPUT:
         return vckt.Port.Direction.INPUT
     if port.direction == signal.PortDir.OUTPUT:
@@ -229,18 +248,17 @@ def export_port_dir(port: signal.Port) -> vckt.Port.Direction:
         return vckt.Port.Direction.INOUT
     if port.direction == signal.PortDir.NONE:
         return vckt.Port.Direction.NONE
-    raise ValueError
+    raise ValueError(f"Invalid PortDir {port.direction}")
 
 
-def export_conn(
+def export_connection_target(
     sig: Union[signal.Signal, signal.Slice, signal.Concat]
-) -> vckt.Connection:
-    """ Export a proto Connection """
+) -> vckt.ConnectionTarget:
+    """Export a proto `ConnectionTarget`"""
 
-    pconn = vckt.Connection()  # Create a proto-Connection
+    pconn = vckt.ConnectionTarget()  # Create a proto-Connection
     if isinstance(sig, signal.Signal):
-        pconn.sig.name = sig.name
-        pconn.sig.width = sig.width
+        pconn.sig = sig.name
     elif isinstance(sig, signal.Slice):
         pslice = export_slice(sig)
         pconn.slice.CopyFrom(pslice)
@@ -248,14 +266,15 @@ def export_conn(
         pconc = export_concat(sig)
         pconn.concat.CopyFrom(pconc)
     else:
-        raise TypeError(f"Invalid argument to `ProtoExporter.export_conn`: {sig}")
+        msg = f"Invalid argument to `export_connection_target`: {sig}"
+        raise TypeError(msg)
     return pconn
 
 
 def export_slice(slize: signal.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. """
+    """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"
         raise RuntimeError(msg)
@@ -268,18 +287,18 @@ def export_slice(slize: signal.Slice) -> vckt.Slice:
 
 
 def export_concat(concat: signal.Concat) -> vckt.Concat:
-    """ Export (potentially recursive) Signal Concatenations """
+    """Export (potentially recursive) Signal Concatenations"""
     pconc = vckt.Concat()
     for part in concat.parts:
-        pconc.parts.append(export_conn(part))
+        pconc.parts.append(export_connection_target(part))
     return pconc
 
 
 def export_primitive_params(params: Any) -> Dict[str, ScalarOption]:
-    """ Convert the parameters of an `IDEAL` primitive into their VLSIR form. 
-    Returns the result as a dictionary of {name: ScalarOption}s. """
+    """Convert the parameters of an `IDEAL` primitive into their VLSIR form.
+    Returns the result as a dictionary of {name: ScalarOption}s."""
 
-    if isinstance(params, primitives.PulseVoltageSourceParams):
+    if isinstance(params, PulseVoltageSourceParams):
         return dict(
             v1=params.v1,
             v2=params.v2,
@@ -295,10 +314,10 @@ def export_primitive_params(params: Any) -> Dict[str, ScalarOption]:
 
 
 def dictify_params(params: Any) -> Dict[str, ScalarOption]:
-    """ Turn a `paramclass` of parameters into a dictionary of exportable values. 
-    This is essentially a one-layer replacement for the standard library's 
-    `dataclasses.as_dict`, which we replace for sake of our "near-scalar" types 
-    such as `Prefixed` numbers. """
+    """Turn a `paramclass` of parameters into a dictionary of exportable values.
+    This is essentially a one-layer replacement for the standard library's
+    `dataclasses.as_dict`, which we replace for sake of our "near-scalar" types
+    such as `Prefixed` numbers."""
 
     if isinstance(params, dict):
         return params
@@ -314,7 +333,7 @@ def dictify_params(params: Any) -> Dict[str, ScalarOption]:
 def export_param_value(
     val: Union[int, float, str, Decimal, Prefixed, Enum]
 ) -> Optional[vlsir.ParamValue]:
-    """ Export a `ParamValue`. """
+    """Export a `ParamValue`."""
 
     if isinstance(val, type(None)):
         return None
@@ -337,7 +356,7 @@ def export_param_value(
 
 
 def export_prefix(pre: Prefix) -> vlsir.SIPrefix:
-    """ Export an enumerated `Prefix` """
+    """Export an enumerated `Prefix`"""
     map = {
         -24: vlsir.SIPrefix.YOCTO,
         -21: vlsir.SIPrefix.ZEPTO,
@@ -366,12 +385,13 @@ def export_prefix(pre: Prefix) -> vlsir.SIPrefix:
 
 
 def export_prefixed(pref: Prefixed) -> vlsir.Prefixed:
-    """ Export a `Prefixed` number """
+    """Export a `Prefixed` number"""
 
     # Export the metric prefix
     prefix = export_prefix(pref.prefix)
 
     # And export the numeric part, dispatched across its type.
+    # FIXME: `hdl21.Prefixed` numbers are *all* `Decimal` now, this could probably be retired.
     if isinstance(pref.number, int):
         return vlsir.Prefixed(prefix=prefix, integer=pref.number)
     elif isinstance(pref.number, float):
diff --git a/hdl21/signal.py b/hdl21/signal.py
index 1a20bb1..9dd4a1f 100644
--- a/hdl21/signal.py
+++ b/hdl21/signal.py
@@ -20,26 +20,27 @@
 
 """
 
-from typing import Callable, Optional, Any, List, Union, Set 
+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
 
 # Local imports
 from .connect import connectable, is_connectable
-from .instance import PortRef
+from .portref import PortRef
 
 
-def _slice_(*, parent: Union["Signal", "Slice", "Concat"], key: Any) -> "Slice":
-    """ Square-bracket slicing into Signals, Concatenations, and Slices. 
-    Assuming valid inputs, returns a signal-`Slice`.  
+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: 
+    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. 
+    * 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, 
+    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.
     """
 
@@ -101,7 +102,7 @@ def _slice_(*, parent: Union["Signal", "Slice", "Concat"], key: Any) -> "Slice":
 
 
 def slices(cls: type) -> type:
-    """ Decorator to add the 'square-bracket indexing produces `Slice`s' behavior. """
+    """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__"
@@ -118,7 +119,7 @@ def __getitem__(self, key: Any) -> "Slice":
 
 
 class PortDir(Enum):
-    """ Port-Direction Enumeration """
+    """Port-Direction Enumeration"""
 
     INPUT = 0
     OUTPUT = 1
@@ -127,7 +128,7 @@ class PortDir(Enum):
 
 
 class Visibility(Enum):
-    """ Port-Visibility Enumeration """
+    """Port-Visibility Enumeration"""
 
     INTERNAL = 0  # Internal, Module-private Signal
     PORT = 1  # Exposed as a Port
@@ -155,104 +156,114 @@ class Signal:
     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")
 
 
+def _copy_to_internal(sig: Signal) -> Signal:
+    """Make a copy of `sig`, replacing its visibility and port-direction to be internal."""
+    sig = copy(sig)
+    sig.vis = Visibility.INTERNAL
+    sig.direction = PortDir.NONE
+    sig._parent_module = None
+    return sig
+
+
 def Signals(num: int, **kwargs) -> List[Signal]:
-    """ 
-    Create `num` new Signals.  
-    Typical usage: 
+    """
+    Create `num` new Signals.
+    Typical usage:
     ```python
-    @h.module 
+    @h.module
     class UsesSignals:
         bias, fold, mirror = h.Signals(3)
     ```
-    Note the `num` value is required to support the tuple-destructuring use-case shown above. 
+    Note the `num` value is required to support the tuple-destructuring use-case shown above.
     """
     return _plural(fn=Signal, num=num, **kwargs)
 
 
 def Input(**kwargs) -> Signal:
-    """ Input Port Constructor. Thin wrapper around `hdl21.Signal` """
+    """Input Port Constructor. Thin wrapper around `hdl21.Signal`"""
     return Signal(vis=Visibility.PORT, direction=PortDir.INPUT, **kwargs)
 
 
 def Inputs(num: int, **kwargs) -> List[Signal]:
-    """ 
-    Create `num` new Input Ports.  
-    Typical usage: 
+    """
+    Create `num` new Input Ports.
+    Typical usage:
     ```python
-    @h.module 
+    @h.module
     class UsesInputs:
         a, b, c, VDD, VSS = h.Inputs(5)
     ```
-    Note the `num` value is required to support the tuple-destructuring use-case shown above. 
+    Note the `num` value is required to support the tuple-destructuring use-case shown above.
     """
     return _plural(fn=Input, num=num, **kwargs)
 
 
 def Output(**kwargs) -> Signal:
-    """ Output Port Constructor. Thin wrapper around `hdl21.Signal` """
+    """Output Port Constructor. Thin wrapper around `hdl21.Signal`"""
     return Signal(vis=Visibility.PORT, direction=PortDir.OUTPUT, **kwargs)
 
 
 def Outputs(num: int, **kwargs) -> List[Signal]:
-    """ 
-    Create `num` new Output Ports.  
-    Typical usage: 
+    """
+    Create `num` new Output Ports.
+    Typical usage:
     ```python
-    @h.module 
+    @h.module
     class UsesOutputs:
         tdo, tms, tck = h.Outputs(3)
     ```
-    Note the `num` value is required to support the tuple-destructuring use-case shown above. 
+    Note the `num` value is required to support the tuple-destructuring use-case shown above.
     """
     return _plural(fn=Output, num=num, **kwargs)
 
 
 def Inout(**kwargs) -> Signal:
-    """ Inout Port Constructor. Thin wrapper around `hdl21.Signal` """
+    """Inout Port Constructor. Thin wrapper around `hdl21.Signal`"""
     return Signal(vis=Visibility.PORT, direction=PortDir.INOUT, **kwargs)
 
 
 def Inouts(num: int, **kwargs) -> List[Signal]:
-    """ 
-    Create `num` new Inout Ports.  
-    Typical usage: 
+    """
+    Create `num` new Inout Ports.
+    Typical usage:
     ```python
-    @h.module 
+    @h.module
     class UsesInouts:
         gpio1, gpio2 = h.Inouts(2)
     ```
-    Note the `num` value is required to support the tuple-destructuring use-case shown above. 
+    Note the `num` value is required to support the tuple-destructuring use-case shown above.
     """
     return _plural(fn=Inout, num=num, **kwargs)
 
 
 def Port(direction=PortDir.NONE, **kwargs) -> Signal:
-    """ Port Constructor. Thin wrapper around `hdl21.Signal`.
+    """Port Constructor. Thin wrapper around `hdl21.Signal`.
     The `direction` argument sets the Port's direction,
-    and defaults to the unknown direction `PortDir.NONE`. """
+    and defaults to the unknown direction `PortDir.NONE`."""
     return Signal(direction=direction, vis=Visibility.PORT, **kwargs)
 
 
 def Ports(num: int, **kwargs) -> List[Signal]:
-    """ 
-    Create `num` new Ports.  
-    Typical usage: 
+    """
+    Create `num` new Ports.
+    Typical usage:
     ```python
-    @h.module 
+    @h.module
     class UsesPorts:
         inp, out = h.Ports(2)
     ```
-    Note the `num` value is required to support the tuple-destructuring use-case shown above. 
+    Note the `num` value is required to support the tuple-destructuring use-case shown above.
     """
     return _plural(fn=Port, num=num, **kwargs)
 
 
 def _plural(*, fn: Callable, num: int, **kwargs) -> List[Signal]:
-    """ Internal helper method for creating `num` identical `Signal` objects via callable `fn`. """
+    """Internal helper method for creating `num` identical `Signal` objects via callable `fn`."""
     rv = list()
     for _ in range(num):
         rv.append(fn(**kwargs))
@@ -262,8 +273,8 @@ def _plural(*, fn: Callable, num: int, **kwargs) -> List[Signal]:
 @slices
 @connectable
 class Concat:
-    """ Signal Concatenation
-    Uses *Python-convention* ordering, in which "LSBs", i.e. index 0, are specified first. """
+    """Signal Concatenation
+    Uses *Python-convention* ordering, in which "LSBs", i.e. index 0, are specified first."""
 
     def __init__(self, *parts):
         for p in parts:
@@ -295,15 +306,17 @@ def __repr__(self):
 @connectable
 @dataclass
 class Slice:
-    """ Signal Slice, comprising a subset of its width """
+    """Signal Slice, comprising a subset of its width"""
 
-    signal: Union[Signal, Concat, "Slice"]  # Parent Signal
+    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")
@@ -315,12 +328,12 @@ def __post_init_post_parse__(self):
 
     @property
     def width(self) -> int:
-        """ Slice Width Accessor """
+        """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 """
+        """Slice equality requires *identity* between parent Signals"""
         if not isinstance(other, Slice):
             return NotImplemented
         return (
@@ -331,23 +344,23 @@ def __eq__(self, other: "Slice") -> bool:
         )
 
 
-Slice.__pydantic_model__.update_forward_refs()
-
 # Slice-compatible type aliases
 Sliceable = Union[Signal, Concat, Slice]
 
+Slice.__pydantic_model__.update_forward_refs()
+
 
 @connectable
 @dataclass
 class NoConn:
     """
-    # No-Connect 
-    
+    # 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). 
+
+    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
@@ -357,9 +370,9 @@ class NoConn:
     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. """
+        """`NoConn`s are "equal" only if identical objects."""
         return self is other
 
     def __hash__(self) -> int:
-        """ `NoConn`s are "equal" only if identical objects. """
+        """`NoConn`s are "equal" only if identical objects."""
         return hash(id(self))
diff --git a/hdl21/sim/data.py b/hdl21/sim/data.py
index 510eacc..8ae9049 100644
--- a/hdl21/sim/data.py
+++ b/hdl21/sim/data.py
@@ -4,11 +4,16 @@
 
 from decimal import Decimal
 from enum import Enum
-from typing import Union, Any, Optional, List, get_args
+from typing import Union, Any, Optional, List, Sequence, get_args
 from pathlib import Path
 from dataclasses import field
 
-import vlsirtools
+import vlsirtools.spice as vsp
+
+# Create a few aliases to the VLSIR sim-results types
+from vlsirtools.spice import SimResultUnion
+from vlsirtools.spice.sim_data import SimResult
+from vlsir.spice_pb2 import SimResult as SimResultProto
 
 # Local Imports
 from ..datatype import datatype
@@ -25,16 +30,16 @@
 
 
 def tb(name: str) -> Module:
-    """ Create a new testbench module. 
-    Includes the "testbench interface": 
-    a single, scalar, undirected Port named "VSS". """
+    """Create a new testbench module.
+    Includes the "testbench interface":
+    a single, scalar, undirected Port named "VSS"."""
     tb = Module(name=name)
     tb.VSS = Port(width=1)
     return tb
 
 
 def is_tb(i: Instantiable) -> bool:
-    """ Boolean indication of whether Module `m` meets the test-bench interface. """
+    """Boolean indication of whether Module `m` meets the test-bench interface."""
     if isinstance(i, (Module, ExternalModuleCall)):
         m = i
     elif isinstance(i, GeneratorCall):
@@ -54,16 +59,16 @@ def is_tb(i: Instantiable) -> bool:
 
 
 def simattr(cls) -> type:
-    """ 
-    Add a class to the `simattrs` set of generation methods. 
-    This is the magic that enables methods including: 
-    ```python 
+    """
+    Add a class to the `simattrs` set of generation methods.
+    This is the magic that enables methods including:
+    ```python
     sim = Sim()
     sim.param(**kwargs)
     sim.dc(**kwargs)
     sim.tran(**kwargs)
     ```
-    To create the classes defined here, and add them to `sim`. 
+    To create the classes defined here, and add them to `sim`.
     """
 
     # Class names are lower-cases to create the dictionary key,
@@ -75,15 +80,17 @@ def simattr(cls) -> type:
 @simattr
 @datatype
 class Param:
-    """ Simulation Parameter-Value """
+    """Simulation Parameter-Value"""
 
-    name: str  # Parameter Name
-    val: ParamVal  # Parameter Value
+    # Parameter Value
+    val: ParamVal
+    # Parameter Name. Generally required at simulation-time, but `Optional` for during construction stages.
+    name: Optional[str] = None
 
 
 @datatype
 class LinearSweep:
-    """ Linear Sweep """
+    """Linear Sweep"""
 
     start: ParamVal
     stop: ParamVal
@@ -92,16 +99,16 @@ class LinearSweep:
 
 @datatype
 class LogSweep:
-    """ Logarithmic / Decade Sweep """
+    """Logarithmic / Decade Sweep"""
 
-    start: float
-    stop: float
+    start: ParamVal
+    stop: ParamVal
     npts: int
 
 
 @datatype
 class PointSweep:
-    """ List of Points Sweep """
+    """List of Points Sweep"""
 
     points: List[ParamVal]
 
@@ -115,8 +122,8 @@ def is_sweep(val: Any) -> bool:
 
 
 class AnalysisType(Enum):
-    """ Enumerated Analysis-Types 
-    Corresponding to the entries in the `Analysis` type-union. """
+    """Enumerated Analysis-Types
+    Corresponding to the entries in the `Analysis` type-union."""
 
     OP = "op"
     DC = "dc"
@@ -130,7 +137,7 @@ class AnalysisType(Enum):
 @simattr
 @datatype
 class Op:
-    """ Operating Point Analysis """
+    """Operating Point Analysis"""
 
     name: Optional[str] = None  # Optional analysis name
 
@@ -142,7 +149,7 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class Dc:
-    """ DC Steady-State Analysis """
+    """DC Steady-State Analysis"""
 
     var: Union[str, Param]  # Swept parameter, or its name
     sweep: Sweep  # Sweep values
@@ -156,7 +163,7 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class Ac:
-    """ AC Small-Signal Analysis """
+    """AC Small-Signal Analysis"""
 
     sweep: LogSweep  # Sweep values. Always log-valued.
     name: Optional[str] = None  # Optional analysis name
@@ -169,7 +176,7 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class Tran:
-    """ Transient Analysis """
+    """Transient Analysis"""
 
     tstop: ParamVal  # Stop time
     tstep: Optional[ParamVal] = None  # Optional time-step recommendation
@@ -183,7 +190,7 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class SweepAnalysis:
-    """ Sweep over `inner` analyses """
+    """Sweep over `inner` analyses"""
 
     inner: List["Analysis"]  # Inner Analyses
     var: Union[str, Param]  # Sweep variable, or its name
@@ -198,7 +205,7 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class MonteCarlo:
-    """ Add monte-carlo variations to one or more `inner` analyses. """
+    """Add monte-carlo variations to one or more `inner` analyses."""
 
     inner: List["Analysis"]  # Inner Analyses
     npts: int  # Number of points
@@ -212,8 +219,8 @@ def tp(self) -> AnalysisType:
 @simattr
 @datatype
 class CustomAnalysis:
-    """ String-defined, non-first-class analysis statement
-    Primarily for simulator-specific specialty analyses. """
+    """String-defined, non-first-class analysis statement
+    Primarily for simulator-specific specialty analyses."""
 
     cmd: str  # Analysis command string
     name: Optional[str] = None  # Optional analysis name
@@ -232,7 +239,7 @@ def is_analysis(val: Any) -> bool:
 
 
 class SaveMode(Enum):
-    """ Enumerated data-saving modes """
+    """Enumerated data-saving modes"""
 
     NONE = "none"
     ALL = "all"
@@ -252,8 +259,8 @@ class SaveMode(Enum):
 @simattr
 @datatype
 class Save:
-    """ Save Control-Element 
-    Adds content to the target simulation output """
+    """Save Control-Element
+    Adds content to the target simulation output"""
 
     targ: SaveTarget
 
@@ -261,37 +268,40 @@ class Save:
 @simattr
 @datatype
 class Meas:
-    """ Measurement """
+    """Measurement"""
 
     analysis: Union["Analysis", str]  # Target `Analysis`, or its type-name
-    name: str  # Measurement name
     expr: str  # Measured expression. FIXME: just a string to be evaluated, no in-Python semantics
+    name: Optional[str] = None  # Measurement name
 
 
 @simattr
 @datatype
 class Include:
-    """ Include a File Path """
+    """Include a File Path"""
 
-    path: Path
+    path: Path  # Path to include
+    name: Optional[str] = None  # Name, used in class-based `Sim` definitions
 
 
 @simattr
 @datatype
 class Lib:
-    """ Include a Library Section """
+    """Include a Library Section"""
 
-    path: Path
-    section: str
+    path: Path  # Path to include
+    section: str  # Library Section
+    name: Optional[str] = None  # Name, used in class-based `Sim` definitions
 
 
 @simattr
 @datatype
 class Literal:
-    """ Simulation-Control Literal, 
-    expressed as netlist-language text for a particular target language. """
+    """Simulation-Control Literal,
+    expressed as netlist-language text for a particular target language."""
 
     txt: str
+    name: Optional[str] = None  # Name, used in class-based `Sim` definitions
 
 
 # Spice-Sim Attribute-Union
@@ -305,7 +315,7 @@ def is_control(val: Any) -> bool:
 @simattr
 @datatype
 class Options:
-    """ Simulation Options """
+    """Simulation Options"""
 
     temper: Optional[int] = None  # Temperature
     tnom: Optional[int] = None  # Nominal temperature
@@ -313,6 +323,8 @@ class Options:
     reltol: Optional[float] = None
     iabstol: Optional[float] = None
 
+    name: Optional[str] = None  # Name, used in class-based `Sim` definitions
+
 
 # Spice-Sim Attribute-Union
 SimAttr = Union[Analysis, Control, Options]
@@ -324,35 +336,50 @@ def is_simattr(val: Any) -> bool:
 
 @datatype
 class Sim:
-    """ 
-    # Simulation Input 
+    """
+    # Simulation Input
 
-    Comprises a `Instantiable`, typically `Module`, testbench and set of simulation control input. 
-    Testbenches must adhere to the testbench IO interface: 
-    A single, width-one port, nominally named "VSS", and expected to be connected from "simulator ground" to the DUT's ground. 
+    Comprises a `Instantiable`, typically `Module`, testbench and set of simulation control input.
+    Testbenches must adhere to the testbench IO interface:
+    A single, width-one port, nominally named "VSS", and expected to be connected from "simulator ground" to the DUT's ground.
     """
 
     # Testbench, with its dependencies via `Instance`s
     tb: Instantiable
     # Simulation Control Attributes
     attrs: List[SimAttr] = field(default_factory=list)
+    # Optional simulation name
+    name: Optional[str] = None
 
     def add(self, *attrs: List[SimAttr]) -> Union[SimAttr, List[SimAttr]]:
-        """ Add one or more `SimAttr`s to the simulation. 
-        Returns the inserted attributes, either as a list or a single `SimAttr`. """
+        """Add one or more `SimAttr`s to the simulation.
+        Returns the inserted attributes, either as a list or a single `SimAttr`."""
         for attr in attrs:
+            if not is_simattr(attr):
+                raise TypeError
             self.attrs.append(attr)
         if len(attrs) == 1:
             return attrs[0]
         return list(attrs)
 
-    def run(
-        self, opts: Optional[vlsirtools.spice.SimOptions] = None
-    ) -> vlsirtools.spice.SimResultUnion:
-        """ Invoke simulation via `vlsirtools.spice`. """
+    def run(self, opts: Optional[vsp.SimOptions] = None) -> vsp.SimResultUnion:
+        """Invoke simulation via `vlsirtools.spice`."""
         from .to_proto import to_proto
 
-        return vlsirtools.spice.sim(inp=to_proto(self), opts=opts)
+        return vsp.sim(inp=to_proto(self), opts=opts)
+
+
+def run(
+    inp: Union[Sim, Sequence[Sim]], opts: Optional[vsp.SimOptions] = None
+) -> Union[vsp.SimResultUnion, Sequence[vsp.SimResultUnion]]:
+    """Invoke one or more `Sim`s via `vlsirtools.spice`."""
+
+    from .to_proto import to_proto
+
+    if not isinstance(inp, Sequence):
+        return vsp.sim(inp=inp, opts=opts)
+
+    return vsp.sim(inp=[to_proto(s) for s in inp], opts=opts)
 
 
 def _add_attr_func(name: str, cls: type):
@@ -371,3 +398,105 @@ def _method(self, *args, **kwargs):
 # Add all the `simattrs` as methods on `Sim`
 for name, cls in _simattrs.items():
     _add_attr_func(name, cls)
+
+
+def sim(cls: type) -> Sim:
+    """
+    # `Sim` Definition Decorator
+
+    Converts a class-body full of simulation attributes (`SimAttr`s) to a `Sim`.
+    Example Usage:
+
+    ```python
+    import hdl21 as h
+    from hdl21.sim import *
+
+    @sim
+    class MySim:
+        tb = tb(name="mytb")
+
+        x = Param(5)
+        y = Param(6)
+        mydc = Dc(var=x, sweep=PointSweep([1]))
+        myac = Ac(sweep=LogSweep(1e1, 1e10, 10))
+        mytran = Tran(tstop=11 * h.prefix.PICO)
+        mysweep = SweepAnalysis(
+            inner=[mytran],
+            var=x,
+            sweep=LinearSweep(0, 1, 2),
+        )
+        mymc = MonteCarlo(
+            inner=[Dc(var="y", sweep=PointSweep([1]), name="swpdc")],
+            npts=11,
+        )
+        delay = Meas(analysis=mytran, expr="trig_targ_something")
+        opts = Options(reltol=1e-9)
+
+        # Attributes whose names don't really matter can be called anything,
+        # but must be *assigned* into the class, not just constructed.
+        _ = Save(SaveMode.ALL)
+
+        # Non-`SimAttr`s such as `a_path` below will be dropped from the `Sim` definition,
+        # but can be referred to by the following attributes.
+        a_path = "/home/models"
+        _ = Include(a_path)
+        _ = Lib(path=a_path, section="fast")
+    ```
+
+    Class-based `Sim` definitions retain all class members which are `SimAttr`s and drop all others.
+    Non-`SimAttr`-valued fields can nonetheless be handy for defining intermediate values upon which the ultimate SimAttrs depend,
+    such as the `a_path` field in the example aboe.
+
+    Classes decoratated by `sim` have two special field names:
+
+    * (Required) `tb` sets the simulation testbench
+    * (Optional) `name` sets the name of the simulation
+
+    Several other names are disallowed in `sim` class-definitions,
+    generally corresponding to the names of the `Sim` class's fields and methods.
+    Disallowed names include: `["attrs", "add", "run", "namespace"]`.
+    """
+
+    if cls.__bases__ != (object,):
+        raise RuntimeError(f"Invalid @hdl21.sim inheriting from {cls.__bases__}")
+
+    protected_names = ["attrs", "add", "run", "namespace"]
+
+    # Initialize the content of the eventual `Sim`.
+    # Note we largely can't create it now because the `tb` field is required at construction time.
+    name: Optional[str] = None
+    tb: Optional[Instantiable] = None
+    attrs: List[SimAttr] = list()
+
+    # Any class-body content that isn't either (a) one of those special names, or (b) a `SimAttr`,
+    # will be "forgotten" from the `Sim` definition.
+    # This can nonetheless be handy for defining intermediate values upon which the ultimate SimAttrs depend.
+    forgetme: List[Any] = list()
+
+    # Take a lap through the class dictionary, type-check everything and assign relevant attributes to the sim
+    for key, val in cls.__dict__.items():
+        if key in protected_names:
+            raise RuntimeError(f"Invalid field name {key} in Sim {cls}")
+        elif key == "tb":  # Set the test-bench attribute
+            tb = val
+        elif key == "name":  # Set the sim-name attribute
+            name = val
+        elif is_simattr(val):
+            # Add to the sim-attributes list
+            # Special case Python's conventional "ignored" name, the underscore.
+            # Leave attributes named "_"'s `name` field set to `None`.
+            if key != "_":
+                val.name = key
+            attrs.append(val)
+        else:
+            forgetme.append(val)
+
+    if tb is None:
+        raise RuntimeError(f"No `tb` defined in Sim {cls}")
+
+    # Create the `Sim` object
+    name = name or cls.__name__
+    sim = Sim(name=name, tb=tb, attrs=attrs)
+
+    # And return the `Sim`
+    return sim
diff --git a/hdl21/sim/delay.py b/hdl21/sim/delay.py
index 804a3ca..8e2b339 100644
--- a/hdl21/sim/delay.py
+++ b/hdl21/sim/delay.py
@@ -16,7 +16,7 @@
 
 # Local Imports
 from ..module import Module
-from ..signal import Signal, Port, Visibility, PortDir
+from ..signal import Signal, Port, Visibility, PortDir, _copy_to_internal
 from ..instantiable import Instantiable
 from ..prefix import Prefix
 from ..primitives import Vdc, Vpu, Cap
@@ -70,23 +70,23 @@ class DelaySimParams:
 
 @dataclass
 class DelaySimResult:
-    """ Results from a `DelaySim` """
+    """Results from a `DelaySim`"""
 
     # Per-output delays, keyed by output signal name
     delays: Dict[str, Optional[float]]
 
 
 def delays(p: DelaySimParams, opts: Optional[vsp.SimOptions] = None) -> DelaySimResult:
-    """ Create and run a delay `Sim`. 
-    
-    Executes four discrete steps, all defined in this module: 
+    """Create and run a delay `Sim`.
+
+    Executes four discrete steps, all defined in this module:
 
     * `create_sim`
     * `run` the generated `Sim`
-    * `get_meas` is a short step to gather the measurement dictionary. It will (should) eventually disappear. 
+    * `get_meas` is a short step to gather the measurement dictionary. It will (should) eventually disappear.
     * `collect_result`
 
-    Each individually for cases needing to "get in between" them. 
+    Each individually for cases needing to "get in between" them.
     """
     sim = create_sim(p)
     results = sim.run(opts=opts)
@@ -95,7 +95,7 @@ def delays(p: DelaySimParams, opts: Optional[vsp.SimOptions] = None) -> DelaySim
 
 
 def create_sim(p: DelaySimParams) -> Sim:
-    """ Create a delay `Sim` """
+    """Create a delay `Sim`"""
 
     # Create the testbench `Module` and `Sim` input
     tb = Module(name=f"{p.dut.name}Tb")
@@ -144,7 +144,7 @@ def create_sim(p: DelaySimParams) -> Sim:
         sig = p.dut.get(vname)
         if sig is None:
             raise RuntimeError(f"Invalid Supply {vname} in creating delay for {p.dut}")
-        sig = _copy_to_internal(sig)
+        sig = tb.add(_copy_to_internal(sig))
         tb.add(Vdc(Vdc.Params(dc=vval))(p=sig, n=tb.vss), name=f"v{sig.name}")
         tb.dut.connect(sig.name, sig)
 
@@ -154,10 +154,9 @@ def create_sim(p: DelaySimParams) -> Sim:
             continue  # Skip the primary input
 
         # Create a Signal for each DUT input
-        inp = _copy_to_internal(inp)
+        inp = tb.add(_copy_to_internal(inp))
         if inp.width != 1:
             raise RuntimeError(f"Unsupported `Delay` sim for bus input {inp}")
-        tb.add(inp)
         tb.dut.connect(inp.name, inp)
 
         # Get its logic state
@@ -177,7 +176,7 @@ def create_sim(p: DelaySimParams) -> Sim:
     # Create everything associated with each output - load caps and measurements
     dut_outputs = [s for s in p.dut.ports.values() if s.direction == PortDir.OUTPUT]
     for out in dut_outputs:
-        out = _copy_to_internal(out)
+        out = tb.add(_copy_to_internal(out))
         if out.width != 1:
             raise RuntimeError(f"Unsupported `Delay` sim for bus output {inp}")
         tb.dut.connect(out.name, out)
@@ -207,10 +206,10 @@ def create_sim(p: DelaySimParams) -> Sim:
 def collect_result(
     p: DelaySimParams, meas: Dict[str, Optional[float]]
 ) -> DelaySimResult:
-    """ 
-    Collect measured output delays 
-    After simulation has run and data has been pulled back into memory, 
-    extract the delay value for each of `p.module`'s outputs. 
+    """
+    Collect measured output delays
+    After simulation has run and data has been pulled back into memory,
+    extract the delay value for each of `p.module`'s outputs.
     """
     delays = {}
     dut_outputs = [s for s in p.dut.ports.values() if s.direction == PortDir.OUTPUT]
@@ -227,19 +226,11 @@ def collect_result(
 
 
 def get_meas(results: SimResultUnion) -> Dict[str, Optional[float]]:
-    """ Get the `measurements` dict from either of the `VlsirTools` sim-results types. 
-    FIXME: this should get pushed down to `VlsirTools`. """
+    """Get the `measurements` dict from either of the `VlsirTools` sim-results types.
+    FIXME: this should get pushed down to `VlsirTools`."""
 
     if isinstance(results, sd.SimResult):
         return results.an[0].measurements
     if isinstance(results, vsp.SimResult):
         return results.an[0].tran.measurements
     raise TypeError
-
-
-def _copy_to_internal(sig: Signal) -> Signal:
-    """ Make a copy of `sig`, replacing its visibility and port-direction to be internal. """
-    sig = copy(sig)
-    sig.vis = Visibility.INTERNAL
-    sig.direction = PortDir.NONE
-    return sig
diff --git a/hdl21/sim/to_proto.py b/hdl21/sim/to_proto.py
index fefa7f5..62140a7 100644
--- a/hdl21/sim/to_proto.py
+++ b/hdl21/sim/to_proto.py
@@ -17,13 +17,13 @@
 
 
 def to_proto(sim: data.Sim) -> vsp.SimInput:
-    """ Convert a `Sim` to a VLSIR `SimInput` """
+    """Convert a `Sim` to a VLSIR `SimInput`"""
     exporter = ProtoExporter(sim=sim)
     return exporter.export()
 
 
 class ProtoExporter:
-    """ Simulation Protobuf Exporter """
+    """Simulation Protobuf Exporter"""
 
     def __init__(self, sim: data.Sim):
         self.sim = sim  # Store the input `Sim`
@@ -31,8 +31,8 @@ def __init__(self, sim: data.Sim):
         self.analysis_count = 0
 
     def export(self) -> vsp.SimInput:
-        """ Primary export method. Converts `sim.tb` and its dependencies to a `Package`, 
-        and all other `SimAttr`s to VLSIR attributes. """
+        """Primary export method. Converts `sim.tb` and its dependencies to a `Package`,
+        and all other `SimAttr`s to VLSIR attributes."""
         from ..proto import to_proto as module_to_proto
         from ..instantiable import qualname
 
@@ -52,7 +52,7 @@ def export(self) -> vsp.SimInput:
         return self.inp
 
     def export_attr(self, attr: data.SimAttr) -> None:
-        """ Export a Sim Attribute. Primarily dispatches across internal union-types. """
+        """Export a Sim Attribute. Primarily dispatches across internal union-types."""
         from .data import is_analysis, is_control
 
         if isinstance(attr, data.Options):
@@ -65,7 +65,7 @@ def export_attr(self, attr: data.SimAttr) -> None:
             raise TypeError(f"Invalid SimAttr: {attr}")
 
     def export_analysis(self, an: data.Analysis) -> vsp.Analysis:
-        """ Export an `Analysis`, largely dispatching across types and re-assembling into a `vsp.Analysis`. """
+        """Export an `Analysis`, largely dispatching across types and re-assembling into a `vsp.Analysis`."""
         if isinstance(an, data.Op):
             return vsp.Analysis(op=self.export_op(an))
         elif isinstance(an, data.Dc):
@@ -84,21 +84,22 @@ def export_analysis(self, an: data.Analysis) -> vsp.Analysis:
             raise TypeError(f"Invalid Analysis {an}")
 
     def next_analysis_name(self) -> str:
-        """ Create a name for the next user-unnamed Analysis. 
-        Format: `Analysis{num}`, where `num` increases across all analyses. """
+        """Create a name for the next user-unnamed Analysis.
+        Format: `Analysis{num}`, where `num` increases across all analyses."""
         name = f"Analysis{self.analysis_count}"
         self.analysis_count += 1
         return name
 
     def export_op(self, op: data.Op) -> vsp.OpInput:
-        """ Export an operating point analysis """
+        """Export an operating point analysis"""
         analysis_name = op.name or self.next_analysis_name()
         return vsp.OpInput(
-            analysis_name=analysis_name, ctrls=[],  # FIXME: analysis-specific controls
+            analysis_name=analysis_name,
+            ctrls=[],  # FIXME: analysis-specific controls
         )
 
     def export_dc(self, dc: data.Dc) -> vsp.DcInput:
-        """ Export a DC analysis """
+        """Export a DC analysis"""
         analysis_name = dc.name or self.next_analysis_name()
         return vsp.DcInput(
             analysis_name=analysis_name,
@@ -108,29 +109,29 @@ def export_dc(self, dc: data.Dc) -> vsp.DcInput:
         )
 
     def export_ac(self, ac: data.Ac) -> vsp.AcInput:
-        """ Export an AC analysis """
+        """Export an AC analysis"""
         analysis_name = ac.name or self.next_analysis_name()
         return vsp.AcInput(
             analysis_name=analysis_name,
-            fstart=ac.sweep.start,
-            fstop=ac.sweep.stop,
+            fstart=export_float(ac.sweep.start),
+            fstop=export_float(ac.sweep.stop),
             npts=ac.sweep.npts,
             ctrls=[],  # FIXME: analysis-specific controls
         )
 
     def export_tran(self, tran: data.Tran) -> vsp.TranInput:
-        """ Export a transient analysis """
+        """Export a transient analysis"""
         analysis_name = tran.name or self.next_analysis_name()
         return vsp.TranInput(
             analysis_name=analysis_name,
-            tstop=tran.tstop,
-            tstep=tran.tstep,
+            tstop=export_float(tran.tstop),  # FIXME: move schema to Param / Prefixed
+            tstep=export_float(tran.tstep),
             ic={},  # FIXME: initial conditions
             ctrls=[],  # FIXME: analysis-specific controls
         )
 
     def export_sweep_analysis(self, swp_an: data.SweepAnalysis) -> vsp.SweepInput:
-        """ Export a swept, nested set of one or more inner analyses as a `SweepInput`. """
+        """Export a swept, nested set of one or more inner analyses as a `SweepInput`."""
         analysis_name = swp_an.name or self.next_analysis_name()
         return vsp.SweepInput(
             analysis_name=analysis_name,
@@ -141,7 +142,7 @@ def export_sweep_analysis(self, swp_an: data.SweepAnalysis) -> vsp.SweepInput:
         )
 
     def export_monte(self, monte: data.MonteCarlo) -> vsp.MonteInput:
-        """ Export a monte-carlo analysis """
+        """Export a monte-carlo analysis"""
         analysis_name = monte.name or self.next_analysis_name()
         return vsp.MonteInput(
             analysis_name=analysis_name,
@@ -154,7 +155,7 @@ def export_monte(self, monte: data.MonteCarlo) -> vsp.MonteInput:
     def export_custom_analysis(
         self, an: data.CustomAnalysis
     ) -> vsp.CustomAnalysisInput:
-        """ Export a custom analysis """
+        """Export a custom analysis"""
         analysis_name = an.name or self.next_analysis_name()
         return vsp.CustomAnalysisInput(
             analysis_name=analysis_name,
@@ -163,7 +164,7 @@ def export_custom_analysis(
         )
 
     def export_sweep_variable(self, var: Union[str, data.Param]) -> str:
-        """ Export a sweep-variable to its name. """
+        """Export a sweep-variable to its name."""
         if isinstance(var, str):
             return var
         elif isinstance(var, data.Param):
@@ -171,7 +172,7 @@ def export_sweep_variable(self, var: Union[str, data.Param]) -> str:
         raise TypeError(f"Invalid sweep variable {var}")
 
     def export_sweep(self, sweep: data.Sweep) -> vsp.Sweep:
-        """ Export a data sweep. """
+        """Export a data sweep."""
         if isinstance(sweep, data.LinearSweep):
             return vsp.Sweep(
                 linear=vsp.LinearSweep(
@@ -197,7 +198,7 @@ def export_sweep(self, sweep: data.Sweep) -> vsp.Sweep:
 
 
 def export_options(options: data.Options) -> vsp.SimOptions:
-    """ Export simulation options """
+    """Export simulation options"""
     return vsp.SimOptions(
         temp=options.temper,
         tnom=options.tnom,
@@ -208,7 +209,7 @@ def export_options(options: data.Options) -> vsp.SimOptions:
 
 
 def export_control(ctrl: data.Control) -> vsp.Control:
-    """ Export a `Control` element """
+    """Export a `Control` element"""
     if isinstance(ctrl, data.Include):
         return vsp.Control(include=export_include(ctrl))
     if isinstance(ctrl, data.Lib):
@@ -255,7 +256,7 @@ def export_save(save: data.Save) -> vsp.Save:
 
 
 def export_meas(meas: data.Meas) -> vsp.Meas:
-    """ Export a measurement """
+    """Export a measurement"""
     return vsp.Meas(
         analysis_type=export_analysis_type(meas.analysis),
         name=meas.name,
@@ -264,7 +265,7 @@ def export_meas(meas: data.Meas) -> vsp.Meas:
 
 
 def export_analysis_type(an: Union[str, data.Analysis]) -> str:
-    """ Export an `AnalysisType`, or string representation thereof. """
+    """Export an `AnalysisType`, or string representation thereof."""
     if isinstance(an, str):
         return an
     if data.is_analysis(an):
@@ -273,19 +274,22 @@ def export_analysis_type(an: Union[str, data.Analysis]) -> str:
 
 
 def export_param(param: data.Param) -> vlsir.Param:
-    """ Export a parameter declaration """
+    """Export a parameter declaration"""
     from ..proto.to_proto import export_param_value
 
     return vlsir.Param(name=param.name, value=export_param_value(param.val))
 
 
 def export_literal(literal: data.Literal) -> str:
-    """ Export a simulation literal, as its text value """
+    """Export a simulation literal, as its text value"""
     return literal.txt
 
 
 def export_float(num: Union[float, int, Decimal, Prefixed]) -> float:
-    """ Export a `Number` union-type to a float, or protobuf float/double. """
+    """Export a `Number` union-type to a float, or protobuf float/double."""
+    if num is None:
+        # FIXME: this is the protobuf default, but we really wanna make fields that use it optional
+        return 0.0
     if isinstance(num, float):
         return num
     if isinstance(num, (int, Decimal, Prefixed)):
diff --git a/hdl21/source_info.py b/hdl21/source_info.py
new file mode 100644
index 0000000..17b0a6e
--- /dev/null
+++ b/hdl21/source_info.py
@@ -0,0 +1,48 @@
+"""
+# Python Source Info
+
+Track the source file and line number of user calls. 
+"""
+
+import inspect
+from pathlib import Path
+from types import ModuleType
+from typing import Optional, List
+
+from pydantic.dataclasses import dataclass
+
+
+@dataclass
+class SourceInfo:
+    filepath: Path
+    linenum: int
+    pymodule: Optional[ModuleType]
+
+
+def source_info(skipfiles: List[str]) -> Optional[SourceInfo]:
+    """
+    # Get Source Info
+
+    Find the (python) source of the first python-stack-frame from outside `skipfiles` (plus *this* file).
+    Returns `None` if *no* such modules are in the call-stack.
+    """
+
+    if not isinstance(skipfiles, list):
+        raise RuntimeError(
+            "Internal Error - source_info() called with non-list skipfiles"
+        )
+
+    skipfiles = set(skipfiles)
+    skipfiles.add(__file__)
+
+    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
+            )
+
+    # No caller outside `skipfiles` found anywhere in the call-stack.
+    return None
diff --git a/hdl21/walker.py b/hdl21/walker.py
index d2b10e2..9f77203 100644
--- a/hdl21/walker.py
+++ b/hdl21/walker.py
@@ -3,49 +3,64 @@
 """
 
 # Std-Lib
-from types import SimpleNamespace
+from typing import List
 
 # Local imports
+from .elab import Elaboratables
 from .module import Module, ExternalModuleCall
 from .instance import Instance
 from .primitives import PrimitiveCall
-from .bundle import Bundle, BundleInstance
-from .signal import Port, PortDir, Signal, Visibility
-from .generator import Generator, GeneratorCall
+from .bundle import BundleInstance
+from .generator import GeneratorCall
 
 
 class HierarchyWalker:
-    """Hierarchical Traversal Helper
-    Walks a hierarchical `Module` or `SimpleNamespace` thereof.
-    Designed to be used as a base-class for extensions such as
-    process-specific instance-replacements."""
-
-    def visit_namespace(self, ns: SimpleNamespace):
-        for k, v in ns.__dict__.items():
-            if isinstance(v, Module):
-                self.visit_module(v)
-            if isinstance(v, SimpleNamespace):
-                self.visit_namespace(v)
-
-    def visit_generator_call(self, call: GeneratorCall):
-        raise NotImplementedError
-
-    def visit_module(self, module: Module):
+    """
+    # Hierarchical Walker
+
+    Walks a hierarchical design tree.
+    Designed to be used as a base-class for extensions such as process-specific instance-replacements.
+    """
+
+    def visit_elaboratables(self, src: Elaboratables) -> None:
+        """Visit an `Elaboratables` object.
+        Largely dispatches across the type-union of elaboratable objects."""
+
+        if isinstance(src, Module):
+            return self.visit_module(src)
+        if isinstance(src, GeneratorCall):
+            return self.visit_generator_call(src)
+        if isinstance(src, List):
+            for x in src:
+                self.visit_elaboratables(x)
+            return
+        raise TypeError
+
+    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)
+
+    def visit_module(self, module: Module) -> None:
+        """Visit a `Module`.
+        Primary method for most manipulations."""
+
         for inst in module.instances.values():
             self.visit_instance(inst)
         for inst in module.bundles.values():
             self.visit_bundle_instance(inst)
 
-    def visit_external_module(self, call: ExternalModuleCall):
+    def visit_external_module(self, call: ExternalModuleCall) -> None:
         return
 
-    def visit_primitive_call(self, call: PrimitiveCall):
+    def visit_primitive_call(self, call: PrimitiveCall) -> None:
         return
 
-    def visit_bundle_instance(self, inst: BundleInstance):
+    def visit_bundle_instance(self, inst: BundleInstance) -> None:
         return
 
-    def visit_instance(self, inst: Instance):
+    def visit_instance(self, inst: Instance) -> None:
+        """Visit a hierarchical `Instance`."""
+
         if isinstance(inst.of, GeneratorCall):
             return self.visit_generator_call(call=inst.of)
         if isinstance(inst.of, Module):
diff --git a/pdks/Asap7/asap7/asap7.py b/pdks/Asap7/asap7/asap7.py
index 84bbac6..61b5bbd 100644
--- a/pdks/Asap7/asap7/asap7.py
+++ b/pdks/Asap7/asap7/asap7.py
@@ -8,7 +8,7 @@
 The primitive components of the ASAP7 PDK are comprised solely of core Mos transistors `{n,p}mos_{rvt,lvt,slvt,sram}`. 
 
 FIXME!: Unlike the common subckt-based models provided by physical PDKs, the ASAP7 transistors are provided solely 
-as BSIM-CMG `.model` definitions. These are represented in the `vlsir` proto-schema as (FIXME: ...)
+as BSIM-CMG `.model` definitions. These are represented as (FIXME: ...)
 
 """
 
@@ -22,7 +22,6 @@
 import hdl21 as h
 from hdl21.pdk import PdkInstallation
 from hdl21.primitives import Mos, MosType, MosVth, MosParams
-from vlsir import circuit as vckt
 
 
 @dataclass
@@ -72,13 +71,13 @@ class Install(PdkInstallation):
 
 
 class Asap7Walker(h.HierarchyWalker):
-    """ Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s. """
+    """Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s."""
 
     def __init__(self):
         self.mos_modcalls = dict()
 
     def visit_instance(self, inst: h.Instance):
-        """ Replace instances of `h.Primitive` with our `ExternalModule`s """
+        """Replace instances of `h.Primitive` with our `ExternalModule`s"""
         if isinstance(inst.of, h.PrimitiveCall):
             inst.of = self.replace_primitive(inst.of)
             return
@@ -95,14 +94,14 @@ def replace_primitive(
         return primcall
 
     def mos_module(self, params: MosParams) -> h.ExternalModule:
-        """ Retrieve or create an `ExternalModule` for a MOS of parameters `params`. """
+        """Retrieve or create an `ExternalModule` for a MOS of parameters `params`."""
         mod = _mos_modules.get((params.tp, params.vth), None)
         if mod is None:
             raise RuntimeError(f"No Mos module {modname}")
         return mod
 
     def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
-        """ Retrieve or create a `Call` for MOS parameters `params`."""
+        """Retrieve or create a `Call` for MOS parameters `params`."""
         # First check our cache
         if params in self.mos_modcalls:
             return self.mos_modcalls[params]
@@ -122,8 +121,6 @@ def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
         return modcall
 
 
-def compile(src: vckt.Package) -> vckt.Package:
-    """ Compile proto-Package `src` to the ASAP7 technology """
-    ns = h.from_proto(src)
-    Asap7Walker().visit_namespace(ns)
-    return h.to_proto(ns)
+def compile(src: h.Elaboratables) -> None:
+    """Compile `src` to the ASAP7 technology"""
+    return Asap7Walker().visit_elaboratables(src)
diff --git a/pdks/Asap7/setup.py b/pdks/Asap7/setup.py
index 167ed4c..dd7488a 100644
--- a/pdks/Asap7/setup.py
+++ b/pdks/Asap7/setup.py
@@ -28,5 +28,5 @@
     packages=find_packages(),
     python_requires=">=3.8, <4",
     install_requires=[f"hdl21=={_VLSIR_VERSION}"],
-    extras_require={"dev": ["pytest==5.2", "coverage", "pytest-cov", "twine"]},
+    extras_require={"dev": ["pytest==7.1", "coverage", "pytest-cov", "twine"]},
 )
diff --git a/pdks/Sky130/setup.py b/pdks/Sky130/setup.py
index e0c2354..be1f76f 100644
--- a/pdks/Sky130/setup.py
+++ b/pdks/Sky130/setup.py
@@ -28,5 +28,5 @@
     packages=find_packages(),
     python_requires=">=3.8, <4",
     install_requires=[f"hdl21=={_VLSIR_VERSION}"],
-    extras_require={"dev": ["pytest==5.2", "coverage", "pytest-cov", "twine"]},
+    extras_require={"dev": ["pytest==7.1", "coverage", "pytest-cov", "twine"]},
 )
diff --git a/pdks/Sky130/sky130/sky130.py b/pdks/Sky130/sky130/sky130.py
index 6cae7ee..97badd8 100644
--- a/pdks/Sky130/sky130/sky130.py
+++ b/pdks/Sky130/sky130/sky130.py
@@ -34,20 +34,19 @@
 import hdl21 as h
 from hdl21.pdk import PdkInstallation
 from hdl21.primitives import Mos, MosType, MosVth, MosParams
-from vlsir import circuit as vckt
 
 
 @dataclass
 class Install(PdkInstallation):
-    """ Pdk Installation Data 
-    External data provided by site-specific installations """
+    """Pdk Installation Data
+    External data provided by site-specific installations"""
 
     model_lib: Path  # Path to the transistor models included in this module
 
 
 @h.paramclass
 class Sky130MosParams:
-    """ Parameters for the PDK MOS transistor modules """
+    """Parameters for the PDK MOS transistor modules"""
 
     # l = 1 w = 1 ad = 0 as = 0 pd = 0 ps = 0 nrd = 0 nrs = 0 sa = 0 sb = 0 sd = 0 mult = 1 nf = 1.0
 
@@ -58,7 +57,7 @@ class Sky130MosParams:
 
 
 def _xtor_module(modname: str) -> h.ExternalModule:
-    """ Transistor module creator, with module-name `name`."""
+    """Transistor module creator, with module-name `name`."""
     return h.ExternalModule(
         domain="sky130",
         name=modname,
@@ -88,13 +87,13 @@ def _xtor_module(modname: str) -> h.ExternalModule:
 
 
 class Sky130Walker(h.HierarchyWalker):
-    """ Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s. """
+    """Hierarchical Walker, converting `h.Primitive` instances to process-defined `ExternalModule`s."""
 
     def __init__(self):
         self.mos_modcalls = dict()
 
     def visit_instance(self, inst: h.Instance):
-        """ Replace instances of `h.Primitive` with our `ExternalModule`s """
+        """Replace instances of `h.Primitive` with our `ExternalModule`s"""
         if isinstance(inst.of, h.PrimitiveCall):
             inst.of = self.replace_primitive(inst.of)
             return
@@ -111,7 +110,7 @@ def replace_primitive(
         return primcall
 
     def mos_module(self, params: MosParams) -> h.ExternalModule:
-        """ Retrieve or create an `ExternalModule` for a MOS of parameters `params`. """
+        """Retrieve or create an `ExternalModule` for a MOS of parameters `params`."""
         mod = xtors.get((params.tp, params.vth), None)
         if mod is None:
             raise RuntimeError(
@@ -120,7 +119,7 @@ def mos_module(self, params: MosParams) -> h.ExternalModule:
         return mod
 
     def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
-        """ Retrieve or create a `Call` for MOS parameters `params`."""
+        """Retrieve or create a `Call` for MOS parameters `params`."""
         # First check our cache
         if params in self.mos_modcalls:
             return self.mos_modcalls[params]
@@ -134,7 +133,10 @@ def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
         w = "650n" if params.w is None else params.w
         l = "150n" if params.l is None else params.l
         modparams = Sky130MosParams(
-            w=f"({w} * 1e6)", l=f"({l} * 1e6)", nf=params.npar, mult=1,
+            w=f"({w} * 1e6)",
+            l=f"({l} * 1e6)",
+            nf=params.npar,
+            mult=1,
         )
 
         # Combine the two into a call, cache and return it
@@ -143,8 +145,6 @@ def mos_module_call(self, params: MosParams) -> h.ExternalModuleCall:
         return modcall
 
 
-def compile(src: vckt.Package) -> vckt.Package:
-    """ Compile proto-Package `src` to the SkyWater 130nm technology """
-    ns = h.from_proto(src)
-    Sky130Walker().visit_namespace(ns)
-    return h.to_proto(ns)
+def compile(src: h.Elaboratables) -> None:
+    """Compile `src` to the Sample technology"""
+    return Sky130Walker().visit_elaboratables(src)
diff --git a/scratch/diff.py b/scratch/diff.py
deleted file mode 100644
index 1ee0db5..0000000
--- a/scratch/diff.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
-# Hdl21 Differential Bundle and Facilities for Differential Circuits 
-
-"""
-
-from pydantic.dataclasses import dataclass
-
-# Local imports
-from .signal import Signal
-from .bundle import bundle
-from .instantiable import Instantiable
-
-
-@bundle
-class DiffSomething:
-    """ Differential Bundle """
-
-    p = Signal(width=1, desc="Positive")
-    n = Signal(width=1, desc="Negative")
-
-
-@dataclass
-class DiffSomethingElse:
-    """ Differential Instance Bundle """
-
-    of: Instantiable  # Target Module
-    conns: Dict[str, Any]  # Really [str, Connectable]
-
-
-def diff_inst(of: Instantiable) -> DiffSomethingElse:
-    """ Create a Differential Instance Bundle """
-    return DiffSomethingElse(of=of, conns={})
-
diff --git a/setup.py b/setup.py
index d33c930..6e6caf7 100644
--- a/setup.py
+++ b/setup.py
@@ -31,9 +31,9 @@
     install_requires=[
         f"vlsir=={_VLSIR_VERSION}",
         f"vlsirtools=={_VLSIR_VERSION}",
-        "pydantic==1.8.2",  # Note we are especially sensitive to this version of `pydantic`, see https://github.com/dan-fritchman/Hdl21/issues/15
+        "pydantic==1.9.1",
     ],
     extras_require={
-        "dev": ["pytest==5.2", "coverage", "pytest-cov", "black==19.10b0", "twine"]
+        "dev": ["pytest==7.1", "coverage", "pytest-cov", "black==22.6", "twine"]
     },
 )


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index ad50d52..11cbcb5 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -27,15 +27,24 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
+    
     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
+    
     - name: Install dependencies
       run: bash .github/install-${{ matrix.dep-installer }}.sh
+
     - name: Test with pytest
       run: |
         pytest -sv --cov=./ --cov-report=xml
+
+    - name: Black Formatting Check 
+      uses: psf/black@stable
+      with:
+        version: "22.6"
+
     - name: Upload coverage to Codecov # Adapted from https://github.com/codecov/codecov-action#usage
       uses: codecov/codecov-action@v1
       with:
diff --git a/hdl21/pdk/sample_pdk/test_sample_pdk.py b/hdl21/pdk/sample_pdk/test_sample_pdk.py
index 37227ce..d26dbaf 100644
--- a/hdl21/pdk/sample_pdk/test_sample_pdk.py
+++ b/hdl21/pdk/sample_pdk/test_sample_pdk.py
@@ -34,26 +34,20 @@ def test_compile():
     hasmos = mosmodule()
 
     # Compile it for the PDK
-    pkg = h.to_proto(hasmos)
-    pdk_pkg = sample_pdk.compile(pkg)
-
-    # Import it back into Modules & Namespaces
-    ns = h.from_proto(pdk_pkg)
-    rt = ns.hdl21.pdk.sample_pdk.test_sample_pdk.hasmos
+    sample_pdk.compile(hasmos)
 
     # And check what came back
-    assert isinstance(rt.n, h.Instance)
-    assert isinstance(rt.p, h.Instance)
-    assert isinstance(rt.n.of, h.ExternalModuleCall)
-    assert isinstance(rt.p.of, h.ExternalModuleCall)
-    assert isinstance(rt.n.of.params, dict)
-    assert isinstance(rt.p.of.params, dict)
+    assert isinstance(hasmos.n, h.Instance)
+    assert isinstance(hasmos.p, h.Instance)
+    assert isinstance(hasmos.n.of, h.ExternalModuleCall)
+    assert isinstance(hasmos.p.of, h.ExternalModuleCall)
+    assert isinstance(hasmos.n.of.params, sample_pdk.SamplePdkMosParams)
+    assert isinstance(hasmos.p.of.params, sample_pdk.SamplePdkMosParams)
 
 
 def test_netlist():
     hasmos = mosmodule()
 
     # Netlist it for the PDK
-    pkg = h.to_proto(hasmos)
-    pkg = sample_pdk.compile(pkg)
-    h.netlist(pkg, StringIO(), "spectre")
+    sample_pdk.compile(hasmos)
+    h.netlist(hasmos, StringIO(), fmt="spectre")
diff --git a/hdl21/pdk/test_pdk.py b/hdl21/pdk/test_pdk.py
new file mode 100644
index 0000000..be62e7e
--- /dev/null
+++ b/hdl21/pdk/test_pdk.py
@@ -0,0 +1,22 @@
+from .corner import Corner, CornerType, CmosCorner
+
+
+def test_corner1():
+    assert str(Corner.TYP) == "Corner.TYP"
+    assert str(Corner.FAST) == "Corner.FAST"
+    assert str(Corner.SLOW) == "Corner.SLOW"
+
+
+def test_cornertype1():
+    assert str(CornerType.MOS) == "CornerType.MOS"
+    assert str(CornerType.CMOS) == "CornerType.CMOS"
+    assert str(CornerType.RES) == "CornerType.RES"
+    assert str(CornerType.CAP) == "CornerType.CAP"
+
+
+def test_cmos1():
+    assert str(CmosCorner.TT) == "CmosCorner.TT"
+    assert str(CmosCorner.FF) == "CmosCorner.FF"
+    assert str(CmosCorner.SS) == "CmosCorner.SS"
+    assert str(CmosCorner.FS) == "CmosCorner.FS"
+    assert str(CmosCorner.SF) == "CmosCorner.SF"
diff --git a/hdl21/sim/tests/test_sim.py b/hdl21/sim/tests/test_sim.py
index b77855a..10c6c35 100644
--- a/hdl21/sim/tests/test_sim.py
+++ b/hdl21/sim/tests/test_sim.py
@@ -5,7 +5,7 @@
 
 
 def test_sim1():
-    """ Test minimal `Sim` creation """
+    """Test minimal `Sim` creation"""
     s = Sim(tb=tb("empty"), attrs=[])
     assert isinstance(s, Sim)
     assert s.tb.name == "empty"
@@ -14,7 +14,7 @@ def test_sim1():
 
 
 def test_sim2():
-    """ Test creating a more fully-featured sim """
+    """Test creating a more fully-featured sim"""
     Sim(
         tb=tb(name="mytb"),
         attrs=[
@@ -29,7 +29,9 @@ def test_sim2():
                 name="mysweep",
             ),
             MonteCarlo(
-                inner=[Dc(var="y", sweep=PointSweep([1]), name="swpdc"),],
+                inner=[
+                    Dc(var="y", sweep=PointSweep([1]), name="swpdc"),
+                ],
                 npts=11,
                 name="mymc",
             ),
@@ -43,7 +45,7 @@ def test_sim2():
 
 
 def test_simattrs():
-    """ Test the "sim attrs" feature, which adds methods to `Sim` for each `SimAttr` """
+    """Test the "sim attrs" feature, which adds methods to `Sim` for each `SimAttr`"""
 
     s = Sim(tb=tb("mytb"))
 
@@ -54,7 +56,11 @@ def test_simattrs():
     assert tr.tstop == 11 * h.prefix.p
     sw = s.sweepanalysis(inner=[tr], var=p, sweep=LinearSweep(0, 1, 2), name="mysweep")
     mc = s.montecarlo(
-        inner=[Dc(var="y", sweep=PointSweep([1]), name="swpdc"),], npts=11, name="mymc"
+        inner=[
+            Dc(var="y", sweep=PointSweep([1]), name="swpdc"),
+        ],
+        npts=11,
+        name="mymc",
     )
     s.save(SaveMode.ALL)
     s.meas(analysis=tr, name="a_delay", expr="trig_targ_something")
@@ -63,6 +69,52 @@ def test_simattrs():
     s.options(reltol=1e-9)
 
 
+def test_sim_decorator():
+    """Test creating the same Sim, via the class decorator"""
+
+    @h.sim.sim
+    class MySim:
+        tb = tb(name="mytb")
+
+        x = Param(5)
+        y = Param(6)
+        mydc = Dc(var=x, sweep=PointSweep([1]))
+        myac = Ac(sweep=LogSweep(1e1, 1e10, 10))
+        mytran = Tran(tstop=11 * h.prefix.p)
+        mysweep = SweepAnalysis(
+            inner=[mytran],
+            var=x,
+            sweep=LinearSweep(0, 1, 2),
+        )
+        mymc = MonteCarlo(
+            inner=[
+                Dc(var="y", sweep=PointSweep([1]), name="swpdc"),
+            ],
+            npts=11,
+        )
+        a_delay = Meas(analysis=mytran, expr="trig_targ_something")
+        opts = Options(reltol=1e-9)
+
+        # Attributes whose names don't really matter can be called anything,
+        # but must be *assigned* into the class, not just constructed.
+        _ = Save(SaveMode.ALL)
+
+        # The `a_path` field will be dropped from the `Sim` definition,
+        # but can be referred to by the following attributes.
+        a_path = "/home/models"
+        _ = Include(a_path)
+        _ = Lib(path=a_path, section="fast")
+
+    assert isinstance(MySim, Sim)
+    assert MySim.name == "MySim"
+    assert isinstance(MySim.tb, h.Module)
+    assert MySim.tb.name == "mytb"
+    assert isinstance(MySim.attrs, list)
+    for attr in MySim.attrs:
+        assert is_simattr(attr)
+    assert not hasattr(MySim, "a_path")
+
+
 def test_tb():
     mytb = tb("mytb")
     assert isinstance(mytb, h.Module)
@@ -70,7 +122,7 @@ def test_tb():
 
 
 def test_proto1():
-    """ Test exporting `Sim` to the VLSIR Protobuf schema """
+    """Test exporting `Sim` to the VLSIR Protobuf schema"""
 
     s = Sim(
         tb=tb(name="mytb"),
@@ -86,7 +138,9 @@ def test_proto1():
                 name="mysweep",
             ),
             MonteCarlo(
-                inner=[Dc(var="y", sweep=PointSweep([1]), name="swpdc"),],
+                inner=[
+                    Dc(var="y", sweep=PointSweep([1]), name="swpdc"),
+                ],
                 npts=11,
                 name="mymc",
             ),
@@ -108,8 +162,8 @@ def test_proto1():
 
 
 def test_generator_sim():
-    """ Test creating and exporting `Sim` with generator-valued DUTs, 
-    particularly several with different parameter-values. """
+    """Test creating and exporting `Sim` with generator-valued DUTs,
+    particularly several with different parameter-values."""
 
     @h.paramclass
     class P:  # A largely dummy param-class
@@ -177,9 +231,9 @@ def empty_tb() -> h.Module:
 
     @h.module
     class EmptyTb:
-        """ An Empty TestBench, 
-        or as close as we can get to one without some simulators failing. 
-        AKA, a resistor to ground. """
+        """An Empty TestBench,
+        or as close as we can get to one without some simulators failing.
+        AKA, a resistor to ground."""
 
         VSS = h.Port()
         s = h.Signal()
@@ -190,10 +244,11 @@ class EmptyTb:
 
 
 @pytest.mark.skipif(
-    vlsirtools.spice.default() is None, reason="No simulator available",
+    vlsirtools.spice.default() is None,
+    reason="No simulator available",
 )
 def test_empty_sim1():
-    """ Create and run an empty `Sim`, returning a VLSIR_PROTO """
+    """Create and run an empty `Sim`, returning a VLSIR_PROTO"""
 
     from hdl21.sim import Sim, to_proto
     import vlsir.spice_pb2 as vsp
@@ -206,14 +261,15 @@ def test_empty_sim1():
 
 
 @pytest.mark.skipif(
-    vlsirtools.spice.default() is None, reason="No simulator available",
+    vlsirtools.spice.default() is None,
+    reason="No simulator available",
 )
 @pytest.mark.skipif(
     vlsirtools.spice.default() == vlsirtools.spice.SupportedSimulators.XYCE,
     reason="No support for `Xyce` + `SimData` python types",
 )
 def test_empty_sim2():
-    """ Create and run an empty `Sim`, returning SIM_DATA """
+    """Create and run an empty `Sim`, returning SIM_DATA"""
 
     from hdl21.sim import Sim, to_proto
     import vlsir.spice_pb2 as vsp
diff --git a/hdl21/tests/test_exports.py b/hdl21/tests/test_exports.py
index 5c425e1..cce86c0 100644
--- a/hdl21/tests/test_exports.py
+++ b/hdl21/tests/test_exports.py
@@ -6,10 +6,9 @@
 * Importing back from VLSIR
 """
 
-import sys, copy, pytest
+import sys, pytest
 from io import StringIO
 from types import SimpleNamespace
-from enum import Enum, EnumMeta, auto
 from textwrap import dedent
 
 # Import the PUT (package under test)
@@ -19,7 +18,7 @@
 
 @pytest.mark.xfail(reason="#1 https://github.com/dan-fritchman/Hdl21/issues/1")
 def test_export_strides():
-    """ Test exporting connections with non-unit Slice-strides """
+    """Test exporting connections with non-unit Slice-strides"""
 
     c = h.Module(name="c")
     c.p = h.Input(width=2)
@@ -28,7 +27,7 @@ def test_export_strides():
     p.s = h.Signal(width=20)
     p.c = c(p=p.s[::10])  # Connect two of the 20 bits, with stride 10
 
-    h.netlist(h.to_proto(p), sys.stdout, "verilog")
+    h.netlist(h.to_proto(p), sys.stdout, fmt="verilog")
 
 
 def test_prim_proto1():
@@ -126,7 +125,7 @@ def test_proto2():
     assert isinstance(pm, vlsir.circuit.Module)
     assert pm.name == "hdl21.tests.test_exports.Child1"
     assert len(pm.ports) == 2
-    assert len(pm.signals) == 0
+    assert len(pm.signals) == 2
     assert len(pm.instances) == 0
     assert len(pm.parameters) == 0
 
@@ -135,7 +134,7 @@ def test_proto2():
     assert isinstance(pm, vlsir.circuit.Module)
     assert pm.name == "hdl21.tests.test_exports.Child2"
     assert len(pm.ports) == 2
-    assert len(pm.signals) == 0
+    assert len(pm.signals) == 2
     assert len(pm.instances) == 0
     assert len(pm.parameters) == 0
 
@@ -144,6 +143,7 @@ def test_proto2():
     assert isinstance(pm, vlsir.circuit.Module)
     assert pm.name == "hdl21.tests.test_exports.TestProto2"
     assert len(pm.ports) == 0
+    assert len(pm.signals) == 2
     assert len(pm.instances) == 2
     assert len(pm.parameters) == 0
 
@@ -172,7 +172,7 @@ def test_proto3():
     assert isinstance(pm, vlsir.circuit.Module)
     assert pm.name == "hdl21.tests.test_exports.M1"
     assert len(pm.ports) == 2
-    assert len(pm.signals) == 0
+    assert len(pm.signals) == 2
     assert len(pm.instances) == 0
     assert len(pm.parameters) == 0
 
@@ -293,7 +293,7 @@ def test_proto_roundtrip2():
 
 
 def test_netlist_fmts():
-    """ Test netlisting basic types to several formats """
+    """Test netlisting basic types to several formats"""
 
     @h.module
     class Bot:
@@ -314,7 +314,7 @@ class Top:
 
     # Convert the proto-package to a netlist
     nl = StringIO()
-    h.netlist(ppkg, nl, "spectre")
+    h.netlist(ppkg, nl, fmt="spectre")
     nl = nl.getvalue()
 
     # Basic checks on its contents
@@ -328,7 +328,7 @@ class Top:
 
     # Convert the proto-package to another netlist format
     nl = StringIO()
-    h.netlist(ppkg, nl, "verilog")
+    h.netlist(ppkg, nl, fmt="verilog")
     nl = nl.getvalue()
 
     # Basic checks on its contents
@@ -342,7 +342,7 @@ class Top:
     assert "endmodule // Top" in nl
 
     nl = StringIO()
-    h.netlist(ppkg, nl, "spice")
+    h.netlist(ppkg, nl, fmt="spice")
     nl = nl.getvalue()
     assert ".SUBCKT Bot \n+ s_2 s_1 s_0 p" in nl
     assert ".SUBCKT Top \n+ p" in nl
@@ -360,7 +360,7 @@ class DUT:
 
     ppkg = h.to_proto(DUT)
     nl = StringIO()
-    h.netlist(ppkg, nl, "spice")
+    h.netlist(ppkg, nl, fmt="spice")
     good = dedent(
         """\
         .SUBCKT DUT 
@@ -393,9 +393,9 @@ class DUT:
 
 
 def test_bad_proto_naming():
-    """ Test some naming cases which are alright in Python, 
-    but generate naming conflicts in serialization. 
-    Generally these boil down to "same `Module`-names in same Python module". """
+    """Test some naming cases which are alright in Python,
+    but generate naming conflicts in serialization.
+    Generally these boil down to "same `Module`-names in same Python module"."""
 
     # Create a naming conflict between Module definitions
     Ma1 = h.Module(name="Ma")
@@ -428,7 +428,7 @@ class MaParent:
 
 
 def test_generator_recall():
-    """ Test multi-calling generators """
+    """Test multi-calling generators"""
 
     @h.generator
     def CallMeTwice(_: h.HasNoParams) -> h.Module:
@@ -460,16 +460,16 @@ class Caller:
 
 
 def test_rountrip_external_module():
-    """ Test round-tripping `ExternalModule`s between Hdl21 and VLSIR Proto """
+    """Test round-tripping `ExternalModule`s between Hdl21 and VLSIR Proto"""
 
-    @h.paramclass 
-    class P: # Our ExternalModule's parameter-type
+    @h.paramclass
+    class P:  # Our ExternalModule's parameter-type
         a = h.Param(dtype=int, desc="a", default=1)
         b = h.Param(dtype=str, desc="b", default="two")
 
     E = h.ExternalModule(name="E", port_list=[], paramtype=P)
 
-    @h.module 
+    @h.module
     class HasE:
         e = E(P())()
 
diff --git a/hdl21/tests/test_hdl21.py b/hdl21/tests/test_hdl21.py
index dd73071..54f8caf 100644
--- a/hdl21/tests/test_hdl21.py
+++ b/hdl21/tests/test_hdl21.py
@@ -16,7 +16,7 @@ def test_version():
 
 
 def test_module1():
-    """ Initial Module Test """
+    """Initial Module Test"""
 
     @h.module
     class M1:
@@ -150,169 +150,6 @@ class HasGen:
     assert HasGen.c.of is M
 
 
-def test_params1():
-    # Initial param-class test
-
-    @h.paramclass
-    class MyParams:
-        a = h.Param(dtype=int, default=5, desc="your fave")
-
-    assert h.isparamclass(MyParams)
-
-    m = MyParams()
-    assert isinstance(m.a, int)
-    assert m.a == 5
-    assert isinstance(m.__params__["a"], h.Param)
-    assert m.defaults() == dict(a=5)
-    assert m.descriptions() == dict(a="your fave")
-
-
-def test_params2():
-    @h.paramclass
-    class Pc2:
-        # Required
-        r = h.Param(dtype=str, desc="required")
-        # Optional
-        o = h.Param(dtype=str, default="hmmm", desc="optional")
-
-    assert h.isparamclass(Pc2)
-    assert Pc2.defaults() == dict(o="hmmm")
-    assert Pc2.descriptions() == dict(r="required", o="optional")
-
-    p = Pc2(r="provided")
-    assert isinstance(p.r, str)
-    assert p.r == "provided"
-    assert isinstance(p.o, str)
-    assert p.o == "hmmm"
-    assert p.defaults() == dict(o="hmmm")
-    assert p.descriptions() == dict(r="required", o="optional")
-
-
-def test_params3():
-    # Test parameters with a creation-time list-tuple conversion
-    @h.paramclass
-    class HasTuple:
-        t = h.Param(dtype=tuple, desc="Go ahead, try a list")
-
-    ht = HasTuple(t=[1, 2, 3])
-    assert isinstance(ht.t, tuple)
-    assert ht.t == (1, 2, 3)
-
-
-def test_params4():
-    # Test some param-class nesting
-    from dataclasses import asdict
-
-    @h.paramclass
-    class Inner:
-        i = h.Param(dtype=int, desc="Inner int-field")
-
-    @h.paramclass
-    class Outer:
-        inner = h.Param(dtype=Inner, desc="Inner fields")
-        f = h.Param(dtype=float, desc="A float", default=3.14159)
-
-    o = Outer(inner=Inner(11))
-    assert isinstance(o, Outer)
-    assert isinstance(o.inner, Inner)
-    assert o.inner == Inner(11)
-    assert o.inner.i == 11
-    assert o.f == 3.14159
-
-    # Create from a (nested) dictionary
-    d1 = {"inner": {"i": 11}, "f": 22.2}
-    o1 = Outer(**d1)
-    uname1 = h.params._unique_name(o1)
-    # Convert back to another dictionary
-    d2 = asdict(o1)
-    # And check they line up
-    assert d1 == d2
-    # Round-trip back to an `Outer`
-    o2 = Outer(**d2)
-    uname2 = h.params._unique_name(o2)
-    assert uname1 == uname2
-    assert uname1 == "3dcc309796996b3a8a61db66631c5a93"
-
-
-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
-
-        @paramclass
-        class C(TabError):  # Of course this is a sub-class of the best built-in class
-            ...
-
-    @paramclass
-    class C:
-        a = Param(dtype=int, desc="Gonna Fail!")
-
-    with pytest.raises(RuntimeError):
-        # Test that sub-classing a paramclass fails
-
-        class D(C):
-            ...
-
-    with pytest.raises(TypeError):
-        # Test that missing arguments fail
-        c = C()
-
-    with pytest.raises(ValidationError):
-        # Test invalid argument types fail
-
-        c = C(a=TabError)
-
-    with pytest.raises(FrozenInstanceError):
-        # Test that attempts at mutating paramclasses fail
-        c = C(a=3)
-        c.a = 4
-
-    with pytest.raises(RuntimeError):
-        # Test "no Module sub-classing"
-
-        class E(h.Module):
-            ...
-
-    with pytest.raises(RuntimeError):
-        # Test "no decorating inherited types"
-
-        @h.module
-        class E2(TabError):
-            ...
-
-    with pytest.raises(RuntimeError):
-        # Test bad parameter names
-
-        @h.paramclass
-        class P:
-            descriptions = h.Param(dtype=str, desc="", default="BAD!")
-
-    with pytest.raises(RuntimeError):
-        # Test bad parameter names
-
-        @h.paramclass
-        class P:
-            defaults = h.Param(dtype=str, desc="", default="BAD!")
-
-    with pytest.raises(RuntimeError):
-        # Test non-params in `@paramclass`
-
-        @h.paramclass
-        class P:
-            something = 11
-
-    with pytest.raises(RuntimeError):
-        # Test a bad argument type
-        h.params._unique_name(33)
-
-
 def test_array1():
     @h.module
     class InArray:
@@ -330,7 +167,7 @@ class InArray:
 
 
 def test_array2():
-    """ Basic Instance-Array Test """
+    """Basic Instance-Array Test"""
     a = h.Module(name="a")
     a.inp = h.Port(width=1)
     a.out = h.Port(width=1)
@@ -353,7 +190,7 @@ class HasArray2:
 
 
 def test_cycle1():
-    """ Test cyclical connection-graphs, i.e. a back-to-back pair of instances """
+    """Test cyclical connection-graphs, i.e. a back-to-back pair of instances"""
 
     @h.module
     class Thing:
@@ -622,7 +459,7 @@ class System:
 
 
 def test_bigger_bundles():
-    """ Test a slightly more elaborate Bundle-based system """
+    """Test a slightly more elaborate Bundle-based system"""
 
     class HostDevice(Enum):
         HOST = auto()
@@ -870,7 +707,7 @@ def test_signal_concat1():
 
 
 def test_slice_module1():
-    # Make use of slicing and concatenation in a module
+    """Make use of slicing and concatenation in a module"""
 
     C = h.Module(name="C")
     C.p1 = h.Port(width=1)
@@ -878,15 +715,15 @@ def test_slice_module1():
     C.p7 = h.Port(width=7)
 
     P = h.Module(name="P")
-    C.s4 = h.Signal(width=4)
-    C.s2 = h.Signal(width=2)
-    P.ic = C(p1=C.s4[0], p4=C.s4, p7=h.Concat(C.s4, C.s2, C.s2[0]))
+    P.s4 = h.Signal(width=4)
+    P.s2 = h.Signal(width=2)
+    P.ic = C(p1=P.s4[0], p4=P.s4, p7=h.Concat(P.s4, P.s2, P.s2[0]))
 
     h.elaborate(P)
 
 
 def test_module_as_param():
-    """ Test using a `Module` as a parameter-value """
+    """Test using a `Module` as a parameter-value"""
 
     @h.paramclass
     class HasModuleParam:
@@ -904,7 +741,7 @@ def UsesModuleParam(params: HasModuleParam) -> h.Module:
 
 
 def test_mos_generator():
-    """ Initial test of built-in series-Mos generator """
+    """Initial test of built-in series-Mos generator"""
 
     Mos = h.generators.Mos
     m = Mos(Mos.Params(nser=2))
@@ -929,7 +766,7 @@ def test_mos_generator():
 
 
 def test_series_parallel_generator():
-    """ Initial test of the general-purpose series-parallel generator """
+    """Initial test of the general-purpose series-parallel generator"""
 
     from hdl21.generators import SeriesPar
 
@@ -962,7 +799,7 @@ class M:  # Unit cell
 
 
 def test_instance_mult():
-    """ Initial tests of instance-array-generation by multiplication """
+    """Initial tests of instance-array-generation by multiplication"""
 
     Child = h.Module(name="Child")
     Parent = h.Module(name="Parent")
@@ -1004,7 +841,7 @@ def test_instance_mult():
 
 
 def test_instance_mult2():
-    """ Test connecting to multiplication-generated InstArrays """
+    """Test connecting to multiplication-generated InstArrays"""
 
     @h.module
     class Child:
@@ -1032,7 +869,7 @@ class Parent:
 
 
 def test_instance_mult3():
-    """ Test connecting to an "already connected" Instance """
+    """Test connecting to an "already connected" Instance"""
 
     @h.module
     class Child:
@@ -1057,7 +894,7 @@ class Parent:
 
 
 def test_instance_mult4():
-    """ Test connecting non-unit-width Arrays """
+    """Test connecting non-unit-width Arrays"""
 
     @h.module
     class Child:
@@ -1087,7 +924,7 @@ class Parent:
 
 
 def test_bad_width_conn():
-    """ Test invalid connection-widths """
+    """Test invalid connection-widths"""
     c = h.Module(name="c")
     c.p = h.Port(width=3)  # Width-3 Port
     q = h.Module(name="q")
@@ -1099,7 +936,7 @@ def test_bad_width_conn():
 
 
 def test_bad_bundle_conn():
-    """ Test invalid Bundle connections """
+    """Test invalid Bundle connections"""
 
     @h.bundle
     class P:
@@ -1123,7 +960,7 @@ class Q:
 
 
 def test_illegal_module_attrs():
-    """ Test attempting to add illegal attributes """
+    """Test attempting to add illegal attributes"""
 
     m = h.Module()
     with pytest.raises(TypeError):
@@ -1164,18 +1001,18 @@ def fail(self):
 
 
 def test_copy_signal():
-    """ Copying a Signal """
+    """Copying a Signal"""
     copy.copy(h.Signal())
 
 
 def test_copy_bundle_instance():
-    """ Copying a BundleInstance """
+    """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 """
+    """Test de-structuring bundles to individual Signals"""
 
     @h.bundle
     class B:
@@ -1203,7 +1040,8 @@ class Parent:
 
 
 def test_orphanage():
-    """ Test that orphaned Module-attributes fail at elaboration """
+    """Test that orphaned Module-attributes fail at elaboration"""
+
     m1 = h.Module(name="m1")
     m1.s = h.Signal()  # Signal `s` is now "parented" by `m1`
 
@@ -1219,7 +1057,7 @@ def test_orphanage():
 
 
 def test_orphanage2():
-    """ Test orphaning a bundle instance """
+    """Test orphaning a bundle instance"""
 
     @h.bundle
     class B:
@@ -1241,7 +1079,7 @@ class B:
 
 
 def test_orphanage3():
-    """ Test orphaning an instance """
+    """Test orphaning an instance"""
     I = h.Module(name="I")
     i = I()  # Instance to be orphaned
 
@@ -1258,9 +1096,28 @@ def test_orphanage3():
         h.elaborate(m1)
 
 
+def test_orphanage4():
+    """Test an orphan Instance connection"""
+
+    m1 = h.Module(name="m1")
+    m1.p = h.Port()
+
+    # The "orphan signal" will not be owned by any module
+    the_orphan_signal = h.Signal()
+    m2 = h.Module(name="m2")
+    m2.i = m1(p=the_orphan_signal)  # <= Problem's here
+
+    # Note elaborating `m1` continues to work
+    h.elaborate(m1)
+
+    with pytest.raises(RuntimeError):
+        # Elaborating `m2` should fail
+        h.elaborate(m2)
+
+
 @pytest.mark.xfail(reason="#6 https://github.com/dan-fritchman/Hdl21/issues/6")
 def test_wrong_decorator():
-    """ Mistake `Module` for `module` """
+    """Mistake `Module` for `module`"""
 
     with pytest.raises(RuntimeError):
 
@@ -1270,7 +1127,7 @@ class M:
 
 
 def test_elab_noconn():
-    """ Initial test of elaborating a `NoConn` """
+    """Initial test of elaborating a `NoConn`"""
 
     @h.module
     class Inner:
@@ -1287,7 +1144,7 @@ class HasNoConn:
 
 
 def test_bad_noconn():
-    """ Test that a doubly-connected `NoConn` should fail """
+    """Test that a doubly-connected `NoConn` should fail"""
 
     @h.module
     class Inner:
@@ -1310,7 +1167,7 @@ class Bad:
 
 
 def test_array_concat_conn():
-    """ Test connecting a `Concat` to an `InstArray` """
+    """Test connecting a `Concat` to an `InstArray`"""
 
     Child = h.Module(name="Child")
     Child.p3 = h.Input(width=3)
@@ -1325,7 +1182,7 @@ def test_array_concat_conn():
 
 
 def test_slice_resolution():
-    """ Test resolutions of slice combinations """
+    """Test resolutions of slice combinations"""
 
     # This is a very private import of the slice-resolver function
     from hdl21.elab.elaborators.slices import _resolve_slice
@@ -1366,8 +1223,8 @@ def test_slice_resolution():
 
 
 def test_common_attr_errors():
-    """ Test that common errors provide helpful feedback. 
-    For example, adding `Module`s where one wants an `Instance`. """
+    """Test that common errors provide helpful feedback.
+    For example, adding `Module`s where one wants an `Instance`."""
 
     M = h.Module(name="M")
 
@@ -1420,7 +1277,7 @@ def G(p: h.HasNoParams) -> h.Module:
 
 
 def test_generator_call_by_kwargs():
-    """ Test the capacity for generators to create their param-classes inline. """
+    """Test the capacity for generators to create their param-classes inline."""
 
     @h.paramclass
     class P:
@@ -1447,10 +1304,14 @@ def M(p: P) -> h.Module:
 
 
 def test_instance_array_portrefs():
-    """ Test Instance Arrays connected by port-references """
+    """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")
@@ -1468,7 +1329,7 @@ def test_instance_array_portrefs():
 
 
 def test_array_bundle():
-    """ Test bundle-valued connections to instance arrays. """
+    """Test bundle-valued connections to instance arrays."""
 
     @h.bundle
     class B:
@@ -1495,7 +1356,7 @@ class HasArr:
 
 @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 """
+    """Test connecting via BundleRef to a sub-Bundle"""
 
     @h.bundle
     class B1:
@@ -1518,7 +1379,7 @@ class HasB2:
 
 
 def test_anon_bundle_port_conn():
-    """ Test connecting via PortRef to an AnonymousBundle """
+    """Test connecting via PortRef to an AnonymousBundle"""
 
     @h.bundle
     class B:
@@ -1550,10 +1411,10 @@ class Top:
 
 
 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. 
-    Based on the design of `ResolvePortRefs`, this is a thing that (we think) 
-    is not possible. """
+    """Test, or at least try to test, jamming more than one Signal
+    into the `group` concept used by the `ResolvePortRefs` elaborator pass.
+    Based on the design of `ResolvePortRefs`, this is a thing that (we think)
+    is not possible."""
 
     @h.module
     class I:
@@ -1582,7 +1443,7 @@ class M:
 
 
 def test_bad_conns():
-    """ Test the errors produced by elaborating with invalid Port connections """
+    """Test the errors produced by elaborating with invalid Port connections"""
 
     @h.module
     class I:
@@ -1626,7 +1487,7 @@ class O:
 
 
 def test_anon_bundle_refs():
-    """ Test adding `BundleRef`s to `AnonymousBundle`s. """
+    """Test adding `BundleRef`s to `AnonymousBundle`s."""
 
     @h.bundle
     class Diff:
@@ -1652,3 +1513,105 @@ class HasHasDiff:
 
     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")
+
+    @h.generator
+    def SliceMyP(params: Width) -> h.Module:
+        m = h.Module()
+        m.p = h.Port(width=params.width)  # Port with parametrized width
+        return m
+
+    @h.module
+    class ScalarPort:
+        p = h.Port()  # A width-one port
+
+    @h.module
+    class HasGen:
+        g = SliceMyP(width=8)()
+        # Connect the MSB of `g.p` to `s.p`
+        s = ScalarPort(p=g.p[-1])
+
+    h.elaborate(HasGen)
+
+
+def test_pair1():
+    """Test of the `Diff` and `Pair` signal and instance bundles"""
+
+    @h.module
+    class I:
+        ps = h.Port(desc="Port we'll connect to a scalar")
+        pd = h.Port(desc="Port we'll connect to a diff")
+        pa = h.Port(desc="Port we'll connect to an AnonymousBundle")
+
+    @h.module
+    class O:
+        d = h.Diff()
+        s = h.Signal()
+        pair = h.Pair(I)(pd=d, ps=s, pa=h.inverse(d))
+
+    h.elaborate(O)
+
+    assert len(O.instances) == 2
+    assert "pair_p" in O.instances
+    assert "pair_n" in O.instances
+    assert O.pair_p.conns["ps"] is O.s
+    assert O.pair_n.conns["ps"] is O.s
+    assert O.pair_p.conns["pd"] is O.d_p
+    assert O.pair_n.conns["pd"] is O.d_n
+    assert O.pair_p.conns["pa"] is O.d_n
+    assert O.pair_n.conns["pa"] is O.d_p
+    assert len(O.signals) == 3
+    assert len(O.bundles) == 0
+    assert len(O.instbundles) == 0
+
+
+@pytest.mark.xfail(reason="#33 https://github.com/dan-fritchman/Hdl21/issues/33")
+def test_noconn_types():
+    """Test connecting `NoConn`s to a variety of port-types."""
+
+    @h.module
+    class Inner:
+        # Inner module with a handful of port-types
+        s = h.Port()
+        b = h.Port(width=11)
+        d = h.Diff(port=True)
+
+    @h.module
+    class Outer:
+        i = Inner(s=h.NoConn(), b=h.NoConn(), d=h.NoConn())
+
+    h.elaborate(Outer)
+
+
+def test_deep_hierarchy():
+    """Test a deep hierarchy, without much content."""
+
+    # Create the leaf level module. Empty with a single Port `p`.
+    M0 = h.Module(name=f"M0")
+    M0.p = h.Port()
+    prev = M0
+    M1 = None
+
+    # Create lots of layers above it, each of which instantiates the previous.
+    for k in range(100):
+        m = h.Module(name=f"M{k}")
+        m.p = h.Port()
+        m.i = h.Instance(of=prev)(p=m.p)
+        if M1 is None:
+            M1 = m
+        prev = m
+
+    # Elaborate the final, highest-level module.
+    h.elaborate(m)
+
+    # Screw up a connection, and check we get a RuntimeError.
+    M1.i.not_a_real_port = M1.p
+    with pytest.raises(RuntimeError):
+        h.elaborate(m)
diff --git a/hdl21/tests/test_params.py b/hdl21/tests/test_params.py
new file mode 100644
index 0000000..63bb359
--- /dev/null
+++ b/hdl21/tests/test_params.py
@@ -0,0 +1,218 @@
+""" 
+# Hdl21 Parameters 
+# Unit Tests 
+"""
+
+import copy, pytest
+from dataclasses import field
+from enum import Enum, EnumMeta, auto
+
+from pydantic import ValidationError
+from pydantic.dataclasses import dataclass
+
+# Import the PUT (package under test)
+import hdl21 as h
+
+
+def test_params1():
+    # Initial param-class test
+
+    @h.paramclass
+    class MyParams:
+        a = h.Param(dtype=int, default=5, desc="your fave")
+
+    assert h.isparamclass(MyParams)
+
+    m = MyParams()
+    assert isinstance(m.a, int)
+    assert m.a == 5
+    assert isinstance(m.__params__["a"], h.Param)
+    assert m.defaults() == dict(a=5)
+    assert m.descriptions() == dict(a="your fave")
+
+
+def test_params2():
+    @h.paramclass
+    class Pc2:
+        # Required
+        r = h.Param(dtype=str, desc="required")
+        # Optional
+        o = h.Param(dtype=str, default="hmmm", desc="optional")
+
+    assert h.isparamclass(Pc2)
+    assert Pc2.defaults() == dict(o="hmmm")
+    assert Pc2.descriptions() == dict(r="required", o="optional")
+
+    p = Pc2(r="provided")
+    assert isinstance(p.r, str)
+    assert p.r == "provided"
+    assert isinstance(p.o, str)
+    assert p.o == "hmmm"
+    assert p.defaults() == dict(o="hmmm")
+    assert p.descriptions() == dict(r="required", o="optional")
+
+
+def test_params3():
+    # Test parameters with a creation-time list-tuple conversion
+    @h.paramclass
+    class HasTuple:
+        t = h.Param(dtype=tuple, desc="Go ahead, try a list")
+
+    ht = HasTuple(t=[1, 2, 3])
+    assert isinstance(ht.t, tuple)
+    assert ht.t == (1, 2, 3)
+
+
+def test_params4():
+    # Test some param-class nesting
+    from dataclasses import asdict
+
+    @h.paramclass
+    class Inner:
+        i = h.Param(dtype=int, desc="Inner int-field")
+
+    @h.paramclass
+    class Outer:
+        inner = h.Param(dtype=Inner, desc="Inner fields")
+        f = h.Param(dtype=float, desc="A float", default=3.14159)
+
+    o = Outer(inner=Inner(11))
+    assert isinstance(o, Outer)
+    assert isinstance(o.inner, Inner)
+    assert o.inner == Inner(11)
+    assert o.inner.i == 11
+    assert o.f == 3.14159
+
+    # Create from a (nested) dictionary
+    d1 = {"inner": {"i": 11}, "f": 22.2}
+    o1 = Outer(**d1)
+    uname1 = h.params._unique_name(o1)
+    # Convert back to another dictionary
+    d2 = asdict(o1)
+    # And check they line up
+    assert d1 == d2
+    # Round-trip back to an `Outer`
+    o2 = Outer(**d2)
+    uname2 = h.params._unique_name(o2)
+    assert uname1 == uname2
+    assert uname1 == "3dcc309796996b3a8a61db66631c5a93"
+
+
+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
+
+        @paramclass
+        class C(TabError):  # Of course this is a sub-class of the best built-in class
+            ...
+
+    @paramclass
+    class C:
+        a = Param(dtype=int, desc="Gonna Fail!")
+
+    with pytest.raises(RuntimeError):
+        # Test that sub-classing a paramclass fails
+
+        class D(C):
+            ...
+
+    with pytest.raises(TypeError):
+        # Test that missing arguments fail
+        c = C()
+
+    with pytest.raises(ValidationError):
+        # Test invalid argument types fail
+
+        c = C(a=TabError)
+
+    with pytest.raises(FrozenInstanceError):
+        # Test that attempts at mutating paramclasses fail
+        c = C(a=3)
+        c.a = 4
+
+    with pytest.raises(RuntimeError):
+        # Test "no Module sub-classing"
+
+        class E(h.Module):
+            ...
+
+    with pytest.raises(RuntimeError):
+        # Test "no decorating inherited types"
+
+        @h.module
+        class E2(TabError):
+            ...
+
+    with pytest.raises(RuntimeError):
+        # Test bad parameter names
+
+        @h.paramclass
+        class P:
+            descriptions = h.Param(dtype=str, desc="", default="BAD!")
+
+    with pytest.raises(RuntimeError):
+        # Test bad parameter names
+
+        @h.paramclass
+        class P:
+            defaults = h.Param(dtype=str, desc="", default="BAD!")
+
+    with pytest.raises(RuntimeError):
+        # Test non-params in `@paramclass`
+
+        @h.paramclass
+        class P:
+            something = 11
+
+    with pytest.raises(RuntimeError):
+        # Test a bad argument type
+        h.params._unique_name(33)
+
+
+@pytest.mark.xfail(reason="#30 https://github.com/dan-fritchman/Hdl21/issues/30")
+def test_param_default_factory():
+    """Test the `default_factory` feature of `Param`"""
+
+    # Test the pydantic versions without `paramclass` first
+
+    @dataclass
+    class NoFactory:
+        i: int = 11
+
+    @dataclass
+    class HasFactory:
+        a: int = field(default_factory=lambda: 5)
+        l: list = field(default_factory=list)
+        no: NoFactory = field(default_factory=NoFactory)
+
+    has = HasFactory()
+
+    assert has.a == 5
+    assert has.l == []
+    assert has.no == NoFactory(i=11)
+
+    # And test `paramclass`es
+
+    @h.paramclass
+    class NoFactory:
+        i = h.Param(dtype=int, desc="i", default=11)
+
+    @h.paramclass
+    class HasFactory:
+        a = h.Param(dtype=int, desc="a", default_factory=lambda: 5)
+        l = h.Param(dtype=list, desc="l", default_factory=list)
+        no = h.Param(dtype=NoFactory, desc="no", default_factory=NoFactory)
+
+    has = HasFactory()
+
+    assert has.a == 5
+    assert has.l == []
+    assert has.no == NoFactory(i=11)
diff --git a/hdl21/tests/test_prefix.py b/hdl21/tests/test_prefix.py
index 3980c53..2a20119 100644
--- a/hdl21/tests/test_prefix.py
+++ b/hdl21/tests/test_prefix.py
@@ -3,13 +3,13 @@
 
 
 def test_decimal():
-    """ This isnt a test of Hdl21 so much as a demo and reminder of how 
-    the standard library's `Decimal` works, particularly in conjunction with 
-    our widely-used `pydantic.dataclasses`. 
+    """This isnt a test of Hdl21 so much as a demo and reminder of how
+    the standard library's `Decimal` works, particularly in conjunction with
+    our widely-used `pydantic.dataclasses`.
 
     In short: `pydantic` converts `Decimal` valued fields to strings before
-    passing them to the `Decimal` constructor. 
-    https://pydantic-docs.helpmanual.io/usage/types/ """
+    passing them to the `Decimal` constructor.
+    https://pydantic-docs.helpmanual.io/usage/types/"""
 
     # Decimal-only
     assert Decimal(11) == Decimal(11)
@@ -33,7 +33,7 @@ class HasDecimal:
 
 
 def test_prefix_numbers():
-    """ Check that we get the numeric types we expect in each `Prefixed` """
+    """Check that we get the numeric types we expect in each `Prefixed`"""
 
     x = h.Prefixed(11, h.Prefix.FEMTO)
     assert isinstance(x.number, Decimal)
@@ -62,7 +62,7 @@ def test_prefix_shortname():
 
 
 def test_prefix_from_exp():
-    """ Test creating `Prefix` from an integer exponent. """
+    """Test creating `Prefix` from an integer exponent."""
 
     assert h.Prefix.from_exp(-24) == h.Prefix.YOCTO
     assert h.Prefix.from_exp(-21) == h.Prefix.ZEPTO
@@ -92,7 +92,7 @@ def test_prefix_from_exp():
 
 
 def test_prefix_mul():
-    """ Test `Prefix` multiplication. """
+    """Test `Prefix` multiplication."""
     from hdl21.prefix import µ
 
     assert 5 * µ == 5 * h.Prefix.MICRO
@@ -100,7 +100,7 @@ def test_prefix_mul():
 
 
 def test_e():
-    """ Test the `e` shorthand notation for exponential creation """
+    """Test the `e` shorthand notation for exponential creation"""
     from hdl21.prefix import e
 
     assert e(-24) == h.Prefix.YOCTO
@@ -126,14 +126,14 @@ def test_e():
 
 
 def test_e_mult():
-    """ Test multiplying by the `e` function results, e.g. `11 * e(-9) """
+    """Test multiplying by the `e` function results, e.g. `11 * e(-9)"""
     from hdl21.prefix import e
 
     assert 11 * e(-9) == h.Prefixed(11, h.Prefix.NANO)
 
 
 def test_prefix_scaling():
-    """ Test cases of `Prefixed` multiplication which do not land on other `Prefix`es, and require scaling """
+    """Test cases of `Prefixed` multiplication which do not land on other `Prefix`es, and require scaling"""
     from hdl21.prefix import e
 
     # 1e-11, scaled to 10e-12
@@ -144,3 +144,11 @@ def test_prefix_scaling():
 
     # 11.11e14, scaled to 1111e12
     assert 11.11 * e(15) * h.Prefix.DECI == 1111 * e(12)
+
+
+def test_prefix_conversion():
+    """Test types that can be converted to `Prefixed`'s internal `Decimal`."""
+
+    h.Prefixed(number="11.11", prefix=h.Prefix.YOCTO)
+    h.Prefixed(number=11.11, prefix=h.Prefix.YOCTO)
+    h.Prefixed(number=11, prefix=h.Prefix.YOCTO)
diff --git a/pdks/Sky130/sky130/test_sky130.py b/pdks/Sky130/sky130/test_sky130.py
index 0e079e2..7199034 100644
--- a/pdks/Sky130/sky130/test_sky130.py
+++ b/pdks/Sky130/sky130/test_sky130.py
@@ -35,29 +35,23 @@ def test_compile():
     hasmos = mosmodule()
 
     # Compile it for the PDK
-    pkg = h.to_proto(hasmos)
-    pdk_pkg = sky130.compile(pkg)
-
-    # Import it back into Modules & Namespaces
-    ns = h.from_proto(pdk_pkg)
-    rt = ns.sky130.test_sky130.hasmos
+    sky130.compile(hasmos)
 
     # And check what came back
-    assert isinstance(rt.n, h.Instance)
-    assert isinstance(rt.p, h.Instance)
-    assert isinstance(rt.n.of, h.ExternalModuleCall)
-    assert isinstance(rt.p.of, h.ExternalModuleCall)
-    assert isinstance(rt.n.of.params, dict)
-    assert isinstance(rt.p.of.params, dict)
+    assert isinstance(hasmos.n, h.Instance)
+    assert isinstance(hasmos.p, h.Instance)
+    assert isinstance(hasmos.n.of, h.ExternalModuleCall)
+    assert isinstance(hasmos.p.of, h.ExternalModuleCall)
+    assert isinstance(hasmos.n.of.params, sky130.Sky130MosParams)
+    assert isinstance(hasmos.p.of.params, sky130.Sky130MosParams)
 
 
 def test_netlist():
     hasmos = mosmodule()
 
     # Netlist it for the PDK
-    pkg = h.to_proto(hasmos)
-    pkg = sky130.compile(pkg)
-    h.netlist(pkg, StringIO(), "spectre")
+    sky130.compile(hasmos)
+    h.netlist(hasmos, StringIO(), fmt="spectre")
 
 
 def test_module1():