import math
from typing import Sequence
from dataclasses import dataclass, field
from jitx.via import Via
from jitx.transform import Transform
from jitx.net import Net, Port, DiffPair, PortAttachment
from jitx.circuit import Circuit
from jitx.component import Component
from jitx.feature import KeepOut, Custom
from jitx.layerindex import LayerSet
from jitx.landpattern import Landpattern, Pad, PadMapping
from jitx.symbol import Symbol, Pin, SymbolMapping
from jitx.shapes import Shape
from jitx.shapes.primitive import Empty, Circle
from jitx._structural import Container
[docs]
@dataclass
class ViaGroundCage:
"""Base class for defining the ground cage for a Via Structure"""
via_def: type[Via]
""" Via Definition for the ground cage vias. This definition will be used to
instantiate each of the needed vias.
"""
[docs]
def place_via_cage(self, n: Net):
"""User is expected to override this method and generate the
via instances needed for the cage.
Args:
n: Net to which the vias of the cage will be connected. This would
typically be ground in most applications.
"""
raise NotImplementedError("Missing 'place_via_cage' method for ViaGroundCage")
[docs]
@dataclass
class PolarViaGroundCage(ViaGroundCage):
"""Polar Via Ground Cage
This type implements a polar-coordinate system for defining the vias of the
ground cage.
"""
via_def: type[Via]
"""Via Definition to use for all of the via placements.
"""
count: int
"""Total number of via placements. Must be positive.
"""
radius: float
"""Radius in mm for the circular pattern of via placements. Must be positive.
"""
theta: float = 0.0
"""Starting angle for the pattern. Value in degrees.
Default value is 0.0 degrees which points to the right along the X axis.
"""
skips: list[int] = field(default_factory=list)
""" Skipped indices in the via pattern. Each value in this collection
must be in the range `[0, count-1]`
"""
pose: Transform = field(default_factory=Transform.identity)
""" Change the location and placement of the ground cage
with respect to the origin of the via structure. The
default value is the `IDENTITY` transformation.
"""
def __post_init__(self):
assert self.count > 0
assert self.radius > 0.0
for i, skip in enumerate(self.skips):
assert skip < self.count, (
f"Skip[{i}]={skip} Not less than count '{self.count}'"
)
[docs]
def place_via_cage(self, n: Net):
def compute_loc(i: int) -> Transform:
phase = 2.0 * math.pi * i / self.count
phase += math.radians(self.theta)
return Transform.rotate(math.degrees(phase)) * Transform.translate(
self.radius, 0.0
)
valid_pos = [x for x in range(self.count) if x not in self.skips]
via_set = []
for i in valid_pos:
tx = self.pose * compute_loc(i)
v = self.via_def().at(tx)
via_set.append(v)
n += v
# Debug Mode
# if debug-mode:
# for i in 0 to count(v) do:
# val pos = pose * compute-pos(v, i)
# layer(debug-layer) = pos * Circle(0.1)
[docs]
class AntiPad(Container):
"""Base class for Anti-Pad constructors"""
[docs]
def place_anti_pad(self):
raise NotImplementedError("Missing 'place_anti_pad' method for Antipad")
[docs]
@dataclass
class SimpleAntiPad(AntiPad):
"""Trivial Anti-Pad Generator
This Anti-Pad type generates `KeepOut` shapes on the passed layers
and then positions them according the the `pose` argument.
"""
shape: Shape
""" KeepOut shape to be applied to all requested layers.
"""
layers: LayerSet
""" Set of layers where keepout will be applied.
"""
pose: Transform = field(default_factory=Transform.identity)
""" Optional transform to apply to the antipads so that they can be
positioned with respect to the via-structure's origin.
This value is `Transform.identity` by default.
"""
[docs]
def place_anti_pad(self):
self.KO = KeepOut(self.pose * self.shape, self.layers, pour=True)
[docs]
class InsertionPoint(Container):
"""Base class for insertion point in the layout.
NOTE: The current tool doesn't support placing actual insertion
points from code. Eventually we would like to support this.
"""
[docs]
def place_insertion_point(self):
raise NotImplementedError(
"Missing 'place_insertion_point' method for Insertion Point Decoration"
)
[docs]
@dataclass
class InsertionPointDecorator(InsertionPoint):
"""Construct a custom layer decoration that indicates where an
insertion point should be place manually by the user. Typically,
the user would drag out from the via so that it will follow as the
via structure moves.
"""
scale: float = 1.0
layerName: str = "insertpt"
pose: Transform = field(default_factory=Transform.identity)
def __post_init__(self):
assert self.scale > 0.0
assert len(self.layerName) > 0
[docs]
def place_insertion_point(self):
# sh = bullseye([0.15, 0.25], 0.1)
sh = Circle(radius=0.15)
self.insertPt = Custom(self.pose * sh, name=self.layerName)
[docs]
class TopoSymbol(Symbol):
"""Dummy Symbol for the via structure `TopoPin` component."""
p = Pin(at=(0, 0), length=3)
def __init__(self):
self.body = Circle(diameter=1.0)
[docs]
class TopoPad(Pad):
"""Dummy Pad for the `TopoLP`.
This pad is an empty shape and only serves to be
a location for identifying the `TopoPin`'s port
in the physical design.
"""
def __init__(self):
self.shape = Empty()
[docs]
class TopoLP(Landpattern):
"""Dummy Landpattern for the `TopoPin` construct
for creating via structures.
"""
def __init__(self):
self.p = TopoPad().at(0.0, 0.0)
[docs]
class TopoPin(Component):
"""Define a component for holding the via structrure's pin definition.
For a single-ended via, one of these instances is created.
For a diff-pair via, two are created.
"""
p = Port()
def __init__(self):
self.symb = TopoSymbol()
self.lp = TopoLP()
self.cmappings = [
SymbolMapping({self.p: self.symb.p}),
PadMapping({self.p: self.lp.p}),
]
[docs]
class ViaStructure(Circuit):
"""Base class for ViaStructure definitions"""
def __init__(
self,
ground_cages: Sequence[ViaGroundCage],
antipads: Sequence[AntiPad],
insertion_points: Sequence[InsertionPoint],
):
"""Constructor for base class
Args:
ground_cages - Set of zero or more ground cage structures.
antipads - Set of zero or more antipad definitions to apply to the via structure.
insertion_points - Set of zero or more insertion point locators.
"""
# via structures are floating, they vias are in a fixed position inside
self.at(floating=True)
self.ground_cages = ground_cages
self.antipads = antipads
self.insertion_points = insertion_points
[docs]
def generate_common_structures(self, common: Net):
for gndCage in self.ground_cages:
gndCage.place_via_cage(common)
for antipad in self.antipads:
antipad.place_anti_pad()
for insert in self.insertion_points:
insert.place_insertion_point()
[docs]
@staticmethod
def create_std_insertion_points(radius: float):
assert radius > 0.0
offset = Transform.translate(0, radius)
return [
InsertionPointDecorator(pose=offset),
InsertionPointDecorator(pose=Transform.rotate(180.0) * offset),
]
[docs]
class SingleViaStructure(ViaStructure):
"""Single-Ended Signal Via structure
This object constructs a Circuit definition that can generate
via structure instances for single-ended signals (ie single `Port` nets).
User must instantiate an via structure instance and net/topo it in the
circuit like a normal component.
"""
sig_in = Port()
sig_out = Port()
COMMON = Port()
def __init__(
self,
via_def: type[Via],
*,
ground_cages: Sequence[ViaGroundCage],
antipads: Sequence[AntiPad],
insertion_points: Sequence[InsertionPoint],
):
"""Construct a single-ended via structure instance
Args:
via_def: Via Type that will be instantiated to construct
the signal via for the structure.
ground_cages - Set of zero or more ground cage structures.
antipads - Set of zero or more antipad definitions to apply to the via structure.
insertion_points - Set of zero or more insertion point locators.
"""
super().__init__(ground_cages, antipads, insertion_points)
self.GND = Net(name="COMMON_n")
self.GND += self.COMMON
self.sigComp = TopoPin().at(0.0, 0.0)
self.TOPO = self.sig_in >> self.sigComp.p >> self.sig_out
self.attached = PortAttachment(self.sigComp.p, via_def().at(0.0, 0.0))
self.generate_common_structures(self.GND)
[docs]
class DifferentialViaStructure(ViaStructure):
"""Differential Pair Via Structure
This object constructs a Circuit definition that can generate a
via structure instance for support a `DiffPair` port net.
User must instantiate an instance of this via structure type and
net/topo it into the circuit like a normal component instance.
"""
sig_in = DiffPair()
sig_out = DiffPair()
COMMON = Port()
def __init__(
self,
via_defs: type[Via] | tuple[type[Via], type[Via]],
pitch: float,
*,
ground_cages: Sequence[ViaGroundCage],
antipads: Sequence[AntiPad],
insertion_points: Sequence[InsertionPoint],
):
"""Construct a new Differential Via Structure instance.
Args:
via_defs: Via Type that will be instantiated to construct
the signal vias for the structure. If this value is a tuple
of 2 Via definitions, then we will use separate via definitions
for the P and N signals, respectively.
pitch: Distance between the P and N signal vias in mm.
ground_cages - Set of zero or more ground cage structures.
antipads - Set of zero or more antipad definitions to apply to the via structure.
insertion_points - Set of zero or more insertion point locators.
"""
super().__init__(ground_cages, antipads, insertion_points)
if not isinstance(via_defs, tuple):
via_defs = (via_defs, via_defs)
assert len(via_defs) == 2
assert pitch > 0
self.GND = Net(name="COMMON_n")
self.GND += self.COMMON
self.P_comp = TopoPin().at(pitch / 2, 0.0)
self.N_comp = TopoPin().at(-pitch / 2, 0.0)
self.TOPO_P = self.sig_in.p >> self.P_comp.p >> self.sig_out.p
self.TOPO_N = self.sig_in.n >> self.N_comp.p >> self.sig_out.n
self.ATTACH_P = PortAttachment(self.P_comp.p, via_defs[0]().at(pitch / 2, 0.0))
self.ATTACH_N = PortAttachment(self.N_comp.p, via_defs[1]().at(-pitch / 2, 0.0))
self.generate_common_structures(self.GND)