## Forum Replies Created

Viewing 20 posts - 1 through 20 (of 191 total)
• Author
Posts
• in reply to: PDK Implementation utilising demofab as a base #6868
Ronald
Keymaster

Dear Peter,

A PDK can be seen as a module under Nazca Design under its original license. Demofab PDK is part of the Nazca installer on the website as this make the demofab distribution a single install.

To create your separate PDK file that installs as a Python package, you need to make a Python install file as for any Python module/package.

As for the building blocks, you indeed need to add them to pdk_30_BB_library.py for best practice.

Ronald

#6754
Ronald
Keymaster

Dear bhchoi98,

I think this was solved in the answer to Pervaiz:

If you follow the installation on https://nazca-design.org/installation/ you should be fine.

“pip install nazca” is an unrelated package to Nazca Design.

Ronald

in reply to: Connecting two different pins including rotation #6712
Ronald
Keymaster
in reply to: Place a cell along the interconnect path #6681
Ronald
Keymaster

Dear Tommaso,

Thank you for the wishes.

Interconnects are based on (curved) lines. A line can be assigned a (varying) thickness and thus becomes a waveguide (or metal line). Lines can be described in countless ways. Compare for example a curved Nazca cobra interconnect with a straight line. Finding positions along a line at equal distance requires in generic terms a parameterized description of the line [x(t), y(t)] that traces the line at a constant speed w.r.t. to parameter t. However, it is unlikely that the length of an arbitrary line is linear in t by default. For that to happen we would need condition “A” to be true:

A: (dx(t)/dt)^2 + (dy(t)/dt)^2) = C^2,

where C is a constant and C * dt describes the (local) distance between points on the line.

Fortunately, we can enforce condition “A” across the whole line having the same C by defining a new parameter s and function t = f(s) such that we obtain condition “B” for all s:

B: (dx(f)/df * df(s)/ds)^2 + (dy(f)/df * df(s)/ds)^2 = C^2.

If we consider an interconnect to be a series of N connected (and possibly different) line definitions (e.g. bend, straight, straight, bend etc), indexed as i = 0, … N-1, we need to create a function fi(s) for each line such that all N lines now have the same C value under condition “B”. Here we assume f(s) covers the full t domain monotonously. We also need to know the length of each line in the series to get to the same C along the whole interconnect. In short, we need to reparameterize all interconnect elements from t to s following “B”.

Obtaining exact positions along the interconnect may have varying difficulty levels depending on the line type. A straight line of length L described as x(t) = t * L, where t in [0, 1] will have C = L under condition “A”. If C is no longer free to choose we apply condition “B” and use t = f(s) = a * s and obtain L * a = C and find a = C / L.
Hence, the linearized and normalized “constant speed” parametric straight line in s can be expressed as
x(s) = s * C, where s in [0, L / C].

Similarly, for an arc bend defined by [R*cos(phi*t), R*sin(phi*t)], with t in [0, 1] and reparametrization with t = f(s) = a * s we obtain a = C /(phi * R) and the linearized and normalized “constant speed” parametric bend in s becomes
[x(s), y(s)] = [R*cos(s*C/R), R*sin(s*C/R)], for s in [0, phi*R/C]

For curves without an analytical solution we would need to resort to a (ideally generic) numerical solution to reparametrize them.

Still, when placing elements along the interconnect, as you describe in your question, one needs to be able access all the interconnect elements (line definitions) in the first place, and calculate [x(s), y(s)] for a certain list of s values. The good news is that Nazca already has been testing since last summer an interconnect upgrade which is a dict-based description of all the elements in an interconnect (versions > 0.5.13). Hence, you can simply scan through the those line elements, apply the reparametrization as described above, and generate equidistant positions along the interconnect. More elegantly, one would create an output terminal that generates these positions when provided with the interconnect dict as input. The default terminal generates gds layout. In addition, a final solution would store reparametrization options inside the interconnect line definitions from the start, or as a generic filter function. Reparameterization is also already an ongoing development in Nazca for other purposes. The website release is a bit overdue, not because little has happened, but rather the opposite.

Ronald

in reply to: Merge two structures #6645
Ronald
Keymaster

In the case of merging polygons in a cell, the cell_iter() can help out, like described in the example below. The merge_cell_polygons function returns a new cell with the merged result, although the pyclipper may not handle all cases as desired for GDS.

``````
import nazca as nd
from collections import defaultdict

def merge_cell_polygons(cell):
"""Flatten a cell and merge polygons per layer.

Args:
cell (Cell): cell to flatten and merge polygon of.

Returns:
Cell: flattened cell with merged polygons per layer.
"""
layerpgons = defaultdict(list)
for P in nd.cell_iter(cell, flat=True):
if P.cell_start:
for pgon, xy, bbox in P.iters['polygon']:
layerpgons[pgon.layer].append(xy)
with nd.Cell(name=f"{cell.cell_name}_merged") as C:
for layer, pgons in layerpgons.items():
merged = nd.clipper.merge_polygons(pgons)
for pgon in merged:
nd.Polygon(points=pgon, layer=layer).put(0)
return C

with nd.Cell('test') as my_cell:

merged_cell = merge_cell_polygons(my_cell)
merged_cell.put()``````

Ronald

in reply to: Merge two structures #6632
Ronald
Keymaster

Dear Neetesh,

The picture helps, thanks. This can be approached analogous to the thread remove holes.

Note that if you intend to draw just polygon shapes rather than circuit elements, it is better to use nd.Polygon() objects in Nazca and the nd.geometries module for geometry helper functions, instead of nd.strt() and nd.bend() structures. In the former case, one can manipulate polygons directly with the nd.pyclipper module. Also check out the merge_polygons function in the inverted MMI tutorial.

In the “bend and strt” case you first have to pry the polygons out of the bend or strt cells. Note also that bends and strt are circuit elements, which have a much larger overhead associated with them. This becomes more noticeable when you use (very) large numbers of them.

Ronald

in reply to: Using “xsections” with a custom building block #6615
Ronald
Keymaster

Dear Milan,

You can try `print(nd.get_xsection('myXS').mask_layers)` to see layer info for ‘myXS’.
It returns a Pandas DataFrame like this:

``````           xsection  layer  datatype  tech  ...  growy1  growy2  accuracy  polyline
layer_name                                  ...
layer1         myXS      1         0  None  ...     0.0     0.0     0.001       0.0
NoFill         myXS      2         0  None  ...     0.0     0.0     0.100       0.0``````

Ronald

in reply to: Get global pin coordinates #6614
Ronald
Keymaster

Dear Dominik,

You are correct. Thanks for adding that. The cell_iter() by default will not drill down identical branch signatures more than once. In case you would like to visit all branch copies, e.g. to find all instances, the revisit=True will indeed visit the whole cell_tree. Hence, revisit identical branch signatures, mounted on different parts of the celltree. Another way to visit all instances is to use cell_iter(…, flat=True) or cell_iter(…, hierarchy=’flat’).

Ronald

in reply to: Get global pin coordinates #6612
Ronald
Keymaster

Dear Dominik,

The cell_iter() can be of help. It can locate all elements in a celltree with respect to a topcell.
Place this before nd.export_gds():

``````for params in nd.cell_iter(cell_top):
if params.cell_start:  # check if we are on a cell_open pass (or check for a cell_close).
if params.cell.cell_name == "Device":  # check if we are in the right cell
pointer, flip = params.transflip_glob  # get cell's translation (pointer) and flip w.r.t. cell_top
for name, pin in params.cell.pin.items():  # print all pin names and locations
print(name, pointer.copy().move_ptr(pin.pointer)))  # add pin position in the cell to the cell translation.

# output:
org Pointer:    (x = 0.00, y = 20.00, a = 0.00Â°)
gc1 Pointer:    (x = 0.00, y = 20.00, a = 180.00Â°)
gc2 Pointer:    (x = 100.00, y = 20.00, a = 180.00Â°)
a0 Pointer:     (x = 0.00, y = 20.00, a = 360.00Â°)
b0 Pointer:     (x = 0.00, y = 20.00, a = 360.00Â°)``````

The .copy() is needed to not change the original pointer with the .move_ptr().
The ‘org’ pin is always created as cell origin.
The ‘a0’ and ‘b0’ pins are default Nazca pins. You can get rid of those using cell_device.default_pins(‘gc1’, ‘gc2’) inside the cell_device cell definition.

Ronald

in reply to: Using “xsections” with a custom building block #6608
Ronald
Keymaster

Dear Milan,

A “NoFill” layer to protect from tiling is a specific layer. You can add that to your xsection definition and use interconnects. Whenever you use that interconnect your get the NoFill layer.

In another case, when working directly with Polygon objects, you can grow the Polygon (with pyclipper installed) and redirect the result to the NoFill layer.

The above solutions look like this when applied to the MMI polygon tutorial:

``````import nazca as nd

grow = 5.0  # NoFill clearance

# create a layers and xsections:
nd.add_layer(name='NoFill', layer=2, accuracy=0.1)  # NoFill can be course, here 0.1 um resolution
ic = nd.interconnects.Interconnect(xs="myXS", width=2.0, radius=10.0)  # create interconnect

# create a building block (cell) from Polygon points and add the NoFill layer
with nd.Cell('building_block') as bb:
bb_body = [
(5.0, -5.0), (5.0, -1.0), (0.0, -1.0), (0.0, 1.0),
(5.0, 1.0), (5.0, 5.0), (35.0, 5.0), (35.0, 3.5),
(40.0, 3.5), (40.0, 1.5), (35.0, 1.5), (35.0, -1.5),
(40.0, -1.5), (40.0, -3.5), (35.0, -3.5), (35.0, -5.0)
]
poly = nd.Polygon(points=bb_body, layer='layer1')
poly.put(0)
poly.grow(layer='NoFill', grow=grow).put(0)  # add NoFill layer based on original Polygon.

nd.Pin('a0').put(0, 0, 180)
nd.Pin('b0').put(40, 2.5, 0)
nd.Pin('b1').put(40, -2.5, 0)
nd.put_stub()

#  draw MMIs and interconnects and automatically get NoFill layers:
bb.put(0)
mmi = bb.put('b1')
ic.strt(length=10).put(mmi.pin['a0'])
ic.bend(angle=90).put()

nd.export_gds(filename="nofill")``````

A less surgical alternative is to use the cell’s bounding box and fill it with the NoFill layer (not shown in the example above).

Ronald

in reply to: Using “xsections” with a custom building block #6606
Ronald
Keymaster

Xsections are global objects. As such you do not have to add them to cells as a xsection before you can use them. You can just use them when they are defined, see xsections and layers.

Note that Polygon objects, as used in the example you refer to, do not work with xsections. They work with layers.

Conceptually a xsection is mostly applied to represent a waveguide or metal guide, consisting of one or more layers, captured in a xsection. Polygons are more free form geometries for drawing, like when used in a BB as in the example.

Ronald

#6602
Ronald
Keymaster

Dear Cecil,

When using gds hierarchy under (no multiple of 90 degree) angles or in between grid-point translations, the snapping errors (better: delta’s) you see are fundamentally caused by looking at two non-aligned discrete coordinate systems on top of each other.

Interconnects within a single cell are by default not instantiated and snap to the same parent grid. In contrast, the Vipers are now child cells in your example, each in their own grid system. If you flatten topcell “nazca” in your example you will enforce a single parent grid to snap to for all structures in cell “nazca”.

Ronald

#6595
Ronald
Keymaster

Dear Jon,

The interconnect error flags in layer 1500 can be fine tuned to have zero false positives. They are essential for design validation in my experience. That said, there is no full description I can point to yet.

Basically, the interconnection DRC can check in pin2pin connections for xs, width, angle, radius and symmetry violations. Each of those can be fine tuned, e.g. for metal xsections it is safe to switch of width DRC, or to allow metal of xsection type-1 to connect to metal of xsection type-2.

In case you connect two different xsections or widths on purpose, like the tapers you mention, you can use the move() method on a pin to indicate this is desired, e.g. for a Node (pin) variable p you can do p.move(width=7.0) and set up a new pin width of 7.0 for the DRC, or, similarly p.move(xs=’newxs’). This “moves” pin properties in a similar (syntax) way as moving x, y and a coordinates. A quick and dirty way is to silence all pin2pin DRC in a specific placement is to use .put(…, drc=False), but you may now hide true positives as well.

Ronald

in reply to: Put a cell into a different one #6594
Ronald
Keymaster

Dear ale35,

It would be a bad habit to change a cell after creation, nor should it be needed in any normal design situation. It’s a bit like the Terminator movies, as you are changing history.

Technically, it is possible to update a cell after closure (and there are some nifty use cases), but it is advised to not do this as you may create inconsistencies and, again, most likely it is not needed.

Could you indicate why cell1 needs an update with cell2 after closure, in order to find an alternative solution?

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”, because 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()

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

#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()

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

in reply to: Issues with length of Euler bend #6591
Ronald
Keymaster

Dear Jon,

A correct observation. This has been fixed a while ago in the development version and will make it in the new public Nazca release. That release will also have more advanced Euler bends that by default fix euler-bend scaling as well as provide automated transitions to arc-bends if a specified mininum bend radius is reached. The updated Euler will also use a better discretisation setting for the specified mask resolution.

Ronald

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,

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`.