#!/usr/bin/python
########################################################################################################################
#
# Copyright (c) 2020, Nifty Chips Laboratory, Hanyang University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
# following disclaimer in the documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
########################################################################################################################
"""
This module implements interfaces with gds files via gdspy.
"""
import logging
import pprint
from math import log10
from decimal import *
import laygo2.util.transform as tf
import numpy as np
import laygo2.object
def _load_layermap(layermapfile):
"""
Load layermap information from layermapfile (Foundry techfile can be used)
Parameters
----------
layermapfile : str
layermap filename.
The example file can be found in default.layermap or see below:
#technology layer information
#layername layerpurpose stream# datatype
text drawing 100 0
prBoundary drawing 101 0
M1 drawing 50 0
M1 pin 50 10
M2 drawing 51 0
M2 pin 51 10
Returns
-------
dict
constructed layermap information.
"""
layermap = dict()
f = open(layermapfile, "r")
for line in f:
tokens = line.split()
if not len(tokens) == 0:
if not tokens[0].startswith("#"):
name = tokens[0]
# if not layermap.has_key(name):
if name not in layermap:
layermap[name] = dict()
layermap[name][tokens[1]] = {
"layer": int(tokens[2]),
"datatype": int(tokens[3]),
}
return layermap
def _translate_obj(
objname,
obj,
layermap,
scale=0.001,
master=None,
offset=np.array([0, 0]),
pin_label_height=0.1,
):
"""
Convert an object to corresponding skill commands.
offset : np.array([int, int])
Offsets to obj.xy
"""
# import gdspy here to avoid unnecessary C++ compliations for non-gds options.
import gdspy
if master is None:
mxy = np.array([0, 0])
mtf = "R0"
else: # if the translated object has a master (e.g. VirtualInstance)
mxy = master.xy
mtf = master.transform
if obj.__class__ == laygo2.object.Rect:
## TODO: add color handling.
# color = obj.color # coloring function example for skill.
_xy = np.sort(obj.xy, axis=0) # make sure obj.xy is sorted
_xy = mxy + np.dot(
_xy
+ np.array(
[[-obj.hextension, -obj.vextension], [obj.hextension, obj.vextension]]
),
tf.Mt(mtf).T,
)
l = layermap[obj.layer[0]][obj.layer[1]]
rect = gdspy.Rectangle((_xy[0, 0], _xy[0, 1]), (_xy[1, 0], _xy[1, 1]), **l)
return rect
elif obj.__class__ == laygo2.object.Path:
# TODO: implement path export function.
pass
elif obj.__class__ == laygo2.object.Pin:
if obj.elements is None:
_objelem = [obj]
else:
_objelem = obj.elements
item = []
for idx, _obj in np.ndenumerate(_objelem):
_xy = mxy + np.dot(_obj.xy, tf.Mt(mtf).T)
l = layermap[_obj.layer[0]][_obj.layer[1]]
rect = gdspy.Rectangle((_xy[0, 0], _xy[0, 1]), (_xy[1, 0], _xy[1, 1]), **l)
_xy_c = 0.5 * (_xy[0, :] + _xy[1, :])
text = gdspy.Label(
_obj.netname, _xy_c, "nw", magnification=pin_label_height * 100
)
item += [rect, text]
return item
elif obj.__class__ == laygo2.object.Text:
# TODO: implement text export function.
pass
elif obj.__class__ == laygo2.object.Instance:
print(
"[Warning] laygo2.interface.gdspy: Instance transform is not implemented yet."
)
_xy = mxy + np.dot(obj.xy, tf.Mt(mtf).T)
if master is None:
transform = obj.transform
else: # if the translated object has a master (e.g. VirtualInstance)
transform = tf.combine(obj.transform, master.transform)
if obj.shape is None:
num_rows = 1
num_cols = 1
sp_rows = 0
sp_cols = 0
else:
num_rows = obj.shape[1]
num_cols = obj.shape[0]
sp_rows = obj.pitch[1]
sp_cols = obj.pitch[0]
# if obj.params is None: # gds cannot handle pcell parameters.
# inst_params = "nil"
# else:
# inst_params = _py2skill_inst_params(obj.params['pcell_params'])
inst = gdspy.CellReference(obj.cellname, _xy) # , transform)
return inst
elif obj.__class__ == laygo2.object.VirtualInstance:
item = []
if obj.shape is None:
for elem_name, elem in obj.native_elements.items():
if not elem.__class__ == laygo2.object.Pin:
if obj.name == None:
obj.name = "NoName"
else:
pass
item += [
_translate_obj(
obj.name + "_" + elem_name,
elem,
layermap=layermap,
master=obj,
scale=scale,
pin_label_height=pin_label_height,
)
]
else: # arrayed VirtualInstance
for i, j in np.ndindex(tuple(obj.shape.tolist())): # iterate over obj.shape
for elem_name, elem in obj.native_elements.items():
if not elem.__class__ == laygo2.object.Pin:
item += [
_translate_obj(
obj.name + "_" + elem_name + str(i) + "_" + str(j),
elem,
layermap=layermap,
master=obj[i, j],
scale=scale,
pin_label_height=pin_label_height,
)
]
return item
return None
# raise Exception("No corresponding GDS structure for:"+obj.summarize())
[docs]
def export(
db,
filename,
cellname=None,
scale=1e-9,
layermapfile="default.layermap",
physical_unit=1e-9,
logical_unit=0.001,
pin_label_height=0.1,
svg_filename=None,
png_filename=None,
):
"""
Export a laygo2.object.database.Library object to a gds file via gdspy.
Parameters
----------
db: laygo2.database.Library
The library database to exported.
filename: str, optional
The name of output gds file.
cellname: str or List[str]
The name(s) of cell(s) to be exported.
scale: float
The scaling factor between laygo2's integer coordinats actual physical coordinates.
layermapfile : str
the name of layermap file.
physical_unit : float, optional
GDS physical unit.
logical_unit : float, optional
GDS logical unit.
pin_label_height : float, optional
the height of pin label.
svg_filename: str, optional
If specified, it exports a svg file with the specified filename.
svg_filename: str, optional
If specified, it exports a png file with the specified filename
(svg_filename needs to be specified as well).
Example
-------
>>> import laygo2
>>> from laygo2.object.database import Design
>>> from laygo2.object.physical import Rect, Pin, Instance, Text
>>> # Create a design.
>>> dsn = Design(name="mycell", libname="genlib")
>>> # Create layout objects.
>>> r0 = Rect(xy=[[0, 0], [100, 100]], layer=["M1", "drawing"])
>>> p0 = Pin(xy=[[0, 0], [50, 50]], layer=["M1", "pin"], name="P")
>>> i0 = Instance(libname="tlib", cellname="t0", name="I0", xy=[0, 0])
>>> t0 = Text(xy=[[50, 50], [100, 100]], layer=["text", "drawing"], text="T")
>>> # Add the layout objects to the design object.
>>> dsn.append(r0)
>>> dsn.append(p0)
>>> dsn.append(i0)
>>> dsn.append(t0)
>>> #
>>> # Export to a gds file.
>>> lib = laygo2.object.database.Library(name="mylib")
>>> lib.append(dsn)
>>> laygo2.interface.gds.export(lib, filename="mylayout.gds")
"""
# Compute scale parameter.
_scale = round(1 / scale * physical_unit / logical_unit)
# 1um in phy
# 1um/1nm = 1000 in laygo2 if scale = 1e-9 (1nm)
# 1000/1nm*1nm/0.001 = 1000000 in gds if physical_unit = 1e-9 (1nm) and logical_unit = 0.001
# Load layermap file.
layermap = _load_layermap(layermapfile) # load layermap information
# Construct cellname.
cellname = (
db.keys() if cellname is None else cellname
) # export all cells if cellname is not given.
cellname = (
[cellname] if isinstance(cellname, str) else cellname
) # convert to a list for iteration.
# import gdspy here to avoid unnecessary C++ compliations for non-gds options.
import gdspy
# Create library.
lib = gdspy.GdsLibrary()
for cn in cellname:
# Create cell.
cell = lib.new_cell(cn)
# Translate objects.
for objname, obj in db[cn].items():
tobj = _translate_obj(
objname,
obj,
layermap=layermap,
scale=_scale,
pin_label_height=pin_label_height,
)
if tobj is not None:
cell.add(tobj)
lib.write_gds(filename)
if svg_filename is not None:
cell.write_svg(svg_filename)
if svg_filename is not None:
# import cairosvg here to avoid unnecessary lib installation for non-gds options.
import cairosvg
cairosvg.svg2png(url=svg_filename, write_to=png_filename, scale=1.0)
# gdspy.LayoutViewer()