Creating an Item Definition

This is an example for creating an item definition for a pcell.

# Libraries with item definitions created in Python need to specify Python is enabled for the library in eesof_lib.cfg,
# located in the library directory
# eesof_lib.cfg:
# PYTHON_ENABLED=TRUE

# An item definition written in Python must reside in a module called itemdef.py located in the cell's directory.
# The function create_itemdef will be called by ADS when your item definition is loaded.
from keysight.ads import de

def create_itemdef(cell: de.Cell):
    # Create item definition here
    ...
# Copyright Keysight Technologies 2023 - 2024
import math

from keysight.ads import de
from keysight.ads.de import db_uu

# Copy and paste this example inside the ADS Python console.


# An item definition resides in the cell's directory, named itemdef.py and contains
# the function called create_itemdef(de.Cell)
def create_itemdef(cell: de.Cell) -> None:
    # The library must have a cell with the name of the item definition
    assert cell.name == "regular_polygon"
    library = cell.library
    # Create and add a point form and formset to the library
    point_form, point_formset = create_point_form_and_formset_and_add_to_library(library)
    # The point represents a design placement offset from the center point of a regular polygon
    param_offset_point = de.db.ModelParam("offset", "Offset from Center", point_formset, de.db.ModelUnitType.LENGTH)
    # Default values for parameters may be set directly, like this:
    default_coordinate_param = de.db.std_string_param("0.0 mil")
    param_offset_point.default_value = de.db.compound_param(
        point_form.name, [default_coordinate_param, default_coordinate_param]
    )
    # Or, default values may be set using a callback, like this:
    # NOTE: When both a default callback and a stored default value are set,
    # the value returned by the callback will take precedence
    default_value_callback = de.db.ModelCb(
        de.db.ModelCbType.PARAMETER_DEFAULT_VALUE,
        lambda param, model_definition, design: get_default_coordinate(param, model_definition, design),
    )
    param_offset_point.callbacks = default_value_callback
    standard_formset = de.db.model_lib.formsets["StdFormSet"]
    assert standard_formset
    # Number of sides for the polygon, octagons by default
    default_sides_param = de.db.std_string_param("8")
    param_number_of_sides = de.db.ModelParam(
        "sides", "number of sides", standard_formset, de.db.ModelUnitType.NO_UNIT, de.db.ModelParamType.INT
    )
    param_number_of_sides.default_value = default_sides_param
    # The distance from the center point to any vertex
    default_radius_param = de.db.std_string_param("100.0 mil")
    param_radius = de.db.ModelParam(
        "radius", "distance from center point to any vertex", standard_formset, de.db.ModelUnitType.LENGTH
    )
    param_radius.default_value = default_radius_param
    # The orientation (rotation angle), in degrees, of the polygon. A 22.5 degree rotated octagon
    # will be placed such that two sides are parallel to both the X and the Y axes
    default_orientation_param = de.db.std_string_param("22.5")
    param_orientation = de.db.ModelParam(
        "orientation", "angle of orientation", standard_formset, de.db.ModelUnitType.ANGLE, de.db.ModelParamType.REAL
    )
    param_orientation.default_value = default_orientation_param
    # Now that the parameters have all been defined, create the model definition for the regular polygon
    regular_polygon = de.db.ModelDef("regular_polygon", "regular_polygon")
    # Each placed instance will be automatically named X1, X2, X3, etc.
    regular_polygon.inst_name_prefix = "X"
    # Append the parameters
    regular_polygon.parameters = [param_offset_point, param_number_of_sides, param_radius, param_orientation]
    # Add a netlist callback, if desired
    netlist_cb = de.db.ModelCb(de.db.ModelCbType.ITEM_NETLIST, netlist_callback)
    regular_polygon.callbacks = netlist_cb
    # And add the model definition to the library
    de.add_model_definition(library, regular_polygon)


def netlist_callback(netlist_inst: de.db.StandardInstance) -> str:
    # Use the NetlistStringBuilder to build up the netlist string
    from keysight.ads.de.experimental.netlist_helper import NetlistStringBuilder

    builder = NetlistStringBuilder(netlist_inst)
    if False:
        # Functionally equivalent to the else clause
        return builder.clear_and_get_default_netlist_str()
    else:
        if False:
            # Functionally equivalent to the else clause
            builder.append_model_and_instance_name()
        else:
            builder.append_model_name()
            builder.append_str(":")
            builder.append_instance_name()

        builder.append_connectivity()
        if False:
            # Functionally equivalent to the else clause
            builder.append_parameters()
        else:
            builder.append_parameter("offset")
            builder.append_parameter("sides")
            builder.append_parameter("radius")
            builder.append_parameter("orientation")
            # append_parameter will raise an exception on a bad parameter name
            try:
                builder.append_parameter("not_a_parameter_name")
            except RuntimeError:
                pass
    return builder.netlist_str


def create_point_form_and_formset_and_add_to_library(
    library: de.Library,
) -> tuple[de.db.CompoundForm, de.db.Formset]:
    formset = de.db.model_lib.formsets["StdFormSet"]
    # Point parameters will use StdForm and are specified using a coordinate system
    default_coordinate_value = de.db.std_string_param("0 mil")
    # A point is a compound form with two parameters, X and Y
    # The first parameter represents the X coordinate
    param_first = de.db.ModelParam("first", "x coordinate", formset, de.db.ModelUnitType.LENGTH)
    param_first.default_value = default_coordinate_value
    # The second parameter represents the Y coordinate
    param_second = de.db.ModelParam("second", "y coordinate", formset, de.db.ModelUnitType.LENGTH)
    param_second.default_value = default_coordinate_value
    # Create a PointForm netlisted and displayed as the value of the first parameter and the second parameter (x,y)
    point_form = de.db.CompoundForm("PointForm", "x,y", [param_first, param_second])
    # Note, the net_format and display_format default to displaying comma separated sub-parameters
    assert "%0s,%1s" == point_form.net_format
    assert "%0s,%1s" == point_form.display_format
    # We want parameters to display in parentheses, so we modify the display format
    point_form.display_format = "(%0s,%1s)"

    # Add the form to the library
    library.forms.add(point_form)
    # Create a formset
    point_formset = de.db.Formset("PointForms", [point_form])
    # Add the formset to the library
    library.formsets.add(point_formset)
    return point_form, point_formset


def get_default_coordinate(
    param: de.db.ModelParam, model_def: de.db.ModelDefBase, design: db_uu.Design
) -> de.db.ParamItemCompound:
    default_coordinate_param = de.db.std_string_param("0.0 mil")
    # NOTE: If the param's formset has more than one form, this callback
    # would need a way to determine which form_name to use for the compound param.
    form_name = param.formset.forms[0].name
    return de.db.compound_param(form_name, [default_coordinate_param, default_coordinate_param])


def create_workspace_and_itemdef() -> None:
    import os

    # Try and create a workspace in the home directory
    home_dir = os.environ["HOME"]
    workspace_path = os.path.join(home_dir, "workspaces/RegularPolygon_wrk")
    # Ensure there is no open workspace
    if de.workspace_is_open():
        de.close_workspace()
    # Create the workspace and library, this will throw if the workspace already exists
    de.create_workspace(workspace_path)
    de.open_workspace(workspace_path)
    library = de.create_new_library("polygon_lib", os.path.join(workspace_path, "polygon_lib"))
    # Create the item definition
    create_itemdef_example(library)


def create_itemdef_example(library: de.Library) -> None:
    from keysight.ads.de.experimental.generate_symbol import SymbolGenerator

    library.setup_schematic_tech()
    # Create the regular_polygon cell and schematic
    assert library.cell_exists("regular_polygon") is False
    cell = library.create_cell("regular_polygon")
    schematic = db_uu.create_symbol(f"{library.name}:regular_polygon:schematic")
    # Create a basic symbol for when an instance is placed in a schematic
    symbol = db_uu.create_symbol(f"{library.name}:regular_polygon:symbol")
    sym_gen = SymbolGenerator(symbol, schematic, 0.25, 0.25)
    sym_gen.is_dual_symbol_type = True
    sym_gen.should_replace = True
    sym_gen.pin_shape = "square"
    sym_gen.generate_symbol()
    symbol.save_design()
    # Create the item definition. This would normally reside itemdef.py inside the
    # cell's directory
    create_itemdef(cell)
    assert library.cell_exists("cell_sch") is False
    library.create_cell("cell_sch")
    schematic = db_uu.create_schematic(f"{library.name}:cell_sch:schematic")
    reg_poly = schematic.add_instance(
        db_uu.LCVName(library.name, "regular_polygon", "symbol"), (0, 0), name="POLY1", angle=0
    )
    reg_poly2 = schematic.add_instance(
        db_uu.LCVName(library.name, "regular_polygon", "symbol"), (3, 0), name="POLY2", angle=0
    )

    # Modify some of the parameter values
    reg_poly.parameters["sides"].value = "12"
    reg_poly.parameters["radius"].value = "150 mil"

    offset = reg_poly2.parameters["offset"]
    assert isinstance(offset, de.db.ParamCompound)
    offset.sub_params[0].value = "2.0 mil"
    offset.sub_params[1].value = "3.0 mil"
    reg_poly2.parameters["sides"].value = "16"
    reg_poly2.parameters["radius"].value = "250 mil"

    schematic.save_design()


# The artwork generation macro specified in the Customize Pcell dialog inside ADS
# The artwork generation function typically appears in a py file inside the layout view of
# your item definition
def generate_artwork_for_regular_polygon_pcell(design: db_uu.Design) -> None:
    # Retrieve the parameters from the design
    params = design.pcell_parameters
    offset_point_mks = params["offset"].value_from_list_app_type()
    number_of_sides = params["sides"].value
    assert isinstance(number_of_sides, int)
    radius = params["radius"].value
    assert isinstance(radius, float)
    orientation = params["orientation"].value
    assert isinstance(orientation, float)
    # Points are stored in MKS and need to be converted to user units
    scale_factor = design.meter_to_uu_factor
    offset_point_uu = (offset_point_mks[0] * scale_factor, offset_point_mks[1] * scale_factor)  # type: ignore
    # Generate the points for the polygon
    polygon_points = []
    for side in range(number_of_sides):
        x = (math.sin((float(side)) / number_of_sides * 2 * math.pi) * radius) * scale_factor
        y = (math.cos((float(side)) / number_of_sides * 2 * math.pi) * radius) * scale_factor
        polygon_points.append((x, y))

    outline = de.Outline(polygon_points)
    transform = de.Transform()
    # Transform the points by the offset
    transform.translate(point=(offset_point_uu[0], offset_point_uu[1]))
    # And the angle of orientation
    transform.rotate_degrees(orientation)
    outline.transform(transform, 0.0)
    layer_id = design.create_layer_id("cond")
    # Place the polygon on the design
    design.add_polygon(layer_id, outline.points)


# Run this example inside the ADS Python console.
# Open up cell_sch:schematic, select Simulate -> Generate Netlist
# TODO: Throws an error if the workspace already exists, fix
# if de.is_pde_app():
#    create_workspace_and_itemdef()
On this page