Forum Replies Created

Viewing 20 posts - 21 through 40 (of 194 total)
  • Author
    Posts
  • in reply to: Waveguide Cross-section and Mode Solver #6571
    Ronald
    Keymaster

    Dear Heiner,

    Good to hear Nazca helps you with your work.

    You certainly have a relevant question on xsections and index models. Nazca has functionality to define the geometrical cross-sectional structure of a waveguide and connect it to a modesolver, e.g. for obtaining the Neff of modes, precisely as in your questions. The package demofab inside Nazca does contain examples of two rib-style xsections in file pdk_15_technology.py.

    This so far has been based on a simple symmetric slab solver, ridge stacks and the effective index method (EIM). This early xsection and solver implementation was mainly done to play with Nazca’s internal architecture and user syntax, and explore possible paths to connect that to mask layout for later development.

    Over the last year or so we have been (and are) upgrading as intended this waveguide cross-sectional geometry and index calculation architecture to be able to connect it to basically any solver with at least a command-line interface on board or, alternatively, a mathematical model of your waveguide index. As this topic has been considered a beta feature there are no tutorials on it. It actually may not work as intended in the last few releases.

    In summary, it would require a few months of patience to have this updated index model and solver handling available in the public Nazca release. If it moves to the release state there will also be a tutorial on it.

    Ronald

    in reply to: How to customize the top cell name #6569
    Ronald
    Keymaster

    Dear David,

    The advised way to export an explicit topcell is to specify it in export_gds() like

    import nazca as nd
    
    with nd.Cell("mycellname") as MYCELL:
        #stuff
    
    nd.export_gds(topcells=MYCELL)

    The keyword “topcells” can also take a list of cells, in such case effectively creating a gds library.

    It is possible to change the default “nazca” name, but there is no real need for that:
    nd.cfg.defaultcellname = "NAZCA"

    Ronald

    in reply to: Broken / Disjointed interconnects from tutorial #6547
    Ronald
    Keymaster

    Dear japon,

    Good that you ask, as more people seem confused about this feature.

    That offset between a bend and straight waveguide is intended in this demo interconnect. In various technologies (like InP) this offset is often needed for an optically smooth transition (looking “disjunct” geometrically). The light (mode) can be thought of as being swept to the outer side wall of the bend. Nazca takes care of this offset for you. Many designs made in other tools tend to be wrong by not having this offset automatically (you have to add it manually if you happen to know this) leading to underperforming photonic circuits (excess loss and higher order mode generation). As this is a demo interconnect, the offset is a bit exaggerated in size.

    Note1: The arrows at the pins from straight to bend indicate this connection is correct (intended): they are aligned.

    Note2: There are no arrows inside the sbend element (by choice) as this is a single interconnect.

    The offset is a property ‘os’ of the xsection, here xsection “Shallow” in demofab. When creating an interconnect demo.shallow (in the demofab PDK) from xsection “Shallow” that property is automatically copied into the interconnect as property ‘offset’. To not have an offset, either define a xsection without ‘os’ or ‘os=0’ in the first place, or in this case when it is already added to an existing interconnect, simply switch of the offset in the interconnect via demo.shallow.offset = 0.

    Ronald

    in reply to: Manhattan routing using interconnect? #6536
    Ronald
    Keymaster

    Hi Lumico,

    Manhattan routing is what you get when you connect pins that are multiples of 90 degrees for most interconnects. Avoid cobra_p2p(), bend_strt_bend_p2p(), vipers and mambas. There are ways to restrict sbend() or sbend_p2p() angles to 45 degrees with the Amax keyword.

    To understand your question better, would you have a picture on what you would like to achieve or expect and what you get?

    Maybe you refer to routing groups of waveguides?

    Ronald

    in reply to: Hashme things to watch out for #6514
    Ronald
    Keymaster

    Dear Jon,

    The hashme syntax to use is indeed as described in your code snippet, and as described in the hashme tutorial.

    The decorator stores the function profile on a 1-level deep stack. This information is consumed by the first nd.Cell() encountered (possibly unwanted in a subroutine), and deleted at function closure.

    In case (1) you describe, the nested hashme calls, the second hashme will override the first, so by the time you want to use the first, it is gone. This can be avoided placing calls after the with nd.Cell() statement. Note that hashme=True keyword assignment is optional in newer Nazca versions since about 2020.

    In case (2) I think the hashme is not closed yet and this messes up the stack.

    Hence, the hashme creates a 1-level max deep stack and this info is consumed by the first nd.Cell() statement. The stack can probably be made deeper to allow for nested calls to work as well (although a similar result can be achieved by placing function calls in a decorated function after the with statement as mentioned, but the designer has to do the housekeeping).

    Ronald

    in reply to: Mandatory xsections #6512
    Ronald
    Keymaster

    This, for example, works just fine. Also in 0.5.13.

    import nazca as nd
    nd.bend().put()

    It automatically creates a default xsection = “nazca”, as in earlier releases.

    Ronald

    in reply to: Matching interconnect lengths #6510
    Ronald
    Keymaster

    Dear Ruslan,

    Good to hear Nazca works well for you.

    Starting with your last image: Nazca uses outward pointing connections by definition. If you define your own block/cell, make sure you define pins pointing outwards from the circuit element. This would never lead to confusion of bend directions. Bend directions follow the standard mathematical convention of positive angles for counter clockwise rotation. Moreover, Nazca avoids quite nasty global layout states, e.g. no global cross section or global flip states, nor mixed in- and outward pin/port directions as mentioned above.

    For the dotted L1 line case, you can just add an upward bend to the two blocks you connect this particular shape to. Connect the two bends with ubend_p2p(length=L), where L is the length of the straight section, like in a sliding trumpet, used to extend the shape.

    For length extraction in two port elements/cells you can use the nd.trace module, for geometrical path lengths. To optimize the length of a section you can use a root or minimizer solver, as discussed in this trail_cellpost. The trial cell decorator shown there will be part of the nd.bb_util module in Nazca >0.5.13. (@nd.bb_util.trial_cell).

    If you want to trace through interconnects and blocks/cells you can resort to the pathfinder module. That would need a dedicated tutorial to explain how to use it though. It can handle geometrical, optical and electrical path information.

    Ronald

    in reply to: How to measure and control AMZI imbalance #6491
    Ronald
    Keymaster

    Dear Marcin,

    That’s indeed the way forward. Though, the mmi ‘a0’ pin is connected to itself in your example. If you also use it as start pin you will have a zero length path only. If I use the below in nazca.0.5.13 (nd.findpath(...)) it does what I expect.

    nd.connect_path(left_mmi.pin['a0'], right_mmi.pin['b0'], 1.0) # note the 'b0'

    One can not use an “opt” connectivity without implementing compact models in the PDK under the “opt” connections. Something using “trace”, along this post, could be quite useful. You can multiply the geometrical lengths with your own compact (refractive index) model. The MMI you can not trace, as the trace method is intended for 2-port components only, but you do use what you know about its phase transmission to set your target arm diff.

    Regards,
    Ronald

    in reply to: The system cannot find the file specified #6490
    Ronald
    Keymaster

    Dear RuiM,

    The functions you mention are not Nazca functions, but I guess from someone’s private PDK “nazca_imos”. You would have to ask the PDK owner about what happens.

    Regards,
    Ronald

    in reply to: How to measure and control AMZI imbalance #6486
    Ronald
    Keymaster

    Dear Marcin,

    You follow the right approach, though note that paths have to be defined in the building blocks you use. The message says that cell sp_mmi1x2_dp has no path information (no tracker), so the pathfinder can not continue. A Nazca path can track various connection types (tracker, e.g. for optical or electrical netlists, or polarization states). Demofab in nazca.demofab has some basic examples, using connect_path().

    Note that in Nazca.0.5.13 the nd.pathfinder() function has been renamed nd.findpath() to avoid confusion with the module already named pathfinder.

    See also this trial cellsthread on MZI optimization.

    Regards,
    Ronald

    Ronald
    Keymaster

    Hi A,

    Below is an example to achieve what you want. Depending on your polygons etc. you may want to add a more fancy polygon analysis, but that is not Nazca specific.

    import nazca as nd
    import nazca.geometries as geom
    from nazca.interconnects import Interconnect
    
    def rect1(layer_n=1):
        rect_shape = nd.geom.rectangle(length=20, height=5)
        with nd.Cell('rect1') as cell:
            nd.Polygon(layer=1, points=rect_shape).put(0)
            nd.Polygon(layer=10, points=rect_shape).put(2, 10)
            # guide=ic.sbend_p2p(pin1=r1, pin2=r2.pin['a0']).put()
        return cell
     
    rect1().put(array=[4, [30, 0], 2, [0, 20]])  # This could be a gds_load() based cell.
    
    # 1. Extract all the polygon from cell into a dict with key (layer, x, y),
    #    where x and y will be, just a choice, the geometrical average of the polygon points.
    polygons = {}
    for params in nd.cell_iter(nd.cfg.topcell, flat=True):
        for pgon, points, bbox in params.iters['polygon']:
            xp, yp = zip(*points)
            xavg, yavg = round(sum(xp) / len(xp), 2), round(sum(yp) / len(yp), 2)
            polygons[(pgon.layer, xavg, yavg)] = points
            
    # 2. Analyse polygons: order them and extract positions for pins.
    for i, ((layer, x, y), points) in enumerate(sorted(polygons.items())):
        nd.text(str(i), height=2, align='cc', layer=0).put(x, y)
        sort = sorted(points)
        x0 = sort[0][0] + 0.5 * (sort[1][0] - sort[0][0])
        y0 = sort[0][1] + 0.5 * (sort[1][1] - sort[0][1])
        w0 = abs(sort[1][1] - sort[0][1])
        nd.Pin(f'a{i}', width=w0).put(x0, y0, 180)
        x1 = sort[-1][0] + 0.5 * (sort[-2][0] - sort[-1][0])
        y1 = sort[-1][1] + 0.5 * (sort[-2][1] - sort[-1][1])
        nd.Pin(f'b{i}', width=w0).put(x1, y1, 0)
    
    # 3. Add interconnects.
    ic = Interconnect(radius=5.0, width=w0)
    for i, (name, pin) in enumerate(nd.cfg.topcell.pin.items()):
        if i in [0, 1, 2, 3, 4, 5]: 
            ic.bend_strt_bend_p2p(
                pin1=nd.cfg.topcell.pin[f'b{i}'], 
                pin2=nd.cfg.topcell.pin[f'a{i+10}'],
            ).put()
        
    nd.export_plt()

    Note that it would make sense to put item 2 and 3 in the script above inside a new cell with nd.Cell("newcellname") as C:, and work with cell C, rather than working on the default topcell under nd.cfg.topcell.

    Ronald

    Ronald
    Keymaster

    Hi A,

    Thanks for the picture.

    Do you know the polygon positions in advance, or would you have to extract them from the gds?

    Ronald

    Ronald
    Keymaster

    Dear A,

    A picture would help, but in general, for using interconnects in an existing gds one has load the gds in a Nazca cell and assign Nazca pins to the cell at positions in the gds that matches the polygon positions to give a meaning to it. This tutorial on creating a building block from gdsmay help.

    For implementing xsection in one or more specific gds layer(s), which you can use in interconnects see xsections_and_layers.

    There is also a tutorial on interconnectson how to use the interconnects to connect pins in your layout.

    For general concepts see getting_started.

    Regards,
    Ronald

    in reply to: Accuracy of path lengths & length_geo attribute #6475
    Ronald
    Keymaster

    Dear Jon,

    Thanks for the question. I think there are a number of dimensions to this problem: gds hierarchy, arc length and shape area. Thanks for mentioning the area script.

    Starting with the hierarchy. If you build a structure using multiple cells, each cell will snap floating point positions to the local integer cell grid. If you connect those cells again floating point accurate to other cells or each other, their grids likely have an offset (translation and/or rotation) with respect to each other, such that the grid nodes do no exactly overlap. This leads to overlaps or gaps of shapes of max sqrt(2)*gridsize. The most straight forward way to resolve this is the use a single grid to snap all structures on, i.e. by flattening them in to a single cell. For a series of Nazca interconnects this is easy to do. An alternative way is to wait until mask export and reposition an instance such its grid matches the parent cell, while relocating everything in the cell in the opposite direction. This may require to generate multiple copies of the cell.

    As for the length of a waveguide, the center line of a waveguide, which can be curved, is discretized in a linear piecewise fashion to create a shape, which in the limit of an increasing number of elements will give the, say, mathematical length. Each floating point linear segment is slightly shorter than the arc points it connects, so the sum of the elements on a circular arc is a bit shorter than the mathematical length it represents. (Although the lines can also cut the arcs such that the arc is equally right and left of the line segments.) In gds export those points have to snap to the gds integer grid. This step actually may lead to a slightly larger, but more likely, again a slightly shorter, center line. It depends on how the smooth curve shape is positioned on the grid. The geometrical length stored in the element in Nazca is its mathematical length, not the sum of the linear elements. Note the linear elements could be considered before or after grid snapping.

    Finally, the area. A waveguide will have two egde polygons, for which the same arguments apply as for the center line. From the waveguide area, the arc length can be extracted, as you indicate. For a grid of 1 nm the area can be filled in steps of 1 nm^2. The gds tile shape still has to follow the waveguide, so in practise the area steps with be several times that resolution when changing the width or radius of the waveguide. To match the area in the gds such that it yields the correct center line length may need a tiny adaptation of the radius and or width. The adaptation depends on the deviation of the extracted arc length from the mathematical length. Note this has to be on the final gds grid, i.e. the toplevel grid after flattening, or on a cell whose grid overlaps the topcell grid, to not change the result after flattening.

    That said, your increased resolution to 1e-12 will effectively lead to higher sampling on a finer grid, and all curves will be smoother and closer to their mathematical length. So yes, it works. However, if you send your mask off to the mask vendor they will regrid your mask to their process resolution, which brings you back to square one, e.g. the 1 nm resolution. Hence, ideally you know exactly what happens to your gds before it enters the litho.

    Also, be aware that the effective refractive index in curves is different from that in straight waveguides. This may lead to significant phase errors when not taken into account.

    Regards,
    Ronald

    in reply to: Show Pins outside the cell definition #6467
    Ronald
    Keymaster

    Dear Tommaso,

    Some updates will be available in nazca >0.5.13 to add or hide stubs after cell creation (in closed cells).

    The put_stub function has been ported as a Cell method, hence, you can now also say C.put_stub() where you use nd.put_stub(). Along this path you can add stubs as cellA.put_stub() after the cell is closed; By exception, as normally you will get (a desired) Python Exception when connecting to a closed cell directly (in a circuit you would connect to an instance of the cell, not the cell itself). Still, it is advised not to add stubs after cell closure, because such changes will not be reflected in the bounding box of the cell, which may even lead to netlist errors when stubs/pins are placed outside the bbox.

    Better is to place all the stubs you may want to visualize inside the cell during cell creation, as would be the normal situation. Then use the new cell method hide_stub(), which will exclude stubs and their annotation from export; The hide_stub method will be allowed to operate on closed cells.

    An other way to hide stubs, also used in Nazca PDKs, is placing them in logical layers and just switch them off in the layer view. This will affect all stubs at once, and not a selected few though.

    Ronald

    Ronald
    Keymaster

    As of Nazca 0.5.13 a loaded gds file in Nazca will automatically scale to the gds database unit setting you are working in (the active gds_db_ unit), with respect to the setting in the loaded file. This can be additionally changed (as before) by setting the “scale” keyword in load_gds() to a factor other than 1.0.

    Ronald

     

    in reply to: How to open and modify a gds? #6458
    Ronald
    Keymaster

    Hello yw3a14,

    This tutorial will be helpful.
    Basically, create a new cell, load the gds with load_gds().put() and create and put pins with nd.Pin().put(). You can also flatten the gds in your new cell using instantiate=False:

    import nazca as nd
    
    with nd.Cell(name='cellname') as C:
        nd.load_gds(
            filename='<filename>',
            cellname='<cellname>',
            instantiate=False, 
        ).put(0) # puts (0, 0) in the gds at (0, 0) of cell C.
    
        # Add Pins
        nd.Pin('<name>', width=<width>, xs=...).put(<x>, <y>, <angle>) 
        ...
    
    C.put(0)

    Ronald

    in reply to: Create trial cells when using minimizer algorithm? #6446
    Ronald
    Keymaster

    A decorator can clean up the code a bit like this:

    def trial_cell(func):
        def wrapper(*args, **kwargs):
            cell = kwargs.pop('cell', False)
            if not cell:
                S1 = set(nd.cfg.cellnames.keys())    
            out = func(*args, cell=cell, **kwargs)
            if not cell: 
                S2 = set(nd.cfg.cellnames.keys())
                for s in S2 - S1:
                    nd.cfg.cellnames.pop(s)
            return out
        return wrapper
    
    @trial_cell
    def cell_to_solve(offset=10, target_diff=20, cell=False):
        with nd.Cell('test') as C:
            m1 = demo.mmi1x2_dp().put(0)
            nd.trace.trace_start()
            demo.deep.sbend(offset=offset).put(m1.pin['b0'])
            demo.deep.sbend(offset=-offset).put()
            nd.trace.trace_stop()
            L1 = nd.trace.trace_length()
            m2 = demo.mmi2x2_dp().put()
            nd.trace.trace_start()
            demo.deep.sbend_p2p(pin1=m1.pin['b1'], pin2=m2.pin['a1']).put()
            nd.trace.trace_stop()
            L2 = nd.trace.trace_length()
            diff = L1 - L2
        if cell:    
            return C
        else:
            return diff - target_diff

    Ronald

    in reply to: Create trial cells when using minimizer algorithm? #6445
    Ronald
    Keymaster

    The resulting MZI as reference, for offset = 71.4428629.

    Ronald

    in reply to: Create trial cells when using minimizer algorithm? #6444
    Ronald
    Keymaster

    Dear Jon,

    The below MZI example in demofab may contain what you are looking for.

    A root-solver optimizes the MZI for a specific target geometrical path length difference. The MZI method behaves either like a function to optimize for a path length difference in the root-solver, or it returns a Nazca Cell. In the former case it deletes all cells generated in the call; Particularly related to your question, I create sets S1 and S2 from dict nd.cfg.cellnames just before and just after a cell iteration. This dict is leading in tracking cell warnings etc., and popping the new cells S2 – S1 will make Nazca forget about them.

    The example here uses the “trace” module for measuring geometrical interconnect length, but this concept also can be exploited in a more fancy way utilizing the pathfinder module and compact models, which is something that would need dedicated tutorials.

    As for your radius question, interconnects do have a build-in minimum radius check option, which is based on the xsection.minimum_radius attribute of the xs you assign to the interconnect.

    from functools import partial
    from scipy.optimize import fsolve
    import nazca as nd
    import nazca.demofab as demo
    
    def cell_to_solve(offset=10, target_diff=20, cell=False):
        """Create a temporary MZI cell to return the arm length difference or return the Cell.
    
        Free parameter here is the sbend offset.
    
        Args:
            cell (bool): False: return distance from target; True: Return MZI cell.
    
        Returns:
            Cell | float:
        """
        if not cell:
            S1 = set(nd.cfg.cellnames.keys())
        with nd.Cell('test') as C:
            m1 = demo.mmi1x2_dp().put(0)
            nd.trace.trace_start()
            demo.deep.sbend(offset=offset).put(m1.pin['b0'])
            demo.deep.sbend(offset=-offset).put()
            nd.trace.trace_stop()
            L1 = nd.trace.trace_length()
            m2 = demo.mmi2x2_dp().put()
            nd.trace.trace_start()
            demo.deep.sbend_p2p(pin1=m1.pin['b1'], pin2=m2.pin['a1']).put()
            nd.trace.trace_stop()
            L2 = nd.trace.trace_length()
            diff = L1 - L2
        if cell:
            return C
        else:
            S2 = set(nd.cfg.cellnames.keys())
            for dS in S2 - S1:
                nd.cfg.cellnames.pop(dS)
            return diff - target_diff
    
    def find_MZI_solution(target_diff, initial_offset):
        """Find an optimized offset to reach the target-diff in the MZI using a standard root solver.
    
        Args:
            target_diff (float): target armlength difference in MZI
            inititial_offset (float): starting offset to find a solution
        """
        func = partial(cell_to_solve, target_diff=target_diff)
        root = fsolve(func, [initial_offset])
        return func(offset=root[0], cell=True)
    
    MZI = find_MZI_solution(target_diff=50, initial_offset=70)
    nd.export_png(MZI)

    Ronald

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