Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

When inserting pCells with python module, unable to change pCell parameters in code

I am using the standalone python module to insert some pCells into a layout. As long as using the default parameters (defined in the library), everything works fine, but I can't seem to be able to change the parameters from the default values. Maybe I am missing something about how to properly instantiate pCells?

Here some sample code

import klayout.db as pya
# import the pCell library
from CPW.pymacros import CPW_pieces as cpw

lay = pya.Layout()
top = lay.create_cell("TOP")
dbu = lay.dbu

output_file = "gdsFiles/test01.gds"

# load the Mask library
libMask = pya.Library.library_by_name("CircuitQEDMask")
# load the pCell definitions
cpwStraight = libMask.layout().pcell_declaration("CPW_Straight");
if cpwStraight is None:
    raise Exception("Unknown PCell 'CPW_Straight'")

# get default parameters and change parameter 'ln'
paraDict = dict(zip([p.name for p in cpwStraight.get_parameters()], [p.default for p in cpwStraight.get_parameters()]))
paraDict['ln'] = 1000

# second way of defining new parameters
newLength = 1000
para = []
for p in cpwStraight.get_parameters():
    if p.name != 'ln':
        para.append(p.default)
    else:
        para.append(newLength)

# try 1, create pcell variant with parameters paraDict and insert in top cell
pcell = lay.create_cell("CPW_Straight", "CircuitQEDMask", paraDict)
trans = pya.ICplxTrans.new(1, 0, False, 0, 1000 / dbu)
top.insert(pya.CellInstArray.new(pcell.cell_index(), trans))

# second try
pcell2 = lay.add_pcell_variant(libMask, cpwStraight.id(), para)
top.insert(pya.CellInstArray(pcell2, trans))

lay.write(output_file)

When opening the resulting gds in kLayout, the pCells are produced and placed fine, but with the default parameters as defined by our library and not using the modified 'ln' parameter. I can edit the parameters in the GUI, no problem, but I don't seem to get it working from out the python code.
I also reproduce the same behavior when using the standard Circle pCell in the myLib library, pCells I place from within the code are produced using the default parameter values, regardless of what parameters I try to specify.

Now, potentially a clue, for example

for inst in top.each_inst():
    print(inst.pcell_parameters())

returns [], similarly

for inst in top.each_inst():
    inst.change_pcell_parameter('ln', 1000)
    print(inst.pcell_parameters())

still returns [] (and nothing is changed in the layout).

Any help appreciated

Clemens

Comments

  • Hi Clemens,

    looks like there is an issue in the standalone modules when reimplementing C++ interfaces in Python.

    Here is code which works in the application:

    # --------------------------------------------------------
    #  Library definition
    
    class TestPCell(pya.PCellDeclarationHelper):
    
      def __init__(self):
    
        super(TestPCell, self).__init__()
    
        self.param("l", self.TypeLayer, "Layer", default = pya.LayerInfo(1, 0))
        self.param("r", self.TypeDouble, "Radius", default = 1000.0)
        self.param("n", self.TypeInt, "Number of points", default = 64)
    
      def display_text_impl(self):
        return "Circle (l=%s,r=%.12g,n=%d)" % (str(self.l), self.r, self.n)
    
      def produce_impl(self):
        self.cell.shapes(self.l_layer).insert(pya.DPolygon.ellipse(pya.DBox(-self.r, -self.r, self.r, self.r), self.n))
    
    class TestLib(pya.Library):
    
      def __init__(self):
        self.description = "Test library"
        self.layout().register_pcell("Circle", TestPCell())
        self.register("TestLib")
    
    # create and register the library
    lib = TestLib()
    
    
    # --------------------------------------------------------
    #  Client code
    
    lay = pya.Layout()
    top = lay.create_cell("TOP")
    
    output_file = "test.gds"
    
    para = { "l": pya.LayerInfo(1, 0) }
    
    pcell = lay.create_cell("Circle", "TestLib", para)
    trans = pya.DCplxTrans.new(1, 0, False, 0, 0)
    top.insert(pya.DCellInstArray(pcell.cell_index(), trans))
    
    para = { "l": pya.LayerInfo(2, 0), "r": 750.0 }
    
    pcell = lay.create_cell("Circle", "TestLib", para)
    trans = pya.DCplxTrans.new(1, 0, False, 0, 2000.0)
    top.insert(pya.DCellInstArray(pcell.cell_index(), trans))
    
    para = { "l": pya.LayerInfo(2, 0), "r": 500.0, "n": 16 }
    
    pcell = lay.create_cell("Circle", "TestLib", para)
    trans = pya.DCplxTrans.new(1, 0, False, 0, 3000.0)
    top.insert(pya.DCellInstArray(pcell.cell_index(), trans))
    
    lay.write(output_file)
    

    Please note the simpliciations I made

    • "D"-type objects for working in micrometer units
    • Simply use a dict for passing the PCell parameters - all undefined ones are replaced by default values

    The standalone version isn't working yet, because "produce_impl" etc. are never getting called. I'll take a look at this.

    Matthias

  • Hi Matthias,
    Thanks for the reply and the example.
    Do I understand correctly that there is no way currently to change this behavior due to the way the standalone module places the pCells? I.e. it only puts a placeholder in and does not in fact evaluate / place the shapes?
    Is it possible this functionality would be added in the (near) future?

    Our use-case would be something like this:
    Large scale designs can be created with python code in whatever is your favorite editor, including iPython variants. The resulting designs are however still fully editable in the kLayout gui and parameters can be adjusted if necessary using the pCell functionality.

    Do you have a suggestion for a workaround solution?
    I guess when giving up the pCell functionality, we could redefine the parts of our library as normal cells, which I assume could be placed?

    Thanks,

    Clemens

  • Hi Clemens,

    the issue is actually already fixed (https://github.com/klayoutmatthias/klayout/issues/197). With the latest pymod branch, you can now generate layouts with PCells with the standalone Python module. At least with my sample code it works.

    The issue essentially was that reimplementation of C++ methods did not work at all with the Python module. This is an essential implementation detail of the PCell functionality and in the end the PCell essentially became unimplemented, hence empty.

    Please try and give feedback if it works (preferably on the ticket or with a new ticket).

    Kind regards,

    Matthias

  • Hi Matthias,
    Sounds excellent, would love to try, but I am on the windows branch, is there an updated version of that too? The Pypi version (https://pypi.org/project/klayout/) has not been updated since end of october, so probably not yet?
    Cheers,

    Clemens

  • edited November 27

    Hi Clemens,

    PyPI does not allow me to update files once released. As the pymod module is still undergoing development, I don't want to flood their repository with small updates and only occasionally increase the dev version. I have updated now to dev8.

    However, you can always pick the automated builds from here: https://www.klayout.org/downloads/master/

    Matthias

  • Thanks for that, and sorry for the hassle.
    With the new version the module code executes fine, and the resulting gds file is larger than in previous attempts, indicating that the production code is called.
    However I cannot open the file anymore with klayout (internal error: false was not true). I have tried with the latest stable version (25.5) as well as the new build 0.26, neither can open the files and the 0.26 version even cannot open previously fine gds files, but crashes with a stream error.
    Would you prefer I log a bug for these issues on github?

    Cheers,

    Clemens

  • edited November 27

    Hi Clemens,

    0.26 is far from stable, although I don't think it should crash. But without further details I can't say whether it's the intermediate build or a real issue. BTW: I switched everything under development to master branch. 0.25 is the latest stable branch.

    Regarding the issue with the GDS file written: I think something is wrong when KLayout tries to recover the PCell instances. When a GDS file with PCells is read, KLayout will try to find the PCells referenced inside the GDS file and refresh the PCell layout. The message you describe may be caused by the code that is executed when this refresh happens.

    Does the message mention any source location?

    You can try to open it without PCell code active ("klayout -rx ..."). If the errors still persists, please file an issue. If possible, please attach the broken GDS file.

    Thanks,

    Matthias

  • Hi Matthias,
    Thanks for the patience, opening the files using option -rx does work and the designs seem to be placed properly and parameters set as wanted.
    The full error message when trying to open without disabling Pcells is

    Internal Error: ../../../src/gsi/gsi/gsiClass.h:112 false was not true

    This is on windows 10, stable version 0.25.5

    Cheers,

    Clemens

  • edited November 29

    Hi Clemens,

    Although the message is kind of cryptic, I can tell a little bit from that.

    Basically it says that some object was stored in a string representation and this object does not support deserialization.

    It looks as if some PCell parameters are objects of a kind that does not support serialization. The primitive objects like Box/DBox/Polygon/DPolygon/Point/DPoint ... support serialization. Higher-level objects such as Layout don't.

    Here are the objects that support serialization and can be stored as PCell parameters:

    • Box, Edge, EdgePair, Path, Point, Polygon, SimplePolygon, Text, Vector
    • The corresponding "D" types (DBox, ...)
    • Region, Edges, EdgePairs
    • LayerInfo

    So maybe you can check the parameters you're giving your PCells. I think that at least one parameter is an object which is not of that kind. If you had a simple example of such a case, I could try to make the reader print a more reasonable message. I hope however, this remains a rare case.

    Thanks,

    Matthias

  • Thanks again, that was very helpful!
    The issue was with one of the ways I was defining the parameters for the pCells. As long as I use a proper dict and not just a list of values, things seem to work fine.
    To be clear, the following code snippet (in the context of my earlier example) does not work and produces the mentioned error message

    # defining new parameters
    newLength = 1000
    para = []
    for p in cpwStraight.get_parameters():
        if p.name != 'ln':
            para.append(p.default)
        else:
            para.append(newLength)
    
    pcell2 = lay.add_pcell_variant(libMask, cpwStraight.id(), para)
    top.insert(pya.CellInstArray(pcell2, trans))
    

    whereas

    # get default parameters and change parameter 'ln'
    paraDict = dict(zip([p.name for p in cpwStraight.get_parameters()], [p.default for p in cpwStraight.get_parameters()]))
    paraDict['ln'] = 1000
    
    # create pcell variant with parameters paraDict and insert in top cell
    pcell = lay.create_cell("CPW_Straight", "CircuitQEDMask", paraDict)
    trans = pya.ICplxTrans.new(1, 0, False, 0, 1000 / dbu)
    top.insert(pya.CellInstArray.new(pcell.cell_index(), trans))
    

    works perfectly.
    Both ways of creating pCells are mentioned at various places in the forum and documentation, maybe I missed some way of doing it properly in the first case?

    Cheers,

    Clemens

  • edited November 29

    Hi Clemens,

    unfortunately forum entries become outdated over time, so things may look a bit confusing. In general, old solutions still work, but there may be easier ways now. I wish those entries would age like paper becoming yellow and torn, but there is no such feature in this forum system :-)

    The PCell parameter setting is one of these topics. The old way was to supply a list of parameters which had to match the declaration list. The dict-based "create_cell" is the most recent variant. "add_pcell_variant" with a dict is somewhat in between.

    Below you find a sample with all three methods. Please note that you don't need to supply the default values for the dict. They should be filled in automatically.

    I can't say why the dict way works while the other doesn't. Basically there should not be a difference. Maybe it's related to the way the defaults are defined. But without further knowledge, I can't say why.

    Below is the code that shows the variants.

    Regards,

    Matthias

    # Preparation
    
    ly = pya.Layout()
    l1 = ly.layer(1, 0)
    tc = ly.create_cell("TOP")
    
    lib = pya.Library.library_by_name("Basic")
    circle = lib.layout().pcell_declaration("CIRCLE")
    
    
    # First method: use the (modern) "create_cell" method with a dict for the parameters
    # NOTE: 
    #  - parameters not defined in the dict will get the default value automatically
    #  - the circle has two radius values: the "actual_radius" which is taken for 
    #    computation and the "radius" which is the value entered. Both do not need to be
    #    in sync to account for the second input method using the handle. For generation,
    #    "actual_radius" is sufficient, but don't be confused when you want to edit the
    #    circle's radius. It will be "0.1" (the default).
    
    c1 = ly.create_cell("CIRCLE", "Basic", { "layer": pya.LayerInfo(1, 0), "actual_radius": 100.0 })
    tc.insert(pya.DCellInstArray(c1.cell_index(), pya.DTrans(pya.DVector(0, 0))))
    
    # Second method: "add_pcell_variant" which is slightly less convenient, but still takes
    # a dict. Default values are automatically added.
    
    c2_index = ly.add_pcell_variant(lib, circle.id(), { "layer": pya.LayerInfo(1, 0), "actual_radius": 101.0 })
    tc.insert(pya.DCellInstArray(c2_index, pya.DTrans(pya.DVector(0, 500.0))))
    
    # Third (old) method: "add_pcell_variant", but with a list which has to match the list
    # of parameter declarations. Quite inconvenient.
    
    para = []
    for p in circle.get_parameters():
      if p.name == "layer":
        para.append(pya.LayerInfo(1, 0))
      elif p.name == "actual_radius":
        para.append(102.0)
      else:
        para.append(p.default)
    
    c3_index = ly.add_pcell_variant(lib, circle.id(), para)
    tc.insert(pya.DCellInstArray(c3_index, pya.DTrans(pya.DVector(0, 1000.0))))
    
    # Done. 
    
    ly.write("test.gds")
    
Sign In or Register to comment.