Merge branch 'dev' into gridless_router

This commit is contained in:
Eren Dogan 2023-07-25 20:03:59 -07:00
commit 54ce1377c5
48 changed files with 9910 additions and 575 deletions

1
.gitignore vendored
View File

@ -23,3 +23,4 @@ sky130A/
sky130B/ sky130B/
skywater-pdk/ skywater-pdk/
sky130_fd_bd_sram/ sky130_fd_bd_sram/
docker/openram-ubuntu.log

View File

@ -1 +1 @@
1.2.22 1.2.26

View File

@ -66,8 +66,6 @@ class contact(hierarchy_design):
self.offset = vector(0, 0) self.offset = vector(0, 0)
self.implant_type = implant_type self.implant_type = implant_type
self.well_type = well_type self.well_type = well_type
# Module does not have pins, but has empty pin list.
self.pins = []
self.create_layout() self.create_layout()
def create_layout(self): def create_layout(self):

View File

@ -41,17 +41,17 @@ class design(hierarchy_design):
if prop and prop.hard_cell: if prop and prop.hard_cell:
# The pins get added from the spice file, so just check # The pins get added from the spice file, so just check
# that they matched here # that they matched here
debug.check(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, 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_indices(prop.port_indices)
self.add_pin_names(prop.port_map) 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, (width, height) = utils.get_libcell_size(self.cell_name,
GDS["unit"], GDS["unit"],
layer[prop.boundary_layer]) 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, self.cell_name,
GDS["unit"]) GDS["unit"])
@ -126,5 +126,3 @@ class design(hierarchy_design):
for inst in self.insts: for inst in self.insts:
total_module_power += inst.mod.analytical_power(corner, load) total_module_power += inst.mod.analytical_power(corner, load)
return total_module_power return total_module_power

View File

@ -161,8 +161,8 @@ class geometry:
class instance(geometry): class instance(geometry):
""" """
An instance of an instance/module with a specified location and An instance of a module with a specified location, rotation,
rotation spice pins, and spice nets
""" """
def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0):
"""Initializes an instance to represent a module""" """Initializes an instance to represent a module"""
@ -176,6 +176,18 @@ class instance(geometry):
self.rotate = rotate self.rotate = rotate
self.offset = vector(offset).snap_to_grid() self.offset = vector(offset).snap_to_grid()
self.mirror = mirror self.mirror = mirror
# 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: if OPTS.netlist_only:
self.width = 0 self.width = 0
self.height = 0 self.height = 0
@ -274,6 +286,34 @@ class instance(geometry):
new_pins.append(p) new_pins.append(p)
return new_pins 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): def calculate_transform(self, node):
#set up the rotation matrix #set up the rotation matrix
angle = math.radians(float(node.rotate)) angle = math.radians(float(node.rotate))

View File

@ -135,11 +135,11 @@ class hierarchy_design(spice, layout):
# Translate port names to external nets # Translate port names to external nets
if len(port_nets) != len(self.pins): if len(port_nets) != len(self.pins):
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
self.pins), list(self.pins)),
1) 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)) 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: if subinst in self.graph_inst_exclude:
continue continue
subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name 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 # Translate port names to external nets
if len(port_nets) != len(self.pins): if len(port_nets) != len(self.pins):
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets, debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,
self.pins), list(self.pins)),
1) 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)) 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_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name
subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst_ports = self.translate_nets(conns, port_dict, inst_name)
for si_port, conn in zip(subinst_ports, conns): 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 # The final pin names will depend on the spice hierarchy, so
# they are passed as an input. # 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() input_pins = self.get_inputs()
output_pins = self.get_outputs() output_pins = self.get_outputs()
inout_pins = self.get_inouts() inout_pins = self.get_inouts()
@ -197,7 +197,7 @@ class hierarchy_design(spice, layout):
def __str__(self): def __str__(self):
""" override print function output """ """ override print function output """
pins = ",".join(self.pins) pins = ",".join(list(self.pins))
insts = [" {}".format(x) for x in self.insts] insts = [" {}".format(x) for x in self.insts]
objs = [" {}".format(x) for x in self.objs] objs = [" {}".format(x) for x in self.objs]
s = "********** design {0} **********".format(self.cell_name) s = "********** design {0} **********".format(self.cell_name)
@ -208,7 +208,7 @@ class hierarchy_design(spice, layout):
def __repr__(self): def __repr__(self):
""" override print function output """ """ 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: for i in self.objs:
text+=str(i) + ",\n" text+=str(i) + ",\n"
for i in self.insts: for i in self.insts:

View File

@ -629,7 +629,7 @@ class layout():
""" """
Return a pin list of all pins 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)): def copy_layout_pin(self, instance, pin_name, new_name="", relative_offset=vector(0, 0)):
""" """
@ -1523,6 +1523,7 @@ class layout():
""" Return the pin shapes as blockages for non-top-level blocks. """ """ 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 # FIXME: We don't have a body contact in ptx, so just ignore it for now
import copy 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) pin_names = copy.deepcopy(self.pins)
if self.name.startswith("pmos") or self.name.startswith("nmos"): if self.name.startswith("pmos") or self.name.startswith("nmos"):
pin_names.remove("B") pin_names.remove("B")

View File

@ -13,6 +13,7 @@ from pprint import pformat
from openram import debug from openram import debug
from openram import tech from openram import tech
from openram import OPTS from openram import OPTS
from collections import OrderedDict
from .delay_data import delay_data from .delay_data import delay_data
from .wire_spice_model import wire_spice_model from .wire_spice_model import wire_spice_model
from .power_data import power_data from .power_data import power_data
@ -49,20 +50,18 @@ class spice():
if not os.path.exists(self.lvs_file): if not os.path.exists(self.lvs_file):
self.lvs_file = self.sp_file self.lvs_file = self.sp_file
self.valid_signal_types = ["INOUT", "INPUT", "OUTPUT", "BIAS", "POWER", "GROUND"]
# Holds subckts/mods for this module # Holds subckts/mods for this module
self.mods = set() self.mods = set()
# Holds the pins for this module (in order) # Holds the pins for this module (in order)
self.pins = [] # on Python3.7+ regular dictionaries guarantee order too, but we allow use of v3.5+
# The type map of each pin: INPUT, OUTPUT, INOUT, POWER, GROUND self.pins = OrderedDict()
# for each instance, this is the set of nets/nodes that map to the pins for this instance
self.pin_type = {}
# An (optional) list of indices to reorder the pins to match the spice. # An (optional) list of indices to reorder the pins to match the spice.
self.pin_indices = [] self.pin_indices = []
# THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the # THE CONNECTIONS MUST MATCH THE ORDER OF THE PINS (restriction imposed by the
# Spice format) # Spice format)
self.conns = [] # internal nets, which may or may not be connected to pins of the same name
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.) self.nets = {}
# If this is set, it will not output subckt or instances of this (for row/col caps etc.)
self.no_instances = False self.no_instances = False
# If we are doing a trimmed netlist, these are the instance that will be filtered # If we are doing a trimmed netlist, these are the instance that will be filtered
self.trim_insts = set() self.trim_insts = set()
@ -90,128 +89,114 @@ class spice():
def add_pin(self, name, pin_type="INOUT"): def add_pin(self, name, pin_type="INOUT"):
""" Adds a pin to the pins list. Default type is INOUT signal. """ """ Adds a pin to the pins list. Default type is INOUT signal. """
self.pins.append(name) debug.check(name not in self.pins, "cannot add duplicate spice pin {}".format(name))
self.pin_type[name]=pin_type self.pins[name] = pin_spice(name, pin_type, self)
debug.check(pin_type in self.valid_signal_types,
"Invalid signaltype for {0}: {1}".format(name,
pin_type))
def add_pin_list(self, pin_list, pin_type="INOUT"): def add_pin_list(self, pin_list, pin_type="INOUT"):
""" Adds a pin_list to the pins list """ """ 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. # 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: 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) self.add_pin(pin, pin_type)
elif len(pin_type)==len(pin_list): elif len(pin_type)==len(pin_list):
for (pin, ptype) in zip(pin_list, pin_type): for (pin, type) in zip(pin_list, pin_type):
debug.check(ptype in self.valid_signal_types, self.add_pin(pin, type)
"Invalid signaltype for {0}: {1}".format(pin,
ptype))
self.add_pin(pin, ptype)
else: 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): 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 self.pin_indices = index_list
def get_ordered_inputs(self, input_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: if not self.pin_indices:
return input_list return input_list
new_list = [input_list[x] for x in self.pin_indices] new_list = [input_list[x] for x in self.pin_indices]
return new_list return new_list
def add_pin_types(self, type_list): def update_pin_types(self, type_list):
""" """ Change pin types for all the cell's pins. """
Add 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\
# This only works if self.pins == bitcell.pin_names \n pin names={}\n port types={}".format(self.name, list(self.pins), type_list))
if len(type_list) != len(self.pins): for pin, type in zip(self.pins.values(), type_list):
debug.error("{} spice subcircuit number of port types does not match number of pins\ pin.set_type(type)
\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 get_pin_type(self, name): def get_pin_type(self, name):
""" Returns the type of the signal pin. """ """ Returns the type of the signal pin. """
pin_type = self.pin_type[name] pin = self.pins.get(name)
debug.check(pin_type in self.valid_signal_types, debug.check(pin is not None, "Spice pin {} not found".format(name))
"Invalid signaltype for {0}: {1}".format(name, pin_type)) return pin.type
return pin_type
def get_pin_dir(self, name): def get_pin_dir(self, name):
""" Returns the direction of the pin. (Supply/ground are INOUT). """ """ 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" return "INOUT"
else: else:
return self.pin_type[name] return pin_type
def get_inputs(self): 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 = [] input_list = []
for pin in self.pins: for pin in self.pins.values():
if self.pin_type[pin]=="INPUT": if pin.type == "INPUT":
input_list.append(pin) input_list.append(pin.name)
return input_list return input_list
def get_outputs(self): 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 = [] output_list = []
for pin in self.pins: for pin in self.pins.values():
if self.pin_type[pin]=="OUTPUT": if pin.type == "OUTPUT":
output_list.append(pin) output_list.append(pin.name)
return output_list 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=""): def copy_pins(self, other_module, suffix=""):
""" This will copy all of the pins from the other module and add an optional suffix.""" """ This will copy all of the pins from the other module and add an optional suffix."""
for pin in other_module.pins: for pin in other_module.pins.values():
self.add_pin(pin + suffix, other_module.get_pin_type(pin)) self.add_pin(pin.name + suffix, pin.type)
def get_inouts(self): def connect_inst(self, args):
""" 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):
""" """
Connects the pins of the last instance added 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) num_args = len(args)
# Order the arguments if the hard cell has a custom port order # Order the arguments if the hard cell has a custom port order
ordered_args = self.get_ordered_inputs(args) ordered_args = self.get_ordered_inputs(args)
if (check and num_pins != num_args): if (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 arg_pins = ordered_args
else: else:
arg_pins = ordered_args + [""] * (num_pins - num_args) 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)]) 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, debug.error("Connection mismatch:\nInst ({0}) -> Mod ({1})\n{2}".format(num_args,
@ -219,27 +204,17 @@ class spice():
modpins_string), modpins_string),
1) 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 def create_nets(self, names_list):
if check and (len(self.insts)!=len(self.conns)): nets = []
insts_string=pformat(self.insts) for name in names_list:
conns_string=pformat(self.conns) # 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
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name, net = self.nets.setdefault(name, net_spice(name, self))
len(self.insts), nets.append(net)
len(self.conns))) return nets
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 sp_read(self): def sp_read(self):
""" """
@ -258,7 +233,7 @@ class spice():
subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE) subckt = re.compile("^.subckt {}".format(self.cell_name), re.IGNORECASE)
subckt_line = list(filter(subckt.search, self.spice))[0] subckt_line = list(filter(subckt.search, self.spice))[0]
# parses line into ports and remove subckt # parses line into ports and remove subckt
self.pins = subckt_line.split(" ")[2:] self.add_pin_list(subckt_line.split(" ")[2:])
else: else:
debug.info(4, "no spfile {0}".format(self.sp_file)) debug.info(4, "no spfile {0}".format(self.sp_file))
self.spice = [] self.spice = []
@ -279,10 +254,10 @@ class spice():
subckt_line = list(filter(subckt.search, self.lvs))[0] subckt_line = list(filter(subckt.search, self.lvs))[0]
# parses line into ports and remove subckt # parses line into ports and remove subckt
lvs_pins = subckt_line.split(" ")[2:] 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, "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.lvs_file,
self.pins, list(self.pins),
self.sp_file)) self.sp_file))
def check_net_in_spice(self, net_name): def check_net_in_spice(self, net_name):
@ -327,78 +302,72 @@ class spice():
# If spice isn't defined, we dynamically generate one. # If spice isn't defined, we dynamically generate one.
# recursively write the modules # recursively write the modules
for i in self.mods: for mod in self.mods:
if self.contains(i, usedMODS): if self.contains(mod, usedMODS):
continue continue
usedMODS.append(i) usedMODS.append(mod)
i.sp_write_file(sp, usedMODS, lvs, trim) mod.sp_write_file(sp, usedMODS, lvs, trim)
if len(self.insts) == 0: if len(self.insts) == 0:
return return
if self.pins == []: if len(self.pins) == 0:
return return
# write out the first spice line (the subcircuit) # 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, sp.write("\n.SUBCKT {0}\n+ {1}\n".format(self.cell_name,
wrapped_pins)) 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 # Also write pins as comments
for pin in self.pins: for pin in self.pins.values():
sp.write("* {1:6}: {0} \n".format(pin, self.pin_type[pin])) sp.write("* {1:6}: {0} \n".format(pin.name, pin.type))
for line in self.comments: for line in self.comments:
sp.write("* {}\n".format(line)) sp.write("* {}\n".format(line))
# every instance must have a set of connections, even if it is empty. # every instance must be connected with the connect_inst function
if len(self.insts) != len(self.conns): # TODO: may run into empty pin lists edge case, not sure yet
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.cell_name, connected = True
len(self.insts), for inst in self.insts:
len(self.conns))) if inst.connected:
debug.error("Instances: \n" + str(self.insts)) continue
debug.error("-----") debug.error("Instance {} spice pins not connected".format(str(inst)))
debug.error("Connections: \n" + str(self.conns), 1) 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. # we don't need to output connections of empty instances.
# these are wires and paths # these are wires and paths
if self.conns[i] == []: if len(inst.spice_pins) == 0:
continue continue
# Instance with no devices in it needs no subckt/instance # Instance with no devices in it needs no subckt/instance
if self.insts[i].mod.no_instances: if inst.mod.no_instances:
continue continue
# If this is a trimmed netlist, skip it by adding comment char # 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("* ") sp.write("* ")
if lvs and hasattr(self.insts[i].mod, "lvs_device"): if lvs and hasattr(inst.mod, "lvs_device"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name, sp.write(inst.mod.lvs_device.format(inst.name,
" ".join(self.conns[i]))) " ".join(inst.get_connections())))
sp.write("\n") sp.write("\n")
elif hasattr(self.insts[i].mod, "spice_device"): elif hasattr(inst.mod, "spice_device"):
sp.write(self.insts[i].mod.spice_device.format(self.insts[i].name, sp.write(inst.mod.spice_device.format(inst.name,
" ".join(self.conns[i]))) " ".join(inst.get_connections())))
sp.write("\n") sp.write("\n")
else: else:
wrapped_connections = "\n+ ".join(tr.wrap(" ".join(self.conns[i]))) 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(self.insts[i].name, sp.write("X{0}\n*+ {1}\n*+ {2}\n".format(inst.name,
wrapped_connections, wrapped_connections,
self.insts[i].mod.cell_name)) 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)) sp.write(".ENDS {0}\n".format(self.cell_name))
@ -727,6 +696,12 @@ class spice():
aliases.append(net) aliases.append(net)
return aliases 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): 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). 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 return True
# Check connections of all other subinsts # Check connections of all other subinsts
mod_set = set() 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): 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): if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod):
return True return True
@ -756,3 +731,149 @@ class spice():
return self == mod and \ return self == mod and \
child_net.lower() == alias_net.lower() and \ child_net.lower() == alias_net.lower() and \
parent_net.lower() == alias_net.lower() 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

View File

@ -18,7 +18,7 @@ class lef:
""" """
SRAM LEF Class open GDS file, read pins information, obstruction SRAM LEF Class open GDS file, read pins information, obstruction
and write them to LEF file. 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): def __init__(self, layers):
# LEF db units per micron # LEF db units per micron

View File

@ -19,11 +19,12 @@ from .simulation import *
from .measurements import * from .measurements import *
from .model_check import * from .model_check import *
from .analytical_util import * from .analytical_util import *
from .fake_sram import *
debug.info(1, "Initializing characterizer...") debug.info(1, "Initializing characterizer...")
OPTS.spice_exe = "" OPTS.spice_exe = ""
if not OPTS.analytical_delay: if not OPTS.analytical_delay or OPTS.top_process in ["memfunc", "memchar"]:
if OPTS.spice_name: if OPTS.spice_name:
# Capitalize Xyce # Capitalize Xyce
if OPTS.spice_name == "xyce": if OPTS.spice_name == "xyce":

View File

@ -7,6 +7,7 @@
# #
import os import os
import re import re
from enum import Enum
from openram import debug from openram import debug
from openram import OPTS from openram import OPTS
@ -107,3 +108,33 @@ def check_dict_values_is_float(dict):
if type(value)!=float: if type(value)!=float:
return False return False
return True 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

View File

@ -13,10 +13,10 @@ from openram import OPTS
from .stimuli import * from .stimuli import *
from .trim_spice import * from .trim_spice import *
from .charutils import * from .charutils import *
from .sram_op import *
from .bit_polarity import *
from .simulation import simulation from .simulation import simulation
from .measurements import * from .measurements import *
from os import path
import re
class delay(simulation): 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) super().__init__(sram, spfile, corner)
self.targ_read_ports = [] self.targ_read_ports = []
@ -47,10 +47,17 @@ class delay(simulation):
self.num_wmasks = int(math.ceil(self.word_size / self.write_size)) self.num_wmasks = int(math.ceil(self.word_size / self.write_size))
else: else:
self.num_wmasks = 0 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_load_slew(0, 0)
self.set_corner(corner) self.set_corner(corner)
self.create_signal_names() self.create_signal_names()
self.add_graph_exclusions() self.add_graph_exclusions()
self.meas_id = 0
def create_measurement_objects(self): def create_measurement_objects(self):
""" Create the measurements used for read and write ports """ """ Create the measurements used for read and write ports """
@ -79,10 +86,12 @@ class delay(simulation):
self.clk_frmt = "clk{0}" # Unformatted clock name 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 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 = []
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_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.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_str = sram_op.READ_ZERO
self.delay_meas[-1].meta_add_delay = False
self.read_lib_meas+=self.delay_meas self.read_lib_meas+=self.delay_meas
self.slew_meas = [] self.slew_meas = []
@ -103,9 +112,10 @@ class delay(simulation):
self.read_lib_meas[-1].meta_str = "disabled_read0" 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. # This will later add a half-period to the spice time delay. Only for reading 0.
for obj in self.read_lib_meas: # FIXME: Removed this to check, see if it affects anything
if obj.meta_str is sram_op.READ_ZERO: #for obj in self.read_lib_meas:
obj.meta_add_delay = True # if obj.meta_str is sram_op.READ_ZERO:
# obj.meta_add_delay = True
read_measures = [] read_measures = []
read_measures.append(self.read_lib_meas) 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_bitline_measurement_objects())
read_measures.append(self.create_debug_measurement_objects()) read_measures.append(self.create_debug_measurement_objects())
read_measures.append(self.create_read_bit_measures()) 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 return read_measures
@ -158,6 +170,7 @@ class delay(simulation):
write_measures = [] write_measures = []
write_measures.append(self.write_lib_meas) write_measures.append(self.write_lib_meas)
write_measures.append(self.create_write_bit_measures()) write_measures.append(self.create_write_bit_measures())
return write_measures return write_measures
def create_debug_measurement_objects(self): 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_col = self.get_data_bit_column_number(probe_address, probe_data)
bit_row = self.get_address_row_number(probe_address) 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) if OPTS.top_process == "memchar":
storage_names = cell_inst.mod.get_storage_net_names() 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" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={0}").format(storage_names)) "supported for characterization. Storage nets={0}").format(storage_names))
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": 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): def create_sen_and_bitline_path_measures(self):
"""Create measurements for the s_en and bitline paths for individual delays per stage.""" """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 # # FIXME: There should be a default_read_port variable in this case, pathing is done with this
# but is never mentioned otherwise # # but is never mentioned otherwise
port = self.read_ports[0] port = self.read_ports[0]
sen_and_port = self.sen_name + str(port) sen_and_port = self.sen_name + str(port)
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the 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] bitline_path = bl_paths[0]
# Get the measures # Get the measures
self.sen_path_meas = self.create_delay_path_measures(sen_path) self.sen_path_meas = self.create_delay_path_measures(sen_path, "sen")
self.bl_path_meas = self.create_delay_path_measures(bitline_path) self.bl_path_meas = self.create_delay_path_measures(bitline_path, "bl")
all_meas = self.sen_path_meas + self.bl_path_meas all_meas = self.sen_path_meas + self.bl_path_meas
# Paths could have duplicate measurements, remove them before they go to the stim file # Paths could have duplicate measurements, remove them before they go to the stim file
@ -278,7 +295,7 @@ class delay(simulation):
return unique_measures 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.""" """Creates measurements for each net along given path."""
# Determine the directions (RISE/FALL) of signals # Determine the directions (RISE/FALL) of signals
@ -290,6 +307,8 @@ class delay(simulation):
cur_net, next_net = path[i], path[i + 1] cur_net, next_net = path[i], path[i + 1]
cur_dir, next_dir = path_dirs[i], path_dirs[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 = "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: 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)) 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 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") self.sf.write("\n* SRAM output loads\n")
for port in self.read_ports: 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)) 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): def write_delay_stimulus(self):
@ -385,15 +404,20 @@ class delay(simulation):
# creates and opens stimulus file for writing # creates and opens stimulus file for writing
self.delay_stim_sp = "delay_stim.sp" 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") 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": if OPTS.spice_name == "spectre":
self.sf.write("simulator lang=spice\n") 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.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
self.load, self.load,
self.slew)) self.slew))
self.stim = stimuli(self.sf, self.corner) self.stim = stimuli(self.sf, self.mf, self.corner)
# include files in stimulus file # include files in stimulus file
self.stim.write_include(self.trim_sp_file) self.stim.write_include(self.trim_sp_file)
@ -418,6 +442,7 @@ class delay(simulation):
t_rise=self.slew, t_rise=self.slew,
t_fall=self.slew) t_fall=self.slew)
self.sf.write(".include {0}".format(temp_meas))
# self.load_all_measure_nets() # self.load_all_measure_nets()
self.write_delay_measures() self.write_delay_measures()
# self.write_simulation_saves() # self.write_simulation_saves()
@ -426,6 +451,7 @@ class delay(simulation):
self.stim.write_control(self.cycle_times[-1] + self.period) self.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close() self.sf.close()
self.mf.close()
def write_power_stimulus(self, trim): def write_power_stimulus(self, trim):
""" Creates a stimulus file to measure leakage power only. """ Creates a stimulus file to measure leakage power only.
@ -435,10 +461,15 @@ class delay(simulation):
# creates and opens stimulus file for writing # creates and opens stimulus file for writing
self.power_stim_sp = "power_stim.sp" 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 = open(temp_stim, "w")
self.sf.write("* Power stimulus for period of {0}n\n\n".format(self.period)) 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 # include UNTRIMMED files in stimulus file
if trim: if trim:
@ -451,7 +482,7 @@ class delay(simulation):
# generate data and addr signals # generate data and addr signals
self.sf.write("\n* Generation of data and address signals\n") self.sf.write("\n* Generation of data and address signals\n")
for write_port in self.write_ports: 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), self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name, write_port, i),
v_val=0) v_val=0)
for port in self.all_ports: for port in self.all_ports:
@ -470,12 +501,14 @@ class delay(simulation):
for port in self.all_ports: for port in self.all_ports:
self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0) self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0)
self.sf.write(".include {}".format(temp_meas))
self.write_power_measures() self.write_power_measures()
# run until the end of the cycle time # run until the end of the cycle time
self.stim.write_control(2 * self.period) self.stim.write_control(2 * self.period)
self.sf.close() self.sf.close()
self.mf.close()
def get_measure_variants(self, port, measure_obj, measure_type=None): 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 # Output some comments to aid where cycles start and
# what is happening # what is happening
for comment in self.cycle_comments: for comment in self.cycle_comments:
self.sf.write("* {0}\n".format(comment)) self.mf.write("* {0}\n".format(comment))
self.sf.write("\n") self.sf.write("\n")
for read_port in self.targ_read_ports: 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) self.write_delay_measures_read_port(read_port)
for write_port in self.targ_write_ports: 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) self.write_delay_measures_write_port(write_port)
def load_pex_net(self, net: str): def load_pex_net(self, net: str):
@ -605,8 +638,8 @@ class delay(simulation):
return net return net
original_net = net original_net = net
net = net[len(prefix):] net = net[len(prefix):]
net = net.replace(".", "_").replace("[", "\[").replace("]", "\]") net = net.replace(".", "_").replace("[", r"\[").replace("]", r"\]")
for pattern in ["\sN_{}_[MXmx]\S+_[gsd]".format(net), net]: for pattern in [r"\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
try: try:
match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file]) match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file])
return prefix + match.decode().strip() return prefix + match.decode().strip()
@ -616,8 +649,8 @@ class delay(simulation):
def load_all_measure_nets(self): def load_all_measure_nets(self):
measurement_nets = set() measurement_nets = set()
for port, meas in zip(self.targ_read_ports * len(self.read_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.targ_write_ports * len(self.write_meas_lists),
self.read_meas_lists + self.write_meas_lists): self.read_meas_lists + self.write_meas_lists):
for measurement in meas: for measurement in meas:
visited = getattr(measurement, 'pex_visited', False) visited = getattr(measurement, 'pex_visited', False)
@ -684,6 +717,8 @@ class delay(simulation):
self.sf.write("\n* Measure statements for idle leakage power\n") self.sf.write("\n* Measure statements for idle leakage power\n")
# add measure statements for power # add measure statements for power
# TODO: Convert to measure statement insted of using stimuli
# measure = power_measure('leakage_power',
t_initial = self.period t_initial = self.period
t_final = 2 * self.period t_final = 2 * self.period
self.stim.gen_meas_power(meas_name="leakage_power", self.stim.gen_meas_power(meas_name="leakage_power",
@ -791,7 +826,7 @@ class delay(simulation):
for port in self.targ_write_ports: for port in self.targ_write_ports:
if not self.check_bit_measures(self.write_bit_meas, port): 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)) debug.info(2, "Checking write values for port {0}".format(port))
write_port_dict = {} write_port_dict = {}
@ -805,7 +840,7 @@ class delay(simulation):
for port in self.targ_read_ports: for port in self.targ_read_ports:
# First, check that the memory has the right values at the right times # First, check that the memory has the right values at the right times
if not self.check_bit_measures(self.read_bit_meas, port): 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)) debug.info(2, "Checking read delay values for port {0}".format(port))
# Check sen timing, then bitlines, then general measurements. # Check sen timing, then bitlines, then general measurements.
@ -828,7 +863,8 @@ class delay(simulation):
result[port].update(read_port_dict) 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) return (True, result)
@ -919,7 +955,7 @@ class delay(simulation):
def check_bitline_meas(self, v_discharged_bl, v_charged_bl): def check_bitline_meas(self, v_discharged_bl, v_charged_bl):
""" """
Checks the value of the discharging bitline. Confirms s_en timing errors. 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) # 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 # 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: if type(val) != float or val > self.period / 2:
debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val)) debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
value_dict[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 return value_dict
def run_power_simulation(self): 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) 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 # high-to-low delays start at neg. clk edge, so they need to be less than half_period
half_period = self.period / 2 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 \ 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 or delay_lh<0 or slew_hl<0 or slew_lh<0: 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, 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, delays_str,
slews_str)) slews_str))
@ -1003,6 +1039,13 @@ class delay(simulation):
delays_str, delays_str,
slews_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 return True
def find_min_period(self, feasible_delays): def find_min_period(self, feasible_delays):
@ -1108,32 +1151,143 @@ class delay(simulation):
Netlist reduced for simulation. Netlist reduced for simulation.
""" """
super().set_probe(probe_address, probe_data) super().set_probe(probe_address, probe_data)
self.prepare_netlist()
def prepare_netlist(self): def prepare_netlist(self):
""" Prepare a trimmed netlist and regular netlist. """ """ Prepare a trimmed netlist and regular netlist. """
# Set up to trim the netlist here if that is enabled # Set up to trim the netlist here if that is enabled
# TODO: Copy old netlist if memchar
if OPTS.trim_netlist: 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.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: else:
# The non-reduced netlist file when it is disabled # 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 # 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 # 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): def analysis_init(self, probe_address, probe_data):
"""Sets values which are dependent on the data address/bit being tested.""" """Sets values which are dependent on the data address/bit being tested."""
self.set_probe(probe_address, probe_data) self.set_probe(probe_address, probe_data)
self.create_graph() self.prepare_netlist()
self.set_internal_spice_names() if OPTS.top_process == "memchar":
self.create_measurement_names() self.set_spice_names()
self.create_measurement_objects() 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): def analyze(self, probe_address, probe_data, load_slews):
""" """
@ -1145,7 +1299,7 @@ class delay(simulation):
self.analysis_init(probe_address, probe_data) self.analysis_init(probe_address, probe_data)
loads = [] loads = []
slews = [] slews = []
for load,slew in load_slews: for load, slew in load_slews:
loads.append(load) loads.append(load)
slews.append(slew) slews.append(slew)
self.load=max(loads) 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. # 4) At the minimum period, measure the delay, slew and power for all slew/load pairs.
self.period = min_period self.period = min_period
char_port_data = self.simulate_loads_and_slews(load_slews, leakage_offset) 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") debug.warning("Path delay lists not correctly generated for characterizations of more than 1 load,slew")
# Get and save the path delays # 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 # Removed from characterization output temporarily
#char_sram_data["bl_path_measures"] = bl_delays # char_sram_data["bl_path_measures"] = bl_delays
#char_sram_data["sen_path_measures"] = sen_delays # char_sram_data["sen_path_measures"] = sen_delays
#char_sram_data["bl_path_names"] = bl_names # char_sram_data["bl_path_names"] = bl_names
#char_sram_data["sen_path_names"] = sen_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. # 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) self.alter_lh_char_data(char_port_data)
@ -1185,7 +1340,7 @@ class delay(simulation):
def alter_lh_char_data(self, char_port_data): 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.""" """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: for port in self.all_ports:
char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl'] char_port_data[port]['delay_lh'] = char_port_data[port]['delay_hl']
char_port_data[port]['slew_lh'] = char_port_data[port]['slew_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""" """Simulate all specified output loads and input slews pairs of all ports"""
measure_data = self.get_empty_measure_data_dict() 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. # 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_read_ports = self.read_ports
self.targ_write_ports = self.write_ports self.targ_write_ports = self.write_ports
@ -1255,8 +1409,8 @@ class delay(simulation):
inverse_address = self.calculate_inverse_address() inverse_address = self.calculate_inverse_address()
# For now, ignore data patterns and write ones or zeros # For now, ignore data patterns and write ones or zeros
data_ones = "1" * self.word_size data_ones = "1" * (self.word_size + self.num_spare_cols)
data_zeros = "0" * self.word_size data_zeros = "0" * (self.word_size + self.num_spare_cols)
wmask_ones = "1" * self.num_wmasks wmask_ones = "1" * self.num_wmasks
if self.t_current == 0: 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. # 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_read_port = self.get_available_port(get_read_port=True)
cur_write_port = self.get_available_port(get_read_port=False) 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") "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") "Characterizer requires at least 1 write port")
# Create test cycles for specified target ports. # Create test cycles for specified target ports.
@ -1380,7 +1534,7 @@ class delay(simulation):
""" Generates the PWL data inputs for a simulation timing test. """ """ Generates the PWL data inputs for a simulation timing test. """
for write_port in self.write_ports: 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) 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) 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) 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: 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) 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)

View File

@ -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)])

View File

@ -7,13 +7,17 @@
# #
import math import math
import random import random
import time
import collections import collections
from os import path
import shutil
from numpy import binary_repr from numpy import binary_repr
from openram import debug from openram import debug
from openram import OPTS from openram import OPTS
from .stimuli import * from .stimuli import *
from .charutils import * from .charutils import *
from .simulation import simulation from .simulation import simulation
from .measurements import voltage_at_measure
class functional(simulation): class functional(simulation):
@ -28,17 +32,24 @@ class functional(simulation):
# Seed the characterizer with a constant seed for unit tests # Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test: if OPTS.is_unit_test:
random.seed(12345) 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: if not spfile:
# self.sp_file is assigned in base class # self.sp_file is assigned in base class
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist) 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: if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
if period:
self.period = period
if not output_path: if not output_path:
self.output_path = OPTS.openram_temp self.output_path = OPTS.openram_temp
else: else:
@ -73,13 +84,20 @@ class functional(simulation):
self.set_spice_constants() self.set_spice_constants()
self.set_stimulus_variables() self.set_stimulus_variables()
# Override default period
if period:
self.period = period
# For the debug signal names # For the debug signal names
self.wordline_row = 0 self.wordline_row = 0
self.bitline_column = 0 self.bitline_column = 0
self.create_signal_names() self.create_signal_names()
self.add_graph_exclusions() #self.add_graph_exclusions()
self.create_graph() #self.create_graph()
self.set_internal_spice_names() #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() self.q_name, self.qbar_name = self.get_bit_name()
debug.info(2, "q:\t\t{0}".format(self.q_name)) debug.info(2, "q:\t\t{0}".format(self.q_name))
debug.info(2, "qbar:\t{0}".format(self.qbar_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) 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) self.add_noop_all_ports(comment)
# 1. Write all the write ports 2x to seed a bunch of locations. # 1. Write all the write ports 2x to seed a bunch of locations.
for i in range(3): for i in range(3):
for port in self.write_ports: for port in self.write_ports:
@ -267,7 +284,7 @@ class functional(simulation):
self.read_check.append([word, self.read_check.append([word,
"{0}{1}".format(self.dout_name, port), "{0}{1}".format(self.dout_name, port),
self.t_current + self.period, self.t_current + self.period,
int(self.t_current/self.period)]) int(self.t_current / self.period)])
def read_stim_results(self): def read_stim_results(self):
# Extract dout values from spice timing.lis # Extract dout values from spice timing.lis
@ -275,7 +292,8 @@ class functional(simulation):
sp_read_value = "" sp_read_value = ""
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
measure_name = "v{0}_{1}ck{2}".format(dout_port.lower(), bit, cycle) 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 # FIXME: Ignore the spare columns for now
if bit >= self.word_size: if bit >= self.word_size:
value = 0 value = 0
@ -294,10 +312,11 @@ class functional(simulation):
self.v_low, self.v_low,
self.v_high) self.v_high)
except ValueError: 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, bit,
value, value,
eo_period) eo_period,
measure_name)
return (0, error) return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, cycle]) self.read_results.append([sp_read_value, dout_port, eo_period, cycle])
@ -318,8 +337,8 @@ class functional(simulation):
cycle, cycle,
self.read_results[i][2], self.read_results[i][2],
check_name) check_name)
return(0, error) return (0, error)
return(1, "SUCCESS") return (1, "SUCCESS")
def gen_wmask(self): def gen_wmask(self):
wmask = "" wmask = ""
@ -347,7 +366,7 @@ class functional(simulation):
def gen_data(self): def gen_data(self):
""" Generates a random word to write. """ """ Generates a random word to write. """
# Don't use 0 or max value # 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) data_bits = binary_repr(random_value, self.word_size)
if self.num_spare_cols>0: if self.num_spare_cols>0:
random_value = random.randint(0, self.max_col_data) random_value = random.randint(0, self.max_col_data)
@ -380,13 +399,16 @@ class functional(simulation):
def write_functional_stimulus(self): def write_functional_stimulus(self):
""" Writes SPICE stimulus. """ """ Writes SPICE stimulus. """
self.stim_sp = "functional_stim.sp" 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 = open(temp_stim, "w")
self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period)) 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 # Write include statements
self.stim.write_include(self.sp_file) self.stim.write_include(self.temp_spice)
# Write Vdd/Gnd statements # Write Vdd/Gnd statements
self.sf.write("\n* Global Power Supplies\n") self.sf.write("\n* Global Power Supplies\n")
@ -470,6 +492,7 @@ class functional(simulation):
# Generate dout value measurements # Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n") self.sf.write("\n * Generation of dout measurements\n")
self.measures = {}
for (word, dout_port, eo_period, cycle) in self.read_check: for (word, dout_port, eo_period, cycle) in self.read_check:
t_initial = eo_period t_initial = eo_period
@ -477,28 +500,37 @@ class functional(simulation):
num_bits = self.word_size + self.num_spare_cols num_bits = self.word_size + self.num_spare_cols
for bit in range(num_bits): for bit in range(num_bits):
signal_name = "{0}_{1}".format(dout_port, bit) 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]) voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name, self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
measure_name, measure_name,
voltage_value, voltage_value,
eo_period)) eo_period))
self.stim.gen_meas_value(meas_name=measure_name, # TODO: Convert to measurement statement instead of stimuli
dout=signal_name, meas = voltage_at_measure(measure_name, signal_name)
t_initial=t_initial, self.measures[measure_name] = meas
t_final=t_final) 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.stim.write_control(self.cycle_times[-1] + self.period)
self.sf.close() self.sf.close()
self.mf.close()
# FIXME: Similar function to delay.py, refactor this # FIXME: Similar function to delay.py, refactor this
def get_bit_name(self): def get_bit_name(self):
""" Get a bit cell name """ """ Get a bit cell name """
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) # TODO: Find a way to get the cell_name and storage_names statically
storage_names = cell_inst.mod.get_storage_net_names() # (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" # storage_names = cell_inst.mod.get_storage_net_names()
"supported for characterization. Storage nets={0}").format(storage_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]) q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1]) qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])

View File

@ -82,6 +82,7 @@ class lib:
debug.info(1, "Slews: {0}".format(self.slews)) debug.info(1, "Slews: {0}".format(self.slews))
debug.info(1, "Loads: {0}".format(self.loads)) debug.info(1, "Loads: {0}".format(self.loads))
debug.info(1, "self.load_slews : {0}".format(self.load_slews)) debug.info(1, "self.load_slews : {0}".format(self.load_slews))
def create_corners(self): def create_corners(self):
""" Create corners for characterization. """ """ Create corners for characterization. """
# Get the corners from the options file # Get the corners from the options file
@ -801,7 +802,8 @@ class lib:
# information of checks # information of checks
# run it only the first time # 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 # write area
datasheet.write(str(self.sram.width * self.sram.height) + ',') datasheet.write(str(self.sram.width * self.sram.height) + ',')

View File

@ -15,15 +15,16 @@ from .charutils import *
class spice_measurement(ABC): class spice_measurement(ABC):
"""Base class for spice stimulus measurements.""" """Base class for spice stimulus measurements."""
def __init__(self, measure_name, measure_scale=None, has_port=True): 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.name = measure_name
self.measure_scale = measure_scale self.measure_scale = measure_scale
self.has_port = has_port #Needed for error checking self.has_port = has_port # Needed for error checking
#Some meta values used externally. variables are added here for consistency accross the objects # Some meta values used externally. variables are added here for consistency accross the objects
self.meta_str = None self.meta_str = None
self.meta_add_delay = False self.meta_add_delay = False
@abstractmethod @abstractmethod
def get_measure_function(self): def measure_function(self):
return None return None
@abstractmethod @abstractmethod
@ -31,28 +32,25 @@ class spice_measurement(ABC):
return None return None
def write_measure(self, stim_obj, input_tuple): 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_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): def retrieve_measure(self, port=None):
self.port_error_check(port) 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)) value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
else: else:
value = parse_spice_list("timing", "{0}".format(self.name.lower())) 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 return value
else: else:
return value*self.measure_scale return value * self.measure_scale
def port_error_check(self, port): def port_error_check(self, port):
if self.has_port and port == None: if self.has_port and port is None:
debug.error("Cannot retrieve measurement, port input was expected.",1) debug.error("Cannot retrieve measurement, port input was expected.", 1)
elif not self.has_port and port != None: elif not self.has_port and port is not None:
debug.error("Unexpected port input received during measure retrieval.",1) debug.error("Unexpected port input received during measure retrieval.", 1)
class delay_measure(spice_measurement): class delay_measure(spice_measurement):
@ -71,8 +69,18 @@ class delay_measure(spice_measurement):
spice_measurement.__init__(self, measure_name, measure_scale, has_port) 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) self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
def get_measure_function(self): def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
return stimuli.gen_meas_delay """ 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): 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""" """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 trig_val = self.trig_val_of_vdd * vdd_voltage
targ_val = self.targ_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 # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(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.trig_val_of_vdd = 0.9
self.targ_val_of_vdd = 0.1 self.targ_val_of_vdd = 0.1
else: 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.trig_name_no_port = signal_name
self.targ_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) spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(power_type) self.set_meas_constants(power_type)
def get_measure_function(self): def measure_function(self, stim_obj, meas_name, t_initial, t_final):
return stimuli.gen_meas_power """ 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): 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)""" """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): def get_measure_values(self, t_initial, t_final, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port) self.port_error_check(port)
if port != None: if port is not None:
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
else: else:
meas_name = self.name meas_name = self.name
@ -160,8 +178,15 @@ class voltage_when_measure(spice_measurement):
spice_measurement.__init__(self, measure_name, measure_scale, has_port) spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd) self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
def get_measure_function(self): def measure_function(self, stim_obj, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
return stimuli.gen_meas_find_voltage """ 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): 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)""" """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): def get_measure_values(self, trig_td, vdd_voltage, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port) 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 # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
trig_name = self.trig_name_no_port.format(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) spice_measurement.__init__(self, measure_name, measure_scale, has_port)
self.set_meas_constants(targ_name) self.set_meas_constants(targ_name)
def get_measure_function(self): def measure_function(self, stim_obj, meas_name, targ_name, time_at):
return stimuli.gen_meas_find_voltage_at_time """ 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): 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)""" """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): def get_measure_values(self, time_at, port=None):
"""Constructs inputs to stimulus measurement function. Variant values are inputs here.""" """Constructs inputs to stimulus measurement function. Variant values are inputs here."""
self.port_error_check(port) 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 # For dictionary indexing reasons, the name is formatted differently than the signals
meas_name = "{}{}".format(self.name, port) meas_name = "{}{}".format(self.name, port)
targ_name = self.targ_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port)

View File

@ -42,7 +42,13 @@ class setup_hold():
self.stim_sp = "sh_stim.sp" self.stim_sp = "sh_stim.sp"
temp_stim = OPTS.openram_temp + self.stim_sp temp_stim = OPTS.openram_temp + self.stim_sp
self.sf = open(temp_stim, "w") 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) self.write_header(correct_value)
@ -56,6 +62,7 @@ class setup_hold():
correct_value=correct_value) correct_value=correct_value)
self.write_clock() self.write_clock()
self.sf.write(".include {}\n".format(temp_meas))
self.write_measures(mode=mode, self.write_measures(mode=mode,
correct_value=correct_value) correct_value=correct_value)
@ -63,6 +70,7 @@ class setup_hold():
self.stim.write_control(4 * self.period) self.stim.write_control(4 * self.period)
self.sf.close() self.sf.close()
self.mf.close()
def write_header(self, correct_value): def write_header(self, correct_value):
""" Write the header file with all the models and the power supplies. """ """ Write the header file with all the models and the power supplies. """
@ -131,7 +139,7 @@ class setup_hold():
else: else:
dout_rise_or_fall = "FALL" 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" trig_name = "clk"
targ_name = "Q" targ_name = "Q"
trig_val = targ_val = 0.5 * self.vdd_voltage trig_val = targ_val = 0.5 * self.vdd_voltage
@ -168,29 +176,33 @@ class setup_hold():
target_time=feasible_bound, target_time=feasible_bound,
correct_value=correct_value) correct_value=correct_value)
self.stim.run_sim(self.stim_sp) 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... # We use a 1/2 speed clock for some reason...
setuphold_time = (feasible_bound - 2 * self.period) setuphold_time = (feasible_bound - 2 * self.period)
if mode == "SETUP": # SETUP is clk-din, not din-clk if mode == "SETUP": # SETUP is clk-din, not din-clk
passing_setuphold_time = -1 * setuphold_time passing_setuphold_time = -1 * setuphold_time
else: else:
passing_setuphold_time = setuphold_time passing_setuphold_time = setuphold_time
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}"
correct_value, .format(mode,
ideal_clk_to_q, correct_value,
setuphold_time)) ideal_clk_to_q,
setuphold_time))
if type(ideal_clk_to_q)!=float: if type(ideal_clk_to_q)!=float:
debug.error("Initial hold time fails for data value feasible " debug.error("Initial hold time fails for data value feasible "
"bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound, "bound {0} Clk-to-Q {1} Setup/Hold {2}"
ideal_clk_to_q, .format(feasible_bound,
setuphold_time), ideal_clk_to_q,
setuphold_time),
2) 2)
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode, debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} "
setuphold_time, .format(mode,
feasible_bound, setuphold_time,
2 * self.period)) feasible_bound,
2 * self.period))
while True: while True:
target_time = (feasible_bound + infeasible_bound) / 2 target_time = (feasible_bound + infeasible_bound) / 2
@ -198,11 +210,12 @@ class setup_hold():
target_time=target_time, target_time=target_time,
correct_value=correct_value) correct_value=correct_value)
debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}".format(mode, debug.info(2, "{0} value: {1} Target time: {2} Infeasible: {3} Feasible: {4}"
correct_value, .format(mode,
target_time, correct_value,
infeasible_bound, target_time,
feasible_bound)) infeasible_bound,
feasible_bound))
self.stim.run_sim(self.stim_sp) self.stim.run_sim(self.stim_sp)
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay")) clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))

View File

@ -235,7 +235,7 @@ class simulation():
self.add_wmask(wmask, port) self.add_wmask(wmask, port)
self.add_spare_wen("1" * self.num_spare_cols, 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: for unselected_port in self.all_ports:
if unselected_port != port: if unselected_port != port:
self.add_noop_one_port(unselected_port) self.add_noop_one_port(unselected_port)
@ -267,7 +267,7 @@ class simulation():
self.add_wmask("0" * self.num_wmasks, port) self.add_wmask("0" * self.num_wmasks, port)
self.add_spare_wen("0" * self.num_spare_cols, 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: for unselected_port in self.all_ports:
if unselected_port != port: if unselected_port != port:
self.add_noop_one_port(unselected_port) self.add_noop_one_port(unselected_port)
@ -356,14 +356,14 @@ class simulation():
self.add_noop_one_port(port) 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: for unselected_port in self.all_ports:
if unselected_port != port: if unselected_port != port:
self.add_noop_one_port(unselected_port) self.add_noop_one_port(unselected_port)
def append_cycle_comment(self, port, comment): def append_cycle_comment(self, port, comment):
"""Add comment to list to be printed in stimulus file""" """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 = "{0:.2f} ns:".format(self.t_current)
time_spacing = len(time) + 6 time_spacing = len(time) + 6
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times), 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)] split_word2 = [x + '_' * (n != 0 and n % 4 == 0) for n, x in enumerate(split_word)]
# Join the word unreversed back together # Join the word unreversed back together
new_word = ''.join(reversed(split_word2)) new_word = ''.join(reversed(split_word2))
return(new_word) return (new_word)
# Split extra cols # Split extra cols
if self.num_spare_cols > 0: 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, comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word,
addr, addr,
port, port,
int(t_current/self.period), int(t_current / self.period),
t_current, t_current,
t_current+self.period) t_current + self.period)
elif op == "partial_write": 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)" 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, comment = str.format(word,
@ -454,7 +454,7 @@ class simulation():
for i in range(abits): for i in range(abits):
pin_names.append("{0}{1}_{2}".format(addr_name, port, i)) 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): for port in range(total_ports):
pin_names.append("CSB{0}".format(port)) pin_names.append("CSB{0}".format(port))
for port in range(total_ports): 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 # other initializations can only be done during analysis when a bit has been selected
# for testing. # for testing.
self.sram.bank.graph_exclude_precharge() if OPTS.top_process != 'memchar':
self.sram.graph_exclude_addr_dff() self.sram.bank.graph_exclude_precharge()
self.sram.graph_exclude_data_dff() self.sram.graph_exclude_addr_dff()
self.sram.graph_exclude_ctrl_dffs() self.sram.graph_exclude_data_dff()
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits() self.sram.graph_exclude_ctrl_dffs()
self.sram.bank.bitcell_array.graph_exclude_replica_col_bits()
def set_internal_spice_names(self): def set_internal_spice_names(self):
""" """
@ -515,9 +516,9 @@ class simulation():
self.sen_name = sen_with_port self.sen_name = sen_with_port
debug.warning("Error occurred while determining SEN name. Can cause faults in simulation.") 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) 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 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 self.bl_name = bl_name_port
@ -535,7 +536,7 @@ class simulation():
'{}{}_{}'.format(self.dout_name, port, self.probe_data)) '{}{}_{}'.format(self.dout_name, port, self.probe_data))
self.sen_name = self.get_sen_name(self.graph.all_paths) 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.bl_name = "bl{0}_{1}".format(port, OPTS.word_size - 1)
self.br_name = "br{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. 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.clear_exclude_bits() # Removes previous bit exclusions
self.sram.graph_exclude_bits(self.wordline_row, self.bitline_column) 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: if self.words_per_row > 1:
self.sram.graph_clear_column_mux(port) self.sram.graph_clear_column_mux(port)
self.sram.graph_exclude_column_mux(self.bitline_column, port) self.sram.graph_exclude_column_mux(self.bitline_column, port)

View File

@ -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

View File

@ -22,7 +22,7 @@ from openram import OPTS
class stimuli(): class stimuli():
""" Class for providing stimuli functions """ """ Class for providing stimuli functions """
def __init__(self, stim_file, corner): def __init__(self, stim_file, meas_file, corner):
self.vdd_name = "vdd" self.vdd_name = "vdd"
self.gnd_name = "gnd" self.gnd_name = "gnd"
self.pmos_name = tech.spice["pmos"] self.pmos_name = tech.spice["pmos"]
@ -31,6 +31,7 @@ class stimuli():
self.tx_length = tech.drc["minlength_channel"] self.tx_length = tech.drc["minlength_channel"]
self.sf = stim_file self.sf = stim_file
self.mf = meas_file
(self.process, self.voltage, self.temperature) = corner (self.process, self.voltage, self.temperature) = corner
found = False found = False
@ -136,7 +137,7 @@ class stimuli():
offset, offset,
t_rise, t_rise,
t_fall, 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)) period))
def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup): 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): 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 """ """ 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" 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_name,
trig_val, trig_val,
trig_dir, 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): 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 """ """ 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" 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, targ_name,
trig_name, trig_name,
trig_val, trig_val,
@ -204,7 +205,7 @@ class stimuli():
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
""" Creates the .meas statement for voltage at time""" """ Creates the .meas statement for voltage at time"""
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" 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, targ_name,
time_at)) time_at))
@ -215,15 +216,15 @@ class stimuli():
power_exp = "power" power_exp = "power"
else: else:
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')" 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, power_exp,
t_initial, t_initial,
t_final)) t_final))
def gen_meas_value(self, meas_name, dout, 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} 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) # 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) self.mf.write(measure_string)
def write_control(self, end_time, runlvl=4): def write_control(self, end_time, runlvl=4):
""" Write the control cards to run and end the simulation """ """ 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 DEVICE TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n") self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n")
self.sf.write(".OPTIONS LINSOL type=klu\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> # 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)) self.sf.write(".TRAN {0}p {1}n 0n {0}p\n".format(timestep, end_time))
elif OPTS.spice_name: elif OPTS.spice_name:
@ -411,12 +412,12 @@ class stimuli():
from openram import CONDA_HOME from openram import CONDA_HOME
cmd = "source {0}/bin/activate && {1} && conda deactivate".format(CONDA_HOME, cmd) cmd = "source {0}/bin/activate && {1} && conda deactivate".format(CONDA_HOME, cmd)
debug.info(2, 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_stdout.close()
spice_stderr.close() spice_stderr.close()
if (retcode > valid_retcode): if (proc.returncode > valid_retcode):
debug.error("Spice simulation error: " + cmd, -1) debug.error("Spice simulation error: " + cmd, -1)
else: else:
end_time = datetime.datetime.now() end_time = datetime.datetime.now()

View File

@ -85,14 +85,15 @@ class trim_spice():
wl_regex = r"wl\d*_{}".format(wl_address) wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_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]) self.remove_insts("bitcell_array",[wl_regex,bl_regex])
# 2. Keep sense amps basd on BL # 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_no_port_regex])
#self.remove_insts("sense_amp_array",[bl_regex])
# 3. Keep column muxes basd on BL # 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 # 4. Keep write driver based on DATA
data_regex = r"data_{}".format(data_bit) data_regex = r"data_{}".format(data_bit)

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# See LICENSE for licensing information. # See LICENSE for licensing information.
# #
# Copyright (c) 2016-2023 Regents of the University of California and The Board # 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 import sys
from globals import * from globals import *
from importlib import reload
(OPTS, args) = parse_args() (OPTS, args) = parse_args()
@ -41,36 +42,29 @@ slew = float(args[3])
import debug import debug
init_openram(config_file=config_file, is_unit_test=False) 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 OPTS.check_lvsdrc = False
# Put the temp output in the output path since it is what we want to generate! # Put the temp output in the output path since it is what we want to generate!
old_openram_temp = OPTS.openram_temp old_openram_temp = OPTS.openram_temp
OPTS.openram_temp = OPTS.output_path OPTS.openram_temp = OPTS.output_path
from sram import sram
s = sram(name=OPTS.output_name, sram_config=c)
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 characterizer import delay from characterizer import delay
import tech import tech
# Set up the delay and set to the nominal corner # 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 # Set the period
d.period = period d.period = period
# Set the load of outputs and slew of inputs # 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 OPTS.openram_temp = old_openram_temp
# Delete temp files, remove the dir, etc. # Delete temp files, remove the dir, etc.
end_openram() end_openram()

View File

@ -42,7 +42,7 @@ class column_decoder(design):
def create_instances(self): def create_instances(self):
self.column_decoder_inst = self.add_inst(name="column_decoder", self.column_decoder_inst = self.add_inst(name="column_decoder",
mod=self.column_decoder) mod=self.column_decoder)
self.connect_inst(self.pins) self.connect_inst(list(self.pins))
def create_layout(self): def create_layout(self):
self.column_decoder_inst.place(vector(0,0)) self.column_decoder_inst.place(vector(0,0))

View File

@ -1212,7 +1212,7 @@ class pbitcell(bitcell_base):
if self.dummy_bitcell: if self.dummy_bitcell:
return 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 # 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) rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names)

View File

@ -104,6 +104,8 @@ class options(optparse.Values):
sim_data_path = None sim_data_path = None
# A list of load/slew tuples # A list of load/slew tuples
use_specified_load_slew = None use_specified_load_slew = None
# Spice simulation raw file
spice_raw_file = None
################### ###################
# Run-time vs accuracy options. # Run-time vs accuracy options.
@ -126,14 +128,18 @@ class options(optparse.Values):
# Output config with all options # Output config with all options
output_extended_config = False output_extended_config = False
# Output temporary file used to format HTML page # Output temporary file used to format HTML page
output_datasheet_info = False output_datasheet_info = True
# Determines which analytical model to use. # Determines which analytical model to use.
# Available Models: elmore, linear_regression # Available Models: elmore, linear_regression
model_name = "elmore" model_name = "elmore"
# Write graph to a file
write_graph = False
################### ###################
# Tool options # Tool options
################### ###################
# Top process that was ran (openram, memchar, memfunc)
top_process = None
# Use conda to install the default tools # Use conda to install the default tools
# (existing tools will be used if disabled) # (existing tools will be used if disabled)
use_conda = True use_conda = True
@ -174,6 +180,15 @@ class options(optparse.Values):
# Purge the temp directory after a successful # Purge the temp directory after a successful
# run (doesn't purge on errors, anyhow) # 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 # Route the input/output pins to the perimeter
perimeter_pins = True perimeter_pins = True

View File

@ -60,6 +60,14 @@ class sram():
if not OPTS.is_unit_test: if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time) 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): def sp_write(self, name, lvs=False, trim=False):
self.s.sp_write(name, lvs, trim) self.s.sp_write(name, lvs, trim)
@ -95,18 +103,40 @@ class sram():
# is loaded and the right tools are selected # is loaded and the right tools are selected
from openram import verify from openram import verify
from openram.characterizer import functional from openram.characterizer import functional
from openram.characterizer import delay
# Save the spice file # Save the spice file
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
spname = OPTS.output_path + self.s.name + ".sp" spname = OPTS.output_path + self.s.name + ".sp"
debug.print_raw("SP: Writing to {0}".format(spname)) debug.print_raw("SP: Writing to {0}".format(spname))
self.sp_write(spname) self.sp_write(spname)
# Save a functional simulation file with default period
functional(self.s, functional(self.s,
os.path.basename(spname), spname,
cycles=200, cycles=200,
output_path=OPTS.output_path) output_path=OPTS.output_path)
print_time("Spice writing", datetime.datetime.now(), start_time) 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: if not OPTS.netlist_only:
# Write the layout # Write the layout
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
@ -154,8 +184,6 @@ class sram():
# Use generated spice file for characterization # Use generated spice file for characterization
sp_file = spname sp_file = spname
# Save a functional simulation file
# Characterize the design # Characterize the design
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
from openram.characterizer import lib from openram.characterizer import lib

View File

@ -14,7 +14,8 @@ from openram import OPTS
class sram_config: class sram_config:
""" This is a structure that is used to hold the SRAM configuration options. """ """ 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.word_size = word_size
self.num_words = num_words self.num_words = num_words
# Don't add a write mask if it is the same size as the data word # 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: except ImportError:
self.array_col_multiple = 1 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 # This will get over-written when we determine the organization
self.words_per_row = words_per_row self.words_per_row = words_per_row
@ -175,3 +181,43 @@ class sram_config:
return int(words_per_row * tentative_num_rows / 16) return int(words_per_row * tentative_num_rows / 16)
return words_per_row 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

View File

@ -25,6 +25,8 @@ class model_delay_test(openram_test):
openram.init_openram(config_file, is_unit_test=True) openram.init_openram(config_file, is_unit_test=True)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True 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 # This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload from importlib import reload

View File

@ -61,35 +61,36 @@ class timing_sram_test(openram_test):
data.update(port_data[0]) data.update(port_data[0])
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'slew_lh': [0.2592187], golden_data = {'delay_hl': [0.263898],
'slew_hl': [0.2592187], 'delay_lh': [0.263898],
'delay_lh': [0.2465583], 'disabled_read0_power': [0.06625703],
'disabled_write0_power': [0.1924678], 'disabled_read1_power': [0.07531121],
'disabled_read0_power': [0.152483], 'disabled_write0_power': [0.09350641999999999],
'write0_power': [0.3409064], 'disabled_write1_power': [0.09988823000000001],
'disabled_read1_power': [0.1737818], 'leakage_power': 0.01192385,
'read0_power': [0.3096708], 'min_period': 2.031,
'read1_power': [0.3107916], 'read0_power': [0.14745439999999999],
'delay_hl': [0.2465583], 'read1_power': [0.1470831],
'write1_power': [0.26915849999999997], 'slew_hl': [0.027165],
'leakage_power': 0.002044307, 'slew_lh': [0.027165],
'min_period': 0.898, 'write0_power': [0.1630546],
'disabled_write1_power': [0.201411]} 'write1_power': [0.1319501]}
elif OPTS.tech_name == "scn4m_subm": elif OPTS.tech_name == "scn4m_subm":
golden_data = {'read1_power': [12.11658], golden_data = {'delay_hl': [1.8259260000000002],
'write1_power': [10.52653], 'delay_lh': [1.8259260000000002],
'read0_power': [11.956710000000001], 'disabled_read0_power': [6.722809],
'disabled_write0_power': [7.673665], 'disabled_read1_power': [8.104113],
'disabled_write1_power': [7.981922000000001], 'disabled_write0_power': [8.900671],
'slew_lh': [1.868836], 'disabled_write1_power': [9.188668],
'slew_hl': [1.868836], 'leakage_power': 0.6977637,
'delay_hl': [1.8598510000000001], 'min_period': 6.562,
'delay_lh': [1.8598510000000001], 'read0_power': [15.45948],
'leakage_power': 0.005457728, 'read1_power': [15.48587],
'disabled_read0_power': [5.904712], 'slew_hl': [0.1936536],
'min_period': 6.875, 'slew_lh': [0.1936536],
'disabled_read1_power': [7.132159], 'write0_power': [17.03442],
'write0_power': [13.406400000000001]} 'write1_power': [13.05424]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail

View File

@ -69,35 +69,36 @@ class timing_sram_test(openram_test):
data.update(port_data[0]) data.update(port_data[0])
if OPTS.tech_name == "freepdk45": if OPTS.tech_name == "freepdk45":
golden_data = {'delay_hl': [0.24671600000000002], golden_data = {'delay_hl': [0.2764415],
'delay_lh': [0.24671600000000002], 'delay_lh': [0.2764415],
'disabled_read0_power': [0.1749204], 'disabled_read0_power': [0.18364834],
'disabled_read1_power': [0.1873704], 'disabled_read1_power': [0.20878333999999998],
'disabled_write0_power': [0.204619], 'disabled_write0_power': [0.24064433999999998],
'disabled_write1_power': [0.2262653], 'disabled_write1_power': [0.27207664],
'leakage_power': 0.0021375310000000002, 'leakage_power': 0.0443369,
'min_period': 0.977, 'min_period': 0.938,
'read0_power': [0.3856875], 'read0_power': [0.37790994],
'read1_power': [0.38856060000000003], 'read1_power': [0.37646214],
'slew_hl': [0.2842019], 'slew_hl': [0.0266144],
'slew_lh': [0.2842019], 'slew_lh': [0.0266144],
'write0_power': [0.45274410000000004], 'write0_power': [0.44694044],
'write1_power': [0.38727789999999995]} 'write1_power': [0.36824544000000003]}
elif OPTS.tech_name == "scn4m_subm": elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.882508], golden_data = {'delay_hl': [1.905376],
'delay_lh': [1.882508], 'delay_lh': [1.905376],
'disabled_read0_power': [7.487227], 'disabled_read0_power': [7.673850999999999],
'disabled_read1_power': [8.749013], 'disabled_read1_power': [10.051073],
'disabled_write0_power': [9.268901], 'disabled_write0_power': [10.638803],
'disabled_write1_power': [9.962973], 'disabled_write1_power': [10.385253],
'leakage_power': 0.0046686359999999994, 'leakage_power': 2.704021,
'min_period': 7.188, 'min_period': 6.875,
'read0_power': [16.64011], 'read0_power': [17.583853],
'read1_power': [17.20825], 'read1_power': [17.689162999999997],
'slew_hl': [2.039655], 'slew_hl': [0.19331199999999998],
'slew_lh': [2.039655], 'slew_lh': [0.19331199999999998],
'write0_power': [19.31883], 'write0_power': [20.607043],
'write1_power': [15.297369999999999]} 'write1_power': [16.107403]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail

View File

@ -84,20 +84,20 @@ class timing_sram_test(openram_test):
'write0_power': [0.429895901], 'write0_power': [0.429895901],
'write1_power': [0.383337501]} 'write1_power': [0.383337501]}
elif OPTS.tech_name == "scn4m_subm": elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.884186], golden_data = {'delay_hl': [1.78586],
'delay_lh': [1.884186], 'delay_lh': [1.78586],
'disabled_read0_power': [20.86336], 'disabled_read0_power': [7.8296693788],
'disabled_read1_power': [22.10636], 'disabled_read1_power': [9.1464723788],
'disabled_write0_power': [22.62321], 'disabled_write0_power': [9.6889073788],
'disabled_write1_power': [23.316010000000002], 'disabled_write1_power': [10.4123023788],
'leakage_power': 13.351170000000002, 'leakage_power': 0.0002442851,
'min_period': 7.188, 'min_period': 6.875,
'read0_power': [29.90159], 'read0_power': [16.995952378800002],
'read1_power': [30.47858], 'read1_power': [17.5845523788],
'slew_hl': [2.042723], 'slew_hl': [2.039202],
'slew_lh': [2.042723], 'slew_lh': [2.039202],
'write0_power': [32.13199], 'write0_power': [19.785462378800002],
'write1_power': [28.46703]} 'write1_power': [15.742192378799999]}
else: else:
self.assertTrue(False) # other techs fail self.assertTrue(False) # other techs fail
# Check if no too many or too few results # Check if no too many or too few results

View File

@ -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())

View File

@ -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())

View File

@ -180,6 +180,8 @@ BROKEN_STAMPS = \
sky130/30_openram_back_end_test.ok \ sky130/30_openram_back_end_test.ok \
sky130/30_openram_front_end_library_test.ok \ sky130/30_openram_front_end_library_test.ok \
sky130/30_openram_front_end_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 /, ,$*)) gettech = $(word 1,$(subst /, ,$*))
getfile = $(word 2,$(subst /, ,$*)) getfile = $(word 2,$(subst /, ,$*))

View File

@ -5,10 +5,17 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # 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): output_name = "sram"
NONINVERTING = 0
INVERTING = 1
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

View File

@ -46,7 +46,8 @@ Measures the timing/power through SPICE simulation:
* Testing Support Modules * Testing Support Modules
* Other modules are derivatives of the simulation module used in the unit tests * 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 Options
* Characterization by Configuration File * Characterization by Configuration File

79
sram_char.py Executable file
View File

@ -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()

View File

@ -32,6 +32,8 @@ if len(args) != 1:
print(openram.USAGE) print(openram.USAGE)
sys.exit(2) sys.exit(2)
# Set top process to openram
OPTS.top_process = 'openram'
# These depend on arguments, so don't load them until now. # These depend on arguments, so don't load them until now.
from openram import debug from openram import debug
@ -76,4 +78,3 @@ s.save()
# Delete temp files etc. # Delete temp files etc.
openram.end_openram() openram.end_openram()
openram.print_time("End", datetime.datetime.now(), start_time) openram.print_time("End", datetime.datetime.now(), start_time)

78
sram_func.py Executable file
View File

@ -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()

View File

@ -1,11 +1,16 @@
.SUBCKT sense_amp bl br dout en vdd gnd .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_1 dint 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_3 net_1 dint 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_2 dint 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_8 net_1 dint 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_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_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_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 .ENDS sense_amp

View File

@ -1,136 +1,183 @@
magic magic
tech scmos tech scmos
timestamp 1536089670 timestamp 1681333912
<< nwell >> << nwell >>
rect 0 0 40 102 rect 0 28 40 153
<< pwell >> << pwell >>
rect 0 102 40 163 rect 0 153 40 214
rect 0 0 40 28
<< ntransistor >> << ntransistor >>
rect 21 130 23 139 rect 21 181 23 190
rect 12 108 14 117 rect 12 159 14 168
rect 20 108 22 117 rect 20 159 22 168
rect 13 10 15 22
rect 21 18 23 22
<< ptransistor >> << ptransistor >>
rect 12 78 14 96 rect 12 129 14 147
rect 20 78 22 96 rect 20 129 22 147
rect 11 20 13 44 rect 11 71 13 95
rect 27 20 29 44 rect 27 71 29 95
rect 13 34 15 58
rect 21 34 23 42
<< ndiffusion >> << ndiffusion >>
rect 20 130 21 139 rect 20 181 21 190
rect 23 130 24 139 rect 23 181 24 190
rect 11 108 12 117 rect 11 159 12 168
rect 14 108 15 117 rect 14 159 15 168
rect 19 108 20 117 rect 19 159 20 168
rect 22 108 23 117 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 >> << pdiffusion >>
rect 7 94 12 96 rect 7 145 12 147
rect 11 80 12 94 rect 11 131 12 145
rect 7 78 12 80 rect 7 129 12 131
rect 14 94 20 96 rect 14 145 20 147
rect 14 80 15 94 rect 14 131 15 145
rect 19 80 20 94 rect 19 131 20 145
rect 14 78 20 80 rect 14 129 20 131
rect 22 94 27 96 rect 22 145 27 147
rect 22 80 23 94 rect 22 131 23 145
rect 22 78 27 80 rect 22 129 27 131
rect 10 20 11 44 rect 10 71 11 95
rect 13 20 14 44 rect 13 71 14 95
rect 26 20 27 44 rect 26 71 27 95
rect 29 20 30 44 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 >> << ndcontact >>
rect 16 130 20 139 rect 16 181 20 190
rect 24 130 28 139 rect 24 181 28 190
rect 7 108 11 117 rect 7 159 11 168
rect 15 108 19 117 rect 15 159 19 168
rect 23 108 27 117 rect 23 159 27 168
rect 8 10 12 22
rect 16 10 20 22
rect 24 18 28 22
<< pdcontact >> << pdcontact >>
rect 7 80 11 94 rect 7 131 11 145
rect 15 80 19 94 rect 15 131 19 145
rect 23 80 27 94 rect 23 131 27 145
rect 6 20 10 44 rect 6 71 10 95
rect 14 20 18 44 rect 14 71 18 95
rect 22 20 26 44 rect 22 71 26 95
rect 30 20 34 44 rect 30 71 34 95
rect 8 34 12 58
rect 16 34 20 58
rect 24 34 28 42
<< psubstratepcontact >> << psubstratepcontact >>
rect 32 137 36 141 rect 32 188 36 192
rect 32 13 36 17
<< nsubstratencontact >> << nsubstratencontact >>
rect 27 70 31 74 rect 27 121 31 125
rect 27 55 31 59
<< polysilicon >> << polysilicon >>
rect 21 139 23 149 rect 21 190 23 200
rect 21 129 23 130 rect 21 180 23 181
rect 3 127 23 129 rect 3 178 23 180
rect 3 47 5 127 rect 3 98 5 178
rect 12 122 34 124 rect 12 173 34 175
rect 12 117 14 122 rect 12 168 14 173
rect 20 117 22 119 rect 20 168 22 170
rect 12 96 14 108 rect 12 147 14 159
rect 20 96 22 108 rect 20 147 22 159
rect 32 105 34 122 rect 32 156 34 173
rect 30 101 34 105 rect 30 152 34 156
rect 12 76 14 78 rect 12 127 14 129
rect 20 69 22 78 rect 20 120 22 129
rect 13 67 22 69 rect 13 118 22 120
rect 9 55 11 65 rect 9 106 11 116
rect 32 55 34 101 rect 32 106 34 152
rect 33 51 34 55 rect 33 102 34 106
rect 3 45 13 47 rect 3 96 13 98
rect 11 44 13 45 rect 11 95 13 96
rect 27 44 29 46 rect 27 95 29 97
rect 11 19 13 20 rect 11 70 13 71
rect 27 19 29 20 rect 27 70 29 71
rect 11 17 29 19 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 >> << polycontact >>
rect 20 149 24 153 rect 20 200 24 204
rect 26 101 30 105 rect 26 152 30 156
rect 9 65 13 69 rect 9 116 13 120
rect 9 51 13 55 rect 9 102 13 106
rect 29 51 33 55 rect 29 102 33 106
rect 3 63 7 67
rect 14 27 18 31
<< metal1 >> << metal1 >>
rect -2 149 20 153 rect -2 200 20 204
rect 24 149 36 153 rect 24 200 36 204
rect 28 133 32 137 rect 28 184 32 188
rect 16 117 19 130 rect 16 168 19 181
rect 7 94 11 108 rect 7 145 11 159
rect 23 105 27 108 rect 23 156 27 159
rect 23 101 26 105 rect 23 152 26 156
rect 7 69 11 80 rect 7 120 11 131
rect 15 94 19 96 rect 15 145 19 147
rect 15 78 19 80 rect 15 129 19 131
rect 23 94 27 101 rect 23 145 27 152
rect 23 78 27 80 rect 23 129 27 131
rect 15 75 18 78 rect 15 126 18 129
rect 15 74 31 75 rect 15 125 31 126
rect 15 72 27 74 rect 15 123 27 125
rect 7 65 9 69 rect 7 116 9 120
rect 6 44 9 54 rect 6 95 9 105
rect 33 51 34 55 rect 33 102 34 106
rect 31 44 34 51 rect 31 95 34 102
rect 3 20 6 23 rect 3 71 6 74
rect 3 15 7 20 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 >> << m2contact >>
rect 32 133 36 137 rect 32 184 36 188
rect 27 66 31 70 rect 27 117 31 121
rect 13 44 17 48 rect 13 95 17 99
rect 22 44 26 48 rect 22 95 26 99
rect 3 11 7 15 rect 27 51 31 55
rect 32 17 36 21
rect 3 4 7 8
<< metal2 >> << metal2 >>
rect 10 48 14 163 rect 10 99 14 214
rect 20 48 24 163 rect 20 99 24 214
rect 32 129 36 133 rect 32 180 36 184
rect 27 62 31 66 rect 27 113 31 117
rect 10 44 13 48 rect 10 95 13 99
rect 20 44 22 48 rect 20 95 22 99
rect 3 0 7 11 rect 3 0 7 4
rect 10 0 14 44 rect 10 0 14 95
rect 20 0 24 44 rect 20 0 24 95
rect 27 47 31 51
rect 32 21 36 25
<< bb >> << bb >>
rect 0 0 34 163 rect 0 0 34 214
<< labels >> << labels >>
flabel metal1 0 149 0 149 4 FreeSans 26 0 0 0 en rlabel metal2 5 2 5 2 1 dout
rlabel metal2 34 131 34 131 1 gnd rlabel metal2 29 49 29 49 1 vdd
rlabel metal2 29 64 29 64 1 vdd rlabel metal2 22 212 22 212 5 br
rlabel metal2 12 161 12 161 5 bl rlabel metal2 12 212 12 212 5 bl
rlabel metal2 22 161 22 161 5 br rlabel metal2 29 115 29 115 1 vdd
rlabel metal2 5 3 5 3 1 dout 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 >> << properties >>
string path 270.000 468.000 270.000 486.000 288.000 486.000 288.000 468.000 270.000 468.000 string path 270.000 468.000 270.000 486.000 288.000 486.000 288.000 468.000 270.000 468.000
<< end >> << end >>

View File

@ -5,11 +5,15 @@
* SPICE3 file created from sense_amp.ext - technology: scmos * SPICE3 file created from sense_amp.ext - technology: scmos
M1000 gnd en a_56_432# gnd n w=1.8u l=0.4u 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 M1001 a_56_432# a_48_304# dint gnd n w=1.8u l=0.4u
M1002 a_48_304# dout a_56_432# 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# dout vdd p w=3.6u l=0.4u M1003 vdd a_48_304# dint vdd p w=3.6u l=0.4u
M1004 a_48_304# dout vdd 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 dout vdd p w=4.8u 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 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 .ENDS