clean version

This commit is contained in:
FriedrichWu 2024-09-05 10:08:59 +02:00
parent 0ae194f17c
commit 6743049d44
10 changed files with 171 additions and 2629 deletions

View File

@ -5,6 +5,8 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
# This version aims to keep the track out/vertical to the dff pins
# Now only consider the channel at the south, but works fine with channel at the north
import collections
from openram import debug
from openram.tech import drc
@ -85,7 +87,8 @@ class channel_route(design):
layer_stack,
directions=None,
vertical=False,
parent=None):
parent=None,
dff_area=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
@ -106,6 +109,7 @@ class channel_route(design):
self.vertical = vertical
# For debugging...
self.parent = parent
self.dff_area = dff_area # this is a special value to handle dff areas, should be true when routing col_dff/dffs
if not directions or directions == "pref":
# Use the preferred layer directions
@ -139,7 +143,17 @@ class channel_route(design):
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
# For debug
debug.warning("layer horizontal: {0}".format(self.horizontal_layer))
debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch))
debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch))
debug.warning("horizontal_space: {0}".format(self.horizontal_space))
debug.warning("layer vertical: {0}".format(self.vertical_layer))
debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch))
debug.warning("vertical_pitch: {0}".format(self.vertical_pitch))
debug.warning("vertical_space: {0}".format(self.vertical_space))
self.route()
def remove_net_from_graph(self, pin, g):
@ -219,8 +233,19 @@ class channel_route(design):
real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y))
else:
real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y)
current_offset = real_channel_offset
if self.dff_area == False:
current_offset = real_channel_offset
else: # special handle for dff area
current_offset = vector(real_channel_offset.x, real_channel_offset.y + 5) # make route out of dffs area
if self.layer_stack == self.m2_stack:
self.vertical_nonpref_pitch = self.horizontal_pitch
if self.layer_stack == self.m1_stack:
current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs
if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top
current_offset = real_channel_offset # no offset to avoid overlap problem at the top
# Sort nets by left edge value
nets.sort()
while len(nets) > 0:

View File

@ -1,427 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2024 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.
#
# This version aims to keep the track out/vertical to the dff pins
# Now only consider the channel at the south
import collections
from openram import debug
from openram.tech import drc
from .vector import vector
from .design 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 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 pins_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.pin_overlap(pin1, pin2, pitch):
return True
return False
def segment_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):
unique_id = 0
def __init__(self,
netlist,
offset,
layer_stack,
directions=None,
vertical=False,
parent=None):
"""
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
super().__init__(name)
self.netlist = netlist
self.offset = offset
self.layer_stack = layer_stack
self.directions = directions
self.vertical = vertical
# For debugging...
self.parent = parent
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
# For debug
debug.warning("layer horizontal: {0}".format(self.horizontal_layer))
debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch))
debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch))
debug.warning("horizontal_space: {0}".format(self.horizontal_space))
debug.warning("layer vertical: {0}".format(self.vertical_layer))
debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch))
debug.warning("vertical_pitch: {0}".format(self.vertical_pitch))
debug.warning("vertical_space: {0}".format(self.vertical_space))
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.segment_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.pins_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
self.min_value = min([n.min_value for n in nets])
self.max_value = min([n.max_value for n in nets])
if self.vertical:
real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y))
else:
real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y)
current_offset = real_channel_offset
current_offset = vector(real_channel_offset.x, current_offset.y + 5) # make route out of dffs area
if self.layer_stack == self.m2_stack:
self.vertical_nonpref_pitch = self.horizontal_pitch
if self.layer_stack == self.m1_stack:
current_offset = vector(real_channel_offset.x, current_offset.y + 14) # make sure no overlap between col_dffs & data_dffs
if real_channel_offset.y > 0: # which means this is channnel router for coldff at the top
current_offset = real_channel_offset # no offset to avoid overlap problem at the top
# 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.horizontal_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.vertical_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:
debug.info(0, "Channel offset: {}".format(real_channel_offset))
debug.info(0, "Current offset: {}".format(current_offset))
debug.info(0, "VCG {}".format(str(vcg)))
debug.info(0, "HCG {}".format(str(hcg)))
for net in nets:
debug.info(0, "{0} pin: {1}".format(net.name, str(net.pins)))
if self.parent:
debug.info(0, "Saving vcg.gds")
self.parent.gds_write("vcg.gds")
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 = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
self.height = self.max_value + self.vertical_nonpref_pitch - self.offset.y
else:
self.width = self.max_value + self.horizontal_nonpref_pitch - self.offset.x
self.height = current_offset.y + self.vertical_nonpref_pitch - self.offset.y
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)

View File

@ -5,6 +5,7 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
# This aim to change the position of the submodule
import datetime
from math import ceil
from importlib import import_module, reload
@ -205,16 +206,16 @@ class sram_1bank(design, verilog, lef):
if not OPTS.is_unit_test:
print_time("Submodules", datetime.datetime.now(), start_time)
def create_layout(self):
def create_layout(self, position_add=0, mod=0):
""" Layout creation """
start_time = datetime.datetime.now()
self.place_instances()
self.place_instances_changeable(position_add=position_add)
if not OPTS.is_unit_test:
print_time("Placement", datetime.datetime.now(), start_time)
start_time = datetime.datetime.now()
self.route_layout()
self.route_layout(mod=mod)
if not OPTS.is_unit_test:
print_time("Routing", datetime.datetime.now(), start_time)
@ -238,7 +239,7 @@ class sram_1bank(design, verilog, lef):
# Only run this if not a unit test, because unit test will also verify it.
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
print_time("Verification", datetime.datetime.now(), start_time)
def create_modules(self):
debug.error("Must override pure virtual function.", -1)
@ -318,7 +319,7 @@ class sram_1bank(design, verilog, lef):
# Grid is left with many top level pins
pass
def route_escape_pins(self, bbox=None):
def route_escape_pins(self, bbox=None, mod=0):
"""
Add the top-level pins for a single bank SRAM with control.
"""
@ -360,11 +361,16 @@ class sram_1bank(design, verilog, lef):
else:
for bit in range(self.num_spare_cols):
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
from openram.router import signal_escape_router as router
# mod Use for control which edge/position the pins(dout) will be placed
# 0 -> default
# 1 -> all top/bottom
# 2 -> all left/right
rtr = router(layers=self.m3_stack,
bbox=bbox,
design=self)
design=self,
mod=mod)
rtr.route(pins_to_route)
def compute_bus_sizes(self):
@ -778,11 +784,11 @@ class sram_1bank(design, verilog, lef):
self.spare_wen_dff_insts = self.create_spare_wen_dff()
else:
self.num_spare_cols = 0
def place_instances(self):
def place_instances_changeable(self, position_add=0):
"""
This places the instances for a single bank SRAM with control
logic and up to 2 ports.
logic and up to 2 ports, but be able to change the io the dff position
"""
# No orientation or offset
@ -843,7 +849,11 @@ class sram_1bank(design, verilog, lef):
self.add_layout_pins(add_vias=False)
self.route_dffs(add_routes=False)
self.remove_layout_pins()
for port in self.all_ports:
# Add the extra position
self.data_bus_size[port] += position_add
# Re-place with the new channel size
self.place_dffs()
@ -1051,7 +1061,7 @@ class sram_1bank(design, verilog, lef):
"spare_wen{0}[{1}]".format(port, bit),
start_layer=pin_layer)
def route_layout(self):
def route_layout(self, mod=0):
""" Route a single bank SRAM """
self.route_clk()
@ -1075,7 +1085,8 @@ class sram_1bank(design, verilog, lef):
if OPTS.perimeter_pins:
# We now route the escape routes far enough out so that they will
# reach past the power ring or stripes on the sides
self.route_escape_pins(init_bbox)
self.route_escape_pins(bbox=init_bbox, mod=mod)
if OPTS.route_supplies:
self.route_supplies(init_bbox)
@ -1118,7 +1129,8 @@ class sram_1bank(design, verilog, lef):
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
parent=self,
dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs
# This causes problem in magic since it sometimes cannot extract connectivity of instances
# with no active devices.
self.add_inst(cr.name, cr)
@ -1130,7 +1142,8 @@ class sram_1bank(design, verilog, lef):
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
parent=self,
dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs
# This causes problem in magic since it sometimes cannot extract connectivity of instances
# with no active devices.
self.add_inst(cr.name, cr)
@ -1167,7 +1180,7 @@ class sram_1bank(design, verilog, lef):
if len(route_map) > 0:
# This layer stack must be different than the column addr dff layer stack
layer_stack = self.m3_stack
layer_stack = self.m2_stack
if port == 0:
# This is relative to the bank at 0,0 or the s_en which is routed on M3 also
if "s_en" in self.control_logic_insts[port].mod.pin_map:
@ -1181,7 +1194,8 @@ class sram_1bank(design, verilog, lef):
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
parent=self,
dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs
if add_routes:
# This causes problem in magic since it sometimes cannot extract connectivity of instances
# with no active devices.
@ -1201,7 +1215,8 @@ class sram_1bank(design, verilog, lef):
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
parent=self,
dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs
if add_routes:
# This causes problem in magic since it sometimes cannot extract connectivity of instances
# with no active devices.

File diff suppressed because it is too large Load Diff

View File

@ -17,13 +17,24 @@ class signal_escape_router(router):
This is the signal escape router that uses the Hanan grid graph method.
"""
def __init__(self, layers, design, bbox=None):
def __init__(self, layers, design, bbox=None, mod=0):
# `router` is the base router class
router.__init__(self, layers, design, bbox)
# New pins are the side supply pins
self.new_pins = {}
# Use for add distance of dout pins at the perimeter
self.distance_right = 0
self.distance_left = 0
# Use for control which edge/position the pins(dout) will be placed
# 0 -> default
# 1 -> all top/bottom
# 2 -> all left/right
self.state_mod = mod
def route(self, pin_names):
@ -76,8 +87,6 @@ class signal_escape_router(router):
self.find_vias(new_vias)
routed_count += 1
debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max))
print("route pins:")
print(source)
self.replace_layout_pins()
@ -177,18 +186,13 @@ class signal_escape_router(router):
rect=rect,
layer_name_pp=layer)
self.fake_pins.append(pin)
print("this is add_per")
print(pin.name)
print(pin.center)
def create_fake_pin(self, pin):
""" Create a fake pin on the perimeter orthogonal to the given pin. """
ll, ur = self.bbox
c = pin.center()
print("inside pin name")
print("----------------------------------------------------------")
print(pin.name)
# Find the closest edge
edge, vertical = self.get_closest_edge(c)
@ -202,30 +206,49 @@ class signal_escape_router(router):
fake_center = vector(ur.x + self.track_wire * 2, c.y)
if edge == "top":
fake_center = vector(c.x, ur.y + self.track_wire * 2)
#fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout
"""
pattern = r'^addr0_1'
# relocate the pin position
pattern = r'^dout'
if re.match(pattern, pin.name):
vertical = True
fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin
#do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y?
"""
if self.state_mod == 0:
pass# do not change, default
elif self.state_mod == 1: # all top/bottom
if edge == "right":
vertical = False
fake_center = vector(c.x, ll.y - self.track_wire * 2)
self.distance_right += 1
else:
if edge == "left":
vertical = False
fake_center = vector(c.x, ll.y + self.track_wire * 2)
self.distance_left += 1
elif self.state_mod == 2: # all left/right
if (edge == "bottom") or (edge == "right"):# change to the east
vertical = True
fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right)
self.distance_right += 1
else:
if (edge == "top") or (edge == "left"):# change to the west
vertical = True
fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left)
self.distance_left += 1
else:
debug.error("wrong state mod!", -1)
# Create the fake pin shape
layer = self.get_layer(int(not vertical))
half_wire_vector = vector([self.half_wire] * 2)
nll = fake_center - half_wire_vector
nur = fake_center + half_wire_vector
#not test jet
#half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire])
#nll = fake_center - half_wire_vector - half_wire_vector
#nur = fake_center + half_wire_vector + half_wire_vector
rect = [nll, nur]
pin = graph_shape(name="fake",
rect=rect,
layer_name_pp=layer)
print("this create_fake_pin")
print(pin.name)
print(pin.center)
return pin
@ -234,8 +257,6 @@ class signal_escape_router(router):
to_route = []
for name in pin_names:
print("==============the pin names===================")
print(name)
pin = next(iter(self.pins[name]))
fake = self.create_fake_pin(pin)
to_route.append((pin, fake, pin.distance(fake)))

View File

@ -1,311 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram import OPTS
from .graph import graph
from .graph_shape import graph_shape
from .router import router
import re
class signal_escape_router_change(router):
"""
This is the signal escape router that uses the Hanan grid graph method.
"""
def __init__(self, layers, design, bbox=None, mod=0):
# `router` is the base router class
router.__init__(self, layers, design, bbox)
# New pins are the side supply pins
self.new_pins = {}
# Use for add distance of dout pins at the perimeter
self.distance_right = 0
self.distance_left = 0
# Use for control which edge/position the pins(dout) will be placed
# 0 -> default
# 1 -> all top/bottom
# 2 -> all left/right
self.state_mod = mod
def route(self, pin_names):
""" Route the given pins to the perimeter. """
debug.info(1, "Running signal escape router...")
# Prepare gdsMill to find pins and blockages
self.prepare_gds_reader()
# Find pins to be routed
for name in pin_names:
self.find_pins(name)
# Find blockages and vias
self.find_blockages()
self.find_vias()
# Convert blockages and vias if they overlap a pin
self.convert_vias()
self.convert_blockages()
# Add fake pins on the perimeter to do the escape routing on
self.add_perimeter_fake_pins()
# Add vdd and gnd pins as blockages as well
# NOTE: This is done to make vdd and gnd pins DRC-safe
for pin in self.all_pins:
self.blockages.append(self.inflate_shape(pin))
# Route vdd and gnd
routed_count = 0
routed_max = len(pin_names)
for source, target, _ in self.get_route_pairs(pin_names):
# Change fake pin's name so the graph will treat it as routable
target.name = source.name
# Create the graph
g = graph(self)
g.create_graph(source, target)
# Find the shortest path from source to target
path = g.find_shortest_path()
# If no path is found, throw an error
if path is None:
self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target)
debug.error("Couldn't route from {} to {}.".format(source, target), -1)
# Create the path shapes on layout
new_wires, new_vias = self.add_path(path)
self.new_pins[source.name] = new_wires[-1]
# Find the recently added shapes
self.find_blockages(name, new_wires)
self.find_vias(new_vias)
routed_count += 1
debug.info(2, "Routed {} of {} signal pins".format(routed_count, routed_max))
print("route pins:")
print(source)
self.replace_layout_pins()
def get_closest_edge(self, point):
""" Return a point's the closest edge and the edge's axis direction. """
ll, ur = self.bbox
# Snap the pin to the perimeter and break the iteration
ll_diff_x = abs(point.x - ll.x)
ll_diff_y = abs(point.y - ll.y)
ur_diff_x = abs(point.x - ur.x)
ur_diff_y = abs(point.y - ur.y)
min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y)
if min_diff == ll_diff_x:
return "left", True
if min_diff == ll_diff_y:
return "bottom", False
if min_diff == ur_diff_x:
return "right", True
return "top", False
def prepare_path(self, path):
"""
Override the `prepare_path` method from the `router` class to prevent
overflows from the SRAM layout area.
"""
ll, ur = self.bbox
nodes = super().prepare_path(path)
new_nodes = []
for i in range(len(nodes)):
node = nodes[i]
c = node.center
# Haven't overflown yet
if ll.x < c.x and c.x < ur.x and ll.y < c.y and c.y < ur.y:
new_nodes.append(node)
continue
# Snap the pin to the perimeter and break the iteration
edge, _ = self.get_closest_edge(c)
if edge == "left":
fake_center = vector3d(ll.x + self.half_wire, c.y, c.z)
if edge == "bottom":
fake_center = vector3d(c.x, ll.y + self.half_wire, c.z)
if edge == "right":
fake_center = vector3d(ur.x - self.half_wire, c.y, c.z)
if edge == "top":
fake_center = vector3d(c.x, ur.y - self.half_wire, c.z)
node.center = fake_center
new_nodes.append(node)
break
return new_nodes
def add_perimeter_fake_pins(self):
"""
Add the fake pins on the perimeter to where the signals will be routed.
These perimeter fake pins are only used to replace layout pins at the
end of routing.
"""
ll, ur = self.bbox
wide = self.track_wire
for side in ["top", "bottom", "left", "right"]:
vertical = side in ["left", "right"]
# Calculate the lower left coordinate
if side == "top":
offset = vector(ll.x, ur.y - wide)
elif side == "bottom":
offset = vector(ll.x, ll.y)
elif side == "left":
offset = vector(ll.x, ll.y)
elif side == "right":
offset = vector(ur.x - wide, ll.y)
# Calculate width and height
shape = ur - ll
if vertical:
shape_width = wide
shape_height = shape.y
else:
shape_width = shape.x
shape_height = wide
# Add this new pin
# They must lie on the non-preferred direction since the side supply
# pins will lie on the preferred direction
layer = self.get_layer(int(not vertical))
nll = vector(offset.x, offset.y)
nur = vector(offset.x + shape_width, offset.y + shape_height)
rect = [nll, nur]
pin = graph_shape(name="fake",
rect=rect,
layer_name_pp=layer)
self.fake_pins.append(pin)
print("this is add_per")
print(pin.name)
print(pin.center)
def create_fake_pin(self, pin):
""" Create a fake pin on the perimeter orthogonal to the given pin. """
ll, ur = self.bbox
c = pin.center()
print("inside pin name")
print("----------------------------------------------------------")
print(pin.name)
# Find the closest edge
edge, vertical = self.get_closest_edge(c)
# Keep the fake pin out of the SRAM layout are so that they won't be
# blocked by previous signals if they're on the same orthogonal line
if edge == "left":
fake_center = vector(ll.x - self.track_wire * 2, c.y)
if edge == "bottom":
fake_center = vector(c.x, ll.y - self.track_wire * 2)
if edge == "right":
fake_center = vector(ur.x + self.track_wire * 2, c.y)
if edge == "top":
fake_center = vector(c.x, ur.y + self.track_wire * 2)
#fake_center = vector(ll.x - self.track_wire * 2, c.y) # test if here we could change the pin position at the layout
# relocate the pin position
pattern = r'^dout'
if re.match(pattern, pin.name):
if self.state_mod == 0:
pass# do not change, default
elif self.state_mod == 1: # all top/bottom
if edge == "right":
vertical = False
fake_center = vector(c.x, ll.y - self.track_wire * 2)
self.distance_right += 1
else:
if edge == "left":
vertical = False
fake_center = vector(c.x, ll.y + self.track_wire * 2)
self.distance_left += 1
elif self.state_mod == 2: # all left/right
if (edge == "bottom") or (edge == "right"):# change to the east
vertical = True
fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right)
self.distance_right += 1
else:
if (edge == "top") or (edge == "left"):# change to the west
vertical = True
fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left)
self.distance_left += 1
else:
debug.error("wrong state mod!", -1)
''' # old version
if edge == "bottom":# change to the east
vertical = True
fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right)
self.distance_right += 1
else:
if edge == "top":# change to the west
vertical = True
fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left)
self.distance_left += 1
'''
"""
pattern = r'^addr0_1'
if re.match(pattern, pin.name):
vertical = True
fake_center = vector(ll.x - self.track_wire * 2, c.y + self.track_wire * 4)# fix still do not know how to control the distance between every fake pin
#do not know why after this, all fake out pins are put at the same position -> because the originl inside pin has same y?
"""
# Create the fake pin shape
layer = self.get_layer(int(not vertical))
half_wire_vector = vector([self.half_wire] * 2)
nll = fake_center - half_wire_vector
nur = fake_center + half_wire_vector
#not test jet
#half_wire_vector = vector([self.half_wire] * 2)# out *2 means vector([self.half_wire, self.half_wire])
#nll = fake_center - half_wire_vector - half_wire_vector
#nur = fake_center + half_wire_vector + half_wire_vector
rect = [nll, nur]
pin = graph_shape(name="fake",
rect=rect,
layer_name_pp=layer)
print("this create_fake_pin")
print(pin.name)
print(pin.center)
return pin
def get_route_pairs(self, pin_names):
""" Return the pairs to be routed. """
to_route = []
for name in pin_names:
print("==============the pin names===================")
print(name)
pin = next(iter(self.pins[name]))
fake = self.create_fake_pin(pin)
to_route.append((pin, fake, pin.distance(fake)))
return sorted(to_route, key=lambda x: x[2])
def replace_layout_pins(self):
""" Replace the old layout pins with new ones around the perimeter. """
for name, pin in self.new_pins.items():
print("name:{0}".format(name))
print("replace_pin->{0}".format(pin))
pin = graph_shape(pin.name, pin.boundary, pin.lpp)
# Find the intersection of this pin on the perimeter
for fake in self.fake_pins:
edge = pin.intersection(fake)
if edge:
break
self.design.replace_layout_pin(name, edge)
print("pass!!!!!!!")

View File

@ -76,8 +76,6 @@ class supply_router(router):
# Create the graph
g = graph(self)
g.create_graph(source, target)
# debug
debug.warning("graph creat success!")
# Find the shortest path from source to target
path = g.find_shortest_path()
# If no path is found, throw an error

View File

@ -50,13 +50,65 @@ class sram():
self.name = name
from openram.modules.sram_1bank import sram_1bank as sram
#from openram.modules.sram_new import sram_1bank as sram
num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports
self.s = sram(name, sram_config)
self.s.create_netlist()
self.s.create_netlist()# not placed & routed jet
cur_state = "IDLE"
if not OPTS.netlist_only:
self.s.create_layout()
i = 0
while i < (OPTS.word_size + 100):
debug.warning("current state: state = {0}".format(cur_state))
if cur_state == "IDLE":# default, fisrt try
try:
self.s.create_layout(position_add=i)
except AssertionError as e:
cur_state = "ALL_TOP_BOTTOM"
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
if num_ports > 1:
i = 16
else:
i = 0
continue
cur_state = "FINISH"
break
elif cur_state == "ALL_TOP_BOTTOM":
try:
self.s.create_layout(position_add=i, mod=1)
except AssertionError as e:
cur_state = "ALL_LEFT_RIGHT"
i = OPTS.word_size + 24
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
continue
cur_state = "FINISH"
break
elif cur_state == "ALL_LEFT_RIGHT":
try:
self.s.create_layout(position_add=i, mod=2)
except AssertionError as e:
cur_state = "ALL_LEFT_RIGHT"
i = i + 1
if i == (99 + OPTS.word_size):# failed in rounting
debug.error("Failed in rounting", -1)
break
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
continue
cur_state = "FINISH"
break
else:
cur_state = "FINISH"
break
if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time)
@ -110,7 +162,7 @@ class sram():
spname = OPTS.output_path + self.s.name + ".sp"
debug.print_raw("SP: Writing to {0}".format(spname))
self.sp_write(spname)
#''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck
# Save a functional simulation file with default period
functional(self.s,
spname,
@ -132,7 +184,7 @@ class sram():
d.targ_write_ports = [self.s.write_ports[0]]
d.write_delay_stimulus()
print_time("DELAY", datetime.datetime.now(), start_time)
#''' #comment the above when generating big sram, and then disable drc/;vs, bevause magic_ext stuck
# Save trimmed spice file
temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path)
self.sp_write(temp_trim_sp, lvs=False, trim=True)

View File

@ -1,338 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2024 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 os
import shutil
import datetime
import copy
from openram import debug
from openram import sram_config as config
from openram import OPTS, print_time
class sram():
"""
This is not a design module, but contains an SRAM design instance.
It could later try options of number of banks and organization to compare
results.
We can later add visualizer and other high-level functions as needed.
"""
def __init__(self, sram_config=None, name=None):
# Create default configs if custom config isn't provided
if sram_config is None:
sram_config = config(word_size=OPTS.word_size,
num_words=OPTS.num_words,
write_size=OPTS.write_size,
num_banks=OPTS.num_banks,
words_per_row=OPTS.words_per_row,
num_spare_rows=OPTS.num_spare_rows,
num_spare_cols=OPTS.num_spare_cols)
if name is None:
name = OPTS.output_name
sram_config.set_local_config(self)
# reset the static duplicate name checker for unit tests
# in case we create more than one SRAM
from openram.base import design
design.name_map=[]
debug.info(2, "create sram of size {0} with {1} num of words {2} banks".format(self.word_size,
self.num_words,
self.num_banks))
start_time = datetime.datetime.now()
self.name = name
from openram.modules.sram_new import sram_1bank as sram
num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports
self.s = sram(name, sram_config)
self.s.create_netlist()# not placed & routed jet
cur_state = "IDLE"
if not OPTS.netlist_only:
i = 0
while i < (OPTS.word_size + 100):
print("current iteration: i = {0}".format(i))
debug.warning("current state: state = {0}".format(cur_state))
if cur_state == "IDLE":# default, fisrt try
try:
self.s.create_layout_recrusive(position_add=i)
except AssertionError as e:
cur_state = "ALL_TOP_BOTTOM"
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
if num_ports > 1:
i = 16
else:
i = 0
continue
cur_state = "FINISH"
break
elif cur_state == "ALL_TOP_BOTTOM":
try:
self.s.create_layout_recrusive(position_add=i, mod=1)
except AssertionError as e:
cur_state = "ALL_LEFT_RIGHT"
i = OPTS.word_size + 24
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
continue
cur_state = "FINISH"
break
elif cur_state == "ALL_LEFT_RIGHT":
try:
self.s.create_layout_recrusive(position_add=i, mod=2)
except AssertionError as e:
cur_state = "ALL_LEFT_RIGHT"
i = i + 1
if i == (99 + OPTS.word_size):# failed in rounting
debug.error("Failed in rounting", -1)
break
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
continue
cur_state = "FINISH"
break
else:
cur_state = "FINISH"
break
''' # 2nd version
self.s = sram(name, sram_config)
self.s.create_netlist()# not placed & routed jet
if not OPTS.netlist_only:
i = 0
supply_route_not_found = False
while i < (OPTS.word_size + 100):
print("current iteration: i = {0}".format(i))
try:
self.s.create_layout_recrusive(position_add=i)
except AssertionError as e:
supply_route_not_found = True
if i == (99 + OPTS.word_size):# failed in rounting
debug.error("Failed in rounting", -1)
break
if (supply_route_not_found):
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
if i == 0:# after first try
i = OPTS.word_size + 20
else:
i = i + 1
supply_route_not_found = False
else:# successfully routed
break
'''
'''#old version
self.s = sram(name, sram_config)
self.s.create_netlist()# not placed & routed jet
#self.s_tmp = copy.deepcopy(self.s) # need deepcopy
if not OPTS.netlist_only:
i = 98
supply_route_not_found = False
#self.s_tmp = copy.deepcopy(self.s) # need deepcopy
while i < 100:
print("current iteration: {0}".format(i))
try:
self.s.create_layout_recrusive(position_add=i)
except AssertionError as e:
supply_route_not_found = True
if i == 99:# failed in rounting
debug.error("Failed in rounting", -1)
break
if (supply_route_not_found):
#self.s_tmp = copy.deepcopy(self.s) # need deepcopy
del self.s
self.s = sram(name, sram_config)
self.s.create_netlist()
i = i + 1
supply_route_not_found = False
else:# successfully routed
#self.s = copy.deepcopy(self.s_tmp) # need deepcopy
break
'''
if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time)
def get_sp_name(self):
if OPTS.use_pex:
# Use the extracted spice file
return self.pex_name
else:
# Use generated spice file for characterization
return self.sp_name
def sp_write(self, name, lvs=False, trim=False):
self.s.sp_write(name, lvs, trim)
def lef_write(self, name):
self.s.lef_write(name)
def gds_write(self, name):
self.s.gds_write(name)
def verilog_write(self, name):
self.s.verilog_write(name)
if self.num_banks != 1:
from openram.modules.sram_multibank import sram_multibank
mb = sram_multibank(self.s)
mb.verilog_write(name[:-2] + '_top.v')
def extended_config_write(self, name):
"""Dump config file with all options.
Include defaults and anything changed by input config."""
f = open(name, "w")
var_dict = dict((name, getattr(OPTS, name)) for name in dir(OPTS) if not name.startswith('__') and not callable(getattr(OPTS, name)))
for var_name, var_value in var_dict.items():
if isinstance(var_value, str):
f.write(str(var_name) + " = " + "\"" + str(var_value) + "\"\n")
else:
f.write(str(var_name) + " = " + str(var_value)+ "\n")
f.close()
def save(self):
""" Save all the output files while reporting time to do it as well. """
# Import this at the last minute so that the proper tech file
# is loaded and the right tools are selected
from openram import verify
from openram.characterizer import functional
from openram.characterizer import delay
# Save the spice file
start_time = datetime.datetime.now()
spname = OPTS.output_path + self.s.name + ".sp"
debug.print_raw("SP: Writing to {0}".format(spname))
self.sp_write(spname)
'''
# Save a functional simulation file with default period
functional(self.s,
spname,
cycles=200,
output_path=OPTS.output_path)
print_time("Spice writing", datetime.datetime.now(), start_time)
# Save stimulus and measurement file
start_time = datetime.datetime.now()
debug.print_raw("DELAY: Writing stimulus...")
d = delay(self.s, spname, ("TT", 5, 25), output_path=OPTS.output_path)
if (self.s.num_spare_rows == 0):
probe_address = "1" * self.s.addr_size
else:
probe_address = "0" + "1" * (self.s.addr_size - 1)
probe_data = self.s.word_size - 1
d.analysis_init(probe_address, probe_data)
d.targ_read_ports.extend(self.s.read_ports)
d.targ_write_ports = [self.s.write_ports[0]]
d.write_delay_stimulus()
print_time("DELAY", datetime.datetime.now(), start_time)
'''
# Save trimmed spice file
temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path)
self.sp_write(temp_trim_sp, lvs=False, trim=True)
if not OPTS.netlist_only:
# Write the layout
start_time = datetime.datetime.now()
gdsname = OPTS.output_path + self.s.name + ".gds"
debug.print_raw("GDS: Writing to {0}".format(gdsname))
self.gds_write(gdsname)
if OPTS.check_lvsdrc:
verify.write_drc_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname),
extract=True,
final_verification=True,
output_path=OPTS.output_path)
print_time("GDS", datetime.datetime.now(), start_time)
# Create a LEF physical model
start_time = datetime.datetime.now()
lefname = OPTS.output_path + self.s.name + ".lef"
debug.print_raw("LEF: Writing to {0}".format(lefname))
self.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
# Save the LVS file
start_time = datetime.datetime.now()
lvsname = OPTS.output_path + self.s.name + ".lvs.sp"
debug.print_raw("LVS: Writing to {0}".format(lvsname))
self.sp_write(lvsname, lvs=True)
if not OPTS.netlist_only and OPTS.check_lvsdrc:
verify.write_lvs_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname),
sp_name=os.path.basename(lvsname),
final_verification=True,
output_path=OPTS.output_path)
print_time("LVS writing", datetime.datetime.now(), start_time)
# Save the extracted spice file
if OPTS.use_pex:
start_time = datetime.datetime.now()
# Output the extracted design if requested
pexname = OPTS.output_path + self.s.name + ".pex.sp"
spname = OPTS.output_path + self.s.name + ".sp"
verify.run_pex(self.s.name, gdsname, spname, output=pexname)
sp_file = pexname
print_time("Extraction", datetime.datetime.now(), start_time)
else:
# Use generated spice file for characterization
sp_file = spname
# Characterize the design
start_time = datetime.datetime.now()
from openram.characterizer import lib
debug.print_raw("LIB: Characterizing... ")
lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file)
print_time("Characterization", datetime.datetime.now(), start_time)
# Write the config file
start_time = datetime.datetime.now()
try:
from shutil import copyfile
copyfile(OPTS.config_file, OPTS.output_path + OPTS.output_name + '.py')
except shutil.SameFileError:
pass
debug.print_raw("Config: Writing to {0}".format(OPTS.output_path + OPTS.output_name + '.py'))
print_time("Config", datetime.datetime.now(), start_time)
# Write the datasheet
start_time = datetime.datetime.now()
from openram.datasheet import datasheet_gen
dname = OPTS.output_path + self.s.name + ".html"
debug.print_raw("Datasheet: Writing to {0}".format(dname))
datasheet_gen.datasheet_write(dname)
print_time("Datasheet", datetime.datetime.now(), start_time)
# Write a verilog model
start_time = datetime.datetime.now()
vname = OPTS.output_path + self.s.name + '.v'
debug.print_raw("Verilog: Writing to {0}".format(vname))
self.verilog_write(vname)
print_time("Verilog", datetime.datetime.now(), start_time)
# Write out options if specified
if OPTS.output_extended_config:
start_time = datetime.datetime.now()
oname = OPTS.output_path + OPTS.output_name + "_extended.py"
debug.print_raw("Extended Config: Writing to {0}".format(oname))
self.extended_config_write(oname)
print_time("Extended Config", datetime.datetime.now(), start_time)

View File

@ -69,12 +69,13 @@ for path in output_files:
debug.print_raw(path)
# Create an SRAM (we can also pass sram_config, see documentation/tutorials for details)
#from openram import sram
#s = sram()
from openram import sram_new_test
s = sram_new_test.sram()
from openram import sram
s = sram()
#from openram import sram_openroad_test
#s = sram_openroad_test.sram()
# Output the files for the resulting SRAM
s.save()
#s.save_only()
'''
del s
s = sram_road.sram(mod=1)