Forum Replies Created

Viewing 20 posts - 21 through 40 (of 142 total)
  • Author
    Posts
  • Ronald
    Keymaster

    Dear Cameron,

    GDS natively supports arrays on cell instances that span two vectors (dx1, dy1) and (dx2, dy2), not circles. For a circular array you have to place cells simply on a circle.

    from math import sin, cos, pi
    import nazca as nd
    
    cell = nd.load_gds(filename="my.gds", cellname="my_cell_name")
    
    with nd.Cell('circle') as C: 
        N = 20
        radius = 500
        for i in range(N):
            cell.put(radius*cos(i*2*pi/N), radius*sin(i*2*pi/N), 0)
     
    C.put()
    nd.export_gds()

    For a native cell array check out the gds arrays tutorial.

    Ronald

    in reply to: get layers used in a layout #6099
    Ronald
    Keymaster

    Dear Bastien,

    All layers are stored in a Pandas DataFrame in the cfg module. You can access it as
    df = nd.cfg.layer_table

    The method nd.get_layers() simply gets you the same DataFrame with a column filter added, but a column name has been changed into “layer_name”, hence the error. Will fix that and put a test on it.

    Alternatively to print the layers to stdout you can use
    nd.show_layers()

    Ronald

    in reply to: Hashme with multiple cells #6096
    Ronald
    Keymaster

    Dear Joel,

    I checked that indeed **kwargs fed parameters do not pop up in the hashme after introspection (via inspect module). My guess (observation?) is that **kwargs are dealt with in another corner of inspect and are hiding there. Something to investigate…

    Ronald

    in reply to: Clipping check, distance and length for interconnects #6091
    Ronald
    Keymaster

    Dear iv_tern,

    print points rather than pgon.points for the polygon coordinates with respect to the first instantiated parent cell, which maximaly goes up to the cell provided to cell_iter(), the “topcell” in this context.

    To get the polygon with respect to the topcell, regardless of any cell instantiation, flatten the hierarchy with flat=True or hierarchy='flat'.

    In your case you are looking for the position with respect to the “nazca” top cell, which is always created as a blank canvas when importing nazca.

    – nazca
    .. – tp1
    …. – polygon1
    .. – tp2
    …. – polygon2

    That “nazca” topcell can be accessed as nd.cfg.defaultcell and you get:

    for NT in nd.cell_iter(nd.cfg.defaultcell, flat=True):
        if NT.cell_start:
            print(f"\ncell: '{NT.cell.cell_name}'")
            for i, (pgon, points, bbox) in enumerate(NT.iters['polygon']):
                print(f"{i}: points: {points}, layer: '{pgon.layer}'")

    output:

    cell: 'nazca'
    
    cell: 'toy_poly2'
    0: points: [(40.0, 40.0), (25.0, 40.0), (26.0, 50.0), (20.0, 40.0)], layer: '1/0/None'
    
    cell: 'toy_poly1'
    0: points: [(0, 0), (0, 7), (10, 14), (0, 25)], layer: '1/0/None'

    Alternatively put the design in a custom topcell:

    nd.with(name='topcell') as top:
        p1 = tp1.put(0, 0, 0)
        p2 = tp2.put(40, 40, 90)

    and use nd.cell_iter(cell=top, flat=True) to get the coordinates as before and nd.export_gds(top) in case you export cell “top” to gds.

    Ronald

    • This reply was modified 9 months ago by Ronald. Reason: Replaced nd.cfg.cells[1] by nd.cfg.defaultcell. Apart from that, note the nd.clear_layer() call created an extra nazca topcell, hence the cells[1] in the earlier exampler rather than cells[0]. In nazca0.5.11 that has been corrected and a cells[0] would be required if using the cells reference
    in reply to: Nazca reset within the same interpreter #6089
    Ronald
    Keymaster

    Dear Dima,

    Will this help, inserted at the top of your file?

    from IPython import get_ipython
    get_ipython().magic('reset -sf')

    Ronald

    in reply to: Clipping check, distance and length for interconnects #6084
    Ronald
    Keymaster

    Dear iv_tern,

    The properties are accessible through the Cell objects, which are themselves in a tree structure.

    If you want to see the polygons (if I interpret your question well) you can iterate over all cells under a specific cell and in each cell iterate over all polygons. The method cell_iter() is available for that as demonstrated in the example below.

    import nazca as nd
    import nazca.demofab as demo
    
    cell_1 = demo.deep.strt(length=10, width=5)
    
    print("points in original polygon:")
    for NT in nd.cell_iter(cell_1):  # NT is a named tuple object
        if NT.cell_start:
            print(f"\ncell: '{NT.cell.cell_name}'")
            for i, (pgon, points, bbox) in enumerate(NT.iters['polygon']):
                print(f"{i}: points: {pgon.points}, layer: '{pgon.layer}'")

    In the line (pgon, points, bbox), the “pgon” refers to the original polygon object, the “points” to the polygon translated w.r.t. the top cell provided to cell_iter, and “bbox” the translated bounding box.

    Ronald

    in reply to: Adding pins to user defined cell #6064
    Ronald
    Keymaster

    Dear dnortheast,

    The layout of the cell is correct, only the provided pin reference is not pointing to an instance object. To help identify this situation quickly I added a warning that will show up in the Nazca logfile, i.e. referencing of the Pin(pin=…) to a pin in a cell outside the cell you are building (available after the 0.5.10 release). It will not complain when referencing to a pin inside the same parent cell or a pin in an instance.

    Ronald

    in reply to: Clipping check, distance and length for interconnects #6063
    Ronald
    Keymaster

    Dear iv_tern,

    When you place elements (cells) Nazca checks on all of the pins of the element if they are closer than a small value epsilon (typically < 1 nm) from an existing pin in the parent cell. In that case Nazca internally creates a connection between all matching pins for a complete circuit netlist. The connections are validated against a number of conditions, like the pin width, xsection and symmetry. Note this is on pins, not an arbitrary feature of the polygons or polylines; It is about circuit connectivity in this context, not geometrical drawings.

    The same feature can be used for snapping (useful in a drag & drop environment) by using a larger epsilon and let the placement follow the existing pin if close enough and if all the other required pin properties (apart from spatial proximity) match according to a customizable set of rules. It needs some small adaptations in the code to be able to switch from proximity based in-place connections to snapping connections depending on the situation (script vs mouse).


    The distance between two pins can be obtained as follows

    x, y, a = nazca.diff(pin1, pin2)

    Some other features in this context may be useful, e.g. getting the position in between two pins:

    x, y, a = nazca.midpoint(pin1, pin2)
    pin = nazca.midpointer(pin1, pin2)


    The geometrical length of interconnects can be obtained by starting a “trace”:

    import nazca as nd
    ic = nd.interconnects.Interconnect(width=1.0, radius=100)
    
    nd.trace.trace_start()
    ic.strt(length=200).put(0)
    ic.sbend(offset=100).put()
    ic.bend(angle=45).put()
    nd.trace.trace_stop()
    length = nd.trace.trace_length()
    print(length)

    The trace does not check if elements are connected.

    Instead of the trace method you can use the pathfinder, which returns all connected paths and their geometrical lengths, starting in the pin provided to the “start” keyword.

    import nazca as nd
    ic = nd.interconnects.Interconnect(width=1.0, radius=100)
    
    e1 = ic.strt(length=200).put(0)
    ic.sbend(offset=100).put()
    ic.bend(angle=45).put()
    nd.pathfinder(start=e1.pin['a0'])

    Note that some parametric interconnect elements may not have a path length yet.

    Ronald

    in reply to: Adding pins to user defined cell #6059
    Ronald
    Keymaster

    Dear dnortheast,

    If no pins are placed in a new cell Nazca will place two default pins, i.e. input ‘a0’ at (0, 0, 180) and output ‘b0’ at (0, 0, 0) in the cell’s coordinates, so the cell can be connected.

    The pins you placed are on pins in the original cells (wg1strt and wg2strt) rather than the instances of those cells in your “pulley” cell, i.e. the result of their placement via a put(). So those pins in your example appear at the coordinates as they are in the original cell rather than their instance location in your new “pulley” cell, where you want them. The solution is as follows:

    # old (incorrect)
    wg2strt.put()
    nd.Pin(name='a0', pin=wg1strt.pin['b0']).put()
    
    # new (correct)
    an_instance_of_the_cell_wg2strt = wg2strt.put()
    nd.Pin(name='a0', pin=an_instance_of_the_cell_wg2strt.pin['b0']).put()

    See also this post about cells and instances

    Ronald

    in reply to: Drawing line with mathematical function #6052
    Ronald
    Keymaster

    Dear Cameron,

    A correct observation; You can play with x, y, and w at the same time to get what you need.

    For the geometrical path length of a parametric curve along (x, y) some calculus is required:

    length = integral [ sqrt (x(t)^2 + y(t)^2) * dt ], t from t=0 to t=1

    Analytically you may have the integral already, then that would be the most accurate solution. Otherwise take the sum of the line segments for a set of finite dt sections for t along [0, 1], where you need to set a criterion that ensures you obtain a sufficiently close approximation of the length. Perhaps scipy.integrate is of use.

    Ronald

    in reply to: Drawing line with mathematical function #6049
    Ronald
    Keymaster

    Dear Cameron,

    If I interpret your question correctly, i.e. that you want to modulate the edge of the waveguide, there are a few ways to get a sin-shaped waveguide edge.

    The most flexible is to define a parametric curve in x(t), y(t), and w(t), where in your case you exploit w(t). See also Free form curves, which discusses arbitrary parametric curves.

    import numpy as np
    import nazca as nd
    
    xs = nd.add_xsection(name='myXS')
    xs.width = 5.0
    nd.add_layer(name='myLay1', layer=(5, 0))
    nd.add_layer2xsection(xsection='myXS', layer='myLay1')
    
    # create functions x, y, w for the parametric waveguide:
    def x(t, wavelength, periods, **kwargs):
        """X as function of t and free parameters."""
        return periods * wavelength * t
    def y(t, **kwargs):
        """Y as function of t and free parameters."""
        return 0
    def w(t, width1=None, width2=None, amplitude=1.0, periods=10, **kwargs):
        """Width as function of t, width1 and width2 and free parameters."""
        return width1 + amplitude * np.sin(t * periods * np.pi)
    
    # create the new parametric function using the template Tp_viper():
    params = {'wavelength': 2.0, 'amplitude': 0.5, 'periods': 50}  # assign *all* free params used in functions x, y, and w.
    sin_edge = nd.Tp_viper(x, y, w, xs='myXS', **params)
    
    # put waveguides:
    sin_edge().put(0)
    sin_edge(width1=10.0, wavelength=4.0, amplitude=4.0, N=2000).put(0, 15)
    nd.export_gds()

    The result is the following waveguides:

    The second solution is to define a perturbation of the edge of a straight waveguide element. This hasn’t been an actively in use option, but I dusted it off for nazca-0.5.10 to work in the following way (not using a sin but 2*t for simplicity). The edge1 function in t adds to the normal width of the guide, but it becomes absolute when using width=1.0.

    import nazca as nd
    s = nd.strt(length=10, edge1=lambda t: 2*t + 1.0, edgepoints=100, layer=1).put(0)
    nd.export_gds()

    The above would work the same for straight interconnects.

    Lastly, you can draw your desired waveguide directly as a polygon:

    import nazca as nd
    points = [(x1, y1), (x2, y2), ...] # your shape
    nd.Polygon(points=points, layer=1).put(0)
    nd.export_gds()

    You ideally would put this in a cell and add pins to it as described in Make and put a Cell.

    Ronald

    Ronald
    Keymaster

    Dear Cecil,

    If I understand your new question correctly you want to add an extra parameter, here width_output, on your side to the whitebox in addition to the blackbox parameters. Such a parameter should not affect any pin positions or bbox size. I can not image a direct use case, unless you do something like porting the same black box to two different processes internally. You can probably use the partial method from Pythons functools module. It basically applies, in the example below, the width_output parameter before feeding the white_pcell into the replacement dictionary. After partial(white_pcell, width_output) only the blackbox parameters are left. Referring to the example code in my reply above above this looks like

    from functools import partial
    
    ...
    def white_pcell(width_wg, offset, radius, width_output):
    # do other things
    ...
    
    d = {'blackbox': partial(white_pcell, width_output)}

    Ronald

    in reply to: Sub-1-nm geometry #6041
    Ronald
    Keymaster

    What would we be without garbagebags?

    Thanks for updating with the solution.

    Romald

    in reply to: rebuild cell #6040
    Ronald
    Keymaster

    Dear Paul,

    That is an interesting example. Only the last cell in the loop makes it to the final gds layout, because the rebuild reuses the normal export_gds() code base that by default clears all cells at the end. Two ways forward:

    Solution 1:

    In a call directly to export_gds() the clear setting can be changed by passing clear=False. The rebuild() does not have to clear keyword yet up and including 0.5.9. You can add it though by going into Nazca module layout.py and in def rebuild() add the line export.clear = False, somewhere before the line export.generate_layout(cell).

    Solution 2:

    Another way is to use the code to reconstruct cellA and not clearing any cells. In the example below I use nazca-0.5.9 to make use of the custom suffix option and some variable name improvements. Note that each new cell needs a new unique name. Below that is done using the layer number as a suffix.

    import nazca as nd
    
    with nd.Cell(name='CellA') as cellA:
        nd.strt(length=2, width=2, layer=0).put()
    
    grid_points = [(0, 0), (4, 0), (0, 4), (4, 4)]
    
    def custom_rebuild(cell, layermap, suffix="_new"):
        ly = nd.layout(layermap=layermap, suffix=suffix)
        for params in nd.cell_iter(cell):
            if params.cell_start:
                if params.cell_open:
                    ly.cell_open(params)
                ly.add_polygons(params)
                ly.add_polylines(params)
                ly.add_annotations(params)
                ly.add_instances(params)
            elif params.cell_close:
                ly.cell_close(params)
        return ly.topcell
    
    for i, (x,y) in enumerate(grid_points):
        new_cell = custom_rebuild(cellA, layermap={0:i}, suffix=f"_{i}")
        new_cell.put(x,y)
    
    nd.export_gds()

    Ronald

    in reply to: rebuild cell #6032
    Ronald
    Keymaster

    Dear Paul,

    If you would like to map layers upon export to gds you can simply do this:

    import nazca as nd
    
    with nd.Cell(name='CellA') as cellA:
        nd.strt(length=10, width=5, layer=1).put()
        nd.bend(radius=20, width=2, angle=90, layer=2).put()
    
    nd.export_gds(topcells=cellA, layermap={1: 3, 2: 4}, layermapmode='all')

    The (hiding) rebuild() function was a special case of the cell_iter() in 0.5.7. The cell_iter() has many use cases and it requires several dedicated tutorials to cover it. These will be posted in the near future. But for layermapping you can use the example above, no need for the cell_iter().

    Ronald

    in reply to: Disable naming warnings #6028
    Ronald
    Keymaster

    Dear ale35,

    The best way to get rid of warnings is to solve the underlying reason or, second best, redirecting them to a log file so they do not scroll over the screen but are awaiting review on disk. For the latter see logging.

    To resolve the cell rename warning avoid recreating a cell more than once, i.e. do not do the following:

    import nazca as nd
    
    def createA():
        with nd.Cell('test') as C:
            pass
        return C
    
    createA()
    createA() # causes a renaming warning
    createA() # causes a renaming warning

    but instead do

    import nazca as nd
    
    def createA():
        with nd.Cell('test') as C:
            pass
        return C
    
    A = createA()
    A.put()
    A.put()
    A.put()

    Another way to resolve these warnings is to use the @hashme decorator. It checks on cell name reuse and returns the existing cell rather then recreating it under a new name:

    import nazca as nd
    
    @nd.bb_util.hashme('test')
    def createA():
        with nd.Cell() as C:
            pass
        return C
    
    createA()
    createA() # no more renaming warning
    createA() # no more renaming warning

    Ronald

    Ronald
    Keymaster

    Dear Cecil,

    Your approach is correct. What is missing is that you need to fill the cell’s ‘parameter’ attribute with a dict like {parametername : value}. The parametername is expressed as a string.

    This parameter attribute is automatically filled by the @hashme decorator, which I added in the example below to your ‘blackbox’ in combination with an autobbox=True. To give the autobbox dimension in your case with only two pins, I added the userbbox. The autobbox finds the boundaries and calls the correct put_boundingbox() function internally, so that explicit call can be deleted.

    In all it adds a boundingbox and all the blackbox function parameters as annotation to the gds. Those parameters are read back in during the pcell replacement to generate the parametric white cell. I also added some keywords to your calls to make the code more robust. Additionally, I added version info to your cell via the ‘version’ attribute of the cell, which adds a extra information to the black cell, again as annotation.

    This pcell flow can be used for IP protection and there are several more elements to this flow to make this pcell IP-replacement work in basically any situation. If you know your pcell names you can simply look for them in the gds, but Nazca can also detect “unknown” pcells, as well as tempering with black boxes to make the whole process 100% reliable. All these features require a somewhat longer tutorial.

    import nazca as nd
    version = {'owner': 'cecil'}
    
    def some_calculation(offset, radius):
        return 2.0 + offset + 0.5*radius
    
    @nd.bb_util.hashme('blackbox')
    def blackbox(width_wg, offset, radius):
        with nd.Cell() as C:
            C.version = version
            length = some_calculation(offset, radius)
            C.userbbox = [(0, -1.5*offset), (length, -1.5*offset), (length, 1.5*offset), (0, 1.5*offset)]
            C.autobbox = True
            nd.Pin('a0').put(0, 0, 180)
            nd.Pin('b0').put(length, offset)
            nd.put_stub()
        return C
    
    def white_pcell(width_wg=2, offset=100, radius=500, layer=1):
        with nd.Cell(name='white_pcell', cnt=True) as C:
            length = some_calculation(offset, radius)
            nd.strt(length=length, width=width_wg).put(0)
            # do some stuff to create the pattern
            # same pin placement as above
        return C
    
    nd.strt(width=2, length=200, layer=1).put(0)
    blackbox(width_wg=2, offset=150, radius=1000).put()
    blackbox(width_wg=2, offset=20, radius=500).put()
    nd.export_gds(filename='test')
    
    # Import and conversion black to white:
    d = {'blackbox': white_pcell}
    nd.replaceCells(gdsin='test.gds', gdsout='testwhite.gds', PcellFunctionMap=d)

    Ronald

    in reply to: Hashme with multiple cells #5993
    Ronald
    Keymaster

    Dear ale35,

    The tutorial on hashme may be of help.

    All cells generated by hashed function calls have the _$#### hash suffix as unique identifier.
    In newer Nazca versions (0.5.7 and up) the hashme=True has become optional in de Cell() call.

    The hashme decorator does not only check for the cell name, but for the full call profile and it generates a unique cell name based on that (with the hash). You can also manage cell names yourself and simply send or generate a unique cell name every time you call your cell generating function. Hashme does all the work for you in one line with a few characters in case of parametrized cells.

    Ronald

    Ronald
    Keymaster

    Dear Daneel,

    Which Nazca version are you using? I can not reproduce the asymmetric result. Would you have a short piece of code generating the guides like in the picture you attached?

    The sbend_p2p() has by default a fall-back option (keyword bsb=True) where it draws a bend_straight_bend_p2p() interconnect if for some reason the sbend did not return a valid solution. This is what the left interconnect in your picture looks like. If you set bsb=False Nazca will connect the points with a straight line in the error layer. These events are also recorded in the Nazca logfile, which you can activate like

    import nazca as nd
    nd.logfile(__file__)

    By default, the horizontal line of the sbend is in the direction of pin1. This horizontal orientation can be set/changed via the ref keyword if needed. The sbend_p2p will automatically add extra bends in the pin1 start and pin2 en points to accommodate the sbend_p2p under the ref condition, e.g. ref = (0, 0, 20) to place the sbend straight section under 20 degree w.r.t. the local x-axis. Setting ref to pin2 will use pin2 rather than pin1 as reference.

    Ronald

    Ronald
    Keymaster

    Dear Daneel,

    Would you have a picture to clarify the question?

    As a general remark: The point-to-point interconnect sbend_p2p(pin1, pin2) takes pin1 as the direction of horizontal part of the sbend and ignores the angle of pin2. If pin2 has a wrong angle to connect to the sbend output smoothly, this will show up as an angle error in the pin2pin DRC. You can switch the DRC on using nazca.pin2pin_drc_on(). By default the sbend will add an extra bend to pin2 to ensure a smooth connection.

    Ronald

Viewing 20 posts - 21 through 40 (of 142 total)