mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-09-07 03:20:39 +02:00
fix merge conflicts
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
import collections
|
||||
import debug
|
||||
from tech import drc
|
||||
from vector import vector
|
||||
import design
|
||||
|
||||
|
||||
class channel_net():
|
||||
def __init__(self, net_name, pins, vertical):
|
||||
self.name = net_name
|
||||
self.pins = pins
|
||||
self.vertical = vertical
|
||||
|
||||
# Keep track of the internval
|
||||
if vertical:
|
||||
self.min_value = min(i.by() for i in pins)
|
||||
self.max_value = max(i.uy() for i in pins)
|
||||
else:
|
||||
self.min_value = min(i.lx() for i in pins)
|
||||
self.max_value = max(i.rx() for i in pins)
|
||||
|
||||
# Keep track of the conflicts
|
||||
self.conflicts = []
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.min_value < other.min_value
|
||||
|
||||
def vcg_pin_overlap(self, pin1, pin2, pitch):
|
||||
""" Check for vertical or horizontal overlap of the two pins """
|
||||
|
||||
# FIXME: If the pins are not in a row, this may break.
|
||||
# However, a top pin shouldn't overlap another top pin,
|
||||
# for example, so the extra comparison *shouldn't* matter.
|
||||
|
||||
# Pin 1 must be in the "BOTTOM" set
|
||||
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
|
||||
|
||||
# Pin 1 must be in the "LEFT" set
|
||||
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
|
||||
overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap)
|
||||
return overlaps
|
||||
|
||||
def vcg_nets_overlap(self, other, pitch):
|
||||
"""
|
||||
Check all the pin pairs on two nets and return a pin
|
||||
overlap if any pin overlaps.
|
||||
"""
|
||||
|
||||
for pin1 in self.pins:
|
||||
for pin2 in other.pins:
|
||||
if self.vcg_pin_overlap(pin1, pin2, pitch):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def hcg_nets_overlap(self, other):
|
||||
"""
|
||||
Check if the horizontal span of the two nets overlaps eachother.
|
||||
"""
|
||||
min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value
|
||||
max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value
|
||||
return min_overlap or max_overlap
|
||||
|
||||
|
||||
class channel_route(design.design):
|
||||
|
||||
unique_id = 0
|
||||
|
||||
def __init__(self,
|
||||
netlist,
|
||||
offset,
|
||||
layer_stack,
|
||||
directions=None,
|
||||
vertical=False):
|
||||
"""
|
||||
The net list is a list of the nets with each net being a list of pins
|
||||
to be connected. The offset is the lower-left of where the
|
||||
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. The track size must be the number of
|
||||
nets times the *nonpreferred* routing of the non-track layer pitch.
|
||||
|
||||
"""
|
||||
name = "cr_{0}".format(channel_route.unique_id)
|
||||
channel_route.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.netlist = netlist
|
||||
self.offset = offset
|
||||
self.layer_stack = layer_stack
|
||||
self.directions = directions
|
||||
self.vertical = vertical
|
||||
|
||||
if not directions or directions == "pref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
elif directions == "nonpref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
# Use the layer directions specified to the router rather than
|
||||
# the preferred directions
|
||||
debug.check(directions[0] != directions[1], "Must have unique layer directions.")
|
||||
if directions[0] == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
self.vertical_layer = layer_stack[2]
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.vertical_layer)
|
||||
(self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
|
||||
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
|
||||
|
||||
self.route()
|
||||
|
||||
def remove_net_from_graph(self, pin, g):
|
||||
"""
|
||||
Remove the pin from the graph and all conflicts
|
||||
"""
|
||||
g.pop(pin, None)
|
||||
|
||||
# Remove the pin from all conflicts
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin, conflicts in g.items():
|
||||
if pin in conflicts:
|
||||
g[other_pin].remove(pin)
|
||||
return g
|
||||
|
||||
def route(self):
|
||||
# Create names for the nets for the graphs
|
||||
nets = []
|
||||
index = 0
|
||||
# print(self.netlist)
|
||||
for pin_list in self.netlist:
|
||||
nets.append(channel_net("n{}".format(index), pin_list, self.vertical))
|
||||
index += 1
|
||||
|
||||
# Create the (undirected) horizontal constraint graph
|
||||
hcg = collections.OrderedDict()
|
||||
for net1 in nets:
|
||||
for net2 in nets:
|
||||
if net1.name == net2.name:
|
||||
continue
|
||||
if net1.hcg_nets_overlap(net2):
|
||||
try:
|
||||
hcg[net1.name].add(net2.name)
|
||||
except KeyError:
|
||||
hcg[net1.name] = set([net2.name])
|
||||
try:
|
||||
hcg[net2.name].add(net1.name)
|
||||
except KeyError:
|
||||
hcg[net2.name] = set([net1.name])
|
||||
|
||||
|
||||
# Initialize the vertical conflict graph (vcg)
|
||||
# and make a list of all pins
|
||||
vcg = collections.OrderedDict()
|
||||
|
||||
# print("Nets:")
|
||||
# for net_name in nets:
|
||||
# print(net_name, [x.name for x in nets[net_name]])
|
||||
|
||||
# Find the vertical pin conflicts
|
||||
# FIXME: O(n^2) but who cares for now
|
||||
if self.vertical:
|
||||
pitch = self.horizontal_nonpref_pitch
|
||||
else:
|
||||
pitch = self.vertical_nonpref_pitch
|
||||
|
||||
for net in nets:
|
||||
vcg[net.name] = set()
|
||||
|
||||
for net1 in nets:
|
||||
for net2 in nets:
|
||||
# Skip yourself
|
||||
if net1.name == net2.name:
|
||||
continue
|
||||
|
||||
if net1.vcg_nets_overlap(net2, pitch):
|
||||
vcg[net2.name].add(net1.name)
|
||||
|
||||
# Check if there are any cycles net1 <---> net2 in the VCG
|
||||
|
||||
|
||||
# Some of the pins may be to the left/below the channel offset,
|
||||
# so adjust if this is the case
|
||||
min_value = min([n.min_value for n in nets])
|
||||
if self.vertical:
|
||||
real_channel_offset = vector(self.offset.x, min_value)
|
||||
else:
|
||||
real_channel_offset = vector(min_value, self.offset.y)
|
||||
current_offset = real_channel_offset
|
||||
|
||||
# Sort nets by left edge value
|
||||
nets.sort()
|
||||
while len(nets) > 0:
|
||||
|
||||
current_offset_value = current_offset.y if self.vertical else current_offset.x
|
||||
|
||||
# from pprint import pformat
|
||||
# print("VCG:\n", pformat(vcg))
|
||||
# for name,net in vcg.items():
|
||||
# print(name, net.min_value, net.max_value, net.conflicts)
|
||||
# print(current_offset)
|
||||
# get a route from conflict graph with empty fanout set
|
||||
for net in nets:
|
||||
# If it has no conflicts and the interval is to the right of the current offset in the track
|
||||
if net.min_value >= current_offset_value and len(vcg[net.name]) == 0:
|
||||
# print("Routing {}".format(net.name))
|
||||
# Add the trunk routes from the bottom up for
|
||||
# horizontal or the left to right for vertical
|
||||
if self.vertical:
|
||||
self.add_vertical_trunk_route(net.pins,
|
||||
current_offset,
|
||||
self.vertical_nonpref_pitch)
|
||||
current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch)
|
||||
else:
|
||||
self.add_horizontal_trunk_route(net.pins,
|
||||
current_offset,
|
||||
self.horizontal_nonpref_pitch)
|
||||
current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y)
|
||||
|
||||
# Remove the net from other constriants in the VCG
|
||||
vcg = self.remove_net_from_graph(net.name, vcg)
|
||||
nets.remove(net)
|
||||
|
||||
break
|
||||
else:
|
||||
# If we made a full pass and the offset didn't change...
|
||||
current_offset_value = current_offset.y if self.vertical else current_offset.x
|
||||
initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x
|
||||
if current_offset_value == initial_offset_value:
|
||||
# FIXME: We don't support cyclic VCGs right now.
|
||||
debug.error("Cyclic VCG in channel router.", -1)
|
||||
|
||||
# Increment the track and reset the offset to the start (like a typewriter)
|
||||
if self.vertical:
|
||||
current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y)
|
||||
else:
|
||||
current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch)
|
||||
|
||||
# Return the size of the channel
|
||||
if self.vertical:
|
||||
self.width = 0
|
||||
self.height = current_offset.y
|
||||
return current_offset.y + self.vertical_nonpref_pitch - self.offset.y
|
||||
else:
|
||||
self.width = current_offset.x
|
||||
self.height = 0
|
||||
return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
|
||||
|
||||
def get_layer_pitch(self, layer):
|
||||
""" Return the track pitch on a given layer """
|
||||
try:
|
||||
# FIXME: Using non-pref pitch here due to overlap bug in VCG constraints.
|
||||
# It should just result in inefficient channel width but will work.
|
||||
pitch = getattr(self, "{}_pitch".format(layer))
|
||||
nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer))
|
||||
space = getattr(self, "{}_space".format(layer))
|
||||
except AttributeError:
|
||||
debug.error("Cannot find layer pitch.", -1)
|
||||
return (nonpref_pitch, pitch, pitch - space, space)
|
||||
|
||||
def add_horizontal_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with
|
||||
the trunk located at the given y offset.
|
||||
"""
|
||||
max_x = max([pin.center().x for pin in pins])
|
||||
min_x = min([pin.center().x for pin in pins])
|
||||
|
||||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
non_preferred_route = max_x - min_x <= pitch
|
||||
|
||||
if non_preferred_route:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
||||
# Add the horizontal trunk on the vertical layer!
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(min_x - half_layer_width, trunk_offset.y),
|
||||
vector(max_x + half_layer_width, trunk_offset.y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
if pin.cy() < trunk_offset.y:
|
||||
pin_pos = pin.uc()
|
||||
else:
|
||||
pin_pos = pin.bc()
|
||||
|
||||
# No bend needed here
|
||||
mid = vector(pin_pos.x, trunk_offset.y)
|
||||
self.add_path(self.vertical_layer, [pin_pos, mid])
|
||||
else:
|
||||
# Add the horizontal trunk
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(min_x, trunk_offset.y),
|
||||
vector(max_x, trunk_offset.y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Find the correct side of the pin
|
||||
if pin.cy() < trunk_offset.y:
|
||||
pin_pos = pin.uc()
|
||||
else:
|
||||
pin_pos = pin.bc()
|
||||
mid = vector(pin_pos.x, trunk_offset.y)
|
||||
self.add_path(self.vertical_layer, [pin_pos, mid])
|
||||
if not non_preferred_route:
|
||||
self.add_via_center(layers=self.layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.vertical_layer,
|
||||
offset=pin_pos)
|
||||
|
||||
def add_vertical_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with the
|
||||
trunk located at the given x offset.
|
||||
"""
|
||||
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
|
||||
non_preferred_route = max_y - min_y <= pitch
|
||||
|
||||
if non_preferred_route:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
||||
# Add the vertical trunk on the horizontal layer!
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(trunk_offset.x, min_y - half_layer_width),
|
||||
vector(trunk_offset.x, max_y + half_layer_width)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Find the correct side of the pin
|
||||
if pin.cx() < trunk_offset.x:
|
||||
pin_pos = pin.rc()
|
||||
else:
|
||||
pin_pos = pin.lc()
|
||||
# No bend needed here
|
||||
mid = vector(trunk_offset.x, pin_pos.y)
|
||||
self.add_path(self.horizontal_layer, [pin_pos, mid])
|
||||
else:
|
||||
# Add the vertical trunk
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(trunk_offset.x, min_y),
|
||||
vector(trunk_offset.x, max_y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Find the correct side of the pin
|
||||
if pin.cx() < trunk_offset.x:
|
||||
pin_pos = pin.rc()
|
||||
else:
|
||||
pin_pos = pin.lc()
|
||||
mid = vector(trunk_offset.x, pin_pos.y)
|
||||
self.add_path(self.horizontal_layer, [pin_pos, mid])
|
||||
if not non_preferred_route:
|
||||
self.add_via_center(layers=self.layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.horizontal_layer,
|
||||
offset=pin_pos)
|
||||
|
||||
|
||||
+164
-82
@@ -8,7 +8,10 @@
|
||||
import hierarchy_design
|
||||
import debug
|
||||
from tech import drc, layer
|
||||
import tech
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
import sys
|
||||
|
||||
|
||||
class contact(hierarchy_design.hierarchy_design):
|
||||
@@ -26,22 +29,42 @@ class contact(hierarchy_design.hierarchy_design):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"),
|
||||
def __init__(self, layer_stack, dimensions=(1, 1), directions=None,
|
||||
implant_type=None, well_type=None, name=""):
|
||||
# This will ignore the name parameter since
|
||||
# we can guarantee a unique name here
|
||||
|
||||
hierarchy_design.hierarchy_design.__init__(self, name)
|
||||
debug.info(4, "create contact object {0}".format(name))
|
||||
|
||||
self.add_comment("layers: {0}".format(layer_stack))
|
||||
self.add_comment("dimensions: {0}".format(dimensions))
|
||||
if implant_type or well_type:
|
||||
self.add_comment("implant type: {}\n".format(implant_type))
|
||||
self.add_comment("well_type: {}\n".format(well_type))
|
||||
|
||||
|
||||
self.is_well_contact = implant_type == well_type
|
||||
|
||||
# If we have a special tap layer, use it
|
||||
self.layer_stack = layer_stack
|
||||
self.dimensions = dimensions
|
||||
self.directions = directions
|
||||
|
||||
# Non-preferred directions
|
||||
if directions == "nonpref":
|
||||
first_dir = "H" if self.get_preferred_direction(layer_stack[0])=="V" else "V"
|
||||
second_dir = "H" if self.get_preferred_direction(layer_stack[2])=="V" else "V"
|
||||
self.directions = (first_dir, second_dir)
|
||||
# Preferred directions
|
||||
elif directions == "pref":
|
||||
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||
tech.preferred_directions[layer_stack[2]])
|
||||
# User directions
|
||||
elif directions:
|
||||
self.directions = directions
|
||||
# Preferred directions
|
||||
else:
|
||||
self.directions = (tech.preferred_directions[layer_stack[0]],
|
||||
tech.preferred_directions[layer_stack[2]])
|
||||
self.offset = vector(0, 0)
|
||||
self.implant_type = implant_type
|
||||
self.well_type = well_type
|
||||
@@ -56,30 +79,39 @@ class contact(hierarchy_design.hierarchy_design):
|
||||
self.create_contact_array()
|
||||
self.create_first_layer_enclosure()
|
||||
self.create_second_layer_enclosure()
|
||||
self.create_nitride_cut_enclosure()
|
||||
|
||||
self.height = max(obj.offset.y + obj.height for obj in self.objs)
|
||||
self.width = max(obj.offset.x + obj.width for obj in self.objs)
|
||||
self.height = max(self.first_layer_position.y + self.first_layer_height,
|
||||
self.second_layer_position.y + self.second_layer_height)
|
||||
self.width = max(self.first_layer_position.x + self.first_layer_width,
|
||||
self.second_layer_position.x + self.second_layer_width)
|
||||
|
||||
# Do not include the select layer in the height/width
|
||||
if self.implant_type and self.well_type:
|
||||
self.create_implant_well_enclosures()
|
||||
elif self.implant_type or self.well_type:
|
||||
debug.error(-1, "Must define both implant and well type or none at all.")
|
||||
debug.error(-1,
|
||||
"Must define both implant and well type or none.")
|
||||
|
||||
def setup_layers(self):
|
||||
""" Locally assign the layer names. """
|
||||
|
||||
(first_layer, via_layer, second_layer) = self.layer_stack
|
||||
self.first_layer_name = first_layer
|
||||
self.via_layer_name = via_layer
|
||||
# Some technologies have a separate active
|
||||
# contact from the poly contact
|
||||
# We will use contact for DRC, but active_contact for output
|
||||
if first_layer == "active" or second_layer == "active":
|
||||
self.via_layer_name_expanded = "active_" + via_layer
|
||||
else:
|
||||
self.via_layer_name_expanded = via_layer
|
||||
self.second_layer_name = second_layer
|
||||
|
||||
# Contacts will have unique per first layer
|
||||
if via_layer in tech.layer:
|
||||
self.via_layer_name = via_layer
|
||||
elif via_layer == "contact":
|
||||
if first_layer in ("active", "poly"):
|
||||
self.via_layer_name = first_layer + "_" + via_layer
|
||||
elif second_layer in ("active", "poly"):
|
||||
self.via_layer_name = second_layer + "_" + via_layer
|
||||
else:
|
||||
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||
else:
|
||||
debug.error("Invalid via layer {}".format(via_layer), -1)
|
||||
|
||||
def setup_layout_constants(self):
|
||||
""" Determine the design rules for the enclosure layers """
|
||||
@@ -95,70 +127,105 @@ class contact(hierarchy_design.hierarchy_design):
|
||||
# The extend rule applies to asymmetric enclosures in one direction.
|
||||
# The enclosure rule applies to symmetric enclosure component.
|
||||
|
||||
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||
first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
self.first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
|
||||
self.first_layer_enclosure = drc("{0}_enclose_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
# If there's a different rule for active
|
||||
# FIXME: Make this more elegant
|
||||
if self.is_well_contact and self.first_layer_name == "active" and "tap_extend_contact" in drc.keys():
|
||||
self.first_layer_extend = drc("tap_extend_contact")
|
||||
else:
|
||||
self.first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
|
||||
|
||||
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||
second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
self.second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
|
||||
self.second_layer_enclosure = drc("{0}_enclose_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
self.second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
|
||||
|
||||
# In some technologies, the minimum width may be larger
|
||||
# than the overlap requirement around the via, so
|
||||
# check this for each dimension.
|
||||
if self.directions[0] == "V":
|
||||
self.first_layer_horizontal_enclosure = max(first_layer_enclosure,
|
||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(first_layer_extend,
|
||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.first_layer_horizontal_enclosure = max(self.first_layer_enclosure,
|
||||
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(self.first_layer_extend,
|
||||
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||
elif self.directions[0] == "H":
|
||||
self.first_layer_horizontal_enclosure = max(first_layer_extend,
|
||||
(first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(first_layer_enclosure,
|
||||
(first_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.first_layer_horizontal_enclosure = max(self.first_layer_extend,
|
||||
(self.first_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.first_layer_vertical_enclosure = max(self.first_layer_enclosure,
|
||||
(self.first_layer_minwidth - self.contact_array_height) / 2)
|
||||
else:
|
||||
debug.error("Invalid first layer direction.", -1)
|
||||
debug.error("Invalid first layer direction: ".format(self.directions[0]), -1)
|
||||
|
||||
# In some technologies, the minimum width may be larger than the overlap requirement around the via, so
|
||||
# In some technologies, the minimum width may be larger
|
||||
# than the overlap requirement around the via, so
|
||||
# check this for each dimension.
|
||||
if self.directions[1] == "V":
|
||||
self.second_layer_horizontal_enclosure = max(second_layer_enclosure,
|
||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_vertical_enclosure = max(second_layer_extend,
|
||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_horizontal_enclosure = max(self.second_layer_enclosure,
|
||||
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_vertical_enclosure = max(self.second_layer_extend,
|
||||
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||
elif self.directions[1] == "H":
|
||||
self.second_layer_horizontal_enclosure = max(second_layer_extend,
|
||||
(second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_vertical_enclosure = max(second_layer_enclosure,
|
||||
(second_layer_minwidth - self.contact_array_width) / 2)
|
||||
self.second_layer_horizontal_enclosure = max(self.second_layer_extend,
|
||||
(self.second_layer_minwidth - self.contact_array_height) / 2)
|
||||
self.second_layer_vertical_enclosure = max(self.second_layer_enclosure,
|
||||
(self.second_layer_minwidth - self.contact_array_width) / 2)
|
||||
else:
|
||||
debug.error("Invalid second layer direction.", -1)
|
||||
debug.error("Invalid secon layer direction: ".format(self.directions[1]), -1)
|
||||
|
||||
def create_contact_array(self):
|
||||
""" Create the contact array at the origin"""
|
||||
# offset for the via array
|
||||
self.via_layer_position = vector(
|
||||
max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure),
|
||||
max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure))
|
||||
max(self.first_layer_horizontal_enclosure,
|
||||
self.second_layer_horizontal_enclosure),
|
||||
max(self.first_layer_vertical_enclosure,
|
||||
self.second_layer_vertical_enclosure))
|
||||
|
||||
for i in range(self.dimensions[1]):
|
||||
offset = self.via_layer_position + vector(0, self.contact_pitch * i)
|
||||
offset = self.via_layer_position + vector(0,
|
||||
self.contact_pitch * i)
|
||||
for j in range(self.dimensions[0]):
|
||||
self.add_rect(layer=self.via_layer_name_expanded,
|
||||
self.add_rect(layer=self.via_layer_name,
|
||||
offset=offset,
|
||||
width=self.contact_width,
|
||||
height=self.contact_width)
|
||||
offset = offset + vector(self.contact_pitch, 0)
|
||||
|
||||
def create_nitride_cut_enclosure(self):
|
||||
""" Special layer that encloses poly contacts in some processes """
|
||||
# Check if there is a special poly nitride cut layer
|
||||
if "npc" not in tech.layer:
|
||||
return
|
||||
|
||||
npc_enclose_poly = drc("npc_enclose_poly")
|
||||
npc_enclose_offset = vector(npc_enclose_poly, npc_enclose_poly)
|
||||
# Only add for poly layers
|
||||
if self.first_layer_name == "poly":
|
||||
self.add_rect(layer="npc",
|
||||
offset=self.first_layer_position - npc_enclose_offset,
|
||||
width=self.first_layer_width + 2 * npc_enclose_poly,
|
||||
height=self.first_layer_height + 2 * npc_enclose_poly)
|
||||
elif self.second_layer_name == "poly":
|
||||
self.add_rect(layer="npc",
|
||||
offset=self.second_layer_position - npc_enclose_offset,
|
||||
width=self.second_layer_width + 2 * npc_enclose_poly,
|
||||
height=self.second_layer_height + 2 * npc_enclose_poly)
|
||||
|
||||
def create_first_layer_enclosure(self):
|
||||
# this is if the first and second layers are different
|
||||
self.first_layer_position = vector(
|
||||
max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0),
|
||||
max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0))
|
||||
|
||||
self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure
|
||||
self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure
|
||||
self.add_rect(layer=self.first_layer_name,
|
||||
self.first_layer_width = max(self.contact_array_width + 2 * self.first_layer_horizontal_enclosure,
|
||||
self.first_layer_minwidth)
|
||||
self.first_layer_height = max(self.contact_array_height + 2 * self.first_layer_vertical_enclosure,
|
||||
self.first_layer_minwidth)
|
||||
if self.is_well_contact and self.first_layer_name == "active" and "tap" in layer:
|
||||
first_layer_name = "tap"
|
||||
else:
|
||||
first_layer_name = self.first_layer_name
|
||||
self.add_rect(layer=first_layer_name,
|
||||
offset=self.first_layer_position,
|
||||
width=self.first_layer_width,
|
||||
height=self.first_layer_height)
|
||||
@@ -169,57 +236,72 @@ class contact(hierarchy_design.hierarchy_design):
|
||||
max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0),
|
||||
max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0))
|
||||
|
||||
self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure
|
||||
self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure
|
||||
self.second_layer_width = max(self.contact_array_width + 2 * self.second_layer_horizontal_enclosure,
|
||||
self.second_layer_minwidth)
|
||||
self.second_layer_height = max(self.contact_array_height + 2 * self.second_layer_vertical_enclosure,
|
||||
self.second_layer_minwidth)
|
||||
self.add_rect(layer=self.second_layer_name,
|
||||
offset=self.second_layer_position,
|
||||
width=self.second_layer_width,
|
||||
height=self.second_layer_height)
|
||||
|
||||
def create_implant_well_enclosures(self):
|
||||
implant_position = self.first_layer_position - [drc("implant_enclosure_active")] * 2
|
||||
implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active")
|
||||
implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active")
|
||||
implant_position = self.first_layer_position - [drc("implant_enclose_active")] * 2
|
||||
implant_width = self.first_layer_width + 2 * drc("implant_enclose_active")
|
||||
implant_height = self.first_layer_height + 2 * drc("implant_enclose_active")
|
||||
self.add_rect(layer="{}implant".format(self.implant_type),
|
||||
offset=implant_position,
|
||||
width=implant_width,
|
||||
height=implant_height)
|
||||
well_position = self.first_layer_position - [drc("well_enclosure_active")] * 2
|
||||
well_width = self.first_layer_width + 2 * drc("well_enclosure_active")
|
||||
well_height = self.first_layer_height + 2 * drc("well_enclosure_active")
|
||||
self.add_rect(layer="{}well".format(self.well_type),
|
||||
offset=well_position,
|
||||
width=well_width,
|
||||
height=well_height)
|
||||
|
||||
# Optionally implant well if layer exists
|
||||
well_layer = "{}well".format(self.well_type)
|
||||
if well_layer in tech.layer:
|
||||
well_width_rule = drc("minwidth_" + well_layer)
|
||||
self.well_enclose_active = drc(well_layer + "_enclose_active")
|
||||
self.well_width = max(self.first_layer_width + 2 * self.well_enclose_active,
|
||||
well_width_rule)
|
||||
self.well_height = max(self.first_layer_height + 2 * self.well_enclose_active,
|
||||
well_width_rule)
|
||||
center_pos = vector(0.5*self.width, 0.5*self.height)
|
||||
well_position = center_pos - vector(0.5*self.well_width, 0.5*self.well_height)
|
||||
self.add_rect(layer=well_layer,
|
||||
offset=well_position,
|
||||
width=self.well_width,
|
||||
height=self.well_height)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
""" Get total power of a module """
|
||||
return self.return_power()
|
||||
|
||||
|
||||
from sram_factory import factory
|
||||
# Set up a static for each layer to be used for measurements
|
||||
for layer_stack in tech.layer_stacks:
|
||||
(layer1, via, layer2) = layer_stack
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=layer_stack)
|
||||
module = sys.modules[__name__]
|
||||
# Also create a contact that is just the first layer
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
setattr(module, layer1 + "_contact", cont)
|
||||
else:
|
||||
setattr(module, layer1 + "_via", cont)
|
||||
|
||||
# Set up a static for each well contact for measurements
|
||||
if "nwell" in tech.layer:
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=tech.active_stack,
|
||||
implant_type="n",
|
||||
well_type="n")
|
||||
module = sys.modules[__name__]
|
||||
setattr(module, "nwell_contact", cont)
|
||||
|
||||
if "pwell" in tech.layer:
|
||||
cont = factory.create(module_type="contact",
|
||||
layer_stack=tech.active_stack,
|
||||
implant_type="p",
|
||||
well_type="p")
|
||||
module = sys.modules[__name__]
|
||||
setattr(module, "pwell_contact", cont)
|
||||
|
||||
# This is not instantiated and used for calculations only.
|
||||
# These are static 1x1 contacts to reuse in all the design modules.
|
||||
well = factory.create(module_type="contact",
|
||||
layer_stack=("active", "contact", "metal1"),
|
||||
directions=("H", "V"))
|
||||
active = factory.create(module_type="contact",
|
||||
layer_stack=("active", "contact", "metal1"),
|
||||
directions=("H", "V"))
|
||||
poly = factory.create(module_type="contact",
|
||||
layer_stack=("poly", "contact", "metal1"),
|
||||
directions=("V", "H"))
|
||||
m1m2 = factory.create(module_type="contact",
|
||||
layer_stack=("metal1", "via1", "metal2"),
|
||||
directions=("H", "V"))
|
||||
m2m3 = factory.create(module_type="contact",
|
||||
layer_stack=("metal2", "via2", "metal3"),
|
||||
directions=("V", "H"))
|
||||
if "metal4" in layer.keys():
|
||||
m3m4 = factory.create(module_type="contact",
|
||||
layer_stack=("metal3", "via3", "metal4"),
|
||||
directions=("H", "V"))
|
||||
else:
|
||||
m3m4 = None
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2020 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.
|
||||
#
|
||||
|
||||
class _pins:
|
||||
def __init__(self, pin_dict):
|
||||
# make the pins elements of the class to allow "." access.
|
||||
# For example: props.bitcell.cell_6t.pin.bl = "foobar"
|
||||
for k,v in pin_dict.items():
|
||||
self.__dict__[k] = v
|
||||
|
||||
class _cell:
|
||||
def __init__(self, pin_dict):
|
||||
pin_dict.update(self._default_power_pins())
|
||||
self._pins = _pins(pin_dict)
|
||||
|
||||
@property
|
||||
def pin(self):
|
||||
return self._pins
|
||||
|
||||
def _default_power_pins(self):
|
||||
return { 'vdd' : 'vdd', 'gnd' : 'gnd' }
|
||||
|
||||
class _mirror_axis:
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
class _bitcell:
|
||||
def __init__(self, mirror, split_wl, cell_6t, cell_1rw1r, cell_1w1r):
|
||||
self.mirror = mirror
|
||||
self.split_wl = split_wl
|
||||
self._6t = cell_6t
|
||||
self._1rw1r = cell_1rw1r
|
||||
self._1w1r = cell_1w1r
|
||||
|
||||
def _default():
|
||||
axis = _mirror_axis(True, False)
|
||||
cell_6t = _cell({'bl' : 'bl',
|
||||
'br' : 'br',
|
||||
'wl' : 'wl'})
|
||||
|
||||
cell_1rw1r = _cell({'bl0' : 'bl0',
|
||||
'br0' : 'br0',
|
||||
'bl1' : 'bl1',
|
||||
'br1' : 'br1',
|
||||
'wl0' : 'wl0',
|
||||
'wl1' : 'wl1'})
|
||||
cell_1w1r = _cell({'bl0' : 'bl0',
|
||||
'br0' : 'br0',
|
||||
'bl1' : 'bl1',
|
||||
'br1' : 'br1',
|
||||
'wl0' : 'wl0',
|
||||
'wl1' : 'wl1'})
|
||||
return _bitcell(cell_6t=cell_6t,
|
||||
cell_1rw1r=cell_1rw1r,
|
||||
cell_1w1r=cell_1w1r,
|
||||
split_wl = False,
|
||||
mirror=axis)
|
||||
|
||||
@property
|
||||
def cell_6t(self):
|
||||
return self._6t
|
||||
|
||||
@property
|
||||
def cell_1rw1r(self):
|
||||
return self._1rw1r
|
||||
|
||||
@property
|
||||
def cell_1w1r(self):
|
||||
return self._1w1r
|
||||
|
||||
|
||||
class _dff:
|
||||
def __init__(self, use_custom_ports, custom_port_list, custom_type_list, clk_pin):
|
||||
self.use_custom_ports = use_custom_ports
|
||||
self.custom_port_list = custom_port_list
|
||||
self.custom_type_list = custom_type_list
|
||||
self.clk_pin = clk_pin
|
||||
|
||||
class _dff_buff:
|
||||
def __init__(self, use_custom_ports, custom_buff_ports, add_body_contacts):
|
||||
self.use_custom_ports = use_custom_ports
|
||||
self.buf_ports = custom_buff_ports
|
||||
self.add_body_contacts = add_body_contacts
|
||||
|
||||
class _dff_buff_array:
|
||||
def __init__(self, use_custom_ports, add_body_contacts):
|
||||
self.use_custom_ports = use_custom_ports
|
||||
self.add_body_contacts = add_body_contacts
|
||||
|
||||
class cell_properties():
|
||||
"""
|
||||
This contains meta information about the custom designed cells. For
|
||||
instance, pin names, or the axis on which they need to be mirrored. These
|
||||
can be overriden in the tech.py file.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.names = {}
|
||||
|
||||
self._bitcell = _bitcell._default()
|
||||
|
||||
self._dff = _dff(use_custom_ports = False,
|
||||
custom_port_list = ["D", "Q", "clk", "vdd", "gnd"],
|
||||
custom_type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"],
|
||||
clk_pin= "clk")
|
||||
|
||||
self._dff_buff = _dff_buff(use_custom_ports = False,
|
||||
custom_buff_ports = ["D", "qint", "clk", "vdd", "gnd"],
|
||||
add_body_contacts = False)
|
||||
|
||||
self._dff_buff_array = _dff_buff_array(use_custom_ports = False,
|
||||
add_body_contacts = False)
|
||||
|
||||
self._write_driver = _cell({'din': 'din',
|
||||
'bl' : 'bl',
|
||||
'br' : 'br',
|
||||
'en' : 'en'})
|
||||
self._sense_amp = _cell({'bl' : 'bl',
|
||||
'br' : 'br',
|
||||
'dout' : 'dout',
|
||||
'en' : 'en'})
|
||||
|
||||
@property
|
||||
def bitcell(self):
|
||||
return self._bitcell
|
||||
|
||||
@property
|
||||
def dff(self):
|
||||
return self._dff
|
||||
|
||||
@property
|
||||
def dff_buff(self):
|
||||
return self._dff_buff
|
||||
|
||||
@property
|
||||
def dff_buff_array(self):
|
||||
return self._dff_buff_array
|
||||
|
||||
@property
|
||||
def write_driver(self):
|
||||
return self._write_driver
|
||||
|
||||
@property
|
||||
def sense_amp(self):
|
||||
return self._sense_amp
|
||||
+186
-31
@@ -6,14 +6,16 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
from hierarchy_design import hierarchy_design
|
||||
from utils import round_to_grid
|
||||
import contact
|
||||
from globals import OPTS
|
||||
import re
|
||||
|
||||
|
||||
class design(hierarchy_design):
|
||||
"""
|
||||
This is the same as the hierarchy_design class except it contains
|
||||
some DRC constants and analytical models for other modules to reuse.
|
||||
some DRC/layer constants and analytical models for other modules to reuse.
|
||||
|
||||
"""
|
||||
|
||||
@@ -21,42 +23,195 @@ class design(hierarchy_design):
|
||||
hierarchy_design.__init__(self, name)
|
||||
|
||||
self.setup_drc_constants()
|
||||
self.setup_layer_constants()
|
||||
self.setup_multiport_constants()
|
||||
|
||||
from tech import layer
|
||||
self.m1_pitch = max(contact.m1m2.width, contact.m1m2.height) + max(self.m1_space, self.m2_space)
|
||||
self.m2_pitch = max(contact.m2m3.width, contact.m2m3.height) + max(self.m2_space, self.m3_space)
|
||||
if "metal4" in layer:
|
||||
self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space)
|
||||
def setup_layer_constants(self):
|
||||
"""
|
||||
These are some layer constants used
|
||||
in many places in the compiler.
|
||||
"""
|
||||
|
||||
from tech import layer_indices
|
||||
import tech
|
||||
for layer in layer_indices:
|
||||
key = "{}_stack".format(layer)
|
||||
|
||||
# Set the stack as a local helper
|
||||
try:
|
||||
layer_stack = getattr(tech, key)
|
||||
setattr(self, key, layer_stack)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Skip computing the pitch for active
|
||||
if layer == "active":
|
||||
continue
|
||||
|
||||
# Add the pitch
|
||||
setattr(self,
|
||||
"{}_pitch".format(layer),
|
||||
self.compute_pitch(layer, True))
|
||||
|
||||
# Add the non-preferrd pitch (which has vias in the "wrong" way)
|
||||
setattr(self,
|
||||
"{}_nonpref_pitch".format(layer),
|
||||
self.compute_pitch(layer, False))
|
||||
|
||||
if False:
|
||||
from tech import preferred_directions
|
||||
print(preferred_directions)
|
||||
from tech import layer, layer_indices
|
||||
for name in layer_indices:
|
||||
if name == "active":
|
||||
continue
|
||||
try:
|
||||
print("{0} width {1} space {2}".format(name,
|
||||
getattr(self, "{}_width".format(name)),
|
||||
getattr(self, "{}_space".format(name))))
|
||||
|
||||
print("pitch {0} nonpref {1}".format(getattr(self, "{}_pitch".format(name)),
|
||||
getattr(self, "{}_nonpref_pitch".format(name))))
|
||||
except AttributeError:
|
||||
pass
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
def compute_pitch(self, layer, preferred=True):
|
||||
|
||||
"""
|
||||
This is the preferred direction pitch
|
||||
i.e. we take the minimum or maximum contact dimension
|
||||
"""
|
||||
# Find the layer stacks this is used in
|
||||
from tech import layer_stacks
|
||||
pitches = []
|
||||
for stack in layer_stacks:
|
||||
# Compute the pitch with both vias above and below (if they exist)
|
||||
if stack[0] == layer:
|
||||
pitches.append(self.compute_layer_pitch(stack, preferred))
|
||||
if stack[2] == layer:
|
||||
pitches.append(self.compute_layer_pitch(stack[::-1], True))
|
||||
|
||||
return max(pitches)
|
||||
|
||||
def compute_layer_pitch(self, layer_stack, preferred):
|
||||
|
||||
(layer1, via, layer2) = layer_stack
|
||||
try:
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
contact1 = getattr(contact, layer1 + "_contact")
|
||||
else:
|
||||
contact1 = getattr(contact, layer1 + "_via")
|
||||
except AttributeError:
|
||||
contact1 = getattr(contact, layer2 + "_via")
|
||||
|
||||
if preferred:
|
||||
if self.get_preferred_direction(layer1) == "V":
|
||||
contact_width = contact1.first_layer_width
|
||||
else:
|
||||
contact_width = contact1.first_layer_height
|
||||
else:
|
||||
self.m3_pitch = self.m2_pitch
|
||||
if self.get_preferred_direction(layer1) == "V":
|
||||
contact_width = contact1.first_layer_height
|
||||
else:
|
||||
contact_width = contact1.first_layer_width
|
||||
layer_space = getattr(self, layer1 + "_space")
|
||||
|
||||
#print(layer_stack)
|
||||
#print(contact1)
|
||||
pitch = contact_width + layer_space
|
||||
|
||||
return round_to_grid(pitch)
|
||||
|
||||
def setup_drc_constants(self):
|
||||
""" These are some DRC constants used in many places in the compiler."""
|
||||
from tech import drc, layer
|
||||
self.well_width = drc("minwidth_well")
|
||||
self.poly_width = drc("minwidth_poly")
|
||||
self.poly_space = drc("poly_to_poly")
|
||||
self.m1_width = drc("minwidth_metal1")
|
||||
self.m1_space = drc("metal1_to_metal1")
|
||||
self.m2_width = drc("minwidth_metal2")
|
||||
self.m2_space = drc("metal2_to_metal2")
|
||||
self.m3_width = drc("minwidth_metal3")
|
||||
self.m3_space = drc("metal3_to_metal3")
|
||||
if "metal4" in layer:
|
||||
self.m4_width = drc("minwidth_metal4")
|
||||
self.m4_space = drc("metal4_to_metal4")
|
||||
self.active_width = drc("minwidth_active")
|
||||
self.active_space = drc("active_to_body_active")
|
||||
self.contact_width = drc("minwidth_contact")
|
||||
"""
|
||||
These are some DRC constants used in many places
|
||||
in the compiler.
|
||||
"""
|
||||
# Make some local rules for convenience
|
||||
from tech import drc
|
||||
for rule in drc.keys():
|
||||
# Single layer width rules
|
||||
match = re.search(r"minwidth_(.*)", rule)
|
||||
if match:
|
||||
if match.group(1) == "active_contact":
|
||||
setattr(self, "contact_width", drc(match.group(0)))
|
||||
else:
|
||||
setattr(self, match.group(1) + "_width", drc(match.group(0)))
|
||||
|
||||
self.poly_to_active = drc("poly_to_active")
|
||||
self.poly_extend_active = drc("poly_extend_active")
|
||||
self.poly_to_polycontact = drc("poly_to_polycontact")
|
||||
self.contact_to_gate = drc("contact_to_gate")
|
||||
self.well_enclose_active = drc("well_enclosure_active")
|
||||
self.implant_enclose_active = drc("implant_enclosure_active")
|
||||
self.implant_space = drc("implant_to_implant")
|
||||
# Single layer area rules
|
||||
match = re.search(r"minarea_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
# Single layer spacing rules
|
||||
match = re.search(r"(.*)_to_(.*)", rule)
|
||||
if match and match.group(1) == match.group(2):
|
||||
setattr(self, match.group(1) + "_space", drc(match.group(0)))
|
||||
elif match and match.group(1) != match.group(2):
|
||||
if match.group(2) == "poly_active":
|
||||
setattr(self, match.group(1) + "_to_contact",
|
||||
drc(match.group(0)))
|
||||
else:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
match = re.search(r"(.*)_enclose_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
match = re.search(r"(.*)_extend_(.*)", rule)
|
||||
if match:
|
||||
setattr(self, match.group(0), drc(match.group(0)))
|
||||
|
||||
# Create the maximum well extend active that gets used
|
||||
# by cells to extend the wells for interaction with other cells
|
||||
from tech import layer
|
||||
self.well_extend_active = 0
|
||||
if "nwell" in layer:
|
||||
self.well_extend_active = max(self.well_extend_active, self.nwell_extend_active)
|
||||
if "pwell" in layer:
|
||||
self.well_extend_active = max(self.well_extend_active, self.pwell_extend_active)
|
||||
|
||||
# The active offset is due to the well extension
|
||||
if "pwell" in layer:
|
||||
self.pwell_enclose_active = drc("pwell_enclose_active")
|
||||
else:
|
||||
self.pwell_enclose_active = 0
|
||||
if "nwell" in layer:
|
||||
self.nwell_enclose_active = drc("nwell_enclose_active")
|
||||
else:
|
||||
self.nwell_enclose_active = 0
|
||||
# Use the max of either so that the poly gates will align properly
|
||||
self.well_enclose_active = max(self.pwell_enclose_active,
|
||||
self.nwell_enclose_active,
|
||||
self.active_space)
|
||||
|
||||
# These are for debugging previous manual rules
|
||||
if False:
|
||||
print("poly_width", self.poly_width)
|
||||
print("poly_space", self.poly_space)
|
||||
print("m1_width", self.m1_width)
|
||||
print("m1_space", self.m1_space)
|
||||
print("m2_width", self.m2_width)
|
||||
print("m2_space", self.m2_space)
|
||||
print("m3_width", self.m3_width)
|
||||
print("m3_space", self.m3_space)
|
||||
print("m4_width", self.m4_width)
|
||||
print("m4_space", self.m4_space)
|
||||
print("active_width", self.active_width)
|
||||
print("active_space", self.active_space)
|
||||
print("contact_width", self.contact_width)
|
||||
print("poly_to_active", self.poly_to_active)
|
||||
print("poly_extend_active", self.poly_extend_active)
|
||||
print("poly_to_contact", self.poly_to_contact)
|
||||
print("active_contact_to_gate", self.active_contact_to_gate)
|
||||
print("poly_contact_to_gate", self.poly_contact_to_gate)
|
||||
print("well_enclose_active", self.well_enclose_active)
|
||||
print("implant_enclose_active", self.implant_enclose_active)
|
||||
print("implant_space", self.implant_space)
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
class drc_error(Exception):
|
||||
"""Exception raised for DRC errors.
|
||||
|
||||
Attributes:
|
||||
expression -- input expression in which the error occurred
|
||||
message -- explanation of the error
|
||||
"""
|
||||
|
||||
# def __init__(self, expression, message):
|
||||
# self.expression = expression
|
||||
# self.message = message
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
+67
-46
@@ -53,7 +53,7 @@ class geometry:
|
||||
y = item[0] * math.sin(angle) + item[1] * mirr * math.cos(angle) + offset[1]
|
||||
coordinate += [[x, y]]
|
||||
return coordinate
|
||||
|
||||
|
||||
def normalize(self):
|
||||
""" Re-find the LL and UR points after a transform """
|
||||
(first, second) = self.boundary
|
||||
@@ -66,14 +66,19 @@ class geometry:
|
||||
def update_boundary(self):
|
||||
""" Update the boundary with a new placement. """
|
||||
self.compute_boundary(self.offset, self.mirror, self.rotate)
|
||||
|
||||
|
||||
def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0):
|
||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance. """
|
||||
"""
|
||||
Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance.
|
||||
"""
|
||||
if OPTS.netlist_only:
|
||||
self.boundary = [vector(0, 0), vector(0, 0)]
|
||||
return
|
||||
|
||||
(ll, ur) = [vector(0, 0), vector(self.width, self.height)]
|
||||
|
||||
# Mirroring is performed before rotation
|
||||
if mirror == "MX":
|
||||
ll = ll.scale(1, -1)
|
||||
ur = ur.scale(1, -1)
|
||||
@@ -83,8 +88,14 @@ class geometry:
|
||||
elif mirror == "XY":
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
|
||||
if rotate == 90:
|
||||
elif mirror == "" or mirror == "R0":
|
||||
pass
|
||||
else:
|
||||
debug.error("Invalid mirroring: {}".format(mirror), -1)
|
||||
|
||||
if rotate == 0:
|
||||
pass
|
||||
elif rotate == 90:
|
||||
ll = ll.rotate_scale(-1, 1)
|
||||
ur = ur.rotate_scale(-1, 1)
|
||||
elif rotate == 180:
|
||||
@@ -93,22 +104,24 @@ class geometry:
|
||||
elif rotate == 270:
|
||||
ll = ll.rotate_scale(1, -1)
|
||||
ur = ur.rotate_scale(1, -1)
|
||||
else:
|
||||
debug.error("Invalid rotation: {}".format(rotate), -1)
|
||||
|
||||
self.boundary = [offset + ll, offset + ur]
|
||||
self.normalize()
|
||||
|
||||
|
||||
def ll(self):
|
||||
""" Return the lower left corner """
|
||||
return self.boundary[0]
|
||||
|
||||
|
||||
def ur(self):
|
||||
""" Return the upper right corner """
|
||||
return self.boundary[1]
|
||||
|
||||
|
||||
def lr(self):
|
||||
""" Return the lower right corner """
|
||||
return vector(self.boundary[1].x, self.boundary[0].y)
|
||||
|
||||
|
||||
def ul(self):
|
||||
""" Return the upper left corner """
|
||||
return vector(self.boundary[0].x, self.boundary[1].y)
|
||||
@@ -132,11 +145,16 @@ class geometry:
|
||||
def cx(self):
|
||||
""" Return the center x """
|
||||
return 0.5 * (self.boundary[0].x + self.boundary[1].x)
|
||||
|
||||
|
||||
def cy(self):
|
||||
""" Return the center y """
|
||||
return 0.5 * (self.boundary[0].y + self.boundary[1].y)
|
||||
|
||||
def center(self):
|
||||
""" Return the center coordinate """
|
||||
return vector(self.cx(), self.cy())
|
||||
|
||||
|
||||
class instance(geometry):
|
||||
"""
|
||||
An instance of an instance/module with a specified location and
|
||||
@@ -147,7 +165,7 @@ class instance(geometry):
|
||||
geometry.__init__(self)
|
||||
debug.check(mirror not in ["R90", "R180", "R270"],
|
||||
"Please use rotation and not mirroring during instantiation.")
|
||||
|
||||
|
||||
self.name = name
|
||||
self.mod = mod
|
||||
self.gds = mod.gds
|
||||
@@ -165,10 +183,10 @@ class instance(geometry):
|
||||
self.width = round_to_grid(mod.width)
|
||||
self.height = round_to_grid(mod.height)
|
||||
self.compute_boundary(offset, mirror, rotate)
|
||||
|
||||
|
||||
debug.info(4, "creating instance: " + self.name)
|
||||
|
||||
def get_blockages(self, layer, top=False):
|
||||
def get_blockages(self, lpp, top=False):
|
||||
""" Retrieve blockages of all modules in this instance.
|
||||
Apply the transform of the instance placement to give absolute blockages."""
|
||||
angle = math.radians(float(self.rotate))
|
||||
@@ -192,20 +210,19 @@ class instance(geometry):
|
||||
if self.mod.is_library_cell:
|
||||
# Writes library cell blockages as shapes instead of a large metal blockage
|
||||
blockages = []
|
||||
blockages = self.mod.gds.getBlockages(layer)
|
||||
blockages = self.mod.gds.getBlockages(lpp)
|
||||
for b in blockages:
|
||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
||||
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||
else:
|
||||
blockages = self.mod.get_blockages(layer)
|
||||
blockages = self.mod.get_blockages(lpp)
|
||||
for b in blockages:
|
||||
new_blockages.append(self.transform_coords(b,self.offset, mirr, angle))
|
||||
new_blockages.append(self.transform_coords(b, self.offset, mirr, angle))
|
||||
return new_blockages
|
||||
|
||||
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Recursively writes all the sub-modules in this instance"""
|
||||
debug.info(4, "writing instance: " + self.name)
|
||||
# make sure to write out my module/structure
|
||||
# make sure to write out my module/structure
|
||||
# (it will only be written the first time though)
|
||||
self.mod.gds_write_file(self.gds)
|
||||
# now write an instance of my module/structure
|
||||
@@ -214,7 +231,7 @@ class instance(geometry):
|
||||
offsetInMicrons=self.offset,
|
||||
mirror=self.mirror,
|
||||
rotate=self.rotate)
|
||||
|
||||
|
||||
def place(self, offset, mirror="R0", rotate=0):
|
||||
""" This updates the placement of an instance. """
|
||||
# Update the placement of an already added instance
|
||||
@@ -222,27 +239,27 @@ class instance(geometry):
|
||||
self.mirror = mirror
|
||||
self.rotate = rotate
|
||||
self.update_boundary()
|
||||
debug.info(3, "placing instance {}".format(self))
|
||||
debug.info(3, "placing instance {}".format(self))
|
||||
|
||||
def get_pin(self,name,index=-1):
|
||||
def get_pin(self, name, index=-1):
|
||||
""" Return an absolute pin that is offset and transformed based on
|
||||
this instance location. Index will return one of several pins."""
|
||||
|
||||
import copy
|
||||
if index == -1:
|
||||
pin = copy.deepcopy(self.mod.get_pin(name))
|
||||
pin.transform(self.offset,self.mirror,self.rotate)
|
||||
pin.transform(self.offset, self.mirror, self.rotate)
|
||||
return pin
|
||||
else:
|
||||
pins = copy.deepcopy(self.mod.get_pin(name))
|
||||
pin.transform(self.offset,self.mirror,self.rotate)
|
||||
pins.transform(self.offset, self.mirror, self.rotate)
|
||||
return pin[index]
|
||||
|
||||
def get_num_pins(self, name):
|
||||
""" Return the number of pins of a given name """
|
||||
return len(self.mod.get_pins(name))
|
||||
|
||||
def get_pins(self,name):
|
||||
def get_pins(self, name):
|
||||
""" Return an absolute pin that is offset and transformed based on
|
||||
this instance location. """
|
||||
|
||||
@@ -251,7 +268,7 @@ class instance(geometry):
|
||||
|
||||
new_pins = []
|
||||
for p in pin:
|
||||
p.transform(self.offset,self.mirror,self.rotate)
|
||||
p.transform(self.offset, self.mirror, self.rotate)
|
||||
new_pins.append(p)
|
||||
return new_pins
|
||||
|
||||
@@ -391,14 +408,16 @@ class instance(geometry):
|
||||
""" override print function output """
|
||||
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
|
||||
|
||||
|
||||
class path(geometry):
|
||||
"""Represents a Path"""
|
||||
|
||||
def __init__(self, layerNumber, coordinates, path_width):
|
||||
def __init__(self, lpp, coordinates, path_width):
|
||||
"""Initializes a path for the specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "path"
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.coordinates = map(lambda x: [x[0], x[1]], coordinates)
|
||||
self.coordinates = vector(self.coordinates).snap_to_grid()
|
||||
self.path_width = path_width
|
||||
@@ -411,32 +430,33 @@ class path(geometry):
|
||||
"""Writes the path to GDS"""
|
||||
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
|
||||
new_layout.addPath(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
coordinates=self.coordinates,
|
||||
width=self.path_width)
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Fail since we don't support paths yet. """
|
||||
assert(0)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "path: layer=" + self.layerNumber + " w=" + self.width
|
||||
return "path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( path: layer=" + self.layerNumber + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
||||
return "( path: layer=" + self.layerNumber + " purpose=" + str(self.layerPurpose) + " w=" + self.width + " coords=" + str(self.coordinates) + " )"
|
||||
|
||||
|
||||
class label(geometry):
|
||||
"""Represents a text label"""
|
||||
|
||||
def __init__(self, text, layerNumber, offset, zoom=-1):
|
||||
def __init__(self, text, lpp, offset, zoom=-1):
|
||||
"""Initializes a text label for specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "label"
|
||||
self.text = text
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
|
||||
if zoom<0:
|
||||
@@ -446,14 +466,14 @@ class label(geometry):
|
||||
|
||||
self.size = 0
|
||||
|
||||
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||
debug.info(4, "creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
|
||||
|
||||
def gds_write_file(self, new_layout):
|
||||
"""Writes the text label to GDS"""
|
||||
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
|
||||
new_layout.addText(text=self.text,
|
||||
layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
offsetInMicrons=self.offset,
|
||||
magnification=self.zoom,
|
||||
rotate=None)
|
||||
@@ -461,24 +481,25 @@ class label(geometry):
|
||||
def get_blockages(self, layer):
|
||||
""" Returns an empty list since text cannot be blockages. """
|
||||
return []
|
||||
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "label: " + self.text + " layer=" + str(self.layerNumber)
|
||||
return "label: " + self.text + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose)
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
|
||||
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||
|
||||
|
||||
|
||||
class rectangle(geometry):
|
||||
"""Represents a rectangular shape"""
|
||||
|
||||
def __init__(self, layerNumber, offset, width, height):
|
||||
def __init__(self, lpp, offset, width, height):
|
||||
"""Initializes a rectangular shape for specified layer"""
|
||||
geometry.__init__(self)
|
||||
self.name = "rect"
|
||||
self.layerNumber = layerNumber
|
||||
self.layerNumber = lpp[0]
|
||||
self.layerPurpose = lpp[1]
|
||||
self.offset = vector(offset).snap_to_grid()
|
||||
self.size = vector(width, height).snap_to_grid()
|
||||
self.width = round_to_grid(self.size.x)
|
||||
@@ -487,7 +508,7 @@ class rectangle(geometry):
|
||||
|
||||
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
|
||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||
|
||||
|
||||
def get_blockages(self, layer):
|
||||
""" Returns a list of one rectangle if it is on this layer"""
|
||||
if self.layerNumber == layer:
|
||||
@@ -502,7 +523,7 @@ class rectangle(geometry):
|
||||
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
|
||||
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
|
||||
new_layout.addBox(layerNumber=self.layerNumber,
|
||||
purposeNumber=0,
|
||||
purposeNumber=self.layerPurpose,
|
||||
offsetInMicrons=self.offset,
|
||||
width=self.width,
|
||||
height=self.height,
|
||||
@@ -514,4 +535,4 @@ class rectangle(geometry):
|
||||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " )"
|
||||
return "( rect: @" + str(self.offset) + " WxH=" + str(self.width) + "x" + str(self.height) + " layer=" + str(self.layerNumber) + " purpose=" + str(self.layerPurpose) + " )"
|
||||
|
||||
@@ -7,15 +7,10 @@
|
||||
#
|
||||
import hierarchy_layout
|
||||
import hierarchy_spice
|
||||
import globals
|
||||
import verify
|
||||
import debug
|
||||
import os
|
||||
from globals import OPTS
|
||||
import graph_util
|
||||
|
||||
total_drc_errors = 0
|
||||
total_lvs_errors = 0
|
||||
import tech
|
||||
|
||||
class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
"""
|
||||
@@ -28,126 +23,165 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
self.gds_file = OPTS.openram_tech + "gds_lib/" + name + ".gds"
|
||||
self.sp_file = OPTS.openram_tech + "sp_lib/" + name + ".sp"
|
||||
|
||||
# If we have a separate lvs directory, then all the lvs files
|
||||
# should be in there (all or nothing!)
|
||||
try:
|
||||
lvs_subdir = tech.lvs_lib
|
||||
except AttributeError:
|
||||
lvs_subdir = "lvs_lib"
|
||||
lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
|
||||
|
||||
if os.path.exists(lvs_dir):
|
||||
self.lvs_file = lvs_dir + name + ".sp"
|
||||
else:
|
||||
self.lvs_file = self.sp_file
|
||||
|
||||
self.drc_errors = "skipped"
|
||||
self.lvs_errors = "skipped"
|
||||
|
||||
self.name = name
|
||||
hierarchy_spice.spice.__init__(self, name)
|
||||
hierarchy_layout.layout.__init__(self, name)
|
||||
self.init_graph_params()
|
||||
|
||||
def get_layout_pins(self,inst):
|
||||
def get_layout_pins(self, inst):
|
||||
""" Return a map of pin locations of the instance offset """
|
||||
# find the instance
|
||||
for i in self.insts:
|
||||
if i.name == inst.name:
|
||||
break
|
||||
else:
|
||||
debug.error("Couldn't find instance {0}".format(inst_name),-1)
|
||||
debug.error("Couldn't find instance {0}".format(inst.name), -1)
|
||||
inst_map = inst.mod.pin_map
|
||||
return inst_map
|
||||
|
||||
|
||||
def DRC_LVS(self, final_verification=False, top_level=False):
|
||||
def DRC_LVS(self, final_verification=False, force_check=False):
|
||||
"""Checks both DRC and LVS for a module"""
|
||||
|
||||
# Final verification option does not allow nets to be connected by label.
|
||||
# Unit tests will check themselves.
|
||||
if OPTS.is_unit_test:
|
||||
import verify
|
||||
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
if not OPTS.check_lvsdrc:
|
||||
# Unit tests will check themselves.
|
||||
elif not force_check and OPTS.is_unit_test:
|
||||
return
|
||||
elif not force_check and not OPTS.check_lvsdrc:
|
||||
return
|
||||
# Do not run if disabled in options.
|
||||
if (OPTS.inline_lvsdrc or top_level):
|
||||
elif (OPTS.inline_lvsdrc or force_check or final_verification):
|
||||
|
||||
global total_drc_errors
|
||||
global total_lvs_errors
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
self.sp_write(tempspice)
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.lvs_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
# Final verification option does not allow nets to be connected by label.
|
||||
self.drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
||||
self.lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
|
||||
num_drc_errors = verify.run_drc(self.name, tempgds, extract=True, final_verification=final_verification)
|
||||
num_lvs_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
debug.check(num_drc_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_drc_errors))
|
||||
debug.check(num_lvs_errors == 0,"LVS failed for {0} with {1} errors(s)".format(self.name,num_lvs_errors))
|
||||
total_drc_errors += num_drc_errors
|
||||
total_lvs_errors += num_lvs_errors
|
||||
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
# force_check is used to determine decoder height and other things, so we shouldn't fail
|
||||
# if that flag is set
|
||||
if OPTS.inline_lvsdrc and not force_check:
|
||||
debug.check(self.drc_errors == 0,
|
||||
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||
self.drc_errors))
|
||||
debug.check(self.lvs_errors == 0,
|
||||
"LVS failed for {0} with {1} errors(s)".format(self.name,
|
||||
self.lvs_errors))
|
||||
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def DRC(self, final_verification=False):
|
||||
"""Checks DRC for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_drc_errors
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.gds_write(tempgds)
|
||||
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
||||
total_drc_errors += num_errors
|
||||
debug.check(num_errors == 0,"DRC failed for {0} with {1} error(s)".format(self.name,num_error))
|
||||
num_errors = verify.run_drc(self.name, tempgds, final_verification=final_verification)
|
||||
debug.check(num_errors == 0,
|
||||
"DRC failed for {0} with {1} error(s)".format(self.name,
|
||||
num_errors))
|
||||
|
||||
os.remove(tempgds)
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempgds)
|
||||
|
||||
def LVS(self, final_verification=False):
|
||||
"""Checks LVS for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
if (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
global total_lvs_errors
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp,self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp,self.name)
|
||||
self.sp_write(tempspice)
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
return
|
||||
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
|
||||
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name)
|
||||
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name)
|
||||
self.lvs_write(tempspice)
|
||||
self.gds_write(tempgds)
|
||||
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
|
||||
total_lvs_errors += num_errors
|
||||
debug.check(num_errors == 0,"LVS failed for {0} with {1} error(s)".format(self.name,num_errors))
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
debug.check(num_errors == 0,
|
||||
"LVS failed for {0} with {1} error(s)".format(self.name,
|
||||
num_errors))
|
||||
if OPTS.purge_temp:
|
||||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def init_graph_params(self):
|
||||
"""Initializes parameters relevant to the graph creation"""
|
||||
#Only initializes a set for checking instances which should not be added
|
||||
# Only initializes a set for checking instances which should not be added
|
||||
self.graph_inst_exclude = set()
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Recursively create graph from instances in module."""
|
||||
|
||||
#Translate port names to external nets
|
||||
# Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||
self.pins),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
if subinst in self.graph_inst_exclude:
|
||||
continue
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_name = inst_name + '.X' + subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
|
||||
|
||||
def build_names(self, name_dict, inst_name, port_nets):
|
||||
"""Collects all the nets and the parent inst of that net."""
|
||||
#Translate port names to external nets
|
||||
# Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
|
||||
self.pins),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_name = inst_name + '.X' + subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
for si_port, conn in zip(subinst_ports, conns):
|
||||
#Only add for first occurrence
|
||||
# Only add for first occurrence
|
||||
if si_port.lower() not in name_dict:
|
||||
mod_info = {'mod':self, 'int_net':conn}
|
||||
mod_info = {'mod': self, 'int_net': conn}
|
||||
name_dict[si_port.lower()] = mod_info
|
||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||
|
||||
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
||||
"""Given a list of nets, will compare the internal alias of a mod to determine
|
||||
if the nets have a connection to this mod's net (but not inst).
|
||||
"""
|
||||
if exclusion_set == None:
|
||||
if not exclusion_set:
|
||||
exclusion_set = set()
|
||||
try:
|
||||
self.name_dict
|
||||
@@ -161,17 +195,17 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
int_mod = self.name_dict[net]['mod']
|
||||
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
||||
aliases.append(net)
|
||||
return aliases
|
||||
return aliases
|
||||
|
||||
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
||||
"""Checks if the alias_net in input mod is the same as the input net for this mod (self)."""
|
||||
if self in exclusion_set:
|
||||
return False
|
||||
#Check ports of this mod
|
||||
# Check ports of this mod
|
||||
for pin in self.pins:
|
||||
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
||||
return True
|
||||
#Check connections of all other subinsts
|
||||
# Check connections of all other subinsts
|
||||
mod_set = set()
|
||||
for subinst, inst_conns in zip(self.insts, self.conns):
|
||||
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
||||
@@ -181,7 +215,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
||||
return True
|
||||
mod_set.add(subinst.mod)
|
||||
return False
|
||||
return False
|
||||
|
||||
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
||||
"""Utility function for checking single net alias."""
|
||||
@@ -190,8 +224,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
parent_net.lower() == alias_net.lower()
|
||||
|
||||
def get_mod_net(self, parent_net, child_inst, child_conns):
|
||||
"""Given an instance and net, returns the internal net in the mod
|
||||
corresponding to input net."""
|
||||
"""
|
||||
Given an instance and net, returns the internal net in the mod
|
||||
corresponding to input net.
|
||||
"""
|
||||
for conn, pin in zip(child_conns, child_inst.mod.pins):
|
||||
if parent_net.lower() == conn.lower():
|
||||
return pin
|
||||
@@ -205,27 +241,27 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
converted_conns.append(port_dict[conn])
|
||||
else:
|
||||
converted_conns.append("{}.{}".format(inst_name, conn))
|
||||
return converted_conns
|
||||
return converted_conns
|
||||
|
||||
def add_graph_edges(self, graph, port_nets):
|
||||
"""For every input, adds an edge to every output.
|
||||
Only intended to be used for gates and other simple modules."""
|
||||
#The final pin names will depend on the spice hierarchy, so
|
||||
#they are passed as an input.
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
# The final pin names will depend on the spice hierarchy, so
|
||||
# they are passed as an input.
|
||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
input_pins = self.get_inputs()
|
||||
output_pins = self.get_outputs()
|
||||
inout_pins = self.get_inouts()
|
||||
for inp in input_pins+inout_pins:
|
||||
for out in output_pins+inout_pins:
|
||||
if inp != out: #do not add self loops
|
||||
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
||||
for inp in input_pins + inout_pins:
|
||||
for out in output_pins + inout_pins:
|
||||
if inp != out: # do not add self loops
|
||||
graph.add_edge(pin_dict[inp], pin_dict[out], self)
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
pins = ",".join(self.pins)
|
||||
insts = [" {}".format(x) for x in self.insts]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
s = "********** design {0} **********".format(self.name)
|
||||
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
|
||||
s += "\n objs ({0})=\n{1}\n".format(len(self.objs), "\n".join(objs))
|
||||
@@ -236,8 +272,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
||||
""" override print function output """
|
||||
text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
||||
for i in self.objs:
|
||||
text+=str(i)+",\n"
|
||||
text+=str(i) + ",\n"
|
||||
for i in self.insts:
|
||||
text+=str(i)+",\n"
|
||||
text+=str(i) + ",\n"
|
||||
return text
|
||||
|
||||
|
||||
+825
-626
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,12 @@ import re
|
||||
import os
|
||||
import math
|
||||
import tech
|
||||
from delay_data import *
|
||||
from wire_spice_model import *
|
||||
from power_data import *
|
||||
from delay_data import delay_data
|
||||
from wire_spice_model import wire_spice_model
|
||||
from power_data import power_data
|
||||
import logical_effort
|
||||
|
||||
|
||||
class spice():
|
||||
"""
|
||||
This provides a set of useful generic types for hierarchy
|
||||
@@ -30,19 +31,21 @@ class spice():
|
||||
|
||||
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND"]
|
||||
# Holds subckts/mods for this module
|
||||
self.mods = []
|
||||
self.mods = []
|
||||
# Holds the pins for this module
|
||||
self.pins = []
|
||||
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND
|
||||
# for each instance, this is the set of nets/nodes that map to the pins for this instance
|
||||
self.pin_type = {}
|
||||
self.pin_type = {}
|
||||
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
|
||||
# Spice format)
|
||||
self.conns = []
|
||||
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
|
||||
self.no_instances = False
|
||||
# Keep track of any comments to add the the spice
|
||||
try:
|
||||
self.commments
|
||||
except:
|
||||
except AttributeError:
|
||||
self.comments = []
|
||||
|
||||
self.sp_read()
|
||||
@@ -56,7 +59,7 @@ class spice():
|
||||
|
||||
try:
|
||||
self.commments
|
||||
except:
|
||||
except AttributeError:
|
||||
self.comments = []
|
||||
|
||||
self.comments.append(comment)
|
||||
@@ -65,7 +68,9 @@ class spice():
|
||||
""" Adds a pin to the pins list. Default type is INOUT signal. """
|
||||
self.pins.append(name)
|
||||
self.pin_type[name]=pin_type
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(name,
|
||||
pin_type))
|
||||
|
||||
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
||||
""" Adds a pin_list to the pins list """
|
||||
@@ -73,36 +78,43 @@ class spice():
|
||||
# or a list that is the same length as the pin list.
|
||||
if type(pin_type)==str:
|
||||
for pin in pin_list:
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,pin_type))
|
||||
self.add_pin(pin,pin_type)
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(pin,
|
||||
pin_type))
|
||||
self.add_pin(pin, pin_type)
|
||||
|
||||
elif len(pin_type)==len(pin_list):
|
||||
for (pin,ptype) in zip(pin_list, pin_type):
|
||||
debug.check(ptype in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(pin,ptype))
|
||||
self.add_pin(pin,ptype)
|
||||
for (pin, ptype) in zip(pin_list, pin_type):
|
||||
debug.check(ptype in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(pin,
|
||||
ptype))
|
||||
self.add_pin(pin, ptype)
|
||||
else:
|
||||
debug.error("Mismatch in type and pin list lengths.", -1)
|
||||
|
||||
def add_pin_types(self, type_list):
|
||||
"""Add pin types for all the cell's pins.
|
||||
Typically, should only be used for handmade cells."""
|
||||
#This only works if self.pins == bitcell.pin_names
|
||||
"""
|
||||
Add pin types for all the cell's pins.
|
||||
Typically, should only be used for handmade cells.
|
||||
"""
|
||||
# This only works if self.pins == bitcell.pin_names
|
||||
if self.pin_names != self.pins:
|
||||
debug.error("{} spice subcircuit port names do not match pin_names\
|
||||
\n SPICE names={}\
|
||||
\n Module names={}\
|
||||
".format(self.name, self.pin_names, self.pins),1)
|
||||
self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)}
|
||||
".format(self.name, self.pin_names, self.pins), 1)
|
||||
self.pin_type = {pin: type for pin, type in zip(self.pin_names, type_list)}
|
||||
|
||||
def get_pin_type(self, name):
|
||||
""" Returns the type of the signal pin. """
|
||||
pin_type = self.pin_type[name]
|
||||
debug.check(pin_type in self.valid_signal_types, "Invalid signaltype for {0}: {1}".format(name,pin_type))
|
||||
debug.check(pin_type in self.valid_signal_types,
|
||||
"Invalid signaltype for {0}: {1}".format(name, pin_type))
|
||||
return pin_type
|
||||
|
||||
def get_pin_dir(self, name):
|
||||
""" Returns the direction of the pin. (Supply/ground are INOUT). """
|
||||
if self.pin_type[name] in ["POWER","GROUND"]:
|
||||
if self.pin_type[name] in ["POWER", "GROUND"]:
|
||||
return "INOUT"
|
||||
else:
|
||||
return self.pin_type[name]
|
||||
@@ -125,11 +137,10 @@ class spice():
|
||||
output_list.append(pin)
|
||||
return output_list
|
||||
|
||||
|
||||
def copy_pins(self, other_module, suffix=""):
|
||||
""" This will copy all of the pins from the other module and add an optional suffix."""
|
||||
for pin in other_module.pins:
|
||||
self.add_pin(pin+suffix, other_module.get_pin_type(pin))
|
||||
self.add_pin(pin + suffix, other_module.get_pin_type(pin))
|
||||
|
||||
def get_inouts(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
@@ -144,7 +155,6 @@ class spice():
|
||||
"""Adds a subckt/submodule to the subckt hierarchy"""
|
||||
self.mods.append(mod)
|
||||
|
||||
|
||||
def connect_inst(self, args, check=True):
|
||||
"""Connects the pins of the last instance added
|
||||
It is preferred to use the function with the check to find if
|
||||
@@ -169,21 +179,23 @@ class spice():
|
||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||
len(self.insts),
|
||||
len(self.conns)))
|
||||
debug.error("Instances: \n"+str(insts_string))
|
||||
debug.error("Instances: \n" + str(insts_string))
|
||||
debug.error("-----")
|
||||
debug.error("Connections: \n"+str(conns_string),1)
|
||||
debug.error("Connections: \n" + str(conns_string), 1)
|
||||
|
||||
def get_conns(self, inst):
|
||||
"""Returns the connections of a given instance."""
|
||||
for i in range(len(self.insts)):
|
||||
if inst is self.insts[i]:
|
||||
return self.conns[i]
|
||||
#If not found, returns None
|
||||
# If not found, returns None
|
||||
return None
|
||||
|
||||
def sp_read(self):
|
||||
"""Reads the sp file (and parse the pins) from the library
|
||||
Otherwise, initialize it to null for dynamic generation"""
|
||||
"""
|
||||
Reads the sp file (and parse the pins) from the library
|
||||
Otherwise, initialize it to null for dynamic generation
|
||||
"""
|
||||
if self.sp_file and os.path.isfile(self.sp_file):
|
||||
debug.info(3, "opening {0}".format(self.sp_file))
|
||||
f = open(self.sp_file)
|
||||
@@ -198,17 +210,37 @@ class spice():
|
||||
# parses line into ports and remove subckt
|
||||
self.pins = subckt_line.split(" ")[2:]
|
||||
else:
|
||||
debug.info(4, "no spfile {0}".format(self.sp_file))
|
||||
self.spice = []
|
||||
|
||||
# We don't define self.lvs and will use self.spice if dynamically created
|
||||
# or they are the same file
|
||||
if self.lvs_file != self.sp_file and os.path.isfile(self.lvs_file):
|
||||
debug.info(3, "opening {0}".format(self.lvs_file))
|
||||
f = open(self.lvs_file)
|
||||
self.lvs = f.readlines()
|
||||
for i in range(len(self.lvs)):
|
||||
self.lvs[i] = self.lvs[i].rstrip(" \n")
|
||||
f.close()
|
||||
|
||||
# pins and subckt should be the same
|
||||
# find the correct subckt line in the file
|
||||
subckt = re.compile("^.subckt {}".format(self.name), re.IGNORECASE)
|
||||
subckt_line = list(filter(subckt.search, self.lvs))[0]
|
||||
# parses line into ports and remove subckt
|
||||
lvs_pins = subckt_line.split(" ")[2:]
|
||||
debug.check(lvs_pins == self.pins, "LVS and spice file pin mismatch.")
|
||||
|
||||
def check_net_in_spice(self, net_name):
|
||||
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
|
||||
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
|
||||
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
|
||||
# Remove spaces and lower case then add spaces.
|
||||
# Nets are separated by spaces.
|
||||
net_formatted = ' ' + net_name.lstrip().rstrip().lower() + ' '
|
||||
for line in self.spice:
|
||||
#Lowercase the line and remove any part of the line that is a comment.
|
||||
# Lowercase the line and remove any part of the line that is a comment.
|
||||
line = line.lower().split('*')[0]
|
||||
|
||||
#Skip .subckt or .ENDS lines
|
||||
# Skip .subckt or .ENDS lines
|
||||
if line.find('.') == 0:
|
||||
continue
|
||||
if net_formatted in line:
|
||||
@@ -220,7 +252,7 @@ class spice():
|
||||
nets_match = True
|
||||
for net in nets:
|
||||
nets_match = nets_match and self.check_net_in_spice(net)
|
||||
return nets_match
|
||||
return nets_match
|
||||
|
||||
def contains(self, mod, modlist):
|
||||
for x in modlist:
|
||||
@@ -228,54 +260,64 @@ class spice():
|
||||
return True
|
||||
return False
|
||||
|
||||
def sp_write_file(self, sp, usedMODS):
|
||||
""" Recursive spice subcircuit write;
|
||||
Writes the spice subcircuit from the library or the dynamically generated one"""
|
||||
if not self.spice:
|
||||
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
|
||||
"""
|
||||
Recursive spice subcircuit write;
|
||||
Writes the spice subcircuit from the library or the dynamically generated one
|
||||
"""
|
||||
|
||||
if self.no_instances:
|
||||
return
|
||||
elif not self.spice:
|
||||
# If spice isn't defined, we dynamically generate one.
|
||||
|
||||
# recursively write the modules
|
||||
for i in self.mods:
|
||||
if self.contains(i, usedMODS):
|
||||
continue
|
||||
usedMODS.append(i)
|
||||
i.sp_write_file(sp, usedMODS)
|
||||
i.sp_write_file(sp, usedMODS, lvs_netlist)
|
||||
|
||||
if len(self.insts) == 0:
|
||||
return
|
||||
if self.pins == []:
|
||||
return
|
||||
|
||||
|
||||
# write out the first spice line (the subcircuit)
|
||||
sp.write("\n.SUBCKT {0} {1}\n".format(self.name,
|
||||
" ".join(self.pins)))
|
||||
|
||||
for pin in self.pins:
|
||||
sp.write("* {1:6}: {0} \n".format(pin,self.pin_type[pin]))
|
||||
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
|
||||
|
||||
for line in self.comments:
|
||||
sp.write("* {}\n".format(line))
|
||||
|
||||
# every instance must have a set of connections, even if it is empty.
|
||||
if len(self.insts)!=len(self.conns):
|
||||
if len(self.insts) != len(self.conns):
|
||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
|
||||
len(self.insts),
|
||||
len(self.conns)))
|
||||
debug.error("Instances: \n"+str(self.insts))
|
||||
debug.error("Instances: \n" + str(self.insts))
|
||||
debug.error("-----")
|
||||
debug.error("Connections: \n"+str(self.conns),1)
|
||||
|
||||
|
||||
debug.error("Connections: \n" + str(self.conns), 1)
|
||||
|
||||
for i in range(len(self.insts)):
|
||||
# we don't need to output connections of empty instances.
|
||||
# these are wires and paths
|
||||
if self.conns[i] == []:
|
||||
continue
|
||||
if hasattr(self.insts[i].mod,"spice_device"):
|
||||
# Instance with no devices in it needs no subckt/instance
|
||||
if self.insts[i].mod.no_instances:
|
||||
continue
|
||||
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
|
||||
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
|
||||
" ".join(self.conns[i])))
|
||||
sp.write("\n")
|
||||
elif hasattr(self.insts[i].mod, "spice_device"):
|
||||
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name,
|
||||
" ".join(self.conns[i])))
|
||||
sp.write("\n")
|
||||
|
||||
else:
|
||||
sp.write("X{0} {1} {2}\n".format(self.insts[i].name,
|
||||
" ".join(self.conns[i]),
|
||||
@@ -284,11 +326,14 @@ class spice():
|
||||
sp.write(".ENDS {0}\n".format(self.name))
|
||||
|
||||
else:
|
||||
# write the subcircuit itself
|
||||
# If spice is a hard module, output the spice file contents.
|
||||
# Including the file path makes the unit test fail for other users.
|
||||
#if os.path.isfile(self.sp_file):
|
||||
# if os.path.isfile(self.sp_file):
|
||||
# sp.write("\n* {0}\n".format(self.sp_file))
|
||||
sp.write("\n".join(self.spice))
|
||||
if lvs_netlist and hasattr(self, "lvs"):
|
||||
sp.write("\n".join(self.lvs))
|
||||
else:
|
||||
sp.write("\n".join(self.spice))
|
||||
|
||||
sp.write("\n")
|
||||
|
||||
@@ -302,21 +347,32 @@ class spice():
|
||||
del usedMODS
|
||||
spfile.close()
|
||||
|
||||
def lvs_write(self, spname):
|
||||
"""Writes the lvs to files"""
|
||||
debug.info(3, "Writing to {0}".format(spname))
|
||||
spfile = open(spname, 'w')
|
||||
spfile.write("*FIRST LINE IS A COMMENT\n")
|
||||
usedMODS = list()
|
||||
self.sp_write_file(spfile, usedMODS, True)
|
||||
del usedMODS
|
||||
spfile.close()
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0.0):
|
||||
"""Inform users undefined delay module while building new modules"""
|
||||
|
||||
# FIXME: Slew is not used in the model right now. Can be added heuristically as linear factor
|
||||
# FIXME: Slew is not used in the model right now.
|
||||
# Can be added heuristically as linear factor
|
||||
relative_cap = logical_effort.convert_farad_to_relative_c(load)
|
||||
stage_effort = self.get_stage_effort(relative_cap)
|
||||
|
||||
# If it fails, then keep running with a valid object.
|
||||
if stage_effort == None:
|
||||
if not stage_effort:
|
||||
return delay_data(0.0, 0.0)
|
||||
|
||||
abs_delay = stage_effort.get_absolute_delay()
|
||||
corner_delay = self.apply_corners_analytically(abs_delay, corner)
|
||||
SLEW_APPROXIMATION = 0.1
|
||||
corner_slew = SLEW_APPROXIMATION*corner_delay
|
||||
corner_slew = SLEW_APPROXIMATION * corner_delay
|
||||
return delay_data(corner_delay, corner_slew)
|
||||
|
||||
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||
@@ -326,7 +382,7 @@ class spice():
|
||||
debug.warning("Class {0} name {1}"
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return None
|
||||
return None
|
||||
|
||||
def get_cin(self):
|
||||
"""Returns input load in Femto-Farads. All values generated using
|
||||
@@ -342,35 +398,35 @@ class spice():
|
||||
debug.warning("Design Class {0} input capacitance function needs to be defined"
|
||||
.format(self.__class__.__name__))
|
||||
debug.warning("Class {0} name {1}"
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return 0
|
||||
.format(self.__class__.__name__,
|
||||
self.name))
|
||||
return 0
|
||||
|
||||
def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5):
|
||||
"""
|
||||
Calculate the delay of a mosfet by
|
||||
def cal_delay_with_rc(self, corner, r, c, slew, swing=0.5):
|
||||
"""
|
||||
Calculate the delay of a mosfet by
|
||||
modeling it as a resistance driving a capacitance
|
||||
"""
|
||||
swing_factor = abs(math.log(1-swing)) # time constant based on swing
|
||||
delay = swing_factor * r * c #c is in ff and delay is in fs
|
||||
swing_factor = abs(math.log(1 - swing)) # time constant based on swing
|
||||
delay = swing_factor * r * c # c is in ff and delay is in fs
|
||||
delay = self.apply_corners_analytically(delay, corner)
|
||||
delay = delay * 0.001 #make the unit to ps
|
||||
delay = delay * 0.001 # make the unit to ps
|
||||
|
||||
# Output slew should be linear to input slew which is described
|
||||
# Output slew should be linear to input slew which is described
|
||||
# as 0.005* slew.
|
||||
|
||||
# The slew will be also influenced by the delay.
|
||||
# If no input slew(or too small to make impact)
|
||||
# The mimum slew should be the time to charge RC.
|
||||
# If no input slew(or too small to make impact)
|
||||
# The mimum slew should be the time to charge RC.
|
||||
# Delay * 2 is from 0 to 100% swing. 0.6*2*delay is from 20%-80%.
|
||||
slew = delay * 0.6 * 2 + 0.005 * slew
|
||||
return delay_data(delay = delay, slew = slew)
|
||||
return delay_data(delay=delay, slew=slew)
|
||||
|
||||
def apply_corners_analytically(self, delay, corner):
|
||||
"""Multiply delay by corner factors"""
|
||||
proc,vdd,temp = corner
|
||||
#FIXME: type of delay is needed to know which process to use.
|
||||
proc_mult = max(self.get_process_delay_factor(proc))
|
||||
proc, vdd, temp = corner
|
||||
# FIXME: type of delay is needed to know which process to use.
|
||||
proc_mult = max(self.get_process_delay_factor(proc))
|
||||
volt_mult = self.get_voltage_delay_factor(vdd)
|
||||
temp_mult = self.get_temp_delay_factor(temp)
|
||||
return delay * proc_mult * volt_mult * temp_mult
|
||||
@@ -385,48 +441,51 @@ class spice():
|
||||
elif mos_proc == 'F':
|
||||
proc_factors.append(0.9)
|
||||
elif mos_proc == 'S':
|
||||
proc_factors.append(1.1)
|
||||
proc_factors.append(1.1)
|
||||
return proc_factors
|
||||
|
||||
def get_voltage_delay_factor(self, voltage):
|
||||
"""Returns delay increase due to voltage.
|
||||
Implemented as linear factor based off nominal voltage.
|
||||
"""
|
||||
return tech.spice["nom_supply_voltage"]/voltage
|
||||
return tech.spice["nom_supply_voltage"] / voltage
|
||||
|
||||
def get_temp_delay_factor(self, temp):
|
||||
"""Returns delay increase due to temperature (in C).
|
||||
Determines effect on threshold voltage and then linear factor is estimated.
|
||||
"""
|
||||
#Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
||||
#(k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
||||
thermal_voltage_nom = 0.008625*tech.spice["nom_temperature"]
|
||||
thermal_voltage = 0.008625*temp
|
||||
vthresh = (tech.spice["nom_threshold"]+2*(thermal_voltage-thermal_voltage_nom))
|
||||
#Calculate effect on Vdd-Vth. The current vdd is not used here. A separate vdd factor is calculated.
|
||||
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"])/(tech.spice["nom_supply_voltage"]-vthresh)
|
||||
# Some portions of equation condensed (phi_t = k*T/q for T in Kelvin) in mV
|
||||
# (k/q)/100 = .008625, The division 100 simplifies the conversion from C to K and mV to V
|
||||
thermal_voltage_nom = 0.008625 * tech.spice["nom_temperature"]
|
||||
thermal_voltage = 0.008625 * temp
|
||||
vthresh = (tech.spice["nom_threshold"] + 2 * (thermal_voltage - thermal_voltage_nom))
|
||||
# Calculate effect on Vdd-Vth.
|
||||
# The current vdd is not used here.
|
||||
# A separate vdd factor is calculated.
|
||||
return (tech.spice["nom_supply_voltage"] - tech.spice["nom_threshold"]) / (tech.spice["nom_supply_voltage"] - vthresh)
|
||||
|
||||
def return_delay(self, delay, slew):
|
||||
return delay_data(delay, slew)
|
||||
|
||||
def generate_rc_net(self,lump_num, wire_length, wire_width):
|
||||
def generate_rc_net(self, lump_num, wire_length, wire_width):
|
||||
return wire_spice_model(lump_num, wire_length, wire_width)
|
||||
|
||||
def calc_dynamic_power(self, corner, c, freq, swing=1.0):
|
||||
"""
|
||||
"""
|
||||
Calculate dynamic power using effective capacitance, frequency, and corner (PVT)
|
||||
"""
|
||||
proc,vdd,temp = corner
|
||||
net_vswing = vdd*swing
|
||||
power_dyn = c*vdd*net_vswing*freq
|
||||
proc, vdd, temp = corner
|
||||
net_vswing = vdd * swing
|
||||
power_dyn = c * vdd * net_vswing * freq
|
||||
|
||||
#Apply process and temperature factors. Roughly, process and Vdd affect the delay which affects the power.
|
||||
#No other estimations are currently used. Increased delay->slower freq.->less power
|
||||
proc_div = max(self.get_process_delay_factor(proc))
|
||||
# A pply process and temperature factors.
|
||||
# Roughly, process and Vdd affect the delay which affects the power.
|
||||
# No other estimations are currently used. Increased delay->slower freq.->less power
|
||||
proc_div = max(self.get_process_delay_factor(proc))
|
||||
temp_div = self.get_temp_delay_factor(temp)
|
||||
power_dyn = power_dyn/(proc_div*temp_div)
|
||||
power_dyn = power_dyn / (proc_div * temp_div)
|
||||
|
||||
return power_dyn
|
||||
return power_dyn
|
||||
|
||||
def return_power(self, dynamic=0.0, leakage=0.0):
|
||||
return power_data(dynamic, leakage)
|
||||
|
||||
+231
-152
@@ -8,72 +8,117 @@
|
||||
import debug
|
||||
from tech import GDS, drc
|
||||
from vector import vector
|
||||
from tech import layer
|
||||
from tech import layer, layer_indices
|
||||
import math
|
||||
|
||||
|
||||
class pin_layout:
|
||||
"""
|
||||
A class to represent a rectangular design pin. It is limited to a
|
||||
single shape.
|
||||
"""
|
||||
|
||||
def __init__(self, name, rect, layer_name_num):
|
||||
def __init__(self, name, rect, layer_name_pp):
|
||||
self.name = name
|
||||
# repack the rect as a vector, just in case
|
||||
if type(rect[0])==vector:
|
||||
self.rect = rect
|
||||
if type(rect[0]) == vector:
|
||||
self._rect = rect
|
||||
else:
|
||||
self.rect = [vector(rect[0]),vector(rect[1])]
|
||||
self._rect = [vector(rect[0]), vector(rect[1])]
|
||||
# snap the rect to the grid
|
||||
self.rect = [x.snap_to_grid() for x in self.rect]
|
||||
self._rect = [x.snap_to_grid() for x in self.rect]
|
||||
|
||||
debug.check(self.width()>0,"Zero width pin.")
|
||||
debug.check(self.height()>0,"Zero height pin.")
|
||||
debug.check(self.width() > 0, "Zero width pin.")
|
||||
debug.check(self.height() > 0, "Zero height pin.")
|
||||
|
||||
# These are the valid pin layers
|
||||
valid_layers = { x: layer[x] for x in layer_indices.keys()}
|
||||
|
||||
# if it's a layer number look up the layer name. this assumes a unique layer number.
|
||||
if type(layer_name_num)==int:
|
||||
self.layer = list(layer.keys())[list(layer.values()).index(layer_name_num)]
|
||||
# if it's a string, use the name
|
||||
if type(layer_name_pp) == str:
|
||||
self._layer = layer_name_pp
|
||||
# else it is required to be a lpp
|
||||
else:
|
||||
self.layer=layer_name_num
|
||||
self.layer_num = layer[self.layer]
|
||||
for (layer_name, lpp) in valid_layers.items():
|
||||
if not lpp:
|
||||
continue
|
||||
if self.same_lpp(layer_name_pp, lpp):
|
||||
self._layer = layer_name
|
||||
break
|
||||
else:
|
||||
debug.error("Couldn't find layer {}".format(layer_name_pp), -1)
|
||||
|
||||
self.lpp = layer[self.layer]
|
||||
self._recompute_hash()
|
||||
|
||||
@property
|
||||
def layer(self):
|
||||
return self._layer
|
||||
|
||||
@layer.setter
|
||||
def layer(self, l):
|
||||
self._layer = l
|
||||
self._recompute_hash()
|
||||
|
||||
@property
|
||||
def rect(self):
|
||||
return self._rect
|
||||
|
||||
@rect.setter
|
||||
def rect(self, r):
|
||||
self._rect = r
|
||||
self._recompute_hash()
|
||||
|
||||
def _recompute_hash(self):
|
||||
""" Recompute the hash for our hash cache """
|
||||
self._hash = hash(repr(self))
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
|
||||
return "({} layer={} ll={} ur={})".format(self.name,
|
||||
self.layer,
|
||||
self.rect[0],
|
||||
self.rect[1])
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
override repr function output (don't include
|
||||
"""
|
||||
override repr function output (don't include
|
||||
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
||||
"""
|
||||
return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1])
|
||||
return "(layer={} ll={} ur={})".format(self.layer,
|
||||
self.rect[0],
|
||||
self.rect[1])
|
||||
|
||||
def __hash__(self):
|
||||
""" Implement the hash function for sets etc. """
|
||||
return hash(repr(self))
|
||||
|
||||
"""
|
||||
Implement the hash function for sets etc. We only return a cached
|
||||
value, that is updated when either 'rect' or 'layer' are changed. This
|
||||
is a major speedup, if pin_layout is used as a key for dicts.
|
||||
"""
|
||||
return self._hash
|
||||
|
||||
def __lt__(self, other):
|
||||
""" Provide a function for ordering items by the ll point """
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
|
||||
if ll.x < oll.x and ll.y < oll.y:
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Check if these are the same pins for duplicate checks """
|
||||
if isinstance(other, self.__class__):
|
||||
return (self.layer==other.layer and self.rect == other.rect)
|
||||
return (self.lpp == other.lpp and self.rect == other.rect)
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
def bbox(self, pin_list):
|
||||
"""
|
||||
Given a list of layout pins, create a bounding box layout.
|
||||
"""
|
||||
(ll, ur) = self.rect
|
||||
(ll, ur) = self.rect
|
||||
min_x = ll.x
|
||||
max_x = ur.x
|
||||
min_y = ll.y
|
||||
@@ -85,39 +130,46 @@ class pin_layout:
|
||||
min_y = min(min_y, pin.ll().y)
|
||||
max_y = max(max_y, pin.ur().y)
|
||||
|
||||
self.rect = [vector(min_x,min_y),vector(max_x,max_y)]
|
||||
|
||||
self.rect = [vector(min_x, min_y), vector(max_x, max_y)]
|
||||
|
||||
def fix_minarea(self):
|
||||
"""
|
||||
Try to fix minimum area rule.
|
||||
"""
|
||||
min_area = drc("{}_minarea".format(self.layer))
|
||||
pass
|
||||
|
||||
def inflate(self, spacing=None):
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
Inflate the rectangle by the spacing (or other rule)
|
||||
and return the new rectangle.
|
||||
"""
|
||||
if not spacing:
|
||||
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
|
||||
|
||||
(ll,ur) = self.rect
|
||||
|
||||
(ll, ur) = self.rect
|
||||
spacing = vector(spacing, spacing)
|
||||
newll = ll - spacing
|
||||
newur = ur + spacing
|
||||
|
||||
|
||||
return (newll, newur)
|
||||
|
||||
def intersection(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
min_x = max(ll.x, oll.x)
|
||||
max_x = min(ll.x, oll.x)
|
||||
min_y = max(ll.y, oll.y)
|
||||
max_y = min(ll.y, oll.y)
|
||||
|
||||
return [vector(min_x,min_y),vector(max_x,max_y)]
|
||||
return [vector(min_x, min_y), vector(max_x, max_y)]
|
||||
|
||||
def xoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
x_overlaps = False
|
||||
# check if self is within other x range
|
||||
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
|
||||
@@ -130,8 +182,8 @@ class pin_layout:
|
||||
|
||||
def yoverlaps(self, other):
|
||||
""" Check if shape has x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
y_overlaps = False
|
||||
|
||||
# check if self is within other y range
|
||||
@@ -142,29 +194,29 @@ class pin_layout:
|
||||
y_overlaps = True
|
||||
|
||||
return y_overlaps
|
||||
|
||||
|
||||
def xcontains(self, other):
|
||||
""" Check if shape contains the x overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
return (oll.x >= ll.x and our.x <= ur.x)
|
||||
|
||||
def ycontains(self, other):
|
||||
""" Check if shape contains the y overlap """
|
||||
(ll,ur) = self.rect
|
||||
(oll,our) = other.rect
|
||||
(ll, ur) = self.rect
|
||||
(oll, our) = other.rect
|
||||
|
||||
return (oll.y >= ll.y and our.y <= ur.y)
|
||||
|
||||
|
||||
def contains(self, other):
|
||||
""" Check if a shape contains another rectangle """
|
||||
# If it is the same shape entirely, it is contained!
|
||||
if self == other:
|
||||
return True
|
||||
|
||||
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
if not self.same_lpp(self.lpp, other.lpp):
|
||||
return False
|
||||
|
||||
if not self.xcontains(other):
|
||||
@@ -181,14 +233,13 @@ class pin_layout:
|
||||
if shape.contains(self):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def overlaps(self, other):
|
||||
""" Check if a shape overlaps with a rectangle """
|
||||
# Can only overlap on the same layer
|
||||
if self.layer != other.layer:
|
||||
if not self.same_lpp(self.lpp, other.lpp):
|
||||
return False
|
||||
|
||||
|
||||
x_overlaps = self.xoverlaps(other)
|
||||
y_overlaps = self.yoverlaps(other)
|
||||
|
||||
@@ -197,51 +248,56 @@ class pin_layout:
|
||||
def area(self):
|
||||
""" Return the area. """
|
||||
return self.height()*self.width()
|
||||
|
||||
|
||||
def height(self):
|
||||
""" Return height. Abs is for pre-normalized value."""
|
||||
return abs(self.rect[1].y-self.rect[0].y)
|
||||
|
||||
|
||||
def width(self):
|
||||
""" Return width. Abs is for pre-normalized value."""
|
||||
return abs(self.rect[1].x-self.rect[0].x)
|
||||
|
||||
def normalize(self):
|
||||
""" Re-find the LL and UR points after a transform """
|
||||
(first,second)=self.rect
|
||||
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
|
||||
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
|
||||
self.rect=[ll,ur]
|
||||
|
||||
def transform(self,offset,mirror,rotate):
|
||||
""" Transform with offset, mirror and rotation to get the absolute pin location.
|
||||
We must then re-find the ll and ur. The master is the cell instance. """
|
||||
(ll,ur) = self.rect
|
||||
if mirror=="MX":
|
||||
ll=ll.scale(1,-1)
|
||||
ur=ur.scale(1,-1)
|
||||
elif mirror=="MY":
|
||||
ll=ll.scale(-1,1)
|
||||
ur=ur.scale(-1,1)
|
||||
elif mirror=="XY":
|
||||
ll=ll.scale(-1,-1)
|
||||
ur=ur.scale(-1,-1)
|
||||
|
||||
if rotate==90:
|
||||
ll=ll.rotate_scale(-1,1)
|
||||
ur=ur.rotate_scale(-1,1)
|
||||
elif rotate==180:
|
||||
ll=ll.scale(-1,-1)
|
||||
ur=ur.scale(-1,-1)
|
||||
elif rotate==270:
|
||||
ll=ll.rotate_scale(1,-1)
|
||||
ur=ur.rotate_scale(1,-1)
|
||||
(first, second) = self.rect
|
||||
ll = vector(min(first[0], second[0]), min(first[1], second[1]))
|
||||
ur = vector(max(first[0], second[0]), max(first[1], second[1]))
|
||||
self.rect=[ll, ur]
|
||||
|
||||
self.rect=[offset+ll,offset+ur]
|
||||
def transform(self, offset, mirror, rotate):
|
||||
"""
|
||||
Transform with offset, mirror and rotation
|
||||
to get the absolute pin location.
|
||||
We must then re-find the ll and ur.
|
||||
The master is the cell instance.
|
||||
"""
|
||||
(ll, ur) = self.rect
|
||||
if mirror == "MX":
|
||||
ll = ll.scale(1, -1)
|
||||
ur = ur.scale(1, -1)
|
||||
elif mirror == "MY":
|
||||
ll = ll.scale(-1, 1)
|
||||
ur = ur.scale(-1, 1)
|
||||
elif mirror == "XY":
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
|
||||
if rotate == 90:
|
||||
ll = ll.rotate_scale(-1, 1)
|
||||
ur = ur.rotate_scale(-1, 1)
|
||||
elif rotate == 180:
|
||||
ll = ll.scale(-1, -1)
|
||||
ur = ur.scale(-1, -1)
|
||||
elif rotate == 270:
|
||||
ll = ll.rotate_scale(1, -1)
|
||||
ur = ur.rotate_scale(1, -1)
|
||||
|
||||
self.rect = [offset + ll, offset + ur]
|
||||
self.normalize()
|
||||
|
||||
def center(self):
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),0.5*(self.rect[0].y+self.rect[1].y))
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def cx(self):
|
||||
""" Center x """
|
||||
@@ -250,7 +306,7 @@ class pin_layout:
|
||||
def cy(self):
|
||||
""" Center y """
|
||||
return 0.5*(self.rect[0].y+self.rect[1].y)
|
||||
|
||||
|
||||
# The four possible corners
|
||||
def ll(self):
|
||||
""" Lower left point """
|
||||
@@ -258,17 +314,17 @@ class pin_layout:
|
||||
|
||||
def ul(self):
|
||||
""" Upper left point """
|
||||
return vector(self.rect[0].x,self.rect[1].y)
|
||||
return vector(self.rect[0].x, self.rect[1].y)
|
||||
|
||||
def lr(self):
|
||||
""" Lower right point """
|
||||
return vector(self.rect[1].x,self.rect[0].y)
|
||||
return vector(self.rect[1].x, self.rect[0].y)
|
||||
|
||||
def ur(self):
|
||||
""" Upper right point """
|
||||
return self.rect[1]
|
||||
|
||||
# The possible y edge values
|
||||
|
||||
# The possible y edge values
|
||||
def uy(self):
|
||||
""" Upper y value """
|
||||
return self.rect[1].y
|
||||
@@ -278,80 +334,93 @@ class pin_layout:
|
||||
return self.rect[0].y
|
||||
|
||||
# The possible x edge values
|
||||
|
||||
|
||||
def lx(self):
|
||||
""" Left x value """
|
||||
return self.rect[0].x
|
||||
|
||||
|
||||
def rx(self):
|
||||
""" Right x value """
|
||||
return self.rect[1].x
|
||||
|
||||
|
||||
|
||||
# The edge centers
|
||||
def rc(self):
|
||||
""" Right center point """
|
||||
return vector(self.rect[1].x,0.5*(self.rect[0].y+self.rect[1].y))
|
||||
return vector(self.rect[1].x,
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def lc(self):
|
||||
""" Left center point """
|
||||
return vector(self.rect[0].x,0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
return vector(self.rect[0].x,
|
||||
0.5*(self.rect[0].y+self.rect[1].y))
|
||||
|
||||
def uc(self):
|
||||
""" Upper center point """
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[1].y)
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
self.rect[1].y)
|
||||
|
||||
def bc(self):
|
||||
""" Bottom center point """
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),self.rect[0].y)
|
||||
|
||||
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
||||
self.rect[0].y)
|
||||
|
||||
def gds_write_file(self, newLayout):
|
||||
"""Writes the pin shape and label to GDS"""
|
||||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||
+ str(self.width()) + "x" + str(self.height()) + " @ " + str(self.ll()))
|
||||
newLayout.addBox(layerNumber=layer[self.layer],
|
||||
purposeNumber=0,
|
||||
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
||||
+ str(self.width()) + "x"
|
||||
+ str(self.height()) + " @ " + str(self.ll()))
|
||||
(layer_num, purpose) = layer[self.layer]
|
||||
try:
|
||||
from tech import pin_purpose
|
||||
except ImportError:
|
||||
pin_purpose = purpose
|
||||
try:
|
||||
from tech import label_purpose
|
||||
except ImportError:
|
||||
label_purpose = purpose
|
||||
|
||||
newLayout.addBox(layerNumber=layer_num,
|
||||
purposeNumber=pin_purpose,
|
||||
offsetInMicrons=self.ll(),
|
||||
width=self.width(),
|
||||
height=self.height(),
|
||||
center=False)
|
||||
# Add the tet in the middle of the pin.
|
||||
# This fixes some pin label offsetting when GDS gets imported into Magic.
|
||||
# This fixes some pin label offsetting when GDS gets
|
||||
# imported into Magic.
|
||||
newLayout.addText(text=self.name,
|
||||
layerNumber=layer[self.layer],
|
||||
purposeNumber=0,
|
||||
layerNumber=layer_num,
|
||||
purposeNumber=label_purpose,
|
||||
offsetInMicrons=self.center(),
|
||||
magnification=GDS["zoom"],
|
||||
rotate=None)
|
||||
|
||||
|
||||
def compute_overlap(self, other):
|
||||
""" Calculate the rectangular overlap of two rectangles. """
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
#ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
#ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
# ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
||||
# ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
||||
|
||||
dy = min(r1_ur.y,r2_ur.y)-max(r1_ll.y,r2_ll.y)
|
||||
dx = min(r1_ur.x,r2_ur.x)-max(r1_ll.x,r2_ll.x)
|
||||
|
||||
if dx>=0 and dy>=0:
|
||||
return [dx,dy]
|
||||
dy = min(r1_ur.y, r2_ur.y) - max(r1_ll.y, r2_ll.y)
|
||||
dx = min(r1_ur.x, r2_ur.x) - max(r1_ll.x, r2_ll.x)
|
||||
|
||||
if dx >= 0 and dy >= 0:
|
||||
return [dx, dy]
|
||||
else:
|
||||
return [0,0]
|
||||
return [0, 0]
|
||||
|
||||
def distance(self, other):
|
||||
"""
|
||||
"""
|
||||
Calculate the distance to another pin layout.
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
def dist(x1, y1, x2, y2):
|
||||
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
||||
|
||||
|
||||
left = r2_ur.x < r1_ll.x
|
||||
right = r1_ur.x < r2_ll.x
|
||||
bottom = r2_ur.y < r1_ll.y
|
||||
@@ -368,7 +437,7 @@ class pin_layout:
|
||||
elif left:
|
||||
return r1_ll.x - r2_ur.x
|
||||
elif right:
|
||||
return r2_ll.x - r1.ur.x
|
||||
return r2_ll.x - r1_ur.x
|
||||
elif bottom:
|
||||
return r1_ll.y - r2_ur.y
|
||||
elif top:
|
||||
@@ -376,10 +445,9 @@ class pin_layout:
|
||||
else:
|
||||
# rectangles intersect
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
def overlap_length(self, other):
|
||||
"""
|
||||
"""
|
||||
Calculate the intersection segment and determine its length
|
||||
"""
|
||||
|
||||
@@ -391,21 +459,21 @@ class pin_layout:
|
||||
intersections = self.compute_overlap_segment(other)
|
||||
# This is the common case where two pairs of edges overlap
|
||||
# at two points, so just find the distance between those two points
|
||||
if len(intersections)==2:
|
||||
(p1,p2) = intersections
|
||||
return math.sqrt(pow(p1[0]-p2[0],2) + pow(p1[1]-p2[1],2))
|
||||
if len(intersections) == 2:
|
||||
(p1, p2) = intersections
|
||||
return math.sqrt(pow(p1[0]-p2[0], 2) + pow(p1[1]-p2[1], 2))
|
||||
else:
|
||||
# This is where we had a corner intersection or none
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
def compute_overlap_segment(self, other):
|
||||
"""
|
||||
Calculate the intersection segment of two rectangles
|
||||
"""
|
||||
Calculate the intersection segment of two rectangles
|
||||
(if any)
|
||||
"""
|
||||
(r1_ll,r1_ur) = self.rect
|
||||
(r2_ll,r2_ur) = other.rect
|
||||
(r1_ll, r1_ur) = self.rect
|
||||
(r2_ll, r2_ur) = other.rect
|
||||
|
||||
# The other corners besides ll and ur
|
||||
r1_ul = vector(r1_ll.x, r1_ur.y)
|
||||
@@ -414,23 +482,24 @@ class pin_layout:
|
||||
r2_lr = vector(r2_ur.x, r2_ll.y)
|
||||
|
||||
from itertools import tee
|
||||
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
# R1 edges CW
|
||||
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
||||
r1_edges = []
|
||||
for (p,q) in pairwise(r1_cw_points):
|
||||
r1_edges.append([p,q])
|
||||
|
||||
for (p, q) in pairwise(r1_cw_points):
|
||||
r1_edges.append([p, q])
|
||||
|
||||
# R2 edges CW
|
||||
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
||||
r2_edges = []
|
||||
for (p,q) in pairwise(r2_cw_points):
|
||||
r2_edges.append([p,q])
|
||||
for (p, q) in pairwise(r2_cw_points):
|
||||
r2_edges.append([p, q])
|
||||
|
||||
# There are 4 edges on each rectangle
|
||||
# so just brute force check intersection of each
|
||||
@@ -452,36 +521,46 @@ class pin_layout:
|
||||
q.x >= min(p.x, r.x) and \
|
||||
q.y <= max(p.y, r.y) and \
|
||||
q.y >= min(p.y, r.y):
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def segment_intersection(self, s1, s2):
|
||||
"""
|
||||
"""
|
||||
Determine the intersection point of two segments
|
||||
Return the a segment if they overlap.
|
||||
Return None if they don't.
|
||||
"""
|
||||
(a,b) = s1
|
||||
(c,d) = s2
|
||||
(a, b) = s1
|
||||
(c, d) = s2
|
||||
# Line AB represented as a1x + b1y = c1
|
||||
a1 = b.y - a.y
|
||||
b1 = a.x - b.x
|
||||
c1 = a1*a.x + b1*a.y
|
||||
|
||||
|
||||
# Line CD represented as a2x + b2y = c2
|
||||
a2 = d.y - c.y
|
||||
b2 = c.x - d.x
|
||||
c2 = a2*c.x + b2*c.y
|
||||
|
||||
|
||||
determinant = a1*b2 - a2*b1
|
||||
|
||||
if determinant!=0:
|
||||
if determinant != 0:
|
||||
x = (b2*c1 - b1*c2)/determinant
|
||||
y = (a1*c2 - a2*c1)/determinant
|
||||
|
||||
r = vector(x,y).snap_to_grid()
|
||||
|
||||
r = vector(x, y).snap_to_grid()
|
||||
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
||||
return r
|
||||
|
||||
|
||||
return None
|
||||
|
||||
def same_lpp(self, lpp1, lpp2):
|
||||
"""
|
||||
Check if the layers and purposes are the same.
|
||||
Ignore if purpose is a None.
|
||||
"""
|
||||
if lpp1[1] == None or lpp2[1] == None:
|
||||
return lpp1[0] == lpp2[0]
|
||||
|
||||
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|
||||
|
||||
+34
-23
@@ -5,7 +5,6 @@
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import os
|
||||
import gdsMill
|
||||
import tech
|
||||
import math
|
||||
@@ -16,6 +15,7 @@ from pin_layout import pin_layout
|
||||
|
||||
OPTS = globals.OPTS
|
||||
|
||||
|
||||
def ceil(decimal):
|
||||
"""
|
||||
Performs a ceiling function on the decimal place specified by the DRC grid.
|
||||
@@ -23,29 +23,35 @@ def ceil(decimal):
|
||||
grid = tech.drc["grid"]
|
||||
return math.ceil(decimal * 1 / grid) / (1 / grid)
|
||||
|
||||
|
||||
def round_to_grid(number):
|
||||
"""
|
||||
Rounds an arbitrary number to the grid.
|
||||
"""
|
||||
grid = tech.drc["grid"]
|
||||
grid = tech.drc["grid"]
|
||||
# this gets the nearest integer value
|
||||
number_grid = int(round(round((number / grid), 2), 0))
|
||||
number_off = number_grid * grid
|
||||
return number_off
|
||||
|
||||
|
||||
def snap_to_grid(offset):
|
||||
"""
|
||||
Changes the coodrinate to match the grid settings
|
||||
"""
|
||||
return [round_to_grid(offset[0]),round_to_grid(offset[1])]
|
||||
return [round_to_grid(offset[0]),
|
||||
round_to_grid(offset[1])]
|
||||
|
||||
|
||||
def pin_center(boundary):
|
||||
"""
|
||||
This returns the center of a pin shape in the vlsiLayout border format.
|
||||
"""
|
||||
return [0.5 * (boundary[0] + boundary[2]), 0.5 * (boundary[1] + boundary[3])]
|
||||
return [0.5 * (boundary[0] + boundary[2]),
|
||||
0.5 * (boundary[1] + boundary[3])]
|
||||
|
||||
def auto_measure_libcell(pin_list, name, units, layer):
|
||||
|
||||
def auto_measure_libcell(pin_list, name, units, lpp):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Return these as a set of properties including the cell width/height too.
|
||||
@@ -56,43 +62,44 @@ def auto_measure_libcell(pin_list, name, units, layer):
|
||||
reader.loadFromFile(cell_gds)
|
||||
|
||||
cell = {}
|
||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||
if measure_result == None:
|
||||
measure_result = cell_vlsi.getLayoutBorder(lpp[0])
|
||||
if measure_result:
|
||||
measure_result = cell_vlsi.measureSize(name)
|
||||
[cell["width"], cell["height"]] = measure_result
|
||||
|
||||
for pin in pin_list:
|
||||
(name,layer,boundary)=cell_vlsi.getPinShapeByLabel(str(pin))
|
||||
(name, lpp, boundary) = cell_vlsi.getPinShapeByLabel(str(pin))
|
||||
cell[str(pin)] = pin_center(boundary)
|
||||
return cell
|
||||
|
||||
|
||||
|
||||
def get_gds_size(name, gds_filename, units, layer):
|
||||
def get_gds_size(name, gds_filename, units, lpp):
|
||||
"""
|
||||
Open a GDS file and return the size from either the
|
||||
bounding box or a border layer.
|
||||
"""
|
||||
debug.info(4,"Creating VLSI layout for {}".format(name))
|
||||
debug.info(4, "Creating VLSI layout for {}".format(name))
|
||||
cell_vlsi = gdsMill.VlsiLayout(units=units)
|
||||
reader = gdsMill.Gds2reader(cell_vlsi)
|
||||
reader.loadFromFile(gds_filename)
|
||||
|
||||
cell = {}
|
||||
measure_result = cell_vlsi.getLayoutBorder(layer)
|
||||
if measure_result == None:
|
||||
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
|
||||
measure_result = cell_vlsi.getLayoutBorder(lpp)
|
||||
if not measure_result:
|
||||
debug.info(2, "Layout border failed. Trying to measure size for {}".format(name))
|
||||
measure_result = cell_vlsi.measureSize(name)
|
||||
# returns width,height
|
||||
return measure_result
|
||||
|
||||
def get_libcell_size(name, units, layer):
|
||||
|
||||
def get_libcell_size(name, units, lpp):
|
||||
"""
|
||||
Open a GDS file and return the library cell size from either the
|
||||
bounding box or a border layer.
|
||||
"""
|
||||
|
||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||
return(get_gds_size(name, cell_gds, units, layer))
|
||||
return(get_gds_size(name, cell_gds, units, lpp))
|
||||
|
||||
|
||||
|
||||
def get_gds_pins(pin_names, name, gds_filename, units):
|
||||
@@ -106,20 +113,24 @@ def get_gds_pins(pin_names, name, gds_filename, units):
|
||||
|
||||
cell = {}
|
||||
for pin_name in pin_names:
|
||||
cell[str(pin_name)]=[]
|
||||
pin_list=cell_vlsi.getPinShape(str(pin_name))
|
||||
cell[str(pin_name)] = []
|
||||
pin_list = cell_vlsi.getPinShape(str(pin_name))
|
||||
for pin_shape in pin_list:
|
||||
(layer,boundary)=pin_shape
|
||||
rect=[vector(boundary[0],boundary[1]),vector(boundary[2],boundary[3])]
|
||||
# this is a list because other cells/designs may have must-connect pins
|
||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, layer))
|
||||
(lpp, boundary) = pin_shape
|
||||
rect = [vector(boundary[0], boundary[1]),
|
||||
vector(boundary[2], boundary[3])]
|
||||
# this is a list because other cells/designs
|
||||
# may have must-connect pins
|
||||
cell[str(pin_name)].append(pin_layout(pin_name, rect, lpp))
|
||||
return cell
|
||||
|
||||
|
||||
def get_libcell_pins(pin_list, name, units):
|
||||
"""
|
||||
Open a GDS file and find the pins in pin_list as text on a given layer.
|
||||
Return these as a rectangle layer pair for each pin.
|
||||
"""
|
||||
|
||||
cell_gds = OPTS.openram_tech + "gds_lib/" + str(name) + ".gds"
|
||||
return(get_gds_pins(pin_list, name, cell_gds, units))
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class vector():
|
||||
else:
|
||||
self.x = float(x)
|
||||
self.y = float(y)
|
||||
self._hash = hash((self.x,self.y))
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
@@ -49,7 +50,7 @@ class vector():
|
||||
else:
|
||||
self.x=float(value[0])
|
||||
self.y=float(value[1])
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
override getitem function
|
||||
@@ -97,7 +98,7 @@ class vector():
|
||||
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
|
||||
break things.
|
||||
"""
|
||||
return hash((self.x,self.y))
|
||||
return self._hash
|
||||
|
||||
def snap_to_grid(self):
|
||||
self.x = self.snap_offset_to_grid(self.x)
|
||||
|
||||
+69
-20
@@ -6,23 +6,27 @@
|
||||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import contact
|
||||
from wire_path import wire_path
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class wire(wire_path):
|
||||
"""
|
||||
"""
|
||||
Object metal wire; given the layer type
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
The points should be rectilinear to control the bend points. If
|
||||
not, it will always go down first.
|
||||
The points are the center of the wire.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
The widen option will avoid via-to-via spacing problems for really short segments
|
||||
(added as an option so we can disable it in bus connections)
|
||||
"""
|
||||
def __init__(self, obj, layer_stack, position_list):
|
||||
def __init__(self, obj, layer_stack, position_list, widen_short_wires=True):
|
||||
self.obj = obj
|
||||
self.layer_stack = layer_stack
|
||||
self.position_list = position_list
|
||||
self.widen_short_wires = widen_short_wires
|
||||
self.pins = [] # used for matching parm lengths
|
||||
self.switch_pos_list = []
|
||||
|
||||
@@ -36,6 +40,7 @@ class wire(wire_path):
|
||||
# wires and wire_paths should not be offset to (0,0)
|
||||
|
||||
def setup_layers(self):
|
||||
|
||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
||||
self.via_layer_name = via_layer
|
||||
|
||||
@@ -47,21 +52,49 @@ class wire(wire_path):
|
||||
via_connect = factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
|
||||
# This is used for short connections to avoid via-to-via spacing errors
|
||||
self.vert_layer_contact_width = max(via_connect.second_layer_width,
|
||||
via_connect.first_layer_width)
|
||||
self.horiz_layer_contact_width = max(via_connect.second_layer_height,
|
||||
via_connect.first_layer_height)
|
||||
|
||||
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
||||
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
|
||||
self.pitch = self.compute_pitch(self.layer_stack)
|
||||
|
||||
def compute_pitch(self, layer_stack):
|
||||
|
||||
"""
|
||||
This is contact direction independent pitch,
|
||||
i.e. we take the maximum contact dimension
|
||||
"""
|
||||
(layer1, via, layer2) = layer_stack
|
||||
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
contact1 = getattr(contact, layer1 + "_contact")
|
||||
else:
|
||||
try:
|
||||
contact1 = getattr(contact, layer1 + "_via")
|
||||
except AttributeError:
|
||||
contact1 = getattr(contact, layer2 + "_via")
|
||||
max_contact = max(contact1.width, contact1.height)
|
||||
|
||||
layer1_space = drc("{0}_to_{0}".format(layer1))
|
||||
layer2_space = drc("{0}_to_{0}".format(layer2))
|
||||
pitch = max_contact + max(layer1_space, layer2_space)
|
||||
|
||||
return pitch
|
||||
|
||||
# create a 1x1 contact
|
||||
def create_vias(self):
|
||||
""" Add a via and corner square at every corner of the path."""
|
||||
self.c=factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
c_width = self.c.width
|
||||
c_height = self.c.height
|
||||
|
||||
from itertools import tee,islice
|
||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
||||
threewise=nwise(self.position_list,3)
|
||||
from itertools import tee, islice
|
||||
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||
threewise = nwise(self.position_list, 3)
|
||||
|
||||
for (a, offset, c) in list(threewise):
|
||||
# add a exceptions to prevent a via when we don't change directions
|
||||
@@ -72,18 +105,25 @@ class wire(wire_path):
|
||||
self.obj.add_via_center(layers=self.layer_stack,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def create_rectangles(self):
|
||||
"""
|
||||
"""
|
||||
Create the actual rectangles on the appropriate layers
|
||||
using the position list of the corners.
|
||||
using the position list of the corners.
|
||||
"""
|
||||
pl = self.position_list # position list
|
||||
for index in range(len(pl) - 1):
|
||||
# Horizontal wire segment
|
||||
if pl[index][0] != pl[index + 1][0]:
|
||||
line_length = pl[index + 1][0] - pl[index][0]
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
|
||||
width = self.horiz_layer_contact_width
|
||||
else:
|
||||
width = self.horiz_layer_width
|
||||
temp_offset = [pl[index][0],
|
||||
pl[index][1] - 0.5*self.horiz_layer_width]
|
||||
pl[index][1] - 0.5 * width]
|
||||
# If we go in the negative direction, move the offset
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0] + line_length,
|
||||
temp_offset[1]]
|
||||
@@ -91,10 +131,17 @@ class wire(wire_path):
|
||||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="horizontal",
|
||||
layer_width=self.horiz_layer_width)
|
||||
layer_width=width)
|
||||
# Vertical wire segment
|
||||
elif pl[index][1] != pl[index + 1][1]:
|
||||
line_length = pl[index + 1][1] - pl[index][1]
|
||||
temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width,
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if self.widen_short_wires and abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
|
||||
width = self.vert_layer_contact_width
|
||||
else:
|
||||
width = self.vert_layer_width
|
||||
temp_offset = [pl[index][0] - 0.5 * width,
|
||||
pl[index][1]]
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0],
|
||||
@@ -103,11 +150,13 @@ class wire(wire_path):
|
||||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="vertical",
|
||||
layer_width=self.vert_layer_width)
|
||||
layer_width=width)
|
||||
|
||||
def assert_node(self, A, B):
|
||||
""" Check if the node movements are not big enough for the
|
||||
technology sizes."""
|
||||
"""
|
||||
Check if the node movements are not big enough for the
|
||||
technology sizes.
|
||||
"""
|
||||
X_diff = abs(A[0] - B[0])
|
||||
Y_diff = abs(A[1] - B[1])
|
||||
[minX, minY] = self.node_to_node
|
||||
|
||||
@@ -24,11 +24,12 @@ def create_rectilinear_route(my_list):
|
||||
my_list.append(vector(pl[index][0], pl[index + 1][1]))
|
||||
my_list.append(vector(pl[-1]))
|
||||
return my_list
|
||||
|
||||
|
||||
|
||||
class wire_path():
|
||||
"""
|
||||
Object metal wire_path; given the layer type
|
||||
Add a wire_path of minimium metal width between a set of points.
|
||||
Add a wire_path of minimium metal width between a set of points.
|
||||
The points should be rectilinear to control the bend points. If
|
||||
not, it will always go down first. The points are the center of the wire_path.
|
||||
If width is not given, it uses minimum layer width.
|
||||
@@ -37,7 +38,7 @@ class wire_path():
|
||||
self.obj = obj
|
||||
self.layer_name = layer
|
||||
self.layer_id = techlayer[layer]
|
||||
if width==None:
|
||||
if width == None:
|
||||
self.layer_width = drc["minwidth_{0}".format(layer)]
|
||||
else:
|
||||
self.layer_width = width
|
||||
@@ -46,7 +47,6 @@ class wire_path():
|
||||
self.switch_pos_list = []
|
||||
self.create_layout()
|
||||
|
||||
|
||||
def create_layout(self):
|
||||
self.create_rectilinear()
|
||||
self.connect_corner()
|
||||
@@ -60,9 +60,9 @@ class wire_path():
|
||||
|
||||
def connect_corner(self):
|
||||
""" Add a corner square at every corner of the wire_path."""
|
||||
from itertools import tee,islice
|
||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
||||
threewise=nwise(self.position_list,3)
|
||||
from itertools import tee, islice
|
||||
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||
threewise=nwise(self.position_list, 3)
|
||||
|
||||
for (a, offset, c) in list(threewise):
|
||||
# add a exceptions to prevent a corner when we retrace back in the same direction
|
||||
@@ -74,7 +74,6 @@ class wire_path():
|
||||
offset[1] - 0.5 * self.layer_width]
|
||||
self.draw_corner_wire(corner_offset)
|
||||
|
||||
|
||||
def draw_corner_wire(self, offset):
|
||||
""" This function adds the corner squares since the center
|
||||
line convention only draws to the center of the corner."""
|
||||
@@ -117,7 +116,7 @@ class wire_path():
|
||||
|
||||
def add_line(self, layer_name, length, offset, orientation, layer_width):
|
||||
"""
|
||||
straight line object with layer_minwidth
|
||||
straight line object with layer_minwidth
|
||||
(orientation: "vertical" or "horizontal") default is vertical
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user