2016-11-08 18:57:35 +01:00
|
|
|
import itertools
|
|
|
|
|
import geometry
|
|
|
|
|
import gdsMill
|
|
|
|
|
import debug
|
|
|
|
|
from tech import drc, GDS
|
|
|
|
|
from tech import layer as techlayer
|
|
|
|
|
import os
|
|
|
|
|
from vector import vector
|
2017-08-24 00:02:15 +02:00
|
|
|
from pin_layout import pin_layout
|
2017-12-19 18:01:24 +01:00
|
|
|
import lef
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
class layout(lef.lef):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""
|
|
|
|
|
Class consisting of a set of objs and instances for a module
|
|
|
|
|
This provides a set of useful generic types for hierarchy
|
|
|
|
|
management. If a module is a custom designed cell, it will read from
|
|
|
|
|
the GDS and spice files and perform LVS/DRC. If it is dynamically
|
|
|
|
|
generated, it should implement a constructor to create the
|
|
|
|
|
layout/netlist and perform LVS/DRC.
|
|
|
|
|
"""
|
|
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
def __init__(self, name):
|
|
|
|
|
lef.lef.__init__(self, ["metal1", "metal2", "metal3"])
|
2016-11-08 18:57:35 +01:00
|
|
|
self.name = name
|
|
|
|
|
self.width = None
|
|
|
|
|
self.height = None
|
2017-08-24 00:02:15 +02:00
|
|
|
self.insts = [] # Holds module/cell layout instances
|
|
|
|
|
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
2017-09-14 00:46:41 +02:00
|
|
|
self.pin_map = {} # Holds name->pin_layout map for all pins
|
2016-11-08 18:57:35 +01:00
|
|
|
self.visited = False # Flag for traversing the hierarchy
|
2017-12-19 18:01:24 +01:00
|
|
|
self.is_library_cell = False # Flag for library cells
|
2016-11-08 18:57:35 +01:00
|
|
|
self.gds_read()
|
|
|
|
|
|
|
|
|
|
############################################################
|
|
|
|
|
# GDS layout
|
|
|
|
|
############################################################
|
|
|
|
|
def offset_all_coordinates(self):
|
|
|
|
|
""" This function is called after everything is placed to
|
|
|
|
|
shift the origin in the lowest left corner """
|
2017-09-14 00:46:41 +02:00
|
|
|
offset = self.find_lowest_coords()
|
|
|
|
|
self.translate_all(offset)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def get_gate_offset(self, x_offset, height, inv_num):
|
|
|
|
|
"""Gets the base offset and y orientation of stacked rows of gates
|
|
|
|
|
assuming a minwidth metal1 vdd/gnd rail. Input is which gate
|
|
|
|
|
in the stack from 0..n
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if (inv_num % 2 == 0):
|
|
|
|
|
base_offset=vector(x_offset, inv_num * height)
|
|
|
|
|
y_dir = 1
|
|
|
|
|
else:
|
|
|
|
|
# we lose a rail after every 2 gates
|
|
|
|
|
base_offset=vector(x_offset, (inv_num+1) * height - (inv_num%2)*drc["minwidth_metal1"])
|
|
|
|
|
y_dir = -1
|
|
|
|
|
|
|
|
|
|
return (base_offset,y_dir)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_lowest_coords(self):
|
|
|
|
|
"""Finds the lowest set of 2d cartesian coordinates within
|
|
|
|
|
this layout"""
|
2018-02-03 00:17:21 +01:00
|
|
|
|
|
|
|
|
if len(self.objs)>0:
|
|
|
|
|
lowestx1 = min(obj.lx() for obj in self.objs if obj.name!="label")
|
|
|
|
|
lowesty1 = min(obj.by() for obj in self.objs if obj.name!="label")
|
|
|
|
|
else:
|
|
|
|
|
lowestx1=lowesty1=None
|
|
|
|
|
if len(self.insts)>0:
|
|
|
|
|
lowestx2 = min(inst.lx() for inst in self.insts)
|
|
|
|
|
lowesty2 = min(inst.by() for inst in self.insts)
|
|
|
|
|
else:
|
|
|
|
|
lowestx2=lowesty2=None
|
|
|
|
|
if lowestx1==None:
|
|
|
|
|
return vector(lowestx2,lowesty2)
|
|
|
|
|
elif lowestx2==None:
|
|
|
|
|
return vector(lowestx1,lowesty1)
|
|
|
|
|
else:
|
|
|
|
|
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
def find_highest_coords(self):
|
|
|
|
|
"""Finds the highest set of 2d cartesian coordinates within
|
|
|
|
|
this layout"""
|
2018-02-03 00:17:21 +01:00
|
|
|
|
|
|
|
|
if len(self.objs)>0:
|
|
|
|
|
highestx1 = max(obj.rx() for obj in self.objs if obj.name!="label")
|
|
|
|
|
highesty1 = max(obj.uy() for obj in self.objs if obj.name!="label")
|
|
|
|
|
else:
|
|
|
|
|
highestx1=highesty1=None
|
|
|
|
|
if len(self.insts)>0:
|
|
|
|
|
highestx2 = max(inst.rx() for inst in self.insts)
|
|
|
|
|
highesty2 = max(inst.uy() for inst in self.insts)
|
|
|
|
|
else:
|
|
|
|
|
highestx2=highesty2=None
|
|
|
|
|
if highestx1==None:
|
|
|
|
|
return vector(highestx2,highesty2)
|
|
|
|
|
elif highestx2==None:
|
|
|
|
|
return vector(highestx1,highesty1)
|
|
|
|
|
else:
|
|
|
|
|
return vector(max(highestx1, highestx2), max(highesty1, highesty2))
|
2017-12-19 18:01:24 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-09-14 00:46:41 +02:00
|
|
|
def translate_all(self, offset):
|
|
|
|
|
"""
|
|
|
|
|
Translates all objects, instances, and pins by the given (x,y) offset
|
|
|
|
|
"""
|
2016-11-08 18:57:35 +01:00
|
|
|
for obj in self.objs:
|
2017-09-14 00:46:41 +02:00
|
|
|
obj.offset = vector(obj.offset - offset)
|
2016-11-08 18:57:35 +01:00
|
|
|
for inst in self.insts:
|
2017-09-14 00:46:41 +02:00
|
|
|
inst.offset = vector(inst.offset - offset)
|
|
|
|
|
# The instances have a precomputed boundary that we need to update.
|
|
|
|
|
if inst.__class__.__name__ == "instance":
|
|
|
|
|
inst.compute_boundary(offset.scale(-1,-1))
|
|
|
|
|
for pin_name in self.pin_map.keys():
|
|
|
|
|
# All the pins are absolute coordinates that need to be updated.
|
|
|
|
|
pin_list = self.pin_map[pin_name]
|
|
|
|
|
for pin in pin_list:
|
|
|
|
|
pin.rect = [pin.ll() - offset, pin.ur() - offset]
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0):
|
|
|
|
|
"""Adds an instance of a mod to this module"""
|
|
|
|
|
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
|
2018-03-03 03:05:46 +01:00
|
|
|
debug.info(3, "adding instance {}".format(self.insts[-1]))
|
|
|
|
|
debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
|
2017-08-24 00:02:15 +02:00
|
|
|
return self.insts[-1]
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def get_inst(self, name):
|
|
|
|
|
"""Retrieve an instance by name"""
|
|
|
|
|
for inst in self.insts:
|
|
|
|
|
if inst.name == name:
|
|
|
|
|
return inst
|
|
|
|
|
return None
|
|
|
|
|
|
2017-09-30 01:22:13 +02:00
|
|
|
def add_rect(self, layer, offset, width=0, height=0):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Adds a rectangle on a given layer,offset with width and height"""
|
2017-09-30 01:22:13 +02:00
|
|
|
if width==0:
|
|
|
|
|
width=drc["minwidth_{}".format(layer)]
|
|
|
|
|
if height==0:
|
|
|
|
|
height=drc["minwidth_{}".format(layer)]
|
2016-11-08 18:57:35 +01:00
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2017-12-19 18:01:24 +01:00
|
|
|
layer_num = techlayer[layer]
|
|
|
|
|
if layer_num >= 0:
|
|
|
|
|
self.objs.append(geometry.rectangle(layer_num, offset, width, height))
|
2017-08-24 00:02:15 +02:00
|
|
|
return self.objs[-1]
|
|
|
|
|
return None
|
2017-10-07 00:30:15 +02:00
|
|
|
|
2017-11-30 21:01:04 +01:00
|
|
|
def add_rect_center(self, layer, offset, width=0, height=0):
|
2017-11-29 03:13:32 +01:00
|
|
|
"""Adds a rectangle on a given layer at the center point with width and height"""
|
|
|
|
|
if width==0:
|
|
|
|
|
width=drc["minwidth_{}".format(layer)]
|
|
|
|
|
if height==0:
|
|
|
|
|
height=drc["minwidth_{}".format(layer)]
|
|
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2017-12-19 18:01:24 +01:00
|
|
|
layer_num = techlayer[layer]
|
2017-11-29 03:13:32 +01:00
|
|
|
corrected_offset = offset - vector(0.5*width,0.5*height)
|
2017-12-19 18:01:24 +01:00
|
|
|
if layer_num >= 0:
|
|
|
|
|
self.objs.append(geometry.rectangle(layer_num, corrected_offset, width, height))
|
2017-11-29 03:13:32 +01:00
|
|
|
return self.objs[-1]
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2017-11-30 21:01:04 +01:00
|
|
|
def add_segment_center(self, layer, start, end):
|
2017-11-29 03:13:32 +01:00
|
|
|
""" Add a min-width rectanglular segment using center line on the start to end point """
|
2017-10-07 00:30:15 +02:00
|
|
|
minwidth_layer = drc["minwidth_{}".format(layer)]
|
|
|
|
|
if start.x!=end.x and start.y!=end.y:
|
|
|
|
|
debug.error("Nonrectilinear center rect!",-1)
|
|
|
|
|
elif start.x!=end.x:
|
|
|
|
|
offset = vector(0,0.5*minwidth_layer)
|
2018-02-05 19:22:38 +01:00
|
|
|
return self.add_rect(layer,start-offset,end.x-start.x,minwidth_layer)
|
2017-10-07 00:30:15 +02:00
|
|
|
else:
|
|
|
|
|
offset = vector(0.5*minwidth_layer,0)
|
2018-02-05 19:22:38 +01:00
|
|
|
return self.add_rect(layer,start-offset,minwidth_layer,end.y-start.y)
|
|
|
|
|
|
2017-10-07 00:30:15 +02:00
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def get_pin(self, text):
|
|
|
|
|
""" Return the pin or list of pins """
|
2017-09-30 01:22:13 +02:00
|
|
|
try:
|
|
|
|
|
if len(self.pin_map[text])>1:
|
|
|
|
|
debug.warning("Should use a pin iterator since more than one pin {}".format(text))
|
|
|
|
|
# If we have one pin, return it and not the list.
|
|
|
|
|
# Otherwise, should use get_pins()
|
|
|
|
|
return self.pin_map[text][0]
|
|
|
|
|
except Exception as e:
|
|
|
|
|
#print e
|
|
|
|
|
self.gds_write("missing_pin.gds")
|
|
|
|
|
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1)
|
|
|
|
|
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
|
|
|
|
|
def get_pins(self, text):
|
|
|
|
|
""" Return a pin list (instead of a single pin) """
|
|
|
|
|
return self.pin_map[text]
|
|
|
|
|
|
2017-09-14 00:46:41 +02:00
|
|
|
def copy_layout_pin(self, instance, pin_name, new_name=""):
|
|
|
|
|
"""
|
|
|
|
|
Create a copied version of the layout pin at the current level.
|
|
|
|
|
You can optionally rename the pin to a new name.
|
|
|
|
|
"""
|
|
|
|
|
pins=instance.get_pins(pin_name)
|
|
|
|
|
for pin in pins:
|
|
|
|
|
if new_name=="":
|
|
|
|
|
new_name = pin.name
|
|
|
|
|
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
|
|
|
|
|
|
2017-11-30 20:56:40 +01:00
|
|
|
def add_layout_pin_center_segment(self, text, layer, start, end):
|
2017-09-30 01:22:13 +02:00
|
|
|
""" Creates a path like pin with center-line convention """
|
|
|
|
|
|
|
|
|
|
debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.")
|
|
|
|
|
|
|
|
|
|
minwidth_layer = drc["minwidth_{}".format(layer)]
|
|
|
|
|
|
|
|
|
|
# one of these will be zero
|
|
|
|
|
width = max(start.x,end.x) - min(start.x,end.x)
|
|
|
|
|
height = max(start.y,end.y) - min(start.y,end.y)
|
|
|
|
|
ll_offset = vector(min(start.x,end.x),min(start.y,end.y))
|
|
|
|
|
|
|
|
|
|
# Shift it down 1/2 a width in the 0 dimension
|
|
|
|
|
if height==0:
|
|
|
|
|
ll_offset -= vector(0,0.5*minwidth_layer)
|
|
|
|
|
if width==0:
|
|
|
|
|
ll_offset -= vector(0.5*minwidth_layer,0)
|
|
|
|
|
# This makes sure it is long enough, but also it is not 0 width!
|
|
|
|
|
height = max(minwidth_layer,height)
|
|
|
|
|
width = max(minwidth_layer,width)
|
|
|
|
|
|
|
|
|
|
|
2017-10-07 00:30:15 +02:00
|
|
|
return self.add_layout_pin(text, layer, ll_offset, width, height)
|
2017-11-29 03:13:32 +01:00
|
|
|
|
2017-11-30 20:56:40 +01:00
|
|
|
def add_layout_pin_center_rect(self, text, layer, offset, width=None, height=None):
|
2017-11-29 03:13:32 +01:00
|
|
|
""" Creates a path like pin with center-line convention """
|
|
|
|
|
if width==None:
|
|
|
|
|
width=drc["minwidth_{0}".format(layer)]
|
|
|
|
|
if height==None:
|
|
|
|
|
height=drc["minwidth_{0}".format(layer)]
|
|
|
|
|
|
|
|
|
|
ll_offset = offset - vector(0.5*width,0.5*height)
|
|
|
|
|
|
|
|
|
|
return self.add_layout_pin(text, layer, ll_offset, width, height)
|
|
|
|
|
|
|
|
|
|
|
2017-10-06 02:35:05 +02:00
|
|
|
def remove_layout_pin(self, text):
|
|
|
|
|
"""Delete a labeled pin (or all pins of the same name)"""
|
|
|
|
|
self.pin_map[text]=[]
|
2017-09-14 00:46:41 +02:00
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
def add_layout_pin(self, text, layer, offset, width=None, height=None):
|
2017-08-25 01:22:14 +02:00
|
|
|
"""Create a labeled pin """
|
2017-08-24 00:02:15 +02:00
|
|
|
if width==None:
|
|
|
|
|
width=drc["minwidth_{0}".format(layer)]
|
|
|
|
|
if height==None:
|
|
|
|
|
height=drc["minwidth_{0}".format(layer)]
|
2017-10-06 02:35:05 +02:00
|
|
|
|
2017-08-25 01:22:14 +02:00
|
|
|
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
|
|
|
|
|
|
2017-08-24 00:02:15 +02:00
|
|
|
try:
|
2017-08-25 01:22:14 +02:00
|
|
|
# Check if there's a duplicate!
|
|
|
|
|
# and if so, silently ignore it.
|
|
|
|
|
# Rounding errors may result in some duplicates.
|
|
|
|
|
pin_list = self.pin_map[text]
|
|
|
|
|
for pin in pin_list:
|
|
|
|
|
if pin == new_pin:
|
2017-10-07 00:30:15 +02:00
|
|
|
return pin
|
2017-08-25 01:22:14 +02:00
|
|
|
self.pin_map[text].append(new_pin)
|
2017-08-24 00:02:15 +02:00
|
|
|
except KeyError:
|
2017-08-25 01:22:14 +02:00
|
|
|
self.pin_map[text] = [new_pin]
|
|
|
|
|
|
2017-10-07 00:30:15 +02:00
|
|
|
return new_pin
|
|
|
|
|
|
2017-08-25 01:22:14 +02:00
|
|
|
def add_label_pin(self, text, layer, offset, width=None, height=None):
|
|
|
|
|
"""Create a labeled pin WITHOUT the pin data structure. This is not an
|
|
|
|
|
actual pin but a named net so that we can add a correspondence point
|
|
|
|
|
in LVS.
|
|
|
|
|
"""
|
|
|
|
|
if width==None:
|
|
|
|
|
width=drc["minwidth_{0}".format(layer)]
|
|
|
|
|
if height==None:
|
|
|
|
|
height=drc["minwidth_{0}".format(layer)]
|
|
|
|
|
self.add_rect(layer=layer,
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=height)
|
|
|
|
|
self.add_label(text=text,
|
|
|
|
|
layer=layer,
|
|
|
|
|
offset=offset)
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-05-24 01:18:11 +02:00
|
|
|
def add_label(self, text, layer, offset=[0,0],zoom=-1):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Adds a text label on the given layer,offset, and zoom level"""
|
|
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2017-09-30 01:22:13 +02:00
|
|
|
debug.info(5,"add label " + str(text) + " " + layer + " " + str(offset))
|
2017-12-19 18:01:24 +01:00
|
|
|
layer_num = techlayer[layer]
|
|
|
|
|
if layer_num >= 0:
|
|
|
|
|
self.objs.append(geometry.label(text, layer_num, offset, zoom))
|
2017-08-24 00:02:15 +02:00
|
|
|
return self.objs[-1]
|
|
|
|
|
return None
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
2017-08-07 19:24:45 +02:00
|
|
|
def add_path(self, layer, coordinates, width=None):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Connects a routing path on given layer,coordinates,width."""
|
2017-09-30 01:22:13 +02:00
|
|
|
debug.info(4,"add path " + str(layer) + " " + str(coordinates))
|
2016-11-08 18:57:35 +01:00
|
|
|
import path
|
|
|
|
|
# NOTE: (UNTESTED) add_path(...) is currently not used
|
|
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2017-12-19 18:01:24 +01:00
|
|
|
#layer_num = techlayer[layer]
|
|
|
|
|
#if layer_num >= 0:
|
|
|
|
|
# self.objs.append(geometry.path(layer_num, coordinates, width))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2017-08-07 19:24:45 +02:00
|
|
|
path.path(obj=self,
|
|
|
|
|
layer=layer,
|
|
|
|
|
position_list=coordinates,
|
2018-02-09 19:03:09 +01:00
|
|
|
width=width)
|
2017-08-07 19:24:45 +02:00
|
|
|
|
|
|
|
|
def add_route(self, design, layers, coordinates):
|
2017-04-19 21:41:13 +02:00
|
|
|
"""Connects a routing path on given layer,coordinates,width. The
|
|
|
|
|
layers are the (horizontal, via, vertical). add_wire assumes
|
|
|
|
|
preferred direction routing whereas this includes layers in
|
|
|
|
|
the coordinates.
|
|
|
|
|
"""
|
|
|
|
|
import route
|
2017-09-30 01:22:13 +02:00
|
|
|
debug.info(4,"add route " + str(layers) + " " + str(coordinates))
|
2017-04-19 21:41:13 +02:00
|
|
|
# add an instance of our path that breaks down into rectangles and contacts
|
2017-08-07 19:24:45 +02:00
|
|
|
route.route(obj=self,
|
|
|
|
|
layer_stack=layers,
|
|
|
|
|
path=coordinates)
|
|
|
|
|
|
2017-04-19 21:41:13 +02:00
|
|
|
|
2017-08-07 19:24:45 +02:00
|
|
|
def add_wire(self, layers, coordinates):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Connects a routing path on given layer,coordinates,width.
|
2016-11-17 23:05:50 +01:00
|
|
|
The layers are the (horizontal, via, vertical). """
|
2016-11-08 18:57:35 +01:00
|
|
|
import wire
|
|
|
|
|
# add an instance of our path that breaks down into rectangles and contacts
|
2017-08-07 19:24:45 +02:00
|
|
|
wire.wire(obj=self,
|
|
|
|
|
layer_stack=layers,
|
|
|
|
|
position_list=coordinates)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_contact(self, layers, offset, size=[1,1], mirror="R0", rotate=0, implant_type=None, well_type=None):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" This is just an alias for a via."""
|
|
|
|
|
return self.add_via(layers=layers,
|
|
|
|
|
offset=offset,
|
|
|
|
|
size=size,
|
2017-09-30 01:22:13 +02:00
|
|
|
mirror=mirror,
|
2018-01-11 19:24:44 +01:00
|
|
|
rotate=rotate,
|
|
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_contact_center(self, layers, offset, size=[1,1], mirror="R0", rotate=0, implant_type=None, well_type=None):
|
2017-09-30 01:22:13 +02:00
|
|
|
""" This is just an alias for a via."""
|
2017-11-30 21:01:04 +01:00
|
|
|
return self.add_via_center(layers=layers,
|
2017-11-29 03:13:32 +01:00
|
|
|
offset=offset,
|
|
|
|
|
size=size,
|
|
|
|
|
mirror=mirror,
|
2018-01-11 19:24:44 +01:00
|
|
|
rotate=rotate,
|
|
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2017-09-30 01:22:13 +02:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_via(self, layers, offset, size=[1,1], mirror="R0", rotate=0, implant_type=None, well_type=None):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Add a three layer via structure. """
|
|
|
|
|
import contact
|
|
|
|
|
via = contact.contact(layer_stack=layers,
|
2018-01-11 19:24:44 +01:00
|
|
|
dimensions=size,
|
|
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(via)
|
2018-01-26 21:39:00 +01:00
|
|
|
inst=self.add_inst(name=via.name,
|
|
|
|
|
mod=via,
|
|
|
|
|
offset=offset,
|
|
|
|
|
mirror=mirror,
|
|
|
|
|
rotate=rotate)
|
2016-11-08 18:57:35 +01:00
|
|
|
# We don't model the logical connectivity of wires/paths
|
|
|
|
|
self.connect_inst([])
|
2018-01-26 21:39:00 +01:00
|
|
|
return inst
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2018-01-11 19:24:44 +01:00
|
|
|
def add_via_center(self, layers, offset, size=[1,1], mirror="R0", rotate=0, implant_type=None, well_type=None):
|
2017-09-30 01:22:13 +02:00
|
|
|
""" Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """
|
|
|
|
|
import contact
|
|
|
|
|
via = contact.contact(layer_stack=layers,
|
2018-01-11 19:24:44 +01:00
|
|
|
dimensions=size,
|
|
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2017-09-30 01:22:13 +02:00
|
|
|
|
|
|
|
|
debug.check(mirror=="R0","Use rotate to rotate vias instead of mirror.")
|
|
|
|
|
|
|
|
|
|
height = via.height
|
|
|
|
|
width = via.width
|
|
|
|
|
|
|
|
|
|
if rotate==0:
|
|
|
|
|
corrected_offset = offset + vector(-0.5*width,-0.5*height)
|
|
|
|
|
elif rotate==90:
|
|
|
|
|
corrected_offset = offset + vector(0.5*height,-0.5*width)
|
|
|
|
|
elif rotate==180:
|
|
|
|
|
corrected_offset = offset + vector(-0.5*width,0.5*height)
|
|
|
|
|
elif rotate==270:
|
|
|
|
|
corrected_offset = offset + vector(-0.5*height,0.5*width)
|
|
|
|
|
else:
|
|
|
|
|
debug.error("Invalid rotation argument.",-1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.add_mod(via)
|
2018-01-26 21:39:00 +01:00
|
|
|
inst=self.add_inst(name=via.name,
|
|
|
|
|
mod=via,
|
|
|
|
|
offset=corrected_offset,
|
|
|
|
|
mirror=mirror,
|
|
|
|
|
rotate=rotate)
|
2017-09-30 01:22:13 +02:00
|
|
|
# We don't model the logical connectivity of wires/paths
|
|
|
|
|
self.connect_inst([])
|
2018-01-26 21:39:00 +01:00
|
|
|
return inst
|
2017-09-30 01:22:13 +02:00
|
|
|
|
2017-04-26 23:33:03 +02:00
|
|
|
def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Adds a ptx module to the design."""
|
|
|
|
|
import ptx
|
2017-04-26 23:33:03 +02:00
|
|
|
mos = ptx.ptx(width=width,
|
2016-11-08 18:57:35 +01:00
|
|
|
mults=mults,
|
|
|
|
|
tx_type=tx_type)
|
|
|
|
|
self.add_mod(mos)
|
2018-01-26 21:39:00 +01:00
|
|
|
inst=self.add_inst(name=mos.name,
|
|
|
|
|
mod=mos,
|
|
|
|
|
offset=offset,
|
|
|
|
|
mirror=mirror,
|
|
|
|
|
rotate=rotate)
|
|
|
|
|
return inst
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
|
2017-12-12 23:53:19 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
def gds_read(self):
|
|
|
|
|
"""Reads a GDSII file in the library and checks if it exists
|
|
|
|
|
Otherwise, start a new layout for dynamic generation."""
|
|
|
|
|
# open the gds file if it exists or else create a blank layout
|
|
|
|
|
if os.path.isfile(self.gds_file):
|
2016-11-15 17:57:06 +01:00
|
|
|
debug.info(3, "opening %s" % self.gds_file)
|
2017-12-19 18:01:24 +01:00
|
|
|
self.is_library_cell=True
|
2016-11-08 18:57:35 +01:00
|
|
|
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
|
|
|
|
|
reader = gdsMill.Gds2reader(self.gds)
|
|
|
|
|
reader.loadFromFile(self.gds_file)
|
|
|
|
|
else:
|
2017-09-30 01:22:13 +02:00
|
|
|
debug.info(4, "creating structure %s" % self.name)
|
2016-11-17 22:26:03 +01:00
|
|
|
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def print_gds(self, gds_file=None):
|
|
|
|
|
"""Print the gds file (not the vlsi class) to the terminal """
|
|
|
|
|
if gds_file == None:
|
|
|
|
|
gds_file = self.gds_file
|
2017-09-30 01:22:13 +02:00
|
|
|
debug.info(4, "Printing %s" % gds_file)
|
2016-11-17 20:24:17 +01:00
|
|
|
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
|
2016-11-08 18:57:35 +01:00
|
|
|
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
|
|
|
|
|
reader.loadFromFile(gds_file)
|
|
|
|
|
|
|
|
|
|
def clear_visited(self):
|
|
|
|
|
""" Recursively clear the visited flag """
|
|
|
|
|
if not self.visited:
|
|
|
|
|
for i in self.insts:
|
|
|
|
|
i.mod.clear_visited()
|
|
|
|
|
self.visited = False
|
|
|
|
|
|
|
|
|
|
def gds_write_file(self, newLayout):
|
|
|
|
|
"""Recursive GDS write function"""
|
2018-03-03 03:05:46 +01:00
|
|
|
# Visited means that we already prepared self.gds for this subtree
|
2016-11-08 18:57:35 +01:00
|
|
|
if self.visited:
|
|
|
|
|
return
|
|
|
|
|
for i in self.insts:
|
|
|
|
|
i.gds_write_file(newLayout)
|
|
|
|
|
for i in self.objs:
|
|
|
|
|
i.gds_write_file(newLayout)
|
2017-10-06 02:35:05 +02:00
|
|
|
for pin_name in self.pin_map.keys():
|
|
|
|
|
for pin in self.pin_map[pin_name]:
|
|
|
|
|
pin.gds_write_file(newLayout)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.visited = True
|
|
|
|
|
|
|
|
|
|
def gds_write(self, gds_name):
|
|
|
|
|
"""Write the entire gds of the object to the file."""
|
|
|
|
|
debug.info(3, "Writing to {0}".format(gds_name))
|
|
|
|
|
|
|
|
|
|
writer = gdsMill.Gds2writer(self.gds)
|
2018-03-03 03:05:46 +01:00
|
|
|
# MRG: 3/2/18 We don't want to clear the visited flag since
|
|
|
|
|
# this would result in duplicates of all instances being placed in self.gds
|
|
|
|
|
# which may have been previously processed!
|
|
|
|
|
#self.clear_visited()
|
2016-11-08 18:57:35 +01:00
|
|
|
# recursively create all the remaining objects
|
|
|
|
|
self.gds_write_file(self.gds)
|
|
|
|
|
# populates the xyTree data structure for gds
|
|
|
|
|
# self.gds.prepareForWrite()
|
|
|
|
|
writer.writeToFile(gds_name)
|
|
|
|
|
|
2017-12-19 18:01:24 +01:00
|
|
|
def get_boundary(self):
|
|
|
|
|
""" Return the lower-left and upper-right coordinates of boundary """
|
|
|
|
|
# This assumes nothing spans outside of the width and height!
|
|
|
|
|
return [vector(0,0), vector(self.width, self.height)]
|
|
|
|
|
#return [self.find_lowest_coords(), self.find_highest_coords()]
|
|
|
|
|
|
|
|
|
|
def get_blockages(self, layer, top_level=False):
|
|
|
|
|
"""
|
|
|
|
|
Write all of the obstacles in the current (and children) modules to the lef file
|
|
|
|
|
Do not write the pins since they aren't obstructions.
|
|
|
|
|
"""
|
|
|
|
|
if type(layer)==str:
|
|
|
|
|
layer_num = techlayer[layer]
|
|
|
|
|
else:
|
|
|
|
|
layer_num = layer
|
|
|
|
|
|
|
|
|
|
blockages = []
|
|
|
|
|
for i in self.objs:
|
|
|
|
|
blockages += i.get_blockages(layer_num)
|
|
|
|
|
for i in self.insts:
|
|
|
|
|
blockages += i.get_blockages(layer_num)
|
|
|
|
|
# Must add pin blockages to non-top cells
|
|
|
|
|
if not top_level:
|
|
|
|
|
blockages += self.get_pin_blockages(layer_num)
|
|
|
|
|
return blockages
|
|
|
|
|
|
|
|
|
|
def get_pin_blockages(self, layer_num):
|
|
|
|
|
""" Return the pin shapes as blockages for non-top-level blocks. """
|
|
|
|
|
# FIXME: We don't have a body contact in ptx, so just ignore it for now
|
|
|
|
|
import copy
|
|
|
|
|
pin_names = copy.deepcopy(self.pins)
|
|
|
|
|
if self.name.startswith("pmos") or self.name.startswith("nmos"):
|
|
|
|
|
pin_names.remove("B")
|
|
|
|
|
|
|
|
|
|
blockages = []
|
|
|
|
|
for pin_name in pin_names:
|
|
|
|
|
pin_list = self.get_pins(pin_name)
|
|
|
|
|
for pin in pin_list:
|
|
|
|
|
if pin.layer_num==layer_num:
|
|
|
|
|
blockages += [pin.rect]
|
|
|
|
|
|
|
|
|
|
return blockages
|
|
|
|
|
|
2018-01-31 23:31:50 +01:00
|
|
|
def add_enclosure(self, insts, layer="nwell"):
|
|
|
|
|
""" Add a layer that surrounds the given instances. Useful
|
|
|
|
|
for creating wells, for example. Doesn't check for minimum widths or
|
|
|
|
|
spacings."""
|
|
|
|
|
|
|
|
|
|
xmin=insts[0].lx()
|
|
|
|
|
ymin=insts[0].by()
|
|
|
|
|
xmax=insts[0].rx()
|
|
|
|
|
ymax=insts[0].uy()
|
|
|
|
|
for inst in insts:
|
|
|
|
|
xmin = min(xmin, inst.lx())
|
|
|
|
|
ymin = min(ymin, inst.by())
|
|
|
|
|
xmax = max(xmax, inst.rx())
|
|
|
|
|
ymax = max(ymax, inst.uy())
|
|
|
|
|
|
|
|
|
|
self.add_rect(layer=layer,
|
|
|
|
|
offset=vector(xmin,ymin),
|
|
|
|
|
width=xmax-xmin,
|
|
|
|
|
height=ymax-ymin)
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
def pdf_write(self, pdf_name):
|
|
|
|
|
# NOTE: Currently does not work (Needs further research)
|
|
|
|
|
#self.pdf_name = self.name + ".pdf"
|
|
|
|
|
debug.info(0, "Writing to %s" % pdf_name)
|
|
|
|
|
pdf = gdsMill.pdfLayout(self.gds)
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[0]] = "#219E1C"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[1]] = "#271C9E"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[2]] = "#CC54C8"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[3]] = "#E9C514"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[4]] = "#856F00"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[5]] = "#BD1444"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[6]] = "#FD1444"
|
|
|
|
|
pdf.layerColors[self.gds.layerNumbersInUse[7]] = "#FD1414"
|
|
|
|
|
|
|
|
|
|
pdf.setScale(500)
|
|
|
|
|
pdf.drawLayout()
|
|
|
|
|
pdf.writeToFile(pdf_name)
|
|
|
|
|
|
|
|
|
|
def print_attr(self):
|
|
|
|
|
"""Prints a list of attributes for the current layout object"""
|
|
|
|
|
debug.info(0,
|
|
|
|
|
"|==============================================================================|")
|
|
|
|
|
debug.info(0,
|
2017-12-19 18:01:24 +01:00
|
|
|
"|========= LIST OF OBJECTS (Rects) FOR: " + self.name)
|
2016-11-08 18:57:35 +01:00
|
|
|
debug.info(0,
|
|
|
|
|
"|==============================================================================|")
|
|
|
|
|
for obj in self.objs:
|
2017-12-19 18:01:24 +01:00
|
|
|
debug.info(0, "layer={0} : offset={1} : size={2}".format(obj.layerNumber,
|
|
|
|
|
obj.offset,
|
|
|
|
|
obj.size))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
debug.info(0,
|
|
|
|
|
"|==============================================================================|")
|
|
|
|
|
debug.info(0,
|
2017-12-19 18:01:24 +01:00
|
|
|
"|========= LIST OF INSTANCES FOR: " + self.name)
|
2016-11-08 18:57:35 +01:00
|
|
|
debug.info(0,
|
|
|
|
|
"|==============================================================================|")
|
|
|
|
|
for inst in self.insts:
|
2017-12-19 18:01:24 +01:00
|
|
|
debug.info(0, "name={0} : mod={1} : offset={2}".format(inst.name,
|
|
|
|
|
inst.mod.name,
|
|
|
|
|
inst.offset))
|
|
|
|
|
|