from __future__ import annotations
from collections.abc import Sequence
from dataclasses import replace
from typing import overload
from jitx._structural import Ref, dispose
from jitx.copper import Copper
from jitx.decorators import identityclass
from jitx.inspect import Trace as _Trace
from jitx.query import transformer
from .placement import Positionable
from .shapes import Shape
from .net import (
CoupledRouteConnectionEndpoint,
Port,
DiffPair,
RouteConnectionEndpoint,
TopologyNet,
UncoupledConnectionEndpoint,
)
[docs]
class ControlPoint[T: Port](Positionable, Ref):
"""Base class for control points. Do not subclass or use this directly,
use one of :py:class:`RoutePoint`, :py:class:`PairInsertion`
or :py:class:`PairPoint` instead."""
layer: int
"""The layer on which the control point should be placed."""
port: T
"""The port associated with this route point used for netting."""
[docs]
@identityclass
class Trace:
"""Computed output shape(s) of a control point. While a RoutePoint
would likely have one shape, PairInsertion and PairPoint would have
multiple."""
shapes: Sequence[Shape]
traces: Sequence[Trace] | None = None
"""Traces generated for this control point. Each entry holds the copper
shapes belonging to one net (carried as a :class:`ComputedNet` property
on the Trace). This will only be available once the design has been evaluated
and captured by the runtime."""
def __init__(self, *, layer: int, bundle: type[T]):
self.layer = layer
self.port = bundle()
def __rshift__(self, other: Port | ControlPoint | TopologyNet) -> TopologyNet:
if isinstance(other, TopologyNet):
dispose(other)
return TopologyNet((self,) + other.sequence)
elif isinstance(other, Port | ControlPoint):
return TopologyNet(self, other)
return NotImplemented
[docs]
class RoutePoint[T: Port](ControlPoint):
shape: Shape | None
"""The geometric shape of the control point."""
pad: RouteConnectionEndpoint[T]
"""The route connection end point associated to this control point,
used for routing."""
@overload
def __init__(self, *, layer: int, shape: Shape | None = None, bundle: type[T]): ...
@overload
def __init__(self: RoutePoint[Port], *, layer: int, shape: Shape | None = None): ...
def __init__(self, *, layer: int, shape: Shape | None = None, bundle: type = Port):
super().__init__(layer=layer, bundle=bundle)
self.shape = shape
self.pad = RouteConnectionEndpoint(bundle)
[docs]
class PairInsertion[T: DiffPair](ControlPoint[T]):
"""Differential pair insertion point. Transitions two individual, uncoupled
traces into a differential pair.
Pair insertion points can also be netted directly with other objects using
:py:attr:`~jitx.net.Net` or :py:attr:`~jitx.net.TopologyNet`.
The coupled end of pair insertion can connect to the
:py:attr:`~PairPoint.back` side of a pair point, and an inverted insertion
can connect to the :py:attr:`~PairPoint.front` side of an pair point. The
relationships are reversed if the pair point is inverted. Similarly an
insertion point can only connect to an inverted insertion point. The diagram
below shows why inverting one of the insertions is necessary.
Note that specifying invert does _not_ imply that insertion point is
rotated, to have insertion points face each other, one must be rotated 180
degrees.
The default ``p`` and ``n`` sides are as shown below:
.. code-block:: text
uncoupled.p -- -- uncoupled.p
\\ /
== coupled == (inverted, rotated 180 degrees)
/ \\
uncoupled.n -- -- uncoupled.n
This can be mirrored, (which, for example, would be needed to make a
geometric connection possible on the other end, where the diff-pair route
terminates in a second insertion point) by setting :py:attr:`~.invert=True`.
When used with :py:attr:`~jitx.net.PortAttachment`, it needs an ordered pair
of ports. The first port is connected to the ``p`` side and the second to
the ``n`` side of the control point. Using PortAttachments for this should
not be necessary for the vast majority of cases.
>>> class MyCircuit(Circuit):
... c1 = MyComponent1()
... c2 = MyComponent2()
... def __init__(self):
... self.insertion1 = PairInsertion(layer=0).at(-2, 0)
... self.insertion2 = PairInsertion(layer=0, invert=True).at(2, 0, rotate=180)
... self.nets = [
... Net([self.c1.p1, self.c2.p1]),
... Net([self.c1.p2, self.c2.p2]),
... ]
... self.attachments = [
... PortAttachment([self.c1.p1, self.c1.p2], self.insertion1),
... PortAttachment([self.c2.p1, self.c2.p2], self.insertion2),
... ]
... self.routes = [
... Route(self.insertion1.coupled, self.insertion2.coupled, 0)
... ]
"""
port: T
"""The differential pair port associated to this control point, used for connections."""
coupled: CoupledRouteConnectionEndpoint[T]
"Coupled-side pads of the differential insertion point, used for connections and routing."
uncoupled: UncoupledConnectionEndpoint[T]
"Uncoupled-side pads of the differential insertion point, used for connections and routing."
invert: bool
"""Mirror the chirality of the insertion point."""
@overload
def __init__(self, *, layer: int, bundle: type[T], invert: bool = False): ...
@overload
def __init__(
self: PairInsertion[DiffPair], *, layer: int, invert: bool = False
): ...
def __init__(self, *, layer: int, bundle: type = DiffPair, invert: bool = False):
super().__init__(layer=layer, bundle=bundle)
self.coupled = CoupledRouteConnectionEndpoint(bundle)
self.uncoupled = UncoupledConnectionEndpoint(bundle)
self.invert = invert
[docs]
class PairPoint[T: DiffPair](ControlPoint[T]):
"""A differential pair control point connects two segments of a
differential pair, while still paired, allowing for each segment to be
configured independently.
A pair point can connect to another to other pairs, or insertion points. A
chain of pair points would connect front -> back, as long as neither or both
are inverted. To connect an inverted to non-inverted point you'd connect
front to front or back to back. See the diagram in :py:class:`PairInsertion`
for details.
"""
port: T
"""The differential pair port associated to this control point, used for connections."""
front: CoupledRouteConnectionEndpoint[T]
"Front-side pads of the differential insertion point, used for routing."
back: CoupledRouteConnectionEndpoint[T]
"Back-side pads of the differential insertion point, used for routing."
invert: bool
"""Mirror the chirality of the pair point."""
@overload
def __init__(self, *, layer: int, bundle: type[T], invert: bool = False): ...
@overload
def __init__(self: PairPoint[DiffPair], *, layer: int, invert: bool = False): ...
def __init__(self, *, layer: int, bundle: type = DiffPair, invert: bool = False):
super().__init__(layer=layer, bundle=bundle)
self.front = CoupledRouteConnectionEndpoint(bundle)
self.back = CoupledRouteConnectionEndpoint(bundle)
self.invert = invert
@transformer(ControlPoint, Copper)
def _control_point_to_copper(trace: _Trace, control_point: ControlPoint):
from jitx._translate.lookup import ComputedNet
if not trace.transform or not control_point.transform:
return
trace = replace(trace, transform=trace.transform * control_point.transform)
if control_point.traces:
for cpt in control_point.traces:
cnet = ComputedNet.get(cpt) or ComputedNet(None)
for shape in cpt.shapes:
yield trace, cnet.assign(Copper(shape, control_point.layer))