Home Forums Nazca Questions and Answers Iterate over a cell and return polygons from child cell

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #6576
    garbagebag
    Member

    Hello all,

    First I would like to say thank you for all of your effort developing Nazca Design. It has been a tremendous help.

    Moving on to my question:

    I am trying to create some code that will search through a cell and collect all of the polygons in a certain layer from the cell and any child cells. Thanks to the “Inverted MMI” example, I have come close to finding away to do this:

    import nazca as nd
    
    nd.clear_layers()
    nd.add_layer(name='tr', layer=2)
    
    with nd.Cell(instantiate=True, name='trench') as trench:
        nd.strt(length=5, width=5, layer='tr').put(0, 0)
        nd.strt(length=3, width=7, layer='tr').put()
    
    trenchInstance = trench.put(0, -10, 10)
    
    ## search the trench cell for polygons
    polygons = []
    for params in nd.cell_iter(trench, flat=False):
         for pgon, xy, bbox in params.iters['polygon']:
              if pgon.layer == 'tr':
                   polygons.append(xy)
    
    ## draw the found polygons
    
    for polygon in polygons:
        nd.Polygon(points=polygon, layer='tr').put(0, 0)
    
    ## export gds
    nd.export_gds(filename='forum_problem.gds')

    The only problem is that the polygons extracted from the “trench” cell are found in the nd.strt cells, and the coordinates in the polygons are in the scope of those cells, not the trench cell. Instead of lining up like the nd.strts do, the polygons are just stacked up at (0,0). I suppose that this is the expected behavior.

    Is it possible to automatically convert the polygons into the scope of the parent cell? I could do this myself but I would need the xya and flip of the instances of the nd.strt cells, which I can’t find anywhere in the trench cell or the trenchInstance.

    Thanks for your assistance.

    #6585
    Xaveer
    Moderator

    Dear garbagebag,

    Before going into this, I would like to ask if you can explain why you’d want to do this. It might be that you try something that can better be done in a different way.

    Xaveer

    #6586
    garbagebag
    Member

    Xaveer,

    Generally, I am trying to use Boolean operations to define shapes with interior geometry that are otherwise difficult to build from primitives.

    In this specific case, I have a die-level GDS that consists of an array of different PICs. Each PIC cell (by this I mean a nazca cell) consists of:

    1. a “tile” cell, which has probe pads, test structures, and fabrication process monitors
    2. a “device” cell, which is a photonic waveguide device like a laser, SOA, or MZM
    3. some interconnect objects to connect the pads in the tile to the vias in the device

    The tile cell is complicated but relatively uniform across all the PICs, with a few exceptions, mainly the waveguide in the device cell has a few layers that need to be Boolean subtracted (NOT operation) from a big square in the tile cell. It would be highly desirable to have a uniform p-cell for the tile that was reusable across the entire die and in later masks, and just use some Boolean operations to adapt the geometry inside the tile cell for the specific device that is with it inside the PIC cell.

    Another reason would be to invert the polarity of pattern to adapt it for the specific photoresist tone.

    Previously I have done these Boolean operations in klayout using a DRC script, but, at least for the way that I know how to do it in klayout, the Boolean operations affect the entire mask. This presents some difficulty, as there were eventually a lot of these operations. It became difficult to mentally imagine what the final outcome would be. Some PIC cells needed a different set of Boolean operations, and this required the creation of a bunch of virtual layers which were Booleaned and then added back into the physical layer. It was a big headache, and during the fabrication, there were many errors in the mask that resulted from this extra complexity.

    Ideally, I envision a “p-cell”, which is basically a Python function with some design information as an input, which produces a Nazca cell and then a GDS cell, that is fully complete, so that either the python function or the resulting GDS cell can be transported from one mask layout to another without someone (me) having to chase it around and apply the correct klayout DRC script to it.

    So to accomplish this, I have learned that the klayout Boolean algorithms are accessible as a Python library. They are very robust at resolving the specific issue of polygons with interior geometry and making them compatible with GDS. I can post a simple example of how to do this if you are interested in seeing it. This way a Python function that creates a Nazca cell can do Boolean operations and edit the polygons either before putting them into the cell, which works really nicely already, or after the polygon has been put into the cell but before the cell has been instanced, which is what I’m trying to do here and is more difficult.

    The only issue left is that the Nazca polygon class has to be converted somehow into a klayout polygon class, which is where I run into a problem. In Nazca it is simple and often very helpful to make a many layers deep cell hierarchy, but for the Boolean operation to be successful, the piece of geometry in the “tile” cell (in my example it is just a square) and the geometry of the waveguide trench (which is hopefully arbitrary and maybe very complicated, like an MZM with all adiabatic bends) need to be simple polygons in the coordinate system of the PIC cell.

    Open to any suggestions, even if the suggestion is that the idea is impractical.

    Thanks.

    #6592
    Ronald
    Keymaster

    Dear garbagebag,

    The example below should do what you described (extracting polygons and their position w.r.t. a top cell in a celltree).

    The params is a namedtuple that contains both the translation and flip state of a cell instantiation with respect to its direct parent, as well as with respect to the top cell that is iterated over, i.e. “trench” in your example. The namedtuple keys to look for are transflip_loc and transflip_glob. Both are themselves a tuple of the form (Pointer, flipstate). Here “trans” stands for translation (in x, y, and a).

    Because the iterator will initiate a loop on cell opening and again on cell closure, I added a check for some actions on cell_open is True only, in the example below, and save the xya position in the same manner as you did for the polygon points. Note that on cell closure the polygon iterator is an empty list, so in your initial example everything works just fine without checking for a cell_open condition.

    import nazca as nd
    
    nd.clear_layers()
    nd.add_layer(name='tr', layer=2)
    
    with nd.Cell(instantiate=True, name='trench') as trench:
        nd.strt(length=5, width=5, layer='tr').put(0, 0)
        nd.strt(length=3, width=7, layer='tr').put()
    
    trenchInstance = trench.put(0, -10, 10)
    
    # search the trench cell for polygons
    polygons = []
    xya = []
    for params in nd.cell_iter(trench, flat=False):
        if params.cell_open:
            trans, flip = params.transflip_glob
            for pgon, xy, bbox in params.iters['polygon']:
                  if pgon.layer == 'tr':
                       polygons.append(xy)
                       xya.append(trans.xya())
    
    for polygon, xya in zip(polygons, xya):
        nd.Polygon(points=polygon, layer='tr').put(xya)
    
    nd.export_gds(filename='forum_problem.gds')

    Ronald

    #6593
    Ronald
    Keymaster

    As a second option you can flatten the celltree during iteration. The “xy” in the polygon iterator will always return the coordinates with respect to the first instantiated parent, which is the top cell in the iteration, here “trench”, for flat=True. In the example below I now use params.cell_start instead of params.cell_open, as the former will be True on any cell processed (instantiated or not), whereas the latter is only True if the cell will be instantiated. Depending on your needs cell_open or cell_start could be more useful, but here cell_open will not do the job due to the flat=True, which results in an instantiate=False state. As explained in my previous post you could even omit the check for cell_start completely here.

    import nazca as nd
    
    nd.clear_layers()
    nd.add_layer(name='tr', layer=2)
    
    with nd.Cell(instantiate=True, name='trench') as trench:
        nd.strt(length=5, width=5, layer='tr').put(0, 0)
        nd.strt(length=3, width=7, layer='tr').put()
    
    trenchInstance = trench.put(0, -10, 10)
    
    # search the trench cell for polygons
    polygons = []
    for params in nd.cell_iter(trench, flat=True):
        if params.cell_start:
            for pgon, xy, bbox in params.iters['polygon']:
                if pgon.layer == 'tr':
                       polygons.append(xy)
    
    for polygon in polygons:
        nd.Polygon(points=polygon, layer='tr').put(0)
    
    nd.export_gds(filename='forum_problem.gds')

    Ronald

Viewing 5 posts - 1 through 5 (of 5 total)
  • You must be logged in to reply to this topic.