2019-04-26 21:21:50 +02:00
|
|
|
# See LICENSE for licensing information.
|
|
|
|
|
#
|
2019-06-14 17:43:41 +02:00
|
|
|
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
|
|
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
|
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
|
|
|
# All rights reserved.
|
2019-04-26 21:21:50 +02:00
|
|
|
#
|
2019-08-30 01:06:34 +02:00
|
|
|
import collections
|
2016-11-08 18:57:35 +01:00
|
|
|
import geometry
|
|
|
|
|
import gdsMill
|
|
|
|
|
import debug
|
|
|
|
|
from tech import drc, GDS
|
|
|
|
|
from tech import layer as techlayer
|
|
|
|
|
import os
|
2018-08-28 01:42:48 +02:00
|
|
|
from globals import OPTS
|
2016-11-08 18:57:35 +01:00
|
|
|
from vector import vector
|
2017-08-24 00:02:15 +02:00
|
|
|
from pin_layout import pin_layout
|
2019-12-23 22:16:08 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2019-01-11 23:15:16 +01:00
|
|
|
class layout():
|
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):
|
2016-11-08 18:57:35 +01:00
|
|
|
self.name = name
|
|
|
|
|
self.width = None
|
|
|
|
|
self.height = None
|
2019-12-06 00:14:25 +01:00
|
|
|
self.boundary = 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
|
2018-10-04 23:04:29 +02:00
|
|
|
self.visited = [] # List of modules we have already visited
|
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)
|
2018-07-13 23:45:46 +02:00
|
|
|
return 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):
|
2019-12-23 22:16:08 +01:00
|
|
|
base_offset = vector(x_offset, inv_num * height)
|
2017-08-24 00:02:15 +02:00
|
|
|
y_dir = 1
|
|
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
# we lose a rail after every 2 gates
|
|
|
|
|
base_offset = vector(x_offset,
|
|
|
|
|
(inv_num + 1) * height - \
|
|
|
|
|
(inv_num % 2) * drc["minwidth_m1"])
|
2017-08-24 00:02:15 +02:00
|
|
|
y_dir = -1
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
return (base_offset, y_dir)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def find_lowest_coords(self):
|
2020-01-23 20:43:41 +01:00
|
|
|
"""
|
|
|
|
|
Finds the lowest set of 2d cartesian coordinates within
|
|
|
|
|
this layout
|
|
|
|
|
"""
|
2018-02-03 00:17:21 +01:00
|
|
|
|
2019-12-23 22:16:08 +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")
|
2018-02-03 00:17:21 +01:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
lowestx1 = lowesty1 = None
|
|
|
|
|
if len(self.insts) > 0:
|
2018-02-03 00:17:21 +01:00
|
|
|
lowestx2 = min(inst.lx() for inst in self.insts)
|
|
|
|
|
lowesty2 = min(inst.by() for inst in self.insts)
|
|
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
lowestx2 = lowesty2 = None
|
2019-12-06 00:14:25 +01:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if lowestx1 == None and lowestx2 == None:
|
2019-12-06 00:14:25 +01:00
|
|
|
return None
|
2019-12-23 22:16:08 +01:00
|
|
|
elif lowestx1 == None:
|
|
|
|
|
return vector(lowestx2, lowesty2)
|
|
|
|
|
elif lowestx2 == None:
|
|
|
|
|
return vector(lowestx1, lowesty1)
|
2018-02-03 00:17:21 +01:00
|
|
|
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):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Finds the highest set of 2d cartesian coordinates within
|
|
|
|
|
this layout
|
|
|
|
|
"""
|
|
|
|
|
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")
|
2018-02-03 00:17:21 +01:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
highestx1 = highesty1 = None
|
|
|
|
|
if len(self.insts) > 0:
|
2018-02-03 00:17:21 +01:00
|
|
|
highestx2 = max(inst.rx() for inst in self.insts)
|
|
|
|
|
highesty2 = max(inst.uy() for inst in self.insts)
|
|
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
highestx2 = highesty2 = None
|
|
|
|
|
if highestx1 == None and highestx2 == None:
|
2019-12-06 00:14:25 +01:00
|
|
|
return None
|
2019-12-23 22:16:08 +01:00
|
|
|
elif highestx1 == None:
|
|
|
|
|
return vector(highestx2, highesty2)
|
|
|
|
|
elif highestx2 == None:
|
|
|
|
|
return vector(highestx1, highesty1)
|
2018-02-03 00:17:21 +01:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
return vector(max(highestx1, highestx2),
|
|
|
|
|
max(highesty1, highesty2))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2020-01-23 20:43:41 +01:00
|
|
|
def find_highest_layer_coords(self, layer):
|
|
|
|
|
"""
|
|
|
|
|
Finds the highest set of 2d cartesian coordinates within
|
|
|
|
|
this layout on a layer
|
|
|
|
|
"""
|
|
|
|
|
# Only consider the layer not the purpose for now
|
|
|
|
|
layerNumber = techlayer[layer][0]
|
|
|
|
|
try:
|
|
|
|
|
highestx = max(obj.rx() for obj in self.objs if obj.layerNumber == layerNumber)
|
|
|
|
|
except ValueError:
|
|
|
|
|
highestx =0
|
|
|
|
|
try:
|
|
|
|
|
highesty = max(obj.uy() for obj in self.objs if obj.layerNumber == layerNumber)
|
|
|
|
|
except ValueError:
|
|
|
|
|
highesty = 0
|
|
|
|
|
|
|
|
|
|
for inst in self.insts:
|
|
|
|
|
# This really should be rotated/mirrored etc...
|
|
|
|
|
subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset
|
|
|
|
|
highestx = max(highestx, subcoord.x)
|
|
|
|
|
highesty = max(highesty, subcoord.y)
|
|
|
|
|
|
|
|
|
|
return vector(highestx, highesty)
|
|
|
|
|
|
|
|
|
|
def find_lowest_layer_coords(self, layer):
|
|
|
|
|
"""
|
|
|
|
|
Finds the highest set of 2d cartesian coordinates within
|
|
|
|
|
this layout on a layer
|
|
|
|
|
"""
|
|
|
|
|
# Only consider the layer not the purpose for now
|
|
|
|
|
layerNumber = techlayer[layer][0]
|
|
|
|
|
try:
|
|
|
|
|
lowestx = min(obj.lx() for obj in self.objs if obj.layerNumber == layerNumber)
|
|
|
|
|
except ValueError:
|
|
|
|
|
lowestx = 0
|
|
|
|
|
try:
|
|
|
|
|
lowesty = min(obj.by() for obj in self.objs if obj.layerNumber == layerNumber)
|
|
|
|
|
except ValueError:
|
|
|
|
|
lowesty = 0
|
|
|
|
|
|
|
|
|
|
for inst in self.insts:
|
|
|
|
|
# This really should be rotated/mirrored etc...
|
|
|
|
|
subcoord = inst.mod.find_lowest_layer_coords(layer) + inst.offset
|
|
|
|
|
lowestx = min(lowestx, subcoord.x)
|
|
|
|
|
lowesty = min(lowesty, subcoord.y)
|
|
|
|
|
|
|
|
|
|
return vector(lowestx, lowesty)
|
|
|
|
|
|
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":
|
2018-07-16 22:46:12 +02:00
|
|
|
inst.compute_boundary(inst.offset)
|
2017-09-14 00:46:41 +02:00
|
|
|
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]
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
|
|
|
|
|
""" Adds an instance of a mod to this module """
|
2016-11-08 18:57:35 +01:00
|
|
|
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]))
|
2018-09-24 20:44:32 +02:00
|
|
|
# This is commented out for runtime reasons
|
|
|
|
|
#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):
|
2019-12-23 22:16:08 +01:00
|
|
|
""" Retrieve an instance by name """
|
2017-08-24 00:02:15 +02:00
|
|
|
for inst in self.insts:
|
|
|
|
|
if inst.name == name:
|
|
|
|
|
return inst
|
|
|
|
|
return None
|
|
|
|
|
|
2018-10-19 23:21:03 +02:00
|
|
|
def add_rect(self, layer, offset, width=None, height=None):
|
|
|
|
|
"""
|
|
|
|
|
Adds a rectangle on a given layer,offset with width and height
|
|
|
|
|
"""
|
|
|
|
|
if not width:
|
2019-12-23 22:16:08 +01:00
|
|
|
width = drc["minwidth_{}".format(layer)]
|
2018-10-19 23:21:03 +02:00
|
|
|
if not height:
|
2019-12-23 22:16:08 +01:00
|
|
|
height = drc["minwidth_{}".format(layer)]
|
2016-11-08 18:57:35 +01:00
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2019-10-25 19:03:25 +02:00
|
|
|
lpp = techlayer[layer]
|
|
|
|
|
if lpp[0] >= 0:
|
|
|
|
|
self.objs.append(geometry.rectangle(lpp, 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
|
|
|
|
2018-10-19 23:21:03 +02:00
|
|
|
def add_rect_center(self, layer, offset, width=None, height=None):
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Adds a rectangle on a given layer at the center
|
|
|
|
|
point with width and height
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
|
|
|
|
if not width:
|
2019-12-23 22:16:08 +01:00
|
|
|
width = drc["minwidth_{}".format(layer)]
|
2018-10-19 23:21:03 +02:00
|
|
|
if not height:
|
2019-12-23 22:16:08 +01:00
|
|
|
height = drc["minwidth_{}".format(layer)]
|
2017-11-29 03:13:32 +01:00
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2019-10-25 19:03:25 +02:00
|
|
|
lpp = techlayer[layer]
|
2019-12-23 22:16:08 +01:00
|
|
|
corrected_offset = offset - vector(0.5 * width, 0.5 * height)
|
2019-10-25 19:03:25 +02:00
|
|
|
if lpp[0] >= 0:
|
2019-12-23 22:16:08 +01:00
|
|
|
self.objs.append(geometry.rectangle(lpp,
|
|
|
|
|
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):
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Add a min-width rectanglular segment using center
|
|
|
|
|
line on the start to end point
|
|
|
|
|
"""
|
|
|
|
|
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)
|
|
|
|
|
return self.add_rect(layer,
|
|
|
|
|
start-offset,
|
|
|
|
|
end.x-start.x,
|
|
|
|
|
minwidth_layer)
|
2017-10-07 00:30:15 +02:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
offset = vector(0.5 * minwidth_layer, 0)
|
|
|
|
|
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):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Return the pin or list of pins
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
2017-09-30 01:22:13 +02:00
|
|
|
try:
|
2019-12-23 22:16:08 +01:00
|
|
|
if len(self.pin_map[text]) > 1:
|
2018-03-17 01:46:29 +01:00
|
|
|
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
|
2017-09-30 01:22:13 +02:00
|
|
|
# If we have one pin, return it and not the list.
|
|
|
|
|
# Otherwise, should use get_pins()
|
2019-04-02 00:45:44 +02:00
|
|
|
any_pin = next(iter(self.pin_map[text]))
|
|
|
|
|
return any_pin
|
2019-12-23 22:16:08 +01:00
|
|
|
except Exception:
|
2017-09-30 01:22:13 +02:00
|
|
|
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):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Return a pin list (instead of a single pin)
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
2018-10-06 17:30:38 +02:00
|
|
|
if text in self.pin_map.keys():
|
|
|
|
|
return self.pin_map[text]
|
|
|
|
|
else:
|
2019-04-02 00:59:57 +02:00
|
|
|
return set()
|
2019-06-20 01:03:21 +02:00
|
|
|
|
|
|
|
|
def get_pin_names(self):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
2019-06-20 01:03:21 +02:00
|
|
|
Return a pin list of all pins
|
|
|
|
|
"""
|
|
|
|
|
return self.pin_map.keys()
|
|
|
|
|
|
2017-09-14 00:46:41 +02:00
|
|
|
def copy_layout_pin(self, instance, pin_name, new_name=""):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
2017-09-14 00:46:41 +02:00
|
|
|
Create a copied version of the layout pin at the current level.
|
2019-12-23 22:16:08 +01:00
|
|
|
You can optionally rename the pin to a new name.
|
2017-09-14 00:46:41 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
pins = instance.get_pins(pin_name)
|
2019-07-05 18:03:52 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.check(len(pins) > 0,
|
|
|
|
|
"Could not find pin {}".format(pin_name))
|
2019-07-05 18:03:52 +02:00
|
|
|
|
2017-09-14 00:46:41 +02:00
|
|
|
for pin in pins:
|
2019-12-23 22:16:08 +01:00
|
|
|
if new_name == "":
|
2017-09-14 00:46:41 +02:00
|
|
|
new_name = pin.name
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_layout_pin(new_name,
|
|
|
|
|
pin.layer,
|
|
|
|
|
pin.ll(),
|
|
|
|
|
pin.width(),
|
|
|
|
|
pin.height())
|
2017-09-14 00:46:41 +02:00
|
|
|
|
2019-06-20 01:03:21 +02:00
|
|
|
def copy_layout_pins(self, instance, prefix=""):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
2019-06-20 01:03:21 +02:00
|
|
|
Create a copied version of the layout pin at the current level.
|
2019-12-23 22:16:08 +01:00
|
|
|
You can optionally rename the pin to a new name.
|
2019-06-20 01:03:21 +02:00
|
|
|
"""
|
|
|
|
|
for pin_name in self.pin_map.keys():
|
2019-07-05 18:03:52 +02:00
|
|
|
self.copy_layout_pin(instance, pin_name, prefix+pin_name)
|
2019-06-20 01:03:21 +02:00
|
|
|
|
2018-03-21 21:20:48 +01:00
|
|
|
def add_layout_pin_segment_center(self, text, layer, start, end):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Creates a path like pin with center-line convention
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
2017-09-30 01:22:13 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.check(start.x == end.x or start.y == end.y,
|
|
|
|
|
"Cannot have a non-manhatten layout pin.")
|
2017-09-30 01:22:13 +02:00
|
|
|
|
|
|
|
|
minwidth_layer = drc["minwidth_{}".format(layer)]
|
|
|
|
|
|
|
|
|
|
# one of these will be zero
|
2019-12-23 22:16:08 +01:00
|
|
|
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))
|
2017-09-30 01:22:13 +02:00
|
|
|
|
|
|
|
|
# Shift it down 1/2 a width in the 0 dimension
|
2019-12-23 22:16:08 +01:00
|
|
|
if height == 0:
|
|
|
|
|
ll_offset -= vector(0, 0.5 * minwidth_layer)
|
|
|
|
|
if width == 0:
|
|
|
|
|
ll_offset -= vector(0.5 * minwidth_layer, 0)
|
2017-09-30 01:22:13 +02:00
|
|
|
# This makes sure it is long enough, but also it is not 0 width!
|
2019-12-23 22:16:08 +01:00
|
|
|
height = max(minwidth_layer, height)
|
|
|
|
|
width = max(minwidth_layer, width)
|
2017-09-30 01:22:13 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
return self.add_layout_pin(text,
|
|
|
|
|
layer,
|
|
|
|
|
ll_offset,
|
|
|
|
|
width,
|
|
|
|
|
height)
|
2017-11-29 03:13:32 +01:00
|
|
|
|
2018-03-21 21:20:48 +01:00
|
|
|
def add_layout_pin_rect_center(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 """
|
2018-10-19 23:21:03 +02:00
|
|
|
if not width:
|
2019-12-23 22:16:08 +01:00
|
|
|
width = drc["minwidth_{0}".format(layer)]
|
2018-10-19 23:21:03 +02:00
|
|
|
if not height:
|
2019-12-23 22:16:08 +01:00
|
|
|
height = drc["minwidth_{0}".format(layer)]
|
2017-11-29 03:13:32 +01:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
ll_offset = offset - vector(0.5 * width, 0.5 * height)
|
2017-11-29 03:13:32 +01:00
|
|
|
|
|
|
|
|
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):
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
|
|
|
|
Delete a labeled pin (or all pins of the same name)
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
self.pin_map[text] = set()
|
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):
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Create a labeled pin
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
|
|
|
|
if not width:
|
2019-12-23 22:16:08 +01:00
|
|
|
width = drc["minwidth_{0}".format(layer)]
|
2018-10-19 23:21:03 +02:00
|
|
|
if not height:
|
2019-12-23 22:16:08 +01:00
|
|
|
height = drc["minwidth_{0}".format(layer)]
|
2018-08-28 19:41:19 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
new_pin = pin_layout(text,
|
|
|
|
|
[offset, offset+vector(width, height)],
|
|
|
|
|
layer)
|
2017-08-25 01:22:14 +02:00
|
|
|
|
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.
|
2019-04-02 00:45:44 +02:00
|
|
|
if new_pin not in self.pin_map[text]:
|
|
|
|
|
self.pin_map[text].add(new_pin)
|
2017-08-24 00:02:15 +02:00
|
|
|
except KeyError:
|
2019-04-02 00:45:44 +02:00
|
|
|
self.pin_map[text] = set()
|
|
|
|
|
self.pin_map[text].add(new_pin)
|
2017-08-25 01:22:14 +02:00
|
|
|
|
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):
|
2018-10-19 23:21:03 +02:00
|
|
|
"""
|
|
|
|
|
Create a labeled pin WITHOUT the pin data structure. This is not an
|
2017-08-25 01:22:14 +02:00
|
|
|
actual pin but a named net so that we can add a correspondence point
|
|
|
|
|
in LVS.
|
|
|
|
|
"""
|
2018-10-19 23:21:03 +02:00
|
|
|
if not width:
|
2019-12-23 22:16:08 +01:00
|
|
|
width = drc["minwidth_{0}".format(layer)]
|
2018-10-19 23:21:03 +02:00
|
|
|
if not height:
|
2019-12-23 22:16:08 +01:00
|
|
|
height = drc["minwidth_{0}".format(layer)]
|
2017-08-25 01:22:14 +02:00
|
|
|
self.add_rect(layer=layer,
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=height)
|
|
|
|
|
self.add_label(text=text,
|
|
|
|
|
layer=layer,
|
2019-12-23 22:16:08 +01:00
|
|
|
offset=offset + vector(0.5 * width,
|
|
|
|
|
0.5 * height))
|
2017-08-25 01:22:14 +02:00
|
|
|
|
2019-12-23 22:16:08 +01: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
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset))
|
2019-10-25 19:03:25 +02:00
|
|
|
lpp = techlayer[layer]
|
|
|
|
|
if lpp[0] >= 0:
|
|
|
|
|
self.objs.append(geometry.label(text, lpp, 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."""
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(4, "add path " + str(layer) + " " + str(coordinates))
|
2019-01-26 00:07:56 +01:00
|
|
|
import wire_path
|
2016-11-08 18:57:35 +01:00
|
|
|
# NOTE: (UNTESTED) add_path(...) is currently not used
|
|
|
|
|
# negative layers indicate "unused" layers in a given technology
|
2019-12-23 22:16:08 +01:00
|
|
|
# lpp = techlayer[layer]
|
|
|
|
|
# if lpp[0] >= 0:
|
|
|
|
|
# self.objs.append(geometry.path(lpp, coordinates, width))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2019-01-26 00:07:56 +01:00
|
|
|
wire_path.wire_path(obj=self,
|
2019-12-23 22:16:08 +01:00
|
|
|
layer=layer,
|
|
|
|
|
position_list=coordinates,
|
2019-01-26 00:07:56 +01:00
|
|
|
width=width)
|
2017-08-07 19:24:45 +02:00
|
|
|
|
2018-09-18 23:55:36 +02:00
|
|
|
def add_route(self, layers, coordinates, layer_widths):
|
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
|
2019-12-23 22:16:08 +01: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,
|
2019-12-23 22:16:08 +01:00
|
|
|
layer_stack=layers,
|
2018-09-18 23:55:36 +02:00
|
|
|
path=coordinates,
|
|
|
|
|
layer_widths=layer_widths)
|
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
|
2019-12-23 22:16:08 +01:00
|
|
|
# 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,
|
2019-12-23 22:16:08 +01:00
|
|
|
layer_stack=layers,
|
2017-08-07 19:24:45 +02:00
|
|
|
position_list=coordinates)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2019-04-01 23:23:47 +02:00
|
|
|
def get_preferred_direction(self, layer):
|
|
|
|
|
""" Return the preferred routing directions """
|
2019-12-19 21:54:10 +01:00
|
|
|
from tech import preferred_directions
|
|
|
|
|
return preferred_directions[layer]
|
2019-04-01 23:23:47 +02:00
|
|
|
|
|
|
|
|
def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None):
|
2016-11-08 18:57:35 +01:00
|
|
|
""" Add a three layer via structure. """
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if not directions:
|
2019-12-19 21:54:10 +01:00
|
|
|
directions = (self.get_preferred_direction(layers[0]),
|
|
|
|
|
self.get_preferred_direction(layers[2]))
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2019-01-17 01:56:06 +01:00
|
|
|
from sram_factory import factory
|
|
|
|
|
via = factory.create(module_type="contact",
|
|
|
|
|
layer_stack=layers,
|
|
|
|
|
dimensions=size,
|
2019-04-01 23:23:47 +02:00
|
|
|
directions=directions,
|
2019-01-17 01:56:06 +01:00
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2016-11-08 18:57:35 +01:00
|
|
|
self.add_mod(via)
|
2019-12-23 22:16:08 +01:00
|
|
|
inst = self.add_inst(name=via.name,
|
|
|
|
|
mod=via,
|
|
|
|
|
offset=offset)
|
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
|
|
|
|
2019-04-01 23:23:47 +02:00
|
|
|
def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None):
|
2020-01-23 20:43:41 +01:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Add a three layer via structure by the center coordinate
|
|
|
|
|
accounting for mirroring and rotation.
|
|
|
|
|
"""
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if not directions:
|
2019-12-19 21:54:10 +01:00
|
|
|
directions = (self.get_preferred_direction(layers[0]),
|
|
|
|
|
self.get_preferred_direction(layers[2]))
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2019-01-17 01:56:06 +01:00
|
|
|
from sram_factory import factory
|
|
|
|
|
via = factory.create(module_type="contact",
|
|
|
|
|
layer_stack=layers,
|
|
|
|
|
dimensions=size,
|
2019-04-01 23:23:47 +02:00
|
|
|
directions=directions,
|
2019-01-17 01:56:06 +01:00
|
|
|
implant_type=implant_type,
|
|
|
|
|
well_type=well_type)
|
2017-09-30 01:22:13 +02:00
|
|
|
height = via.height
|
|
|
|
|
width = via.width
|
2018-09-05 01:35:40 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
corrected_offset = offset + vector(-0.5 * width,
|
|
|
|
|
-0.5 * height)
|
2017-09-30 01:22:13 +02:00
|
|
|
|
|
|
|
|
self.add_mod(via)
|
2019-12-23 22:16:08 +01:00
|
|
|
inst = self.add_inst(name=via.name,
|
|
|
|
|
mod=via,
|
|
|
|
|
offset=corrected_offset)
|
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)
|
2020-01-23 20:43:41 +01:00
|
|
|
inst = self.add_inst(name=mos.name,
|
2019-12-23 22:16:08 +01:00
|
|
|
mod=mos,
|
|
|
|
|
offset=offset,
|
|
|
|
|
mirror=mirror,
|
|
|
|
|
rotate=rotate)
|
2018-01-26 21:39:00 +01:00
|
|
|
return inst
|
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."""
|
2018-11-16 22:37:17 +01:00
|
|
|
|
|
|
|
|
# This must be done for netlist only mode too
|
|
|
|
|
if os.path.isfile(self.gds_file):
|
2019-12-23 22:16:08 +01:00
|
|
|
self.is_library_cell = True
|
2018-11-16 22:37:17 +01:00
|
|
|
|
2018-08-28 01:42:48 +02:00
|
|
|
if OPTS.netlist_only:
|
|
|
|
|
self.gds = None
|
|
|
|
|
return
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# open the gds file if it exists or else create a blank layout
|
|
|
|
|
if os.path.isfile(self.gds_file):
|
2018-10-04 23:04:29 +02:00
|
|
|
debug.info(3, "opening {}".format(self.gds_file))
|
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:
|
2018-10-04 23:04:29 +02:00
|
|
|
debug.info(3, "Creating layout structure {}".format(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 """
|
2019-12-23 22:16:08 +01:00
|
|
|
if not gds_file:
|
2016-11-08 18:57:35 +01:00
|
|
|
gds_file = self.gds_file
|
2018-10-04 23:04:29 +02:00
|
|
|
debug.info(4, "Printing {}".format(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 """
|
2018-10-04 23:04:29 +02:00
|
|
|
self.visited = []
|
2016-11-08 18:57:35 +01:00
|
|
|
|
2018-10-04 23:04:29 +02:00
|
|
|
def gds_write_file(self, gds_layout):
|
2016-11-08 18:57:35 +01:00
|
|
|
"""Recursive GDS write function"""
|
2018-03-03 03:05:46 +01:00
|
|
|
# Visited means that we already prepared self.gds for this subtree
|
2018-10-04 23:04:29 +02:00
|
|
|
if self.name in self.visited:
|
2016-11-08 18:57:35 +01:00
|
|
|
return
|
|
|
|
|
for i in self.insts:
|
2018-10-04 23:04:29 +02:00
|
|
|
i.gds_write_file(gds_layout)
|
2016-11-08 18:57:35 +01:00
|
|
|
for i in self.objs:
|
2018-10-04 23:04:29 +02:00
|
|
|
i.gds_write_file(gds_layout)
|
2017-10-06 02:35:05 +02:00
|
|
|
for pin_name in self.pin_map.keys():
|
|
|
|
|
for pin in self.pin_map[pin_name]:
|
2018-10-04 23:04:29 +02:00
|
|
|
pin.gds_write_file(gds_layout)
|
2019-12-06 00:14:25 +01:00
|
|
|
|
|
|
|
|
# If it's not a premade cell
|
|
|
|
|
# and we didn't add our own boundary,
|
|
|
|
|
# we should add a boundary just for DRC in some technologies
|
|
|
|
|
if not self.is_library_cell and not self.boundary:
|
|
|
|
|
# If there is a boundary layer, and we didn't create one, add one.
|
|
|
|
|
if "stdc" in techlayer.keys():
|
|
|
|
|
boundary_layer = "stdc"
|
2019-12-23 22:16:08 +01:00
|
|
|
boundary = [self.find_lowest_coords(),
|
|
|
|
|
self.find_highest_coords()]
|
2019-12-06 00:14:25 +01:00
|
|
|
height = boundary[1][1] - boundary[0][1]
|
|
|
|
|
width = boundary[1][0] - boundary[0][0]
|
|
|
|
|
(layer_number, layer_purpose) = techlayer[boundary_layer]
|
|
|
|
|
gds_layout.addBox(layerNumber=layer_number,
|
|
|
|
|
purposeNumber=layer_purpose,
|
|
|
|
|
offsetInMicrons=boundary[0],
|
|
|
|
|
width=width,
|
|
|
|
|
height=height,
|
|
|
|
|
center=False)
|
2019-12-06 00:33:23 +01:00
|
|
|
debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary))
|
2019-12-06 00:14:25 +01:00
|
|
|
|
2018-10-04 23:04:29 +02:00
|
|
|
self.visited.append(self.name)
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
def gds_write(self, gds_name):
|
|
|
|
|
"""Write the entire gds of the object to the file."""
|
2018-10-04 23:04:29 +02:00
|
|
|
debug.info(3, "Writing to {}".format(gds_name))
|
|
|
|
|
|
|
|
|
|
# If we already wrote a GDS, we need to reset and traverse it again in
|
|
|
|
|
# case we made changes.
|
|
|
|
|
if not self.is_library_cell and self.visited:
|
|
|
|
|
debug.info(3, "Creating layout structure {}".format(self.name))
|
|
|
|
|
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
|
2016-11-08 18:57:35 +01:00
|
|
|
|
|
|
|
|
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!
|
2018-10-04 23:04:29 +02:00
|
|
|
# MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
|
|
|
|
|
self.clear_visited()
|
|
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# recursively create all the remaining objects
|
|
|
|
|
self.gds_write_file(self.gds)
|
2018-10-04 23:04:29 +02:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
# populates the xyTree data structure for gds
|
|
|
|
|
# self.gds.prepareForWrite()
|
|
|
|
|
writer.writeToFile(gds_name)
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(3, "Done writing to {}".format(gds_name))
|
2016-11-08 18:57:35 +01:00
|
|
|
|
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!
|
2019-12-23 22:16:08 +01:00
|
|
|
return [vector(0, 0), vector(self.width, self.height)]
|
2017-12-19 18:01:24 +01:00
|
|
|
#return [self.find_lowest_coords(), self.find_highest_coords()]
|
|
|
|
|
|
|
|
|
|
def get_blockages(self, layer, top_level=False):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Write all of the obstacles in the current (and children)
|
|
|
|
|
modules to the lef file.
|
2017-12-19 18:01:24 +01:00
|
|
|
Do not write the pins since they aren't obstructions.
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
if type(layer) == str:
|
2019-11-15 19:47:59 +01:00
|
|
|
lpp = techlayer[layer]
|
2017-12-19 18:01:24 +01:00
|
|
|
else:
|
2019-11-15 19:47:59 +01:00
|
|
|
lpp = layer
|
2017-12-19 18:01:24 +01:00
|
|
|
|
|
|
|
|
blockages = []
|
|
|
|
|
for i in self.objs:
|
2019-11-15 19:47:59 +01:00
|
|
|
blockages += i.get_blockages(lpp)
|
2017-12-19 18:01:24 +01:00
|
|
|
for i in self.insts:
|
2019-11-15 19:47:59 +01:00
|
|
|
blockages += i.get_blockages(lpp)
|
2017-12-19 18:01:24 +01:00
|
|
|
# Must add pin blockages to non-top cells
|
|
|
|
|
if not top_level:
|
2019-11-15 19:47:59 +01:00
|
|
|
blockages += self.get_pin_blockages(lpp)
|
2017-12-19 18:01:24 +01:00
|
|
|
return blockages
|
|
|
|
|
|
2019-11-15 19:47:59 +01:00
|
|
|
def get_pin_blockages(self, lpp):
|
2017-12-19 18:01:24 +01:00
|
|
|
""" 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:
|
2019-11-15 19:47:59 +01:00
|
|
|
if pin.same_lpp(pin.lpp, lpp):
|
2017-12-19 18:01:24 +01:00
|
|
|
blockages += [pin.rect]
|
|
|
|
|
|
|
|
|
|
return blockages
|
|
|
|
|
|
2018-07-10 19:06:59 +02:00
|
|
|
def create_horizontal_pin_bus(self, layer, pitch, offset, names, length):
|
|
|
|
|
""" Create a horizontal bus of pins. """
|
2019-11-15 19:47:59 +01:00
|
|
|
return self.create_bus(layer,
|
|
|
|
|
pitch,
|
|
|
|
|
offset,
|
|
|
|
|
names,
|
|
|
|
|
length,
|
|
|
|
|
vertical=False,
|
|
|
|
|
make_pins=True)
|
2018-07-10 19:06:59 +02:00
|
|
|
|
|
|
|
|
def create_vertical_pin_bus(self, layer, pitch, offset, names, length):
|
|
|
|
|
""" Create a horizontal bus of pins. """
|
2019-11-15 19:47:59 +01:00
|
|
|
return self.create_bus(layer,
|
|
|
|
|
pitch,
|
|
|
|
|
offset,
|
|
|
|
|
names,
|
|
|
|
|
length,
|
|
|
|
|
vertical=True,
|
|
|
|
|
make_pins=True)
|
2018-07-10 19:06:59 +02:00
|
|
|
|
|
|
|
|
def create_vertical_bus(self, layer, pitch, offset, names, length):
|
|
|
|
|
""" Create a horizontal bus. """
|
2019-11-15 19:47:59 +01:00
|
|
|
return self.create_bus(layer,
|
|
|
|
|
pitch,
|
|
|
|
|
offset,
|
|
|
|
|
names,
|
|
|
|
|
length,
|
|
|
|
|
vertical=True,
|
|
|
|
|
make_pins=False)
|
2018-07-10 19:06:59 +02:00
|
|
|
|
2018-07-17 23:23:29 +02:00
|
|
|
def create_horizontal_bus(self, layer, pitch, offset, names, length):
|
2018-07-10 19:06:59 +02:00
|
|
|
""" Create a horizontal bus. """
|
2019-11-15 19:47:59 +01:00
|
|
|
return self.create_bus(layer,
|
|
|
|
|
pitch,
|
|
|
|
|
offset,
|
|
|
|
|
names,
|
|
|
|
|
length,
|
|
|
|
|
vertical=False,
|
|
|
|
|
make_pins=False)
|
2018-07-10 19:06:59 +02:00
|
|
|
|
|
|
|
|
def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins):
|
2019-11-15 19:47:59 +01:00
|
|
|
"""
|
2018-07-10 19:06:59 +02:00
|
|
|
Create a horizontal or vertical bus. It can be either just rectangles, or actual
|
2020-01-23 20:43:41 +01:00
|
|
|
layout pins. It returns an map of line center line positions indexed by name.
|
2019-11-15 19:47:59 +01:00
|
|
|
The other coordinate is a 0 since the bus provides a range.
|
2018-07-17 23:23:29 +02:00
|
|
|
TODO: combine with channel router.
|
2018-07-10 19:06:59 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# half minwidth so we can return the center line offsets
|
2020-01-23 20:43:41 +01:00
|
|
|
half_minwidth = 0.5 * drc["minwidth_{}".format(layer)]
|
2018-07-10 19:06:59 +02:00
|
|
|
|
|
|
|
|
line_positions = {}
|
|
|
|
|
if vertical:
|
|
|
|
|
for i in range(len(names)):
|
2020-01-23 20:43:41 +01:00
|
|
|
line_offset = offset + vector(i * pitch, 0)
|
2018-07-10 19:06:59 +02:00
|
|
|
if make_pins:
|
|
|
|
|
self.add_layout_pin(text=names[i],
|
|
|
|
|
layer=layer,
|
|
|
|
|
offset=line_offset,
|
|
|
|
|
height=length)
|
|
|
|
|
else:
|
|
|
|
|
self.add_rect(layer=layer,
|
|
|
|
|
offset=line_offset,
|
|
|
|
|
height=length)
|
2019-12-23 22:16:08 +01:00
|
|
|
# Make this the center of the rail
|
|
|
|
|
line_positions[names[i]] = line_offset + vector(half_minwidth,
|
|
|
|
|
0.5 * length)
|
2018-07-10 19:06:59 +02:00
|
|
|
else:
|
|
|
|
|
for i in range(len(names)):
|
2019-12-23 22:16:08 +01:00
|
|
|
line_offset = offset + vector(0,
|
|
|
|
|
i * pitch + half_minwidth)
|
2018-07-10 19:06:59 +02:00
|
|
|
if make_pins:
|
|
|
|
|
self.add_layout_pin(text=names[i],
|
|
|
|
|
layer=layer,
|
|
|
|
|
offset=line_offset,
|
|
|
|
|
width=length)
|
|
|
|
|
else:
|
|
|
|
|
self.add_rect(layer=layer,
|
|
|
|
|
offset=line_offset,
|
|
|
|
|
width=length)
|
2018-07-17 23:23:29 +02:00
|
|
|
# Make this the center of the rail
|
2019-12-23 22:16:08 +01:00
|
|
|
line_positions[names[i]] = line_offset + vector(0.5 * length,
|
|
|
|
|
half_minwidth)
|
2018-07-10 19:06:59 +02:00
|
|
|
|
|
|
|
|
return line_positions
|
2018-07-17 23:23:29 +02:00
|
|
|
|
|
|
|
|
def connect_horizontal_bus(self, mapping, inst, bus_offsets,
|
2019-12-17 20:03:36 +01:00
|
|
|
layer_stack=("m1", "via1", "m2")):
|
2018-07-17 23:23:29 +02:00
|
|
|
""" Horizontal version of connect_bus. """
|
|
|
|
|
self.connect_bus(mapping, inst, bus_offsets, layer_stack, True)
|
|
|
|
|
|
|
|
|
|
def connect_vertical_bus(self, mapping, inst, bus_offsets,
|
2019-12-23 22:16:08 +01:00
|
|
|
layer_stack=("m1", "via1", "m2")):
|
2018-07-17 23:23:29 +02:00
|
|
|
""" Vertical version of connect_bus. """
|
|
|
|
|
self.connect_bus(mapping, inst, bus_offsets, layer_stack, False)
|
|
|
|
|
|
|
|
|
|
def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
2018-07-17 23:23:29 +02:00
|
|
|
Connect a mapping of pin -> name for a bus. This could be
|
2019-12-23 22:16:08 +01:00
|
|
|
replaced with a channel router in the future.
|
|
|
|
|
NOTE: This has only really been tested with point-to-point
|
|
|
|
|
connections (not multiple pins on a net).
|
2018-07-17 23:23:29 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
(horizontal_layer, via_layer, vertical_layer) = layer_stack
|
2018-07-17 23:23:29 +02:00
|
|
|
if horizontal:
|
|
|
|
|
route_layer = vertical_layer
|
|
|
|
|
else:
|
|
|
|
|
route_layer = horizontal_layer
|
|
|
|
|
|
|
|
|
|
for (pin_name, bus_name) in mapping:
|
|
|
|
|
pin = inst.get_pin(pin_name)
|
|
|
|
|
pin_pos = pin.center()
|
|
|
|
|
bus_pos = bus_offsets[bus_name]
|
|
|
|
|
|
|
|
|
|
if horizontal:
|
|
|
|
|
# up/down then left/right
|
|
|
|
|
mid_pos = vector(pin_pos.x, bus_pos.y)
|
|
|
|
|
else:
|
|
|
|
|
# left/right then up/down
|
|
|
|
|
mid_pos = vector(bus_pos.x, pin_pos.y)
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_wire(layer_stack,
|
|
|
|
|
[bus_pos, mid_pos, pin_pos])
|
2018-07-17 23:23:29 +02:00
|
|
|
|
|
|
|
|
# Connect to the pin on the instances with a via if it is
|
|
|
|
|
# not on the right layer
|
|
|
|
|
if pin.layer != route_layer:
|
|
|
|
|
self.add_via_center(layers=layer_stack,
|
2018-07-24 23:26:01 +02:00
|
|
|
offset=pin_pos)
|
2019-12-23 22:16:08 +01:00
|
|
|
# FIXME: output pins tend to not be rotate,
|
|
|
|
|
# but supply pins are. Make consistent?
|
2018-07-24 23:26:01 +02:00
|
|
|
|
2018-07-17 23:23:29 +02:00
|
|
|
# We only need a via if they happened to align perfectly
|
|
|
|
|
# so the add_wire didn't add a via
|
|
|
|
|
if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x):
|
|
|
|
|
self.add_via_center(layers=layer_stack,
|
|
|
|
|
offset=bus_pos,
|
|
|
|
|
rotate=90)
|
2019-12-23 22:16:08 +01:00
|
|
|
|
2019-08-22 00:32:49 +02:00
|
|
|
def get_layer_pitch(self, layer):
|
|
|
|
|
""" Return the track pitch on a given layer """
|
2019-12-23 22:16:08 +01:00
|
|
|
if layer == "m1":
|
|
|
|
|
return (self.m1_pitch,
|
|
|
|
|
self.m1_pitch - self.m1_space,
|
|
|
|
|
self.m1_space)
|
|
|
|
|
elif layer == "m2":
|
|
|
|
|
return (self.m2_pitch,
|
|
|
|
|
self.m2_pitch - self.m2_space,
|
|
|
|
|
self.m2_space)
|
|
|
|
|
elif layer == "m3":
|
|
|
|
|
return (self.m3_pitch,
|
|
|
|
|
self.m3_pitch - self.m3_space,
|
|
|
|
|
self.m3_space)
|
|
|
|
|
elif layer == "m4":
|
2019-08-22 02:11:02 +02:00
|
|
|
from tech import layer as tech_layer
|
2019-12-17 20:03:36 +01:00
|
|
|
if "m4" in tech_layer:
|
2019-12-23 22:16:08 +01:00
|
|
|
return (self.m3_pitch,
|
|
|
|
|
self.m3_pitch - self.m4_space,
|
|
|
|
|
self.m4_space)
|
2019-08-22 02:11:02 +02:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
return (self.m3_pitch,
|
|
|
|
|
self.m3_pitch - self.m3_space,
|
|
|
|
|
self.m3_space)
|
2019-08-22 00:32:49 +02:00
|
|
|
else:
|
|
|
|
|
debug.error("Cannot find layer pitch.")
|
|
|
|
|
|
2018-11-29 22:57:40 +01:00
|
|
|
def add_horizontal_trunk_route(self,
|
|
|
|
|
pins,
|
|
|
|
|
trunk_offset,
|
2019-08-22 02:11:02 +02:00
|
|
|
layer_stack,
|
|
|
|
|
pitch):
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Create a trunk route for all pins with
|
|
|
|
|
the trunk located at the given y offset.
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
|
|
|
|
max_x = max([pin.center().x for pin in pins])
|
|
|
|
|
min_x = min([pin.center().x for pin in pins])
|
2019-08-22 02:11:02 +02:00
|
|
|
|
2018-07-25 20:13:30 +02:00
|
|
|
# if we are less than a pitch, just create a non-preferred layer jog
|
2019-08-22 02:11:02 +02:00
|
|
|
if max_x-min_x <= pitch:
|
2019-12-23 22:16:08 +01:00
|
|
|
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
2019-08-22 21:03:38 +02:00
|
|
|
|
2018-07-25 20:13:30 +02:00
|
|
|
# Add the horizontal trunk on the vertical layer!
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_path(self.vertical_layer,
|
|
|
|
|
[vector(min_x - half_layer_width, trunk_offset.y),
|
|
|
|
|
vector(max_x + half_layer_width, trunk_offset.y)])
|
2018-07-25 20:13:30 +02:00
|
|
|
|
|
|
|
|
# Route each pin to the trunk
|
|
|
|
|
for pin in pins:
|
|
|
|
|
# No bend needed here
|
|
|
|
|
mid = vector(pin.center().x, trunk_offset.y)
|
2019-08-22 21:03:38 +02:00
|
|
|
self.add_path(self.vertical_layer, [pin.center(), mid])
|
2018-07-25 20:13:30 +02:00
|
|
|
else:
|
|
|
|
|
# Add the horizontal trunk
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_path(self.horizontal_layer,
|
|
|
|
|
[vector(min_x, trunk_offset.y),
|
|
|
|
|
vector(max_x, trunk_offset.y)])
|
2018-07-25 20:13:30 +02:00
|
|
|
|
|
|
|
|
# Route each pin to the trunk
|
|
|
|
|
for pin in pins:
|
|
|
|
|
mid = vector(pin.center().x, trunk_offset.y)
|
2019-08-22 21:03:38 +02:00
|
|
|
self.add_path(self.vertical_layer, [pin.center(), mid])
|
2018-11-29 22:57:40 +01:00
|
|
|
self.add_via_center(layers=layer_stack,
|
|
|
|
|
offset=mid)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2018-11-29 22:57:40 +01:00
|
|
|
def add_vertical_trunk_route(self,
|
|
|
|
|
pins,
|
|
|
|
|
trunk_offset,
|
2019-08-22 02:11:02 +02:00
|
|
|
layer_stack,
|
|
|
|
|
pitch):
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Create a trunk route for all pins with the
|
|
|
|
|
trunk located at the given x offset.
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
|
|
|
|
max_y = max([pin.center().y for pin in pins])
|
|
|
|
|
min_y = min([pin.center().y for pin in pins])
|
|
|
|
|
|
|
|
|
|
# if we are less than a pitch, just create a non-preferred layer jog
|
2019-08-22 02:11:02 +02:00
|
|
|
if max_y-min_y <= pitch:
|
2019-08-22 21:03:38 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
2019-08-22 02:11:02 +02:00
|
|
|
|
2019-08-22 21:03:38 +02:00
|
|
|
# Add the vertical trunk on the horizontal layer!
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_path(self.horizontal_layer,
|
|
|
|
|
[vector(trunk_offset.x, min_y - half_layer_width),
|
|
|
|
|
vector(trunk_offset.x,max_y + half_layer_width)])
|
2018-07-25 20:13:30 +02:00
|
|
|
|
|
|
|
|
# Route each pin to the trunk
|
|
|
|
|
for pin in pins:
|
|
|
|
|
# No bend needed here
|
|
|
|
|
mid = vector(trunk_offset.x, pin.center().y)
|
2019-08-22 21:03:38 +02:00
|
|
|
self.add_path(self.horizontal_layer, [pin.center(), mid])
|
2018-07-25 20:13:30 +02:00
|
|
|
else:
|
|
|
|
|
# Add the vertical trunk
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_path(self.vertical_layer,
|
|
|
|
|
[vector(trunk_offset.x, min_y),
|
|
|
|
|
vector(trunk_offset.x, max_y)])
|
2018-07-25 20:13:30 +02:00
|
|
|
|
|
|
|
|
# Route each pin to the trunk
|
|
|
|
|
for pin in pins:
|
|
|
|
|
mid = vector(trunk_offset.x, pin.center().y)
|
2019-08-22 21:03:38 +02:00
|
|
|
self.add_path(self.horizontal_layer, [pin.center(), mid])
|
2018-11-29 22:57:40 +01:00
|
|
|
self.add_via_center(layers=layer_stack,
|
2019-04-01 23:23:47 +02:00
|
|
|
offset=mid)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2018-11-29 21:12:10 +01:00
|
|
|
def create_channel_route(self, netlist,
|
2019-12-23 22:16:08 +01:00
|
|
|
offset,
|
2019-12-13 23:13:41 +01:00
|
|
|
layer_stack,
|
2018-07-28 00:15:40 +02:00
|
|
|
vertical=False):
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
2018-11-29 21:12:10 +01:00
|
|
|
The net list is a list of the nets. Each net is a list of pins
|
|
|
|
|
to be connected. Offset is the lower-left of where the
|
2018-09-12 00:53:12 +02:00
|
|
|
routing channel will start. This does NOT try to minimize the
|
|
|
|
|
number of tracks -- instead, it picks an order to avoid the
|
|
|
|
|
vertical conflicts between pins.
|
|
|
|
|
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
2018-08-15 23:19:04 +02:00
|
|
|
def remove_net_from_graph(pin, g):
|
2018-11-11 21:02:42 +01:00
|
|
|
"""
|
|
|
|
|
Remove the pin from the graph and all conflicts
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
g.pop(pin, None)
|
2018-11-11 21:02:42 +01:00
|
|
|
|
2018-07-26 00:36:16 +02:00
|
|
|
# Remove the pin from all conflicts
|
2018-11-11 21:02:42 +01:00
|
|
|
# FIXME: This is O(n^2), so maybe optimize it.
|
2018-07-26 00:36:16 +02:00
|
|
|
for other_pin,conflicts in g.items():
|
|
|
|
|
if pin in conflicts:
|
|
|
|
|
conflicts.remove(pin)
|
2018-08-15 23:19:04 +02:00
|
|
|
g[other_pin]=conflicts
|
|
|
|
|
return g
|
2018-07-26 00:36:16 +02:00
|
|
|
|
2019-08-22 00:32:49 +02:00
|
|
|
def vcg_nets_overlap(net1, net2, vertical, pitch):
|
2020-01-23 20:43:41 +01:00
|
|
|
"""
|
2018-11-11 21:02:42 +01:00
|
|
|
Check all the pin pairs on two nets and return a pin
|
2019-12-23 22:16:08 +01:00
|
|
|
overlap if any pin overlaps.
|
2018-11-11 21:02:42 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
for pin1 in net1:
|
|
|
|
|
for pin2 in net2:
|
2019-08-22 00:32:49 +02:00
|
|
|
if vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
2018-09-11 22:28:28 +02:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
2019-08-22 00:32:49 +02:00
|
|
|
def vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
2018-11-11 21:02:42 +01:00
|
|
|
""" Check for vertical or horizontal overlap of the two pins """
|
2018-11-29 21:12:10 +01:00
|
|
|
# FIXME: If the pins are not in a row, this may break.
|
2019-12-23 22:16:08 +01:00
|
|
|
# However, a top pin shouldn't overlap another top pin,
|
|
|
|
|
# for example, so the
|
2018-11-29 21:12:10 +01:00
|
|
|
# extra comparison *shouldn't* matter.
|
|
|
|
|
|
2018-11-11 21:25:53 +01:00
|
|
|
# Pin 1 must be in the "BOTTOM" set
|
|
|
|
|
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x-pin2.center().x)<pitch
|
2018-09-11 22:28:28 +02:00
|
|
|
|
2018-11-11 21:02:42 +01:00
|
|
|
# Pin 1 must be in the "LEFT" set
|
2018-09-11 22:43:47 +02:00
|
|
|
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y-pin2.center().y)<pitch
|
2018-11-11 21:02:42 +01:00
|
|
|
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
|
|
|
|
|
return overlaps
|
2018-09-11 22:28:28 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if self.get_preferred_direction(layer_stack[0]) == "V":
|
2019-08-22 02:11:02 +02:00
|
|
|
self.vertical_layer = layer_stack[0]
|
|
|
|
|
self.horizontal_layer = layer_stack[2]
|
2019-08-22 00:32:49 +02:00
|
|
|
else:
|
2019-08-22 02:11:02 +02:00
|
|
|
self.vertical_layer = layer_stack[2]
|
|
|
|
|
self.horizontal_layer = layer_stack[0]
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
layer_stuff = self.get_layer_pitch(self.vertical_layer)
|
|
|
|
|
(self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
|
|
|
|
|
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
|
|
|
|
|
(self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
# FIXME: Must extend this to a horizontal conflict graph
|
|
|
|
|
# too if we want to minimize the
|
2018-07-25 20:13:30 +02:00
|
|
|
# number of tracks!
|
2019-12-23 22:16:08 +01:00
|
|
|
# hcg = {}
|
2018-07-25 20:37:06 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
# Initialize the vertical conflict graph (vcg)
|
|
|
|
|
# and make a list of all pins
|
2019-08-30 01:06:34 +02:00
|
|
|
vcg = collections.OrderedDict()
|
2018-09-11 22:28:28 +02:00
|
|
|
|
|
|
|
|
# Create names for the nets for the graphs
|
2019-08-30 01:06:34 +02:00
|
|
|
nets = collections.OrderedDict()
|
2018-09-11 22:28:28 +02:00
|
|
|
index = 0
|
2019-12-23 22:16:08 +01:00
|
|
|
# print(netlist)
|
2018-09-12 00:53:12 +02:00
|
|
|
for pin_list in netlist:
|
2019-12-23 22:16:08 +01:00
|
|
|
net_name = "n{}".format(index)
|
|
|
|
|
index += 1
|
|
|
|
|
nets[net_name] = pin_list
|
2018-09-11 22:28:28 +02:00
|
|
|
|
2018-07-25 20:13:30 +02:00
|
|
|
# Find the vertical pin conflicts
|
2018-07-26 00:36:16 +02:00
|
|
|
# FIXME: O(n^2) but who cares for now
|
2018-09-11 22:28:28 +02:00
|
|
|
for net_name1 in nets:
|
2018-11-11 21:02:42 +01:00
|
|
|
if net_name1 not in vcg.keys():
|
2019-12-23 22:16:08 +01:00
|
|
|
vcg[net_name1] = []
|
2018-09-11 22:28:28 +02:00
|
|
|
for net_name2 in nets:
|
2018-11-11 21:02:42 +01:00
|
|
|
if net_name2 not in vcg.keys():
|
2019-12-23 22:16:08 +01:00
|
|
|
vcg[net_name2] = []
|
2018-09-11 22:28:28 +02:00
|
|
|
# Skip yourself
|
|
|
|
|
if net_name1 == net_name2:
|
|
|
|
|
continue
|
2019-12-23 22:16:08 +01:00
|
|
|
if vertical and vcg_nets_overlap(nets[net_name1],
|
|
|
|
|
nets[net_name2],
|
|
|
|
|
vertical,
|
|
|
|
|
self.vertical_pitch):
|
2019-08-22 02:11:02 +02:00
|
|
|
vcg[net_name2].append(net_name1)
|
2019-12-23 22:16:08 +01:00
|
|
|
elif not vertical and vcg_nets_overlap(nets[net_name1],
|
|
|
|
|
nets[net_name2],
|
|
|
|
|
vertical,
|
|
|
|
|
self.horizontal_pitch):
|
2018-11-11 21:02:42 +01:00
|
|
|
vcg[net_name2].append(net_name1)
|
2018-08-15 23:19:04 +02:00
|
|
|
|
2018-07-25 20:13:30 +02:00
|
|
|
# list of routes to do
|
|
|
|
|
while vcg:
|
2019-12-23 22:16:08 +01:00
|
|
|
# from pprint import pformat
|
|
|
|
|
# print("VCG:\n",pformat(vcg))
|
2018-07-25 20:13:30 +02:00
|
|
|
# get a route from conflict graph with empty fanout set
|
2019-12-23 22:16:08 +01:00
|
|
|
net_name = None
|
|
|
|
|
for net_name, conflicts in vcg.items():
|
|
|
|
|
if len(conflicts) == 0:
|
|
|
|
|
vcg = remove_net_from_graph(net_name, vcg)
|
2018-07-25 20:13:30 +02:00
|
|
|
break
|
2018-09-11 22:28:28 +02:00
|
|
|
else:
|
|
|
|
|
# FIXME: We don't support cyclic VCGs right now.
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.error("Cyclic VCG in channel router.", -1)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2018-09-11 22:28:28 +02:00
|
|
|
# These are the pins we'll have to connect
|
|
|
|
|
pin_list = nets[net_name]
|
2019-12-23 22:16:08 +01:00
|
|
|
# print("Routing:", net_name, [x.name for x in pin_list])
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2018-09-11 22:28:28 +02:00
|
|
|
# Remove the net from other constriants in the VCG
|
2019-12-23 22:16:08 +01:00
|
|
|
vcg = remove_net_from_graph(net_name, vcg)
|
2018-09-11 22:28:28 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
# Add the trunk routes from the bottom up for
|
|
|
|
|
# horizontal or the left to right for vertical
|
2018-07-25 20:13:30 +02:00
|
|
|
if vertical:
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_vertical_trunk_route(pin_list,
|
|
|
|
|
offset,
|
|
|
|
|
layer_stack,
|
|
|
|
|
self.vertical_pitch)
|
|
|
|
|
offset += vector(self.vertical_pitch, 0)
|
2018-07-25 20:13:30 +02:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
self.add_horizontal_trunk_route(pin_list,
|
|
|
|
|
offset,
|
|
|
|
|
layer_stack,
|
|
|
|
|
self.horizontal_pitch)
|
|
|
|
|
offset += vector(0, self.horizontal_pitch)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2019-12-13 23:13:41 +01:00
|
|
|
def create_vertical_channel_route(self, netlist, offset, layer_stack):
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
|
|
|
|
Wrapper to create a vertical channel route
|
|
|
|
|
"""
|
2019-08-22 00:32:49 +02:00
|
|
|
self.create_channel_route(netlist, offset, layer_stack, vertical=True)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
2019-12-13 23:13:41 +01:00
|
|
|
def create_horizontal_channel_route(self, netlist, offset, layer_stack):
|
2018-07-25 20:13:30 +02:00
|
|
|
"""
|
|
|
|
|
Wrapper to create a horizontal channel route
|
|
|
|
|
"""
|
2019-08-22 00:32:49 +02:00
|
|
|
self.create_channel_route(netlist, offset, layer_stack, vertical=False)
|
2019-05-28 01:32:38 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
def add_boundary(self, ll=vector(0, 0), ur=None):
|
2019-05-28 01:32:38 +02:00
|
|
|
""" Add boundary for debugging dimensions """
|
2019-12-05 01:12:53 +01:00
|
|
|
if "stdc" in techlayer.keys():
|
|
|
|
|
boundary_layer = "stdc"
|
|
|
|
|
else:
|
|
|
|
|
boundary_layer = "boundary"
|
2020-01-16 20:27:39 +01:00
|
|
|
if not ur:
|
2019-12-06 00:14:25 +01:00
|
|
|
self.boundary = self.add_rect(layer=boundary_layer,
|
|
|
|
|
offset=ll,
|
|
|
|
|
height=self.height,
|
|
|
|
|
width=self.width)
|
|
|
|
|
else:
|
|
|
|
|
self.boundary = self.add_rect(layer=boundary_layer,
|
|
|
|
|
offset=ll,
|
|
|
|
|
height=ur.y-ll.y,
|
|
|
|
|
width=ur.x-ll.x)
|
2018-07-25 20:13:30 +02:00
|
|
|
|
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."""
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
xmin = insts[0].lx()
|
|
|
|
|
ymin = insts[0].by()
|
|
|
|
|
xmax = insts[0].rx()
|
|
|
|
|
ymax = insts[0].uy()
|
2018-01-31 23:31:50 +01:00
|
|
|
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,
|
2019-12-23 22:16:08 +01:00
|
|
|
offset=vector(xmin, ymin),
|
2018-01-31 23:31:50 +01:00
|
|
|
width=xmax-xmin,
|
|
|
|
|
height=ymax-ymin)
|
|
|
|
|
|
2018-10-08 18:56:39 +02:00
|
|
|
def copy_power_pins(self, inst, name):
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
This will copy a power pin if it is on M3.
|
|
|
|
|
If it is on M1, it will add a power via too.
|
2018-10-08 18:56:39 +02:00
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
pins = inst.get_pins(name)
|
2018-10-08 18:56:39 +02:00
|
|
|
for pin in pins:
|
2019-12-23 22:16:08 +01:00
|
|
|
if pin.layer == "m3":
|
|
|
|
|
self.add_layout_pin(name,
|
|
|
|
|
pin.layer,
|
|
|
|
|
pin.ll(),
|
|
|
|
|
pin.width(),
|
|
|
|
|
pin.height())
|
|
|
|
|
elif pin.layer == "m1":
|
2018-10-08 18:56:39 +02:00
|
|
|
self.add_power_pin(name, pin.center())
|
|
|
|
|
else:
|
|
|
|
|
debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"):
|
|
|
|
|
"""
|
2018-11-30 21:32:13 +01:00
|
|
|
Add a single power pin from M3 down to M1 at the given center location.
|
|
|
|
|
The starting layer is specified to determine which vias are needed.
|
2018-07-16 19:19:52 +02:00
|
|
|
"""
|
2019-04-01 23:23:47 +02:00
|
|
|
if vertical:
|
2019-12-23 22:16:08 +01:00
|
|
|
direction = ("V", "V")
|
2019-04-01 23:23:47 +02:00
|
|
|
else:
|
2019-12-23 22:16:08 +01:00
|
|
|
direction = ("H", "H")
|
2019-04-01 23:23:47 +02:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if start_layer == "m1":
|
2019-12-13 23:13:41 +01:00
|
|
|
self.add_via_center(layers=self.m1_stack,
|
2019-12-18 11:33:21 +01:00
|
|
|
size=size,
|
2018-11-30 03:47:38 +01:00
|
|
|
offset=loc,
|
2019-04-01 23:23:47 +02:00
|
|
|
directions=direction)
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
if start_layer == "m1" or start_layer == "m2":
|
|
|
|
|
via = self.add_via_center(layers=self.m2_stack,
|
|
|
|
|
size=size,
|
|
|
|
|
offset=loc,
|
|
|
|
|
directions=direction)
|
|
|
|
|
if start_layer == "m3":
|
2018-11-30 21:32:13 +01:00
|
|
|
self.add_layout_pin_rect_center(text=name,
|
2019-12-17 20:03:36 +01:00
|
|
|
layer="m3",
|
2018-11-30 21:32:13 +01:00
|
|
|
offset=loc)
|
|
|
|
|
else:
|
|
|
|
|
self.add_layout_pin_rect_center(text=name,
|
2019-12-17 20:03:36 +01:00
|
|
|
layer="m3",
|
2018-11-30 21:32:13 +01:00
|
|
|
offset=loc,
|
|
|
|
|
width=via.width,
|
|
|
|
|
height=via.height)
|
2018-07-16 19:19:52 +02:00
|
|
|
|
2018-03-12 21:14:53 +01:00
|
|
|
def add_power_ring(self, bbox):
|
|
|
|
|
"""
|
2019-12-23 22:16:08 +01:00
|
|
|
Create vdd and gnd power rings around an area of the bounding box
|
|
|
|
|
argument. Must have a supply_rail_width and supply_rail_pitch
|
|
|
|
|
defined as a member variable. Defines local variables of the
|
|
|
|
|
left/right/top/bottom vdd/gnd center offsets for use in other
|
|
|
|
|
modules..
|
2018-03-12 21:14:53 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
[ll, ur] = bbox
|
|
|
|
|
|
|
|
|
|
supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
|
|
|
|
|
height = (ur.y-ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
|
|
|
|
width = (ur.x-ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
|
|
|
|
|
|
|
|
|
# LEFT vertical rails
|
2019-12-23 22:16:08 +01:00
|
|
|
offset = ll + vector(-2 * self.supply_rail_pitch,
|
|
|
|
|
-2 * self.supply_rail_pitch)
|
|
|
|
|
left_gnd_pin = self.add_layout_pin(text="gnd",
|
|
|
|
|
layer="m2",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=self.supply_rail_width,
|
|
|
|
|
height=height)
|
|
|
|
|
|
|
|
|
|
offset = ll + vector(-1 * self.supply_rail_pitch,
|
|
|
|
|
-1 * self.supply_rail_pitch)
|
|
|
|
|
left_vdd_pin = self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="m2",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=self.supply_rail_width,
|
|
|
|
|
height=height)
|
|
|
|
|
|
|
|
|
|
# RIGHT vertical rails
|
|
|
|
|
offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch)
|
2018-03-12 21:14:53 +01:00
|
|
|
right_gnd_pin = self.add_layout_pin(text="gnd",
|
2019-12-23 22:16:08 +01:00
|
|
|
layer="m2",
|
2018-03-12 21:14:53 +01:00
|
|
|
offset=offset,
|
|
|
|
|
width=self.supply_rail_width,
|
|
|
|
|
height=height)
|
|
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch,
|
|
|
|
|
-1 * self.supply_rail_pitch)
|
|
|
|
|
right_vdd_pin = self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="m2",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=self.supply_rail_width,
|
|
|
|
|
height=height)
|
2018-03-12 21:14:53 +01:00
|
|
|
|
|
|
|
|
# BOTTOM horizontal rails
|
2019-12-23 22:16:08 +01:00
|
|
|
offset = ll + vector(-2 * self.supply_rail_pitch,
|
|
|
|
|
-2 * self.supply_rail_pitch)
|
|
|
|
|
bottom_gnd_pin = self.add_layout_pin(text="gnd",
|
|
|
|
|
layer="m1",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=self.supply_rail_width)
|
|
|
|
|
|
|
|
|
|
offset = ll + vector(-1 * self.supply_rail_pitch,
|
|
|
|
|
-1 * self.supply_rail_pitch)
|
|
|
|
|
bottom_vdd_pin = self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="m1",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=self.supply_rail_width)
|
2018-03-12 21:14:53 +01:00
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
# TOP horizontal rails
|
|
|
|
|
offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch,
|
|
|
|
|
0)
|
|
|
|
|
top_gnd_pin = self.add_layout_pin(text="gnd",
|
|
|
|
|
layer="m1",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=self.supply_rail_width)
|
|
|
|
|
|
|
|
|
|
offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch,
|
|
|
|
|
self.supply_rail_pitch)
|
|
|
|
|
top_vdd_pin = self.add_layout_pin(text="vdd",
|
|
|
|
|
layer="m1",
|
|
|
|
|
offset=offset,
|
|
|
|
|
width=width,
|
|
|
|
|
height=self.supply_rail_width)
|
2018-03-12 21:14:53 +01:00
|
|
|
|
|
|
|
|
# Remember these for connecting things in the design
|
|
|
|
|
self.left_gnd_x_center = left_gnd_pin.cx()
|
|
|
|
|
self.left_vdd_x_center = left_vdd_pin.cx()
|
|
|
|
|
self.right_gnd_x_center = right_gnd_pin.cx()
|
|
|
|
|
self.right_vdd_x_center = right_vdd_pin.cx()
|
|
|
|
|
|
|
|
|
|
self.bottom_gnd_y_center = bottom_gnd_pin.cy()
|
|
|
|
|
self.bottom_vdd_y_center = bottom_vdd_pin.cy()
|
|
|
|
|
self.top_gnd_y_center = top_gnd_pin.cy()
|
|
|
|
|
self.top_vdd_y_center = top_vdd_pin.cy()
|
|
|
|
|
|
2018-03-19 23:26:50 +01:00
|
|
|
# Find the number of vias for this pitch
|
|
|
|
|
self.supply_vias = 1
|
2019-01-17 01:56:06 +01:00
|
|
|
from sram_factory import factory
|
2018-03-19 23:26:50 +01:00
|
|
|
while True:
|
2019-12-23 22:16:08 +01:00
|
|
|
c = factory.create(module_type="contact",
|
|
|
|
|
layer_stack=self.m1_stack,
|
|
|
|
|
dimensions=(self.supply_vias, self.supply_vias))
|
2018-03-19 23:26:50 +01:00
|
|
|
if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width:
|
|
|
|
|
self.supply_vias += 1
|
|
|
|
|
else:
|
2018-03-19 23:57:26 +01:00
|
|
|
self.supply_vias -= 1
|
2018-03-19 23:26:50 +01:00
|
|
|
break
|
|
|
|
|
|
2018-03-12 21:14:53 +01:00
|
|
|
via_points = [vector(self.left_gnd_x_center, self.bottom_gnd_y_center),
|
|
|
|
|
vector(self.left_gnd_x_center, self.top_gnd_y_center),
|
|
|
|
|
vector(self.right_gnd_x_center, self.bottom_gnd_y_center),
|
|
|
|
|
vector(self.right_gnd_x_center, self.top_gnd_y_center),
|
|
|
|
|
vector(self.left_vdd_x_center, self.bottom_vdd_y_center),
|
|
|
|
|
vector(self.left_vdd_x_center, self.top_vdd_y_center),
|
|
|
|
|
vector(self.right_vdd_x_center, self.bottom_vdd_y_center),
|
|
|
|
|
vector(self.right_vdd_x_center, self.top_vdd_y_center)]
|
|
|
|
|
|
|
|
|
|
for pt in via_points:
|
2019-12-13 23:13:41 +01:00
|
|
|
self.add_via_center(layers=self.m1_stack,
|
2018-03-12 21:14:53 +01:00
|
|
|
offset=pt,
|
2019-12-23 22:16:08 +01:00
|
|
|
size=(self.supply_vias,
|
|
|
|
|
self.supply_vias))
|
2018-03-12 21:14:53 +01:00
|
|
|
|
2016-11-08 18:57:35 +01:00
|
|
|
def pdf_write(self, pdf_name):
|
2019-12-23 22:16:08 +01:00
|
|
|
"""
|
|
|
|
|
Display the layout to a PDF file.
|
|
|
|
|
"""
|
|
|
|
|
debug.error("NOTE: Currently does not work (Needs further research)")
|
|
|
|
|
# self.pdf_name = self.name + ".pdf"
|
2018-10-04 23:04:29 +02:00
|
|
|
debug.info(0, "Writing to {}".format(pdf_name))
|
2016-11-08 18:57:35 +01:00
|
|
|
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"""
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2016-11-08 18:57:35 +01:00
|
|
|
"|==============================================================================|")
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2017-12-19 18:01:24 +01:00
|
|
|
"|========= LIST OF OBJECTS (Rects) FOR: " + self.name)
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2016-11-08 18:57:35 +01:00
|
|
|
"|==============================================================================|")
|
|
|
|
|
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
|
|
|
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2016-11-08 18:57:35 +01:00
|
|
|
"|==============================================================================|")
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2017-12-19 18:01:24 +01:00
|
|
|
"|========= LIST OF INSTANCES FOR: " + self.name)
|
2019-12-23 22:16:08 +01:00
|
|
|
debug.info(0,
|
2016-11-08 18:57:35 +01:00
|
|
|
"|==============================================================================|")
|
|
|
|
|
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))
|
|
|
|
|
|