Parametric Cells, or just PCells as we normally call them, is a concept that is frequently used in custom electronic design. As the name suggests, PCells contain a set of parameters that are used to characterize the design or at least a part of it. Once you’ve chosen a set of parameters for your PCell, the PCell can be instantiated (sometimes the word placed is used as well). An instantiated PCell is called a ‘cell’. Often a whole range of possible values for the parameters exist, so it’s possible to instantiate many different cells from one PCell. These cells will be the building blocks of your actual design.
To generate the cell that corresponds to the given parameters, the PCell framework will need to run some code. More specifically, IPKISS allows you to write the logic of your PCell using python.
In most cases we expect the PCell to correspond with a physical design, but this is no requirement. PCells can just as well be used to create more abstract building blocks. A good example is building a circuit using just a netlist and solving it with a circuit solver: no layout or another physical representation needs to be created for this.
The figure below represents the anatomy of a typical IPKISS pcell:
IPKISS’ PCells try to use the same concepts that are used in the electronics industry:
Cell: a unit of reuse in a library (e.g. pmos transistor, ring resonator, …). Can ultimately represent a full design.
PCell: a parametric cell: Cell with parameters on the basis of which the details of the cells are calculated. A PCell can have any number, and any type of parameters.
Parameters: In IPKISS those are called ‘properties’ instead.
View: an object offering a certain view on a (P)Cell for a specific purpose, for instance layout, cross-section, netlist… A PCell can have any number, and any type of Views.
To make this a bit more tangible, let us create a simple dummy PCell named MyPCell, with the following structure.
- PCell Name
- PCell Parameters
( no parameters )
Clearly this PCell does not have any meaning and does not represent a true component, but this allows us to focus on its structure only instead of its functionality. Using IPKISS we would model this structure using the following code:
import ipkiss3.all as i3 class MyPCell(i3.PCell): """ This defines the MyPCell PCell """ _name_prefix = 'MYCELL' # These are the PCell's parameters (or properties): cell_param_a = i3.PositiveNumberProperty(default=1.0) cell_param_b = i3.PositiveNumberProperty(default=2.0) class Layout(i3.LayoutView): """PCells don't have any meaning themselves. A view is used to implement specific parts of the PCell. In the case of a LayoutView, the intention is to describe the physical layout. """ # these are view specific properties: view_param_c = i3.StringProperty(default='C') view_param_d = i3.StringProperty(default='D') def _generate_elements(self, elems): # we access the params through 'self' param1 = self.view_param_c # cell params are accessible from the view param2 = self.cell_param_b return elems class Netlist(i3.NetlistView): pass
In IPKISS you define a PCell by creating a python class that inherits from PCell.
class MyPCell(i3.PCell):. The Parameters of a PCell are modelled using properties: properties
allow you to restrict the range and type of values that you can assign to it.
In the code snippet we specify 2 properties
The MyPCell class on itself only has an abstract meaning. The views of a PCell will give meaning to the design.
A PCell has parameters. In IPKISS, we call them Properties. A property can represent a physical parameter of the cell (width, length, …) but could also represent a functional parameter (loss, impedance, …). A Property has a certain type (integer, string, …) and a value. The value of a Property can be defined in three basic ways:
The value can be set by the user, or
the value is calculated by the PCell itself, or
the value has a pre-defined default value.
Below the cell parameters we notice a second python class, called
Layout, which inherits from
It is clear from the name that this is a View. A view of a PCell is an aspect of the cell with a specific meaning and function in the design flow, such as layout, netlist, pins, material distribution, and so forth. In IPKISS, we define these views centrally from the PCell definition, and synchronize views as much as possible from the start, so that no copy and paste errors or translation errors arise. A View has the following characteristics:
A View has data which it exposes to the user, or which can be exported to files or other design tools (e.g. GDSII, spice netlist, …).
In the view, the user implements how data is generated. For this purpose, it can use the PCell’s properties.
A view can also have its own view-specific properties: parameters which are related only to the definition of that view and are needed only to derive the data of this view.
There are standard viewtypes, but users can also define their own views.
A PCell can have any number and type of views. Though for some types of PCells certain views may make no sense.
Views in IPKISS are implemented using nested classes, doing this makes it more clear that the
Layout implements a part of the
MyPCell cell. As mentioned, the Layout View can have its own parameters. The PCell parameters will be available in all views, while the parameters in the views are specific for that view only.
The data of a View is generated through
_generate_* methods. IPKISS will call those methods to generate the data when required. in the
Layout View of our
MyPCell class, we’ve added a dummy
Our method currently doesn’t implement any logic, but in real LayoutViews this will contain logic to add all the elements to your layout.
_generate_elements method we also show that you can access both the PCell properties and View properties.
Once you’ve specified the PCell with Views and Properties you can start using it to create designs. Instantiating a PCell with a set of properties is done by instantiating the PCell python class. Let’s explain this with an example. First let us create a simple PCell class:
import ipkiss3.all as i3 class MyCircle(i3.PCell): radius = i3.PositiveNumberProperty() def get_diameter(self): # In this method, self refers to the instantiated PCell. return 2*self.radius # referring to cell parameter from within cell class Layout(i3.LayoutView): position = i3.Coord2Property(default=(0.0,0.0)) def _generate_elements(self, elems): # In this method, self refers to the instantiated view. elems += i3.Circle(layer=i3.Layer(0), radius=self.radius, # referring to cell parameter from within view center=self.position, # referring to view parameter from within view ) return elems
Let us now create an instance of our
MyCircle PCell, with a radius of 20:
>>> # instantiates a pcell, radius is required parameter because it doesn't have a default >>> my_circle_cell = MyCircle(name="my_unique_cell_name", radius=20.0) >>> # we can now call the get_diameter() method >>> print(my_circle_cell.get_diameter()) 40.0
radius property does not have a default value. This makes radius a required property that you must
specify when instantiating the PCell.
It is very important to supply an unique name to each pcell. If you do not supply a name,
_name_prefix `` will be used followed by a unique number. In this case, since ``_name_prefix == 'MYCELL'
subsequent instances of MyCell will automatically be named
MYCELL_1 and so on. If a cell exists with the same name, IPKISS will reuse and update the existing cell (and give a Warning).
If IPKISS can’t reuse the existing cell (because it is of a different type), you will get an error. You can’t have two cells with the same name.
In the above example, we created a cell with parameter
radius=20.0. However, we did not yet define the layout view parameters. This is done in a way similar to how the PCell is instantiated, except we call the Layout from the
my_circle_cell PCell we just created:
>>> my_layout = my_circle_cell.Layout(position=(10.0, 0.0))
Here we define the Layout view state of
my_circle_cell, setting specific parameters for the layout. The view can be retrieved from
my_circle_cell later on.
It’s not always necessary to assign the the cell to a dedicated variable, sometimes you’re only interested in the Layout. In this case you can use the following shorthand notation:
>>> layout = MyCircle(radius=10.0).Layout() >>> # if you need the cell afterwards, you can always retrieve it like this: >>> my_circle_cell = layout.cell >>> print(my_circle_cell.radius) 10.0
It’s important to note that a cell can only contain one instance (python object) of a given view class defined in the pcell. For
my_circle_cell, this means
that only one instance of the Layout view can be alive at one time. Calling the view type with parameters multiple times changes the state of that one view object,
it does not create a new view object.
This means you can expect the following behavior:
>>> my_layout_1 = my_circle_cell.Layout(position=(10.0, 0.0)) >>> my_layout_2 = my_circle_cell.Layout(position=(20.0, 0.0)) >>> print(my_layout_1.position) (20.0, 0.0)
We see that the previous state of the view is overridden with the newly supplied parameters.
my_layout_2 effectively point to the same object. This might seem a bit weird at first, but doing this ensures that
my_circle_cell is well defined. If you need two different layouts, you can create two different pcells:
>>> my_layout_1 = MyCircle(radius=10.0).Layout(position=(10.0, 0.0)) >>> my_layout_2 = MyCircle(radius=10.0).Layout(position=(20.0, 0.0)) >>> print(my_layout_1.position) (10.0, 0.0) >>> print(my_layout_2.position) (20.0, 0.0)
In case you want to retrieve a view from a cell you can use the views datastructure to retrieve the layout of a cell:
>>> layout = my_circle_cell.get_default_view(i3.LayoutView) >>> netlist = my_circle_cell.get_default_view(i3.NetlistView)
In the code snippets that we introduced earlier, you’ll notice that all properties in the views have defined default values. IPKISS requires you to provide defaults for all view properties. (This is not the case for pcell properties). The reason for this is that this ensures that a pcell is well defined once it is instantiated. If you don’t explicitly define the view state with specific parameters, the view will still be there and Ipkiss will take the default values to create the initial state.
We’ll illustrate this with a short example, using the MyCircle PCell we defined earlier:
>>> my_circle = MyCircle(radius=10.0, name="my_unique_cell_name") # radius is a required property of the MyCircle PCell >>> layout = my_circle.get_default_view(i3.LayoutView) # we retrieve the view named layout. # This is possible even though we did not explicitly define its state >>> print(layout.radius) # So what we get as parameters are the default values 10.0 >>> print(layout.position) C2(0.0, 0.0)
If you want to make the creation of the view state more explicit, you can always do the following, but it has exactly the same effect.
>>> my_circle = MyCircle(radius=20., name="my_unique_ring_name") >>> layout = my_circle.Layout()
IPKISS will actually not create the view state when you instantiate a PCell. The creation of default view states uses lazy evaluation, which means that the view will only be instantiated when you’re accessing it. As a user, you don’t have to worry about this though.
While parametric cells are extremely useful and flexible, it is often useful to define one or more of such cells with fixed parameter values. These cells can be reference components, often with known and even guaranteed performance.
To create a fixed cell from a PCell, two steps are needed
overriding the default values of the properties
making all the properties read-only
Consider again the
class MyCircle(i3.PCell): radius = i3.PositiveNumberProperty() def get_diameter(self): # In this method, self refers to the instantiated PCell. return 2*self.radius # referring to cell parameter from within cell class Layout(i3.LayoutView): position = i3.Coord2Property(default=(0.0,0.0)) def _generate_elements(self, elems): # In this method, self refers to the instantiated view. elems += i3.Circle(layer=i3.Layer(0), radius=self.radius, # referring to cell parameter from within view center=self.position, # referring to view parameter from within view ) return elems
If we now want to create a class for a
MyCircle cell with radius 20.0 and 30.0, we first specify/override
the default value for the radius using a
_default_radius method and then we lock all the properties down
@i3.lock_properties() class MyCircle20(i3.PCell): def _default_radius(self): return 20.0 @i3.lock_properties() class MyCircle30(i3.PCell): def _default_radius(self): return 30.0
It is now no longer possible to set any parameter on the PCell or its Layout. The only way a PCell object can be made is by calling the PCell class without any parameters:
>>> circle1 = MyCircle20() >>> circle1.radius 20.0 >>> circle1.name 'CELL_1' >>> circle2 = MyCircle20() >>> circle2.name 'CELL_2' >>> circle1 is circle2 False
We see that we can create multiple copies of the same fixed cell. This can be desirable, but in many cases we would like only a single copy of a fixed cell. This is much more efficient. This can be easily accomplished by fixing the name:
@lock_properties() class MyCircle20(i3.PCell): def _default_name(self): return "CIRCLE20" def _default_radius(self): return 20.0
Now the name will be identical (and more meaningful), and because cells are identified by their name, they will not be duplicated:
>>> circle1 = MyCircle20() >>> circle1.radius 20.0 >>> circle1.name 'CIRCLE20' >>> circle2 = MyCircle20() >>> circle2.name 'CIRCLE20' >>> circle1 is circle2 True
The following views are commonly used in IPKISS:
A PCell has interfaces to the rest of the design: connections that allow optical, electrical or other signals to flow into and out of the cell from and to other cells.
In the LayoutView the interfaces are called ports. IPKISS knows optical as well as electrical ports (Ports Reference). A Port describes the physical interface: which layer it is drawn on, which optical or RF waveguide template it connects to and so forth. Optical ports have a physical orientation: the outward pointing angle. They are always bidrectional.
In the NetlistView and the CircuitModelView views and the CompactModel simulation model they are called terms (short for terminals). These are the logical connections of the PCell. Also Terms come in optical and electrical flavors. In most cases one-to-one map between ports and terms.
Ports and terms of PCells as well as CompactModels provided by IPKISS use the following convention for naming ports and terms.
Numbering starts with 1.
Numbering goes from south to north on either side of the device when ports are arranged on the sides.
Port names are related to the logical operation of a device, e.g. ‘in’, ‘out’, ‘anode’, ‘cathode’.
Orientations (e.g. ‘north’, ‘left’) are to be avoided - in a rotated device these loose their meaning.
Directional meaning like in or out is OK even though it must be understood that ports in IPKISS are usually bidirectional.
No number is added if not necessary. Specifically:
A waveguide has ports in and out.
A 1-by-2 splitter would have ports in, out1 and out2.
Component libraries such as those defined by foundries (PDKs) may follow a different convention. Luceda PDKs follow the names defined by the foundry.