Table Of Contents

Previous topic

Welcome to gdsCAD’s documentation!

Next topic

Examples

This Page

User’s Guide

Introduction

gdsCAD is a simple, but powerful, Python package for creating, reading, and manipulating GDSII layout files. It’s suitable for scripting and interactive use. It excels particularly in generating designs with multiple incrementally adjusted objects. gdsCAD uses matplotlib to visualize everything from individual geometry primitives to the entire layout.

This User’s Guide is intended to get you started with gdsCAD. It will introduce you to the package’s most basic objects, before showing you how to organize and manipulate groups of objects. More advanced devices can be found in the Examples page. Details of the interface, and information for developers can be found in The gdsCAD API pages.

Relationship to gdspy

gdsCAD is derived from gdspy by Lucas Heitzmann Gabrielli. The most significant difference is that gdsCAD adds the Layout to allow simultaneous work on multiple GDSII streams, and simplifies the import process. The saving scheme also allows the user to be lazy in maintaining Cell names, and the package is organized into submodules based on function. For its part gdspy has more advanced boolean and fracture functions not yet supported in gdsCAD.

A minimal script

Here is a minimal working script that creates a box and inserts it into a cell which is then added to a layout. The layout is saved as a GDSII stream in the file ‘output.gds’. This illustrates the general gdsCAD workflow of building geometry, adding that to cells, and then adding cells to a layout, before saving. In this example the layout is also displayed in a viewer:

from gdsCAD import *

# Create a Cell and add the box
cell=core.Cell('TOP')

# Create three boxes on layer 2 of different sizes centered at
# the origin and add them to the cell.
for l in (3,6,10):
    box=shapes.Box((-l,-l), (l,l), width=0.2, layer=2)
    cell.add(box)

# Create a layout and add the cell
layout = core.Layout('LIBRARY')
layout.add(cell)

# Save the layout and then display it on screen
layout.save('output.gds')
layout.show()

(Source code, png, hires.png, pdf)

_images/userguide-1.png

Primitive Objects for Drawing Art

There are only two basic objects for drawing art in a GDSII file: Boundary and Path. The object Text creates annotations that can be seen in design viewers, but will not print. All the other primitive classes (Cell, Elements, Layout, etc...) are for managing and organizing objects based on Boundary and Path.

Boundary

Boundary objects are filled, closed polygons. They correspond to the Boundary object defined in the GDSII specification. Boundaries are created by specifying the sequence of points that define the boundary outline. They are closed automatically.

The following code will create an L-shaped polygon:

from gdsCAD import *

points=[(0,0), (20,0), (20,10), (5,10), (5,20), (0,20)]
bdy = core.Boundary(points)
bdy.show()

(Source code, png, hires.png, pdf)

_images/userguide-2.png

Transformations

Boundaries can be copied, and subjected to the geometric transformations rotate, reflect, scale, and translate. There are two ways to make transformations to an object, the first is to use the class methods. These will apply the transformation in place, and the methods all return the object itself so it’s easy to chain transformations together. The alternative is to use the transformation functions found in the utils module. These will make a copy of the object and apply the transformation to the copy. These can also can be used on points or lists of points:

# Transform the body using methods
bdy2 = bdy.copy()
bdy2.scale(2).rotate(45).translate((20,20))
bdy2.layer=2     # Change the layer so that we can see it more easily
bdy2.show()

# Transform the body using the transformations in utils
bdy3 = utils.translate(bdy, (30,0))
bdy3.layer = 3
bdy3.show()

(Source code, png, hires.png, pdf)

_images/userguide-3.png

Bounding Box

The bounding_box attribute tells you the smallest bounding box which the object can fit within in the form [[minx, miny], [maxx, maxy]]. In the figure below the green line shows the red shape’s bounding box.

(Source code, png, hires.png, pdf)

_images/userguide-4.png

Internal Voids

Note that red Boundary shown above is technically illegal, since in the GDSII specification Boundaries cannot be self intersecting, or have internal voids. The way in which such shapes will render is indeterminate. Voids can be created legally, by using XOR postprocessing, or through the use of “keyhole” type geometries:

# Inner box: CCW point order
inner_box = [[1,0], [1,1], [-1,1], [-1,-1], [1,-1], [1,0]]

# Outer box: CW point order
outer_box  = [[2, 0], [2, -2], [-2,-2], [-2, 2], [2,2], [2,0]]

# Join boxes together
points = inner_box + outer_box
bdy = core.Boundary(points)
bdy.show()

(Source code, png, hires.png, pdf)

_images/userguide-5.png

Layers and Datatypes

Boundaries, and all other drawing objects, have layer and datatype attributes like GDSII elements. These can be specified as optional keyword arguments when the object is initialized. If absent layer and datatype are assigned values based on core.default_layer and core.default_datatype. Alternatively, they can be adjusted after the object is created by assigning a new value to the obj.layer attribute:

from gdsCAD import *

points=[(0,0), (10,0), (10,10), (0,10)]
bdy = core.Boundary(points)
bdy.show()

points2=[(10,0), (20,0), (15,10)]
bdy2 = core.Boundary(points2, layer=2)
bdy2.show()

points3=[(0,10), (0,20), (10,15)]
bdy3 = core.Boundary(points3)
bdy3.layer = 3
bdy3.show()

core.default_layer=4

points4=[(10,10), (20,10), (20,20), (10,20)]
bdy4 = core.Boundary(points4)
bdy4.show()

(Source code, png, hires.png, pdf)

_images/userguide-6.png

Path

In contrast to a Boundary which is closed and filled, a Path is unfilled and may be open. They are often employed for drawing wires or other fine electrical connections. Paths have a finite width given by third parameter.:

points=[(-10,0), (0,20), (10,0)]
pth = core.Path(points, 0.5)
pth.show()

(Source code, png, hires.png, pdf)

_images/userguide-7.png

Paths can have different endpoint styles specified by the optional pathtype argument, however because of how they are implement Paths are always shown with rounded endcaps. Thus designs that depend critically on the endpoint geometry should be checked in an external GDSII viewer.

Like Boundaries, Paths cannot legally self intersect.

Derived Objects for Drawing Art

gdsCAD provides several higher order classes for conveniently creating common objects. These are contained in the module gdsCAD.shape and are derived from the base classes core.Boundary and core.Path.

Rectangle and Box

These two classes create filled and unfilled rectangles respectively. The are defined by the positions of two opposite corners, and in the case of Box, the width of the path:

rect=shapes.Rectangle((-10,-10), (0,0))
box=shapes.Box((0,0), (10,10), 1.0, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-8.png

Again, they can transformed through simple geometrical transformations:

rect.rotate(45, center=(-5,-5))
box.scale(3).translate((-14,-14))

(Source code, png, hires.png, pdf)

_images/userguide-9.png

Disk and Circle

These two classes create filled and unfilled circles. They are defined by their center position and radius:

disk=shapes.Disk((-5,-5), 5)
circ=shapes.Circle((10,10), 10, 0.5, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-10.png

It’s possible to draw a disks with a hollow inner radius, this is constructed using a keyhole geometry so it does not defy the restriction that Boundaries cannot have internal voids:

disk=shapes.Disk((0,0), 5, inner_radius=2.5)

(Source code, png, hires.png, pdf)

_images/userguide-11.png

It is possible to draw only segments of both Circles and Disks by specifying an initial and final angle:

disk_arc = shapes.Disk((-5,-5), 5, initial_angle=0, final_angle=90)
circ_segment = shapes.Circle((10,10), 10, 0.5, initial_angle=180, final_angle=270, layer=2)
circ_arc=shapes.Disk((-5, 5), 5, inner_radius = 2, initial_angle=90, final_angle=270, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-12.png

Regular Polygons and Polylines

The two classes RegPolygon and RegPolyline make filled and unfilled regular polygons respectively. The call signature is much the same as for Disk and Circle, with the addition of a parameter N to specify the number of sides:

hex = shapes.RegPolygon((10,10), 15, 6)
pent = shapes.RegPolyline((-10,-10), 20, 5, 2, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-13.png

Making Annotations

There are two ways of adding text to a design: core.Text and shapes.Label or shapes.LineLabel.

Non-printing core.Text

The class Text permits notes and annotations to be added to a design. These will not be printed on the mask are intended only for clarification during the design process. Since they are not true drawing geometry they do not scale in a physical manner with other drawing geometry.

(Source code, png, hires.png, pdf)

_images/userguide-14.png

Printing shapes.Label

On the other hand, annotations made with Label will print with other mask art. In addition to indicating the layer, string to print, and the position Label, requires a text size in user units.:

top = shapes.Label('TOP', 1, (-1, 4), layer=2)
bottom = shapes.Label('BOTTOM', 2, (-5,-5), layer=2)
left = shapes.Label('LEFT', 1, (-4,-2), angle=90, layer=2)
right = shapes.Label('RIGHT', 1, (4,2), horizontal=False, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-15.png

Printing shapes.LineLabel

As an alternative way of drawing text, a line based vector font is provided. Internally it is directly based on the Hershey Vector Font so a large variety of fonts and symbols are available. While LineLabel looks nicer in most cases it is not a monospace font and hence not suited for all use cases.

The LineLabel allows mostly the parameters of Vector but no rotation by angle:

top = shapes.LineLabel('TOP', 1, (-1, 4), layer=2)
bottom = shapes.LineLabel('BOTTOM', 2, (-5,-5), layer=2)
left = shapes.LineLabel('LEFT', 1, (-4,-2), horizontal=True, layer=2)
right = shapes.LineLabel('RIGHT', 1, (4,2), horizontal=False, layer=2)

(Source code, png, hires.png, pdf)

_images/userguide-16.png

Many other fonts can also be selected. Note the use of add_text to add more text to the `LineLabel`:

FONTS = ['cyrilc', 'gothgbt', 'gothgrt', 'gothitt', 'greekc',
         'greekcs', 'greekp', 'greeks', 'italicc', 'italiccs',
         'italict', 'romanc', 'romancs', 'romand', 'romanp',
         'romans', 'romant', 'scriptc', 'scripts']
TEST_TEXT = 'gdsCAD is great. Vector fonts are great too! "%s"!\n'

label = gdsCAD.shapes.LineLabel('', 10)
for font in FONTS:
    label.add_text(TEST_TEXT % font, font)

(Source code, png, hires.png, pdf)

_images/userguide-17.png

Organizing Art

gdsCAD provides three different schemes for collecting different pieces of artwork together: Elements, Cell, and Layout.

Elements

The Elements object is essentially a list of drawing elements. Elements allow groups of objects to be conveniently transformed as one. All elements in the list are coerced to have the same layer, and changing the layer of the Elements object changes the layer of all it’s members:

one = shapes.Box((-10,-10), (10,10), 0.5)
two = shapes.Rectangle((-10,-10), (0,0), layer=2)
three = shapes.Disk((5,5), 5, layer=3)

group = core.Elements((one, two, three))
group.show()

group2 = utils.rotate(group, 45).translate((30,0))
group2.layer = 2
group2.show()

(Source code, png, hires.png, pdf)

_images/userguide-18.png

There are several different methods for initializaing an Elements. Consult the API reference for more examples.

Cell

Cells are collections of multiple geometry elements, and references to other Cells. The contents of a Cell can have different datatypes and layers, so they are a good way of grouping together the many elements that make up a device. Every Cell has its own name.

(Source code, png, hires.png, pdf)

_images/userguide-19.png

CellReference and CellArray

The power of a Cell is that it can itself be inserted as a reference into other Cells. Note that Cells cannot contain circular references. The inserted reference can be subjected to the geometrical transforms, translation (origin), scaling (magnification), and rotation:

top = core.Cell('TOP')

ref1 = core.CellReference(cell)
top.add(ref1)

# Translate
ref2 = core.CellReference(cell, origin=(40,0))
top.add(ref2)

# Scale and Translate
ref3 = core.CellReference(cell, origin=(0, 40), magnification=1.5)
top.add(ref3)

# Rotate and Translate
ref4 = core.CellReference(cell, origin=(40,40), rotation=45)
top.add(ref4)

(Source code, png, hires.png, pdf)

_images/userguide-20.png

When a Cell is added to another Cell using the .add() method this is done by implicitly creating a CellReference object and adding that. Additional parameters to add are interpreted as parameters to the CellReference initialization.:

# This is shorthand...
myCell.add(anotherCell, origin=(20,10))

# ... for this.
ref = core.CellReference(anotherCell, origin=(20,10))
myCell.add(ref)

Many references to a``Cell`` arranged on a rectilinear grid can be created with a CellArray. In this case you specify the number of rows and columns for the array, along with a spacing, and optional arguments indicating the magnification, rotation and translation of the array.:

top = core.Cell('TOP')

array = core.CellArray(cell, 5, 3, (40,40), origin=(20,10), rotation=30, magnification=1.5)
top.add(array)
top.show()

(Source code, png, hires.png, pdf)

_images/userguide-21.png

The `spacing` parameter can be either a 2D vector or a pair of 2D vectors. The latter are interpreted as a pair of basis vectors that describe the lattice, and can be used to generate non-orthogonal arrays. The entries of the former give the spacing in the x and y direction for an orthogonal lattice.:

# A square lattice with 40 unit spacing
arr1 = core.CellArray(cell, 3, 5, spacing=(40,40))
top.add(arr1)

# The same specified with a pair of 2D vectors
arr2 = core.CellArray(cell, 3, 5, spacing=((40,0), (0,40)), origin = (160, 0))
top.add(arr2)

# A hexagonal lattice
a=25.
arr3 = core.CellArray(cell, 3, 5, ((a*sqrt(3)/2, a/2), (a*sqrt(3)/2, -a/2)), origin = (300, 50))
top.add(arr3)

(Source code, png, hires.png, pdf)

_images/userguide-22.png

Note that Cells do not support geometric transformations. So you cannot directly scale or translate a Cell. Instead, apply geometric transforms to a CellReference of the Cell.

Layout

The most basic gdsCAD object is the Layout, which is essentially the GDS stream that you will send to the mask shop. A Layout contains many Cells which can in turn contain drawing elements or references to other Cells. Those Cells in a Layout which are not referred to by any other Cell are known as top level Cells:

cell1 = core.Cell('CELL1')

cell2 = core.Cell('CELL2')

# cell2 is a top-level cell, cell1 is not.
cell2.add(cell1)

layout = core.Layout('LAYOUT')

layout.add(cell2)

# This will return a reference to cell2 only
layout.top_level()

The Layout also plays the important role of keeping track of the scale information for the design. Spatial dimensions of objects in gdsCAD have no units, and the size of objects is determined by the units specified in the Layout. Two parameters govern how dimensions are interpreted: units and precision, these can both be adjusted when the library is created. The default is for units to be in um and precision to be in nm. With this unit scheme a 10x10 box would have dimensions when printed of 10um x 10um, and the smallest possible box would be 1nm x 1nm (although that’s very unlikely to print). In practice it’s best to use the defaults and measure everything in um.

Layout is subclassed from the Python dict, so the Cells within a Layout can be accessed by their name like a Python dict, and it is possible to iterate over the names of the cells:

from gdsCAD import *

A=core.Cell('CELL A')
B=core.Cell('CELL B')

l=core.Layout('layout')
l.add(A)
l.add(B)

# Prints information on cell A
print l['CELL A']

# Prints the names of all cells
for name in l:
    print name

# Remove Cell A
del l['CELL A']

A Layout can be saved to a binary GDSII stream by using the method save(fname). It can be displayed using the method show().

GDSII keeps track of the relationships between Cells according to their names, so it’s important that every Cell in a GDS file have a unique name. In contrast gdsCAD keeps track of cell references by using pointers to the Python object, so the Cell name is only a useful label, but not a critical identifier, and it is not essential that Cell names in gdsCAD be unique. When a Layout is saved, any conflicting Cell names are made unique by appending an alphanumeric code.

Common Features

The following attributes can be found in most (if not all) classes:

  • print obj_name displays some basic information about the object
  • rotate(), translate(), reflect(), scale(): transformation operations on the object which act in place (not present for Cell)
  • show(): display the object in a matplotlib figure
  • bounding_box: return the rectangular extents of the object [[min_x, min_y], [max_x, max_y]]
  • copy(): create a deepcopy of the object
  • artist(): return the matplotlib artist that will draw the object
  • to_gds(): return a binary string representing the object in GDSII format

Drawing elements contain:

  • layer
  • datatype

GdsImport

GDSII streams that have been saved to file can be loaded into gdsCAD using the function GdsImport(). This loads the file and returns its contents in the form of a Layout. It handles most, but not all GDSII stream elements. In this example it loads the builtin Layout of alignment marks included with gdsCAD:

amarks = core.GdsImport(mark_file)
amarks.show()

(Source code, png, hires.png, pdf)

_images/userguide-23.png

GdsImport accepts several optional arguments that allow the cell names, layers, and datatypes to be reassigned on import. For instance the following will move all the art on layer 2 to layer 4:

amarks = core.GdsImport(mark_file, layers={2:4})
amarks.show()

(Source code, png, hires.png, pdf)

_images/userguide-24.png