mirror of https://github.com/VLSIDA/OpenRAM.git
338 lines
14 KiB
Python
338 lines
14 KiB
Python
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
|
|
|
|
class layout:
|
|
"""
|
|
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.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.width = None
|
|
self.height = None
|
|
self.insts = [] # Holds module/cell layout instances
|
|
self.objs = [] # Holds all other objects (labels, geometries, etc)
|
|
|
|
self.visited = False # Flag for traversing the hierarchy
|
|
|
|
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 """
|
|
coordinate = self.find_lowest_coords()
|
|
self.offset_attributes(coordinate)
|
|
self.translate(coordinate)
|
|
|
|
|
|
|
|
def find_lowest_coords(self):
|
|
"""Finds the lowest set of 2d cartesian coordinates within
|
|
this layout"""
|
|
#***1,000,000 number is used to avoid empty sequences errors***
|
|
# FIXME Is this hard coded value ok??
|
|
try:
|
|
lowestx1 = min(rect.offset[0] for rect in self.objs)
|
|
lowesty1 = min(rect.offset[1] for rect in self.objs)
|
|
except:
|
|
[lowestx1, lowesty1] = [1000000.0, 1000000.0]
|
|
try:
|
|
lowestx2 = min(inst.offset[0] for inst in self.insts)
|
|
lowesty2 = min(inst.offset[1] for inst in self.insts)
|
|
except:
|
|
[lowestx2, lowesty2] = [1000000.0, 1000000.0]
|
|
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2))
|
|
|
|
def offset_attributes(self, coordinate):
|
|
"""Translates all stored 2d cartesian coordinates within the
|
|
attr dictionary"""
|
|
# FIXME: This is dangerous. I think we should not do this, but explicitly
|
|
# offset the necessary coordinates.
|
|
|
|
#for attr_key, attr_val in self.attr.items():
|
|
for attr_key in dir(self):
|
|
attr_val = getattr(self,attr_key)
|
|
|
|
# skip the list of things as these will be offset separately
|
|
if (attr_key in ['objs','insts','mods','pins','conns']): continue
|
|
|
|
# if is a list
|
|
if isinstance(attr_val, list):
|
|
|
|
for i in range(len(attr_val)):
|
|
# each unit in the list is a list coordinates
|
|
if isinstance(attr_val[i], (list,vector)):
|
|
attr_val[i] = vector([attr_val[i][0] - coordinate.x,
|
|
attr_val[i][1] - coordinate.y])
|
|
# the list itself is a coordinate
|
|
else:
|
|
if len(attr_val)!=2: continue
|
|
for val in attr_val:
|
|
if not isinstance(val, (int, long, float)): continue
|
|
setattr(self,attr_key, vector([attr_val[0] - coordinate.x,
|
|
attr_val[1] - coordinate.y]))
|
|
break
|
|
|
|
# if is a vector coordinate
|
|
if isinstance(attr_val, vector):
|
|
setattr(self, attr_key, vector(attr_val[0] - coordinate.x,
|
|
attr_val[1] - coordinate.y))
|
|
|
|
|
|
|
|
def translate(self, coordinate):
|
|
"""Translates all 2d cartesian coordinates in a layout given
|
|
the (x,y) offset"""
|
|
for obj in self.objs:
|
|
obj.offset = obj.offset - coordinate
|
|
for inst in self.insts:
|
|
inst.offset = inst.offset - coordinate
|
|
|
|
# FIXME: Make name optional and pick a random one if not specified
|
|
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))
|
|
message = []
|
|
for x in self.insts:
|
|
message.append(x.name)
|
|
debug.info(4, "adding instance" + ",".join(message))
|
|
|
|
def add_rect(self, layer, offset, width, height):
|
|
"""Adds a rectangle on a given layer,offset with width and height"""
|
|
debug.info(3, "adding rectangle (" + str(layer) + ") :"
|
|
+ str(width) + "x" + str(height) + " @ " + str(offset))
|
|
|
|
# negative layers indicate "unused" layers in a given technology
|
|
layerNumber = techlayer[layer]
|
|
if layerNumber >= 0:
|
|
self.objs.append(geometry.rectangle(layerNumber, offset, width, height))
|
|
|
|
def add_layout_pin(self, text, layer, offset, width, height):
|
|
"""Create a labeled pin"""
|
|
self.add_rect(layer=layer,
|
|
offset=offset,
|
|
width=width,
|
|
height=height)
|
|
self.add_label(text=text,
|
|
layer=layer,
|
|
offset=offset)
|
|
|
|
|
|
def add_label(self, text, layer, offset=[0,0],zoom=1):
|
|
"""Adds a text label on the given layer,offset, and zoom level"""
|
|
debug.info(3,"add label " + text + " " + str(layer) + " " + str(offset))
|
|
# negative layers indicate "unused" layers in a given technology
|
|
layerNumber = techlayer[layer]
|
|
if layerNumber >= 0:
|
|
self.objs.append(geometry.label(text, layerNumber, offset, zoom))
|
|
|
|
|
|
def add_path(self, layer, coordinates, width=None, offset=None):
|
|
"""Connects a routing path on given layer,coordinates,width."""
|
|
debug.info(3,"add path " + str(layer) + " " + str(coordinates))
|
|
import path
|
|
# NOTE: (UNTESTED) add_path(...) is currently not used
|
|
# negative layers indicate "unused" layers in a given technology
|
|
#layerNumber = techlayer[layer]
|
|
#if layerNumber >= 0:
|
|
# self.objs.append(geometry.path(layerNumber, coordinates, width))
|
|
|
|
# add an instance of our path that breaks down into rectangles
|
|
if width==None:
|
|
self.layer_width = drc["minwidth_{0}".format(layer)]
|
|
else:
|
|
self.layer_width = width
|
|
# Wires/paths are created so that the first point is (0,0)
|
|
# therefore we offset the instantiation to the first point
|
|
# however, we can override this
|
|
if offset==None:
|
|
inst_offset = coordinates[0]
|
|
else:
|
|
inst_offset = offset
|
|
|
|
route = path.path(layer=layer,
|
|
position_list=coordinates,
|
|
width=self.layer_width)
|
|
self.add_mod(route)
|
|
self.add_inst(name=route.name,
|
|
mod=route,
|
|
offset=inst_offset)
|
|
# We don't model the logical connectivity of wires/paths
|
|
self.connect_inst([])
|
|
return route
|
|
|
|
def add_wire(self, layers, coordinates, offset=None):
|
|
"""Connects a routing path on given layer,coordinates,width.
|
|
The layers are the (vertical, via, horizontal). """
|
|
import wire
|
|
debug.info(3,"add wire " + str(layers) + " " + str(coordinates))
|
|
# Wires/paths are created so that the first point is (0,0)
|
|
# therefore we offset the instantiation to the first point
|
|
# however, we can override this
|
|
if offset==None:
|
|
inst_offset=coordinates[0]
|
|
else:
|
|
inst_offset=offset
|
|
# add an instance of our path that breaks down into rectangles and contacts
|
|
route = wire.wire(layer_stack=layers,
|
|
position_list=coordinates)
|
|
self.add_mod(route)
|
|
self.add_inst(name=route.name,
|
|
mod=route,
|
|
offset=inst_offset)
|
|
# We don't model the logical connectivity of wires/paths
|
|
self.connect_inst([])
|
|
return route
|
|
|
|
def add_contact(self, layers, offset, size=[1,1], mirror="R0", rotate=0):
|
|
""" This is just an alias for a via."""
|
|
return self.add_via(layers=layers,
|
|
offset=offset,
|
|
size=size,
|
|
mirror=mirror,rotate=rotate)
|
|
|
|
def add_via(self, layers, offset, size=[1,1], mirror="R0", rotate=0):
|
|
""" Add a three layer via structure. """
|
|
import contact
|
|
via = contact.contact(layer_stack=layers,
|
|
dimensions=size,
|
|
offset=offset)
|
|
self.add_mod(via)
|
|
self.add_inst(name=via.name,
|
|
mod=via,
|
|
offset=offset,
|
|
mirror=mirror,
|
|
rotate=rotate)
|
|
# We don't model the logical connectivity of wires/paths
|
|
self.connect_inst([])
|
|
return via
|
|
|
|
def add_ptx(self, name, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
|
|
"""Adds a ptx module to the design."""
|
|
import ptx
|
|
mos = ptx.ptx(name=name,
|
|
width=width,
|
|
mults=mults,
|
|
tx_type=tx_type)
|
|
self.add_mod(mos)
|
|
self.add_inst(name=mos.name,
|
|
mod=mos,
|
|
offset=offset,
|
|
mirror=mirror,
|
|
rotate=rotate)
|
|
return mos
|
|
|
|
|
|
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):
|
|
debug.info(2, "opening %s" % self.gds_file)
|
|
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
|
|
reader = gdsMill.Gds2reader(self.gds)
|
|
reader.loadFromFile(self.gds_file)
|
|
# TODO: parse the width/height
|
|
# TODO: parse the pin locations
|
|
else:
|
|
debug.info(2, "creating structure %s" % self.name)
|
|
self.gds = gdsMill.VlsiLayout(
|
|
name=self.name, units=GDS["unit"])
|
|
|
|
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
|
|
debug.info(3, "Printing %s" % gds_file)
|
|
arrayCellLayout = gdsMill.VlsiLayout()
|
|
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"""
|
|
if self.visited:
|
|
return
|
|
for i in self.insts:
|
|
i.gds_write_file(newLayout)
|
|
for i in self.objs:
|
|
i.gds_write_file(newLayout)
|
|
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))
|
|
|
|
#self.gds = gdsMill.VlsiLayout(name=self.name,units=GDS["unit"])
|
|
writer = gdsMill.Gds2writer(self.gds)
|
|
# clear the visited flag for the traversal
|
|
self.clear_visited()
|
|
# 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)
|
|
|
|
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,
|
|
"|========= LIST OF OBJECTS (Rects) FOR: " + self.attr["name"])
|
|
debug.info(0,
|
|
"|==============================================================================|")
|
|
for obj in self.objs:
|
|
debug.info(0, "layer={0} : offset={1} : size={2}".format(
|
|
obj.layerNumber, obj.offset, obj.size))
|
|
|
|
debug.info(0,
|
|
"|==============================================================================|")
|
|
debug.info(0,
|
|
"|========= LIST OF INSTANCES FOR: " +
|
|
self.attr["name"])
|
|
debug.info(0,
|
|
"|==============================================================================|")
|
|
for inst in self.insts:
|
|
debug.info(0, "name={0} : mod={1} : offset={2}".format(
|
|
inst.name, inst.mod.name, inst.offset))
|