Porting from Ipkiss 3.6 to Ipkiss 3.7¶
Porting CircuitCell (Luceda Academy) to IPKISS 3.7¶
Luceda Academy contained CircuitCell, a class to facilitate
defining circuits based on other cells and connectors. It also contained a few workarounds, e.g. for exposing
electrical ports.
Academy’s CircuitCell has now been deprecated in favor of the new i3.Circuit functionality.
The API has been changed from CircuitCell in order to obtain a consistent experience across IPKISS.
The following changes need to be applied to your code:
Remove the import
from circuit.all import CircuitCell.Rename
CircuitCelltoi3.Circuit.Rename
child_cellstoinsts(has been renamed for consistency with IPKISS).Bring the
connectorsandplace_specstogether in 1 argumentspecs.Port the connectors to those in ipkiss3 where applicable (see above).
Rename external_port_names to exposed_ports.
See examples below.
Porting simple cells¶
If you simply called CircuitCell to create a basic (non-parametric) circuit as follows:
# 1. We define the child cells of our circuit.
child_cells = {
"ybranch": splitter,
"fgc_1": fgc,
"fgc_2": fgc,
"fgc_3": fgc
}
# 2. We define the joins (list of tuples), which contain all the ports to be snapped to each other.
joins = [
("fgc_1:opt1", "ybranch:opt1"),
("ybranch:opt2", "fgc_2:opt1"),
("ybranch:opt3", "fgc_3:opt1"),
]
# 3. We define specs, containing all the transformations that apply to each component.
place_specs = [
i3.Place("ybranch:opt1", (0, 0)),
i3.FlipH("fgc_2"),
i3.FlipH("fgc_3")
]
# 4. We define the names of the external ports that we want to access.
external_port_names = {
"fgc_1:fiber": "in",
"fgc_2:fiber": "out1",
"fgc_3:fiber": "out2"
}
# 5. We instantiate the CircuitCell class to create the circuit.
splitter_test = CircuitCell(
name="splitter_test",
child_cells=child_cells,
joins=joins,
place_specs=place_specs,
external_port_names=external_port_names
)
Then this becomes:
# 1. We instantiate the Circuit class to create the circuit
splitter_test = i3.Circuit(
# 2. We define the instances in our circuit.
insts={
"ybranch": splitter,
"fgc_1": fgc,
"fgc_2": fgc,
"fgc_3": fgc
}
# 3. We define the placement & routing specs
specs=[
i3.Place("ybranch:opt1", (0, 0)),
i3.Join([
("fgc_1:opt1", "ybranch:opt1"),
("ybranch:opt2", "fgc_2:opt1"),
("ybranch:opt3", "fgc_3:opt1")
])
]
# 4. We define the names of the ports we want to expose externally
exposed_ports={
"fgc_1:fiber": "in",
"fgc_2:fiber": "out1",
"fgc_3:fiber": "out2"
}
)
Porting parametric cells (inheritance)¶
If you inherited from CircuitCell to define a parametric circuit as follows:
class SplitterTree2Levels(CircuitCell):
# 1. We define the properties of the PCell.
splitter = i3.ChildCellProperty()
spacing_x = i3.PositiveNumberProperty(default=100.0)
spacing_y = i3.PositiveNumberProperty(default=50.0)
def _default_splitter(self):
return pdk.MMI1x2Optimized()
# 2. We define the child cells of our circuit.
def _default_child_cells(self):
return {
"sp_0_0": self.splitter,
"sp_1_0": self.splitter,
"sp_1_1": self.splitter,
}
# 3. We define connectors (list of tuples): ports to be connected + algorithm to connect them (here a Bezier s-bend).
def _default_connectors(self):
return [
("sp_0_0:out1", "sp_1_0:in1", bezier_sbend, {"adiabatic_angle": 1.0}),
("sp_0_0:out2", "sp_1_1:in1", bezier_sbend),
]
# 4. We define placement specs
def _default_place_specs(self):
return [
i3.Place("sp_0_0:in1", (0, 0)),
i3.PlaceRelative("sp_1_0:in1", "sp_0_0:out1", (self.spacing_x, -self.spacing_y / 2)),
i3.PlaceRelative("sp_1_1:in1", "sp_0_0:out2", (self.spacing_x, self.spacing_y / 2)),
]
# 5. We define the names of the external ports.
def _default_external_port_names(self):
return {
"sp_0_0:in1": "in",
"sp_1_0:out1": "out1",
"sp_1_0:out2": "out2",
"sp_1_1:out1": "out3",
"sp_1_1:out2": "out4",
}
Then this becomes now:
class SplitterTree2Levels(i3.Circuit):
# 1. We define the properties of the PCell.
splitter = i3.ChildCellProperty()
spacing_x = i3.PositiveNumberProperty(default=100.0)
spacing_y = i3.PositiveNumberProperty(default=50.0)
def _default_splitter(self):
return pdk.MMI1x2Optimized()
# 2. We define the instances in our circuit.
def _default_insts(self):
return {
"sp_0_0": self.splitter,
"sp_1_0": self.splitter,
"sp_1_1": self.splitter,
}
# 3. We define the placement and routing specifications
def _default_specs(self):
bezier_rounding_1 = i3.SplineRoundingAlgorithm(adiabatic_angles=(1.0, 1.0))
bezier_rounding_default = i3.SplineRoundingAlgorithm()
return [
i3.Place("sp_0_0:in1", (0, 0)),
i3.PlaceRelative("sp_1_0:in1", "sp_0_0:out1", (self.spacing_x, -self.spacing_y / 2)),
i3.PlaceRelative("sp_1_1:in1", "sp_0_0:out2", (self.spacing_x, self.spacing_y / 2)),
i3.ConnectBend("sp_0_0:out1", "sp_1_0:in1", rounding_algorithm=bezier_rounding1),
i3.ConnectBend("sp_0_0:out2", "sp_1_1:in1", rounding_algorithm=bezier_rounding_default),
]
# 4. We define the names of the external ports.
def _default_exposed_ports(self):
return {
"sp_0_0:in1": "in",
"sp_1_0:out1": "out1",
"sp_1_0:out2": "out2",
"sp_1_1:out1": "out3",
"sp_1_1:out2": "out4",
}
Porting PlaceComponents/PlaceAndAutoRoute¶
i3.PlaceComponents and PlaceAndAutoRoute have been deprecated in favor of the more versatile i3.place_and_route and its convenience class i3.Circuit
Replacing your circuits that use PlaceComponents is a matter of renaming the parameters and using placement specs instead of child_transformations.
Example porting PlaceComponents:
from technologies import silicon_photonics
from ipkiss3 import all as i3
from picazzo3.filters.ring import RingRect180DropFilter, RingRectNotchFilter
from picazzo3.wg.splitters import WgY90Splitter
from picazzo3.routing.place_route import PlaceComponents
ring1 = RingRectNotchFilter()
ring2 = RingRect180DropFilter()
splitter = WgY90Splitter()
pc = PlaceComponents(child_cells={"spl": splitter,
"com": splitter,
"arm1": ring1,
"arm2": ring2},
)
layout = pc.Layout(child_transformations={"arm1": (30, -30),
"arm2": (30, 30),
"com": i3.HMirror(0.0)+i3.Translation((60, 0))}
)
layout.visualize(annotate=True)
Becomes the following:
from technologies import silicon_photonics
from ipkiss3 import all as i3
from picazzo3.filters.ring import RingRect180DropFilter, RingRectNotchFilter
from picazzo3.wg.splitters import WgY90Splitter
from picazzo3.routing.place_route import PlaceComponents
ring1 = RingRectNotchFilter()
ring2 = RingRect180DropFilter()
splitter = WgY90Splitter()
circuit = i3.Circuit(
insts={
'spl': splitter,
'com': splitter,
'arm1': ring1,
'arm2': ring2,
},
specs=[
i3.Place('arm1', (30, -30)),
i3.Place('arm2', (30, 30)),
i3.Place('com', (60, 0)),
i3.FlipH('com'),
]
)
lay = circuit.Layout()
lay.visualize(annotate=True)
Porting PlaceAndAutoRoute goes in a similar way. Attention should be paid to the waveguide template used for the connecting waveguides, since the behavior is slightly different:
In
PlaceAndAutoRoute, by defaulti3.TECH.PCELL.WG.DEFAULTdefined in the technology (PDK) is used as the waveguide template for the connections.In a connector such as
i3.ConnectManhattan, by default thetrace_templateof the start port is used.
Therefore if code based on PlaceAndAutoRoute relied on default parameters, you may need to explicitly specify the waveguide template. See Connector Reference for full information on using connectors.
Example porting PlaceAndAutoRoute:
from technologies import silicon_photonics
from ipkiss3 import all as i3
from picazzo3.filters.ring import RingRect180DropFilter, RingRectNotchFilter
from picazzo3.wg.splitters import WgY90Splitter
from picazzo3.routing.place_route import PlaceAndAutoRoute
ring1 = RingRectNotchFilter()
ring2 = RingRect180DropFilter()
splitter = WgY90Splitter()
pr = PlaceAndAutoRoute(child_cells={"spl": splitter,
"com": splitter,
"arm1": ring1,
"arm2": ring2},
links=[("spl:arm1", "arm1:in"),
("arm1:out", "com:arm1"),
("spl:arm2", "arm2:in1"),
("arm2:out1", "com:arm2")]
)
layout = pr.Layout(child_transformations={"arm1": (30, -30),
"arm2": (30, 30),
"com": i3.HMirror(0.0)+i3.Translation((60, 0))},
bend_radius=10.0,
manhattan=True
)
layout.visualize(annotate=True)
Becomes:
from technologies import silicon_photonics
from ipkiss3 import all as i3
from picazzo3.filters.ring import RingRect180DropFilter, RingRectNotchFilter
from picazzo3.wg.splitters import WgY90Splitter
ring1 = RingRectNotchFilter()
ring2 = RingRect180DropFilter()
splitter = WgY90Splitter()
circuit = i3.Circuit(
insts={
"spl": splitter,
"com": splitter,
"arm1": ring1,
"arm2": ring2
},
specs=[
i3.Place('arm1', (30, -30)),
i3.Place('arm2', (30, 30)),
i3.Place('com', (60, 0)),
i3.FlipH('com'),
i3.ConnectManhattan([
("spl:arm1", "arm1:in"),
("arm1:out", "com:arm1"),
("spl:arm2", "arm2:in1"),
("arm2:out1", "com:arm2")
], bend_radius=10)
]
)
lay = circuit.Layout()
lay.visualize(annotate=True)
Note
The highlight_waveguide_crossing parameter of PlaceAndAutoRoute is not available on i3.Circuit.
If this feature is crucial to you, reach out to us at support@lucedaphotonics.com.
Porting i3.place_insts¶
As the newly added i3.place_and_route is a superset of i3.place_insts, the latter has been deprecated.
You can just rename i3.place_insts to i3.place_and_route.