Placement and Routing Reference
An important aspect of building a circuit layout is the placement and routing of cells.
In IPKISS, this is done using i3.Circuit
.
This placement and routing function works by choosing a set of instances, and defining specifications (placement, joining, alignment, how to connect, …) that describe how these instances should be placed and connected. The different specifications for placement and routing are listed below.
Functions and classes
The main class to use for building circuits is i3.Circuit
. It is an easy-to-use class built around the core algorithm i3.place_and_route
, which performs the placement and routing in the layout view.
i3.Circuit
also uses i3.NetlistFromLayout
, so that the netlist is automatically extracted from the layout. This enables layout-accurate circuit simulations.
Function to place and route a series of instances with the help of placement specifications and connectors. |
A PCell which derives its layout, netlist and model from a set of specifications in order to create a circuit. |
|
Parametric Cell for logically connecting multiple components. |
Specifications
The placement specifications are the following:
Join instances together. |
|
Specifies that an instance or its port should be placed on a given position and angle (angle is optional). |
|
Specifies that an instance (inst1) or a port (inst1:port) should be placed relative to another instance (inst2) or a port (inst2:port) with a given offset (x, y) and an angle (optional). |
|
Specifies that instances or ports should be aligned horizontally. |
|
Specifies that instances or ports should be aligned vertically. |
|
Specifies that a horizontally mirrored version of the component must be placed. |
|
Specifies that a vertically mirrored version of the instance must be placed. |
Note
Always be as specific as possible when providing the placement specifications. The placement engine will change the positions of instances that are specified in the specifications list.
For instance, if you have multiple specs without using Place, that tells the placement engine that the location of the full circuit (or at least that one instance) is not important. The instances will still be placed relative to each other as specified, but their position might change when you add more specs. By using Place, you anchor your circuit.
The routing specifications are the following:
Scalar connectors
Base class for connectors. |
|
A connector that uses |
|
A connector that uses |
|
Connector for creating an as simple as possible bend between two optical ports based on the given rounding parameters. |
|
A connector that uses |
Bundle connectors
Connects multiple ports together using a bundle of waveguides separated by a fixed distance. |
Fanouts for Bundle connectors
Create S-bend-like routes that fanout all the start ports to evenly spaced outputs. |
|
Create L-bend-like routes that fanout all the start ports to evenly spaced outputs. |
Route control
Argument control points of connector should be a list. Elements of the control points list can be related to the following:
Description of where a route should pass through. |
|
Horizontal control point class. |
|
Vertical control point class. |
|
Symbolic object that represents a start value or position. |
|
Symbolic object that represents an end value or position. |
|
Symbolic object that represents a previous value or position. |
|
Place a via and change trace template at a certain point along the route. |
Control points
Control points i3.CP
allow you to specify certain points through which the route should go.
Description of where a route should pass through. |
import si_fab.all as pdk
import ipkiss3.all as i3
import matplotlib.pyplot as plt
fc = pdk.FC_TE_1550()
circuit = i3.Circuit(
insts={"fc_in": fc, "fc_out": fc},
specs=[
i3.Place("fc_in:out", position=(0, 0)),
i3.Place("fc_out:out", position=(50, 50)),
i3.FlipH("fc_out"),
i3.ConnectManhattan(
"fc_in:out",
"fc_out:out",
control_points=[i3.CP((10, 20), i3.NORTH), i3.CP((30, 30), i3.EAST)],
),
],
)
circuit.Layout().visualize(show=False)
plt.arrow(10, 20, 0, 2, width=1.0)
plt.arrow(30, 30, 2, 0, width=1.0)
plt.show()
import si_fab.all as pdk # noqa
import ipkiss3.all as i3
from picazzo3.fibcoup.curved.cell import FiberCouplerCurvedGrating
import matplotlib.pyplot as plt
gc = FiberCouplerCurvedGrating()
circuit = i3.Circuit(
insts={"gc_in": gc, "gc_out": gc},
specs=[
i3.Place("gc_in", (0, 0)),
i3.Place("gc_out", (100, 100), angle=180),
i3.ConnectManhattan(
"gc_in:out",
"gc_out:out",
control_points=[
i3.CP((90, 10), i3.NORTH),
i3.CP((50, 40), i3.WEST),
i3.CP((0, None)),
],
),
],
)
circuit.get_default_view(i3.LayoutView).visualize(show=False)
plt.arrow(90, 10, 0, 10, width=0.5)
plt.arrow(50, 40, -10, 0, width=0.5)
plt.axvline(0, color="k", linestyle="--")
plt.show()
Horizontal and Vertical control lines
i3.ConnectManhattan
and i3.RouteManhattan
allow the definition of horizontal and vertical control lines.
Horizontal control point class. |
|
Vertical control point class. |
i3.H
and i3.V
represent Horizontal and Vertical lines on a grid.
They are a special case of the more general i3.CP
control points,
requiring you to only specify an X-value for i3.V
and a Y-value for i3.H
:
i3.CP((None, 10)) == i3.H(10)
i3.CP((10, None)) == i3.V(10)
Note
When supplying i3.H and i3.V as control lines they have to be alternated as the lines should cross each other in the order you want the routing to go.
Below are some examples using i3.ConnectManhattan
:
An example showing the syntax:
from ipkiss3 import all as i3
control_points = [i3.V(10), i3.H(40), i3.V(35)]
connector = i3.ConnectManhattan('inst1:out', 'inst2:out', control_points=control_points)
An example showing the usage in an obstacle avoidance situation:
import si_fab.all as pdk
from ipkiss3 import all as i3
from picazzo3.fibcoup.curved import FiberCouplerCurvedGrating
import matplotlib.pyplot as plt
# Placing the components in a circuit
gr = FiberCouplerCurvedGrating()
control_points = [i3.V(-40), i3.H(30), i3.V(50)]
circuit = i3.Circuit(
insts={
'gr': gr,
'grb1': gr,
'grb2': gr
},
specs=[
i3.Place('gr', position=(0, 0)),
i3.Place('grb1', position=(-100, 0)),
i3.Place('grb2', position=(+100, 0), angle=180),
i3.ConnectManhattan(
'grb1:out', 'grb2:out',
control_points=control_points,
)
]
)
lay = circuit.Layout()
lay.visualize(annotate=True, show=False)
plt.axvline(x=-40, color='k', linestyle='--')
plt.axhline(y=30, color='k', linestyle='--')
plt.axvline(x=50, color='k', linestyle='--')
plt.show()
Relative route control
Sometimes positions or values are only known relative to an anchor like the start or end port. In that case you can use symbols to represent these unknowns, and IPKISS will fill in the appropriate values.
Symbolic object that represents a start value or position. |
|
Symbolic object that represents an end value or position. |
|
Symbolic object that represents a previous value or position. |
import si_fab.all as pdk
import ipkiss3.all as i3
control_points = [i3.CP(i3.START + (30, 10), i3.NORTH), # control point at (30, 10) relative to the start port
i3.CP((50, 50), i3.EAST), # control point at (50, 50) relative to (0, 0)
i3.CP(i3.END - (30, 10), i3.NORTH)] # control point at (-30, -10) relative to the end port
gc = pdk.FC_TE_1550()
circuit = i3.Circuit(
insts={"gc1": gc, "gc2": gc},
specs=[
i3.Place("gc1:out", (0, 0)),
i3.Place("gc2:out", (100, 100), 180),
i3.ConnectManhattan(
"gc1:out",
"gc2:out",
control_points=control_points,
bend_radius=5.0
),
],
)
circuit.get_default_view(i3.LayoutView).visualize()
gc = pdk.FC_TE_1550()
circuit = i3.Circuit(
insts={"gc1": gc, "gc2": gc},
specs=[
i3.Place("gc1:out", (0, 0)),
i3.Place("gc2:out", (100, 100), 180),
i3.ConnectManhattan(
"gc1:out",
"gc2:out",
control_points=[i3.H(i3.END - 40)],
bend_radius=5.0
),
],
)
circuit.get_default_view(i3.LayoutView).visualize()
Electrical routing
Place a via and change trace template at a certain point along the route. |
In contrast to optical ports, electrical ports typically don’t have a specified angle.
Therefore, routing from one electrical port to another often requires that the start and end angle of the route be determined by the user.
There is also a specialized control point, i3.VIA
, that allows you to switch layers and insert vias along the route.
i3.ConnectElectrical
is similar to i3.ConnectManhattan
but allows us to specify the start_angle and the end_angle when necessary.
import si_fab.all as pdk # noqa
import ipkiss3.all as i3
bp = pdk.BondPad()
circuit = i3.Circuit(
insts={"pad1": bp, "pad2": bp},
specs=[
i3.Place("pad1:m1", (0, 0)),
i3.Place("pad2:m1", (150, 150)),
i3.ConnectElectrical(
"pad1:m1",
"pad2:m1",
start_angle=0,
end_angle=-90,
),
],
)
circuit.get_default_view(i3.LayoutView).visualize()
However, if the angle information is available on the port, then it is not required to specify the corresponding start_angle or end_angle:
import si_fab.all as pdk # noqa
import ipkiss3.all as i3
class Pad(i3.PCell):
class Layout(i3.LayoutView):
size = i3.Size2Property(default=(50.0, 50.0), doc="Size of the bondpad")
metal_layer = i3.LayerProperty(default=i3.TECH.PPLAYER.M1.LINE, doc="Metal used for the bondpad")
def _generate_elements(self, elems):
elems += i3.Rectangle(layer=self.metal_layer, box_size=self.size)
return elems
def _generate_ports(self, ports):
ports += i3.ElectricalPort(
name="m1",
position=(0.0, 0.0),
shape=i3.ShapeRectangle(box_size=self.size),
process=self.metal_layer.process,
angle=0,
)
return ports
pad = Pad()
M1_wire_tpl = pdk.M1WireTemplate().Layout(width=10)
circuit = i3.Circuit(
insts={"pad1": pad, "pad2": pad},
specs=[
i3.Place("pad1:m1", (0, 0)),
i3.Place("pad2:m1", (150, 150)),
i3.ConnectElectrical(
"pad1:m1",
"pad2:m1",
trace_template=M1_wire_tpl,
),
],
)
circuit.get_default_view(i3.LayoutView).visualize()
The given start_angle or end_angle will take precedence over the angle of the start or end port:
M1_wire_tpl = pdk.M1WireTemplate().Layout(width=10)
circuit = i3.Circuit(
insts={"pad1": Pad(), "pad2": Pad()},
specs=[
i3.Place("pad1:m1", (0, 0)),
i3.Place("pad2:m1", (150, 150)),
i3.ConnectElectrical(
"pad1:m1",
"pad2:m1",
trace_template=M1_wire_tpl,
end_angle=-90,
),
],
)
circuit.get_default_view(i3.LayoutView).visualize()
Use i3.VIA and i3.CP to route electrical wire:
import si_fab.all as pdk
import ipkiss3.all as i3
import matplotlib.pyplot as plt
class VIA_M1_M2_ARRAY(i3.PCell):
box_size = i3.Size2Property(default=(10, 10))
via_pitch = i3.Size2Property(
default=i3.TECH.BONDPAD.VIA_PITCH, doc="2D pitch (center-to-center) of the vias"
)
via = i3.ChildCellProperty( doc="Via used")
def _default_via(self):
return pdk.VIA_M1_M2()
class Layout(i3.LayoutView):
def _generate_elements(self, elems):
elems += i3.Rectangle(layer=i3.TECH.PPLAYER.M1, box_size=self.box_size)
elems += i3.Rectangle(layer=i3.TECH.PPLAYER.M2, box_size=self.box_size)
return elems
def _generate_instances(self, insts):
periods_x = int(self.box_size[0] / self.via_pitch[0]) - 1
periods_y = int(self.box_size[1] / self.via_pitch[1]) - 1
insts += i3.ARef(
reference=self.via,
origin=(
-(periods_x - 1) * self.via_pitch[0] / 2.0,
-(periods_y - 1) * self.via_pitch[1] / 2.0,
),
period=self.via_pitch,
n_o_periods=(periods_x, periods_y),
)
return insts
bp = pdk.BondPad()
via_array = VIA_M1_M2_ARRAY()
M1_wire_tpl = pdk.M1WireTemplate().Layout(width=8)
M2_wire_tpl = pdk.M2WireTemplate().Layout(width=10)
circuit = i3.Circuit(
insts={"pad1": bp, "pad2": bp},
specs=[
i3.Place("pad1", (0, 0)),
i3.Place("pad2", (200, 200)),
i3.ConnectElectrical(
"pad1:m1",
"pad2:m2",
start_angle=90,
end_angle=180,
trace_template=M1_wire_tpl,
control_points=[
i3.VIA(
(50, 70),
direction_in=i3.WEST,
direction_out=i3.NORTH,
trace_template=M2_wire_tpl,
layout=via_array,
),
i3.CP((None,120)),
i3.CP((110,150),i3.NORTH)
],
),
],
)
circuit.Layout().visualize(show=False)
plt.axhline(y=120, color='k', linestyle='--')
plt.arrow(110, 150, 0, 10, width=2)
plt.scatter(110,150, c="C1", s=80, marker="x")
plt.show()