mirror of https://github.com/VLSIDA/OpenRAM.git
merge in dev
This commit is contained in:
commit
e23289d5ae
|
|
@ -23,3 +23,4 @@ sky130A/
|
|||
sky130B/
|
||||
skywater-pdk/
|
||||
sky130_fd_bd_sram/
|
||||
docker/openram-ubuntu.log
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -97,6 +97,7 @@ $(SRAM_LIB_DIR): check-pdk-root
|
|||
@echo "Cloning SRAM library..."
|
||||
@[ -d $(SRAM_LIB_DIR) ] || \
|
||||
git clone $(SRAM_LIB_GIT_REPO) $(SRAM_LIB_DIR)
|
||||
@git -C $(SRAM_LIB_DIR) fetch
|
||||
@git -C $(SRAM_LIB_DIR) checkout $(SRAM_LIB_GIT_COMMIT)
|
||||
|
||||
install: $(SRAM_LIB_DIR)
|
||||
|
|
|
|||
|
|
@ -66,8 +66,6 @@ class contact(hierarchy_design):
|
|||
self.offset = vector(0, 0)
|
||||
self.implant_type = implant_type
|
||||
self.well_type = well_type
|
||||
# Module does not have pins, but has empty pin list.
|
||||
self.pins = []
|
||||
self.create_layout()
|
||||
|
||||
def create_layout(self):
|
||||
|
|
|
|||
|
|
@ -41,17 +41,17 @@ class design(hierarchy_design):
|
|||
if prop and prop.hard_cell:
|
||||
# The pins get added from the spice file, so just check
|
||||
# that they matched here
|
||||
debug.check(prop.port_names == self.pins,
|
||||
"Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, self.pins))
|
||||
debug.check(prop.port_names == list(self.pins),
|
||||
"Custom cell pin names do not match spice file:\n{0} vs {1}".format(prop.port_names, list(self.pins)))
|
||||
self.add_pin_indices(prop.port_indices)
|
||||
self.add_pin_names(prop.port_map)
|
||||
self.add_pin_types(prop.port_types)
|
||||
self.update_pin_types(prop.port_types)
|
||||
|
||||
|
||||
(width, height) = utils.get_libcell_size(self.cell_name,
|
||||
GDS["unit"],
|
||||
layer[prop.boundary_layer])
|
||||
self.pin_map = utils.get_libcell_pins(self.pins,
|
||||
self.pin_map = utils.get_libcell_pins(list(self.pins),
|
||||
self.cell_name,
|
||||
GDS["unit"])
|
||||
|
||||
|
|
@ -126,5 +126,3 @@ class design(hierarchy_design):
|
|||
for inst in self.insts:
|
||||
total_module_power += inst.mod.analytical_power(corner, load)
|
||||
return total_module_power
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ class geometry:
|
|||
|
||||
class instance(geometry):
|
||||
"""
|
||||
An instance of an instance/module with a specified location and
|
||||
rotation
|
||||
An instance of a module with a specified location, rotation,
|
||||
spice pins, and spice nets
|
||||
"""
|
||||
def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0, is_bitcell=False):
|
||||
"""Initializes an instance to represent a module"""
|
||||
|
|
@ -177,6 +177,18 @@ class instance(geometry):
|
|||
self.offset = vector(offset).snap_to_grid()
|
||||
self.mirror = mirror
|
||||
self.is_bitcell = is_bitcell
|
||||
# track if the instance's spice pin connections have been made
|
||||
self.connected = False
|
||||
|
||||
# deepcopy because this instance needs to
|
||||
# change attributes in these spice objects
|
||||
self.spice_pins = copy.deepcopy(self.mod.pins)
|
||||
self.spice_nets = copy.deepcopy(self.mod.nets)
|
||||
for pin in self.spice_pins.values():
|
||||
pin.set_inst(self)
|
||||
for net in self.spice_nets.values():
|
||||
net.set_inst(self)
|
||||
|
||||
if OPTS.netlist_only:
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
|
|
@ -275,6 +287,34 @@ class instance(geometry):
|
|||
new_pins.append(p)
|
||||
return new_pins
|
||||
|
||||
def connect_spice_pins(self, nets_list):
|
||||
"""
|
||||
add the connection between instance pins and module nets
|
||||
to both of their respective objects
|
||||
nets_list must be the same length as self.spice_pins
|
||||
"""
|
||||
if len(nets_list) == 0 and len(self.spice_pins) == 0:
|
||||
# this is the only valid case to skip the following debug check
|
||||
# because this with no pins are often connected arbitrarily
|
||||
self.connected = True
|
||||
return
|
||||
debug.check(not self.connected,
|
||||
"instance {} has already been connected".format(self.name))
|
||||
debug.check(len(self.spice_pins) == len(nets_list),
|
||||
"must provide list of nets the same length as pin list\
|
||||
when connecting an instance")
|
||||
for pin in self.spice_pins.values():
|
||||
net = nets_list.pop(0)
|
||||
pin.set_inst_net(net)
|
||||
net.connect_pin(pin)
|
||||
self.connected = True
|
||||
|
||||
def get_connections(self):
|
||||
conns = []
|
||||
for pin in self.spice_pins.values():
|
||||
conns.append(pin.inst_net.name)
|
||||
return conns
|
||||
|
||||
def calculate_transform(self, node):
|
||||
#set up the rotation matrix
|
||||
angle = math.radians(float(node.rotate))
|
||||
|
|
|
|||
|
|
@ -135,11 +135,11 @@ class hierarchy_design(spice, layout):
|
|||
# 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),
|
||||
list(self.pins)),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
for subinst, conns in zip(self.insts, self.get_instance_connections()):
|
||||
if subinst in self.graph_inst_exclude:
|
||||
continue
|
||||
subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
|
||||
|
|
@ -153,11 +153,11 @@ class hierarchy_design(spice, layout):
|
|||
# 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),
|
||||
list(self.pins)),
|
||||
1)
|
||||
port_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
port_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
for subinst, conns in zip(self.insts, self.get_instance_connections()):
|
||||
subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
for si_port, conn in zip(subinst_ports, conns):
|
||||
|
|
@ -186,7 +186,7 @@ class hierarchy_design(spice, layout):
|
|||
"""
|
||||
# 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)}
|
||||
pin_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)}
|
||||
input_pins = self.get_inputs()
|
||||
output_pins = self.get_outputs()
|
||||
inout_pins = self.get_inouts()
|
||||
|
|
@ -197,7 +197,7 @@ class hierarchy_design(spice, layout):
|
|||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
pins = ",".join(self.pins)
|
||||
pins = ",".join(list(self.pins))
|
||||
insts = [" {}".format(x) for x in self.insts]
|
||||
objs = [" {}".format(x) for x in self.objs]
|
||||
s = "********** design {0} **********".format(self.cell_name)
|
||||
|
|
@ -208,7 +208,7 @@ class hierarchy_design(spice, layout):
|
|||
|
||||
def __repr__(self):
|
||||
""" override print function output """
|
||||
text="( design: " + self.name + " pins=" + str(self.pins) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
||||
text="( design: " + self.name + " pins=" + str(list(self.pins)) + " " + str(self.width) + "x" + str(self.height) + " )\n"
|
||||
for i in self.objs:
|
||||
text+=str(i) + ",\n"
|
||||
for i in self.insts:
|
||||
|
|
|
|||
|
|
@ -641,7 +641,7 @@ class layout():
|
|||
"""
|
||||
Return a pin list of all pins
|
||||
"""
|
||||
return self.pins
|
||||
return list(self.pins)
|
||||
|
||||
def copy_layout_pin(self, instance, pin_name, new_name="", relative_offset=vector(0, 0)):
|
||||
"""
|
||||
|
|
@ -1535,6 +1535,7 @@ class layout():
|
|||
""" Return the pin shapes as blockages for non-top-level blocks. """
|
||||
# FIXME: We don't have a body contact in ptx, so just ignore it for now
|
||||
import copy
|
||||
# FIXME: this may not work now that self.pins is a dict as defined in hierarchy_spice
|
||||
pin_names = copy.deepcopy(self.pins)
|
||||
if self.name.startswith("pmos") or self.name.startswith("nmos"):
|
||||
pin_names.remove("B")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from pprint import pformat
|
|||
from openram import debug
|
||||
from openram import tech
|
||||
from openram import OPTS
|
||||
from collections import OrderedDict
|
||||
from .delay_data import delay_data
|
||||
from .wire_spice_model import wire_spice_model
|
||||
from .power_data import power_data
|
||||
|
|
@ -49,20 +50,18 @@ class spice():
|
|||
if not os.path.exists(self.lvs_file):
|
||||
self.lvs_file = self.sp_file
|
||||
|
||||
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"]
|
||||
# Holds subckts/mods for this module
|
||||
self.mods = set()
|
||||
# Holds the pins for this module (in order)
|
||||
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 = {}
|
||||
# on Python3.7+ regular dictionaries guarantee order too, but we allow use of v3.5+
|
||||
self.pins = OrderedDict()
|
||||
# An (optional) list of indices to reorder the pins to match the spice.
|
||||
self.pin_indices = []
|
||||
# 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.)
|
||||
# internal nets, which may or may not be connected to pins of the same name
|
||||
self.nets = {}
|
||||
# If this is set, it will not output subckt or instances of this (for row/col caps etc.)
|
||||
self.no_instances = False
|
||||
# If we are doing a trimmed netlist, these are the instance that will be filtered
|
||||
self.trim_insts = set()
|
||||
|
|
@ -90,128 +89,114 @@ class spice():
|
|||
|
||||
def add_pin(self, name, pin_type="INOUT"):
|
||||
""" 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(name not in self.pins, "cannot add duplicate spice pin {}".format(name))
|
||||
self.pins[name] = pin_spice(name, pin_type, self)
|
||||
|
||||
def add_pin_list(self, pin_list, pin_type="INOUT"):
|
||||
""" Adds a pin_list to the pins list """
|
||||
# The type list can be a single type for all pins
|
||||
# The pin type list can be a single type for all pins
|
||||
# or a list that is the same length as the pin list.
|
||||
if type(pin_type)==str:
|
||||
if isinstance(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)
|
||||
|
||||
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, type) in zip(pin_list, pin_type):
|
||||
self.add_pin(pin, type)
|
||||
else:
|
||||
debug.error("Mismatch in type and pin list lengths.", -1)
|
||||
debug.error("Pin type must be a string or list of strings the same length as pin_list", -1)
|
||||
|
||||
def add_pin_indices(self, index_list):
|
||||
"""
|
||||
Add pin indices for all the cell's pins.
|
||||
"""
|
||||
""" Add pin indices for all the cell's pins. """
|
||||
self.pin_indices = index_list
|
||||
|
||||
def get_ordered_inputs(self, input_list):
|
||||
"""
|
||||
Return the inputs reordered to match the pins.
|
||||
"""
|
||||
""" Return the inputs reordered to match the pins. """
|
||||
if not self.pin_indices:
|
||||
return input_list
|
||||
|
||||
new_list = [input_list[x] for x in self.pin_indices]
|
||||
return new_list
|
||||
|
||||
def add_pin_types(self, type_list):
|
||||
"""
|
||||
Add pin types for all the cell's pins.
|
||||
"""
|
||||
# This only works if self.pins == bitcell.pin_names
|
||||
if len(type_list) != len(self.pins):
|
||||
debug.error("{} spice subcircuit number of port types does not match number of pins\
|
||||
\n SPICE names={}\
|
||||
\n Module names={}\
|
||||
".format(self.name, self.pins, type_list), 1)
|
||||
self.pin_type = {pin: type for pin, type in zip(self.pins, type_list)}
|
||||
def update_pin_types(self, type_list):
|
||||
""" Change pin types for all the cell's pins. """
|
||||
debug.check(len(type_list) == len(self.pins),
|
||||
"{} spice subcircuit number of port types does not match number of pins\
|
||||
\n pin names={}\n port types={}".format(self.name, list(self.pins), type_list))
|
||||
for pin, type in zip(self.pins.values(), type_list):
|
||||
pin.set_type(type)
|
||||
|
||||
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))
|
||||
return pin_type
|
||||
pin = self.pins.get(name)
|
||||
debug.check(pin is not None, "Spice pin {} not found".format(name))
|
||||
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"]:
|
||||
pin_type = self.get_pin_type(name)
|
||||
if pin_type in ["POWER", "GROUND"]:
|
||||
return "INOUT"
|
||||
else:
|
||||
return self.pin_type[name]
|
||||
return pin_type
|
||||
|
||||
def get_inputs(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
may be over-ridden by submodules that didn't use pin directions yet."""
|
||||
"""
|
||||
These use pin types to determine pin lists.
|
||||
Returns names only, to maintain historical interface.
|
||||
"""
|
||||
input_list = []
|
||||
for pin in self.pins:
|
||||
if self.pin_type[pin]=="INPUT":
|
||||
input_list.append(pin)
|
||||
for pin in self.pins.values():
|
||||
if pin.type == "INPUT":
|
||||
input_list.append(pin.name)
|
||||
return input_list
|
||||
|
||||
def get_outputs(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
may be over-ridden by submodules that didn't use pin directions yet."""
|
||||
"""
|
||||
These use pin types to determine pin lists.
|
||||
Returns names only, to maintain historical interface.
|
||||
"""
|
||||
output_list = []
|
||||
for pin in self.pins:
|
||||
if self.pin_type[pin]=="OUTPUT":
|
||||
output_list.append(pin)
|
||||
for pin in self.pins.values():
|
||||
if pin.type == "OUTPUT":
|
||||
output_list.append(pin.name)
|
||||
return output_list
|
||||
|
||||
def get_inouts(self):
|
||||
"""
|
||||
These use pin types to determine pin lists.
|
||||
Returns names only, to maintain historical interface.
|
||||
"""
|
||||
inout_list = []
|
||||
for pin in self.pins.values():
|
||||
if pin.type == "INOUT":
|
||||
inout_list.append(pin.name)
|
||||
return inout_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))
|
||||
for pin in other_module.pins.values():
|
||||
self.add_pin(pin.name + suffix, pin.type)
|
||||
|
||||
def get_inouts(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
may be over-ridden by submodules that didn't use pin directions yet."""
|
||||
inout_list = []
|
||||
for pin in self.pins:
|
||||
if self.pin_type[pin]=="INOUT":
|
||||
inout_list.append(pin)
|
||||
return inout_list
|
||||
|
||||
def connect_inst(self, args, check=True):
|
||||
def connect_inst(self, args):
|
||||
"""
|
||||
Connects the pins of the last instance added
|
||||
It is preferred to use the function with the check to find if
|
||||
there is a problem. The check option can be set to false
|
||||
where we dynamically generate groups of connections after a
|
||||
group of modules are generated.
|
||||
"""
|
||||
|
||||
num_pins = len(self.insts[-1].mod.pins)
|
||||
spice_pins = list(self.insts[-1].spice_pins)
|
||||
num_pins = len(spice_pins)
|
||||
num_args = len(args)
|
||||
|
||||
# Order the arguments if the hard cell has a custom port order
|
||||
ordered_args = self.get_ordered_inputs(args)
|
||||
|
||||
if (check and num_pins != num_args):
|
||||
if (num_pins != num_args):
|
||||
if num_pins < num_args:
|
||||
mod_pins = self.insts[-1].mod.pins + [""] * (num_args - num_pins)
|
||||
mod_pins = spice_pins + [""] * (num_args - num_pins)
|
||||
arg_pins = ordered_args
|
||||
else:
|
||||
arg_pins = ordered_args + [""] * (num_pins - num_args)
|
||||
mod_pins = self.insts[-1].mod.pins
|
||||
mod_pins = spice_pins
|
||||
|
||||
modpins_string = "\n".join(["{0} -> {1}".format(arg, mod) for (arg, mod) in zip(arg_pins, mod_pins)])
|
||||
debug.error("Connection mismatch:\nInst ({0}) -> Mod ({1})\n{2}".format(num_args,
|
||||
|
|
@ -219,27 +204,17 @@ class spice():
|
|||
modpins_string),
|
||||
1)
|
||||
|
||||
self.conns.append(ordered_args)
|
||||
ordered_nets = self.create_nets(ordered_args)
|
||||
self.insts[-1].connect_spice_pins(ordered_nets)
|
||||
|
||||
# This checks if we don't have enough instance port connections for the number of insts
|
||||
if check and (len(self.insts)!=len(self.conns)):
|
||||
insts_string=pformat(self.insts)
|
||||
conns_string=pformat(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(insts_string))
|
||||
debug.error("-----")
|
||||
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
|
||||
return None
|
||||
def create_nets(self, names_list):
|
||||
nets = []
|
||||
for name in names_list:
|
||||
# setdefault adds to the dict if it doesn't find the net in it already
|
||||
# then it returns the net it found or created, a net_spice object
|
||||
net = self.nets.setdefault(name, net_spice(name, self))
|
||||
nets.append(net)
|
||||
return nets
|
||||
|
||||
def sp_read(self):
|
||||
"""
|
||||
|
|
@ -258,7 +233,7 @@ class spice():
|
|||
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
|
||||
subckt_line = list(filter(subckt.search, self.spice))[0]
|
||||
# parses line into ports and remove subckt
|
||||
self.pins = subckt_line.split(" ")[2:]
|
||||
self.add_pin_list(subckt_line.split(" ")[2:])
|
||||
else:
|
||||
debug.info(4, "no spfile {0}".format(self.sp_file))
|
||||
self.spice = []
|
||||
|
|
@ -279,10 +254,10 @@ class spice():
|
|||
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,
|
||||
debug.check(lvs_pins == list(self.pins),
|
||||
"Spice netlists for LVS and simulation have port mismatches:\n{0} (LVS {1})\nvs\n{2} (sim {3})".format(lvs_pins,
|
||||
self.lvs_file,
|
||||
self.pins,
|
||||
list(self.pins),
|
||||
self.sp_file))
|
||||
|
||||
def check_net_in_spice(self, net_name):
|
||||
|
|
@ -327,78 +302,72 @@ class spice():
|
|||
# If spice isn't defined, we dynamically generate one.
|
||||
|
||||
# recursively write the modules
|
||||
for i in self.mods:
|
||||
if self.contains(i, usedMODS):
|
||||
for mod in self.mods:
|
||||
if self.contains(mod, usedMODS):
|
||||
continue
|
||||
usedMODS.append(i)
|
||||
i.sp_write_file(sp, usedMODS, lvs, trim)
|
||||
usedMODS.append(mod)
|
||||
mod.sp_write_file(sp, usedMODS, lvs, trim)
|
||||
|
||||
if len(self.insts) == 0:
|
||||
return
|
||||
if self.pins == []:
|
||||
if len(self.pins) == 0:
|
||||
return
|
||||
|
||||
# write out the first spice line (the subcircuit)
|
||||
wrapped_pins = "\n+ ".join(tr.wrap(" ".join(self.pins)))
|
||||
wrapped_pins = "\n+ ".join(tr.wrap(" ".join(list(self.pins))))
|
||||
sp.write("\n.SUBCKT {0}\n+ {1}\n".format(self.cell_name,
|
||||
wrapped_pins))
|
||||
|
||||
# write a PININFO line
|
||||
if False:
|
||||
pin_info = "*.PININFO"
|
||||
for pin in self.pins:
|
||||
if self.pin_type[pin] == "INPUT":
|
||||
pin_info += " {0}:I".format(pin)
|
||||
elif self.pin_type[pin] == "OUTPUT":
|
||||
pin_info += " {0}:O".format(pin)
|
||||
else:
|
||||
pin_info += " {0}:B".format(pin)
|
||||
sp.write(pin_info + "\n")
|
||||
|
||||
# Also write pins as comments
|
||||
for pin in self.pins:
|
||||
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin]))
|
||||
for pin in self.pins.values():
|
||||
sp.write("* {1:6}: {0} \n".format(pin.name, pin.type))
|
||||
|
||||
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):
|
||||
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.cell_name,
|
||||
len(self.insts),
|
||||
len(self.conns)))
|
||||
debug.error("Instances: \n" + str(self.insts))
|
||||
debug.error("-----")
|
||||
debug.error("Connections: \n" + str(self.conns), 1)
|
||||
# every instance must be connected with the connect_inst function
|
||||
# TODO: may run into empty pin lists edge case, not sure yet
|
||||
connected = True
|
||||
for inst in self.insts:
|
||||
if inst.connected:
|
||||
continue
|
||||
debug.error("Instance {} spice pins not connected".format(str(inst)))
|
||||
connected = False
|
||||
debug.check(connected, "{0} : Not all instance spice pins are connected.".format(self.cell_name))
|
||||
|
||||
for i in range(len(self.insts)):
|
||||
for inst in self.insts:
|
||||
# we don't need to output connections of empty instances.
|
||||
# these are wires and paths
|
||||
if self.conns[i] == []:
|
||||
if len(inst.spice_pins) == 0:
|
||||
continue
|
||||
|
||||
# Instance with no devices in it needs no subckt/instance
|
||||
if self.insts[i].mod.no_instances:
|
||||
if inst.mod.no_instances:
|
||||
continue
|
||||
|
||||
# If this is a trimmed netlist, skip it by adding comment char
|
||||
if trim and self.insts[i].name in self.trim_insts:
|
||||
if trim and inst.name in self.trim_insts:
|
||||
sp.write("* ")
|
||||
|
||||
if lvs 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])))
|
||||
if lvs and hasattr(inst.mod, "lvs_device"):
|
||||
sp.write(inst.mod.lvs_device.format(inst.name,
|
||||
" ".join(inst.get_connections())))
|
||||
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])))
|
||||
elif hasattr(inst.mod, "spice_device"):
|
||||
sp.write(inst.mod.spice_device.format(inst.name,
|
||||
" ".join(inst.get_connections())))
|
||||
sp.write("\n")
|
||||
else:
|
||||
wrapped_connections = "\n+ ".join(tr.wrap(" ".join(self.conns[i])))
|
||||
|
||||
sp.write("X{0}\n+ {1}\n+ {2}\n".format(self.insts[i].name,
|
||||
wrapped_connections,
|
||||
self.insts[i].mod.cell_name))
|
||||
if trim and inst.name in self.trim_insts:
|
||||
wrapped_connections = "\n*+ ".join(tr.wrap(" ".join(inst.get_connections())))
|
||||
sp.write("X{0}\n*+ {1}\n*+ {2}\n".format(inst.name,
|
||||
wrapped_connections,
|
||||
inst.mod.cell_name))
|
||||
else:
|
||||
wrapped_connections = "\n+ ".join(tr.wrap(" ".join(inst.get_connections())))
|
||||
sp.write("X{0}\n+ {1}\n+ {2}\n".format(inst.name,
|
||||
wrapped_connections,
|
||||
inst.mod.cell_name))
|
||||
|
||||
sp.write(".ENDS {0}\n".format(self.cell_name))
|
||||
|
||||
|
|
@ -727,6 +696,12 @@ class spice():
|
|||
aliases.append(net)
|
||||
return aliases
|
||||
|
||||
def get_instance_connections(self):
|
||||
conns = []
|
||||
for inst in self.insts:
|
||||
conns.append(inst.get_connections())
|
||||
return conns
|
||||
|
||||
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).
|
||||
|
|
@ -739,7 +714,7 @@ class spice():
|
|||
return True
|
||||
# Check connections of all other subinsts
|
||||
mod_set = set()
|
||||
for subinst, inst_conns in zip(self.insts, self.conns):
|
||||
for subinst, inst_conns in zip(self.insts, self.get_instance_connections()):
|
||||
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
||||
if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod):
|
||||
return True
|
||||
|
|
@ -756,3 +731,149 @@ class spice():
|
|||
return self == mod and \
|
||||
child_net.lower() == alias_net.lower() and \
|
||||
parent_net.lower() == alias_net.lower()
|
||||
|
||||
|
||||
class pin_spice():
|
||||
"""
|
||||
A class to represent a spice netlist pin.
|
||||
mod is the parent module that created this pin.
|
||||
mod_net is the net object of this pin's parent module. It must have the same name as the pin.
|
||||
inst is the instance this pin is a part of, if any.
|
||||
inst_net is the net object from mod's nets which connects to this pin.
|
||||
"""
|
||||
|
||||
valid_pin_types = ["INOUT", "INPUT", "OUTPUT", "POWER", "GROUND", "BIAS"]
|
||||
|
||||
def __init__(self, name, type, mod):
|
||||
self.name = name
|
||||
self.set_type(type)
|
||||
self.mod = mod
|
||||
self.mod_net = None
|
||||
self.inst = None
|
||||
self.inst_net = None
|
||||
|
||||
# TODO: evaluate if this makes sense... and works
|
||||
self._hash = hash(self.name)
|
||||
|
||||
def set_type(self, type):
|
||||
debug.check(type in pin_spice.valid_pin_types,
|
||||
"Invalid pin type for {0}: {1}".format(self.name, type))
|
||||
self.type = type
|
||||
|
||||
def set_mod_net(self, net):
|
||||
debug.check(isinstance(net, net_spice), "net must be a net_spice object")
|
||||
debug.check(net.name == self.name, "module spice net must have same name as spice pin")
|
||||
self.mod_net = net
|
||||
|
||||
def set_inst(self, inst):
|
||||
self.inst = inst
|
||||
|
||||
def set_inst_net(self, net):
|
||||
if self.inst_net is not None:
|
||||
debug.error("pin {} is already connected to net {}\
|
||||
so it cannot also be connected to net {}\
|
||||
".format(self.name, self.inst_net.name, net.name), 1)
|
||||
debug.check(isinstance(net, net_spice), "net must be a net_spice object")
|
||||
self.inst_net = net
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "(pin_name={} type={})".format(self.name, self.type)
|
||||
|
||||
def __repr__(self):
|
||||
""" override repr function output """
|
||||
return self.name
|
||||
|
||||
def __eq__(self, name):
|
||||
return (name == self.name) if isinstance(name, str) else super().__eq__(name)
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Implement the hash function for sets etc.
|
||||
Only hash name since spice does not allow two pins to share a name.
|
||||
Provides a speedup if pin_spice is used as a key for dicts.
|
||||
"""
|
||||
return self._hash
|
||||
|
||||
def __deepcopy__(original, memo):
|
||||
"""
|
||||
This function is defined so that instances of modules can make deep
|
||||
copies of their parent module's pins dictionary. It is only expected
|
||||
to be called by the instance class __init__ function. Mod and mod_net
|
||||
should not be deep copies but references to the existing mod and net
|
||||
objects they refer to in the original. If inst is already defined this
|
||||
function will throw an error because that means it was called on a pin
|
||||
from an instance, which is not defined behavior.
|
||||
"""
|
||||
debug.check(original.inst is None,
|
||||
"cannot make a deepcopy of a spice pin from an inst")
|
||||
pin = pin_spice(original.name, original.type, original.mod)
|
||||
if original.mod_net is not None:
|
||||
pin.set_mod_net(original.mod_net)
|
||||
return pin
|
||||
|
||||
|
||||
class net_spice():
|
||||
"""
|
||||
A class to represent a spice net.
|
||||
mod is the parent module that created this net.
|
||||
pins are all the pins connected to this net.
|
||||
inst is the instance this net is a part of, if any.
|
||||
"""
|
||||
|
||||
def __init__(self, name, mod):
|
||||
self.name = name
|
||||
self.pins = []
|
||||
self.mod = mod
|
||||
self.inst = None
|
||||
|
||||
# TODO: evaluate if this makes sense... and works
|
||||
self._hash = hash(self.name)
|
||||
|
||||
def connect_pin(self, pin):
|
||||
debug.check(isinstance(pin, pin_spice), "pin must be a pin_spice object")
|
||||
if pin in self.pins:
|
||||
debug.warning("pin {} was already connected to net {} ... why was it connected again?".format(pin.name, self.name))
|
||||
else:
|
||||
self.pins.append(pin)
|
||||
|
||||
def set_inst(self, inst):
|
||||
self.inst = inst
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "(net_name={} type={})".format(self.name, self.type)
|
||||
|
||||
def __repr__(self):
|
||||
""" override repr function output """
|
||||
return self.name
|
||||
|
||||
def __eq__(self, name):
|
||||
return (name == self.name) if isinstance(name, str) else super().__eq__(name)
|
||||
|
||||
def __hash__(self):
|
||||
"""
|
||||
Implement the hash function for sets etc.
|
||||
Only hash name since spice does not allow two nets to share a name
|
||||
(on the same level of hierarchy, or rather they will be the same net).
|
||||
Provides a speedup if net_spice is used as a key for dicts.
|
||||
"""
|
||||
return self._hash
|
||||
|
||||
def __deepcopy__(original, memo):
|
||||
"""
|
||||
This function is defined so that instances of modules can make deep
|
||||
copies of their parent module's nets dictionary. It is only expected
|
||||
to be called by the instance class __init__ function. Mod
|
||||
should not be a deep copy but a reference to the existing mod
|
||||
object it refers to in the original. If inst is already defined this
|
||||
function will throw an error because that means it was called on a net
|
||||
from an instance, which is not defined behavior.
|
||||
"""
|
||||
debug.check(original.inst is None,
|
||||
"cannot make a deepcopy of a spice net from an inst")
|
||||
net = net_spice(original.name, original.mod)
|
||||
if original.pins != []:
|
||||
# TODO: honestly I'm not sure if this is right but we'll see...
|
||||
net.pins = original.pins
|
||||
return net
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class lef:
|
|||
"""
|
||||
SRAM LEF Class open GDS file, read pins information, obstruction
|
||||
and write them to LEF file.
|
||||
This is inherited by the sram_base class.
|
||||
This is inherited by the sram_1bank class.
|
||||
"""
|
||||
def __init__(self, layers):
|
||||
# LEF db units per micron
|
||||
|
|
|
|||
|
|
@ -19,11 +19,12 @@ from .simulation import *
|
|||
from .measurements import *
|
||||
from .model_check import *
|
||||
from .analytical_util import *
|
||||
from .fake_sram import *
|
||||
|
||||
debug.info(1, "Initializing characterizer...")
|
||||
OPTS.spice_exe = ""
|
||||
|
||||
if not OPTS.analytical_delay:
|
||||
if not OPTS.analytical_delay or OPTS.top_process in ["memfunc", "memchar"]:
|
||||
if OPTS.spice_name:
|
||||
# Capitalize Xyce
|
||||
if OPTS.spice_name == "xyce":
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#
|
||||
import os
|
||||
import re
|
||||
from enum import Enum
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
|
@ -107,3 +108,33 @@ def check_dict_values_is_float(dict):
|
|||
if type(value)!=float:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def bidir_search(func, upper, lower, time_out=9):
|
||||
"""
|
||||
Performs bidirectional search over given function with given
|
||||
upper and lower bounds.
|
||||
"""
|
||||
time_count = 0
|
||||
while time_count < time_out:
|
||||
val = (upper + lower) / 2
|
||||
if func(val):
|
||||
return (True, val)
|
||||
time_count += 1
|
||||
return (False, 0)
|
||||
|
||||
|
||||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
|
||||
|
||||
class sram_op(Enum):
|
||||
READ_ZERO = 0
|
||||
READ_ONE = 1
|
||||
WRITE_ZERO = 2
|
||||
WRITE_ONE = 3
|
||||
DISABLED_READ_ZERO = 4
|
||||
DISABLED_READ_ONE = 5
|
||||
DISABLED_WRITE_ZERO = 6
|
||||
DISABLED_WRITE_ONE = 7
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ from openram import OPTS
|
|||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
from .sram_op import *
|
||||
from .bit_polarity import *
|
||||
from .simulation import simulation
|
||||
from .measurements import *
|
||||
from os import path
|
||||
import re
|
||||
|
||||
|
||||
class delay(simulation):
|
||||
|
|
@ -37,7 +37,7 @@ class delay(simulation):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
def __init__(self, sram, spfile, corner, output_path=None):
|
||||
super().__init__(sram, spfile, corner)
|
||||
|
||||
self.targ_read_ports = []
|
||||
|
|
@ -47,10 +47,17 @@ class delay(simulation):
|
|||
self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
if output_path is None:
|
||||
self.output_path = OPTS.openram_temp
|
||||
else:
|
||||
self.output_path = output_path
|
||||
|
||||
self.set_load_slew(0, 0)
|
||||
self.set_corner(corner)
|
||||
self.create_signal_names()
|
||||
self.add_graph_exclusions()
|
||||
self.meas_id = 0
|
||||
|
||||
def create_measurement_objects(self):
|
||||
""" Create the measurements used for read and write ports """
|
||||
|
|
@ -79,10 +86,12 @@ class delay(simulation):
|
|||
self.clk_frmt = "clk{0}" # Unformatted clock name
|
||||
targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
|
||||
self.delay_meas = []
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9))
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "FALL", "RISE", measure_scale=1e9))
|
||||
self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
|
||||
self.delay_meas[-1].meta_add_delay = False
|
||||
self.delay_meas.append(delay_measure("delay_hl", self.clk_frmt, targ_name, "FALL", "FALL", measure_scale=1e9))
|
||||
self.delay_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
self.delay_meas[-1].meta_add_delay = False
|
||||
self.read_lib_meas+=self.delay_meas
|
||||
|
||||
self.slew_meas = []
|
||||
|
|
@ -103,9 +112,10 @@ class delay(simulation):
|
|||
self.read_lib_meas[-1].meta_str = "disabled_read0"
|
||||
|
||||
# This will later add a half-period to the spice time delay. Only for reading 0.
|
||||
for obj in self.read_lib_meas:
|
||||
if obj.meta_str is sram_op.READ_ZERO:
|
||||
obj.meta_add_delay = True
|
||||
# FIXME: Removed this to check, see if it affects anything
|
||||
#for obj in self.read_lib_meas:
|
||||
# if obj.meta_str is sram_op.READ_ZERO:
|
||||
# obj.meta_add_delay = True
|
||||
|
||||
read_measures = []
|
||||
read_measures.append(self.read_lib_meas)
|
||||
|
|
@ -113,7 +123,9 @@ class delay(simulation):
|
|||
read_measures.append(self.create_bitline_measurement_objects())
|
||||
read_measures.append(self.create_debug_measurement_objects())
|
||||
read_measures.append(self.create_read_bit_measures())
|
||||
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||
# TODO: Maybe don't do this here (?)
|
||||
if OPTS.top_process != "memchar":
|
||||
read_measures.append(self.create_sen_and_bitline_path_measures())
|
||||
|
||||
return read_measures
|
||||
|
||||
|
|
@ -158,6 +170,7 @@ class delay(simulation):
|
|||
write_measures = []
|
||||
write_measures.append(self.write_lib_meas)
|
||||
write_measures.append(self.create_write_bit_measures())
|
||||
|
||||
return write_measures
|
||||
|
||||
def create_debug_measurement_objects(self):
|
||||
|
|
@ -216,8 +229,12 @@ class delay(simulation):
|
|||
|
||||
bit_col = self.get_data_bit_column_number(probe_address, probe_data)
|
||||
bit_row = self.get_address_row_number(probe_address)
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
if OPTS.top_process == "memchar":
|
||||
cell_name = self.cell_name.format(bit_row, bit_col)
|
||||
storage_names = ("Q", "Q_bar")
|
||||
else:
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
|
|
@ -239,8 +256,8 @@ class delay(simulation):
|
|||
def create_sen_and_bitline_path_measures(self):
|
||||
"""Create measurements for the s_en and bitline paths for individual delays per stage."""
|
||||
|
||||
# FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||
# but is never mentioned otherwise
|
||||
# # FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||
# # but is never mentioned otherwise
|
||||
port = self.read_ports[0]
|
||||
sen_and_port = self.sen_name + str(port)
|
||||
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
|
||||
|
|
@ -255,8 +272,8 @@ class delay(simulation):
|
|||
bitline_path = bl_paths[0]
|
||||
|
||||
# Get the measures
|
||||
self.sen_path_meas = self.create_delay_path_measures(sen_path)
|
||||
self.bl_path_meas = self.create_delay_path_measures(bitline_path)
|
||||
self.sen_path_meas = self.create_delay_path_measures(sen_path, "sen")
|
||||
self.bl_path_meas = self.create_delay_path_measures(bitline_path, "bl")
|
||||
all_meas = self.sen_path_meas + self.bl_path_meas
|
||||
|
||||
# Paths could have duplicate measurements, remove them before they go to the stim file
|
||||
|
|
@ -278,7 +295,7 @@ class delay(simulation):
|
|||
|
||||
return unique_measures
|
||||
|
||||
def create_delay_path_measures(self, path):
|
||||
def create_delay_path_measures(self, path, process):
|
||||
"""Creates measurements for each net along given path."""
|
||||
|
||||
# Determine the directions (RISE/FALL) of signals
|
||||
|
|
@ -290,6 +307,8 @@ class delay(simulation):
|
|||
cur_net, next_net = path[i], path[i + 1]
|
||||
cur_dir, next_dir = path_dirs[i], path_dirs[i + 1]
|
||||
meas_name = "delay_{0}_to_{1}".format(cur_net, next_net)
|
||||
meas_name += "_" + process + "_id" + str(self.meas_id)
|
||||
self.meas_id += 1
|
||||
if i + 1 != len(path) - 1:
|
||||
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False))
|
||||
else: # Make the last measurement always measure on FALL because is a read 0
|
||||
|
|
@ -368,7 +387,7 @@ class delay(simulation):
|
|||
|
||||
self.sf.write("\n* SRAM output loads\n")
|
||||
for port in self.read_ports:
|
||||
for i in range(self.word_size):
|
||||
for i in range(self.word_size + self.num_spare_cols):
|
||||
self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port, i, self.dout_name, self.load))
|
||||
|
||||
def write_delay_stimulus(self):
|
||||
|
|
@ -385,15 +404,20 @@ class delay(simulation):
|
|||
|
||||
# creates and opens stimulus file for writing
|
||||
self.delay_stim_sp = "delay_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.delay_stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.delay_stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
|
||||
# creates and opens measure file for writing
|
||||
self.delay_meas_sp = "delay_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.delay_meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
|
||||
if OPTS.spice_name == "spectre":
|
||||
self.sf.write("simulator lang=spice\n")
|
||||
self.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
|
||||
self.load,
|
||||
self.slew))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
# include files in stimulus file
|
||||
self.stim.write_include(self.trim_sp_file)
|
||||
|
||||
|
|
@ -418,6 +442,7 @@ class delay(simulation):
|
|||
t_rise=self.slew,
|
||||
t_fall=self.slew)
|
||||
|
||||
self.sf.write(".include {0}".format(temp_meas))
|
||||
# self.load_all_measure_nets()
|
||||
self.write_delay_measures()
|
||||
# self.write_simulation_saves()
|
||||
|
|
@ -426,6 +451,7 @@ class delay(simulation):
|
|||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def write_power_stimulus(self, trim):
|
||||
""" Creates a stimulus file to measure leakage power only.
|
||||
|
|
@ -435,10 +461,15 @@ class delay(simulation):
|
|||
|
||||
# creates and opens stimulus file for writing
|
||||
self.power_stim_sp = "power_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.power_stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.power_stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Power stimulus for period of {0}n\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
# creates and opens measure file for writing
|
||||
self.power_meas_sp = "power_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.power_meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
# include UNTRIMMED files in stimulus file
|
||||
if trim:
|
||||
|
|
@ -451,7 +482,7 @@ class delay(simulation):
|
|||
# generate data and addr signals
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
for write_port in self.write_ports:
|
||||
for i in range(self.word_size):
|
||||
for i in range(self.word_size + self.num_spare_cols):
|
||||
self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i),
|
||||
v_val=0)
|
||||
for port in self.all_ports:
|
||||
|
|
@ -470,12 +501,14 @@ class delay(simulation):
|
|||
for port in self.all_ports:
|
||||
self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0)
|
||||
|
||||
self.sf.write(".include {}".format(temp_meas))
|
||||
self.write_power_measures()
|
||||
|
||||
# run until the end of the cycle time
|
||||
self.stim.write_control(2 * self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def get_measure_variants(self, port, measure_obj, measure_type=None):
|
||||
"""
|
||||
|
|
@ -587,15 +620,15 @@ class delay(simulation):
|
|||
# Output some comments to aid where cycles start and
|
||||
# what is happening
|
||||
for comment in self.cycle_comments:
|
||||
self.sf.write("* {0}\n".format(comment))
|
||||
self.mf.write("* {0}\n".format(comment))
|
||||
|
||||
self.sf.write("\n")
|
||||
for read_port in self.targ_read_ports:
|
||||
self.sf.write("* Read ports {0}\n".format(read_port))
|
||||
self.mf.write("* Read ports {0}\n".format(read_port))
|
||||
self.write_delay_measures_read_port(read_port)
|
||||
|
||||
for write_port in self.targ_write_ports:
|
||||
self.sf.write("* Write ports {0}\n".format(write_port))
|
||||
self.mf.write("* Write ports {0}\n".format(write_port))
|
||||
self.write_delay_measures_write_port(write_port)
|
||||
|
||||
def load_pex_net(self, net: str):
|
||||
|
|
@ -605,8 +638,8 @@ class delay(simulation):
|
|||
return net
|
||||
original_net = net
|
||||
net = net[len(prefix):]
|
||||
net = net.replace(".", "_").replace("[", "\[").replace("]", "\]")
|
||||
for pattern in ["\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
|
||||
net = net.replace(".", "_").replace("[", r"\[").replace("]", r"\]")
|
||||
for pattern in [r"\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
|
||||
try:
|
||||
match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file])
|
||||
return prefix + match.decode().strip()
|
||||
|
|
@ -616,8 +649,8 @@ class delay(simulation):
|
|||
|
||||
def load_all_measure_nets(self):
|
||||
measurement_nets = set()
|
||||
for port, meas in zip(self.targ_read_ports * len(self.read_meas_lists) +
|
||||
self.targ_write_ports * len(self.write_meas_lists),
|
||||
for port, meas in zip(self.targ_read_ports * len(self.read_meas_lists)
|
||||
+ self.targ_write_ports * len(self.write_meas_lists),
|
||||
self.read_meas_lists + self.write_meas_lists):
|
||||
for measurement in meas:
|
||||
visited = getattr(measurement, 'pex_visited', False)
|
||||
|
|
@ -684,6 +717,8 @@ class delay(simulation):
|
|||
self.sf.write("\n* Measure statements for idle leakage power\n")
|
||||
|
||||
# add measure statements for power
|
||||
# TODO: Convert to measure statement insted of using stimuli
|
||||
# measure = power_measure('leakage_power',
|
||||
t_initial = self.period
|
||||
t_final = 2 * self.period
|
||||
self.stim.gen_meas_power(meas_name="leakage_power",
|
||||
|
|
@ -791,7 +826,7 @@ class delay(simulation):
|
|||
|
||||
for port in self.targ_write_ports:
|
||||
if not self.check_bit_measures(self.write_bit_meas, port):
|
||||
return(False, {})
|
||||
return (False, {})
|
||||
|
||||
debug.info(2, "Checking write values for port {0}".format(port))
|
||||
write_port_dict = {}
|
||||
|
|
@ -805,7 +840,7 @@ class delay(simulation):
|
|||
for port in self.targ_read_ports:
|
||||
# First, check that the memory has the right values at the right times
|
||||
if not self.check_bit_measures(self.read_bit_meas, port):
|
||||
return(False, {})
|
||||
return (False, {})
|
||||
|
||||
debug.info(2, "Checking read delay values for port {0}".format(port))
|
||||
# Check sen timing, then bitlines, then general measurements.
|
||||
|
|
@ -828,7 +863,8 @@ class delay(simulation):
|
|||
|
||||
result[port].update(read_port_dict)
|
||||
|
||||
self.path_delays = self.check_path_measures()
|
||||
if self.sen_path_meas and self.bl_path_meas:
|
||||
self.path_delays = self.check_path_measures()
|
||||
|
||||
return (True, result)
|
||||
|
||||
|
|
@ -919,7 +955,7 @@ class delay(simulation):
|
|||
def check_bitline_meas(self, v_discharged_bl, v_charged_bl):
|
||||
"""
|
||||
Checks the value of the discharging bitline. Confirms s_en timing errors.
|
||||
Returns true if the bitlines are at there expected value.
|
||||
Returns true if the bitlines are at there their value.
|
||||
"""
|
||||
# The inputs looks at discharge/charged bitline rather than left or right (bl/br)
|
||||
# Performs two checks, discharging bitline is at least 10% away from vdd and there is a
|
||||
|
|
@ -942,7 +978,7 @@ class delay(simulation):
|
|||
if type(val) != float or val > self.period / 2:
|
||||
debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
|
||||
value_dict[meas.name] = val
|
||||
#debug.info(0, "value_dict={}".format(value_dict))
|
||||
# debug.info(0, "value_dict={}".format(value_dict))
|
||||
return value_dict
|
||||
|
||||
def run_power_simulation(self):
|
||||
|
|
@ -992,8 +1028,8 @@ class delay(simulation):
|
|||
slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl, slew_lh)
|
||||
# high-to-low delays start at neg. clk edge, so they need to be less than half_period
|
||||
half_period = self.period / 2
|
||||
if abs(delay_hl)>half_period or abs(delay_lh)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \
|
||||
or delay_hl<0 or delay_lh<0 or slew_hl<0 or slew_lh<0:
|
||||
if abs(delay_hl)>half_period or abs(delay_lh)>half_period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \
|
||||
or (delay_hl<0 and delay_lh<0) or slew_hl<0 or slew_lh<0:
|
||||
debug.info(2, "UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str,
|
||||
delays_str,
|
||||
slews_str))
|
||||
|
|
@ -1003,6 +1039,13 @@ class delay(simulation):
|
|||
delays_str,
|
||||
slews_str))
|
||||
|
||||
if delay_lh < 0 and delay_hl > 0:
|
||||
result_dict["delay_lh"] = result_dict["delay_hl"]
|
||||
debug.info(2, "delay_lh captured precharge, using delay_hl instead")
|
||||
elif delay_hl < 0 and delay_lh > 0:
|
||||
result_dict["delay_hl"] = result_dict["delay_lh"]
|
||||
debug.info(2, "delay_hl captured precharge, using delay_lh instead")
|
||||
|
||||
return True
|
||||
|
||||
def find_min_period(self, feasible_delays):
|
||||
|
|
@ -1108,32 +1151,143 @@ class delay(simulation):
|
|||
Netlist reduced for simulation.
|
||||
"""
|
||||
super().set_probe(probe_address, probe_data)
|
||||
self.prepare_netlist()
|
||||
|
||||
def prepare_netlist(self):
|
||||
""" Prepare a trimmed netlist and regular netlist. """
|
||||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
# TODO: Copy old netlist if memchar
|
||||
if OPTS.trim_netlist:
|
||||
#self.trim_sp_file = "{0}trimmed.sp".format(self.output_path)
|
||||
self.trim_sp_file = "{0}trimmed.sp".format(OPTS.openram_temp)
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
# Only genrate spice when running openram process
|
||||
if OPTS.top_process != "memchar":
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
self.trim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
self.trim_sp_file = "{0}sram.sp".format(self.output_path)
|
||||
|
||||
# The non-reduced netlist file for power simulation
|
||||
self.sim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
self.sim_sp_file = "{0}sram.sp".format(self.output_path)
|
||||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
if self.sp_file != self.sim_sp_file:
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
def recover_measurment_objects(self):
|
||||
mf_path = path.join(OPTS.output_path, "delay_meas.sp")
|
||||
self.sen_path_meas = None
|
||||
self.bl_path_meas = None
|
||||
if not path.exists(mf_path):
|
||||
debug.info(1, "Delay measure file not found. Skipping measure recovery")
|
||||
return
|
||||
mf = open(mf_path, "r")
|
||||
measure_text = mf.read()
|
||||
port_iter = re.finditer(r"\* (Read|Write) ports (\d*)", measure_text)
|
||||
port_measure_lines = []
|
||||
loc = 0
|
||||
port_name = ''
|
||||
for port in port_iter:
|
||||
port_measure_lines.append((port_name, measure_text[loc:port.end(0)]))
|
||||
loc = port.start(0)
|
||||
port_name = port.group(1) + port.group(2)
|
||||
|
||||
mf.close()
|
||||
# Cycle comments, not sure if i need this
|
||||
# cycle_lines = port_measure_lines.pop(0)[1]
|
||||
# For now just recover the bit_measures and sen_and_bitline_path_measures
|
||||
self.read_meas_lists.append([])
|
||||
self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
# bit_measure_rule = re.compile(r"\.meas tran (v_q_a\d+_b\d+_(read|write)_(zero|one)\d+) FIND v\((.*)\) AT=(\d+(\.\d+)?)n")
|
||||
# for measures in port_measure_lines:
|
||||
# port_name = measures[0]
|
||||
# text = measures[1]
|
||||
# bit_measure_iter = bit_measure_rule.finditer(text)
|
||||
# for bit_measure in bit_measure_iter:
|
||||
# meas_name = bit_measure.group(1)
|
||||
# read = bit_measure.group(2) == "read"
|
||||
# cycle = bit_measure.group(3)
|
||||
# probe = bit_measure.group(4)
|
||||
# polarity = bit_polarity.NONINVERTING
|
||||
# if "q_bar" in meas_name:
|
||||
# polarity = bit_polarity.INVERTING
|
||||
# meas = voltage_at_measure(meas_name, probe)
|
||||
# if read:
|
||||
# if cycle == "one":
|
||||
# meas.meta_str = sram_op.READ_ONE
|
||||
# else:
|
||||
# meas.meta_str = sram_op.READ_ZERO
|
||||
# self.read_bit_meas[polarity].append(meas)
|
||||
# self.read_meas_lists[-1].append(meas)
|
||||
# else:
|
||||
# if cycle == "one":
|
||||
# meas.meta_str = sram_op.WRITE_ONE
|
||||
# else:
|
||||
# meas.meta_str = sram_op.WRITE_ZERO
|
||||
# self.write_bit_meas[polarity].append(meas)
|
||||
# self.write_meas_lists[-1].append(meas)
|
||||
|
||||
delay_path_rule = re.compile(r"\.meas tran delay_(.*)_to_(.*)_(sen|bl)_(id\d*) TRIG v\((.*)\) VAL=(\d+(\.\d+)?) (RISE|FALL)=(\d+) TD=(\d+(\.\d+)?)n TARG v\((.*)\) VAL=(\d+(\.\d+)?) (RISE|FALL)=(\d+) TD=(\d+(\.\d+)?)n")
|
||||
port = self.read_ports[0]
|
||||
meas_buff = []
|
||||
self.sen_path_meas = []
|
||||
self.bl_path_meas = []
|
||||
for measures in port_measure_lines:
|
||||
text = measures[1]
|
||||
delay_path_iter = delay_path_rule.finditer(text)
|
||||
for delay_path_measure in delay_path_iter:
|
||||
from_ = delay_path_measure.group(1)
|
||||
to_ = delay_path_measure.group(2)
|
||||
path_ = delay_path_measure.group(3)
|
||||
id_ = delay_path_measure.group(4)
|
||||
trig_rise = delay_path_measure.group(8)
|
||||
targ_rise = delay_path_measure.group(15)
|
||||
meas_name = "delay_{0}_to_{1}_{2}_{3}".format(from_, to_, path_, id_)
|
||||
meas = delay_measure(meas_name, from_, to_, trig_rise, targ_rise, measure_scale=1e9, has_port=False)
|
||||
meas.meta_str = sram_op.READ_ZERO
|
||||
meas.meta_add_delay = True
|
||||
meas_buff.append(meas)
|
||||
if path_ == "sen":
|
||||
self.sen_path_meas.extend(meas_buff.copy())
|
||||
meas_buff.clear()
|
||||
elif path_ == "bl":
|
||||
self.bl_path_meas.extend(meas_buff.copy())
|
||||
meas_buff.clear()
|
||||
self.read_meas_lists.append(self.sen_path_meas + self.bl_path_meas)
|
||||
|
||||
def set_spice_names(self):
|
||||
"""This is run in place of set_internal_spice_names function from
|
||||
simulation.py when running stand-alone characterizer."""
|
||||
self.bl_name = OPTS.bl_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col=self.bitline_column)
|
||||
self.br_name = OPTS.br_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col=self.bitline_column)
|
||||
self.sen_name = OPTS.sen_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator)
|
||||
self.cell_name = OPTS.cell_format.format(name=self.sram.name,
|
||||
hier_sep=OPTS.hier_seperator,
|
||||
row="{}",
|
||||
col="{}")
|
||||
|
||||
def analysis_init(self, probe_address, probe_data):
|
||||
"""Sets values which are dependent on the data address/bit being tested."""
|
||||
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
self.prepare_netlist()
|
||||
if OPTS.top_process == "memchar":
|
||||
self.set_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
self.recover_measurment_objects()
|
||||
else:
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
|
||||
def analyze(self, probe_address, probe_data, load_slews):
|
||||
"""
|
||||
|
|
@ -1145,7 +1299,7 @@ class delay(simulation):
|
|||
self.analysis_init(probe_address, probe_data)
|
||||
loads = []
|
||||
slews = []
|
||||
for load,slew in load_slews:
|
||||
for load, slew in load_slews:
|
||||
loads.append(load)
|
||||
slews.append(slew)
|
||||
self.load=max(loads)
|
||||
|
|
@ -1168,15 +1322,16 @@ class delay(simulation):
|
|||
# 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
|
||||
self.period = min_period
|
||||
char_port_data = self.simulate_loads_and_slews(load_slews, leakage_offset)
|
||||
if OPTS.use_specified_load_slew != None and len(load_slews) > 1:
|
||||
if OPTS.use_specified_load_slew is not None and len(load_slews) > 1:
|
||||
debug.warning("Path delay lists not correctly generated for characterizations of more than 1 load,slew")
|
||||
# Get and save the path delays
|
||||
bl_names, bl_delays, sen_names, sen_delays = self.get_delay_lists(self.path_delays)
|
||||
if self.sen_path_meas and self.bl_path_meas:
|
||||
bl_names, bl_delays, sen_names, sen_delays = self.get_delay_lists(self.path_delays)
|
||||
# Removed from characterization output temporarily
|
||||
#char_sram_data["bl_path_measures"] = bl_delays
|
||||
#char_sram_data["sen_path_measures"] = sen_delays
|
||||
#char_sram_data["bl_path_names"] = bl_names
|
||||
#char_sram_data["sen_path_names"] = sen_names
|
||||
# char_sram_data["bl_path_measures"] = bl_delays
|
||||
# char_sram_data["sen_path_measures"] = sen_delays
|
||||
# char_sram_data["bl_path_names"] = bl_names
|
||||
# char_sram_data["sen_path_names"] = sen_names
|
||||
# FIXME: low-to-high delays are altered to be independent of the period. This makes the lib results less accurate.
|
||||
self.alter_lh_char_data(char_port_data)
|
||||
|
||||
|
|
@ -1185,7 +1340,7 @@ class delay(simulation):
|
|||
def alter_lh_char_data(self, char_port_data):
|
||||
"""Copies high-to-low data to low-to-high data to make them consistent on the same clock edge."""
|
||||
|
||||
# This is basically a hack solution which should be removed/fixed later.
|
||||
# This is basically a hack solution which should be removed/fixed later.
|
||||
for port in self.all_ports:
|
||||
char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl']
|
||||
char_port_data[port]['slew_lh'] = char_port_data[port]['slew_hl']
|
||||
|
|
@ -1194,7 +1349,6 @@ class delay(simulation):
|
|||
"""Simulate all specified output loads and input slews pairs of all ports"""
|
||||
|
||||
measure_data = self.get_empty_measure_data_dict()
|
||||
path_dict = {}
|
||||
# Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways.
|
||||
self.targ_read_ports = self.read_ports
|
||||
self.targ_write_ports = self.write_ports
|
||||
|
|
@ -1255,8 +1409,8 @@ class delay(simulation):
|
|||
inverse_address = self.calculate_inverse_address()
|
||||
|
||||
# For now, ignore data patterns and write ones or zeros
|
||||
data_ones = "1" * self.word_size
|
||||
data_zeros = "0" * self.word_size
|
||||
data_ones = "1" * (self.word_size + self.num_spare_cols)
|
||||
data_zeros = "0" * (self.word_size + self.num_spare_cols)
|
||||
wmask_ones = "1" * self.num_wmasks
|
||||
|
||||
if self.t_current == 0:
|
||||
|
|
@ -1352,9 +1506,9 @@ class delay(simulation):
|
|||
# Get any available read/write port in case only a single write or read ports is being characterized.
|
||||
cur_read_port = self.get_available_port(get_read_port=True)
|
||||
cur_write_port = self.get_available_port(get_read_port=False)
|
||||
debug.check(cur_read_port != None,
|
||||
debug.check(cur_read_port is not None,
|
||||
"Characterizer requires at least 1 read port")
|
||||
debug.check(cur_write_port != None,
|
||||
debug.check(cur_write_port is not None,
|
||||
"Characterizer requires at least 1 write port")
|
||||
|
||||
# Create test cycles for specified target ports.
|
||||
|
|
@ -1380,7 +1534,7 @@ class delay(simulation):
|
|||
""" Generates the PWL data inputs for a simulation timing test. """
|
||||
|
||||
for write_port in self.write_ports:
|
||||
for i in range(self.word_size):
|
||||
for i in range(self.word_size + self.num_spare_cols):
|
||||
sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i)
|
||||
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[write_port][i], self.period, self.slew, 0.05)
|
||||
|
||||
|
|
@ -1402,3 +1556,6 @@ class delay(simulation):
|
|||
self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||
if port in self.readwrite_ports:
|
||||
self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||
if self.sram.num_wmasks:
|
||||
for bit in range(self.sram.num_wmasks):
|
||||
self.stim.gen_pwl("WMASK{0}_{1}".format(port, bit), self.cycle_times, self.wmask_values[port][bit], self.period, self.slew, 0.05)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram import sram_config
|
||||
from math import ceil
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class fake_sram(sram_config):
|
||||
"""
|
||||
This is an SRAM class that doesn't actually create an instance.
|
||||
It will read neccessary members from HTML file from a previous run.
|
||||
"""
|
||||
def __init__(self, name, word_size, num_words, write_size=None, num_banks=1,
|
||||
words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
sram_config.__init__(self, word_size, num_words, write_size,
|
||||
num_banks, words_per_row, num_spare_rows,
|
||||
num_spare_cols)
|
||||
self.name = name
|
||||
if write_size and self.write_size != self.word_size:
|
||||
self.num_wmasks = int(ceil(self.word_size / self.write_size))
|
||||
else:
|
||||
self.num_wmasks = 0
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
Taken from ../base/design.py
|
||||
These are contants and lists that aid multiport design.
|
||||
Ports are always in the order RW, W, R.
|
||||
Port indices start from 0 and increment.
|
||||
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||
|
||||
"""
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
# These are the read/write port indices.
|
||||
self.readwrite_ports = []
|
||||
# These are the read/write and write-only port indices
|
||||
self.write_ports = []
|
||||
# These are the write-only port indices.
|
||||
self.writeonly_ports = []
|
||||
# These are the read/write and read-only port indices
|
||||
self.read_ports = []
|
||||
# These are the read-only port indices.
|
||||
self.readonly_ports = []
|
||||
# These are all the ports
|
||||
self.all_ports = list(range(total_ports))
|
||||
|
||||
# The order is always fixed as RW, W, R
|
||||
port_number = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.readwrite_ports.append(port_number)
|
||||
self.write_ports.append(port_number)
|
||||
self.read_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.write_ports.append(port_number)
|
||||
self.writeonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_r_ports):
|
||||
self.read_ports.append(port_number)
|
||||
self.readonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
|
||||
def generate_pins(self):
|
||||
self.pins = ['vdd', 'gnd']
|
||||
self.pins.extend(['clk{}'.format(port) for port in range(
|
||||
OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports)])
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.pins.extend(['din{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['dout{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.addr_size)])
|
||||
self.pins.extend(['spare_wen{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_spare_cols)])
|
||||
if self.num_wmasks != 0:
|
||||
self.pins.extend(['wmask{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_wmasks)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port), 'web{}'.format(port)])
|
||||
|
||||
start_port = OPTS.num_rw_ports
|
||||
for port in range(start_port, start_port + OPTS.num_r_ports):
|
||||
self.pins.extend(['dout{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.addr_size)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port)])
|
||||
|
||||
start_port += OPTS.num_r_ports
|
||||
for port in range(start_port, start_port + OPTS.num_w_ports):
|
||||
self.pins.extend(['din{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.word_size + self.num_spare_cols)])
|
||||
self.pins.extend(['spare_wen{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_spare_cols)])
|
||||
self.pins.extend(['addr{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.addr_size)])
|
||||
if self.num_wmasks != 0:
|
||||
self.pins.extend(['wmask{0}[{1}]'.format(port, bit)
|
||||
for bit in range(self.num_wmasks)])
|
||||
|
||||
self.pins.extend(['csb{}'.format(port), 'web{}'.format(port)])
|
||||
|
|
@ -7,13 +7,17 @@
|
|||
#
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
import collections
|
||||
from os import path
|
||||
import shutil
|
||||
from numpy import binary_repr
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
from .stimuli import *
|
||||
from .charutils import *
|
||||
from .simulation import simulation
|
||||
from .measurements import voltage_at_measure
|
||||
|
||||
|
||||
class functional(simulation):
|
||||
|
|
@ -28,17 +32,24 @@ class functional(simulation):
|
|||
# Seed the characterizer with a constant seed for unit tests
|
||||
if OPTS.is_unit_test:
|
||||
random.seed(12345)
|
||||
elif OPTS.functional_seed:
|
||||
random.seed(OPTS.functional_seed)
|
||||
else:
|
||||
seed = time.time_ns()
|
||||
random.seed(seed)
|
||||
debug.info(1, "Random seed for functional simulation: {}".format(seed))
|
||||
|
||||
|
||||
if not spfile:
|
||||
# self.sp_file is assigned in base class
|
||||
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
|
||||
# Copy sp file to temp dir
|
||||
self.temp_spice = path.join(OPTS.openram_temp, "sram.sp")
|
||||
shutil.copy(self.sp_file, self.temp_spice)
|
||||
|
||||
if not corner:
|
||||
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
|
||||
|
||||
if period:
|
||||
self.period = period
|
||||
|
||||
if not output_path:
|
||||
self.output_path = OPTS.openram_temp
|
||||
else:
|
||||
|
|
@ -73,13 +84,20 @@ class functional(simulation):
|
|||
self.set_spice_constants()
|
||||
self.set_stimulus_variables()
|
||||
|
||||
# Override default period
|
||||
if period:
|
||||
self.period = period
|
||||
|
||||
# For the debug signal names
|
||||
self.wordline_row = 0
|
||||
self.bitline_column = 0
|
||||
self.create_signal_names()
|
||||
self.add_graph_exclusions()
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
#self.add_graph_exclusions()
|
||||
#self.create_graph()
|
||||
#self.set_internal_spice_names()
|
||||
self.bl_name = "xsram:xbank0:bl_0_{}"
|
||||
self.br_name = "xsram:xbank0:br_0_{}"
|
||||
self.sen_name = "xsram:s_en"
|
||||
self.q_name, self.qbar_name = self.get_bit_name()
|
||||
debug.info(2, "q:\t\t{0}".format(self.q_name))
|
||||
debug.info(2, "qbar:\t{0}".format(self.qbar_name))
|
||||
|
|
@ -145,7 +163,6 @@ class functional(simulation):
|
|||
comment = self.gen_cycle_comment("noop", "0" * self.word_size, "0" * self.bank_addr_size, "0" * self.num_wmasks, 0, self.t_current)
|
||||
self.add_noop_all_ports(comment)
|
||||
|
||||
|
||||
# 1. Write all the write ports 2x to seed a bunch of locations.
|
||||
for i in range(3):
|
||||
for port in self.write_ports:
|
||||
|
|
@ -267,7 +284,7 @@ class functional(simulation):
|
|||
self.read_check.append([word,
|
||||
"{0}{1}".format(self.dout_name, port),
|
||||
self.t_current + self.period,
|
||||
int(self.t_current/self.period)])
|
||||
int(self.t_current / self.period)])
|
||||
|
||||
def read_stim_results(self):
|
||||
# Extract dout values from spice timing.lis
|
||||
|
|
@ -275,7 +292,8 @@ class functional(simulation):
|
|||
sp_read_value = ""
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
measure_name = "v{0}_{1}ck{2}".format(dout_port.lower(), bit, cycle)
|
||||
value = parse_spice_list("timing", measure_name)
|
||||
# value = parse_spice_list("timing", measure_name)
|
||||
value = self.measures[measure_name].retrieve_measure(port=0)
|
||||
# FIXME: Ignore the spare columns for now
|
||||
if bit >= self.word_size:
|
||||
value = 0
|
||||
|
|
@ -294,10 +312,11 @@ class functional(simulation):
|
|||
self.v_low,
|
||||
self.v_high)
|
||||
except ValueError:
|
||||
error ="FAILED: {0}_{1} value {2} at time {3}n is not a float.".format(dout_port,
|
||||
error ="FAILED: {0}_{1} value {2} at time {3}n is not a float. Measure: {4}".format(dout_port,
|
||||
bit,
|
||||
value,
|
||||
eo_period)
|
||||
eo_period,
|
||||
measure_name)
|
||||
|
||||
return (0, error)
|
||||
self.read_results.append([sp_read_value, dout_port, eo_period, cycle])
|
||||
|
|
@ -318,8 +337,8 @@ class functional(simulation):
|
|||
cycle,
|
||||
self.read_results[i][2],
|
||||
check_name)
|
||||
return(0, error)
|
||||
return(1, "SUCCESS")
|
||||
return (0, error)
|
||||
return (1, "SUCCESS")
|
||||
|
||||
def gen_wmask(self):
|
||||
wmask = ""
|
||||
|
|
@ -347,7 +366,7 @@ class functional(simulation):
|
|||
def gen_data(self):
|
||||
""" Generates a random word to write. """
|
||||
# Don't use 0 or max value
|
||||
random_value = random.randint(1, self.max_data - 1)
|
||||
random_value = random.randint(1, self.max_data)
|
||||
data_bits = binary_repr(random_value, self.word_size)
|
||||
if self.num_spare_cols>0:
|
||||
random_value = random.randint(0, self.max_col_data)
|
||||
|
|
@ -380,13 +399,16 @@ class functional(simulation):
|
|||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
self.stim_sp = "functional_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp)
|
||||
temp_stim = path.join(self.output_path, self.stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
self.meas_sp = "functional_meas.sp"
|
||||
temp_meas = path.join(self.output_path, self.meas_sp)
|
||||
self.mf = open(temp_meas, "w")
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
# Write include statements
|
||||
self.stim.write_include(self.sp_file)
|
||||
self.stim.write_include(self.temp_spice)
|
||||
|
||||
# Write Vdd/Gnd statements
|
||||
self.sf.write("\n* Global Power Supplies\n")
|
||||
|
|
@ -470,6 +492,7 @@ class functional(simulation):
|
|||
|
||||
# Generate dout value measurements
|
||||
self.sf.write("\n * Generation of dout measurements\n")
|
||||
self.measures = {}
|
||||
|
||||
for (word, dout_port, eo_period, cycle) in self.read_check:
|
||||
t_initial = eo_period
|
||||
|
|
@ -477,28 +500,37 @@ class functional(simulation):
|
|||
num_bits = self.word_size + self.num_spare_cols
|
||||
for bit in range(num_bits):
|
||||
signal_name = "{0}_{1}".format(dout_port, bit)
|
||||
measure_name = "V{0}ck{1}".format(signal_name, cycle)
|
||||
measure_name = "v{0}ck{1}".format(signal_name, cycle)
|
||||
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
|
||||
|
||||
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
|
||||
measure_name,
|
||||
voltage_value,
|
||||
eo_period))
|
||||
self.stim.gen_meas_value(meas_name=measure_name,
|
||||
dout=signal_name,
|
||||
t_initial=t_initial,
|
||||
t_final=t_final)
|
||||
# TODO: Convert to measurement statement instead of stimuli
|
||||
meas = voltage_at_measure(measure_name, signal_name)
|
||||
self.measures[measure_name] = meas
|
||||
meas.write_measure(self.stim, ((t_initial + t_final) / 2, 0))
|
||||
# self.stim.gen_meas_value(meas_name=measure_name,
|
||||
# dout=signal_name,
|
||||
# t_initial=t_initial,
|
||||
# t_final=t_final
|
||||
|
||||
self.sf.write(".include {0}\n".format(temp_meas))
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
# FIXME: Similar function to delay.py, refactor this
|
||||
def get_bit_name(self):
|
||||
""" Get a bit cell name """
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
# TODO: Find a way to get the cell_name and storage_names statically
|
||||
# (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
||||
# storage_names = cell_inst.mod.get_storage_net_names()
|
||||
# debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
# "supported for characterization. Storage nets={0}").format(storage_names))
|
||||
cell_name = "xsram:xbank0:xbitcell_array:xbitcell_array:xbit_r0_c0"
|
||||
storage_names = ("Q", "Q_bar")
|
||||
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
|
||||
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class lib:
|
|||
debug.info(1, "Slews: {0}".format(self.slews))
|
||||
debug.info(1, "Loads: {0}".format(self.loads))
|
||||
debug.info(1, "self.load_slews : {0}".format(self.load_slews))
|
||||
|
||||
def create_corners(self):
|
||||
""" Create corners for characterization. """
|
||||
# Get the corners from the options file
|
||||
|
|
@ -801,7 +802,8 @@ class lib:
|
|||
|
||||
# information of checks
|
||||
# run it only the first time
|
||||
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||
if OPTS.top_process != "memchar":
|
||||
datasheet.write("{0},{1},".format(self.sram.drc_errors, self.sram.lvs_errors))
|
||||
|
||||
# write area
|
||||
datasheet.write(str(self.sram.width * self.sram.height) + ',')
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ from .charutils import *
|
|||
class spice_measurement(ABC):
|
||||
"""Base class for spice stimulus measurements."""
|
||||
def __init__(self, measure_name, measure_scale=None, has_port=True):
|
||||
#Names must be unique for correct spice simulation, but not enforced here.
|
||||
# Names must be unique for correct spice simulation, but not enforced here.
|
||||
self.name = measure_name
|
||||
self.measure_scale = measure_scale
|
||||
self.has_port = has_port #Needed for error checking
|
||||
#Some meta values used externally. variables are added here for consistency accross the objects
|
||||
self.has_port = has_port # Needed for error checking
|
||||
# Some meta values used externally. variables are added here for consistency accross the objects
|
||||
self.meta_str = None
|
||||
self.meta_add_delay = False
|
||||
|
||||
@abstractmethod
|
||||
def get_measure_function(self):
|
||||
def measure_function(self):
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
|
|
@ -31,28 +32,25 @@ class spice_measurement(ABC):
|
|||
return None
|
||||
|
||||
def write_measure(self, stim_obj, input_tuple):
|
||||
measure_func = self.get_measure_function()
|
||||
if measure_func == None:
|
||||
debug.error("Did not set measure function",1)
|
||||
measure_vals = self.get_measure_values(*input_tuple)
|
||||
measure_func(stim_obj, *measure_vals)
|
||||
self.measure_function(stim_obj, *measure_vals)
|
||||
|
||||
def retrieve_measure(self, port=None):
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
|
||||
else:
|
||||
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
|
||||
if type(value)!=float or self.measure_scale == None:
|
||||
if type(value)!=float or self.measure_scale is None:
|
||||
return value
|
||||
else:
|
||||
return value*self.measure_scale
|
||||
return value * self.measure_scale
|
||||
|
||||
def port_error_check(self, port):
|
||||
if self.has_port and port == None:
|
||||
debug.error("Cannot retrieve measurement, port input was expected.",1)
|
||||
elif not self.has_port and port != None:
|
||||
debug.error("Unexpected port input received during measure retrieval.",1)
|
||||
if self.has_port and port is None:
|
||||
debug.error("Cannot retrieve measurement, port input was expected.", 1)
|
||||
elif not self.has_port and port is not None:
|
||||
debug.error("Unexpected port input received during measure retrieval.", 1)
|
||||
|
||||
|
||||
class delay_measure(spice_measurement):
|
||||
|
|
@ -71,8 +69,18 @@ class delay_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_delay
|
||||
def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
trig_td,
|
||||
targ_name,
|
||||
targ_val,
|
||||
targ_dir,
|
||||
targ_td))
|
||||
|
||||
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd):
|
||||
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
|
||||
|
|
@ -91,7 +99,7 @@ class delay_measure(spice_measurement):
|
|||
trig_val = self.trig_val_of_vdd * vdd_voltage
|
||||
targ_val = self.targ_val_of_vdd * vdd_voltage
|
||||
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
|
|
@ -121,7 +129,7 @@ class slew_measure(delay_measure):
|
|||
self.trig_val_of_vdd = 0.9
|
||||
self.targ_val_of_vdd = 0.1
|
||||
else:
|
||||
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
|
||||
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str), 1)
|
||||
self.trig_name_no_port = signal_name
|
||||
self.targ_name_no_port = signal_name
|
||||
|
||||
|
|
@ -135,8 +143,18 @@ class power_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(power_type)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_power
|
||||
def measure_function(self, stim_obj, meas_name, t_initial, t_final):
|
||||
""" Creates the .meas statement for the measurement of avg power """
|
||||
# power mea cmd is different in different spice:
|
||||
if OPTS.spice_name == "hspice":
|
||||
power_exp = "power"
|
||||
else:
|
||||
# FIXME: Obtain proper vdd and gnd name
|
||||
power_exp = "par('(-1*v(" + "vdd" + ")*I(v" + "vdd" + "))')"
|
||||
stim_obj.mf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def set_meas_constants(self, power_type):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -146,7 +164,7 @@ class power_measure(spice_measurement):
|
|||
def get_measure_values(self, t_initial, t_final, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
else:
|
||||
meas_name = self.name
|
||||
|
|
@ -160,8 +178,15 @@ class voltage_when_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_find_voltage
|
||||
def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
trig_td))
|
||||
|
||||
def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -173,7 +198,7 @@ class voltage_when_measure(spice_measurement):
|
|||
def get_measure_values(self, trig_td, vdd_voltage, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
|
|
@ -194,8 +219,12 @@ class voltage_at_measure(spice_measurement):
|
|||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(targ_name)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_find_voltage_at_time
|
||||
def measure_function(self, stim_obj, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
stim_obj.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
def set_meas_constants(self, targ_name):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
|
|
@ -204,7 +233,7 @@ class voltage_at_measure(spice_measurement):
|
|||
def get_measure_values(self, time_at, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
if port is not None:
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ class setup_hold():
|
|||
self.stim_sp = "sh_stim.sp"
|
||||
temp_stim = OPTS.openram_temp + self.stim_sp
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
# creates and opens the measure file for writing
|
||||
self.meas_sp = "sh_meas.sp"
|
||||
temp_meas = OPTS.openram_temp + self.meas_sp
|
||||
self.mf = open(temp_meas, "w")
|
||||
|
||||
self.stim = stimuli(self.sf, self.mf, self.corner)
|
||||
|
||||
self.write_header(correct_value)
|
||||
|
||||
|
|
@ -56,6 +62,7 @@ class setup_hold():
|
|||
correct_value=correct_value)
|
||||
|
||||
self.write_clock()
|
||||
self.sf.write(".include {}\n".format(temp_meas))
|
||||
|
||||
self.write_measures(mode=mode,
|
||||
correct_value=correct_value)
|
||||
|
|
@ -63,6 +70,7 @@ class setup_hold():
|
|||
self.stim.write_control(4 * self.period)
|
||||
|
||||
self.sf.close()
|
||||
self.mf.close()
|
||||
|
||||
def write_header(self, correct_value):
|
||||
""" Write the header file with all the models and the power supplies. """
|
||||
|
|
@ -131,7 +139,7 @@ class setup_hold():
|
|||
else:
|
||||
dout_rise_or_fall = "FALL"
|
||||
|
||||
self.sf.write("\n* Measure statements for pass/fail verification\n")
|
||||
self.mf.write("\n* Measure statements for pass/fail verification\n")
|
||||
trig_name = "clk"
|
||||
targ_name = "Q"
|
||||
trig_val = targ_val = 0.5 * self.vdd_voltage
|
||||
|
|
@ -168,29 +176,33 @@ class setup_hold():
|
|||
target_time=feasible_bound,
|
||||
correct_value=correct_value)
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
ideal_clk_to_q = convert_to_float(parse_spice_list("timing",
|
||||
"clk2q_delay"))
|
||||
# We use a 1/2 speed clock for some reason...
|
||||
setuphold_time = (feasible_bound - 2 * self.period)
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
passing_setuphold_time = -1 * setuphold_time
|
||||
else:
|
||||
passing_setuphold_time = setuphold_time
|
||||
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode,
|
||||
correct_value,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time))
|
||||
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}"
|
||||
.format(mode,
|
||||
correct_value,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time))
|
||||
|
||||
if type(ideal_clk_to_q)!=float:
|
||||
debug.error("Initial hold time fails for data value feasible "
|
||||
"bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
"bound {0} Clk-to-Q {1} Setup/Hold {2}"
|
||||
.format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
2)
|
||||
|
||||
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode,
|
||||
setuphold_time,
|
||||
feasible_bound,
|
||||
2 * self.period))
|
||||
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} "
|
||||
.format(mode,
|
||||
setuphold_time,
|
||||
feasible_bound,
|
||||
2 * self.period))
|
||||
|
||||
while True:
|
||||
target_time = (feasible_bound + infeasible_bound) / 2
|
||||
|
|
@ -198,11 +210,12 @@ class setup_hold():
|
|||
target_time=target_time,
|
||||
correct_value=correct_value)
|
||||
|
||||
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode,
|
||||
correct_value,
|
||||
target_time,
|
||||
infeasible_bound,
|
||||
feasible_bound))
|
||||
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}"
|
||||
.format(mode,
|
||||
correct_value,
|
||||
target_time,
|
||||
infeasible_bound,
|
||||
feasible_bound))
|
||||
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ class simulation():
|
|||
self.add_wmask(wmask, port)
|
||||
self.add_spare_wen("1" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
|
@ -267,7 +267,7 @@ class simulation():
|
|||
self.add_wmask("0" * self.num_wmasks, port)
|
||||
self.add_spare_wen("0" * self.num_spare_cols, port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
|
@ -356,14 +356,14 @@ class simulation():
|
|||
|
||||
self.add_noop_one_port(port)
|
||||
|
||||
#Add noops to all other ports.
|
||||
# Add noops to all other ports.
|
||||
for unselected_port in self.all_ports:
|
||||
if unselected_port != port:
|
||||
self.add_noop_one_port(unselected_port)
|
||||
|
||||
def append_cycle_comment(self, port, comment):
|
||||
"""Add comment to list to be printed in stimulus file"""
|
||||
#Clean up time before appending. Make spacing dynamic as well.
|
||||
# Clean up time before appending. Make spacing dynamic as well.
|
||||
time = "{0:.2f} ns:".format(self.t_current)
|
||||
time_spacing = len(time) + 6
|
||||
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
|
||||
|
|
@ -388,7 +388,7 @@ class simulation():
|
|||
split_word2 = [x + '_' * (n != 0 and n % 4 == 0) for n, x in enumerate(split_word)]
|
||||
# Join the word unreversed back together
|
||||
new_word = ''.join(reversed(split_word2))
|
||||
return(new_word)
|
||||
return (new_word)
|
||||
|
||||
# Split extra cols
|
||||
if self.num_spare_cols > 0:
|
||||
|
|
@ -414,9 +414,9 @@ class simulation():
|
|||
comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
|
||||
addr,
|
||||
port,
|
||||
int(t_current/self.period),
|
||||
int(t_current / self.period),
|
||||
t_current,
|
||||
t_current+self.period)
|
||||
t_current + self.period)
|
||||
elif op == "partial_write":
|
||||
str = "\tWriting (partial) {0} to address {1} with mask bit {2} (from port {3}) during cycle {4} ({5}ns - {6}ns)"
|
||||
comment = str.format(word,
|
||||
|
|
@ -454,7 +454,7 @@ class simulation():
|
|||
for i in range(abits):
|
||||
pin_names.append("{0}{1}_{2}".format(addr_name, port, i))
|
||||
|
||||
#Control signals not finalized.
|
||||
# Control signals not finalized.
|
||||
for port in range(total_ports):
|
||||
pin_names.append("CSB{0}".format(port))
|
||||
for port in range(total_ports):
|
||||
|
|
@ -493,11 +493,12 @@ class simulation():
|
|||
|
||||
# other initializations can only be done during analysis when a bit has been selected
|
||||
# for testing.
|
||||
self.sram.bank.graph_exclude_precharge()
|
||||
self.sram.graph_exclude_addr_dff()
|
||||
self.sram.graph_exclude_data_dff()
|
||||
self.sram.graph_exclude_ctrl_dffs()
|
||||
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
|
||||
if OPTS.top_process != 'memchar':
|
||||
self.sram.bank.graph_exclude_precharge()
|
||||
self.sram.graph_exclude_addr_dff()
|
||||
self.sram.graph_exclude_data_dff()
|
||||
self.sram.graph_exclude_ctrl_dffs()
|
||||
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
|
||||
|
||||
def set_internal_spice_names(self):
|
||||
"""
|
||||
|
|
@ -515,9 +516,9 @@ class simulation():
|
|||
self.sen_name = sen_with_port
|
||||
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.")
|
||||
|
||||
column_addr = self.get_column_addr()
|
||||
# column_addr = self.get_column_addr()
|
||||
bl_name_port, br_name_port = self.get_bl_name(self.graph.all_paths, port)
|
||||
port_pos = -1 - len(str(column_addr)) - len(str(port))
|
||||
# port_pos = -1 - len(str(column_addr)) - len(str(port))
|
||||
|
||||
if bl_name_port.endswith(str(port) + "_" + str(self.bitline_column)): # single port SRAM case, bl will not be numbered eg bl_0
|
||||
self.bl_name = bl_name_port
|
||||
|
|
@ -535,7 +536,7 @@ class simulation():
|
|||
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
||||
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
#debug.info(2, "s_en {}".format(self.sen_name))
|
||||
# debug.info(2, "s_en {}".format(self.sen_name))
|
||||
|
||||
self.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1)
|
||||
self.br_name = "br{0}_{1}".format(port, OPTS.word_size - 1)
|
||||
|
|
@ -563,10 +564,10 @@ class simulation():
|
|||
Creates timing graph to generate the timing paths for the SRAM output.
|
||||
"""
|
||||
|
||||
#Make exclusions dependent on the bit being tested.
|
||||
# Make exclusions dependent on the bit being tested.
|
||||
self.sram.clear_exclude_bits() # Removes previous bit exclusions
|
||||
self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column)
|
||||
port=self.read_ports[0] #FIXME, port_data requires a port specification, assuming single port for now
|
||||
port=self.read_ports[0] # FIXME, port_data requires a port specification, assuming single port for now
|
||||
if self.words_per_row > 1:
|
||||
self.sram.graph_clear_column_mux(port)
|
||||
self.sram.graph_exclude_column_mux(self.bitline_column, port)
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
|
||||
class sram_op(Enum):
|
||||
READ_ZERO = 0
|
||||
READ_ONE = 1
|
||||
WRITE_ZERO = 2
|
||||
WRITE_ONE = 3
|
||||
|
|
@ -22,7 +22,7 @@ from openram import OPTS
|
|||
class stimuli():
|
||||
""" Class for providing stimuli functions """
|
||||
|
||||
def __init__(self, stim_file, corner):
|
||||
def __init__(self, stim_file, meas_file, corner):
|
||||
self.vdd_name = "vdd"
|
||||
self.gnd_name = "gnd"
|
||||
self.pmos_name = tech.spice["pmos"]
|
||||
|
|
@ -31,6 +31,7 @@ class stimuli():
|
|||
self.tx_length = tech.drc["minlength_channel"]
|
||||
|
||||
self.sf = stim_file
|
||||
self.mf = meas_file
|
||||
|
||||
(self.process, self.voltage, self.temperature) = corner
|
||||
found = False
|
||||
|
|
@ -136,7 +137,7 @@ class stimuli():
|
|||
offset,
|
||||
t_rise,
|
||||
t_fall,
|
||||
0.5*period-0.5*t_rise-0.5*t_fall,
|
||||
0.5 * period - 0.5 * t_rise - 0.5 * t_fall,
|
||||
period))
|
||||
|
||||
def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup):
|
||||
|
|
@ -181,7 +182,7 @@ class stimuli():
|
|||
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
|
|
@ -194,7 +195,7 @@ class stimuli():
|
|||
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
|
|
@ -204,7 +205,7 @@ class stimuli():
|
|||
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
self.mf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
|
|
@ -215,15 +216,15 @@ class stimuli():
|
|||
power_exp = "power"
|
||||
else:
|
||||
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')"
|
||||
self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
self.mf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def gen_meas_value(self, meas_name, dout, t_initial, t_final):
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n\n\n".format(meas_name.lower(), dout, (t_initial + t_final) / 2)
|
||||
#measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
|
||||
self.sf.write(measure_string)
|
||||
# measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
|
||||
self.mf.write(measure_string)
|
||||
|
||||
def write_control(self, end_time, runlvl=4):
|
||||
""" Write the control cards to run and end the simulation """
|
||||
|
|
@ -278,7 +279,7 @@ class stimuli():
|
|||
self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature))
|
||||
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n")
|
||||
self.sf.write(".OPTIONS LINSOL type=klu\n")
|
||||
self.sf.write(".OPTIONS TIMEINT RELTOL=1e-6 ABSTOL=1e-10 method=gear minorder=2\n")
|
||||
self.sf.write(".OPTIONS TIMEINT RELTOL=1e-3 ABSTOL=1e-6 method=gear minorder=2\n")
|
||||
# Format: .TRAN <initial step> <final time> <start time> <step ceiling>
|
||||
self.sf.write(".TRAN {0}p {1}n 0n {0}p\n".format(timestep, end_time))
|
||||
elif OPTS.spice_name:
|
||||
|
|
@ -411,12 +412,12 @@ class stimuli():
|
|||
from openram import CONDA_HOME
|
||||
cmd = "source {0}/bin/activate && {1} && conda deactivate".format(CONDA_HOME, cmd)
|
||||
debug.info(2, cmd)
|
||||
retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
|
||||
proc = subprocess.run(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True)
|
||||
|
||||
spice_stdout.close()
|
||||
spice_stderr.close()
|
||||
|
||||
if (retcode > valid_retcode):
|
||||
if (proc.returncode > valid_retcode):
|
||||
debug.error("Spice simulation error: " + cmd, -1)
|
||||
else:
|
||||
end_time = datetime.datetime.now()
|
||||
|
|
|
|||
|
|
@ -85,14 +85,15 @@ class trim_spice():
|
|||
|
||||
wl_regex = r"wl\d*_{}".format(wl_address)
|
||||
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
bl_no_port_regex = r"bl_{}".format(int(self.words_per_row*data_bit + col_address))
|
||||
|
||||
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
|
||||
|
||||
# 2. Keep sense amps basd on BL
|
||||
# FIXME: The bit lines are not indexed the same in sense_amp_array
|
||||
#self.remove_insts("sense_amp_array",[bl_regex])
|
||||
self.remove_insts("sense_amp_array",[bl_no_port_regex])
|
||||
|
||||
# 3. Keep column muxes basd on BL
|
||||
self.remove_insts("column_mux_array", [bl_regex])
|
||||
self.remove_insts("single_level_column_mux_array", [bl_no_port_regex])
|
||||
|
||||
# 4. Keep write driver based on DATA
|
||||
data_regex = r"data_{}".format(data_bit)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California and The Board
|
||||
|
|
@ -15,6 +15,7 @@ corner, but should probably be extended.
|
|||
|
||||
import sys
|
||||
from globals import *
|
||||
from importlib import reload
|
||||
|
||||
(OPTS, args) = parse_args()
|
||||
|
||||
|
|
@ -41,36 +42,29 @@ slew = float(args[3])
|
|||
import debug
|
||||
|
||||
init_openram(config_file=config_file, is_unit_test=False)
|
||||
|
||||
from sram_config import sram_config
|
||||
c = sram_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)
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
# Put the temp output in the output path since it is what we want to generate!
|
||||
old_openram_temp = OPTS.openram_temp
|
||||
OPTS.openram_temp = OPTS.output_path
|
||||
|
||||
|
||||
|
||||
import sram
|
||||
class fake_sram(sram.sram):
|
||||
""" This is an SRAM that doesn't actually create itself, just computes
|
||||
the sizes. """
|
||||
def __init__(self, word_size, num_words, num_banks, name, num_spare_rows):
|
||||
self.name = name
|
||||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
self.num_banks = num_banks
|
||||
self.num_spare_rows = num_spare_rows
|
||||
c = reload(__import__(OPTS.bitcell))
|
||||
self.mod_bitcell = getattr(c, OPTS.bitcell)
|
||||
self.bitcell = self.mod_bitcell()
|
||||
# to get the row, col, etc.
|
||||
self.compute_sizes()
|
||||
|
||||
sram = fake_sram(OPTS.word_size, OPTS.num_words, OPTS.num_banks, OPTS.output_name)
|
||||
sp_file = OPTS.output_path+OPTS.output_name + ".sp"
|
||||
from sram import sram
|
||||
s = sram(name=OPTS.output_name, sram_config=c)
|
||||
|
||||
from characterizer import delay
|
||||
import tech
|
||||
# Set up the delay and set to the nominal corner
|
||||
d = delay.delay(sram, sp_file, ("TT", tech.spice["nom_supply_voltage"], tech.spice["nom_temperature"]))
|
||||
d = delay(s, s.get_sp_name(), ("TT", tech.spice["nom_supply_voltage"], tech.spice["nom_temperature"]))
|
||||
# Set the period
|
||||
d.period = period
|
||||
# Set the load of outputs and slew of inputs
|
||||
|
|
@ -91,4 +85,3 @@ print("Output files are:\n{0}stim.sp\n{0}sram.sp\n{0}reduced.sp".format(OPTS.out
|
|||
OPTS.openram_temp = old_openram_temp
|
||||
# Delete temp files, remove the dir, etc.
|
||||
end_openram()
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class column_decoder(design):
|
|||
def create_instances(self):
|
||||
self.column_decoder_inst = self.add_inst(name="column_decoder",
|
||||
mod=self.column_decoder)
|
||||
self.connect_inst(self.pins)
|
||||
self.connect_inst(list(self.pins))
|
||||
|
||||
def create_layout(self):
|
||||
self.column_decoder_inst.place(vector(0,0))
|
||||
|
|
|
|||
|
|
@ -1212,7 +1212,7 @@ class pbitcell(bitcell_base):
|
|||
if self.dummy_bitcell:
|
||||
return
|
||||
|
||||
pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)}
|
||||
pin_dict = {pin: port for pin, port in zip(list(self.pins), port_nets)}
|
||||
|
||||
# Edges added wl->bl, wl->br for every port except write ports
|
||||
rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from openram.base import rom_verilog
|
|||
from openram import OPTS, print_time
|
||||
from openram.sram_factory import factory
|
||||
from openram.tech import drc, layer, parameter
|
||||
from openram.router import router_tech
|
||||
|
||||
|
||||
class rom_bank(design,rom_verilog):
|
||||
|
|
@ -111,21 +110,16 @@ class rom_bank(design,rom_verilog):
|
|||
self.place_top_level_pins()
|
||||
self.route_output_buffers()
|
||||
|
||||
rt = router_tech(self.supply_stack, 1)
|
||||
init_bbox = self.get_bbox(side="ring",
|
||||
margin=rt.track_width)
|
||||
self.route_supplies(init_bbox)
|
||||
# We need the initial bbox for the supply rings later
|
||||
# because the perimeter pins will change the bbox
|
||||
# FIXME: Somehow ROM layout behaves weird and doesn't add all the pin
|
||||
# shapes before routing supplies
|
||||
init_bbox = self.get_bbox()
|
||||
if OPTS.route_supplies:
|
||||
self.route_supplies(init_bbox)
|
||||
# Route the pins to the perimeter
|
||||
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
|
||||
bbox = self.get_bbox(side="ring",
|
||||
margin=11*rt.track_width)
|
||||
self.route_escape_pins(bbox)
|
||||
|
||||
|
||||
self.route_escape_pins(init_bbox)
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
|
|
@ -450,24 +444,17 @@ class rom_bank(design,rom_verilog):
|
|||
pin_num = msb - self.col_bits
|
||||
self.add_io_pin(self.decode_inst, "A{}".format(pin_num), name)
|
||||
|
||||
def route_supplies(self, bbox=None):
|
||||
def route_supplies(self, bbox):
|
||||
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name)
|
||||
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from openram.router import supply_grid_router as router
|
||||
else:
|
||||
from openram.router import supply_tree_router as router
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
from openram.router import supply_router as router
|
||||
rtr = router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
rtr.route()
|
||||
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
|
|
@ -507,7 +494,7 @@ class rom_bank(design,rom_verilog):
|
|||
pins_to_route.append("clk")
|
||||
pins_to_route.append("cs")
|
||||
from openram.router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_route)
|
||||
rtr = router(layers=self.m3_stack,
|
||||
bbox=bbox,
|
||||
design=self)
|
||||
rtr.route(pins_to_route)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ from openram.base import channel_route
|
|||
from openram.base import design
|
||||
from openram.base import verilog
|
||||
from openram.base import lef
|
||||
from openram.router import router_tech
|
||||
from openram.sram_factory import factory
|
||||
from openram.tech import spice
|
||||
from openram import OPTS, print_time
|
||||
|
|
@ -252,18 +251,11 @@ class sram_1bank(design, verilog, lef):
|
|||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name])
|
||||
|
||||
if not OPTS.route_supplies:
|
||||
# Do not route the power supply (leave as must-connect pins)
|
||||
return
|
||||
elif OPTS.route_supplies == "grid":
|
||||
from openram.router import supply_grid_router as router
|
||||
else:
|
||||
from openram.router import supply_tree_router as router
|
||||
rtr=router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
|
||||
from openram.router import supply_router as router
|
||||
rtr = router(layers=self.supply_stack,
|
||||
design=self,
|
||||
bbox=bbox,
|
||||
pin_type=OPTS.supply_pin_type)
|
||||
rtr.route()
|
||||
|
||||
if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]:
|
||||
|
|
@ -288,7 +280,7 @@ class sram_1bank(design, verilog, lef):
|
|||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
elif OPTS.route_supplies and OPTS.supply_pin_type == "single":
|
||||
elif OPTS.supply_pin_type == "single":
|
||||
# Update these as we may have routed outside the region (perimeter pins)
|
||||
lowest_coord = self.find_lowest_coords()
|
||||
|
||||
|
|
@ -326,7 +318,7 @@ class sram_1bank(design, verilog, lef):
|
|||
# Grid is left with many top level pins
|
||||
pass
|
||||
|
||||
def route_escape_pins(self, bbox):
|
||||
def route_escape_pins(self, bbox=None):
|
||||
"""
|
||||
Add the top-level pins for a single bank SRAM with control.
|
||||
"""
|
||||
|
|
@ -370,10 +362,10 @@ class sram_1bank(design, verilog, lef):
|
|||
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
|
||||
|
||||
from openram.router import signal_escape_router as router
|
||||
rtr=router(layers=self.m3_stack,
|
||||
design=self,
|
||||
bbox=bbox)
|
||||
rtr.escape_route(pins_to_route)
|
||||
rtr = router(layers=self.m3_stack,
|
||||
bbox=bbox,
|
||||
design=self)
|
||||
rtr.route(pins_to_route)
|
||||
|
||||
def compute_bus_sizes(self):
|
||||
""" Compute the independent bus widths shared between two and four bank SRAMs """
|
||||
|
|
@ -1077,24 +1069,15 @@ class sram_1bank(design, verilog, lef):
|
|||
# Some technologies have an isolation
|
||||
self.add_dnwell(inflate=2.5)
|
||||
|
||||
init_bbox = self.get_bbox()
|
||||
# Route the supplies together and/or to the ring/stripes.
|
||||
# This is done with the original bbox since the escape routes need to
|
||||
# be outside of the ring for OpenLane
|
||||
rt = router_tech(self.supply_stack, 1)
|
||||
init_bbox = self.get_bbox(side="ring",
|
||||
margin=rt.track_width)
|
||||
|
||||
# We need the initial bbox for the supply rings later
|
||||
# because the perimeter pins will change the bbox
|
||||
# Route the pins to the perimeter
|
||||
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
|
||||
bbox = self.get_bbox(side="ring",
|
||||
margin=11*rt.track_width)
|
||||
self.route_escape_pins(bbox)
|
||||
|
||||
self.route_supplies(init_bbox)
|
||||
self.route_escape_pins(init_bbox)
|
||||
if OPTS.route_supplies:
|
||||
self.route_supplies(init_bbox)
|
||||
|
||||
|
||||
def route_dffs(self, add_routes=True):
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ class options(optparse.Values):
|
|||
sim_data_path = None
|
||||
# A list of load/slew tuples
|
||||
use_specified_load_slew = None
|
||||
# Spice simulation raw file
|
||||
spice_raw_file = None
|
||||
|
||||
###################
|
||||
# Run-time vs accuracy options.
|
||||
|
|
@ -113,7 +115,7 @@ class options(optparse.Values):
|
|||
# When enabled, layout is not generated (and no DRC or LVS are performed)
|
||||
netlist_only = False
|
||||
# Whether we should do the final power routing
|
||||
route_supplies = "tree"
|
||||
route_supplies = True
|
||||
supply_pin_type = "ring"
|
||||
# This determines whether LVS and DRC is checked at all.
|
||||
check_lvsdrc = False
|
||||
|
|
@ -126,14 +128,18 @@ class options(optparse.Values):
|
|||
# Output config with all options
|
||||
output_extended_config = False
|
||||
# Output temporary file used to format HTML page
|
||||
output_datasheet_info = False
|
||||
output_datasheet_info = True
|
||||
# Determines which analytical model to use.
|
||||
# Available Models: elmore, linear_regression
|
||||
model_name = "elmore"
|
||||
# Write graph to a file
|
||||
write_graph = False
|
||||
|
||||
###################
|
||||
# Tool options
|
||||
###################
|
||||
# Top process that was ran (openram, memchar, memfunc)
|
||||
top_process = None
|
||||
# Use conda to install the default tools
|
||||
# (existing tools will be used if disabled)
|
||||
use_conda = True
|
||||
|
|
@ -174,6 +180,15 @@ class options(optparse.Values):
|
|||
# Purge the temp directory after a successful
|
||||
# run (doesn't purge on errors, anyhow)
|
||||
|
||||
# Bitline, s_en and cell names used in characterizer
|
||||
bl_format = "X{name}{hier_sep}xbank0{hier_sep}bl_{row}_{col}"
|
||||
br_format = "X{name}{hier_sep}xbank0{hier_sep}br_{row}_{col}"
|
||||
sen_format = "X{name}{hier_sep}xbank0{hier_sep}s_en"
|
||||
cell_format = "X{name}{hier_sep}xbank0{hier_sep}xbitcell_array{hier_sep}xreplica_bitcell_array{hier_sep}xbitcell_array{hier_sep}xbit_r{row}_c{col}"
|
||||
|
||||
# Random seed for functional simulation
|
||||
functional_seed = None
|
||||
|
||||
# Route the input/output pins to the perimeter
|
||||
perimeter_pins = True
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,5 @@
|
|||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from .router import *
|
||||
from .signal_escape_router import *
|
||||
from .signal_router import *
|
||||
from .supply_grid_router import *
|
||||
from .supply_tree_router import *
|
||||
from .supply_router import *
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from enum import Enum
|
||||
from openram import debug
|
||||
from openram.base.vector3d import vector3d
|
||||
|
||||
|
||||
class direction(Enum):
|
||||
NORTH = 1
|
||||
SOUTH = 2
|
||||
EAST = 3
|
||||
WEST = 4
|
||||
UP = 5
|
||||
DOWN = 6
|
||||
NORTHEAST = 7
|
||||
NORTHWEST = 8
|
||||
SOUTHEAST = 9
|
||||
SOUTHWEST = 10
|
||||
|
||||
def get_offset(direct):
|
||||
"""
|
||||
Returns the vector offset for a given direction.
|
||||
"""
|
||||
if direct==direction.NORTH:
|
||||
offset = vector3d(0, 1, 0)
|
||||
elif direct==direction.SOUTH:
|
||||
offset = vector3d(0, -1 ,0)
|
||||
elif direct==direction.EAST:
|
||||
offset = vector3d(1, 0, 0)
|
||||
elif direct==direction.WEST:
|
||||
offset = vector3d(-1, 0, 0)
|
||||
elif direct==direction.UP:
|
||||
offset = vector3d(0, 0, 1)
|
||||
elif direct==direction.DOWN:
|
||||
offset = vector3d(0, 0, -1)
|
||||
elif direct==direction.NORTHEAST:
|
||||
offset = vector3d(1, 1, 0)
|
||||
elif direct==direction.NORTHWEST:
|
||||
offset = vector3d(-1, 1, 0)
|
||||
elif direct==direction.SOUTHEAST:
|
||||
offset = vector3d(1, -1, 0)
|
||||
elif direct==direction.SOUTHWEST:
|
||||
offset = vector3d(-1, -1, 0)
|
||||
else:
|
||||
debug.error("Invalid direction {}".format(direct))
|
||||
|
||||
return offset
|
||||
|
||||
def cardinal_directions(up_down_too=False):
|
||||
temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST]
|
||||
if up_down_too:
|
||||
temp_dirs.extend([direction.UP, direction.DOWN])
|
||||
return temp_dirs
|
||||
|
||||
def cardinal_offsets(up_down_too=False):
|
||||
return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)]
|
||||
|
||||
def all_directions():
|
||||
return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST,
|
||||
direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST]
|
||||
|
||||
def all_offsets():
|
||||
return [direction.get_offset(d) for d in direction.all_directions()]
|
||||
|
||||
def all_neighbors(cell):
|
||||
return [cell + x for x in direction.all_offsets()]
|
||||
|
||||
def cardinal_neighbors(cell):
|
||||
return [cell + x for x in direction.cardinal_offsets()]
|
||||
|
||||
|
|
@ -0,0 +1,442 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
import heapq
|
||||
from copy import deepcopy
|
||||
from openram import debug
|
||||
from openram.base.vector import vector
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram.tech import drc
|
||||
from .graph_node import graph_node
|
||||
from .graph_probe import graph_probe
|
||||
from .graph_utils import snap
|
||||
|
||||
|
||||
class graph:
|
||||
""" This is the graph created from the blockages. """
|
||||
|
||||
def __init__(self, router):
|
||||
|
||||
# This is the graph router that uses this graph
|
||||
self.router = router
|
||||
self.source_nodes = []
|
||||
self.target_nodes = []
|
||||
|
||||
|
||||
def is_routable(self, shape):
|
||||
""" Return if a shape is routable in this graph. """
|
||||
|
||||
return shape.name == self.source.name
|
||||
|
||||
|
||||
def inside_shape(self, point, shape):
|
||||
""" Return if the point is inside the shape. """
|
||||
|
||||
# Check if they're on the same layer
|
||||
if point.z != self.router.get_zindex(shape.lpp):
|
||||
return False
|
||||
# Check if the point is inside the shape
|
||||
ll, ur = shape.rect
|
||||
return shape.on_segment(ll, point, ur)
|
||||
|
||||
|
||||
def get_safe_pin_values(self, pin):
|
||||
""" Get the safe x and y values of the given pin. """
|
||||
|
||||
# Constant values
|
||||
pin = pin.get_core()
|
||||
offset = self.router.half_wire
|
||||
spacing = self.router.track_space
|
||||
size_limit = snap(offset * 4 + spacing)
|
||||
|
||||
x_values = []
|
||||
y_values = []
|
||||
# If one axis size of the pin is greater than the limit, we will take
|
||||
# two points at both ends. Otherwise, we will only take the center of
|
||||
# this pin.
|
||||
if pin.width() > size_limit:
|
||||
x_values.append(snap(pin.lx() + offset))
|
||||
x_values.append(snap(pin.rx() - offset))
|
||||
else:
|
||||
x_values.append(snap(pin.cx()))
|
||||
if pin.height() > size_limit:
|
||||
y_values.append(snap(pin.by() + offset))
|
||||
y_values.append(snap(pin.uy() - offset))
|
||||
else:
|
||||
y_values.append(snap(pin.cy()))
|
||||
|
||||
return x_values, y_values
|
||||
|
||||
|
||||
def is_probe_blocked(self, p1, p2):
|
||||
"""
|
||||
Return if a probe sent from p1 to p2 encounters a blockage.
|
||||
The probe must be sent vertically or horizontally.
|
||||
This function assumes that p1 and p2 are on the same layer.
|
||||
"""
|
||||
|
||||
probe_shape = graph_probe(p1, p2, self.router.get_lpp(p1.z))
|
||||
pll, pur = probe_shape.rect
|
||||
# Check if any blockage blocks this probe
|
||||
for blockage in self.graph_blockages:
|
||||
bll, bur = blockage.rect
|
||||
# Not overlapping
|
||||
if bll.x > pur.x or pll.x > bur.x or bll.y > pur.y or pll.y > bur.y:
|
||||
continue
|
||||
# Not on the same layer
|
||||
if not blockage.same_lpp(blockage.lpp, probe_shape.lpp):
|
||||
continue
|
||||
# Probe is blocked if the shape isn't routable
|
||||
if not self.is_routable(blockage):
|
||||
return True
|
||||
blockage = blockage.get_core()
|
||||
bll, bur = blockage.rect
|
||||
# Not overlapping
|
||||
if bll.x > pur.x or pll.x > bur.x or bll.y > pur.y or pll.y > bur.y:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_node_blocked(self, node, pin_safe=True):
|
||||
""" Return if a node is blocked by a blockage. """
|
||||
|
||||
p = node.center
|
||||
x = p.x
|
||||
y = p.y
|
||||
z = p.z
|
||||
|
||||
def closest(value, checklist):
|
||||
""" Return the distance of the closest value in the checklist. """
|
||||
diffs = [abs(value - other) for other in checklist]
|
||||
return snap(min(diffs))
|
||||
|
||||
wide = self.router.track_wire
|
||||
half_wide = self.router.half_wire
|
||||
spacing = snap(self.router.track_space + half_wide + drc["grid"])
|
||||
blocked = False
|
||||
for blockage in self.graph_blockages:
|
||||
ll, ur = blockage.rect
|
||||
# Not overlapping
|
||||
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
|
||||
continue
|
||||
# Not on the same layer
|
||||
if self.router.get_zindex(blockage.lpp) != z:
|
||||
continue
|
||||
# Blocked if not routable
|
||||
if not self.is_routable(blockage):
|
||||
blocked = True
|
||||
continue
|
||||
blockage = blockage.get_core()
|
||||
ll, ur = blockage.rect
|
||||
# Not overlapping
|
||||
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
|
||||
blocked = True
|
||||
continue
|
||||
# Check if the node is too close to one edge of the shape
|
||||
lengths = [blockage.width(), blockage.height()]
|
||||
centers = blockage.center()
|
||||
ll, ur = blockage.rect
|
||||
safe = [True, True]
|
||||
for i in range(2):
|
||||
if lengths[i] >= wide:
|
||||
min_diff = closest(p[i], [ll[i], ur[i]])
|
||||
if min_diff < half_wide:
|
||||
safe[i] = False
|
||||
elif centers[i] != p[i]:
|
||||
safe[i] = False
|
||||
if not all(safe):
|
||||
blocked = True
|
||||
continue
|
||||
# Check if the node is in a safe region of the shape
|
||||
xs, ys = self.get_safe_pin_values(blockage)
|
||||
xdiff = closest(p.x, xs)
|
||||
ydiff = closest(p.y, ys)
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
if pin_safe and blockage in [self.source, self.target]:
|
||||
return False
|
||||
elif xdiff < spacing and ydiff < spacing:
|
||||
blocked = True
|
||||
return blocked
|
||||
|
||||
|
||||
def is_via_blocked(self, nodes):
|
||||
""" Return if a via on the given point is blocked by another via. """
|
||||
|
||||
# If the nodes are blocked by a blockage other than a via
|
||||
for node in nodes:
|
||||
if self.is_node_blocked(node, pin_safe=False):
|
||||
return True
|
||||
# If the nodes are blocked by a via
|
||||
x = node.center.x
|
||||
y = node.center.y
|
||||
z = node.center.z
|
||||
for via in self.graph_vias:
|
||||
ll, ur = via.rect
|
||||
# Not overlapping
|
||||
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
|
||||
continue
|
||||
center = via.center()
|
||||
# If not in the center
|
||||
if center.x != x or center.y != y:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def create_graph(self, source, target, scale=1):
|
||||
""" Create the graph to run routing on later. """
|
||||
debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target))
|
||||
|
||||
# Save source and target information
|
||||
self.source = source
|
||||
self.target = target
|
||||
|
||||
# Find the region to be routed and only include objects inside that region
|
||||
region = deepcopy(source)
|
||||
region.bbox([target])
|
||||
region.multiply(scale)
|
||||
region = region.inflated_pin(spacing=self.router.track_space)
|
||||
debug.info(3, "Routing region is {}".format(region.rect))
|
||||
|
||||
# Find the blockages that are in the routing area
|
||||
self.graph_blockages = []
|
||||
self.find_graph_blockages(region)
|
||||
|
||||
# Find the vias that are in the routing area
|
||||
self.graph_vias = []
|
||||
self.find_graph_vias(region)
|
||||
|
||||
# Generate the cartesian values from shapes in the area
|
||||
x_values, y_values = self.generate_cartesian_values()
|
||||
# Adjust the routing region to include "edge" shapes
|
||||
region.bbox(self.graph_blockages)
|
||||
# Find and include edge shapes to prevent DRC errors
|
||||
self.find_graph_blockages(region)
|
||||
# Generate the graph nodes from cartesian values
|
||||
self.generate_graph_nodes(x_values, y_values)
|
||||
# Save the graph nodes that lie in source and target shapes
|
||||
self.save_end_nodes()
|
||||
debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages)))
|
||||
debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias)))
|
||||
debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes)))
|
||||
|
||||
# Return the region to scale later if no path is found
|
||||
return region.rect
|
||||
|
||||
|
||||
def find_graph_blockages(self, region):
|
||||
""" Find blockages that overlap the routing region. """
|
||||
|
||||
for blockage in self.router.blockages:
|
||||
# Skip if already included
|
||||
if blockage in self.graph_blockages:
|
||||
continue
|
||||
# Set the region's lpp to current blockage's lpp so that the
|
||||
# overlaps method works
|
||||
region.lpp = blockage.lpp
|
||||
if region.overlaps(blockage):
|
||||
self.graph_blockages.append(blockage)
|
||||
# Make sure that the source or target fake pins are included as blockage
|
||||
for shape in [self.source, self.target]:
|
||||
for blockage in self.graph_blockages:
|
||||
blockage = blockage.get_core()
|
||||
if shape == blockage:
|
||||
break
|
||||
else:
|
||||
self.graph_blockages.append(shape)
|
||||
|
||||
|
||||
def find_graph_vias(self, region):
|
||||
""" Find vias that overlap the routing region. """
|
||||
|
||||
for via in self.router.vias:
|
||||
# Skip if already included
|
||||
if via in self.graph_vias:
|
||||
continue
|
||||
# Set the regions's lpp to current via's lpp so that the
|
||||
# overlaps method works
|
||||
region.lpp = via.lpp
|
||||
if region.overlaps(via):
|
||||
self.graph_vias.append(via)
|
||||
|
||||
|
||||
def generate_cartesian_values(self):
|
||||
"""
|
||||
Generate x and y values from all the corners of the shapes in the
|
||||
routing region.
|
||||
"""
|
||||
|
||||
x_values = set()
|
||||
y_values = set()
|
||||
|
||||
# Add inner values for blockages of the routed type
|
||||
for shape in self.graph_blockages:
|
||||
if not self.is_routable(shape):
|
||||
continue
|
||||
# Get the safe pin values
|
||||
xs, ys = self.get_safe_pin_values(shape)
|
||||
x_values.update(xs)
|
||||
y_values.update(ys)
|
||||
|
||||
# Add corners for blockages
|
||||
offset = vector([drc["grid"]] * 2)
|
||||
for blockage in self.graph_blockages:
|
||||
ll, ur = blockage.rect
|
||||
# Add minimum offset to the blockage corner nodes to prevent overlap
|
||||
nll = snap(ll - offset)
|
||||
nur = snap(ur + offset)
|
||||
x_values.update([nll.x, nur.x])
|
||||
y_values.update([nll.y, nur.y])
|
||||
|
||||
# Add center values for existing vias
|
||||
for via in self.graph_vias:
|
||||
p = via.center()
|
||||
x_values.add(p.x)
|
||||
y_values.add(p.y)
|
||||
|
||||
# Sort x and y values
|
||||
x_values = list(x_values)
|
||||
y_values = list(y_values)
|
||||
x_values.sort()
|
||||
y_values.sort()
|
||||
|
||||
return x_values, y_values
|
||||
|
||||
|
||||
def generate_graph_nodes(self, x_values, y_values):
|
||||
"""
|
||||
Generate all graph nodes using the cartesian values and connect the
|
||||
orthogonal neighbors.
|
||||
"""
|
||||
|
||||
# Generate all nodes
|
||||
self.nodes = []
|
||||
for x in x_values:
|
||||
for y in y_values:
|
||||
for z in [0, 1]:
|
||||
self.nodes.append(graph_node([x, y, z]))
|
||||
|
||||
# Mark nodes that will be removed
|
||||
self.mark_blocked_nodes()
|
||||
|
||||
# Connect closest nodes that won't be removed
|
||||
def search(index, condition, shift):
|
||||
""" Search and connect neighbor nodes. """
|
||||
base_nodes = self.nodes[index:index+2]
|
||||
found = [base_nodes[0].remove,
|
||||
base_nodes[1].remove]
|
||||
while condition(index) and not all(found):
|
||||
nodes = self.nodes[index - shift:index - shift + 2]
|
||||
for k in range(2):
|
||||
if not found[k] and not nodes[k].remove:
|
||||
found[k] = True
|
||||
if not self.is_probe_blocked(base_nodes[k].center, nodes[k].center):
|
||||
base_nodes[k].add_neighbor(nodes[k])
|
||||
index -= shift
|
||||
y_len = len(y_values)
|
||||
for i in range(0, len(self.nodes), 2):
|
||||
search(i, lambda count: (count / 2) % y_len, 2) # Down
|
||||
search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left
|
||||
if not self.nodes[i].remove and \
|
||||
not self.nodes[i + 1].remove and \
|
||||
not self.is_via_blocked(self.nodes[i:i+2]):
|
||||
self.nodes[i].add_neighbor(self.nodes[i + 1])
|
||||
|
||||
# Remove marked nodes
|
||||
self.remove_blocked_nodes()
|
||||
|
||||
|
||||
def mark_blocked_nodes(self):
|
||||
""" Mark graph nodes to be removed that are blocked by a blockage. """
|
||||
|
||||
for i in range(len(self.nodes) - 1, -1, -1):
|
||||
node = self.nodes[i]
|
||||
if self.is_node_blocked(node):
|
||||
node.remove = True
|
||||
|
||||
|
||||
def remove_blocked_nodes(self):
|
||||
""" Remove graph nodes that are marked to be removed. """
|
||||
|
||||
for i in range(len(self.nodes) - 1, -1, -1):
|
||||
node = self.nodes[i]
|
||||
if node.remove:
|
||||
node.remove_all_neighbors()
|
||||
self.nodes.remove(node)
|
||||
|
||||
|
||||
def save_end_nodes(self):
|
||||
""" Save graph nodes that are inside source and target pins. """
|
||||
|
||||
for node in self.nodes:
|
||||
if self.inside_shape(node.center, self.source):
|
||||
self.source_nodes.append(node)
|
||||
elif self.inside_shape(node.center, self.target):
|
||||
self.target_nodes.append(node)
|
||||
|
||||
|
||||
def find_shortest_path(self):
|
||||
"""
|
||||
Find the shortest path from the source node to target node using the
|
||||
A* algorithm.
|
||||
"""
|
||||
|
||||
# Heuristic function to calculate the scores
|
||||
def h(node):
|
||||
""" Return the estimated distance to the closest target. """
|
||||
min_dist = float("inf")
|
||||
for t in self.target_nodes:
|
||||
dist = t.center.distance(node.center) + abs(t.center.z - node.center.z)
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
return min_dist
|
||||
|
||||
# Initialize data structures to be used for A* search
|
||||
queue = []
|
||||
close_set = set()
|
||||
came_from = {}
|
||||
g_scores = {}
|
||||
f_scores = {}
|
||||
|
||||
# Initialize score values for the source nodes
|
||||
for node in self.source_nodes:
|
||||
g_scores[node.id] = 0
|
||||
f_scores[node.id] = h(node)
|
||||
heapq.heappush(queue, (f_scores[node.id], node.id, node))
|
||||
|
||||
# Run the A* algorithm
|
||||
while len(queue) > 0:
|
||||
# Get the closest node from the queue
|
||||
current = heapq.heappop(queue)[2]
|
||||
|
||||
# Skip this node if already discovered
|
||||
if current in close_set:
|
||||
continue
|
||||
close_set.add(current)
|
||||
|
||||
# Check if we've reached the target
|
||||
if current in self.target_nodes:
|
||||
path = []
|
||||
while current.id in came_from:
|
||||
path.append(current)
|
||||
current = came_from[current.id]
|
||||
path.append(current)
|
||||
return path
|
||||
|
||||
# Get the previous node to better calculate the next costs
|
||||
prev_node = None
|
||||
if current.id in came_from:
|
||||
prev_node = came_from[current.id]
|
||||
|
||||
# Update neighbor scores
|
||||
for node in current.neighbors:
|
||||
tentative_score = current.get_edge_cost(node, prev_node) + g_scores[current.id]
|
||||
if node.id not in g_scores or tentative_score < g_scores[node.id]:
|
||||
came_from[node.id] = current
|
||||
g_scores[node.id] = tentative_score
|
||||
f_scores[node.id] = tentative_score + h(node)
|
||||
heapq.heappush(queue, (f_scores[node.id], node.id, node))
|
||||
|
||||
# Return None if not connected
|
||||
return None
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram.tech import drc
|
||||
|
||||
|
||||
class graph_node:
|
||||
""" This class represents a node on the graph. """
|
||||
|
||||
# This is used to assign unique ids to nodes
|
||||
next_id = 0
|
||||
|
||||
def __init__(self, center):
|
||||
|
||||
self.id = graph_node.next_id
|
||||
graph_node.next_id += 1
|
||||
if isinstance(center, vector3d):
|
||||
self.center = center
|
||||
else:
|
||||
self.center = vector3d(center)
|
||||
self.neighbors = []
|
||||
self.remove = False
|
||||
|
||||
|
||||
def add_neighbor(self, other):
|
||||
""" Connect two nodes. """
|
||||
|
||||
if other not in self.neighbors:
|
||||
self.neighbors.append(other)
|
||||
other.neighbors.append(self)
|
||||
|
||||
|
||||
def remove_neighbor(self, other):
|
||||
""" Disconnect two nodes. """
|
||||
|
||||
if other in self.neighbors:
|
||||
self.neighbors.remove(other)
|
||||
other.neighbors.remove(self)
|
||||
|
||||
|
||||
def remove_all_neighbors(self):
|
||||
""" Disconnect all current neighbors. """
|
||||
|
||||
for neighbor in self.neighbors:
|
||||
self.neighbors.remove(neighbor)
|
||||
neighbor.neighbors.remove(self)
|
||||
|
||||
|
||||
def get_direction(self, b):
|
||||
""" Return the direction of path from a to b. """
|
||||
|
||||
horiz = self.center.x == b.center.x
|
||||
vert = self.center.y == b.center.y
|
||||
return (horiz, vert)
|
||||
|
||||
|
||||
def get_edge_cost(self, other, prev_node=None):
|
||||
""" Get the cost of going from this node to the other node. """
|
||||
|
||||
if other in self.neighbors:
|
||||
is_vertical = self.center.x == other.center.x
|
||||
layer_dist = self.center.distance(other.center)
|
||||
# Double the cost if the edge is in non-preferred direction
|
||||
if is_vertical != bool(self.center.z):
|
||||
layer_dist *= 2
|
||||
# Add a constant wire cost to prevent dog-legs
|
||||
if prev_node and self.get_direction(prev_node) != self.get_direction(other):
|
||||
layer_dist += drc["grid"]
|
||||
via_dist = abs(self.center.z - other.center.z)
|
||||
return layer_dist + via_dist
|
||||
return float("inf")
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
|
||||
class graph_probe:
|
||||
"""
|
||||
This class represents a probe sent from one point to another on Hanan graph.
|
||||
This is used to mimic the pin_layout class to utilize its methods.
|
||||
"""
|
||||
|
||||
def __init__(self, p1, p2, lpp):
|
||||
|
||||
self.rect = (p1.min(p2), p1.max(p2))
|
||||
self.lpp = lpp
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram.base.pin_layout import pin_layout
|
||||
from openram.base.vector import vector
|
||||
from openram.tech import drc
|
||||
from .graph_utils import snap
|
||||
|
||||
|
||||
class graph_shape(pin_layout):
|
||||
"""
|
||||
This class inherits the pin_layout class to change some of its behavior for
|
||||
the graph router.
|
||||
"""
|
||||
|
||||
def __init__(self, name, rect, layer_name_pp, core=None):
|
||||
|
||||
pin_layout.__init__(self, name, rect, layer_name_pp)
|
||||
|
||||
# Snap the shape to the grid here
|
||||
ll, ur = self.rect
|
||||
self.rect = [snap(ll), snap(ur)]
|
||||
# Core is the original shape from which this shape is inflated
|
||||
self.core = core
|
||||
|
||||
|
||||
def center(self):
|
||||
""" Override the default `center` behavior. """
|
||||
|
||||
return snap(super().center())
|
||||
|
||||
|
||||
def height(self):
|
||||
""" Override the default `height` behavior. """
|
||||
|
||||
return snap(super().height())
|
||||
|
||||
|
||||
def width(self):
|
||||
""" Override the default `width` behavior. """
|
||||
|
||||
return snap(super().width())
|
||||
|
||||
|
||||
def rename(self, new_name):
|
||||
""" Change the name of `self` and `self.core`. """
|
||||
|
||||
self.name = new_name
|
||||
self.get_core().name = new_name
|
||||
|
||||
|
||||
def get_core(self):
|
||||
"""
|
||||
Return `self` if `self.core` is None. Otherwise, return `self.core`.
|
||||
"""
|
||||
|
||||
if self.core is None:
|
||||
return self
|
||||
return self.core
|
||||
|
||||
|
||||
def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0):
|
||||
""" Override the default inflated_pin behavior. """
|
||||
|
||||
ll, ur = self.inflate(spacing, multiple)
|
||||
extra = vector([extra_spacing] * 2)
|
||||
newll = ll - extra
|
||||
newur = ur + extra
|
||||
inflated_area = (newll, newur)
|
||||
return graph_shape(self.name, inflated_area, self.layer, self)
|
||||
|
||||
|
||||
def multiply(self, scale):
|
||||
""" Multiply the width and height with the scale value. """
|
||||
|
||||
width = (self.width() * (scale - 1)) / 2
|
||||
height = (self.height() * (scale - 1)) / 2
|
||||
ll, ur = self.rect
|
||||
newll = vector(ll.x - width, ll.y - height)
|
||||
newur = vector(ur.x + width, ur.y + height)
|
||||
self.rect = [snap(newll), snap(newur)]
|
||||
|
||||
|
||||
def core_contained_by_any(self, shape_list):
|
||||
"""
|
||||
Return if the core of this shape is contained by any shape's core in the
|
||||
given list.
|
||||
"""
|
||||
|
||||
self_core = self.get_core()
|
||||
for shape in shape_list:
|
||||
shape_core = shape.get_core()
|
||||
if shape_core.contains(self_core):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def aligns(self, other):
|
||||
""" Return if the other shape aligns with this shape. """
|
||||
|
||||
# Shapes must overlap to be able to align
|
||||
if not self.overlaps(other):
|
||||
return False
|
||||
ll, ur = self.rect
|
||||
oll, our = other.rect
|
||||
if ll.x == oll.x and ur.x == our.x:
|
||||
return True
|
||||
if ll.y == oll.y and ur.y == our.y:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
"""
|
||||
Utility functions for graph router.
|
||||
"""
|
||||
from openram.base import vector
|
||||
from openram.tech import drc
|
||||
|
||||
|
||||
def snap(a):
|
||||
""" Use custom `snap` since `vector.snap_to_grid` isn't working. """
|
||||
|
||||
if isinstance(a, vector):
|
||||
return vector(snap(a.x), snap(a.y))
|
||||
return round(a, len(str(drc["grid"]).split('.')[1]))
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from openram import debug
|
||||
from openram.base.vector3d import vector3d
|
||||
from .grid_cell import grid_cell
|
||||
|
||||
|
||||
class grid:
|
||||
"""
|
||||
A two layer routing map. Each cell can be blocked in the vertical
|
||||
or horizontal layer.
|
||||
"""
|
||||
# costs are relative to a unit grid
|
||||
# non-preferred cost allows an off-direction jog of 1 grid
|
||||
# rather than 2 vias + preferred direction (cost 5)
|
||||
VIA_COST = 2
|
||||
NONPREFERRED_COST = 4
|
||||
PREFERRED_COST = 1
|
||||
|
||||
def __init__(self, ll, ur, track_width):
|
||||
""" Initialize the map and define the costs. """
|
||||
|
||||
# list of the source/target grid coordinates
|
||||
self.source = set()
|
||||
self.target = set()
|
||||
|
||||
self.track_width = track_width
|
||||
self.track_widths = [self.track_width, self.track_width, 1.0]
|
||||
self.track_factor = [1 / self.track_width, 1 / self.track_width, 1.0]
|
||||
|
||||
# The bounds are in grids for this
|
||||
# This is really lower left bottom layer and upper right top layer in 3D.
|
||||
self.ll = vector3d(ll.x, ll.y, 0).scale(self.track_factor).round()
|
||||
self.ur = vector3d(ur.x, ur.y, 0).scale(self.track_factor).round()
|
||||
debug.info(1, "BBOX coords: ll=" + str(ll) + " ur=" + str(ur))
|
||||
debug.info(1, "BBOX grids: ll=" + str(self.ll) + " ur=" + str(self.ur))
|
||||
|
||||
# let's leave the map sparse, cells are created on demand to reduce memory
|
||||
self.map={}
|
||||
|
||||
def add_all_grids(self):
|
||||
for x in range(self.ll.x, self.ur.x, 1):
|
||||
for y in range(self.ll.y, self.ur.y, 1):
|
||||
self.add_map(vector3d(x, y, 0))
|
||||
self.add_map(vector3d(x, y, 1))
|
||||
|
||||
def set_blocked(self, n, value=True):
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
self.set_blocked(item, value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].blocked=value
|
||||
|
||||
def is_blocked(self, n):
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
if self.is_blocked(item):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
self.add_map(n)
|
||||
return self.map[n].blocked
|
||||
|
||||
def is_inside(self, n):
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
if self.is_inside(item):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return n.x >= self.ll.x and n.x <= self.ur.x and n.y >= self.ll.y and n.y <= self.ur.y
|
||||
|
||||
def set_path(self, n, value=True):
|
||||
if isinstance(n, (list, tuple, set, frozenset)):
|
||||
for item in n:
|
||||
self.set_path(item, value)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].path=value
|
||||
|
||||
def clear_blockages(self):
|
||||
for k in self.map:
|
||||
self.map[k].blocked=False
|
||||
|
||||
def clear_source(self):
|
||||
for k in self.map:
|
||||
self.map[k].source=False
|
||||
self.source = set()
|
||||
|
||||
def set_source(self, n):
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
self.set_source(item)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].source=True
|
||||
self.map[n].blocked=False
|
||||
self.source.add(n)
|
||||
|
||||
def clear_target(self):
|
||||
for k in self.map:
|
||||
self.map[k].target=False
|
||||
self.target = set()
|
||||
|
||||
def set_target(self, n):
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
self.set_target(item)
|
||||
else:
|
||||
self.add_map(n)
|
||||
self.map[n].target=True
|
||||
self.map[n].blocked=False
|
||||
self.target.add(n)
|
||||
|
||||
def add_source(self, track_list):
|
||||
debug.info(3, "Adding source list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
debug.info(4, "Adding source ={0}".format(str(n)))
|
||||
self.set_source(n)
|
||||
# self.set_blocked(n, False)
|
||||
|
||||
def add_target(self, track_list):
|
||||
debug.info(3, "Adding target list={0}".format(str(track_list)))
|
||||
for n in track_list:
|
||||
debug.info(4, "Adding target ={0}".format(str(n)))
|
||||
self.set_target(n)
|
||||
# self.set_blocked(n, False)
|
||||
|
||||
def get_perimeter_list(self, side="left", layers=[0, 1], width=1, margin=0, offset=0):
|
||||
"""
|
||||
Side specifies which side.
|
||||
Layer specifies horizontal (0) or vertical (1)
|
||||
Width specifies how wide the perimeter "stripe" should be.
|
||||
Works from the inside out from the bbox (ll, ur)
|
||||
"""
|
||||
if "ring" in side:
|
||||
ring_width = width
|
||||
else:
|
||||
ring_width = 0
|
||||
|
||||
if "ring" in side:
|
||||
ring_offset = offset
|
||||
else:
|
||||
ring_offset = 0
|
||||
|
||||
perimeter_list = []
|
||||
# Add the left/right columns
|
||||
if side=="all" or "left" in side:
|
||||
for x in range(self.ll.x - offset, self.ll.x - width - offset, -1):
|
||||
for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1):
|
||||
for layer in layers:
|
||||
perimeter_list.append(vector3d(x, y, layer))
|
||||
|
||||
if side=="all" or "right" in side:
|
||||
for x in range(self.ur.x + offset, self.ur.x + width + offset, 1):
|
||||
for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1):
|
||||
for layer in layers:
|
||||
perimeter_list.append(vector3d(x, y, layer))
|
||||
|
||||
if side=="all" or "bottom" in side:
|
||||
for y in range(self.ll.y - offset, self.ll.y - width - offset, -1):
|
||||
for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1):
|
||||
for layer in layers:
|
||||
perimeter_list.append(vector3d(x, y, layer))
|
||||
|
||||
if side=="all" or "top" in side:
|
||||
for y in range(self.ur.y + offset, self.ur.y + width + offset, 1):
|
||||
for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1):
|
||||
for layer in layers:
|
||||
perimeter_list.append(vector3d(x, y, layer))
|
||||
|
||||
# Add them all to the map
|
||||
self.add_map(perimeter_list)
|
||||
|
||||
return perimeter_list
|
||||
|
||||
def add_perimeter_target(self, side="all", layers=[0, 1]):
|
||||
debug.info(3, "Adding perimeter target")
|
||||
|
||||
perimeter_list = self.get_perimeter_list(side, layers)
|
||||
|
||||
self.set_target(perimeter_list)
|
||||
|
||||
def is_target(self, point):
|
||||
"""
|
||||
Point is in the target set, so we are done.
|
||||
"""
|
||||
return point in self.target
|
||||
|
||||
def add_map(self, n):
|
||||
"""
|
||||
Add a point to the map if it doesn't exist.
|
||||
"""
|
||||
if not isinstance(n, vector3d):
|
||||
for item in n:
|
||||
self.add_map(item)
|
||||
else:
|
||||
if n not in self.map:
|
||||
self.map[n]=grid_cell()
|
||||
|
||||
def block_path(self, path):
|
||||
"""
|
||||
Mark the path in the routing grid as blocked.
|
||||
Also unsets the path flag.
|
||||
"""
|
||||
path.set_path(False)
|
||||
path.set_blocked(True)
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 grid_cell:
|
||||
"""
|
||||
A single cell that can be occupied in a given layer, blocked,
|
||||
visited, etc.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.path = False
|
||||
self.blocked = False
|
||||
self.source = False
|
||||
self.target = False
|
||||
# -1 means it isn't visited yet
|
||||
self.min_cost = -1
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Reset the dynamic info about routing.
|
||||
"""
|
||||
self.min_cost=-1
|
||||
self.min_path=None
|
||||
self.blocked=False
|
||||
self.source=False
|
||||
self.target=False
|
||||
|
||||
def get_cost(self):
|
||||
# We can display the cost of the frontier
|
||||
if self.min_cost > 0:
|
||||
return self.min_cost
|
||||
|
||||
def get_type(self):
|
||||
type_string = ""
|
||||
|
||||
if self.blocked:
|
||||
type_string += "X"
|
||||
|
||||
if self.source:
|
||||
type_string += "S"
|
||||
|
||||
if self.target:
|
||||
type_string += "T"
|
||||
|
||||
if self.path:
|
||||
type_string += "P"
|
||||
|
||||
return type_string
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from itertools import tee
|
||||
from openram.base.vector3d import vector3d
|
||||
from .grid import grid
|
||||
from .direction import direction
|
||||
|
||||
|
||||
class grid_path:
|
||||
"""
|
||||
A grid path is a list of lists of grid cells.
|
||||
It can have a width that is more than one cell.
|
||||
All of the sublists will be the same dimension.
|
||||
Cells should be continguous.
|
||||
It can have a name to define pin shapes as well.
|
||||
"""
|
||||
|
||||
def __init__(self, items=[], name=""):
|
||||
self.name = name
|
||||
if items:
|
||||
self.pathlist = [items]
|
||||
else:
|
||||
self.pathlist = []
|
||||
|
||||
def __str__(self):
|
||||
p = str(self.pathlist)
|
||||
if self.name != "":
|
||||
return (str(self.name) + " : " + p)
|
||||
return p
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
"""
|
||||
override setitem function
|
||||
can set value by pathinstance[index]=value
|
||||
"""
|
||||
self.pathlist[index]=value
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""
|
||||
override getitem function
|
||||
can get value by value=pathinstance[index]
|
||||
"""
|
||||
return self.pathlist[index]
|
||||
|
||||
def __contains__(self, key):
|
||||
"""
|
||||
Determine if cell exists in this path
|
||||
"""
|
||||
# FIXME: Could maintain a hash to make in O(1)
|
||||
for sublist in self.pathlist:
|
||||
for item in sublist:
|
||||
if item == key:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __add__(self, items):
|
||||
"""
|
||||
Override add to do append
|
||||
"""
|
||||
return self.pathlist.extend(items)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.pathlist)
|
||||
|
||||
def trim_last(self):
|
||||
"""
|
||||
Drop the last item
|
||||
"""
|
||||
if len(self.pathlist)>0:
|
||||
self.pathlist.pop()
|
||||
|
||||
def trim_first(self):
|
||||
"""
|
||||
Drop the first item
|
||||
"""
|
||||
if len(self.pathlist)>0:
|
||||
self.pathlist.pop(0)
|
||||
|
||||
def append(self,item):
|
||||
"""
|
||||
Append the list of items to the cells
|
||||
"""
|
||||
self.pathlist.append(item)
|
||||
|
||||
def extend(self,item):
|
||||
"""
|
||||
Extend the list of items to the cells
|
||||
"""
|
||||
self.pathlist.extend(item)
|
||||
|
||||
def set_path(self,value=True):
|
||||
for sublist in self.pathlist:
|
||||
for p in sublist:
|
||||
p.path=value
|
||||
|
||||
def set_blocked(self,value=True):
|
||||
for sublist in self.pathlist:
|
||||
for p in sublist:
|
||||
p.blocked=value
|
||||
|
||||
def get_grids(self):
|
||||
"""
|
||||
Return a set of all the grids in this path.
|
||||
"""
|
||||
newset = set()
|
||||
for sublist in self.pathlist:
|
||||
newset.update(sublist)
|
||||
return newset
|
||||
|
||||
def get_wire_grids(self, start_index, end_index):
|
||||
"""
|
||||
Return a set of all the wire grids in this path.
|
||||
These are the indices in the wave path in a certain range.
|
||||
"""
|
||||
newset = set()
|
||||
for sublist in self.pathlist:
|
||||
newset.update(sublist[start_index:end_index])
|
||||
return newset
|
||||
|
||||
def cost(self):
|
||||
"""
|
||||
The cost of the path is the length plus a penalty for the number
|
||||
of vias. We assume that non-preferred direction is penalized.
|
||||
This cost only works with 1 wide tracks.
|
||||
"""
|
||||
|
||||
# Ignore the source pin layer change, FIXME?
|
||||
def pairwise(iterable):
|
||||
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
||||
a, b = tee(iterable)
|
||||
next(b, None)
|
||||
return zip(a, b)
|
||||
|
||||
plist = list(pairwise(self.pathlist))
|
||||
cost = 0
|
||||
for p0list,p1list in plist:
|
||||
# This is because they are "waves" so pick the first item
|
||||
p0=p0list[0]
|
||||
p1=p1list[0]
|
||||
|
||||
if p0.z != p1.z: # via
|
||||
cost += grid.VIA_COST
|
||||
elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer
|
||||
cost += grid.NONPREFERRED_COST
|
||||
elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer
|
||||
cost += grid.NONPREFERRED_COST
|
||||
else:
|
||||
cost += grid.PREFERRED_COST
|
||||
|
||||
return cost
|
||||
|
||||
def expand_dirs(self):
|
||||
"""
|
||||
Expand from the end in each of the four cardinal directions plus up
|
||||
or down but not expanding to blocked cells. Expands in all
|
||||
directions regardless of preferred directions.
|
||||
|
||||
If the width is more than one, it can only expand in one direction
|
||||
(for now). This is assumed for the supply router for now.
|
||||
|
||||
"""
|
||||
neighbors = []
|
||||
|
||||
for d in direction.cardinal_directions(True):
|
||||
n = self.neighbor(d)
|
||||
if n:
|
||||
neighbors.append(n)
|
||||
|
||||
return neighbors
|
||||
|
||||
def neighbor(self, d):
|
||||
offset = direction.get_offset(d)
|
||||
|
||||
newwave = [point + offset for point in self.pathlist[-1]]
|
||||
|
||||
if newwave in self.pathlist:
|
||||
return None
|
||||
elif newwave[0].z>1 or newwave[0].z<0:
|
||||
return None
|
||||
|
||||
return newwave
|
||||
|
||||
def set_layer(self, zindex):
|
||||
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
|
||||
self.pathlist = new_pathlist
|
||||
|
||||
def overlap(self, other):
|
||||
"""
|
||||
Return the overlap waves ignoring different layers
|
||||
"""
|
||||
|
||||
my_zindex = self.pathlist[0][0].z
|
||||
other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave]
|
||||
# This keeps the wave structure of the self layer
|
||||
shared_waves = []
|
||||
for wave in self.pathlist:
|
||||
for item in wave:
|
||||
# If any item in the wave is not contained, skip it
|
||||
if not item in other_flat_cells:
|
||||
break
|
||||
else:
|
||||
shared_waves.append(wave)
|
||||
|
||||
if len(shared_waves)>0:
|
||||
ll = shared_waves[0][0]
|
||||
ur = shared_waves[-1][-1]
|
||||
return [ll,ur]
|
||||
return None
|
||||
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
"""
|
||||
Some utility functions for sets of grid cells.
|
||||
"""
|
||||
|
||||
import math
|
||||
from openram.base.vector3d import vector3d
|
||||
from .direction import direction
|
||||
|
||||
|
||||
def increment_set(curset, direct):
|
||||
"""
|
||||
Return the cells incremented in given direction
|
||||
"""
|
||||
offset = direction.get_offset(direct)
|
||||
|
||||
newset = set()
|
||||
for c in curset:
|
||||
newc = c+offset
|
||||
newset.add(newc)
|
||||
|
||||
return newset
|
||||
|
||||
|
||||
def remove_border(curset, direct):
|
||||
"""
|
||||
Remove the cells on a given border.
|
||||
"""
|
||||
border = get_border(curset, direct)
|
||||
curset.difference_update(border)
|
||||
|
||||
|
||||
def get_upper_right(curset):
|
||||
ur = None
|
||||
for p in curset:
|
||||
if ur == None or (p.x>=ur.x and p.y>=ur.y):
|
||||
ur = p
|
||||
return ur
|
||||
|
||||
|
||||
def get_lower_left(curset):
|
||||
ll = None
|
||||
for p in curset:
|
||||
if ll == None or (p.x<=ll.x and p.y<=ll.y):
|
||||
ll = p
|
||||
return ll
|
||||
|
||||
|
||||
def get_border(curset, direct):
|
||||
"""
|
||||
Return the furthest cell(s) in a given direction.
|
||||
"""
|
||||
|
||||
# find direction-most cell(s)
|
||||
maxc = []
|
||||
if direct==direction.NORTH:
|
||||
for c in curset:
|
||||
if len(maxc)==0 or c.y>maxc[0].y:
|
||||
maxc = [c]
|
||||
elif c.y==maxc[0].y:
|
||||
maxc.append(c)
|
||||
elif direct==direct.SOUTH:
|
||||
for c in curset:
|
||||
if len(maxc)==0 or c.y<maxc[0].y:
|
||||
maxc = [c]
|
||||
elif c.y==maxc[0].y:
|
||||
maxc.append(c)
|
||||
elif direct==direct.EAST:
|
||||
for c in curset:
|
||||
if len(maxc)==0 or c.x>maxc[0].x:
|
||||
maxc = [c]
|
||||
elif c.x==maxc[0].x:
|
||||
maxc.append(c)
|
||||
elif direct==direct.WEST:
|
||||
for c in curset:
|
||||
if len(maxc)==0 or c.x<maxc[0].x:
|
||||
maxc = [c]
|
||||
elif c.x==maxc[0].x:
|
||||
maxc.append(c)
|
||||
|
||||
newset = set(maxc)
|
||||
return newset
|
||||
|
||||
|
||||
def expand_border(curset, direct):
|
||||
"""
|
||||
Expand the current set of sells in a given direction.
|
||||
Only return the contiguous cells.
|
||||
"""
|
||||
border_set = get_border(curset, direct)
|
||||
next_border_set = increment_set(border_set, direct)
|
||||
return next_border_set
|
||||
|
||||
|
||||
def expand_borders(curset):
|
||||
"""
|
||||
Return the expansions in planar directions.
|
||||
"""
|
||||
north_set=expand_border(curset,direction.NORTH)
|
||||
south_set=expand_border(curset,direction.SOUTH)
|
||||
east_set=expand_border(curset,direction.EAST)
|
||||
west_set=expand_border(curset,direction.WEST)
|
||||
|
||||
return(north_set, east_set, south_set, west_set)
|
||||
|
||||
|
||||
def inflate_cell(cell, distance):
|
||||
"""
|
||||
Expand the current cell in all directions and return the set.
|
||||
"""
|
||||
newset = set(cell)
|
||||
|
||||
if distance==0:
|
||||
return(newset)
|
||||
|
||||
# recursively call this based on the distance
|
||||
for offset in direction.all_offsets():
|
||||
# FIXME: If distance is large this will be inefficient, but it is like 1 or 2
|
||||
newset.update(inflate_cell(cell+offset,distance-1))
|
||||
|
||||
return newset
|
||||
|
||||
|
||||
def inflate_set(curset, distance):
|
||||
"""
|
||||
Expand the set in all directions by the given number of grids.
|
||||
"""
|
||||
if distance<=0:
|
||||
return curset
|
||||
|
||||
newset = curset.copy()
|
||||
# Add all my neighbors
|
||||
for c in curset:
|
||||
newset.update(direction.all_neighbors(c))
|
||||
# Recurse with less depth
|
||||
return inflate_set(newset,distance-1)
|
||||
|
||||
|
||||
def flatten_set(curset):
|
||||
"""
|
||||
Flatten until we have a set of vector3d objects.
|
||||
"""
|
||||
newset = set()
|
||||
for c in curset:
|
||||
if isinstance(c,vector3d):
|
||||
newset.add(c)
|
||||
else:
|
||||
newset.update(flatten_set(c))
|
||||
return newset
|
||||
|
||||
|
||||
def distance_set(coord, curset):
|
||||
"""
|
||||
Return the distance from a coordinate to any item in the set
|
||||
"""
|
||||
min_dist = math.inf
|
||||
for c in curset:
|
||||
min_dist = min(coord.euclidean_distance(c), min_dist)
|
||||
|
||||
return min_dist
|
||||
|
||||
|
|
@ -1,689 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from openram import debug
|
||||
from openram.base.vector import vector
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram.base.pin_layout import pin_layout
|
||||
from .direction import direction
|
||||
|
||||
|
||||
class pin_group:
|
||||
"""
|
||||
A class to represent a group of rectangular design pin.
|
||||
It requires a router to define the track widths and blockages which
|
||||
determine how pin shapes get mapped to tracks.
|
||||
It is initially constructed with a single set of (touching) pins.
|
||||
"""
|
||||
|
||||
def __init__(self, name, pin_set, router):
|
||||
self.name = name
|
||||
# Flag for when it is routed
|
||||
self.routed = False
|
||||
# Flag for when it is enclosed
|
||||
self.enclosed = False
|
||||
|
||||
# This is a list because we can have a pin
|
||||
# group of disconnected sets of pins
|
||||
# and these are represented by separate lists
|
||||
self.pins = set(pin_set)
|
||||
# Remove any redundant pins (i.e. contained in other pins)
|
||||
self.remove_redundant_pins()
|
||||
|
||||
self.router = router
|
||||
# These are the corresponding pin grids for each pin group.
|
||||
self.grids = set()
|
||||
# These are the secondary grids that could
|
||||
# or could not be part of the pin
|
||||
self.secondary_grids = set()
|
||||
|
||||
# The set of blocked grids due to this pin
|
||||
self.blockages = set()
|
||||
|
||||
# This is a set of pin_layout shapes to cover the grids
|
||||
self.enclosures = set()
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
total_string = "(pg {} ".format(self.name)
|
||||
|
||||
pin_string = "\n pins={}".format(self.pins)
|
||||
total_string += pin_string
|
||||
|
||||
grids_string = "\n grids={}".format(self.grids)
|
||||
total_string += grids_string
|
||||
|
||||
grids_string = "\n secondary={}".format(self.secondary_grids)
|
||||
total_string += grids_string
|
||||
|
||||
if self.enclosed:
|
||||
enclosure_string = "\n enclose={}".format(self.enclosures)
|
||||
total_string += enclosure_string
|
||||
|
||||
total_string += ")"
|
||||
return total_string
|
||||
|
||||
def add_pin(self, pin):
|
||||
self.pins.add(pin)
|
||||
self.remove_redundant_pins()
|
||||
|
||||
def __repr__(self):
|
||||
""" override repr function output """
|
||||
return str(self)
|
||||
|
||||
def size(self):
|
||||
return len(self.grids)
|
||||
|
||||
def set_routed(self, value=True):
|
||||
self.routed = value
|
||||
|
||||
def is_routed(self):
|
||||
return self.routed
|
||||
|
||||
def remove_redundant_pins(self):
|
||||
"""
|
||||
Remove redundant pin shapes
|
||||
"""
|
||||
new_pin_list = self.remove_redundant_shapes(list(self.pins))
|
||||
self.pins = set(new_pin_list)
|
||||
|
||||
def remove_redundant_shapes(self, pin_list):
|
||||
"""
|
||||
Remove any pin layout that is contained within another.
|
||||
Returns a new list without modifying pin_list.
|
||||
"""
|
||||
local_debug = False
|
||||
if local_debug:
|
||||
debug.info(0, "INITIAL: {}".format(pin_list))
|
||||
|
||||
add_indices = set(range(len(pin_list)))
|
||||
# This is n^2, but the number is small
|
||||
for index1, pin1 in enumerate(pin_list):
|
||||
# If we remove this pin, it can't contain other pins
|
||||
if index1 not in add_indices:
|
||||
continue
|
||||
|
||||
for index2, pin2 in enumerate(pin_list):
|
||||
# Can't contain yourself,
|
||||
# but compare the indices and not the pins
|
||||
# so you can remove duplicate copies.
|
||||
if index1 == index2:
|
||||
continue
|
||||
# If we already removed it, can't remove it again...
|
||||
if index2 not in add_indices:
|
||||
continue
|
||||
|
||||
if pin1.contains(pin2):
|
||||
if local_debug:
|
||||
debug.info(0, "{0} contains {1}".format(pin1, pin2))
|
||||
add_indices.remove(index2)
|
||||
|
||||
new_pin_list = [pin_list[x] for x in add_indices]
|
||||
|
||||
if local_debug:
|
||||
debug.info(0, "FINAL : {}".format(new_pin_list))
|
||||
|
||||
return new_pin_list
|
||||
|
||||
def compute_enclosures(self):
|
||||
"""
|
||||
Find the minimum rectangle enclosures of the given tracks.
|
||||
"""
|
||||
# Enumerate every possible enclosure
|
||||
pin_list = []
|
||||
for seed in self.grids:
|
||||
(ll, ur) = self.enclose_pin_grids(seed,
|
||||
direction.NORTH,
|
||||
direction.EAST)
|
||||
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
|
||||
pin_list.append(enclosure)
|
||||
|
||||
(ll, ur) = self.enclose_pin_grids(seed,
|
||||
direction.EAST,
|
||||
direction.NORTH)
|
||||
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
|
||||
pin_list.append(enclosure)
|
||||
|
||||
if len(pin_list) == 0:
|
||||
debug.error("Did not find any enclosures for {}".format(self.name))
|
||||
self.router.write_debug_gds("pin_enclosure_error.gds")
|
||||
|
||||
# Now simplify the enclosure list
|
||||
new_pin_list = self.remove_redundant_shapes(pin_list)
|
||||
|
||||
# Now add the right name
|
||||
for pin in new_pin_list:
|
||||
pin.name = self.name
|
||||
|
||||
debug.check(len(new_pin_list) > 0,
|
||||
"Did not find any enclosures.")
|
||||
|
||||
return new_pin_list
|
||||
|
||||
def compute_connector(self, pin, enclosure):
|
||||
"""
|
||||
Compute a shape to connect the pin to the enclosure shape.
|
||||
This assumes the shape will be the dimension of the pin.
|
||||
"""
|
||||
if pin.xoverlaps(enclosure):
|
||||
# Is it vertical overlap, extend pin shape to enclosure
|
||||
plc = pin.lc()
|
||||
prc = pin.rc()
|
||||
elc = enclosure.lc()
|
||||
# erc = enclosure.rc()
|
||||
ymin = min(plc.y, elc.y)
|
||||
ymax = max(plc.y, elc.y)
|
||||
ll = vector(plc.x, ymin)
|
||||
ur = vector(prc.x, ymax)
|
||||
elif pin.yoverlaps(enclosure):
|
||||
# Is it horizontal overlap, extend pin shape to enclosure
|
||||
pbc = pin.bc()
|
||||
puc = pin.uc()
|
||||
ebc = enclosure.bc()
|
||||
# euc = enclosure.uc()
|
||||
xmin = min(pbc.x, ebc.x)
|
||||
xmax = max(pbc.x, ebc.x)
|
||||
ll = vector(xmin, pbc.y)
|
||||
ur = vector(xmax, puc.y)
|
||||
else:
|
||||
# Neither, so we must do a corner-to corner
|
||||
pc = pin.center()
|
||||
ec = enclosure.center()
|
||||
xmin = min(pc.x, ec.x)
|
||||
xmax = max(pc.x, ec.x)
|
||||
ymin = min(pc.y, ec.y)
|
||||
ymax = max(pc.y, ec.y)
|
||||
ll = vector(xmin, ymin)
|
||||
ur = vector(xmax, ymax)
|
||||
|
||||
if ll.x == ur.x or ll.y == ur.y:
|
||||
return None
|
||||
p = pin_layout(pin.name, [ll, ur], pin.layer)
|
||||
return p
|
||||
|
||||
def find_above_connector(self, pin, enclosures):
|
||||
"""
|
||||
Find the enclosure that is to above the pin
|
||||
and make a connector to it's upper edge.
|
||||
"""
|
||||
# Create the list of shapes that contain the pin edge
|
||||
edge_list = []
|
||||
for shape in enclosures:
|
||||
if shape.xcontains(pin):
|
||||
edge_list.append(shape)
|
||||
|
||||
# Sort them by their bottom edge
|
||||
edge_list.sort(key=lambda x: x.by(), reverse=True)
|
||||
|
||||
# Find the bottom edge that is next to the pin's top edge
|
||||
above_item = None
|
||||
for item in edge_list:
|
||||
if item.by() >= pin.uy():
|
||||
above_item = item
|
||||
else:
|
||||
break
|
||||
|
||||
# There was nothing
|
||||
if not above_item:
|
||||
return None
|
||||
# If it already overlaps, no connector needed
|
||||
if above_item.overlaps(pin):
|
||||
return None
|
||||
|
||||
# Otherwise, make a connector to the item
|
||||
p = self.compute_connector(pin, above_item)
|
||||
return p
|
||||
|
||||
def find_below_connector(self, pin, enclosures):
|
||||
"""
|
||||
Find the enclosure that is below the pin
|
||||
and make a connector to it's upper edge.
|
||||
"""
|
||||
# Create the list of shapes that contain the pin edge
|
||||
edge_list = []
|
||||
for shape in enclosures:
|
||||
if shape.xcontains(pin):
|
||||
edge_list.append(shape)
|
||||
|
||||
# Sort them by their upper edge
|
||||
edge_list.sort(key=lambda x: x.uy())
|
||||
|
||||
# Find the upper edge that is next to the pin's bottom edge
|
||||
bottom_item = None
|
||||
for item in edge_list:
|
||||
if item.uy() <= pin.by():
|
||||
bottom_item = item
|
||||
else:
|
||||
break
|
||||
|
||||
# There was nothing to the left
|
||||
if not bottom_item:
|
||||
return None
|
||||
# If it already overlaps, no connector needed
|
||||
if bottom_item.overlaps(pin):
|
||||
return None
|
||||
|
||||
# Otherwise, make a connector to the item
|
||||
p = self.compute_connector(pin, bottom_item)
|
||||
return p
|
||||
|
||||
def find_left_connector(self, pin, enclosures):
|
||||
"""
|
||||
Find the enclosure that is to the left of the pin
|
||||
and make a connector to it's right edge.
|
||||
"""
|
||||
# Create the list of shapes that contain the pin edge
|
||||
edge_list = []
|
||||
for shape in enclosures:
|
||||
if shape.ycontains(pin):
|
||||
edge_list.append(shape)
|
||||
|
||||
# Sort them by their right edge
|
||||
edge_list.sort(key=lambda x: x.rx())
|
||||
|
||||
# Find the right edge that is to the pin's left edge
|
||||
left_item = None
|
||||
for item in edge_list:
|
||||
if item.rx() <= pin.lx():
|
||||
left_item = item
|
||||
else:
|
||||
break
|
||||
|
||||
# There was nothing to the left
|
||||
if not left_item:
|
||||
return None
|
||||
# If it already overlaps, no connector needed
|
||||
if left_item.overlaps(pin):
|
||||
return None
|
||||
|
||||
# Otherwise, make a connector to the item
|
||||
p = self.compute_connector(pin, left_item)
|
||||
return p
|
||||
|
||||
def find_right_connector(self, pin, enclosures):
|
||||
"""
|
||||
Find the enclosure that is to the right of the pin
|
||||
and make a connector to it's left edge.
|
||||
"""
|
||||
# Create the list of shapes that contain the pin edge
|
||||
edge_list = []
|
||||
for shape in enclosures:
|
||||
if shape.ycontains(pin):
|
||||
edge_list.append(shape)
|
||||
|
||||
# Sort them by their right edge
|
||||
edge_list.sort(key=lambda x: x.lx(), reverse=True)
|
||||
|
||||
# Find the left edge that is next to the pin's right edge
|
||||
right_item = None
|
||||
for item in edge_list:
|
||||
if item.lx() >= pin.rx():
|
||||
right_item = item
|
||||
else:
|
||||
break
|
||||
|
||||
# There was nothing to the right
|
||||
if not right_item:
|
||||
return None
|
||||
# If it already overlaps, no connector needed
|
||||
if right_item.overlaps(pin):
|
||||
return None
|
||||
|
||||
# Otherwise, make a connector to the item
|
||||
p = self.compute_connector(pin, right_item)
|
||||
return p
|
||||
|
||||
def find_smallest_connector(self, pin_list, shape_list):
|
||||
"""
|
||||
Compute all of the connectors between the overlapping
|
||||
pins and enclosure shape list.
|
||||
Return the smallest.
|
||||
"""
|
||||
smallest = None
|
||||
for pin in pin_list:
|
||||
for enclosure in shape_list:
|
||||
new_enclosure = self.compute_connector(pin, enclosure)
|
||||
if not smallest or new_enclosure.area() < smallest.area():
|
||||
smallest = new_enclosure
|
||||
|
||||
return smallest
|
||||
|
||||
def find_smallest_overlapping(self, pin_list, shape_list):
|
||||
"""
|
||||
Find the smallest area shape in shape_list that overlaps with any
|
||||
pin in pin_list by a min width.
|
||||
"""
|
||||
|
||||
smallest_shape = None
|
||||
for pin in pin_list:
|
||||
overlap_shape = self.find_smallest_overlapping_pin(pin, shape_list)
|
||||
if overlap_shape:
|
||||
# overlap_length = pin.overlap_length(overlap_shape)
|
||||
if not smallest_shape or overlap_shape.area() < smallest_shape.area():
|
||||
smallest_shape = overlap_shape
|
||||
|
||||
return smallest_shape
|
||||
|
||||
def find_smallest_overlapping_pin(self, pin, shape_list):
|
||||
"""
|
||||
Find the smallest area shape in shape_list that overlaps with any
|
||||
pin in pin_list by a min width.
|
||||
"""
|
||||
|
||||
smallest_shape = None
|
||||
zindex = self.router.get_zindex(pin.lpp[0])
|
||||
(min_width, min_space) = self.router.get_layer_width_space(zindex)
|
||||
|
||||
# Now compare it with every other shape to check how much they overlap
|
||||
for other in shape_list:
|
||||
overlap_length = pin.overlap_length(other)
|
||||
if overlap_length > min_width:
|
||||
if not smallest_shape or other.area() < smallest_shape.area():
|
||||
smallest_shape = other
|
||||
|
||||
return smallest_shape
|
||||
|
||||
def overlap_any_shape(self, pin_list, shape_list):
|
||||
"""
|
||||
Does the given pin overlap any of the shapes in the pin list.
|
||||
"""
|
||||
for pin in pin_list:
|
||||
for other in shape_list:
|
||||
if pin.overlaps(other):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def max_pin_layout(self, pin_list):
|
||||
"""
|
||||
Return the max area pin_layout
|
||||
"""
|
||||
biggest = pin_list[0]
|
||||
for pin in pin_list:
|
||||
if pin.area() > biggest.area():
|
||||
biggest = pin
|
||||
|
||||
return pin
|
||||
|
||||
def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST):
|
||||
"""
|
||||
This encloses a single pin component with a rectangle
|
||||
starting with the seed and expanding dir1 until blocked
|
||||
and then dir2 until blocked.
|
||||
dir1 and dir2 should be two orthogonal directions.
|
||||
"""
|
||||
|
||||
offset1 = direction.get_offset(dir1)
|
||||
offset2 = direction.get_offset(dir2)
|
||||
|
||||
# We may have started with an empty set
|
||||
debug.check(len(self.grids) > 0, "Cannot seed an grid empty set.")
|
||||
|
||||
common_blockages = self.router.get_blocked_grids() & self.grids
|
||||
|
||||
# Start with the ll and make the widest row
|
||||
row = [ll]
|
||||
# Move in dir1 while we can
|
||||
while True:
|
||||
next_cell = row[-1] + offset1
|
||||
# Can't move if not in the pin shape
|
||||
if next_cell in self.grids and next_cell not in common_blockages:
|
||||
row.append(next_cell)
|
||||
else:
|
||||
break
|
||||
# Move in dir2 while we can
|
||||
while True:
|
||||
next_row = [x + offset2 for x in row]
|
||||
for cell in next_row:
|
||||
# Can't move if any cell is not in the pin shape
|
||||
if cell not in self.grids or cell in common_blockages:
|
||||
break
|
||||
else:
|
||||
row = next_row
|
||||
# Skips the second break
|
||||
continue
|
||||
# Breaks from the nested break
|
||||
break
|
||||
|
||||
# Add a shape from ll to ur
|
||||
ur = row[-1]
|
||||
return (ll, ur)
|
||||
|
||||
def enclose_pin(self):
|
||||
"""
|
||||
If there is one set of connected pin shapes,
|
||||
this will find the smallest rectangle enclosure that
|
||||
overlaps with any pin.
|
||||
If there is not, it simply returns all the enclosures.
|
||||
"""
|
||||
self.enclosed = True
|
||||
|
||||
# Compute the enclosure pin_layout list of the set of tracks
|
||||
self.enclosures = self.compute_enclosures()
|
||||
|
||||
# Find a connector to every pin and add it to the enclosures
|
||||
for pin in self.pins:
|
||||
|
||||
# If it is contained, it won't need a connector
|
||||
if pin.contained_by_any(self.enclosures):
|
||||
continue
|
||||
|
||||
# Find a connector in the cardinal directions
|
||||
# If there is overlap, but it isn't contained,
|
||||
# these could all be None
|
||||
# These could also be none if the pin is
|
||||
# diagonal from the enclosure
|
||||
left_connector = self.find_left_connector(pin, self.enclosures)
|
||||
right_connector = self.find_right_connector(pin, self.enclosures)
|
||||
above_connector = self.find_above_connector(pin, self.enclosures)
|
||||
below_connector = self.find_below_connector(pin, self.enclosures)
|
||||
connector_list = [left_connector,
|
||||
right_connector,
|
||||
above_connector,
|
||||
below_connector]
|
||||
filtered_list = list(filter(lambda x: x != None, connector_list))
|
||||
if (len(filtered_list) > 0):
|
||||
import copy
|
||||
bbox_connector = copy.copy(pin)
|
||||
bbox_connector.bbox(filtered_list)
|
||||
self.enclosures.append(bbox_connector)
|
||||
|
||||
# Now, make sure each pin touches an enclosure.
|
||||
# If not, add another (diagonal) connector.
|
||||
# This could only happen when there was no enclosure
|
||||
# in any cardinal direction from a pin
|
||||
if not self.overlap_any_shape(self.pins, self.enclosures):
|
||||
connector = self.find_smallest_connector(self.pins,
|
||||
self.enclosures)
|
||||
if not connector:
|
||||
debug.error("Could not find a connector for {} with {}".format(self.pins,
|
||||
self.enclosures))
|
||||
self.router.write_debug_gds("no_connector.gds")
|
||||
self.enclosures.append(connector)
|
||||
|
||||
# At this point, the pins are overlapping,
|
||||
# but there might be more than one!
|
||||
overlap_set = set()
|
||||
for pin in self.pins:
|
||||
overlap_set.update(self.transitive_overlap(pin, self.enclosures))
|
||||
# Use the new enclosures and recompute the grids
|
||||
# that correspond to them
|
||||
if len(overlap_set) < len(self.enclosures):
|
||||
self.enclosures = overlap_set
|
||||
self.grids = set()
|
||||
# Also update the grid locations with the new
|
||||
# (possibly pruned) enclosures
|
||||
for enclosure in self.enclosures:
|
||||
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
|
||||
enclosure)
|
||||
self.grids.update(sufficient)
|
||||
|
||||
debug.info(3, "Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name,
|
||||
self.pins,
|
||||
self.grids,
|
||||
self.enclosures))
|
||||
|
||||
def transitive_overlap(self, shape, shape_list):
|
||||
"""
|
||||
Given shape, find the elements in shape_list that overlap transitively.
|
||||
I.e. if shape overlaps A and A overlaps B, return both A and B.
|
||||
"""
|
||||
|
||||
augmented_shape_list = set(shape_list)
|
||||
old_connected_set = set()
|
||||
connected_set = set([shape])
|
||||
# Repeat as long as we expand the set
|
||||
while len(connected_set) > len(old_connected_set):
|
||||
old_connected_set = connected_set
|
||||
connected_set = set([shape])
|
||||
for old_shape in old_connected_set:
|
||||
for cur_shape in augmented_shape_list:
|
||||
if old_shape.overlaps(cur_shape):
|
||||
connected_set.add(cur_shape)
|
||||
|
||||
# Remove the original shape
|
||||
connected_set.remove(shape)
|
||||
|
||||
# if len(connected_set)<len(shape_list):
|
||||
# import pprint
|
||||
# print("S: ",shape)
|
||||
# pprint.pprint(shape_list)
|
||||
# pprint.pprint(connected_set)
|
||||
|
||||
return connected_set
|
||||
|
||||
def add_enclosure(self, cell):
|
||||
"""
|
||||
Add the enclosure shape to the given cell.
|
||||
"""
|
||||
for enclosure in self.enclosures:
|
||||
debug.info(4, "Adding enclosure {0} {1}".format(self.name,
|
||||
enclosure))
|
||||
cell.add_rect(layer=enclosure.layer,
|
||||
offset=enclosure.ll(),
|
||||
width=enclosure.width(),
|
||||
height=enclosure.height())
|
||||
|
||||
def perimeter_grids(self):
|
||||
"""
|
||||
Return a list of the grids on the perimeter.
|
||||
This assumes that we have a single contiguous shape.
|
||||
"""
|
||||
perimeter_set = set()
|
||||
cardinal_offsets = direction.cardinal_offsets()
|
||||
for g1 in self.grids:
|
||||
neighbor_grids = [g1 + offset for offset in cardinal_offsets]
|
||||
neighbor_count = sum([x in self.grids for x in neighbor_grids])
|
||||
# If we aren't completely enclosed, we are on the perimeter
|
||||
if neighbor_count < 4:
|
||||
perimeter_set.add(g1)
|
||||
|
||||
return perimeter_set
|
||||
|
||||
def adjacent(self, other):
|
||||
"""
|
||||
Chck if the two pin groups have at least one adjacent pin grid.
|
||||
"""
|
||||
# We could optimize this to just check the boundaries
|
||||
for g1 in self.perimeter_grids():
|
||||
for g2 in other.perimeter_grids():
|
||||
if g1.adjacent(g2):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def adjacent_grids(self, other, separation):
|
||||
"""
|
||||
Determine the sets of grids that are within a separation distance
|
||||
of any grid in the other set.
|
||||
"""
|
||||
# We could optimize this to just check the boundaries
|
||||
adj_grids = set()
|
||||
for g1 in self.grids:
|
||||
for g2 in other.grids:
|
||||
if g1.distance(g2) <= separation:
|
||||
adj_grids.add(g1)
|
||||
|
||||
return adj_grids
|
||||
|
||||
def convert_pin(self):
|
||||
"""
|
||||
Convert the list of pin shapes into sets of routing grids.
|
||||
The secondary set of grids are "optional" pin shapes that
|
||||
should be either blocked or part of the pin.
|
||||
"""
|
||||
# Set of tracks that overlap a pin
|
||||
pin_set = set()
|
||||
# Set of track adjacent to or paritally overlap a pin (not full DRC connection)
|
||||
partial_set = set()
|
||||
|
||||
# for pin in self.pins:
|
||||
# lx = pin.lx()
|
||||
# ly = pin.by()
|
||||
# if lx > 87.9 and lx < 87.99 and ly > 18.56 and ly < 18.6:
|
||||
# breakpoint()
|
||||
for pin in self.pins:
|
||||
debug.info(4, " Converting {0}".format(pin))
|
||||
# Determine which tracks the pin overlaps
|
||||
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
|
||||
pin)
|
||||
pin_set.update(sufficient)
|
||||
partial_set.update(insufficient)
|
||||
|
||||
# Blockages will be a super-set of pins since
|
||||
# it uses the inflated pin shape.
|
||||
blockage_in_tracks = self.router.convert_blockage(pin)
|
||||
# Must include the pins here too because these are computed in a different
|
||||
# way than blockages.
|
||||
blockages = sufficient | insufficient | blockage_in_tracks
|
||||
self.blockages.update(blockages)
|
||||
|
||||
# If we have a blockage, we must remove the grids
|
||||
# Remember, this excludes the pin blockages already
|
||||
blocked_grids = self.router.get_blocked_grids()
|
||||
pin_set.difference_update(blocked_grids)
|
||||
partial_set.difference_update(blocked_grids)
|
||||
|
||||
# At least one of the groups must have some valid tracks
|
||||
if (len(pin_set) == 0 and len(partial_set) == 0):
|
||||
# debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins))
|
||||
|
||||
for pin in self.pins:
|
||||
debug.warning(" Expanding conversion {0}".format(pin))
|
||||
# Determine which tracks the pin overlaps
|
||||
(sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name,
|
||||
pin,
|
||||
expansion=1)
|
||||
|
||||
# This time, don't remove blockages in the hopes that it might be ok.
|
||||
# Could cause DRC problems!
|
||||
pin_set.update(sufficient)
|
||||
partial_set.update(insufficient)
|
||||
|
||||
# If it's still empty, we must bail.
|
||||
if len(pin_set) == 0 and len(partial_set) == 0:
|
||||
debug.error("Unable to find unblocked pin {} {}".format(self.name,
|
||||
self.pins))
|
||||
self.router.write_debug_gds("blocked_pin.gds")
|
||||
|
||||
# Consider the fully connected set first and if not the partial set
|
||||
# if len(pin_set) > 0:
|
||||
# self.grids = pin_set
|
||||
# else:
|
||||
# self.grids = partial_set
|
||||
# Just using the full set simplifies the enclosures, otherwise
|
||||
# we get some pin enclose DRC errors due to off grid pins
|
||||
self.grids = pin_set | partial_set
|
||||
if len(self.grids) < 0:
|
||||
debug.error("Did not find any unblocked grids: {}".format(str(self.pins)))
|
||||
self.router.write_debug_gds("blocked_pin.gds")
|
||||
|
||||
# Remember the secondary grids for removing adjacent pins
|
||||
self.secondary_grids = partial_set
|
||||
|
||||
debug.info(4, " pins {}".format(self.grids))
|
||||
debug.info(4, " secondary {}".format(self.secondary_grids))
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -67,6 +67,11 @@ class router_tech:
|
|||
self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size)
|
||||
self.horiz_layer_minwidth = max(self.horiz_layer_minwidth, max_via_size)
|
||||
|
||||
# Update spacing for the new widths
|
||||
max_width = max(self.vert_layer_minwidth, self.horiz_layer_minwidth)
|
||||
self.vert_layer_spacing = self.get_layer_space(1, max_width)
|
||||
self.horiz_layer_spacing = self.get_layer_space(0, max_width)
|
||||
|
||||
self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing
|
||||
self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing
|
||||
|
||||
|
|
@ -109,23 +114,34 @@ class router_tech:
|
|||
else:
|
||||
debug.error("Invalid zindex {}".format(zindex), -1)
|
||||
|
||||
def get_lpp(self, zindex):
|
||||
if zindex == 1:
|
||||
return self.vert_lpp
|
||||
elif zindex == 0:
|
||||
return self.horiz_lpp
|
||||
else:
|
||||
debug.error("Invalid zindex {}".format(zindex), -1)
|
||||
|
||||
def get_layer_width_space(self, zindex):
|
||||
"""
|
||||
These are the width and spacing of a supply layer given a supply rail
|
||||
of the given number of min wire widths.
|
||||
"""
|
||||
if zindex==1:
|
||||
layer_name = self.vert_layer_name
|
||||
elif zindex==0:
|
||||
layer_name = self.horiz_layer_name
|
||||
else:
|
||||
debug.error("Invalid zindex for track", -1)
|
||||
|
||||
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
|
||||
|
||||
min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
|
||||
min_width = self.get_layer_width(zindex)
|
||||
min_spacing = self.get_layer_space(zindex, min_width)
|
||||
return (min_width, min_spacing)
|
||||
|
||||
def get_layer_width(self, zindex):
|
||||
""" Return the minimum width of a layer. """
|
||||
layer_name = self.get_layer(zindex)
|
||||
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
|
||||
min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
|
||||
return min_width
|
||||
|
||||
def get_layer_space(self, zindex, width=None):
|
||||
""" Return the minimum spacing of a layer given wire width. """
|
||||
if width is None:
|
||||
width = self.get_layer_width(zindex)
|
||||
layer_name = self.get_layer(zindex)
|
||||
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * width, math.inf)
|
||||
return min_spacing
|
||||
|
|
|
|||
|
|
@ -1,104 +1,169 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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)
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from datetime import datetime
|
||||
from openram import debug
|
||||
from openram import print_time
|
||||
from openram.base.vector import vector
|
||||
from openram import OPTS
|
||||
from .graph import graph
|
||||
from .graph_shape import graph_shape
|
||||
from .router import router
|
||||
from .signal_grid import signal_grid
|
||||
|
||||
|
||||
class signal_escape_router(router):
|
||||
"""
|
||||
A router that routes signals to perimeter and makes pins.
|
||||
This is the signal escape router that uses the Hanan grid graph method.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, margin=0):
|
||||
def __init__(self, layers, design, bbox=None):
|
||||
|
||||
# `router` is the base router class
|
||||
router.__init__(self, layers, design, bbox)
|
||||
|
||||
# New pins are the side supply pins
|
||||
self.new_pins = {}
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
# This is the routing region scale
|
||||
scale = 1
|
||||
while True:
|
||||
# Create the graph
|
||||
g = graph(self)
|
||||
region = g.create_graph(source, target, scale)
|
||||
# Find the shortest path from source to target
|
||||
path = g.find_shortest_path()
|
||||
# If there is no path found, exponentially try again with a
|
||||
# larger routing region
|
||||
if path is None:
|
||||
rll, rur = region
|
||||
bll, bur = self.bbox
|
||||
# Stop scaling the region and throw an error
|
||||
if rll.x < bll.x and rll.y < bll.y and \
|
||||
rur.x > bur.x and rur.y > bur.y:
|
||||
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)
|
||||
# Exponentially scale the region
|
||||
scale *= 2
|
||||
debug.info(0, "Retry routing in larger routing region with scale {}".format(scale))
|
||||
continue
|
||||
# Create the path shapes on layout
|
||||
new_shapes = self.add_path(path)
|
||||
self.new_pins[source.name] = new_shapes[0]
|
||||
# Find the recently added shapes
|
||||
self.prepare_gds_reader()
|
||||
self.find_blockages(name)
|
||||
self.find_vias()
|
||||
break
|
||||
self.replace_layout_pins()
|
||||
|
||||
|
||||
def add_perimeter_fake_pins(self):
|
||||
"""
|
||||
This will route on layers in design. It will get the blockages from
|
||||
either the gds file name or the design itself (by saving to a gds file).
|
||||
Add the fake pins on the perimeter to where the signals will be routed.
|
||||
"""
|
||||
router.__init__(self,
|
||||
layers=layers,
|
||||
design=design,
|
||||
bbox=bbox,
|
||||
margin=margin)
|
||||
|
||||
def perimeter_dist(self, pin_name):
|
||||
"""
|
||||
Return the shortest Manhattan distance to the bounding box perimeter.
|
||||
"""
|
||||
loc = self.cell.get_pin(pin_name).center()
|
||||
x_dist = min(loc.x - self.ll.x, self.ur.x - loc.x)
|
||||
y_dist = min(loc.y - self.ll.y, self.ur.y - loc.y)
|
||||
ll, ur = self.bbox
|
||||
wide = self.track_wire
|
||||
|
||||
return min(x_dist, y_dist)
|
||||
for side in ["top", "bottom", "left", "right"]:
|
||||
vertical = side in ["left", "right"]
|
||||
|
||||
def escape_route(self, pin_names):
|
||||
"""
|
||||
Takes a list of tuples (name, side) and routes them. After routing,
|
||||
it removes the old pin and places a new one on the perimeter.
|
||||
"""
|
||||
self.create_routing_grid(signal_grid)
|
||||
# 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)
|
||||
|
||||
start_time = datetime.now()
|
||||
self.find_pins_and_blockages(pin_names)
|
||||
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
|
||||
# Calculate width and height
|
||||
shape = ur - ll
|
||||
if vertical:
|
||||
shape_width = wide
|
||||
shape_height = shape.y
|
||||
else:
|
||||
shape_width = shape.x
|
||||
shape_height = wide
|
||||
|
||||
# Order the routes by closest to the perimeter first
|
||||
# This prevents some pins near the perimeter from being blocked by other pins
|
||||
ordered_pin_names = sorted(pin_names, key=lambda x: self.perimeter_dist(x))
|
||||
# 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)
|
||||
|
||||
# Route the supply pins to the supply rails
|
||||
# Route vdd first since we want it to be shorter
|
||||
start_time = datetime.now()
|
||||
for pin_name in ordered_pin_names:
|
||||
self.route_signal(pin_name)
|
||||
# if pin_name == "dout0[1]":
|
||||
# self.write_debug_gds("postroute.gds", True)
|
||||
|
||||
print_time("Maze routing pins",datetime.now(), start_time, 3)
|
||||
def get_closest_perimeter_fake_pin(self, pin):
|
||||
""" Return the closest fake pin for the given pin. """
|
||||
|
||||
#self.write_debug_gds("final_escape_router.gds",False)
|
||||
min_dist = float("inf")
|
||||
close_fake = None
|
||||
for fake in self.fake_pins:
|
||||
dist = pin.distance(fake)
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
close_fake = fake
|
||||
return close_fake
|
||||
|
||||
return True
|
||||
|
||||
def route_signal(self, pin_name, side="all"):
|
||||
def get_route_pairs(self, pin_names):
|
||||
""" Return the pairs to be routed. """
|
||||
|
||||
for detour_scale in [5 * pow(2, x) for x in range(5)]:
|
||||
debug.info(1, "Escape routing {0} with scale {1}".format(pin_name, detour_scale))
|
||||
to_route = []
|
||||
for name in pin_names:
|
||||
pin = next(iter(self.pins[name]))
|
||||
fake = self.get_closest_perimeter_fake_pin(pin)
|
||||
to_route.append((pin, fake, pin.distance(fake)))
|
||||
return sorted(to_route, key=lambda x: x[2])
|
||||
|
||||
# Clear everything in the routing grid.
|
||||
self.rg.reinit()
|
||||
|
||||
# This is inefficient since it is non-incremental, but it was
|
||||
# easier to debug.
|
||||
self.prepare_blockages()
|
||||
self.clear_blockages(pin_name)
|
||||
def replace_layout_pins(self):
|
||||
""" Replace the old layout pins with new ones around the perimeter. """
|
||||
|
||||
# Add the single component of the pin as the source
|
||||
# which unmarks it as a blockage too
|
||||
self.add_source(pin_name)
|
||||
|
||||
# Marks the grid cells all along the perimeter as a target
|
||||
self.add_perimeter_target(side)
|
||||
|
||||
# if pin_name == "dout0[3]":
|
||||
# self.write_debug_gds("pre_route.gds", False)
|
||||
# breakpoint()
|
||||
|
||||
# Actually run the A* router
|
||||
if self.run_router(detour_scale=detour_scale):
|
||||
new_pin = self.get_perimeter_pin()
|
||||
self.cell.replace_layout_pin(pin_name, new_pin)
|
||||
return
|
||||
|
||||
# if pin_name == "dout0[3]":
|
||||
# self.write_debug_gds("pre_route.gds", False)
|
||||
# breakpoint()
|
||||
|
||||
self.write_debug_gds("debug_route.gds", True)
|
||||
for name, pin in self.new_pins.items():
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,165 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from copy import deepcopy
|
||||
from heapq import heappush,heappop
|
||||
from openram import debug
|
||||
from openram.base.vector3d import vector3d
|
||||
from .grid import grid
|
||||
from .grid_path import grid_path
|
||||
|
||||
|
||||
class signal_grid(grid):
|
||||
"""
|
||||
Expand the two layer grid to include A* search functions for a source and target.
|
||||
"""
|
||||
|
||||
def __init__(self, ll, ur, track_factor):
|
||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||
grid.__init__(self, ll, ur, track_factor)
|
||||
|
||||
def reinit(self):
|
||||
""" Reinitialize everything for a new route. """
|
||||
|
||||
# Reset all the cells in the map
|
||||
for p in self.map.values():
|
||||
p.reset()
|
||||
|
||||
self.clear_source()
|
||||
self.clear_target()
|
||||
|
||||
def init_queue(self):
|
||||
"""
|
||||
Populate the queue with all the source pins with cost
|
||||
to the target. Each item is a path of the grid cells.
|
||||
We will use an A* search, so this cost must be pessimistic.
|
||||
Cost so far will be the length of the path.
|
||||
"""
|
||||
# Counter is used to not require data comparison in Python 3.x
|
||||
# Items will be returned in order they are added during cost ties
|
||||
self.q = []
|
||||
self.counter = 0
|
||||
for s in self.source:
|
||||
cost = self.cost_to_target(s)
|
||||
debug.info(3, "Init: cost=" + str(cost) + " " + str([s]))
|
||||
heappush(self.q, (cost, self.counter, grid_path([vector3d(s)])))
|
||||
self.counter += 1
|
||||
|
||||
def route(self, detour_scale):
|
||||
"""
|
||||
This does the A* maze routing with preferred direction routing.
|
||||
This only works for 1 track wide routes!
|
||||
"""
|
||||
|
||||
# We set a cost bound of the HPWL for run-time. This can be
|
||||
# over-ridden if the route fails due to pruning a feasible solution.
|
||||
any_source_element = next(iter(self.source))
|
||||
cost_bound = detour_scale * self.cost_to_target(any_source_element) * grid.PREFERRED_COST
|
||||
|
||||
# Check if something in the queue is already a source and a target!
|
||||
for s in self.source:
|
||||
if self.is_target(s):
|
||||
return((grid_path([vector3d(s)]), 0))
|
||||
|
||||
# Put the source items into the queue
|
||||
self.init_queue()
|
||||
|
||||
# Keep expanding and adding to the priority queue until we are done
|
||||
while len(self.q)>0:
|
||||
# should we keep the path in the queue as well or just the final node?
|
||||
(cost, count, curpath) = heappop(self.q)
|
||||
debug.info(3, "Queue size: size=" + str(len(self.q)) + " " + str(cost))
|
||||
debug.info(4, "Expanding: cost=" + str(cost) + " " + str(curpath))
|
||||
|
||||
# expand the last element
|
||||
neighbors = self.expand_dirs(curpath)
|
||||
debug.info(4, "Neighbors: " + str(neighbors))
|
||||
|
||||
for n in neighbors:
|
||||
# make a new copy of the path to not update the old ones
|
||||
newpath = deepcopy(curpath)
|
||||
# node is added to the map by the expand routine
|
||||
newpath.append(n)
|
||||
# check if we hit the target and are done
|
||||
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
|
||||
return (newpath, newpath.cost())
|
||||
else:
|
||||
# current path cost + predicted cost
|
||||
current_cost = newpath.cost()
|
||||
target_cost = self.cost_to_target(n[0])
|
||||
predicted_cost = current_cost + target_cost
|
||||
# only add the cost if it is less than our bound
|
||||
if (predicted_cost < cost_bound):
|
||||
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
|
||||
self.map[n[0]].min_path = newpath
|
||||
self.map[n[0]].min_cost = predicted_cost
|
||||
debug.info(4, "Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
|
||||
# add the cost to get to this point if we haven't reached it yet
|
||||
heappush(self.q, (predicted_cost, self.counter, newpath))
|
||||
self.counter += 1
|
||||
#else:
|
||||
# print("Better previous cost.")
|
||||
#else:
|
||||
# print("Cost bounded")
|
||||
|
||||
return (None, None)
|
||||
|
||||
def expand_dirs(self, curpath):
|
||||
"""
|
||||
Expand each of the four cardinal directions plus up or down
|
||||
but not expanding to blocked cells. Expands in all directions
|
||||
regardless of preferred directions.
|
||||
"""
|
||||
|
||||
# Expand all directions.
|
||||
neighbors = curpath.expand_dirs()
|
||||
|
||||
# Filter the out of region ones
|
||||
# Filter the blocked ones
|
||||
valid_neighbors = [x for x in neighbors if self.is_inside(x) and not self.is_blocked(x)]
|
||||
|
||||
return valid_neighbors
|
||||
|
||||
def hpwl(self, src, dest):
|
||||
"""
|
||||
Return half perimeter wire length from point to another.
|
||||
Either point can have positive or negative coordinates.
|
||||
Include the via penalty if there is one.
|
||||
"""
|
||||
hpwl = abs(src.x - dest.x)
|
||||
hpwl += abs(src.y - dest.y)
|
||||
if src.x!=dest.x and src.y!=dest.y:
|
||||
hpwl += grid.VIA_COST
|
||||
return hpwl
|
||||
|
||||
def cost_to_target(self, source):
|
||||
"""
|
||||
Find the cheapest HPWL distance to any target point ignoring
|
||||
blockages for A* search.
|
||||
"""
|
||||
any_target_element = next(iter(self.target))
|
||||
cost = self.hpwl(source, any_target_element)
|
||||
for t in self.target:
|
||||
cost = min(self.hpwl(source, t), cost)
|
||||
|
||||
return cost
|
||||
|
||||
def get_inertia(self, p0, p1):
|
||||
"""
|
||||
Sets the direction based on the previous direction we came from.
|
||||
"""
|
||||
# direction (index) of movement
|
||||
if p0.x==p1.x:
|
||||
return 1
|
||||
elif p0.y==p1.y:
|
||||
return 0
|
||||
else:
|
||||
# z direction
|
||||
return 2
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from openram import debug
|
||||
from openram.router import router
|
||||
|
||||
|
||||
class signal_router(router):
|
||||
"""
|
||||
A router class to read an obstruction map from a gds and plan a
|
||||
route on a given layer. This is limited to two layer routes.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None):
|
||||
"""
|
||||
This will route on layers in design. It will get the blockages from
|
||||
either the gds file name or the design itself (by saving to a gds file).
|
||||
"""
|
||||
router.__init__(self, layers, design, bbox)
|
||||
|
||||
def route(self, src, dest, detour_scale=5):
|
||||
"""
|
||||
Route a single source-destination net and return
|
||||
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
|
||||
This is used to speed up the routing when there is not much detouring needed.
|
||||
"""
|
||||
debug.info(1, "Running signal router from {0} to {1}...".format(src, dest))
|
||||
|
||||
self.pins[src] = []
|
||||
self.pins[dest] = []
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self, 'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid(signal_grid)
|
||||
|
||||
# Get the pin shapes
|
||||
self.find_pins_and_blockages([src, dest])
|
||||
|
||||
# Block everything
|
||||
self.prepare_blockages()
|
||||
# Clear the pins we are routing
|
||||
self.set_blockages(self.pin_components[src], False)
|
||||
self.set_blockages(self.pin_components[dest], False)
|
||||
|
||||
# Now add the src/tgt if they are not blocked by other shapes
|
||||
self.add_source(src)
|
||||
self.add_target(dest)
|
||||
|
||||
if not self.run_router(detour_scale=detour_scale):
|
||||
self.write_debug_gds(stop_program=False)
|
||||
return False
|
||||
|
||||
#self.write_debug_gds(stop_program=False)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from .signal_grid import signal_grid
|
||||
from .grid_path import grid_path
|
||||
|
||||
|
||||
class supply_grid(signal_grid):
|
||||
"""
|
||||
This routes a supply grid. It is derived from a signal grid because it still
|
||||
routes the pins to the supply rails using the same routines.
|
||||
It has a few extra routines to support "waves" which are multiple track wide
|
||||
directional routes (no bends).
|
||||
"""
|
||||
|
||||
def __init__(self, ll, ur, track_width):
|
||||
""" Create a routing map of width x height cells and 2 in the z-axis. """
|
||||
signal_grid.__init__(self, ll, ur, track_width)
|
||||
|
||||
def reinit(self):
|
||||
""" Reinitialize everything for a new route. """
|
||||
|
||||
self.source = set()
|
||||
self.target = set()
|
||||
|
||||
# Reset all the cells in the map
|
||||
for p in self.map.values():
|
||||
p.reset()
|
||||
|
||||
def find_start_wave(self, wave, direct):
|
||||
"""
|
||||
Finds the first loc starting at loc and up that is open.
|
||||
Returns None if it reaches max size first.
|
||||
"""
|
||||
# Don't expand outside the bounding box
|
||||
if wave[0].x > self.ur.x:
|
||||
return None
|
||||
if wave[-1].y > self.ur.y:
|
||||
return None
|
||||
|
||||
while wave and self.is_wave_blocked(wave):
|
||||
wf = grid_path(wave)
|
||||
wave = wf.neighbor(direct)
|
||||
# Bail out if we couldn't increment futher
|
||||
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
|
||||
return None
|
||||
# Return a start if it isn't blocked
|
||||
if not self.is_wave_blocked(wave):
|
||||
return wave
|
||||
|
||||
return wave
|
||||
|
||||
def is_wave_blocked(self, wave):
|
||||
"""
|
||||
Checks if any of the locations are blocked
|
||||
"""
|
||||
for v in wave:
|
||||
if self.is_blocked(v):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def probe(self, wave, direct):
|
||||
"""
|
||||
Expand the wave until there is a blockage and return
|
||||
the wave path.
|
||||
"""
|
||||
wave_path = grid_path()
|
||||
while wave and not self.is_wave_blocked(wave):
|
||||
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
|
||||
break
|
||||
wave_path.append(wave)
|
||||
wave = wave_path.neighbor(direct)
|
||||
|
||||
return wave_path
|
||||
|
|
@ -1,394 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from datetime import datetime
|
||||
from openram import debug
|
||||
from openram.base.vector3d import vector3d
|
||||
from openram import print_time
|
||||
from .router import router
|
||||
from .direction import direction
|
||||
from .supply_grid import supply_grid
|
||||
from . import grid_utils
|
||||
|
||||
|
||||
class supply_grid_router(router):
|
||||
"""
|
||||
A router class to read an obstruction map from a gds and
|
||||
routes a grid to connect the supply on the two layers.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, pin_type=None):
|
||||
"""
|
||||
This will route on layers in design. It will get the blockages from
|
||||
either the gds file name or the design itself (by saving to a gds file).
|
||||
"""
|
||||
start_time = datetime.now()
|
||||
|
||||
# Power rail width in minimum wire widths
|
||||
self.route_track_width = 1
|
||||
|
||||
router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width)
|
||||
|
||||
# The list of supply rails (grid sets) that may be routed
|
||||
self.supply_rails = {}
|
||||
# This is the same as above but as a sigle set for the all the rails
|
||||
self.supply_rail_tracks = {}
|
||||
|
||||
print_time("Init supply router", datetime.now(), start_time, 3)
|
||||
|
||||
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
||||
"""
|
||||
Add power supply rails and connect all pins to these rails.
|
||||
"""
|
||||
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
|
||||
self.vdd_name = vdd_name
|
||||
self.gnd_name = gnd_name
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self, 'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid(supply_grid)
|
||||
|
||||
# Get the pin shapes
|
||||
start_time = datetime.now()
|
||||
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
|
||||
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
|
||||
# Add the supply rails in a mesh network and connect H/V with vias
|
||||
start_time = datetime.now()
|
||||
# Block everything
|
||||
self.prepare_blockages()
|
||||
self.clear_blockages(self.gnd_name)
|
||||
|
||||
|
||||
# Determine the rail locations
|
||||
self.route_supply_rails(self.gnd_name, 0)
|
||||
|
||||
# Block everything
|
||||
self.prepare_blockages()
|
||||
self.clear_blockages(self.vdd_name)
|
||||
# Determine the rail locations
|
||||
self.route_supply_rails(self.vdd_name, 1)
|
||||
print_time("Routing supply rails", datetime.now(), start_time, 3)
|
||||
|
||||
start_time = datetime.now()
|
||||
self.route_simple_overlaps(vdd_name)
|
||||
self.route_simple_overlaps(gnd_name)
|
||||
print_time("Simple overlap routing", datetime.now(), start_time, 3)
|
||||
|
||||
# Route the supply pins to the supply rails
|
||||
# Route vdd first since we want it to be shorter
|
||||
start_time = datetime.now()
|
||||
self.route_pins_to_rails(vdd_name)
|
||||
self.route_pins_to_rails(gnd_name)
|
||||
print_time("Maze routing supplies", datetime.now(), start_time, 3)
|
||||
# self.write_debug_gds("final.gds", False)
|
||||
|
||||
# Did we route everything??
|
||||
if not self.check_all_routed(vdd_name):
|
||||
return False
|
||||
if not self.check_all_routed(gnd_name):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def route_simple_overlaps(self, pin_name):
|
||||
"""
|
||||
This checks for simple cases where a pin component already overlaps a supply rail.
|
||||
It will add an enclosure to ensure the overlap in wide DRC rule cases.
|
||||
"""
|
||||
debug.info(1, "Routing simple overlap pins for {0}".format(pin_name))
|
||||
|
||||
# These are the wire tracks
|
||||
wire_tracks = self.supply_rail_tracks[pin_name]
|
||||
routed_count=0
|
||||
for pg in self.pin_groups[pin_name]:
|
||||
if pg.is_routed():
|
||||
continue
|
||||
|
||||
# First, check if we just overlap, if so, we are done.
|
||||
overlap_grids = wire_tracks & pg.grids
|
||||
if len(overlap_grids)>0:
|
||||
routed_count += 1
|
||||
pg.set_routed()
|
||||
continue
|
||||
|
||||
# Else, if we overlap some of the space track, we can patch it with an enclosure
|
||||
# pg.create_simple_overlap_enclosure(pg.grids)
|
||||
# pg.add_enclosure(self.cell)
|
||||
|
||||
debug.info(1, "Routed {} simple overlap pins".format(routed_count))
|
||||
|
||||
def finalize_supply_rails(self, name):
|
||||
"""
|
||||
Determine which supply rails overlap and can accomodate a via.
|
||||
Remove any supply rails that do not have a via since they are disconnected.
|
||||
NOTE: It is still possible though unlikely that there are disconnected groups of rails.
|
||||
"""
|
||||
|
||||
all_rails = self.supply_rails[name]
|
||||
|
||||
connections = set()
|
||||
via_areas = []
|
||||
for i1, r1 in enumerate(all_rails):
|
||||
# Only consider r1 horizontal rails
|
||||
e = next(iter(r1))
|
||||
if e.z==1:
|
||||
continue
|
||||
|
||||
# We need to move this rail to the other layer for the z indices to match
|
||||
# during the intersection. This also makes a copy.
|
||||
new_r1 = {vector3d(i.x, i.y, 1) for i in r1}
|
||||
|
||||
for i2, r2 in enumerate(all_rails):
|
||||
# Never compare to yourself
|
||||
if i1==i2:
|
||||
continue
|
||||
|
||||
# Only consider r2 vertical rails
|
||||
e = next(iter(r2))
|
||||
if e.z==0:
|
||||
continue
|
||||
|
||||
# Determine if we have sufficient overlap and, if so,
|
||||
# remember:
|
||||
# the indices to determine a rail is connected to another
|
||||
# the overlap area for placement of a via
|
||||
overlap = new_r1 & r2
|
||||
if len(overlap) >= 1:
|
||||
debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap))
|
||||
connections.update([i1, i2])
|
||||
via_areas.append(overlap)
|
||||
|
||||
# Go through and add the vias at the center of the intersection
|
||||
for area in via_areas:
|
||||
ll = grid_utils.get_lower_left(area)
|
||||
ur = grid_utils.get_upper_right(area)
|
||||
center = (ll + ur).scale(0.5, 0.5, 0)
|
||||
self.add_via(center, 1)
|
||||
|
||||
# Determien which indices were not connected to anything above
|
||||
missing_indices = set([x for x in range(len(self.supply_rails[name]))])
|
||||
missing_indices.difference_update(connections)
|
||||
|
||||
# Go through and remove those disconnected indices
|
||||
# (No via was added, so that doesn't need to be removed)
|
||||
for rail_index in sorted(missing_indices, reverse=True):
|
||||
ll = grid_utils.get_lower_left(all_rails[rail_index])
|
||||
ur = grid_utils.get_upper_right(all_rails[rail_index])
|
||||
debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur))
|
||||
self.supply_rails[name].pop(rail_index)
|
||||
|
||||
# Make the supply rails into a big giant set of grids for easy blockages.
|
||||
# Must be done after we determine which ones are connected.
|
||||
self.create_supply_track_set(name)
|
||||
|
||||
def add_supply_rails(self, name):
|
||||
"""
|
||||
Add the shapes that represent the routed supply rails.
|
||||
This is after the paths have been pruned and only include rails that are
|
||||
connected with vias.
|
||||
"""
|
||||
for rail in self.supply_rails[name]:
|
||||
ll = grid_utils.get_lower_left(rail)
|
||||
ur = grid_utils.get_upper_right(rail)
|
||||
z = ll.z
|
||||
pin = self.compute_pin_enclosure(ll, ur, z, name)
|
||||
debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin))
|
||||
self.cell.add_layout_pin(text=name,
|
||||
layer=pin.layer,
|
||||
offset=pin.ll(),
|
||||
width=pin.width(),
|
||||
height=pin.height())
|
||||
|
||||
def compute_supply_rails(self, name, supply_number):
|
||||
"""
|
||||
Compute the unblocked locations for the horizontal and vertical supply rails.
|
||||
Go in a raster order from bottom to the top (for horizontal) and left to right
|
||||
(for vertical). Start with an initial start_offset in x and y direction.
|
||||
"""
|
||||
|
||||
self.supply_rails[name]=[]
|
||||
|
||||
max_yoffset = self.rg.ur.y
|
||||
max_xoffset = self.rg.ur.x
|
||||
min_yoffset = self.rg.ll.y
|
||||
min_xoffset = self.rg.ll.x
|
||||
|
||||
# Horizontal supply rails
|
||||
start_offset = min_yoffset + supply_number
|
||||
for offset in range(start_offset, max_yoffset, 2):
|
||||
# Seed the function at the location with the given width
|
||||
wave = [vector3d(min_xoffset, offset, 0)]
|
||||
# While we can keep expanding east in this horizontal track
|
||||
while wave and wave[0].x < max_xoffset:
|
||||
added_rail = self.find_supply_rail(name, wave, direction.EAST)
|
||||
if not added_rail:
|
||||
# Just seed with the next one
|
||||
wave = [x+vector3d(1, 0, 0) for x in wave]
|
||||
else:
|
||||
# Seed with the neighbor of the end of the last rail
|
||||
wave = added_rail.neighbor(direction.EAST)
|
||||
|
||||
# Vertical supply rails
|
||||
start_offset = min_xoffset + supply_number
|
||||
for offset in range(start_offset, max_xoffset, 2):
|
||||
# Seed the function at the location with the given width
|
||||
wave = [vector3d(offset, min_yoffset, 1)]
|
||||
# While we can keep expanding north in this vertical track
|
||||
while wave and wave[0].y < max_yoffset:
|
||||
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
|
||||
if not added_rail:
|
||||
# Just seed with the next one
|
||||
wave = [x + vector3d(0, 1, 0) for x in wave]
|
||||
else:
|
||||
# Seed with the neighbor of the end of the last rail
|
||||
wave = added_rail.neighbor(direction.NORTH)
|
||||
|
||||
def find_supply_rail(self, name, seed_wave, direct):
|
||||
"""
|
||||
Find a start location, probe in the direction, and see if the rail is big enough
|
||||
to contain a via, and, if so, add it.
|
||||
"""
|
||||
# Sweep to find an initial unblocked valid wave
|
||||
start_wave = self.rg.find_start_wave(seed_wave, direct)
|
||||
|
||||
# This means there were no more unblocked grids in the row/col
|
||||
if not start_wave:
|
||||
return None
|
||||
|
||||
wave_path = self.probe_supply_rail(name, start_wave, direct)
|
||||
|
||||
self.approve_supply_rail(name, wave_path)
|
||||
|
||||
# Return the rail whether we approved it or not,
|
||||
# as it will be used to find the next start location
|
||||
return wave_path
|
||||
|
||||
def probe_supply_rail(self, name, start_wave, direct):
|
||||
"""
|
||||
This finds the first valid starting location and routes a supply rail
|
||||
in the given direction.
|
||||
It returns the space after the end of the rail to seed another call for multiple
|
||||
supply rails in the same "track" when there is a blockage.
|
||||
"""
|
||||
|
||||
# Expand the wave to the right
|
||||
wave_path = self.rg.probe(start_wave, direct)
|
||||
|
||||
if not wave_path:
|
||||
return None
|
||||
|
||||
# drop the first and last steps to leave escape routing room
|
||||
# around the blockage that stopped the probe
|
||||
# except, don't drop the first if it is the first in a row/column
|
||||
if (direct==direction.NORTH and start_wave[0].y>0):
|
||||
wave_path.trim_first()
|
||||
elif (direct == direction.EAST and start_wave[0].x>0):
|
||||
wave_path.trim_first()
|
||||
|
||||
wave_path.trim_last()
|
||||
|
||||
return wave_path
|
||||
|
||||
def approve_supply_rail(self, name, wave_path):
|
||||
"""
|
||||
Check if the supply rail is sufficient (big enough) and add it to the
|
||||
data structure. Return whether it was added or not.
|
||||
"""
|
||||
# We must have at least 2 tracks to drop plus 2 tracks for a via
|
||||
if len(wave_path) >= 4 * self.route_track_width:
|
||||
grid_set = wave_path.get_grids()
|
||||
self.supply_rails[name].append(grid_set)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def route_supply_rails(self, name, supply_number):
|
||||
"""
|
||||
Route the horizontal and vertical supply rails across the entire design.
|
||||
Must be done with lower left at 0,0
|
||||
"""
|
||||
debug.info(1, "Routing supply rail {0}.".format(name))
|
||||
|
||||
# Compute the grid locations of the supply rails
|
||||
self.compute_supply_rails(name, supply_number)
|
||||
|
||||
# Add the supply rail vias (and prune disconnected rails)
|
||||
self.finalize_supply_rails(name)
|
||||
|
||||
# Add the rails themselves
|
||||
self.add_supply_rails(name)
|
||||
|
||||
def create_supply_track_set(self, pin_name):
|
||||
"""
|
||||
Make a single set of all the tracks for the rail and wire itself.
|
||||
"""
|
||||
rail_set = set()
|
||||
for rail in self.supply_rails[pin_name]:
|
||||
rail_set.update(rail)
|
||||
self.supply_rail_tracks[pin_name] = rail_set
|
||||
|
||||
def route_pins_to_rails(self, pin_name):
|
||||
"""
|
||||
This will route each of the remaining pin components to the supply rails.
|
||||
After it is done, the cells are added to the pin blockage list.
|
||||
"""
|
||||
|
||||
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
|
||||
debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name,
|
||||
remaining_components))
|
||||
|
||||
for index, pg in enumerate(self.pin_groups[pin_name]):
|
||||
if pg.is_routed():
|
||||
continue
|
||||
|
||||
debug.info(3, "Routing component {0} {1}".format(pin_name, index))
|
||||
|
||||
# Clear everything in the routing grid.
|
||||
self.rg.reinit()
|
||||
|
||||
# This is inefficient since it is non-incremental, but it was
|
||||
# easier to debug.
|
||||
self.prepare_blockages()
|
||||
self.clear_blockages(self.vdd_name)
|
||||
|
||||
# Add the single component of the pin as the source
|
||||
# which unmarks it as a blockage too
|
||||
self.add_pin_component_source(pin_name, index)
|
||||
|
||||
# Add all of the rails as targets
|
||||
# Don't add the other pins, but we could?
|
||||
self.add_supply_rail_target(pin_name)
|
||||
|
||||
# Actually run the A* router
|
||||
if not self.run_router(detour_scale=5):
|
||||
self.write_debug_gds("debug_route.gds")
|
||||
|
||||
# if index==3 and pin_name=="vdd":
|
||||
# self.write_debug_gds("route.gds",False)
|
||||
|
||||
def add_supply_rail_target(self, pin_name):
|
||||
"""
|
||||
Add the supply rails of given name as a routing target.
|
||||
"""
|
||||
debug.info(4, "Add supply rail target {}".format(pin_name))
|
||||
# Add the wire itself as the target
|
||||
self.rg.set_target(self.supply_rail_tracks[pin_name])
|
||||
# But unblock all the rail tracks including the space
|
||||
self.rg.set_blocked(self.supply_rail_tracks[pin_name], False)
|
||||
|
||||
def set_supply_rail_blocked(self, value=True):
|
||||
"""
|
||||
Add the supply rails of given name as a routing target.
|
||||
"""
|
||||
debug.info(4, "Blocking supply rail")
|
||||
for rail_name in self.supply_rail_tracks:
|
||||
self.rg.set_blocked(self.supply_rail_tracks[rail_name])
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram import debug
|
||||
from openram.base.vector import vector
|
||||
from openram import OPTS
|
||||
from .graph import graph
|
||||
from .graph_shape import graph_shape
|
||||
from .router import router
|
||||
|
||||
|
||||
class supply_router(router):
|
||||
"""
|
||||
This is the supply router that uses the Hanan grid graph method.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, pin_type=None):
|
||||
|
||||
# `router` is the base router class
|
||||
router.__init__(self, layers, design, bbox)
|
||||
|
||||
# Side supply pin type
|
||||
# (can be "top", "bottom", "right", "left", and "ring")
|
||||
self.pin_type = pin_type
|
||||
# New pins are the side supply pins
|
||||
self.new_pins = {}
|
||||
|
||||
|
||||
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
||||
""" Route the given pins in the given order. """
|
||||
debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name))
|
||||
|
||||
# Save pin names
|
||||
self.vdd_name = vdd_name
|
||||
self.gnd_name = gnd_name
|
||||
|
||||
# Prepare gdsMill to find pins and blockages
|
||||
self.prepare_gds_reader()
|
||||
|
||||
# Find pins to be routed
|
||||
self.find_pins(vdd_name)
|
||||
self.find_pins(gnd_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 side pins
|
||||
if self.pin_type in ["top", "bottom", "right", "left"]:
|
||||
self.add_side_pin(vdd_name)
|
||||
self.add_side_pin(gnd_name)
|
||||
elif self.pin_type == "ring":
|
||||
self.add_ring_pin(vdd_name)
|
||||
self.add_ring_pin(gnd_name)
|
||||
else:
|
||||
debug.warning("Side supply pins aren't created.")
|
||||
|
||||
# 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
|
||||
for pin_name in [vdd_name, gnd_name]:
|
||||
pins = self.pins[pin_name]
|
||||
# Route closest pins according to the minimum spanning tree
|
||||
for source, target in self.get_mst_pairs(list(pins)):
|
||||
# This is the routing region scale
|
||||
scale = 1
|
||||
while True:
|
||||
# Create the graph
|
||||
g = graph(self)
|
||||
region = g.create_graph(source, target, scale)
|
||||
# Find the shortest path from source to target
|
||||
path = g.find_shortest_path()
|
||||
# If there is no path found, exponentially try again with a
|
||||
# larger routing region
|
||||
if path is None:
|
||||
rll, rur = region
|
||||
bll, bur = self.bbox
|
||||
# Stop scaling the region and throw an error
|
||||
if rll.x < bll.x and rll.y < bll.y and \
|
||||
rur.x > bur.x and rur.y > bur.y:
|
||||
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)
|
||||
# Exponentially scale the region
|
||||
scale *= 2
|
||||
debug.info(0, "Retry routing in larger routing region with scale {}".format(scale))
|
||||
continue
|
||||
# Create the path shapes on layout
|
||||
self.add_path(path)
|
||||
# Find the recently added shapes
|
||||
self.prepare_gds_reader()
|
||||
self.find_blockages(pin_name)
|
||||
self.find_vias()
|
||||
break
|
||||
|
||||
|
||||
def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4):
|
||||
""" Add supply pin to one side of the layout. """
|
||||
|
||||
ll, ur = self.bbox
|
||||
vertical = side in ["left", "right"]
|
||||
inner = pin_name == self.gnd_name
|
||||
|
||||
# Calculate wires' wideness
|
||||
wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1)
|
||||
|
||||
# Calculate the offset for the inner ring
|
||||
if inner:
|
||||
margin = wideness * 2
|
||||
else:
|
||||
margin = 0
|
||||
|
||||
# Calculate the lower left coordinate
|
||||
if side == "top":
|
||||
offset = vector(ll.x + margin, ur.y - wideness - margin)
|
||||
elif side == "bottom":
|
||||
offset = vector(ll.x + margin, ll.y + margin)
|
||||
elif side == "left":
|
||||
offset = vector(ll.x + margin, ll.y + margin)
|
||||
elif side == "right":
|
||||
offset = vector(ur.x - wideness - margin, ll.y + margin)
|
||||
|
||||
# Calculate width and height
|
||||
shape = ur - ll
|
||||
if vertical:
|
||||
shape_width = wideness
|
||||
shape_height = shape.y
|
||||
else:
|
||||
shape_width = shape.x
|
||||
shape_height = wideness
|
||||
if inner:
|
||||
if vertical:
|
||||
shape_height -= margin * 2
|
||||
else:
|
||||
shape_width -= margin * 2
|
||||
|
||||
# Add this new pin
|
||||
layer = self.get_layer(int(vertical))
|
||||
pin = self.design.add_layout_pin(text=pin_name,
|
||||
layer=layer,
|
||||
offset=offset,
|
||||
width=shape_width,
|
||||
height=shape_height)
|
||||
|
||||
# Add fake pins on this new pin evenly
|
||||
fake_pins = []
|
||||
if vertical:
|
||||
space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1)
|
||||
start_offset = vector(offset.x, offset.y + wideness)
|
||||
else:
|
||||
space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1)
|
||||
start_offset = vector(offset.x + wideness, offset.y)
|
||||
for i in range(1, num_fake_pins + 1):
|
||||
if vertical:
|
||||
offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire))
|
||||
ll = vector(offset.x, offset.y - self.track_wire)
|
||||
ur = vector(offset.x + wideness, offset.y)
|
||||
else:
|
||||
offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y)
|
||||
ll = vector(offset.x - self.track_wire, offset.y)
|
||||
ur = vector(offset.x, offset.y + wideness)
|
||||
rect = [ll, ur]
|
||||
fake_pin = graph_shape(name=pin_name,
|
||||
rect=rect,
|
||||
layer_name_pp=layer)
|
||||
fake_pins.append(fake_pin)
|
||||
return pin, fake_pins
|
||||
|
||||
|
||||
def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4):
|
||||
""" Add the supply ring to the layout. """
|
||||
|
||||
# Add side pins
|
||||
new_pins = []
|
||||
for side in ["top", "bottom", "right", "left"]:
|
||||
new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins)
|
||||
ll, ur = new_shape.rect
|
||||
rect = [ll, ur]
|
||||
layer = self.get_layer(side in ["left", "right"])
|
||||
new_pin = graph_shape(name=pin_name,
|
||||
rect=rect,
|
||||
layer_name_pp=layer)
|
||||
new_pins.append(new_pin)
|
||||
self.pins[pin_name].update(fake_pins)
|
||||
self.fake_pins.extend(fake_pins)
|
||||
|
||||
# Add vias to the corners
|
||||
shift = self.track_wire + self.track_space
|
||||
half_wide = self.track_wire / 2
|
||||
for i in range(4):
|
||||
ll, ur = new_pins[i].rect
|
||||
if i % 2:
|
||||
top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide)
|
||||
else:
|
||||
top_left = vector(ll.x + half_wide, ur.y - half_wide)
|
||||
for j in range(num_vias):
|
||||
for k in range(num_vias):
|
||||
offset = vector(top_left.x + j * shift, top_left.y - k * shift)
|
||||
self.design.add_via_center(layers=self.layers,
|
||||
offset=offset)
|
||||
|
||||
# Save side pins for routing
|
||||
self.new_pins[pin_name] = new_pins
|
||||
for pin in new_pins:
|
||||
self.blockages.append(self.inflate_shape(pin))
|
||||
|
||||
|
||||
def get_mst_pairs(self, pins):
|
||||
"""
|
||||
Return the pin pairs from the minimum spanning tree in a graph that
|
||||
connects all pins together.
|
||||
"""
|
||||
|
||||
pin_count = len(pins)
|
||||
|
||||
# Create an adjacency matrix that connects all pins
|
||||
edges = [[0] * pin_count for i in range(pin_count)]
|
||||
for i in range(pin_count):
|
||||
for j in range(pin_count):
|
||||
# Skip if they're the same pin
|
||||
if i == j:
|
||||
continue
|
||||
# Skip if both pins are fake
|
||||
if pins[i] in self.fake_pins and pins[j] in self.fake_pins:
|
||||
continue
|
||||
edges[i][j] = pins[i].distance(pins[j])
|
||||
|
||||
pin_connected = [False] * pin_count
|
||||
pin_connected[0] = True
|
||||
|
||||
# Add the minimum cost edge in each iteration (Prim's)
|
||||
mst_pairs = []
|
||||
for i in range(pin_count - 1):
|
||||
min_cost = float("inf")
|
||||
s = 0
|
||||
t = 0
|
||||
# Iterate over already connected pins
|
||||
for m in range(pin_count):
|
||||
# Skip if not connected
|
||||
if not pin_connected[m]:
|
||||
continue
|
||||
# Iterate over this pin's neighbors
|
||||
for n in range(pin_count):
|
||||
# Skip if already connected or isn't a neighbor
|
||||
if pin_connected[n] or edges[m][n] == 0:
|
||||
continue
|
||||
# Choose this edge if it's better the the current one
|
||||
if edges[m][n] < min_cost:
|
||||
min_cost = edges[m][n]
|
||||
s = m
|
||||
t = n
|
||||
pin_connected[t] = True
|
||||
mst_pairs.append((pins[s], pins[t]))
|
||||
|
||||
return mst_pairs
|
||||
|
||||
|
||||
def get_new_pins(self, name):
|
||||
""" Return the new supply pins added by this router. """
|
||||
|
||||
return self.new_pins[name]
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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.
|
||||
#
|
||||
from datetime import datetime
|
||||
from scipy.sparse import csr_matrix
|
||||
from scipy.sparse.csgraph import minimum_spanning_tree
|
||||
from openram import debug
|
||||
from openram import print_time
|
||||
from .router import router
|
||||
from . import grid_utils
|
||||
from .signal_grid import signal_grid
|
||||
|
||||
|
||||
class supply_tree_router(router):
|
||||
"""
|
||||
A router class to read an obstruction map from a gds and
|
||||
routes a grid to connect the supply on the two layers.
|
||||
"""
|
||||
|
||||
def __init__(self, layers, design, bbox=None, pin_type=None):
|
||||
"""
|
||||
This will route on layers in design. It will get the blockages from
|
||||
either the gds file name or the design itself (by saving to a gds file).
|
||||
"""
|
||||
# Power rail width in minimum wire widths
|
||||
# This is set to match the signal router so that the grids are aligned
|
||||
# for prettier routes.
|
||||
self.route_track_width = 1
|
||||
|
||||
# The pin escape router already made the bounding box big enough,
|
||||
# so we can use the regular bbox here.
|
||||
if pin_type:
|
||||
debug.check(pin_type in ["left", "right", "top", "bottom", "single", "ring"],
|
||||
"Invalid pin type {}".format(pin_type))
|
||||
self.pin_type = pin_type
|
||||
router.__init__(self,
|
||||
layers,
|
||||
design,
|
||||
bbox=bbox,
|
||||
route_track_width=self.route_track_width)
|
||||
|
||||
|
||||
def route(self, vdd_name="vdd", gnd_name="gnd"):
|
||||
"""
|
||||
Route the two nets in a single layer.
|
||||
Setting pin stripe will make a power rail on the left side.
|
||||
"""
|
||||
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
|
||||
self.vdd_name = vdd_name
|
||||
self.gnd_name = gnd_name
|
||||
|
||||
# Clear the pins if we have previously routed
|
||||
if (hasattr(self, 'rg')):
|
||||
self.clear_pins()
|
||||
else:
|
||||
# Creat a routing grid over the entire area
|
||||
# FIXME: This could be created only over the routing region,
|
||||
# but this is simplest for now.
|
||||
self.create_routing_grid(signal_grid)
|
||||
|
||||
start_time = datetime.now()
|
||||
|
||||
# Get the pin shapes
|
||||
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
|
||||
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
|
||||
|
||||
# Add side pins if enabled
|
||||
if self.pin_type in ["left", "right", "top", "bottom"]:
|
||||
self.add_side_supply_pin(self.vdd_name, side=self.pin_type)
|
||||
self.add_side_supply_pin(self.gnd_name, side=self.pin_type)
|
||||
elif self.pin_type == "ring":
|
||||
self.add_ring_supply_pin(self.vdd_name)
|
||||
self.add_ring_supply_pin(self.gnd_name)
|
||||
|
||||
#self.write_debug_gds("initial_tree_router.gds",False)
|
||||
#breakpoint()
|
||||
|
||||
# Route the supply pins to the supply rails
|
||||
# Route vdd first since we want it to be shorter
|
||||
start_time = datetime.now()
|
||||
self.route_pins(vdd_name)
|
||||
self.route_pins(gnd_name)
|
||||
print_time("Maze routing supplies", datetime.now(), start_time, 3)
|
||||
|
||||
# Did we route everything??
|
||||
if not self.check_all_routed(vdd_name):
|
||||
return False
|
||||
if not self.check_all_routed(gnd_name):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def route_pins(self, pin_name):
|
||||
"""
|
||||
This will route each of the remaining pin components to the other pins.
|
||||
After it is done, the cells are added to the pin blockage list.
|
||||
"""
|
||||
|
||||
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
|
||||
debug.info(1, "Routing {0} with {1} pins.".format(pin_name,
|
||||
remaining_components))
|
||||
|
||||
# Save pin center locations
|
||||
if False:
|
||||
debug.info(2, "Creating location file {0}_{1}.csv".format(self.cell.name, pin_name))
|
||||
f = open("{0}_{1}.csv".format(self.cell.name, pin_name), "w")
|
||||
pin_size = len(self.pin_groups[pin_name])
|
||||
for index1, pg1 in enumerate(self.pin_groups[pin_name]):
|
||||
location = list(pg1.grids)[0]
|
||||
f.write("{0},{1},{2}\n".format(location.x, location.y, location.z))
|
||||
f.close()
|
||||
|
||||
# Create full graph
|
||||
debug.info(2, "Creating adjacency matrix")
|
||||
pin_size = len(self.pin_groups[pin_name])
|
||||
adj_matrix = [[0] * pin_size for i in range(pin_size)]
|
||||
|
||||
for index1, pg1 in enumerate(self.pin_groups[pin_name]):
|
||||
for index2, pg2 in enumerate(self.pin_groups[pin_name]):
|
||||
if index1>=index2:
|
||||
continue
|
||||
dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids))
|
||||
adj_matrix[index1][index2] = dist
|
||||
|
||||
# Find MST
|
||||
debug.info(2, "Finding Minimum Spanning Tree")
|
||||
X = csr_matrix(adj_matrix)
|
||||
from scipy.sparse import save_npz
|
||||
#print("Saving {}.npz".format(self.cell.name))
|
||||
#save_npz("{}.npz".format(self.cell.name), X)
|
||||
#exit(1)
|
||||
|
||||
Tcsr = minimum_spanning_tree(X)
|
||||
mst = Tcsr.toarray().astype(int)
|
||||
connections = []
|
||||
for x in range(pin_size):
|
||||
for y in range(pin_size):
|
||||
if x >= y:
|
||||
continue
|
||||
if mst[x][y]>0:
|
||||
connections.append((x, y))
|
||||
|
||||
# Route MST components
|
||||
level=99
|
||||
for index, (src, dest) in enumerate(connections):
|
||||
if not (index % 25):
|
||||
debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index))
|
||||
self.route_signal(pin_name, src, dest)
|
||||
if False and pin_name == "gnd":
|
||||
debug.info(level, "\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))
|
||||
debug.info(level, ("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages)))
|
||||
self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False)
|
||||
|
||||
#self.write_debug_gds("final_tree_router_{}.gds".format(pin_name), False)
|
||||
#return
|
||||
|
||||
def route_signal(self, pin_name, src_idx, dest_idx):
|
||||
|
||||
# First pass, try to route normally
|
||||
# Second pass, clear prior pin blockages so that you can route over other metal
|
||||
# of the same supply. Otherwise, this can create a lot of circular routes due to accidental overlaps.
|
||||
for unblock_routes in [False, True]:
|
||||
for detour_scale in [2 * pow(2, x) for x in range(5)]:
|
||||
debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale))
|
||||
|
||||
# Clear everything in the routing grid.
|
||||
self.rg.reinit()
|
||||
|
||||
# This is inefficient since it is non-incremental, but it was
|
||||
# easier to debug.
|
||||
self.prepare_blockages(src=(pin_name, src_idx), dest=(pin_name, dest_idx))
|
||||
if unblock_routes:
|
||||
msg = "Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})"
|
||||
debug.warning(msg.format(pin_name,
|
||||
self.pin_groups[pin_name][src_idx].pins))
|
||||
self.set_blockages(self.path_blockages, False)
|
||||
|
||||
# Add the single component of the pin as the source
|
||||
# which unmarks it as a blockage too
|
||||
self.set_pin_component_source(pin_name, src_idx)
|
||||
|
||||
# Marks all pin components except index as target
|
||||
# which unmarks it as a blockage too
|
||||
self.set_pin_component_target(pin_name, dest_idx)
|
||||
|
||||
# Actually run the A* router
|
||||
if self.run_router(detour_scale=detour_scale):
|
||||
return
|
||||
#if detour_scale > 2:
|
||||
# self.write_debug_gds("route_{0}_{1}_d{2}.gds".format(src_idx, dest_idx, detour_scale), False)
|
||||
|
||||
self.write_debug_gds("debug_route.gds", True)
|
||||
|
||||
def add_io_pin(self, instance, pin_name, new_name=""):
|
||||
"""
|
||||
Add a signle input or output pin up to metal 3.
|
||||
"""
|
||||
pin = instance.get_pins(pin_name)
|
||||
|
||||
if new_name == "":
|
||||
new_name = pin_name
|
||||
|
||||
# Just use the power pin function for now to save code
|
||||
self.add_power_pin(name=new_name, loc=pin.center(), start_layer=pin.layer)
|
||||
|
|
@ -60,6 +60,14 @@ class sram():
|
|||
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)
|
||||
|
||||
|
|
@ -95,18 +103,40 @@ class sram():
|
|||
# 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,
|
||||
os.path.basename(spname),
|
||||
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()
|
||||
|
|
@ -154,8 +184,6 @@ class sram():
|
|||
# Use generated spice file for characterization
|
||||
sp_file = spname
|
||||
|
||||
# Save a functional simulation file
|
||||
|
||||
# Characterize the design
|
||||
start_time = datetime.datetime.now()
|
||||
from openram.characterizer import lib
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from openram import OPTS
|
|||
class sram_config:
|
||||
""" This is a structure that is used to hold the SRAM configuration options. """
|
||||
|
||||
def __init__(self, word_size, num_words, write_size=None, num_banks=1, words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
def __init__(self, word_size, num_words, write_size=None, num_banks=1,
|
||||
words_per_row=None, num_spare_rows=0, num_spare_cols=0):
|
||||
self.word_size = word_size
|
||||
self.num_words = num_words
|
||||
# Don't add a write mask if it is the same size as the data word
|
||||
|
|
@ -38,6 +39,11 @@ class sram_config:
|
|||
except ImportError:
|
||||
self.array_col_multiple = 1
|
||||
|
||||
if not self.num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
|
||||
if not self.num_spare_rows:
|
||||
self.num_spare_rows = 0
|
||||
|
||||
# This will get over-written when we determine the organization
|
||||
self.words_per_row = words_per_row
|
||||
|
|
@ -175,3 +181,43 @@ class sram_config:
|
|||
return int(words_per_row * tentative_num_rows / 16)
|
||||
|
||||
return words_per_row
|
||||
|
||||
def setup_multiport_constants(self):
|
||||
"""
|
||||
These are contants and lists that aid multiport design.
|
||||
Ports are always in the order RW, W, R.
|
||||
Port indices start from 0 and increment.
|
||||
A first RW port will have clk0, csb0, web0, addr0, data0
|
||||
A first W port (with no RW ports) will be: clk0, csb0, addr0, data0
|
||||
|
||||
"""
|
||||
total_ports = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
|
||||
|
||||
# These are the read/write port indices.
|
||||
self.readwrite_ports = []
|
||||
# These are the read/write and write-only port indices
|
||||
self.write_ports = []
|
||||
# These are the write-only port indices.
|
||||
self.writeonly_ports = []
|
||||
# These are the read/write and read-only port indices
|
||||
self.read_ports = []
|
||||
# These are the read-only port indices.
|
||||
self.readonly_ports = []
|
||||
# These are all the ports
|
||||
self.all_ports = list(range(total_ports))
|
||||
|
||||
# The order is always fixed as RW, W, R
|
||||
port_number = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.readwrite_ports.append(port_number)
|
||||
self.write_ports.append(port_number)
|
||||
self.read_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.write_ports.append(port_number)
|
||||
self.writeonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
for port in range(OPTS.num_r_ports):
|
||||
self.read_ports.append(port_number)
|
||||
self.readonly_ports.append(port_number)
|
||||
port_number += 1
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_2mux_1rw_1r_spare_cols_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_2mux_1w_1r_spare_cols_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 0
|
||||
OPTS.num_w_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@ class sram_1bank_2mux_global_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
num_words=32,
|
||||
num_banks=1,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_spare_cols_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
write_size=4,
|
||||
num_words=64,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=8,
|
||||
write_size=4,
|
||||
num_words=64,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_4mux_1rw_1r_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
|
|
@ -30,9 +30,6 @@ class sram_1bank_4mux_test(openram_test):
|
|||
num_spare_rows = 0
|
||||
num_spare_cols = 0
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
c = sram_config(word_size=4,
|
||||
num_words=64,
|
||||
num_banks=1,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ class sram_1bank_8mux_1rw_1r_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
from openram import sram_config
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
OPTS.route_supplies = False
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ class model_delay_test(openram_test):
|
|||
openram.init_openram(config_file, is_unit_test=True)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.spice_name = "Xyce"
|
||||
OPTS.num_sim_threads = 8
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
|
|
|
|||
|
|
@ -61,35 +61,36 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'slew_lh': [0.2592187],
|
||||
'slew_hl': [0.2592187],
|
||||
'delay_lh': [0.2465583],
|
||||
'disabled_write0_power': [0.1924678],
|
||||
'disabled_read0_power': [0.152483],
|
||||
'write0_power': [0.3409064],
|
||||
'disabled_read1_power': [0.1737818],
|
||||
'read0_power': [0.3096708],
|
||||
'read1_power': [0.3107916],
|
||||
'delay_hl': [0.2465583],
|
||||
'write1_power': [0.26915849999999997],
|
||||
'leakage_power': 0.002044307,
|
||||
'min_period': 0.898,
|
||||
'disabled_write1_power': [0.201411]}
|
||||
golden_data = {'delay_hl': [0.263898],
|
||||
'delay_lh': [0.263898],
|
||||
'disabled_read0_power': [0.06625703],
|
||||
'disabled_read1_power': [0.07531121],
|
||||
'disabled_write0_power': [0.09350641999999999],
|
||||
'disabled_write1_power': [0.09988823000000001],
|
||||
'leakage_power': 0.01192385,
|
||||
'min_period': 2.031,
|
||||
'read0_power': [0.14745439999999999],
|
||||
'read1_power': [0.1470831],
|
||||
'slew_hl': [0.027165],
|
||||
'slew_lh': [0.027165],
|
||||
'write0_power': [0.1630546],
|
||||
'write1_power': [0.1319501]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'read1_power': [12.11658],
|
||||
'write1_power': [10.52653],
|
||||
'read0_power': [11.956710000000001],
|
||||
'disabled_write0_power': [7.673665],
|
||||
'disabled_write1_power': [7.981922000000001],
|
||||
'slew_lh': [1.868836],
|
||||
'slew_hl': [1.868836],
|
||||
'delay_hl': [1.8598510000000001],
|
||||
'delay_lh': [1.8598510000000001],
|
||||
'leakage_power': 0.005457728,
|
||||
'disabled_read0_power': [5.904712],
|
||||
'min_period': 6.875,
|
||||
'disabled_read1_power': [7.132159],
|
||||
'write0_power': [13.406400000000001]}
|
||||
golden_data = {'delay_hl': [1.8259260000000002],
|
||||
'delay_lh': [1.8259260000000002],
|
||||
'disabled_read0_power': [6.722809],
|
||||
'disabled_read1_power': [8.104113],
|
||||
'disabled_write0_power': [8.900671],
|
||||
'disabled_write1_power': [9.188668],
|
||||
'leakage_power': 0.6977637,
|
||||
'min_period': 6.562,
|
||||
'read0_power': [15.45948],
|
||||
'read1_power': [15.48587],
|
||||
'slew_hl': [0.1936536],
|
||||
'slew_lh': [0.1936536],
|
||||
'write0_power': [17.03442],
|
||||
'write1_power': [13.05424]}
|
||||
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -69,35 +69,36 @@ class timing_sram_test(openram_test):
|
|||
data.update(port_data[0])
|
||||
|
||||
if OPTS.tech_name == "freepdk45":
|
||||
golden_data = {'delay_hl': [0.24671600000000002],
|
||||
'delay_lh': [0.24671600000000002],
|
||||
'disabled_read0_power': [0.1749204],
|
||||
'disabled_read1_power': [0.1873704],
|
||||
'disabled_write0_power': [0.204619],
|
||||
'disabled_write1_power': [0.2262653],
|
||||
'leakage_power': 0.0021375310000000002,
|
||||
'min_period': 0.977,
|
||||
'read0_power': [0.3856875],
|
||||
'read1_power': [0.38856060000000003],
|
||||
'slew_hl': [0.2842019],
|
||||
'slew_lh': [0.2842019],
|
||||
'write0_power': [0.45274410000000004],
|
||||
'write1_power': [0.38727789999999995]}
|
||||
golden_data = {'delay_hl': [0.2764415],
|
||||
'delay_lh': [0.2764415],
|
||||
'disabled_read0_power': [0.18364834],
|
||||
'disabled_read1_power': [0.20878333999999998],
|
||||
'disabled_write0_power': [0.24064433999999998],
|
||||
'disabled_write1_power': [0.27207664],
|
||||
'leakage_power': 0.0443369,
|
||||
'min_period': 0.938,
|
||||
'read0_power': [0.37790994],
|
||||
'read1_power': [0.37646214],
|
||||
'slew_hl': [0.0266144],
|
||||
'slew_lh': [0.0266144],
|
||||
'write0_power': [0.44694044],
|
||||
'write1_power': [0.36824544000000003]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'delay_hl': [1.882508],
|
||||
'delay_lh': [1.882508],
|
||||
'disabled_read0_power': [7.487227],
|
||||
'disabled_read1_power': [8.749013],
|
||||
'disabled_write0_power': [9.268901],
|
||||
'disabled_write1_power': [9.962973],
|
||||
'leakage_power': 0.0046686359999999994,
|
||||
'min_period': 7.188,
|
||||
'read0_power': [16.64011],
|
||||
'read1_power': [17.20825],
|
||||
'slew_hl': [2.039655],
|
||||
'slew_lh': [2.039655],
|
||||
'write0_power': [19.31883],
|
||||
'write1_power': [15.297369999999999]}
|
||||
golden_data = {'delay_hl': [1.905376],
|
||||
'delay_lh': [1.905376],
|
||||
'disabled_read0_power': [7.673850999999999],
|
||||
'disabled_read1_power': [10.051073],
|
||||
'disabled_write0_power': [10.638803],
|
||||
'disabled_write1_power': [10.385253],
|
||||
'leakage_power': 2.704021,
|
||||
'min_period': 6.875,
|
||||
'read0_power': [17.583853],
|
||||
'read1_power': [17.689162999999997],
|
||||
'slew_hl': [0.19331199999999998],
|
||||
'slew_lh': [0.19331199999999998],
|
||||
'write0_power': [20.607043],
|
||||
'write1_power': [16.107403]}
|
||||
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
|
||||
|
|
|
|||
|
|
@ -84,20 +84,20 @@ class timing_sram_test(openram_test):
|
|||
'write0_power': [0.429895901],
|
||||
'write1_power': [0.383337501]}
|
||||
elif OPTS.tech_name == "scn4m_subm":
|
||||
golden_data = {'delay_hl': [1.884186],
|
||||
'delay_lh': [1.884186],
|
||||
'disabled_read0_power': [20.86336],
|
||||
'disabled_read1_power': [22.10636],
|
||||
'disabled_write0_power': [22.62321],
|
||||
'disabled_write1_power': [23.316010000000002],
|
||||
'leakage_power': 13.351170000000002,
|
||||
'min_period': 7.188,
|
||||
'read0_power': [29.90159],
|
||||
'read1_power': [30.47858],
|
||||
'slew_hl': [2.042723],
|
||||
'slew_lh': [2.042723],
|
||||
'write0_power': [32.13199],
|
||||
'write1_power': [28.46703]}
|
||||
golden_data = {'delay_hl': [1.78586],
|
||||
'delay_lh': [1.78586],
|
||||
'disabled_read0_power': [7.8296693788],
|
||||
'disabled_read1_power': [9.1464723788],
|
||||
'disabled_write0_power': [9.6889073788],
|
||||
'disabled_write1_power': [10.4123023788],
|
||||
'leakage_power': 0.0002442851,
|
||||
'min_period': 6.875,
|
||||
'read0_power': [16.995952378800002],
|
||||
'read1_power': [17.5845523788],
|
||||
'slew_hl': [2.039202],
|
||||
'slew_lh': [2.039202],
|
||||
'write0_power': [19.785462378800002],
|
||||
'write1_power': [15.742192378799999]}
|
||||
else:
|
||||
self.assertTrue(False) # other techs fail
|
||||
# Check if no too many or too few results
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 sys, os, re
|
||||
import shutil
|
||||
import getpass
|
||||
import unittest
|
||||
from testutils import *
|
||||
|
||||
import openram
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class sram_char_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
global OPTS
|
||||
out_file = "sram_2_16_1_{0}".format(OPTS.tech_name)
|
||||
out_path = "{0}/testsram_{1}_{2}_{3}".format(OPTS.openram_temp, OPTS.tech_name, getpass.getuser(), os.getpid())
|
||||
OPTS.output_name = out_file
|
||||
OPTS.output_path = out_path
|
||||
|
||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||
config_file = "{}/tests/configs/config_mem_char_func".format(OPENRAM_HOME)
|
||||
|
||||
openram.init_openram(config_file, is_unit_test=False)
|
||||
sp_file = "{0}/tests/sp_files/{1}.sp".format(OPENRAM_HOME, OPTS.output_name)
|
||||
|
||||
debug.info(1, "Testing commandline characterizer script sram_char.py with 2-bit, 16 word SRAM.")
|
||||
|
||||
# make sure we start without the files existing
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
try:
|
||||
os.makedirs(out_path, 0o0750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(out_path, 0o0750)
|
||||
|
||||
# specify the same verbosity for the system call
|
||||
options = ""
|
||||
for i in range(OPTS.verbose_level):
|
||||
options += " -v"
|
||||
|
||||
if OPTS.spice_name:
|
||||
options += " -s {}".format(OPTS.spice_name)
|
||||
|
||||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/../sram_char.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "{0}{1}/../sram_char.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_mem_char_func.py".format(OPENRAM_HOME)
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} {5} 2>&1 > {6}/output.log".format(exe_name,
|
||||
out_file,
|
||||
out_path,
|
||||
options,
|
||||
config_name,
|
||||
sp_file,
|
||||
out_path)
|
||||
debug.info(1, cmd)
|
||||
os.system(cmd)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/{1}.log".format(out_path, out_file), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
# now clean up the directory
|
||||
if not OPTS.keep_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
openram.end_openram()
|
||||
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = openram.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 sys, os, re
|
||||
import shutil
|
||||
import getpass
|
||||
import unittest
|
||||
from testutils import *
|
||||
|
||||
import openram
|
||||
from openram import debug
|
||||
from openram import OPTS
|
||||
|
||||
|
||||
class sram_func_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
global OPTS
|
||||
out_file = "sram_2_16_1_{0}".format(OPTS.tech_name)
|
||||
out_path = "{0}/testsram_{1}_{2}_{3}".format(OPTS.openram_temp, OPTS.tech_name, getpass.getuser(), os.getpid())
|
||||
OPTS.output_name = out_file
|
||||
OPTS.output_path = out_path
|
||||
|
||||
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
|
||||
config_file = "{}/tests/configs/config_mem_char_func".format(OPENRAM_HOME)
|
||||
|
||||
openram.init_openram(config_file, is_unit_test=False)
|
||||
sp_file = "{0}/tests/sp_files/{1}.sp".format(OPENRAM_HOME, OPTS.output_name)
|
||||
|
||||
debug.info(1, "Testing commandline functional simulator script sram_char.py with 2-bit, 16 word SRAM.")
|
||||
|
||||
# make sure we start without the files existing
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
try:
|
||||
os.makedirs(out_path, 0o0750)
|
||||
except OSError as e:
|
||||
if e.errno == 17: # errno.EEXIST
|
||||
os.chmod(out_path, 0o0750)
|
||||
|
||||
# specify the same verbosity for the system call
|
||||
options = ""
|
||||
for i in range(OPTS.verbose_level):
|
||||
options += " -v"
|
||||
|
||||
if OPTS.spice_name:
|
||||
options += " -s {}".format(OPTS.spice_name)
|
||||
|
||||
if OPTS.tech_name:
|
||||
options += " -t {}".format(OPTS.tech_name)
|
||||
|
||||
options += " -j 2"
|
||||
|
||||
# Always perform code coverage
|
||||
if OPTS.coverage == 0:
|
||||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/../sram_func.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "{0}{1}/../sram_func.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_mem_char_func.py".format(OPENRAM_HOME)
|
||||
period_and_cycles = 10
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} {5} {6} {6} 2>&1 > {7}/output.log".format(exe_name,
|
||||
out_file,
|
||||
out_path,
|
||||
options,
|
||||
config_name,
|
||||
sp_file,
|
||||
period_and_cycles,
|
||||
out_path)
|
||||
debug.info(1, cmd)
|
||||
os.system(cmd)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/output.log".format(out_path), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
# now clean up the directory
|
||||
if not OPTS.keep_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
openram.end_openram()
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = openram.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -180,6 +180,8 @@ BROKEN_STAMPS = \
|
|||
sky130/30_openram_back_end_test.ok \
|
||||
sky130/30_openram_front_end_library_test.ok \
|
||||
sky130/30_openram_front_end_test.ok \
|
||||
sky130/30_openram_sram_char_test.ok \
|
||||
sky130/30_openram_sram_func_test.ok \
|
||||
|
||||
gettech = $(word 1,$(subst /, ,$*))
|
||||
getfile = $(word 2,$(subst /, ,$*))
|
||||
|
|
|
|||
|
|
@ -5,10 +5,17 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from openram import OPTS
|
||||
word_size = 2
|
||||
num_words = 16
|
||||
|
||||
from enum import Enum
|
||||
tech_name = OPTS.tech_name
|
||||
if tech_name == "sky130":
|
||||
num_spare_cols = 1
|
||||
num_spare_rows = 1
|
||||
|
||||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
output_name = "sram"
|
||||
|
||||
analytical_delay = False
|
||||
nominal_corner_only = True
|
||||
spice_name = "Xyce"
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -46,7 +46,8 @@ Measures the timing/power through SPICE simulation:
|
|||
* Testing Support Modules
|
||||
* Other modules are derivatives of the simulation module used in the unit tests
|
||||
|
||||
|
||||
## Stand-alone Charaterizer
|
||||
The stand-alone characterizer is a script ([sram_char.py]()sram_char.py) that can be run without generating an SRAM.
|
||||
|
||||
## Characterization Options
|
||||
* Characterization by Configuration File
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ ROM_SRCS=$(filter-out disabled-% %_common.py,$(sort $(notdir $(wildcard $(ROM_CO
|
|||
ROM_DIRS=$(basename $(ROM_SRCS))
|
||||
ROM_STAMPS=$(addsuffix .ok,$(ROM_DIRS))
|
||||
|
||||
DIRS=$(SRAM_DIRS) $(ROM_DIRS)
|
||||
|
||||
configs:
|
||||
@echo
|
||||
@echo "Using OpenRAM at $(OPENRAM_HOME)"
|
||||
|
|
@ -80,12 +82,12 @@ sram: $(WORKING_SRAM_STAMPS)
|
|||
%.ok: sram_configs/%.py
|
||||
@echo "Building $*"
|
||||
@mkdir -p $*
|
||||
@python3 -u $(SRAM_COMPILER) $(OPENRAM_OPTS) -o $* -p $(MACRO_DIR)/$* $(MACRO_DIR)/$< && touch $@
|
||||
@OPENRAM_TMP=$*/tmp python3 -u $(SRAM_COMPILER) $(OPENRAM_OPTS) -o $* -p $(MACRO_DIR)/$* $(MACRO_DIR)/$< && touch $@
|
||||
|
||||
%.ok: rom_configs/%.py
|
||||
@echo "Building $*"
|
||||
@mkdir -p $*
|
||||
@python3 -u $(ROM_COMPILER) $(OPENRAM_OPTS) -o $* -p $(MACRO_DIR)/$* $(MACRO_DIR)/$< && touch $@
|
||||
@OPENRAM_TMP=$*/tmp python3 -u $(ROM_COMPILER) $(OPENRAM_OPTS) -o $* -p $(MACRO_DIR)/$* $(MACRO_DIR)/$< && touch $@
|
||||
|
||||
.DELETE_ON_ERROR: $(WORKING_SRAM_STAMPS) $(WORKING_ROM_STAMPS)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 script will characterize an SRAM previously generated by OpenRAM given a
|
||||
configuration file. Configuration option "use_pex" determines whether extracted
|
||||
or generated spice is used and option "analytical_delay" determines whether
|
||||
an analytical model or spice simulation is used for characterization.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# You don't need the next two lines if you're sure that openram package is installed
|
||||
from common import *
|
||||
make_openram_package()
|
||||
import openram
|
||||
|
||||
(OPTS, args) = openram.parse_args()
|
||||
|
||||
# Override the usage
|
||||
USAGE = "Usage: {} [options] <config file> <spice netlist>\nUse -h for help.\n".format(__file__)
|
||||
|
||||
# Check that we are left with a single configuration file as argument.
|
||||
if len(args) != 2:
|
||||
print(USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
OPTS.top_process = 'memchar'
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
||||
# Parse config file and set up all the options
|
||||
openram.init_openram(config_file=args[0], is_unit_test=False)
|
||||
|
||||
openram.print_banner()
|
||||
|
||||
# Configure the SRAM organization (duplicated from openram.py)
|
||||
from openram.characterizer import fake_sram
|
||||
s = fake_sram(name=OPTS.output_name,
|
||||
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)
|
||||
|
||||
debug.check(os.path.exists(args[1]), "Spice netlist file {} not found.".format(args[1]))
|
||||
sp_file = args[1]
|
||||
s.generate_pins()
|
||||
s.setup_multiport_constants()
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
OPTS.nomimal_corner_only = True
|
||||
|
||||
# TODO: remove this after adding trimmed netlist gen to sram run
|
||||
OPTS.trim_netlist = False
|
||||
|
||||
# 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=s, sp_file=sp_file, use_model=False)
|
||||
print_time("Characterization", datetime.datetime.now(), start_time)
|
||||
|
||||
# Output info about this run
|
||||
print("Output files are:\n{0}*.lib".format(OPTS.output_path))
|
||||
#report_status() #could modify this function to provide relevant info
|
||||
|
||||
# Delete temp files, remove the dir, etc.
|
||||
openram.end_openram()
|
||||
|
|
@ -32,6 +32,8 @@ if len(args) != 1:
|
|||
print(openram.USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
# Set top process to openram
|
||||
OPTS.top_process = 'openram'
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
|
@ -76,4 +78,3 @@ s.save()
|
|||
# Delete temp files etc.
|
||||
openram.end_openram()
|
||||
openram.print_time("End", datetime.datetime.now(), start_time)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2023 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 script will functionally simulate an SRAM previously generated by OpenRAM
|
||||
given a configuration file. Configuration option "use_pex" determines whether
|
||||
extracted or generated spice is used. Command line arguments dictate the
|
||||
number of cycles and period to be simulated.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
# You don't need the next two lines if you're sure that openram package is installed
|
||||
from common import *
|
||||
make_openram_package()
|
||||
import openram
|
||||
|
||||
(OPTS, args) = openram.parse_args()
|
||||
|
||||
# Override the usage
|
||||
USAGE = "Usage: {} [options] <config file> <sp_file> <cycles> <period>\nUse -h for help.\n".format(__file__)
|
||||
|
||||
# Check that we are left with a single configuration file as argument.
|
||||
if len(args) != 4:
|
||||
print(USAGE)
|
||||
sys.exit(2)
|
||||
|
||||
OPTS.top_process = 'memfunc'
|
||||
|
||||
# Parse argument
|
||||
config_file = args[0]
|
||||
sp_file = args[1]
|
||||
cycles = int(args[2])
|
||||
period = float(args[3])
|
||||
|
||||
# These depend on arguments, so don't load them until now.
|
||||
from openram import debug
|
||||
|
||||
# Parse config file and set up all the options
|
||||
openram.init_openram(config_file=config_file, is_unit_test=False)
|
||||
|
||||
openram.print_banner()
|
||||
|
||||
# Configure the SRAM organization (duplicated from openram.py)
|
||||
from openram.characterizer.fake_sram import fake_sram
|
||||
s = fake_sram(name=OPTS.output_name,
|
||||
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)
|
||||
|
||||
s.generate_pins()
|
||||
s.setup_multiport_constants()
|
||||
|
||||
OPTS.netlist_only = True
|
||||
OPTS.check_lvsdrc = False
|
||||
|
||||
# Generate stimulus and run functional simulation on the design
|
||||
start_time = datetime.datetime.now()
|
||||
from openram.characterizer import functional
|
||||
debug.print_raw("Functional simulation... ")
|
||||
f = functional(s, cycles=cycles, spfile=sp_file, period=period, output_path=OPTS.openram_temp)
|
||||
(fail, error) = f.run()
|
||||
debug.print_raw(error)
|
||||
openram.print_time("Functional simulation", datetime.datetime.now(), start_time)
|
||||
|
||||
# Delete temp files, remove the dir, etc. after success
|
||||
if fail:
|
||||
openram.end_openram()
|
||||
Binary file not shown.
|
|
@ -1,11 +1,16 @@
|
|||
|
||||
.SUBCKT sense_amp bl br dout en vdd gnd
|
||||
M_1 dout net_1 vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_3 net_1 dout vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_2 dout net_1 net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_8 net_1 dout net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_5 bl en dout vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_1 dint net_1 vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_3 net_1 dint vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_2 dint net_1 net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_8 net_1 dint net_2 gnd nmos_vtg w=270.0n l=50.0n
|
||||
M_5 bl en dint vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_6 br en net_1 vdd pmos_vtg w=720.0n l=50.0n
|
||||
M_7 net_2 en gnd gnd nmos_vtg w=270.0n l=50.0n
|
||||
|
||||
M_9 dout_bar dint vdd vdd pmos_vtg w=180.0n l=50.0n
|
||||
M_10 dout_bar dint gnd gnd nmos_vtg w=90.0n l=50.0n
|
||||
M_11 dout dout_bar vdd vdd pmos_vtg w=540.0n l=50.0n
|
||||
M_12 dout dout_bar gnd gnd nmos_vtg w=270.0n l=50.0n
|
||||
.ENDS sense_amp
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,136 +1,183 @@
|
|||
magic
|
||||
tech scmos
|
||||
timestamp 1536089670
|
||||
timestamp 1681333912
|
||||
<< nwell >>
|
||||
rect 0 0 40 102
|
||||
rect 0 28 40 153
|
||||
<< pwell >>
|
||||
rect 0 102 40 163
|
||||
rect 0 153 40 214
|
||||
rect 0 0 40 28
|
||||
<< ntransistor >>
|
||||
rect 21 130 23 139
|
||||
rect 12 108 14 117
|
||||
rect 20 108 22 117
|
||||
rect 21 181 23 190
|
||||
rect 12 159 14 168
|
||||
rect 20 159 22 168
|
||||
rect 13 10 15 22
|
||||
rect 21 18 23 22
|
||||
<< ptransistor >>
|
||||
rect 12 78 14 96
|
||||
rect 20 78 22 96
|
||||
rect 11 20 13 44
|
||||
rect 27 20 29 44
|
||||
rect 12 129 14 147
|
||||
rect 20 129 22 147
|
||||
rect 11 71 13 95
|
||||
rect 27 71 29 95
|
||||
rect 13 34 15 58
|
||||
rect 21 34 23 42
|
||||
<< ndiffusion >>
|
||||
rect 20 130 21 139
|
||||
rect 23 130 24 139
|
||||
rect 11 108 12 117
|
||||
rect 14 108 15 117
|
||||
rect 19 108 20 117
|
||||
rect 22 108 23 117
|
||||
rect 20 181 21 190
|
||||
rect 23 181 24 190
|
||||
rect 11 159 12 168
|
||||
rect 14 159 15 168
|
||||
rect 19 159 20 168
|
||||
rect 22 159 23 168
|
||||
rect 12 10 13 22
|
||||
rect 15 10 16 22
|
||||
rect 20 18 21 22
|
||||
rect 23 18 24 22
|
||||
<< pdiffusion >>
|
||||
rect 7 94 12 96
|
||||
rect 11 80 12 94
|
||||
rect 7 78 12 80
|
||||
rect 14 94 20 96
|
||||
rect 14 80 15 94
|
||||
rect 19 80 20 94
|
||||
rect 14 78 20 80
|
||||
rect 22 94 27 96
|
||||
rect 22 80 23 94
|
||||
rect 22 78 27 80
|
||||
rect 10 20 11 44
|
||||
rect 13 20 14 44
|
||||
rect 26 20 27 44
|
||||
rect 29 20 30 44
|
||||
rect 7 145 12 147
|
||||
rect 11 131 12 145
|
||||
rect 7 129 12 131
|
||||
rect 14 145 20 147
|
||||
rect 14 131 15 145
|
||||
rect 19 131 20 145
|
||||
rect 14 129 20 131
|
||||
rect 22 145 27 147
|
||||
rect 22 131 23 145
|
||||
rect 22 129 27 131
|
||||
rect 10 71 11 95
|
||||
rect 13 71 14 95
|
||||
rect 26 71 27 95
|
||||
rect 29 71 30 95
|
||||
rect 12 34 13 58
|
||||
rect 15 34 16 58
|
||||
rect 20 34 21 42
|
||||
rect 23 34 24 42
|
||||
<< ndcontact >>
|
||||
rect 16 130 20 139
|
||||
rect 24 130 28 139
|
||||
rect 7 108 11 117
|
||||
rect 15 108 19 117
|
||||
rect 23 108 27 117
|
||||
rect 16 181 20 190
|
||||
rect 24 181 28 190
|
||||
rect 7 159 11 168
|
||||
rect 15 159 19 168
|
||||
rect 23 159 27 168
|
||||
rect 8 10 12 22
|
||||
rect 16 10 20 22
|
||||
rect 24 18 28 22
|
||||
<< pdcontact >>
|
||||
rect 7 80 11 94
|
||||
rect 15 80 19 94
|
||||
rect 23 80 27 94
|
||||
rect 6 20 10 44
|
||||
rect 14 20 18 44
|
||||
rect 22 20 26 44
|
||||
rect 30 20 34 44
|
||||
rect 7 131 11 145
|
||||
rect 15 131 19 145
|
||||
rect 23 131 27 145
|
||||
rect 6 71 10 95
|
||||
rect 14 71 18 95
|
||||
rect 22 71 26 95
|
||||
rect 30 71 34 95
|
||||
rect 8 34 12 58
|
||||
rect 16 34 20 58
|
||||
rect 24 34 28 42
|
||||
<< psubstratepcontact >>
|
||||
rect 32 137 36 141
|
||||
rect 32 188 36 192
|
||||
rect 32 13 36 17
|
||||
<< nsubstratencontact >>
|
||||
rect 27 70 31 74
|
||||
rect 27 121 31 125
|
||||
rect 27 55 31 59
|
||||
<< polysilicon >>
|
||||
rect 21 139 23 149
|
||||
rect 21 129 23 130
|
||||
rect 3 127 23 129
|
||||
rect 3 47 5 127
|
||||
rect 12 122 34 124
|
||||
rect 12 117 14 122
|
||||
rect 20 117 22 119
|
||||
rect 12 96 14 108
|
||||
rect 20 96 22 108
|
||||
rect 32 105 34 122
|
||||
rect 30 101 34 105
|
||||
rect 12 76 14 78
|
||||
rect 20 69 22 78
|
||||
rect 13 67 22 69
|
||||
rect 9 55 11 65
|
||||
rect 32 55 34 101
|
||||
rect 33 51 34 55
|
||||
rect 3 45 13 47
|
||||
rect 11 44 13 45
|
||||
rect 27 44 29 46
|
||||
rect 11 19 13 20
|
||||
rect 27 19 29 20
|
||||
rect 11 17 29 19
|
||||
rect 21 190 23 200
|
||||
rect 21 180 23 181
|
||||
rect 3 178 23 180
|
||||
rect 3 98 5 178
|
||||
rect 12 173 34 175
|
||||
rect 12 168 14 173
|
||||
rect 20 168 22 170
|
||||
rect 12 147 14 159
|
||||
rect 20 147 22 159
|
||||
rect 32 156 34 173
|
||||
rect 30 152 34 156
|
||||
rect 12 127 14 129
|
||||
rect 20 120 22 129
|
||||
rect 13 118 22 120
|
||||
rect 9 106 11 116
|
||||
rect 32 106 34 152
|
||||
rect 33 102 34 106
|
||||
rect 3 96 13 98
|
||||
rect 11 95 13 96
|
||||
rect 27 95 29 97
|
||||
rect 11 70 13 71
|
||||
rect 27 70 29 71
|
||||
rect 11 68 29 70
|
||||
rect 7 63 23 65
|
||||
rect 13 58 15 60
|
||||
rect 21 42 23 63
|
||||
rect 13 31 15 34
|
||||
rect 13 27 14 31
|
||||
rect 13 22 15 27
|
||||
rect 21 22 23 34
|
||||
rect 21 16 23 18
|
||||
rect 13 8 15 10
|
||||
<< polycontact >>
|
||||
rect 20 149 24 153
|
||||
rect 26 101 30 105
|
||||
rect 9 65 13 69
|
||||
rect 9 51 13 55
|
||||
rect 29 51 33 55
|
||||
rect 20 200 24 204
|
||||
rect 26 152 30 156
|
||||
rect 9 116 13 120
|
||||
rect 9 102 13 106
|
||||
rect 29 102 33 106
|
||||
rect 3 63 7 67
|
||||
rect 14 27 18 31
|
||||
<< metal1 >>
|
||||
rect -2 149 20 153
|
||||
rect 24 149 36 153
|
||||
rect 28 133 32 137
|
||||
rect 16 117 19 130
|
||||
rect 7 94 11 108
|
||||
rect 23 105 27 108
|
||||
rect 23 101 26 105
|
||||
rect 7 69 11 80
|
||||
rect 15 94 19 96
|
||||
rect 15 78 19 80
|
||||
rect 23 94 27 101
|
||||
rect 23 78 27 80
|
||||
rect 15 75 18 78
|
||||
rect 15 74 31 75
|
||||
rect 15 72 27 74
|
||||
rect 7 65 9 69
|
||||
rect 6 44 9 54
|
||||
rect 33 51 34 55
|
||||
rect 31 44 34 51
|
||||
rect 3 20 6 23
|
||||
rect 3 15 7 20
|
||||
rect -2 200 20 204
|
||||
rect 24 200 36 204
|
||||
rect 28 184 32 188
|
||||
rect 16 168 19 181
|
||||
rect 7 145 11 159
|
||||
rect 23 156 27 159
|
||||
rect 23 152 26 156
|
||||
rect 7 120 11 131
|
||||
rect 15 145 19 147
|
||||
rect 15 129 19 131
|
||||
rect 23 145 27 152
|
||||
rect 23 129 27 131
|
||||
rect 15 126 18 129
|
||||
rect 15 125 31 126
|
||||
rect 15 123 27 125
|
||||
rect 7 116 9 120
|
||||
rect 6 95 9 105
|
||||
rect 33 102 34 106
|
||||
rect 31 95 34 102
|
||||
rect 3 71 6 74
|
||||
rect 3 67 7 71
|
||||
rect 20 55 27 58
|
||||
rect 8 22 11 34
|
||||
rect 24 30 28 34
|
||||
rect 18 27 28 30
|
||||
rect 24 22 28 27
|
||||
rect 20 13 32 15
|
||||
rect 20 12 36 13
|
||||
rect 8 8 11 10
|
||||
rect 7 5 11 8
|
||||
<< m2contact >>
|
||||
rect 32 133 36 137
|
||||
rect 27 66 31 70
|
||||
rect 13 44 17 48
|
||||
rect 22 44 26 48
|
||||
rect 3 11 7 15
|
||||
rect 32 184 36 188
|
||||
rect 27 117 31 121
|
||||
rect 13 95 17 99
|
||||
rect 22 95 26 99
|
||||
rect 27 51 31 55
|
||||
rect 32 17 36 21
|
||||
rect 3 4 7 8
|
||||
<< metal2 >>
|
||||
rect 10 48 14 163
|
||||
rect 20 48 24 163
|
||||
rect 32 129 36 133
|
||||
rect 27 62 31 66
|
||||
rect 10 44 13 48
|
||||
rect 20 44 22 48
|
||||
rect 3 0 7 11
|
||||
rect 10 0 14 44
|
||||
rect 20 0 24 44
|
||||
rect 10 99 14 214
|
||||
rect 20 99 24 214
|
||||
rect 32 180 36 184
|
||||
rect 27 113 31 117
|
||||
rect 10 95 13 99
|
||||
rect 20 95 22 99
|
||||
rect 3 0 7 4
|
||||
rect 10 0 14 95
|
||||
rect 20 0 24 95
|
||||
rect 27 47 31 51
|
||||
rect 32 21 36 25
|
||||
<< bb >>
|
||||
rect 0 0 34 163
|
||||
rect 0 0 34 214
|
||||
<< labels >>
|
||||
flabel metal1 0 149 0 149 4 FreeSans 26 0 0 0 en
|
||||
rlabel metal2 34 131 34 131 1 gnd
|
||||
rlabel metal2 29 64 29 64 1 vdd
|
||||
rlabel metal2 12 161 12 161 5 bl
|
||||
rlabel metal2 22 161 22 161 5 br
|
||||
rlabel metal2 5 3 5 3 1 dout
|
||||
rlabel metal2 5 2 5 2 1 dout
|
||||
rlabel metal2 29 49 29 49 1 vdd
|
||||
rlabel metal2 22 212 22 212 5 br
|
||||
rlabel metal2 12 212 12 212 5 bl
|
||||
rlabel metal2 29 115 29 115 1 vdd
|
||||
rlabel metal2 34 182 34 182 1 gnd
|
||||
flabel metal1 0 200 0 200 4 FreeSans 26 0 0 0 en
|
||||
rlabel metal2 34 23 34 23 1 gnd
|
||||
<< properties >>
|
||||
string path 270.000 468.000 270.000 486.000 288.000 486.000 288.000 468.000 270.000 468.000
|
||||
<< end >>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@
|
|||
* SPICE3 file created from sense_amp.ext - technology: scmos
|
||||
|
||||
M1000 gnd en a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1001 a_56_432# a_48_304# dout gnd n w=1.8u l=0.4u
|
||||
M1002 a_48_304# dout a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1003 vdd a_48_304# dout vdd p w=3.6u l=0.4u
|
||||
M1004 a_48_304# dout vdd vdd p w=3.6u l=0.4u
|
||||
M1005 bl en dout vdd p w=4.8u l=0.4u
|
||||
M1001 a_56_432# a_48_304# dint gnd n w=1.8u l=0.4u
|
||||
M1002 a_48_304# dint a_56_432# gnd n w=1.8u l=0.4u
|
||||
M1003 vdd a_48_304# dint vdd p w=3.6u l=0.4u
|
||||
M1004 a_48_304# dint vdd vdd p w=3.6u l=0.4u
|
||||
M1005 bl en dint vdd p w=4.8u l=0.4u
|
||||
M1006 a_48_304# en br vdd p w=4.8u l=0.4u
|
||||
|
||||
M1007 dout_bar dint vdd vdd p w=1.6u l=0.4u
|
||||
M1008 gnd dint dout_bar gnd n w=0.8u l=0.4u
|
||||
M1009 dout dout_bar vdd vdd p w=4.8u l=0.4u
|
||||
M1010 gnd dout_bar dout gnd n w=2.4u l=0.4u
|
||||
.ENDS
|
||||
|
|
|
|||
Loading…
Reference in New Issue