mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into add_wmask
This commit is contained in:
commit
242771f710
|
|
@ -0,0 +1,86 @@
|
|||
import os, copy
|
||||
from collections import defaultdict
|
||||
|
||||
import gdsMill
|
||||
import tech
|
||||
import math
|
||||
import globals
|
||||
import debug
|
||||
from vector import vector
|
||||
from pin_layout import pin_layout
|
||||
|
||||
class timing_graph():
|
||||
"""Implements a directed graph
|
||||
Nodes are currently just Strings.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.graph = defaultdict(set)
|
||||
self.all_paths = []
|
||||
|
||||
def add_edge(self, src_node, dest_node):
|
||||
"""Adds edge to graph. Nodes added as well if they do not exist."""
|
||||
src_node = src_node.lower()
|
||||
dest_node = dest_node.lower()
|
||||
self.graph[src_node].add(dest_node)
|
||||
|
||||
def add_node(self, node):
|
||||
"""Add node to graph with no edges"""
|
||||
node = node.lower()
|
||||
if not node in self.graph:
|
||||
self.graph[node] = set()
|
||||
|
||||
def remove_edges(self, node):
|
||||
"""Helper function to remove edges, useful for removing vdd/gnd"""
|
||||
node = node.lower()
|
||||
self.graph[node] = set()
|
||||
|
||||
def get_all_paths(self, src_node, dest_node, rmv_rail_nodes=True):
|
||||
"""Traverse all paths from source to destination"""
|
||||
src_node = src_node.lower()
|
||||
dest_node = dest_node.lower()
|
||||
|
||||
#Remove vdd and gnd by default
|
||||
#Will require edits if separate supplies are implemented.
|
||||
if rmv_rail_nodes:
|
||||
#Names are also assumed.
|
||||
self.remove_edges('vdd')
|
||||
self.remove_edges('gnd')
|
||||
|
||||
# Mark all the vertices as not visited
|
||||
visited = set()
|
||||
|
||||
# Create an array to store paths
|
||||
path = []
|
||||
self.all_paths = []
|
||||
|
||||
# Call the recursive helper function to print all paths
|
||||
self.get_all_paths_util(src_node, dest_node, visited, path)
|
||||
debug.info(2, "Paths found={}".format(len(self.all_paths)))
|
||||
|
||||
return self.all_paths
|
||||
|
||||
def get_all_paths_util(self, cur_node, dest_node, visited, path):
|
||||
"""Recursive function to find all paths in a Depth First Search manner"""
|
||||
# Mark the current node as visited and store in path
|
||||
visited.add(cur_node)
|
||||
path.append(cur_node)
|
||||
|
||||
# If current vertex is same as destination, then print
|
||||
# current path[]
|
||||
if cur_node == dest_node:
|
||||
self.all_paths.append(copy.deepcopy(path))
|
||||
else:
|
||||
# If current vertex is not destination
|
||||
#Recur for all the vertices adjacent to this vertex
|
||||
for node in self.graph[cur_node]:
|
||||
if node not in visited:
|
||||
self.get_all_paths_util(node, dest_node, visited, path)
|
||||
|
||||
# Remove current vertex from path[] and mark it as unvisited
|
||||
path.pop()
|
||||
visited.remove(cur_node)
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
return "Nodes: {}\nEdges:{} ".format(list(self.graph), self.graph)
|
||||
|
|
@ -12,6 +12,7 @@ import verify
|
|||
import debug
|
||||
import os
|
||||
from globals import OPTS
|
||||
import graph_util
|
||||
|
||||
total_drc_errors = 0
|
||||
total_lvs_errors = 0
|
||||
|
|
@ -30,7 +31,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
self.name = name
|
||||
hierarchy_spice.spice.__init__(self, name)
|
||||
hierarchy_layout.layout.__init__(self, name)
|
||||
|
||||
self.init_graph_params()
|
||||
|
||||
def get_layout_pins(self,inst):
|
||||
""" Return a map of pin locations of the instance offset """
|
||||
|
|
@ -105,6 +106,121 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
os.remove(tempspice)
|
||||
os.remove(tempgds)
|
||||
|
||||
def init_graph_params(self):
|
||||
"""Initializes parameters relevant to the graph creation"""
|
||||
#Only initializes a set for checking instances which should not be added
|
||||
self.graph_inst_exclude = set()
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Recursively create graph from instances in module."""
|
||||
|
||||
#Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
if subinst in self.graph_inst_exclude:
|
||||
continue
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
subinst.mod.build_graph(graph, subinst_name, subinst_ports)
|
||||
|
||||
def build_names(self, name_dict, inst_name, port_nets):
|
||||
"""Collects all the nets and the parent inst of that net."""
|
||||
#Translate port names to external nets
|
||||
if len(port_nets) != len(self.pins):
|
||||
debug.error("Port length mismatch:\nExt nets={}, Ports={}".format(port_nets,self.pins),1)
|
||||
port_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
debug.info(3, "Instance name={}".format(inst_name))
|
||||
for subinst, conns in zip(self.insts, self.conns):
|
||||
subinst_name = inst_name+'.X'+subinst.name
|
||||
subinst_ports = self.translate_nets(conns, port_dict, inst_name)
|
||||
for si_port, conn in zip(subinst_ports, conns):
|
||||
#Only add for first occurrence
|
||||
if si_port.lower() not in name_dict:
|
||||
mod_info = {'mod':self, 'int_net':conn}
|
||||
name_dict[si_port.lower()] = mod_info
|
||||
subinst.mod.build_names(name_dict, subinst_name, subinst_ports)
|
||||
|
||||
def find_aliases(self, inst_name, port_nets, path_nets, alias, alias_mod, exclusion_set=None):
|
||||
"""Given a list of nets, will compare the internal alias of a mod to determine
|
||||
if the nets have a connection to this mod's net (but not inst).
|
||||
"""
|
||||
if exclusion_set == None:
|
||||
exclusion_set = set()
|
||||
try:
|
||||
self.name_dict
|
||||
except AttributeError:
|
||||
self.name_dict = {}
|
||||
self.build_names(self.name_dict, inst_name, port_nets)
|
||||
aliases = []
|
||||
for net in path_nets:
|
||||
net = net.lower()
|
||||
int_net = self.name_dict[net]['int_net']
|
||||
int_mod = self.name_dict[net]['mod']
|
||||
if int_mod.is_net_alias(int_net, alias, alias_mod, exclusion_set):
|
||||
aliases.append(net)
|
||||
return aliases
|
||||
|
||||
def is_net_alias(self, known_net, net_alias, mod, exclusion_set):
|
||||
"""Checks if the alias_net in input mod is the same as the input net for this mod (self)."""
|
||||
if self in exclusion_set:
|
||||
return False
|
||||
#Check ports of this mod
|
||||
for pin in self.pins:
|
||||
if self.is_net_alias_name_check(known_net, pin, net_alias, mod):
|
||||
return True
|
||||
#Check connections of all other subinsts
|
||||
mod_set = set()
|
||||
for subinst, inst_conns in zip(self.insts, self.conns):
|
||||
for inst_conn, mod_pin in zip(inst_conns, subinst.mod.pins):
|
||||
if self.is_net_alias_name_check(known_net, inst_conn, net_alias, mod):
|
||||
return True
|
||||
elif inst_conn.lower() == known_net.lower() and subinst.mod not in mod_set:
|
||||
if subinst.mod.is_net_alias(mod_pin, net_alias, mod, exclusion_set):
|
||||
return True
|
||||
mod_set.add(subinst.mod)
|
||||
return False
|
||||
|
||||
def is_net_alias_name_check(self, parent_net, child_net, alias_net, mod):
|
||||
"""Utility function for checking single net alias."""
|
||||
return self == mod and \
|
||||
child_net.lower() == alias_net.lower() and \
|
||||
parent_net.lower() == alias_net.lower()
|
||||
|
||||
def get_mod_net(self, parent_net, child_inst, child_conns):
|
||||
"""Given an instance and net, returns the internal net in the mod
|
||||
corresponding to input net."""
|
||||
for conn, pin in zip(child_conns, child_inst.mod.pins):
|
||||
if parent_net.lower() == conn.lower():
|
||||
return pin
|
||||
return None
|
||||
|
||||
def translate_nets(self, subinst_ports, port_dict, inst_name):
|
||||
"""Converts connection names to their spice hierarchy equivalent"""
|
||||
converted_conns = []
|
||||
for conn in subinst_ports:
|
||||
if conn in port_dict:
|
||||
converted_conns.append(port_dict[conn])
|
||||
else:
|
||||
converted_conns.append("{}.{}".format(inst_name, conn))
|
||||
return converted_conns
|
||||
|
||||
def add_graph_edges(self, graph, port_nets):
|
||||
"""For every input, adds an edge to every output.
|
||||
Only intended to be used for gates and other simple modules."""
|
||||
#The final pin names will depend on the spice hierarchy, so
|
||||
#they are passed as an input.
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
input_pins = self.get_inputs()
|
||||
output_pins = self.get_outputs()
|
||||
inout_pins = self.get_inouts()
|
||||
for inp in input_pins+inout_pins:
|
||||
for out in output_pins+inout_pins:
|
||||
if inp != out: #do not add self loops
|
||||
graph.add_edge(pin_dict[inp], pin_dict[out])
|
||||
|
||||
def __str__(self):
|
||||
""" override print function output """
|
||||
pins = ",".join(self.pins)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,17 @@ class spice():
|
|||
else:
|
||||
debug.error("Mismatch in type and pin list lengths.", -1)
|
||||
|
||||
def add_pin_types(self, type_list):
|
||||
"""Add pin types for all the cell's pins.
|
||||
Typically, should only be used for handmade cells."""
|
||||
#This only works if self.pins == bitcell.pin_names
|
||||
if self.pin_names != self.pins:
|
||||
debug.error("{} spice subcircuit port names do not match pin_names\
|
||||
\n SPICE names={}\
|
||||
\n Module names={}\
|
||||
".format(self.name, self.pin_names, self.pins),1)
|
||||
self.pin_type = {pin:type for pin,type in zip(self.pin_names, type_list)}
|
||||
|
||||
def get_pin_type(self, name):
|
||||
""" Returns the type of the signal pin. """
|
||||
return self.pin_type[name]
|
||||
|
|
@ -102,6 +113,14 @@ class spice():
|
|||
output_list.append(pin)
|
||||
return output_list
|
||||
|
||||
def get_inouts(self):
|
||||
""" These use pin types to determine pin lists. These
|
||||
may be over-ridden by submodules that didn't use pin directions yet."""
|
||||
inout_list = []
|
||||
for pin in self.pins:
|
||||
if self.pin_type[pin]=="INOUT":
|
||||
inout_list.append(pin)
|
||||
return inout_list
|
||||
|
||||
def add_mod(self, mod):
|
||||
"""Adds a subckt/submodule to the subckt hierarchy"""
|
||||
|
|
@ -136,7 +155,13 @@ class spice():
|
|||
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):
|
||||
"""Reads the sp file (and parse the pins) from the library
|
||||
|
|
@ -157,6 +182,28 @@ class spice():
|
|||
else:
|
||||
self.spice = []
|
||||
|
||||
def check_net_in_spice(self, net_name):
|
||||
"""Checks if a net name exists in the current. Intended to be check nets in hand-made cells."""
|
||||
#Remove spaces and lower case then add spaces. Nets are separated by spaces.
|
||||
net_formatted = ' '+net_name.lstrip().rstrip().lower()+' '
|
||||
for line in self.spice:
|
||||
#Lowercase the line and remove any part of the line that is a comment.
|
||||
line = line.lower().split('*')[0]
|
||||
|
||||
#Skip .subckt or .ENDS lines
|
||||
if line.find('.') == 0:
|
||||
continue
|
||||
if net_formatted in line:
|
||||
return True
|
||||
return False
|
||||
|
||||
def do_nets_exist(self, nets):
|
||||
"""For handmade cell, checks sp file contains the storage nodes."""
|
||||
nets_match = True
|
||||
for net in nets:
|
||||
nets_match = nets_match and self.check_net_in_spice(net)
|
||||
return nets_match
|
||||
|
||||
def contains(self, mod, modlist):
|
||||
for x in modlist:
|
||||
if x.name == mod.name:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class bitcell(design.design):
|
|||
"""
|
||||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
storage_nets = ['Q', 'Qbar']
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"])
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ class bitcell(design.design):
|
|||
self.width = bitcell.width
|
||||
self.height = bitcell.height
|
||||
self.pin_map = bitcell.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
|
||||
parasitic_delay = 1
|
||||
|
|
@ -67,6 +71,14 @@ class bitcell(design.design):
|
|||
column_pins = ["br"]
|
||||
return column_pins
|
||||
|
||||
def get_bl_name(self):
|
||||
"""Get bl name"""
|
||||
return "bl"
|
||||
|
||||
def get_br_name(self):
|
||||
"""Get bl name"""
|
||||
return "br"
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
|
|
@ -75,9 +87,22 @@ class bitcell(design.design):
|
|||
total_power = self.return_power(dynamic, leakage)
|
||||
return total_power
|
||||
|
||||
def get_storage_net_names(self):
|
||||
"""Returns names of storage nodes in bitcell in [non-inverting, inverting] format."""
|
||||
#Checks that they do exist
|
||||
if self.nets_match:
|
||||
return self.storage_nets
|
||||
else:
|
||||
debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets))
|
||||
return None
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
#This is a handmade cell so the value must be entered in the tech.py file or estimated.
|
||||
#Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -20,6 +20,8 @@ class bitcell_1rw_1r(design.design):
|
|||
"""
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
storage_nets = ['Q', 'Q_bar']
|
||||
(width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"])
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ class bitcell_1rw_1r(design.design):
|
|||
self.width = bitcell_1rw_1r.width
|
||||
self.height = bitcell_1rw_1r.height
|
||||
self.pin_map = bitcell_1rw_1r.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
|
||||
parasitic_delay = 1
|
||||
|
|
@ -91,6 +95,14 @@ class bitcell_1rw_1r(design.design):
|
|||
column_pins = ["br0"]
|
||||
return column_pins
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "bl{}".format(port)
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "br{}".format(port)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
|
|
@ -106,3 +118,24 @@ class bitcell_1rw_1r(design.design):
|
|||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def get_storage_net_names(self):
|
||||
"""Returns names of storage nodes in bitcell in [non-inverting, inverting] format."""
|
||||
#Checks that they do exist
|
||||
if self.nets_match:
|
||||
return self.storage_nets
|
||||
else:
|
||||
debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets))
|
||||
return None
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"])
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"])
|
||||
# Port 1 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"])
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"])
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ class bitcell_1w_1r(design.design):
|
|||
"""
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
storage_nets = ['Q', 'Q_bar']
|
||||
(width,height) = utils.get_libcell_size("cell_1w_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"])
|
||||
|
||||
|
|
@ -31,6 +33,8 @@ class bitcell_1w_1r(design.design):
|
|||
self.width = bitcell_1w_1r.width
|
||||
self.height = bitcell_1w_1r.height
|
||||
self.pin_map = bitcell_1w_1r.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
self.nets_match = self.do_nets_exist(self.storage_nets)
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
|
||||
parasitic_delay = 1
|
||||
|
|
@ -91,6 +95,14 @@ class bitcell_1w_1r(design.design):
|
|||
column_pins = ["br0"]
|
||||
return column_pins
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "bl{}".format(port)
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "br{}".format(port)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Bitcell power in nW. Only characterizes leakage."""
|
||||
from tech import spice
|
||||
|
|
@ -106,3 +118,22 @@ class bitcell_1w_1r(design.design):
|
|||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def get_storage_net_names(self):
|
||||
"""Returns names of storage nodes in bitcell in [non-inverting, inverting] format."""
|
||||
#Checks that they do exist
|
||||
if self.nets_match:
|
||||
return self.storage_nets
|
||||
else:
|
||||
debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets))
|
||||
return None
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"])
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"])
|
||||
# Port 1 is a write port, so its timing is not considered here.
|
||||
|
|
|
|||
|
|
@ -97,46 +97,48 @@ class pbitcell(design.design):
|
|||
port = 0
|
||||
|
||||
for k in range(self.num_rw_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.add_pin("bl{}".format(port), "OUTPUT")
|
||||
self.add_pin("br{}".format(port), "OUTPUT")
|
||||
self.rw_bl_names.append("bl{}".format(port))
|
||||
self.rw_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_w_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.add_pin("bl{}".format(port), "INPUT")
|
||||
self.add_pin("br{}".format(port), "INPUT")
|
||||
self.w_bl_names.append("bl{}".format(port))
|
||||
self.w_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_r_ports):
|
||||
self.add_pin("bl{}".format(port))
|
||||
self.add_pin("br{}".format(port))
|
||||
self.add_pin("bl{}".format(port), "OUTPUT")
|
||||
self.add_pin("br{}".format(port), "OUTPUT")
|
||||
self.r_bl_names.append("bl{}".format(port))
|
||||
self.r_br_names.append("br{}".format(port))
|
||||
port += 1
|
||||
|
||||
port = 0
|
||||
for k in range(self.num_rw_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.add_pin("wl{}".format(port), "INPUT")
|
||||
self.rw_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_w_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.add_pin("wl{}".format(port), "INPUT")
|
||||
self.w_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
for k in range(self.num_r_ports):
|
||||
self.add_pin("wl{}".format(port))
|
||||
self.add_pin("wl{}".format(port), "INPUT")
|
||||
self.r_wl_names.append("wl{}".format(port))
|
||||
port += 1
|
||||
|
||||
self.add_pin("vdd")
|
||||
self.add_pin("gnd")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
# if this is a replica bitcell, replace the instances of Q_bar with vdd
|
||||
if self.replica_bitcell:
|
||||
self.Q_bar = "vdd"
|
||||
else:
|
||||
self.Q_bar = "Q_bar"
|
||||
self.Q = "Q"
|
||||
self.storage_nets = [self.Q, self.Q_bar]
|
||||
|
||||
def add_modules(self):
|
||||
""" Determine size of transistors and add ptx modules """
|
||||
|
|
@ -270,20 +272,20 @@ class pbitcell(design.design):
|
|||
# create active for nmos
|
||||
self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left",
|
||||
mod=self.inverter_nmos)
|
||||
self.connect_inst(["Q", self.Q_bar, "gnd", "gnd"])
|
||||
self.connect_inst([self.Q, self.Q_bar, "gnd", "gnd"])
|
||||
|
||||
self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right",
|
||||
mod=self.inverter_nmos)
|
||||
self.connect_inst(["gnd", "Q", self.Q_bar, "gnd"])
|
||||
self.connect_inst(["gnd", self.Q, self.Q_bar, "gnd"])
|
||||
|
||||
# create active for pmos
|
||||
self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left",
|
||||
mod=self.inverter_pmos)
|
||||
self.connect_inst(["Q", self.Q_bar, "vdd", "vdd"])
|
||||
self.connect_inst([self.Q, self.Q_bar, "vdd", "vdd"])
|
||||
|
||||
self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right",
|
||||
mod=self.inverter_pmos)
|
||||
self.connect_inst(["vdd", "Q", self.Q_bar, "vdd"])
|
||||
self.connect_inst(["vdd", self.Q, self.Q_bar, "vdd"])
|
||||
|
||||
def place_storage(self):
|
||||
""" Places the transistors for the crossed coupled inverters in the bitcell """
|
||||
|
|
@ -377,7 +379,7 @@ class pbitcell(design.design):
|
|||
# add read/write transistors
|
||||
self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k),
|
||||
mod=self.readwrite_nmos)
|
||||
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], "Q", "gnd"])
|
||||
self.connect_inst([self.rw_bl_names[k], self.rw_wl_names[k], self.Q, "gnd"])
|
||||
|
||||
self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k),
|
||||
mod=self.readwrite_nmos)
|
||||
|
|
@ -451,7 +453,7 @@ class pbitcell(design.design):
|
|||
# add write transistors
|
||||
self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k),
|
||||
mod=self.write_nmos)
|
||||
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], "Q", "gnd"])
|
||||
self.connect_inst([self.w_bl_names[k], self.w_wl_names[k], self.Q, "gnd"])
|
||||
|
||||
self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k),
|
||||
mod=self.write_nmos)
|
||||
|
|
@ -537,7 +539,7 @@ class pbitcell(design.design):
|
|||
|
||||
self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k),
|
||||
mod=self.read_nmos)
|
||||
self.connect_inst(["gnd", "Q", "RA_to_R_right{}".format(k), "gnd"])
|
||||
self.connect_inst(["gnd", self.Q, "RA_to_R_right{}".format(k), "gnd"])
|
||||
|
||||
# add read transistors
|
||||
self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k),
|
||||
|
|
@ -885,6 +887,18 @@ class pbitcell(design.design):
|
|||
vdd_pos = self.inverter_pmos_right.get_pin("D").center()
|
||||
self.add_path("metal1", [Q_bar_pos, vdd_pos])
|
||||
|
||||
def get_storage_net_names(self):
|
||||
"""Returns names of storage nodes in bitcell in [non-inverting, inverting] format."""
|
||||
return self.storage_nets
|
||||
|
||||
def get_bl_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "bl{}".format(port)
|
||||
|
||||
def get_br_name(self, port=0):
|
||||
"""Get bl name by port"""
|
||||
return "br{}".format(port)
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0, swing = 0.5):
|
||||
parasitic_delay = 1
|
||||
size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
|
||||
|
|
@ -911,3 +925,14 @@ class pbitcell(design.design):
|
|||
access_tx_cin = self.readwrite_nmos.get_cin()
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph for pbitcell. Only readwrite and read ports."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
# Edges added wl->bl, wl->br for every port except write ports
|
||||
rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names)
|
||||
r_pin_names = zip(self.rw_wl_names, self.rw_bl_names, self.rw_br_names)
|
||||
for pin_zip in zip(rw_pin_names, r_pin_names):
|
||||
for wl,bl,br in pin_zip:
|
||||
graph.add_edge(pin_dict[wl],pin_dict[bl])
|
||||
graph.add_edge(pin_dict[wl],pin_dict[br])
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class replica_bitcell(design.design):
|
|||
the technology library. """
|
||||
|
||||
pin_names = ["bl", "br", "wl", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_6t", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_6t", GDS["unit"])
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ class replica_bitcell(design.design):
|
|||
self.width = replica_bitcell.width
|
||||
self.height = replica_bitcell.height
|
||||
self.pin_map = replica_bitcell.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
|
|
@ -36,3 +38,7 @@ class replica_bitcell(design.design):
|
|||
#Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -18,6 +18,7 @@ class replica_bitcell_1rw_1r(design.design):
|
|||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_1rw_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1rw_1r", GDS["unit"])
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ class replica_bitcell_1rw_1r(design.design):
|
|||
self.width = replica_bitcell_1rw_1r.width
|
||||
self.height = replica_bitcell_1rw_1r.height
|
||||
self.pin_map = replica_bitcell_1rw_1r.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
|
|
@ -37,3 +39,15 @@ class replica_bitcell_1rw_1r(design.design):
|
|||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"])
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"])
|
||||
# Port 1 edges
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"])
|
||||
graph.add_edge(pin_dict["wl1"], pin_dict["br1"])
|
||||
|
|
@ -18,6 +18,7 @@ class replica_bitcell_1w_1r(design.design):
|
|||
the technology library. """
|
||||
|
||||
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
|
||||
type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("replica_cell_1w_1r", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "replica_cell_1w_1r", GDS["unit"])
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ class replica_bitcell_1w_1r(design.design):
|
|||
self.width = replica_bitcell_1w_1r.width
|
||||
self.height = replica_bitcell_1w_1r.height
|
||||
self.pin_map = replica_bitcell_1w_1r.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def get_wl_cin(self):
|
||||
"""Return the relative capacitance of the access transistor gates"""
|
||||
|
|
@ -37,3 +39,13 @@ class replica_bitcell_1w_1r(design.design):
|
|||
#FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed.
|
||||
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
|
||||
return 2*access_tx_cin
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges to graph. Multiport bitcell timing graph is too complex
|
||||
to use the add_graph_edges function."""
|
||||
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
|
||||
#Edges hardcoded here. Essentially wl->bl/br for both ports.
|
||||
# Port 0 edges
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"])
|
||||
graph.add_edge(pin_dict["wl0"], pin_dict["br0"])
|
||||
# Port 1 is a write port, so its timing is not considered here.
|
||||
|
|
@ -15,7 +15,6 @@ from .setup_hold import *
|
|||
from .functional import *
|
||||
from .worst_case import *
|
||||
from .simulation import *
|
||||
from .bitline_delay import *
|
||||
from .measurements import *
|
||||
from .model_check import *
|
||||
|
||||
|
|
|
|||
|
|
@ -1,248 +0,0 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys,re,shutil
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from .delay import delay
|
||||
|
||||
class bitline_delay(delay):
|
||||
"""Functions to test for the worst case delay in a target SRAM
|
||||
|
||||
The current worst case determines a feasible period for the SRAM then tests
|
||||
several bits and record the delay and differences between the bits.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, sram, spfile, corner):
|
||||
delay.__init__(self,sram,spfile,corner)
|
||||
self.period = tech.spice["feasible_period"]
|
||||
self.is_bitline_measure = True
|
||||
|
||||
def create_signal_names(self):
|
||||
delay.create_signal_names(self)
|
||||
self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"]
|
||||
self.sen_name = "Xsram.s_en"
|
||||
|
||||
def create_measurement_names(self):
|
||||
"""Create measurement names. The names themselves currently define the type of measurement"""
|
||||
#Altering the names will crash the characterizer. TODO: object orientated approach to the measurements.
|
||||
self.bl_volt_meas_names = ["volt_bl", "volt_br"]
|
||||
self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation
|
||||
self.bl_delay_result_name = "delay_bl_vth" #Used in the return value
|
||||
|
||||
def set_probe(self,probe_address, probe_data):
|
||||
""" Probe address and data can be set separately to utilize other
|
||||
functions in this characterizer besides analyze."""
|
||||
delay.set_probe(self,probe_address, probe_data)
|
||||
self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data)
|
||||
|
||||
def write_delay_measures(self):
|
||||
"""
|
||||
Write the measure statements to quantify the bitline voltage at sense amp enable 50%.
|
||||
"""
|
||||
self.sf.write("\n* Measure statements for delay and power\n")
|
||||
|
||||
# Output some comments to aid where cycles start and
|
||||
for comment in self.cycle_comments:
|
||||
self.sf.write("* {}\n".format(comment))
|
||||
|
||||
for read_port in self.targ_read_ports:
|
||||
self.write_bitline_voltage_measures(read_port)
|
||||
self.write_bitline_delay_measures(read_port)
|
||||
|
||||
def write_bitline_voltage_measures(self, port):
|
||||
"""
|
||||
Add measurments to capture the bitline voltages at 50% Sense amp enable
|
||||
"""
|
||||
debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port))
|
||||
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
|
||||
bitline_port = ""
|
||||
else:
|
||||
bitline_port = str(port)
|
||||
|
||||
sen_port_name = "{}{}".format(self.sen_name,port)
|
||||
for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names):
|
||||
bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column)
|
||||
measure_port_name = "{}{}".format(measure_name,port)
|
||||
self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]])
|
||||
|
||||
def write_bitline_delay_measures(self, port):
|
||||
"""
|
||||
Write the measure statements to quantify the delay and power results for a read port.
|
||||
"""
|
||||
# add measure statements for delays/slews
|
||||
for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names):
|
||||
meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port)
|
||||
self.stim.gen_meas_delay(*meas_values)
|
||||
|
||||
def get_delay_meas_values(self, delay_name, bitline_name, port):
|
||||
"""Get the values needed to generate a Spice measurement statement based on the name of the measurement."""
|
||||
if len(self.all_ports) == 1: #special naming case for single port sram bitlines
|
||||
bitline_port = ""
|
||||
else:
|
||||
bitline_port = str(port)
|
||||
|
||||
meas_name="{0}{1}".format(delay_name, port)
|
||||
targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column)
|
||||
half_vdd = 0.5 * self.vdd_voltage
|
||||
trig_val = half_vdd
|
||||
targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"]
|
||||
trig_name = "clk{0}".format(port)
|
||||
trig_dir="FALL"
|
||||
targ_dir="FALL"
|
||||
#Half period added to delay measurement to negative clock edge
|
||||
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
|
||||
return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
|
||||
|
||||
def gen_test_cycles_one_port(self, read_port, write_port):
|
||||
"""Sets a list of key time-points [ns] of the waveform (each rising edge)
|
||||
of the cycles to do a timing evaluation of a single port """
|
||||
|
||||
# Create the inverse address for a scratch address
|
||||
inverse_address = self.calculate_inverse_address()
|
||||
|
||||
# For now, ignore data patterns and write ones or zeros
|
||||
data_ones = "1"*self.word_size
|
||||
data_zeros = "0"*self.word_size
|
||||
|
||||
if self.t_current == 0:
|
||||
self.add_noop_all_ports("Idle cycle (no positive clock edge)",
|
||||
inverse_address, data_zeros)
|
||||
|
||||
self.add_write("W data 1 address {}".format(inverse_address),
|
||||
inverse_address,data_ones,write_port)
|
||||
|
||||
self.add_write("W data 0 address {} to write value".format(self.probe_address),
|
||||
self.probe_address,data_zeros,write_port)
|
||||
self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1
|
||||
|
||||
# This also ensures we will have a H->L transition on the next read
|
||||
self.add_read("R data 1 address {} to set DOUT caps".format(inverse_address),
|
||||
inverse_address,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1
|
||||
|
||||
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
|
||||
self.probe_address,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
|
||||
|
||||
def get_data_bit_column_number(self, probe_address, probe_data):
|
||||
"""Calculates bitline column number of data bit under test using bit position and mux size"""
|
||||
if self.sram.col_addr_size>0:
|
||||
col_address = int(probe_address[0:self.sram.col_addr_size],2)
|
||||
else:
|
||||
col_address = 0
|
||||
bl_column = int(self.sram.words_per_row*probe_data + col_address)
|
||||
return bl_column
|
||||
|
||||
def run_delay_simulation(self):
|
||||
"""
|
||||
This tries to simulate a period and checks if the result works. If
|
||||
so, it returns True and the delays, slews, and powers. It
|
||||
works on the trimmed netlist by default, so powers do not
|
||||
include leakage of all cells.
|
||||
"""
|
||||
#Sanity Check
|
||||
debug.check(self.period > 0, "Target simulation period non-positive")
|
||||
|
||||
result = [{} for i in self.all_ports]
|
||||
# Checking from not data_value to data_value
|
||||
self.write_delay_stimulus()
|
||||
|
||||
self.stim.run_sim() #running sim prodoces spice output file.
|
||||
|
||||
for port in self.targ_read_ports:
|
||||
#Parse and check the voltage measurements
|
||||
bl_volt_meas_dict = {}
|
||||
for mname in self.bl_volt_meas_names:
|
||||
mname_port = "{}{}".format(mname,port)
|
||||
volt_meas_val = parse_spice_list("timing", mname_port)
|
||||
if type(volt_meas_val)!=float:
|
||||
debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1)
|
||||
bl_volt_meas_dict[mname] = volt_meas_val
|
||||
result[port].update(bl_volt_meas_dict)
|
||||
|
||||
#Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail.
|
||||
bl_delay_meas_dict = {}
|
||||
values_added = 0 #For error checking
|
||||
for mname in self.bl_delay_meas_names: #Parse
|
||||
mname_port = "{}{}".format(mname,port)
|
||||
delay_meas_val = parse_spice_list("timing", mname_port)
|
||||
if type(delay_meas_val)==float: #Only add if value is float, do not error.
|
||||
bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns
|
||||
values_added+=1
|
||||
debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.")
|
||||
debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.")
|
||||
result[port].update(bl_delay_meas_dict)
|
||||
|
||||
# The delay is from the negative edge for our SRAM
|
||||
return (True,result)
|
||||
|
||||
def check_bitline_all_results(self, results):
|
||||
"""Checks the bitline values measured for each tested port"""
|
||||
for port in self.targ_read_ports:
|
||||
self.check_bitline_port_results(results[port])
|
||||
|
||||
def check_bitline_port_results(self, port_results):
|
||||
"""Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing"""
|
||||
bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"]
|
||||
self.check_functionality(bl_volt,br_volt)
|
||||
self.check_swing_from_vdd(bl_volt,br_volt)
|
||||
self.check_differential_swing(bl_volt,br_volt)
|
||||
|
||||
def check_functionality(self, bl_volt, br_volt):
|
||||
"""Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0."""
|
||||
if bl_volt > br_volt:
|
||||
debug.error("Read failure. Value 1 was read instead of 0.",1)
|
||||
|
||||
def check_swing_from_vdd(self, bl_volt, br_volt):
|
||||
"""Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter."""
|
||||
if bl_volt < br_volt:
|
||||
discharge_volt = bl_volt
|
||||
else:
|
||||
discharge_volt = br_volt
|
||||
desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage
|
||||
debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt))
|
||||
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
|
||||
if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage:
|
||||
debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100))
|
||||
|
||||
def check_differential_swing(self, bl_volt, br_volt):
|
||||
"""This check looks at the difference between the bitline voltages. This needs to be large enough to prevent
|
||||
sensing errors."""
|
||||
bitline_swing = abs(bl_volt-br_volt)
|
||||
debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing))
|
||||
vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now.
|
||||
if bitline_swing < vdd_error_margin*self.vdd_voltage:
|
||||
debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin))
|
||||
|
||||
def analyze(self, probe_address, probe_data, slews, loads):
|
||||
"""Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
|
||||
read_port = self.read_ports[0] #only test the first read port
|
||||
bitline_swings = {}
|
||||
self.targ_read_ports = [read_port]
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
debug.info(1,"Bitline swing test: corner {}".format(self.corner))
|
||||
(success, results)=self.run_delay_simulation()
|
||||
debug.check(success, "Bitline Failed: period {}".format(self.period))
|
||||
debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port]))
|
||||
self.check_bitline_all_results(results)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
|
||||
|
|
@ -8,7 +8,17 @@
|
|||
import re
|
||||
import debug
|
||||
from globals import OPTS
|
||||
from enum import Enum
|
||||
|
||||
class sram_op(Enum):
|
||||
READ_ZERO = 0
|
||||
READ_ONE = 1
|
||||
WRITE_ZERO = 2
|
||||
WRITE_ONE = 3
|
||||
|
||||
class bit_polarity(Enum):
|
||||
NONINVERTING = 0
|
||||
INVERTING = 1
|
||||
|
||||
def relative_compare(value1,value2,error_tolerance=0.001):
|
||||
""" This is used to compare relative values for convergence. """
|
||||
|
|
@ -32,7 +42,6 @@ def parse_spice_list(filename, key):
|
|||
f.close()
|
||||
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)
|
||||
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents)
|
||||
|
||||
if val != None:
|
||||
debug.info(4, "Key = " + key + " Val = " + val.group(1))
|
||||
return convert_to_float(val.group(1))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys,re,shutil
|
||||
import sys,re,shutil,copy
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
|
|
@ -17,6 +17,8 @@ from globals import OPTS
|
|||
from .simulation import simulation
|
||||
from .measurements import *
|
||||
import logical_effort
|
||||
import graph_util
|
||||
from sram_factory import factory
|
||||
|
||||
class delay(simulation):
|
||||
"""Functions to measure the delay and power of an SRAM at a given address and
|
||||
|
|
@ -44,6 +46,8 @@ class delay(simulation):
|
|||
self.period = 0
|
||||
self.set_load_slew(0,0)
|
||||
self.set_corner(corner)
|
||||
self.create_signal_names()
|
||||
self.add_graph_exclusions()
|
||||
|
||||
def create_measurement_names(self):
|
||||
"""Create measurement names. The names themselves currently define the type of measurement"""
|
||||
|
|
@ -55,94 +59,258 @@ class delay(simulation):
|
|||
|
||||
def create_measurement_objects(self):
|
||||
"""Create the measurements used for read and write ports"""
|
||||
self.create_read_port_measurement_objects()
|
||||
self.create_write_port_measurement_objects()
|
||||
self.read_meas_lists = self.create_read_port_measurement_objects()
|
||||
self.write_meas_lists = self.create_write_port_measurement_objects()
|
||||
self.check_meas_names(self.read_meas_lists+self.write_meas_lists)
|
||||
|
||||
def check_meas_names(self, measures_lists):
|
||||
"""Given measurements (in 2d list), checks that their names are unique.
|
||||
Spice sim will fail otherwise."""
|
||||
name_set = set()
|
||||
for meas_list in measures_lists:
|
||||
for meas in meas_list:
|
||||
name = meas.name.lower()
|
||||
debug.check(name not in name_set,("SPICE measurements must have unique names. "
|
||||
"Duplicate name={}").format(name))
|
||||
name_set.add(name)
|
||||
|
||||
def create_read_port_measurement_objects(self):
|
||||
"""Create the measurements used for read ports: delays, slews, powers"""
|
||||
|
||||
self.read_meas_objs = []
|
||||
trig_delay_name = "clk{0}"
|
||||
self.read_lib_meas = []
|
||||
self.clk_frmt = "clk{0}" #Unformatted clock name
|
||||
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
|
||||
self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9))
|
||||
self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file.
|
||||
self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9))
|
||||
self.read_meas_objs[-1].meta_str = "read0"
|
||||
self.delay_meas = []
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "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.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.read_lib_meas+=self.delay_meas
|
||||
|
||||
self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9))
|
||||
self.read_meas_objs[-1].meta_str = "read1"
|
||||
self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9))
|
||||
self.read_meas_objs[-1].meta_str = "read0"
|
||||
self.slew_meas = []
|
||||
self.slew_meas.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9))
|
||||
self.slew_meas[-1].meta_str = sram_op.READ_ONE
|
||||
self.slew_meas.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9))
|
||||
self.slew_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
self.read_lib_meas+=self.slew_meas
|
||||
|
||||
self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3))
|
||||
self.read_meas_objs[-1].meta_str = "read1"
|
||||
self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3))
|
||||
self.read_meas_objs[-1].meta_str = "read0"
|
||||
self.read_lib_meas.append(power_measure("read1_power", "RISE", measure_scale=1e3))
|
||||
self.read_lib_meas[-1].meta_str = sram_op.READ_ONE
|
||||
self.read_lib_meas.append(power_measure("read0_power", "FALL", measure_scale=1e3))
|
||||
self.read_lib_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
|
||||
#This will later add a half-period to the spice time delay. Only for reading 0.
|
||||
for obj in self.read_meas_objs:
|
||||
if obj.meta_str is "read0":
|
||||
for obj in self.read_lib_meas:
|
||||
if obj.meta_str is sram_op.READ_ZERO:
|
||||
obj.meta_add_delay = True
|
||||
|
||||
trig_name = "Xsram.s_en{}" #Sense amp enable
|
||||
if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
|
||||
port_format = ""
|
||||
else:
|
||||
port_format = "{}"
|
||||
read_measures = []
|
||||
read_measures.append(self.read_lib_meas)
|
||||
#Other measurements associated with the read port not included in the liberty file
|
||||
read_measures.append(self.create_bitline_measurement_objects())
|
||||
read_measures.append(self.create_debug_measurement_objects())
|
||||
read_measures.append(self.create_read_bit_measures())
|
||||
|
||||
bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
|
||||
br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
|
||||
# self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5))
|
||||
# self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5))
|
||||
return read_measures
|
||||
|
||||
#These are read values but need to be separated for unique error checking.
|
||||
self.create_bitline_delay_measurement_objects()
|
||||
|
||||
def create_bitline_delay_measurement_objects(self):
|
||||
def create_bitline_measurement_objects(self):
|
||||
"""Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements.
|
||||
These measurements are only associated with read values
|
||||
"""
|
||||
self.bitline_delay_objs = []
|
||||
trig_name = "clk{0}"
|
||||
if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name
|
||||
port_format = ""
|
||||
else:
|
||||
port_format = "{}"
|
||||
bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column)
|
||||
br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column)
|
||||
targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd
|
||||
self.bitline_volt_meas = []
|
||||
#Bitline voltage measures
|
||||
self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ZERO",
|
||||
self.bl_name))
|
||||
self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ZERO",
|
||||
self.br_name))
|
||||
self.bitline_volt_meas[-1].meta_str = sram_op.READ_ZERO
|
||||
|
||||
targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
|
||||
# self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
|
||||
# self.bitline_delay_objs[-1].meta_str = "read0"
|
||||
# self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9))
|
||||
# self.bitline_delay_objs[-1].meta_str = "read1"
|
||||
#Enforces the time delay on the bitline measurements for read0 or read1
|
||||
for obj in self.bitline_delay_objs:
|
||||
obj.meta_add_delay = True
|
||||
self.bitline_volt_meas.append(voltage_at_measure("v_bl_READ_ONE",
|
||||
self.bl_name))
|
||||
self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE
|
||||
self.bitline_volt_meas.append(voltage_at_measure("v_br_READ_ONE",
|
||||
self.br_name))
|
||||
self.bitline_volt_meas[-1].meta_str = sram_op.READ_ONE
|
||||
return self.bitline_volt_meas
|
||||
|
||||
def create_write_port_measurement_objects(self):
|
||||
"""Create the measurements used for read ports: delays, slews, powers"""
|
||||
self.write_meas_objs = []
|
||||
self.write_lib_meas = []
|
||||
|
||||
self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3))
|
||||
self.write_meas_objs[-1].meta_str = "write1"
|
||||
self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3))
|
||||
self.write_meas_objs[-1].meta_str = "write0"
|
||||
self.write_lib_meas.append(power_measure("write1_power", "RISE", measure_scale=1e3))
|
||||
self.write_lib_meas[-1].meta_str = sram_op.WRITE_ONE
|
||||
self.write_lib_meas.append(power_measure("write0_power", "FALL", measure_scale=1e3))
|
||||
self.write_lib_meas[-1].meta_str = sram_op.WRITE_ZERO
|
||||
|
||||
def create_signal_names(self):
|
||||
self.addr_name = "A"
|
||||
self.din_name = "DIN"
|
||||
self.dout_name = "DOUT"
|
||||
write_measures = []
|
||||
write_measures.append(self.write_lib_meas)
|
||||
return write_measures
|
||||
|
||||
#This is TODO once multiport control has been finalized.
|
||||
#self.control_name = "CSB"
|
||||
def create_debug_measurement_objects(self):
|
||||
"""Create debug measurement to help identify failures."""
|
||||
self.debug_volt_meas = []
|
||||
for meas in self.delay_meas:
|
||||
#Output voltage measures
|
||||
self.debug_volt_meas.append(voltage_at_measure("v_{}".format(meas.name),
|
||||
meas.targ_name_no_port))
|
||||
self.debug_volt_meas[-1].meta_str = meas.meta_str
|
||||
|
||||
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9)
|
||||
self.sen_meas.meta_str = sram_op.READ_ZERO
|
||||
self.sen_meas.meta_add_delay = True
|
||||
|
||||
return self.debug_volt_meas+[self.sen_meas]
|
||||
|
||||
def create_read_bit_measures(self):
|
||||
"""Adds bit measurements for read0 and read1 cycles"""
|
||||
self.bit_meas = {bit_polarity.NONINVERTING:[], bit_polarity.INVERTING:[]}
|
||||
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
|
||||
for cycle in meas_cycles:
|
||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||
for polarity,meas in single_bit_meas.items():
|
||||
meas.meta_str = cycle
|
||||
self.bit_meas[polarity].append(meas)
|
||||
#Dictionary values are lists, reduce to a single list of measurements
|
||||
return [meas for meas_list in self.bit_meas.values() for meas in meas_list]
|
||||
|
||||
def get_bit_measures(self, meas_tag, probe_address, probe_data):
|
||||
"""Creates measurements for the q/qbar of input bit position.
|
||||
meas_tag is a unique identifier for the measurement."""
|
||||
bit_col = self.get_data_bit_column_number(probe_address, probe_data)
|
||||
bit_row = self.get_address_row_number(probe_address)
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={}").format(storage_names))
|
||||
q_name = cell_name+'.'+str(storage_names[0])
|
||||
qbar_name = cell_name+'.'+str(storage_names[1])
|
||||
#Bit measures, measurements times to be defined later. The measurement names must be unique
|
||||
# but they is enforced externally
|
||||
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False)
|
||||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False)
|
||||
|
||||
return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas}
|
||||
|
||||
def set_load_slew(self,load,slew):
|
||||
""" Set the load and slew """
|
||||
self.load = load
|
||||
self.slew = slew
|
||||
|
||||
def add_graph_exclusions(self):
|
||||
"""Exclude portions of SRAM from timing graph which are not relevant"""
|
||||
#other initializations can only be done during analysis when a bit has been selected
|
||||
#for testing.
|
||||
self.sram.bank.graph_exclude_precharge()
|
||||
self.sram.graph_exclude_addr_dff()
|
||||
self.sram.graph_exclude_data_dff()
|
||||
self.sram.graph_exclude_ctrl_dffs()
|
||||
|
||||
def create_graph(self):
|
||||
"""Creates timing graph to generate the timing paths for the SRAM output."""
|
||||
self.sram.bank.bitcell_array.init_graph_params() #Removes previous bit exclusions
|
||||
self.sram.bank.bitcell_array.graph_exclude_bits(self.wordline_row, self.bitline_column)
|
||||
|
||||
#Generate new graph every analysis as edges might change depending on test bit
|
||||
self.graph = graph_util.timing_graph()
|
||||
self.sram_spc_name = "X{}".format(self.sram.name)
|
||||
self.sram.build_graph(self.graph,self.sram_spc_name,self.pins)
|
||||
|
||||
def set_internal_spice_names(self):
|
||||
"""Sets important names for characterization such as Sense amp enable and internal bit nets."""
|
||||
port = 0
|
||||
self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port), \
|
||||
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
|
||||
|
||||
self.sen_name = self.get_sen_name(self.graph.all_paths)
|
||||
debug.info(2,"s_en name = {}".format(self.sen_name))
|
||||
|
||||
self.bl_name,self.br_name = self.get_bl_name(self.graph.all_paths)
|
||||
debug.info(2,"bl name={}, br name={}".format(self.bl_name,self.br_name))
|
||||
|
||||
def get_sen_name(self, paths):
|
||||
"""Gets the signal name associated with the sense amp enable from input paths.
|
||||
Only expects a single path to contain the sen signal name."""
|
||||
sa_mods = factory.get_mods(OPTS.sense_amp)
|
||||
#Any sense amp instantiated should be identical, any change to that
|
||||
#will require some identification to determine the mod desired.
|
||||
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
|
||||
enable_name = sa_mods[0].get_enable_name()
|
||||
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
|
||||
return sen_name
|
||||
|
||||
def get_bl_name(self, paths):
|
||||
"""Gets the signal name associated with the bitlines in the bank."""
|
||||
cell_mods = factory.get_mods(OPTS.bitcell)
|
||||
if len(cell_mods)>=1:
|
||||
cell_mod = self.get_primary_cell_mod(cell_mods)
|
||||
elif len(cell_mods)==0:
|
||||
debug.error("No bitcells found. Cannot determine bitline names.", 1)
|
||||
|
||||
cell_bl = cell_mod.get_bl_name()
|
||||
cell_br = cell_mod.get_br_name()
|
||||
|
||||
bl_found = False
|
||||
#Only a single path should contain a single s_en name. Anything else is an error.
|
||||
bl_names = []
|
||||
exclude_set = self.get_bl_name_search_exclusions()
|
||||
for int_net in [cell_bl, cell_br]:
|
||||
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
|
||||
|
||||
return bl_names[0], bl_names[1]
|
||||
|
||||
def get_bl_name_search_exclusions(self):
|
||||
"""Gets the mods as a set which should be excluded while searching for name."""
|
||||
#Exclude the RBL as it contains bitcells which are not in the main bitcell array
|
||||
#so it makes the search awkward
|
||||
return set(factory.get_mods(OPTS.replica_bitline))
|
||||
|
||||
def get_primary_cell_mod(self, cell_mods):
|
||||
"""Distinguish bitcell array mod from replica bitline array.
|
||||
Assume there are no replica bitcells in the primary array."""
|
||||
if len(cell_mods) == 1:
|
||||
return cell_mods[0]
|
||||
rbc_mods = factory.get_mods(OPTS.replica_bitcell)
|
||||
non_rbc_mods = []
|
||||
for bitcell in cell_mods:
|
||||
has_cell = False
|
||||
for replica_cell in rbc_mods:
|
||||
has_cell = has_cell or replica_cell.contains(bitcell, replica_cell.mods)
|
||||
if not has_cell:
|
||||
non_rbc_mods.append(bitcell)
|
||||
if len(non_rbc_mods) != 1:
|
||||
debug.error('Multiple bitcell mods found. Cannot distinguish for characterization',1)
|
||||
return non_rbc_mods[0]
|
||||
|
||||
def are_mod_pins_equal(self, mods):
|
||||
"""Determines if there are pins differences in the input mods"""
|
||||
if len(mods) == 0:
|
||||
return True
|
||||
pins = mods[0].pins
|
||||
for mod in mods[1:]:
|
||||
if pins != mod.pins:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_alias_in_path(self, paths, int_net, mod, exclusion_set=None):
|
||||
"""Finds a single alias for the int_net in given paths.
|
||||
More or less hits cause an error"""
|
||||
net_found = False
|
||||
for path in paths:
|
||||
aliases = self.sram.find_aliases(self.sram_spc_name, self.pins, path, int_net, mod, exclusion_set)
|
||||
if net_found and len(aliases) >= 1:
|
||||
debug.error('Found multiple paths with {} net.'.format(int_net),1)
|
||||
elif len(aliases) > 1:
|
||||
debug.error('Found multiple {} nets in single path.'.format(int_net),1)
|
||||
elif not net_found and len(aliases) == 1:
|
||||
path_net_name = aliases[0]
|
||||
net_found = True
|
||||
if not net_found:
|
||||
debug.error("Could not find {} net in timing paths.".format(int_net),1)
|
||||
|
||||
return path_net_name
|
||||
|
||||
def check_arguments(self):
|
||||
"""Checks if arguments given for write_stimulus() meets requirements"""
|
||||
try:
|
||||
|
|
@ -171,12 +339,8 @@ class delay(simulation):
|
|||
|
||||
# instantiate the sram
|
||||
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||
self.stim.inst_sram(sram=self.sram,
|
||||
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
||||
abits=self.addr_size,
|
||||
dbits=self.word_size,
|
||||
sram_name=self.name)
|
||||
self.stim.inst_model(pins=self.pins,
|
||||
model_name=self.sram.name)
|
||||
self.sf.write("\n* SRAM output loads\n")
|
||||
for port in self.read_ports:
|
||||
for i in range(self.word_size):
|
||||
|
|
@ -286,26 +450,35 @@ class delay(simulation):
|
|||
"""Checks the measurement object and calls respective function for related measurement inputs."""
|
||||
meas_type = type(measure_obj)
|
||||
if meas_type is delay_measure or meas_type is slew_measure:
|
||||
return self.get_delay_measure_variants(port, measure_obj)
|
||||
variant_tuple = self.get_delay_measure_variants(port, measure_obj)
|
||||
elif meas_type is power_measure:
|
||||
return self.get_power_measure_variants(port, measure_obj, "read")
|
||||
variant_tuple = self.get_power_measure_variants(port, measure_obj, "read")
|
||||
elif meas_type is voltage_when_measure:
|
||||
return self.get_volt_when_measure_variants(port, measure_obj)
|
||||
variant_tuple = self.get_volt_when_measure_variants(port, measure_obj)
|
||||
elif meas_type is voltage_at_measure:
|
||||
variant_tuple = self.get_volt_at_measure_variants(port, measure_obj)
|
||||
else:
|
||||
debug.error("Input function not defined for measurement type={}".format(meas_type))
|
||||
#Removes port input from any object which does not use it. This shorthand only works if
|
||||
#the measurement has port as the last input. Could be implemented by measurement type or
|
||||
#remove entirely from measurement classes.
|
||||
if not measure_obj.has_port:
|
||||
variant_tuple = variant_tuple[:-1]
|
||||
return variant_tuple
|
||||
|
||||
def get_delay_measure_variants(self, port, delay_obj):
|
||||
"""Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)"""
|
||||
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
|
||||
#vdd is arguably constant as that is true for a single lib file.
|
||||
if delay_obj.meta_str == "read0":
|
||||
if delay_obj.meta_str == sram_op.READ_ZERO:
|
||||
#Falling delay are measured starting from neg. clk edge. Delay adjusted to that.
|
||||
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
|
||||
elif delay_obj.meta_str == "read1":
|
||||
elif delay_obj.meta_str == sram_op.READ_ONE:
|
||||
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
|
||||
else:
|
||||
debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1)
|
||||
|
||||
#These measurements have there time further delayed to the neg. edge of the clock.
|
||||
if delay_obj.meta_add_delay:
|
||||
meas_cycle_delay += self.period/2
|
||||
|
||||
|
|
@ -319,10 +492,24 @@ class delay(simulation):
|
|||
|
||||
return (t_initial, t_final, port)
|
||||
|
||||
def get_volt_when_measure_variants(self, port, power_obj):
|
||||
def get_volt_at_measure_variants(self, port, volt_meas):
|
||||
"""Get the measurement values that can either vary port to port (time delays)"""
|
||||
#Only checking 0 value reads for now.
|
||||
t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]]
|
||||
if volt_meas.meta_str == sram_op.READ_ZERO:
|
||||
#Falling delay are measured starting from neg. clk edge. Delay adjusted to that.
|
||||
meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]]
|
||||
elif volt_meas.meta_str == sram_op.READ_ONE:
|
||||
meas_cycle = self.cycle_times[self.measure_cycles[port][volt_meas.meta_str]]
|
||||
else:
|
||||
debug.error("Unrecognised delay Index={}".format(volt_meas.meta_str),1)
|
||||
#Measurement occurs at the end of the period -> current period start + period
|
||||
at_time = meas_cycle+self.period
|
||||
return (at_time, port)
|
||||
|
||||
def get_volt_when_measure_variants(self, port, volt_meas):
|
||||
"""Get the measurement values that can either vary port to port (time delays)"""
|
||||
#Only checking 0 value reads for now.
|
||||
t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port][sram_op.READ_ZERO]]
|
||||
|
||||
return (t_trig, self.vdd_voltage, port)
|
||||
|
||||
|
|
@ -331,7 +518,8 @@ class delay(simulation):
|
|||
Write the measure statements to quantify the delay and power results for a read port.
|
||||
"""
|
||||
# add measure statements for delays/slews
|
||||
for measure in self.read_meas_objs+self.bitline_delay_objs:
|
||||
for meas_list in self.read_meas_lists:
|
||||
for measure in meas_list:
|
||||
measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
|
||||
measure.write_measure(self.stim, measure_variant_inp_tuple)
|
||||
|
||||
|
|
@ -348,7 +536,8 @@ class delay(simulation):
|
|||
Write the measure statements to quantify the power results for a write port.
|
||||
"""
|
||||
# add measure statements for power
|
||||
for measure in self.write_meas_objs:
|
||||
for meas_list in self.write_meas_lists:
|
||||
for measure in meas_list:
|
||||
measure_variant_inp_tuple = self.get_write_measure_variants(port, measure)
|
||||
measure.write_measure(self.stim, measure_variant_inp_tuple)
|
||||
|
||||
|
|
@ -469,6 +658,7 @@ class delay(simulation):
|
|||
#Sanity Check
|
||||
debug.check(self.period > 0, "Target simulation period non-positive")
|
||||
|
||||
sim_passed = True
|
||||
result = [{} for i in self.all_ports]
|
||||
# Checking from not data_value to data_value
|
||||
self.write_delay_stimulus()
|
||||
|
|
@ -478,26 +668,28 @@ class delay(simulation):
|
|||
#Loop through all targeted ports and collect delays and powers.
|
||||
#Too much duplicate code here. Try reducing
|
||||
for port in self.targ_read_ports:
|
||||
debug.info(2, "Check delay values for port {}".format(port))
|
||||
debug.info(2, "Checking delay values for port {}".format(port))
|
||||
read_port_dict = {}
|
||||
#Get measurements from output file
|
||||
for measure in self.read_meas_objs:
|
||||
for measure in self.read_lib_meas:
|
||||
read_port_dict[measure.name] = measure.retrieve_measure(port=port)
|
||||
|
||||
#Check sen timing, then bitlines, then general measurements.
|
||||
if not self.check_sen_measure(port):
|
||||
return (False,{})
|
||||
success = self.check_debug_measures(port)
|
||||
success = success and self.check_bit_measures()
|
||||
#Check timing for read ports. Power is only checked if it was read correctly
|
||||
if not self.check_valid_delays(read_port_dict):
|
||||
if not self.check_valid_delays(read_port_dict) or not success:
|
||||
return (False,{})
|
||||
if not check_dict_values_is_float(read_port_dict):
|
||||
debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad.
|
||||
|
||||
result[port].update(read_port_dict)
|
||||
|
||||
bitline_delay_dict = self.evaluate_bitline_delay(port)
|
||||
result[port].update(bitline_delay_dict)
|
||||
|
||||
for port in self.targ_write_ports:
|
||||
write_port_dict = {}
|
||||
for measure in self.write_meas_objs:
|
||||
for measure in self.write_lib_meas:
|
||||
write_port_dict[measure.name] = measure.retrieve_measure(port=port)
|
||||
|
||||
if not check_dict_values_is_float(write_port_dict):
|
||||
|
|
@ -505,18 +697,94 @@ class delay(simulation):
|
|||
result[port].update(write_port_dict)
|
||||
|
||||
# The delay is from the negative edge for our SRAM
|
||||
return (True,result)
|
||||
return (sim_passed,result)
|
||||
|
||||
def evaluate_bitline_delay(self, port):
|
||||
"""Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function."""
|
||||
bl_delay_meas_dict = {}
|
||||
values_added = 0 #For error checking
|
||||
for measure in self.bitline_delay_objs:
|
||||
bl_delay_val = measure.retrieve_measure(port=port)
|
||||
if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error.
|
||||
debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1)
|
||||
bl_delay_meas_dict[measure.name] = bl_delay_val
|
||||
return bl_delay_meas_dict
|
||||
def check_sen_measure(self, port):
|
||||
"""Checks that the sen occurred within a half-period"""
|
||||
sen_val = self.sen_meas.retrieve_measure(port=port)
|
||||
debug.info(2,"S_EN delay={} ns".format(sen_val))
|
||||
if self.sen_meas.meta_add_delay:
|
||||
max_delay = self.period/2
|
||||
else:
|
||||
max_delay = self.period
|
||||
return not (type(sen_val) != float or sen_val > max_delay)
|
||||
|
||||
def check_debug_measures(self, port):
|
||||
"""Debug measures that indicate special conditions."""
|
||||
#Currently, only check if the opposite than intended value was read during
|
||||
# the read cycles i.e. neither of these measurements should pass.
|
||||
success = True
|
||||
#FIXME: these checks need to be re-done to be more robust against possible errors
|
||||
bl_vals = {}
|
||||
br_vals = {}
|
||||
for meas in self.bitline_volt_meas:
|
||||
val = meas.retrieve_measure(port=port)
|
||||
if self.bl_name == meas.targ_name_no_port:
|
||||
bl_vals[meas.meta_str] = val
|
||||
elif self.br_name == meas.targ_name_no_port:
|
||||
br_vals[meas.meta_str] = val
|
||||
|
||||
debug.info(2,"{}={}".format(meas.name,val))
|
||||
|
||||
bl_check = False
|
||||
for meas in self.debug_volt_meas:
|
||||
val = meas.retrieve_measure(port=port)
|
||||
debug.info(2,"{}={}".format(meas.name, val))
|
||||
if type(val) != float:
|
||||
continue
|
||||
|
||||
if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage*.1:
|
||||
success = False
|
||||
debug.info(1, "Debug measurement failed. Value {}v was read on read 1 cycle.".format(val))
|
||||
bl_check = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE])
|
||||
elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage*.9:
|
||||
success = False
|
||||
debug.info(1, "Debug measurement failed. Value {}v was read on read 0 cycle.".format(val))
|
||||
bl_check = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE])
|
||||
|
||||
#If the bitlines have a correct value while the output does not then that is a
|
||||
#sen error. FIXME: there are other checks that can be done to solidfy this conclusion.
|
||||
if bl_check:
|
||||
debug.error("Sense amp enable timing error. Increase the delay chain through the configuration file.",1)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def check_bit_measures(self):
|
||||
"""Checks the measurements which represent the internal storage voltages
|
||||
at the end of the read cycle."""
|
||||
success = True
|
||||
for polarity, meas_list in self.bit_meas.items():
|
||||
for meas in meas_list:
|
||||
val = meas.retrieve_measure()
|
||||
debug.info(2,"{}={}".format(meas.name, val))
|
||||
if type(val) != float:
|
||||
continue
|
||||
meas_cycle = meas.meta_str
|
||||
#Loose error conditions. Assume it's not metastable but account for noise during reads.
|
||||
if (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.NONINVERTING) or\
|
||||
(meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.INVERTING):
|
||||
success = val < self.vdd_voltage/2
|
||||
elif (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.INVERTING) or\
|
||||
(meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.NONINVERTING):
|
||||
success = val > self.vdd_voltage/2
|
||||
if not success:
|
||||
debug.info(1,("Wrong value detected on probe bit during read cycle. "
|
||||
"Check writes and control logic for bugs.\n measure={}, op={}, "
|
||||
"bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name,val))
|
||||
return success
|
||||
|
||||
def check_bitline_meas(self, v_discharged_bl, v_charged_bl):
|
||||
"""Checks the value of the discharging bitline. Confirms s_en timing errors.
|
||||
Returns true if the bitlines are at there expected value."""
|
||||
#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
|
||||
#10% vdd difference between the bitlines. Both need to fail to be considered a s_en error.
|
||||
min_dicharge = v_discharged_bl < self.vdd_voltage*0.9
|
||||
min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage*0.1
|
||||
|
||||
debug.info(1,"min_dicharge={}, min_diff={}".format(min_dicharge,min_diff))
|
||||
return (min_dicharge and min_diff)
|
||||
|
||||
def run_power_simulation(self):
|
||||
"""
|
||||
|
|
@ -563,7 +831,8 @@ class delay(simulation):
|
|||
delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh)
|
||||
slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh)
|
||||
half_period = self.period/2 #high-to-low delays start at neg. clk edge, so they need to be less than half_period
|
||||
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)>self.period or abs(slew_hl)>half_period or abs(slew_lh)>self.period \
|
||||
or delay_hl<0 or delay_lh<0 or slew_hl<0 or slew_lh<0:
|
||||
debug.info(2,"UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str,
|
||||
delays_str,
|
||||
slews_str))
|
||||
|
|
@ -712,31 +981,24 @@ class delay(simulation):
|
|||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
def analysis_init(self, probe_address, probe_data):
|
||||
"""Sets values which are dependent on the data address/bit being tested."""
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
|
||||
|
||||
def analyze(self,probe_address, probe_data, slews, loads):
|
||||
def analyze(self, probe_address, probe_data, slews, loads):
|
||||
"""
|
||||
Main function to characterize an SRAM for a table. Computes both delay and power characterization.
|
||||
"""
|
||||
#Dict to hold all characterization values
|
||||
char_sram_data = {}
|
||||
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.create_signal_names()
|
||||
self.create_measurement_names()
|
||||
self.create_measurement_objects()
|
||||
self.analysis_init(probe_address, probe_data)
|
||||
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
# This is for debugging a full simulation
|
||||
# debug.info(0,"Debug simulation running...")
|
||||
# target_period=50.0
|
||||
# feasible_delay_lh=0.059083183
|
||||
# feasible_delay_hl=0.17953789
|
||||
# load=1.6728
|
||||
# slew=0.04
|
||||
# self.try_period(target_period, feasible_delay_lh, feasible_delay_hl)
|
||||
# sys.exit(1)
|
||||
|
||||
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
|
||||
feasible_delays = self.find_feasible_period()
|
||||
|
|
@ -826,7 +1088,7 @@ class delay(simulation):
|
|||
|
||||
self.add_write("W data 0 address {} to write value".format(self.probe_address),
|
||||
self.probe_address,data_zeros,write_port)
|
||||
self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1
|
||||
self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1
|
||||
|
||||
# This also ensures we will have a H->L transition on the next read
|
||||
self.add_read("R data 1 address {} to set DOUT caps".format(inverse_address),
|
||||
|
|
@ -834,14 +1096,14 @@ class delay(simulation):
|
|||
|
||||
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
|
||||
self.probe_address,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
|
||||
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1
|
||||
|
||||
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)",
|
||||
inverse_address,data_zeros)
|
||||
|
||||
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
||||
self.probe_address,data_ones,write_port)
|
||||
self.measure_cycles[write_port]["write1"] = len(self.cycle_times)-1
|
||||
self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1
|
||||
|
||||
self.add_write("W data 0 address {} to clear DIN caps".format(inverse_address),
|
||||
inverse_address,data_zeros,write_port)
|
||||
|
|
@ -852,7 +1114,7 @@ class delay(simulation):
|
|||
|
||||
self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address),
|
||||
self.probe_address,data_zeros,read_port)
|
||||
self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1
|
||||
self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times)-1
|
||||
|
||||
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))",
|
||||
self.probe_address,data_zeros)
|
||||
|
|
@ -908,7 +1170,6 @@ class delay(simulation):
|
|||
"""
|
||||
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0:
|
||||
debug.warning("Analytical characterization results are not supported for multiport.")
|
||||
self.create_signal_names()
|
||||
self.create_measurement_names()
|
||||
power = self.analytical_power(slews, loads)
|
||||
port_data = self.get_empty_measure_data_dict()
|
||||
|
|
|
|||
|
|
@ -203,11 +203,6 @@ class functional(simulation):
|
|||
#print("Binary Conversion: {} to {}".format(value, new_value))
|
||||
return new_value
|
||||
|
||||
def create_signal_names(self):
|
||||
self.addr_name = "A"
|
||||
self.din_name = "DIN"
|
||||
self.dout_name = "DOUT"
|
||||
|
||||
def write_functional_stimulus(self):
|
||||
""" Writes SPICE stimulus. """
|
||||
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
|
||||
|
|
@ -226,12 +221,8 @@ class functional(simulation):
|
|||
|
||||
#Instantiate the SRAM
|
||||
self.sf.write("\n* Instantiation of the SRAM\n")
|
||||
self.stim.inst_sram(sram=self.sram,
|
||||
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||
port_info=(len(self.all_ports), self.write_ports, self.read_ports),
|
||||
abits=self.addr_size,
|
||||
dbits=self.word_size,
|
||||
sram_name=self.name)
|
||||
self.stim.inst_model(pins=self.pins,
|
||||
model_name=self.sram.name)
|
||||
|
||||
# Add load capacitance to each of the read ports
|
||||
self.sf.write("\n* SRAM output loads\n")
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ from .charutils import *
|
|||
|
||||
class spice_measurement(ABC):
|
||||
"""Base class for spice stimulus measurements."""
|
||||
def __init__(self, measure_name, measure_scale=None):
|
||||
def __init__(self, measure_name, measure_scale=None, has_port=True):
|
||||
#Names must be unique for correct spice simulation, but not enforced here.
|
||||
self.name = measure_name
|
||||
self.measure_scale = measure_scale
|
||||
self.has_port = has_port #Needed for error checking
|
||||
#Some meta values used externally. variables are added here for consistency accross the objects
|
||||
self.meta_str = None
|
||||
self.meta_add_delay = False
|
||||
|
|
@ -35,18 +36,29 @@ class spice_measurement(ABC):
|
|||
measure_vals = self.get_measure_values(*input_tuple)
|
||||
measure_func(stim_obj, *measure_vals)
|
||||
|
||||
def retrieve_measure(self, port=""):
|
||||
def retrieve_measure(self, port=None):
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port))
|
||||
else:
|
||||
value = parse_spice_list("timing", "{0}".format(self.name.lower()))
|
||||
if type(value)!=float or self.measure_scale == None:
|
||||
return value
|
||||
else:
|
||||
return value*self.measure_scale
|
||||
|
||||
def port_error_check(self, port):
|
||||
if self.has_port and port == None:
|
||||
debug.error("Cannot retrieve measurement, port input was expected.",1)
|
||||
elif not self.has_port and port != None:
|
||||
debug.error("Unexpected port input received during measure retrieval.",1)
|
||||
|
||||
class delay_measure(spice_measurement):
|
||||
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
|
||||
|
||||
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale)
|
||||
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\
|
||||
trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
|
|
@ -56,10 +68,8 @@ class delay_measure(spice_measurement):
|
|||
"""Set the constants for this measurement: signal names, directions, and trigger scales"""
|
||||
self.trig_dir_str = trig_dir_str
|
||||
self.targ_dir_str = targ_dir_str
|
||||
|
||||
self.trig_val_of_vdd = trig_vdd
|
||||
self.targ_val_of_vdd = targ_vdd
|
||||
|
||||
self.trig_name_no_port = trig_name
|
||||
self.targ_name_no_port = targ_name
|
||||
|
||||
|
|
@ -67,6 +77,7 @@ class delay_measure(spice_measurement):
|
|||
|
||||
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
trig_val = self.trig_val_of_vdd * vdd_voltage
|
||||
targ_val = self.targ_val_of_vdd * vdd_voltage
|
||||
|
||||
|
|
@ -79,13 +90,12 @@ class delay_measure(spice_measurement):
|
|||
meas_name = self.name
|
||||
trig_name = self.trig_name_no_port
|
||||
targ_name = self.targ_name_no_port
|
||||
|
||||
return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td)
|
||||
|
||||
class slew_measure(delay_measure):
|
||||
|
||||
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale)
|
||||
def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(signal_name, slew_dir_str)
|
||||
|
||||
def set_meas_constants(self, signal_name, slew_dir_str):
|
||||
|
|
@ -101,7 +111,6 @@ class slew_measure(delay_measure):
|
|||
self.targ_val_of_vdd = 0.1
|
||||
else:
|
||||
debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1)
|
||||
|
||||
self.trig_name_no_port = signal_name
|
||||
self.targ_name_no_port = signal_name
|
||||
|
||||
|
|
@ -110,8 +119,8 @@ class slew_measure(delay_measure):
|
|||
class power_measure(spice_measurement):
|
||||
"""Generates a spice measurement for the average power between two time points."""
|
||||
|
||||
def __init__(self, measure_name, power_type="", measure_scale=None):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale)
|
||||
def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(power_type)
|
||||
|
||||
def get_measure_function(self):
|
||||
|
|
@ -124,6 +133,7 @@ class power_measure(spice_measurement):
|
|||
|
||||
def get_measure_values(self, t_initial, t_final, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
else:
|
||||
|
|
@ -133,8 +143,8 @@ class power_measure(spice_measurement):
|
|||
class voltage_when_measure(spice_measurement):
|
||||
"""Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
|
||||
|
||||
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale)
|
||||
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None, has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd)
|
||||
|
||||
def get_measure_function(self):
|
||||
|
|
@ -144,13 +154,12 @@ class voltage_when_measure(spice_measurement):
|
|||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
self.trig_dir_str = trig_dir_str
|
||||
self.trig_val_of_vdd = trig_vdd
|
||||
|
||||
self.trig_name_no_port = trig_name
|
||||
self.targ_name_no_port = targ_name
|
||||
|
||||
def get_measure_values(self, trig_td, vdd_voltage, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
#For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
|
|
@ -160,7 +169,33 @@ class voltage_when_measure(spice_measurement):
|
|||
meas_name = self.name
|
||||
trig_name = self.trig_name_no_port
|
||||
targ_name = self.targ_name_no_port
|
||||
|
||||
trig_voltage = self.trig_val_of_vdd*vdd_voltage
|
||||
|
||||
return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td)
|
||||
|
||||
class voltage_at_measure(spice_measurement):
|
||||
"""Generates a spice measurement to measure the voltage at a specific time.
|
||||
The time is considered variant with different periods."""
|
||||
|
||||
def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(targ_name)
|
||||
|
||||
def get_measure_function(self):
|
||||
return stimuli.gen_meas_find_voltage_at_time
|
||||
|
||||
def set_meas_constants(self, targ_name):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
self.targ_name_no_port = targ_name
|
||||
|
||||
def get_measure_values(self, time_at, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
#For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
else:
|
||||
meas_name = self.name
|
||||
targ_name = self.targ_name_no_port
|
||||
return (meas_name,targ_name,time_at)
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ class model_check(delay):
|
|||
self.bl_meas_name, self.bl_slew_name = "bl_measures", "bl_slews"
|
||||
self.power_name = "total_power"
|
||||
|
||||
def create_measurement_names(self):
|
||||
def create_measurement_names(self, port):
|
||||
"""Create measurement names. The names themselves currently define the type of measurement"""
|
||||
#Create delay measurement names
|
||||
wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
|
|
@ -49,7 +49,10 @@ class model_check(delay):
|
|||
else:
|
||||
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
|
||||
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
|
||||
if port not in self.sram.readonly_ports:
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
|
||||
else:
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names
|
||||
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
|
||||
|
||||
# if self.custom_delaychain:
|
||||
|
|
@ -65,45 +68,54 @@ class model_check(delay):
|
|||
else:
|
||||
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
|
||||
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
|
||||
if port not in self.sram.readonly_ports:
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
|
||||
else:
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names
|
||||
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
|
||||
|
||||
self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
|
||||
self.power_meas_names = ['read0_power']
|
||||
|
||||
def create_signal_names(self):
|
||||
def create_signal_names(self, port):
|
||||
"""Creates list of the signal names used in the spice file along the wl and sen paths.
|
||||
Names are re-harded coded here; i.e. the names are hardcoded in most of OpenRAM and are
|
||||
replicated here.
|
||||
"""
|
||||
delay.create_signal_names(self)
|
||||
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
|
||||
wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
|
||||
sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
|
||||
wl_en_driver_signals = ["Xsram.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
|
||||
sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())]
|
||||
if self.custom_delaychain:
|
||||
delay_chain_signal_names = []
|
||||
else:
|
||||
delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())]
|
||||
|
||||
self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\
|
||||
delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())]
|
||||
if len(self.sram.all_ports) > 1:
|
||||
port_format = '{}'
|
||||
else:
|
||||
port_format = ''
|
||||
self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\
|
||||
wl_en_driver_signals+\
|
||||
["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\
|
||||
["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\
|
||||
wl_driver_signals+\
|
||||
["Xsram.Xbank0.wl_{}".format(self.wordline_row)]
|
||||
pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"]
|
||||
["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)]
|
||||
pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]
|
||||
if port not in self.sram.readonly_ports:
|
||||
pre_delay_chain_names+= ["Xsram.Xcontrol{}.Xand2_rbl_in.zb_int".format('{}'), "Xsram.Xcontrol{}.rbl_in".format('{}')]
|
||||
|
||||
self.rbl_en_signal_names = pre_delay_chain_names+\
|
||||
delay_chain_signal_names+\
|
||||
["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"]
|
||||
["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')]
|
||||
|
||||
|
||||
self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\
|
||||
self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\
|
||||
sen_driver_signals+\
|
||||
["Xsram.s_en0"]
|
||||
["Xsram.s_en{}".format('{}')]
|
||||
|
||||
dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
|
||||
self.bl_signal_names = ["Xsram.Xbank0.wl_{}".format(self.wordline_row),\
|
||||
"Xsram.Xbank0.bl_{}".format(self.bitline_column),\
|
||||
self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\
|
||||
"Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\
|
||||
dout_name]
|
||||
|
||||
def create_measurement_objects(self):
|
||||
|
|
@ -369,17 +381,18 @@ class model_check(delay):
|
|||
errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
|
||||
debug.info(1, "Errors:\n{}\n".format(errors))
|
||||
|
||||
def analyze(self, probe_address, probe_data, slews, loads):
|
||||
def analyze(self, probe_address, probe_data, slews, loads, port):
|
||||
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
|
||||
self.load=max(loads)
|
||||
self.slew=max(slews)
|
||||
self.set_probe(probe_address, probe_data)
|
||||
self.create_signal_names()
|
||||
self.create_measurement_names()
|
||||
self.create_signal_names(port)
|
||||
self.create_measurement_names(port)
|
||||
self.create_measurement_objects()
|
||||
data_dict = {}
|
||||
|
||||
read_port = self.read_ports[0] #only test the first read port
|
||||
read_port = port
|
||||
self.targ_read_ports = [read_port]
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
debug.info(1,"Model test: corner {}".format(self.corner))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,19 @@ class simulation():
|
|||
self.v_low = tech.spice["v_threshold_typical"]
|
||||
self.gnd_voltage = 0
|
||||
|
||||
def create_signal_names(self):
|
||||
self.addr_name = "A"
|
||||
self.din_name = "DIN"
|
||||
self.dout_name = "DOUT"
|
||||
self.pins = self.gen_pin_names(port_signal_names=(self.addr_name,self.din_name,self.dout_name),
|
||||
port_info=(len(self.all_ports),self.write_ports,self.read_ports),
|
||||
abits=self.addr_size,
|
||||
dbits=self.word_size)
|
||||
debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \
|
||||
do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,self.pins))
|
||||
#This is TODO once multiport control has been finalized.
|
||||
#self.control_name = "CSB"
|
||||
|
||||
def set_stimulus_variables(self):
|
||||
# Clock signals
|
||||
self.cycle_times = []
|
||||
|
|
@ -230,3 +243,38 @@ class simulation():
|
|||
t_current+self.period)
|
||||
return comment
|
||||
|
||||
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
|
||||
"""Creates the pins names of the SRAM based on the no. of ports."""
|
||||
#This may seem redundant as the pin names are already defined in the sram. However, it is difficult
|
||||
#to extract the functionality from the names, so they are recreated. As the order is static, changing
|
||||
#the order of the pin names will cause issues here.
|
||||
pin_names = []
|
||||
(addr_name, din_name, dout_name) = port_signal_names
|
||||
(total_ports, write_index, read_index) = port_info
|
||||
|
||||
for write_input in write_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(din_name,write_input, i))
|
||||
|
||||
for port in range(total_ports):
|
||||
for i in range(abits):
|
||||
pin_names.append("{0}{1}_{2}".format(addr_name,port,i))
|
||||
|
||||
#Control signals not finalized.
|
||||
for port in range(total_ports):
|
||||
pin_names.append("CSB{0}".format(port))
|
||||
for port in range(total_ports):
|
||||
if (port in read_index) and (port in write_index):
|
||||
pin_names.append("WEB{0}".format(port))
|
||||
|
||||
for port in range(total_ports):
|
||||
pin_names.append("{0}{1}".format(tech.spice["clk"], port))
|
||||
|
||||
for read_output in read_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i))
|
||||
|
||||
pin_names.append("{0}".format(tech.spice["vdd_name"]))
|
||||
pin_names.append("{0}".format(tech.spice["gnd_name"]))
|
||||
return pin_names
|
||||
|
||||
|
|
|
|||
|
|
@ -36,51 +36,15 @@ class stimuli():
|
|||
(self.process, self.voltage, self.temperature) = corner
|
||||
self.device_models = tech.spice["fet_models"][self.process]
|
||||
|
||||
def inst_sram(self, sram, port_signal_names, port_info, abits, dbits, sram_name):
|
||||
self.sram_name = "Xsram"
|
||||
|
||||
def inst_sram(self, pins, inst_name):
|
||||
""" Function to instatiate an SRAM subckt. """
|
||||
pin_names = self.gen_pin_names(port_signal_names, port_info, abits, dbits)
|
||||
#Only checking length. This should check functionality as well (TODO) and/or import that information from the SRAM
|
||||
debug.check(len(sram.pins) == len(pin_names), "Number of pins generated for characterization do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(sram.pins,pin_names))
|
||||
|
||||
self.sf.write("Xsram ")
|
||||
for pin in pin_names:
|
||||
self.sf.write("{} ".format(self.sram_name))
|
||||
for pin in self.sram_pins:
|
||||
self.sf.write("{0} ".format(pin))
|
||||
self.sf.write("{0}\n".format(sram_name))
|
||||
|
||||
def gen_pin_names(self, port_signal_names, port_info, abits, dbits):
|
||||
"""Creates the pins names of the SRAM based on the no. of ports."""
|
||||
#This may seem redundant as the pin names are already defined in the sram. However, it is difficult to extract the
|
||||
#functionality from the names, so they are recreated. As the order is static, changing the order of the pin names
|
||||
#will cause issues here.
|
||||
pin_names = []
|
||||
(addr_name, din_name, dout_name) = port_signal_names
|
||||
(total_ports, write_index, read_index) = port_info
|
||||
|
||||
for write_input in write_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(din_name,write_input, i))
|
||||
|
||||
for port in range(total_ports):
|
||||
for i in range(abits):
|
||||
pin_names.append("{0}{1}_{2}".format(addr_name,port,i))
|
||||
|
||||
#Control signals not finalized.
|
||||
for port in range(total_ports):
|
||||
pin_names.append("CSB{0}".format(port))
|
||||
for port in range(total_ports):
|
||||
if (port in read_index) and (port in write_index):
|
||||
pin_names.append("WEB{0}".format(port))
|
||||
|
||||
for port in range(total_ports):
|
||||
pin_names.append("{0}{1}".format(tech.spice["clk"], port))
|
||||
|
||||
for read_output in read_index:
|
||||
for i in range(dbits):
|
||||
pin_names.append("{0}{1}_{2}".format(dout_name,read_output, i))
|
||||
|
||||
pin_names.append("{0}".format(self.vdd_name))
|
||||
pin_names.append("{0}".format(self.gnd_name))
|
||||
return pin_names
|
||||
self.sf.write("{0}\n".format(inst_name))
|
||||
|
||||
def inst_model(self, pins, model_name):
|
||||
""" Function to instantiate a generic model with a set of pins """
|
||||
|
|
@ -233,6 +197,13 @@ class stimuli():
|
|||
trig_dir,
|
||||
trig_td))
|
||||
|
||||
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name,
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
def gen_meas_power(self, meas_name, t_initial, t_final):
|
||||
""" Creates the .meas statement for the measurement of avg power """
|
||||
# power mea cmd is different in different spice:
|
||||
|
|
|
|||
|
|
@ -405,12 +405,6 @@ class bank(design.design):
|
|||
def add_modules(self):
|
||||
""" Add all the modules using the class loader """
|
||||
|
||||
|
||||
self.bitcell_array = factory.create(module_type="bitcell_array",
|
||||
cols=self.num_cols,
|
||||
rows=self.num_rows)
|
||||
self.add_mod(self.bitcell_array)
|
||||
|
||||
# create arrays of bitline and bitline_bar names for read, write, or all ports
|
||||
self.bitcell = factory.create(module_type="bitcell")
|
||||
self.bl_names = self.bitcell.list_all_bl_names()
|
||||
|
|
@ -418,6 +412,11 @@ class bank(design.design):
|
|||
self.wl_names = self.bitcell.list_all_wl_names()
|
||||
self.bitline_names = self.bitcell.list_all_bitline_names()
|
||||
|
||||
self.bitcell_array = factory.create(module_type="bitcell_array",
|
||||
cols=self.num_cols,
|
||||
rows=self.num_rows)
|
||||
self.add_mod(self.bitcell_array)
|
||||
|
||||
self.precharge_array = []
|
||||
for port in self.all_ports:
|
||||
if port in self.read_ports:
|
||||
|
|
@ -1285,3 +1284,13 @@ class bank(design.design):
|
|||
"""Get the relative capacitance of all the sense amp enable connections in the bank"""
|
||||
#Current bank only uses sen as an enable for the sense amps.
|
||||
return self.sense_amp_array.get_en_cin()
|
||||
|
||||
def graph_exclude_precharge(self):
|
||||
"""Precharge adds a loop between bitlines, can be excluded to reduce complexity"""
|
||||
for inst in self.precharge_array_inst:
|
||||
if inst != None:
|
||||
self.graph_inst_exclude.add(inst)
|
||||
|
||||
def get_cell_name(self, inst_name, row, col):
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
return self.bitcell_array_inst.mod.get_cell_name(inst_name+'.x'+self.bitcell_array_inst.name, row, col)
|
||||
|
|
@ -156,7 +156,7 @@ class bitcell_array(design.design):
|
|||
# Dynamic Power from Bitline
|
||||
bl_wire = self.gen_bl_wire()
|
||||
cell_load = 2 * bl_wire.return_input_cap()
|
||||
bl_swing = parameter["rbl_height_percentage"]
|
||||
bl_swing = OPTS.rbl_delay_percentage
|
||||
freq = spice["default_event_rate"]
|
||||
bitline_dynamic = self.calc_dynamic_power(corner, cell_load, freq, swing=bl_swing)
|
||||
|
||||
|
|
@ -202,3 +202,16 @@ class bitcell_array(design.design):
|
|||
bitcell_wl_cin = self.cell.get_wl_cin()
|
||||
total_cin = bitcell_wl_cin * self.column_size
|
||||
return total_cin
|
||||
|
||||
def graph_exclude_bits(self, targ_row, targ_col):
|
||||
"""Excludes bits in column from being added to graph except target"""
|
||||
#Function is not robust with column mux configurations
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
if row == targ_row and col == targ_col:
|
||||
continue
|
||||
self.graph_inst_exclude.add(self.cell_inst[row,col])
|
||||
|
||||
def get_cell_name(self, inst_name, row, col):
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
return inst_name+'.x'+self.cell_inst[row,col].name, self.cell_inst[row,col]
|
||||
|
|
@ -144,10 +144,10 @@ class control_logic(design.design):
|
|||
self.delay_chain_resized = False
|
||||
c = reload(__import__(OPTS.replica_bitline))
|
||||
replica_bitline = getattr(c, OPTS.replica_bitline)
|
||||
bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"]))
|
||||
bitcell_loads = int(math.ceil(self.num_rows * OPTS.rbl_delay_percentage))
|
||||
#Use a model to determine the delays with that heuristic
|
||||
if OPTS.use_tech_delay_chain_size: #Use tech parameters if set.
|
||||
fanout_list = parameter["static_fanout_list"]
|
||||
fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage]
|
||||
debug.info(1, "Using tech parameters to size delay chain: fanout_list={}".format(fanout_list))
|
||||
self.replica_bitline = factory.create(module_type="replica_bitline",
|
||||
delay_fanout_list=fanout_list,
|
||||
|
|
@ -971,3 +971,7 @@ class control_logic(design.design):
|
|||
if self.port_type == 'rw':
|
||||
total_cin +=self.and2.get_cin()
|
||||
return total_cin
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
"""Exclude dffs from graph as they do not represent critical path"""
|
||||
self.graph_inst_exclude.add(self.ctrl_dff_inst)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class dff(design.design):
|
|||
"""
|
||||
|
||||
pin_names = ["D", "Q", "clk", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("dff", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "dff", GDS["unit"])
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ class dff(design.design):
|
|||
self.width = dff.width
|
||||
self.height = dff.height
|
||||
self.pin_map = dff.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Returns dynamic and leakage power. Results in nW"""
|
||||
|
|
@ -57,3 +59,7 @@ class dff(design.design):
|
|||
#Calculated in the tech file by summing the widths of all the gates and dividing by the minimum width.
|
||||
return parameter["dff_clk_cin"]
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
|
|
@ -624,3 +624,4 @@ class hierarchical_decoder(design.design):
|
|||
else:
|
||||
pre = self.pre3_8
|
||||
return pre.input_load()
|
||||
|
||||
|
|
|
|||
|
|
@ -115,3 +115,4 @@ class precharge_array(design.design):
|
|||
#Assume single port
|
||||
precharge_en_cin = self.pc_cell.get_en_cin()
|
||||
return precharge_en_cin*self.columns
|
||||
|
||||
|
|
@ -25,6 +25,8 @@ class replica_bitline(design.design):
|
|||
|
||||
self.bitcell_loads = bitcell_loads
|
||||
self.delay_fanout_list = delay_fanout_list
|
||||
if len(delay_fanout_list) == 0 or len(delay_fanout_list)%2 == 1:
|
||||
debug.error('Delay chain must contain an even amount of stages to maintain polarity.',1)
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ class sense_amp(design.design):
|
|||
"""
|
||||
|
||||
pin_names = ["bl", "br", "dout", "en", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ class sense_amp(design.design):
|
|||
self.width = sense_amp.width
|
||||
self.height = sense_amp.height
|
||||
self.pin_map = sense_amp.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def input_load(self):
|
||||
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
|
|
@ -59,3 +61,15 @@ class sense_amp(design.design):
|
|||
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
|
||||
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||
return 2*pmos_cin + nmos_cin
|
||||
|
||||
def get_enable_name(self):
|
||||
"""Returns name used for enable net"""
|
||||
#FIXME: A better programmatic solution to designate pins
|
||||
enable_name = "en"
|
||||
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
|
||||
return enable_name
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
|
|
@ -17,7 +17,8 @@ class tri_gate(design.design):
|
|||
netlist should be available in the technology library.
|
||||
"""
|
||||
|
||||
pin_names = ["in", "en", "en_bar", "out", "gnd", "vdd"]
|
||||
pin_names = ["in", "out", "en", "en_bar", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("tri_gate", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "tri_gate", GDS["unit"])
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ class tri_gate(design.design):
|
|||
self.width = tri_gate.width
|
||||
self.height = tri_gate.height
|
||||
self.pin_map = tri_gate.pin_map
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def analytical_delay(self, corner, slew, load=0.0):
|
||||
from tech import spice
|
||||
|
|
@ -49,3 +51,6 @@ class tri_gate(design.design):
|
|||
def input_load(self):
|
||||
return 9*spice["min_tx_gate_c"]
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -18,7 +18,8 @@ class write_driver(design.design):
|
|||
the technology library.
|
||||
"""
|
||||
|
||||
pin_names = ["din", "bl", "br", "en", "gnd", "vdd"]
|
||||
pin_names = ["din", "bl", "br", "en", "vdd", "gnd"]
|
||||
type_list = ["INPUT", "OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
(width,height) = utils.get_libcell_size("write_driver", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "write_driver", GDS["unit"])
|
||||
|
||||
|
|
@ -29,9 +30,13 @@ class write_driver(design.design):
|
|||
self.width = write_driver.width
|
||||
self.height = write_driver.height
|
||||
self.pin_map = write_driver.pin_map
|
||||
|
||||
self.add_pin_types(self.type_list)
|
||||
|
||||
def get_w_en_cin(self):
|
||||
"""Get the relative capacitance of a single input"""
|
||||
# This is approximated from SCMOS. It has roughly 5 3x transistor gates.
|
||||
return 5*3
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -47,8 +47,13 @@ class options(optparse.Values):
|
|||
###################
|
||||
# Optimization options
|
||||
###################
|
||||
# Uses the delay chain size in the tech.py file rather automatic sizing.
|
||||
rbl_delay_percentage = .5 #Approximate percentage of delay compared to bitlines
|
||||
|
||||
# Allow manual adjustment of the delay chain over automatic
|
||||
use_tech_delay_chain_size = False
|
||||
delay_chain_stages = 4
|
||||
delay_chain_fanout_per_stage = 3
|
||||
|
||||
|
||||
|
||||
###################
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class pand2(pgate.pgate):
|
|||
This is a simple buffer used for driving loads.
|
||||
"""
|
||||
def __init__(self, name, size=1, height=None):
|
||||
debug.info(1, "reating pnand2 {}".format(name))
|
||||
debug.info(1, "Creating pnand2 {}".format(name))
|
||||
self.add_comment("size: {}".format(size))
|
||||
|
||||
self.size = size
|
||||
|
|
|
|||
|
|
@ -246,4 +246,3 @@ class pgate(design.design):
|
|||
# offset=implant_offset,
|
||||
# width=implant_width,
|
||||
# height=implant_height)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ class pinv(pgate.pgate):
|
|||
self.beta = beta
|
||||
self.route_output = False
|
||||
|
||||
# Creates the netlist and layout
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
|
||||
def create_netlist(self):
|
||||
|
|
@ -60,7 +59,9 @@ class pinv(pgate.pgate):
|
|||
|
||||
def add_pins(self):
|
||||
""" Adds pins for spice netlist """
|
||||
self.add_pin_list(["A", "Z", "vdd", "gnd"])
|
||||
pin_list = ["A", "Z", "vdd", "gnd"]
|
||||
dir_list = ['INPUT', 'OUTPUT', 'POWER', 'GROUND']
|
||||
self.add_pin_list(pin_list, dir_list)
|
||||
|
||||
|
||||
def determine_tx_mults(self):
|
||||
|
|
@ -281,7 +282,8 @@ class pinv(pgate.pgate):
|
|||
return transition_prob*(c_load + c_para)
|
||||
|
||||
def get_cin(self):
|
||||
"""Return the capacitance of the gate connection in generic capacitive units relative to the minimum width of a transistor"""
|
||||
"""Return the capacitance of the gate connection in generic capacitive
|
||||
units relative to the minimum width of a transistor"""
|
||||
return self.nmos_size + self.pmos_size
|
||||
|
||||
def get_stage_effort(self, cout, inp_is_rise=True):
|
||||
|
|
@ -289,4 +291,13 @@ class pinv(pgate.pgate):
|
|||
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.
|
||||
"""
|
||||
parasitic_delay = 1
|
||||
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
return logical_effort.logical_effort(self.name,
|
||||
self.size,
|
||||
self.get_cin(),
|
||||
cout,
|
||||
parasitic_delay,
|
||||
not inp_is_rise)
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -38,6 +38,8 @@ class pnand2(pgate.pgate):
|
|||
# Creates the netlist and layout
|
||||
pgate.pgate.__init__(self, name, height)
|
||||
|
||||
#For characterization purposes only
|
||||
#self.exclude_nmos_from_graph()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
|
|
@ -58,7 +60,9 @@ class pnand2(pgate.pgate):
|
|||
|
||||
def add_pins(self):
|
||||
""" Adds pins for spice netlist """
|
||||
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
||||
pin_list = ["A", "B", "Z", "vdd", "gnd"]
|
||||
dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND']
|
||||
self.add_pin_list(pin_list, dir_list)
|
||||
|
||||
|
||||
def add_ptx(self):
|
||||
|
|
@ -267,3 +271,14 @@ class pnand2(pgate.pgate):
|
|||
"""
|
||||
parasitic_delay = 2
|
||||
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
|
||||
def exclude_nmos_from_graph(self):
|
||||
"""Exclude the nmos TXs from the graph to reduce complexity"""
|
||||
#The pull-down network has an internal net which causes 2 different in->out paths
|
||||
#Removing them simplifies generic path searching.
|
||||
self.graph_inst_exclude.add(self.nmos1_inst)
|
||||
self.graph_inst_exclude.add(self.nmos2_inst)
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -43,7 +43,9 @@ class pnand3(pgate.pgate):
|
|||
|
||||
def add_pins(self):
|
||||
""" Adds pins for spice netlist """
|
||||
self.add_pin_list(["A", "B", "C", "Z", "vdd", "gnd"])
|
||||
pin_list = ["A", "B", "C", "Z", "vdd", "gnd"]
|
||||
dir_list = ['INPUT', 'INPUT', 'INPUT', 'OUTPUT', 'POWER', 'GROUND']
|
||||
self.add_pin_list(pin_list, dir_list)
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
|
|
@ -278,3 +280,7 @@ class pnand3(pgate.pgate):
|
|||
"""
|
||||
parasitic_delay = 3
|
||||
return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise)
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -40,7 +40,9 @@ class pnor2(pgate.pgate):
|
|||
|
||||
def add_pins(self):
|
||||
""" Adds pins for spice netlist """
|
||||
self.add_pin_list(["A", "B", "Z", "vdd", "gnd"])
|
||||
pin_list = ["A", "B", "Z", "vdd", "gnd"]
|
||||
dir_list = ['INPUT', 'INPUT', 'OUTPUT', 'INOUT', 'INOUT']
|
||||
self.add_pin_list(pin_list, dir_list)
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pins()
|
||||
|
|
@ -238,3 +240,6 @@ class pnor2(pgate.pgate):
|
|||
transition_prob = spice["nor2_transition_prob"]
|
||||
return transition_prob*(c_load + c_para)
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
@ -68,7 +68,13 @@ class ptx(design.design):
|
|||
#self.DRC()
|
||||
|
||||
def create_netlist(self):
|
||||
self.add_pin_list(["D", "G", "S", "B"])
|
||||
pin_list = ["D", "G", "S", "B"]
|
||||
if self.tx_type=="nmos":
|
||||
body_dir = 'GROUND'
|
||||
else: #Assumed that the check for either pmos or nmos is done elsewhere.
|
||||
body_dir = 'POWER'
|
||||
dir_list = ['INOUT', 'INPUT', 'INOUT', body_dir]
|
||||
self.add_pin_list(pin_list, dir_list)
|
||||
|
||||
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
|
||||
# " ".join(self.pins)))
|
||||
|
|
@ -366,3 +372,8 @@ class ptx(design.design):
|
|||
def get_cin(self):
|
||||
"""Returns the relative gate cin of the tx"""
|
||||
return self.tx_width/drc("minwidth_tx")
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
||||
|
|
@ -309,3 +309,46 @@ class sram_1bank(sram_base):
|
|||
self.add_label(text=n,
|
||||
layer=pin.layer,
|
||||
offset=pin.center())
|
||||
|
||||
def graph_exclude_data_dff(self):
|
||||
"""Removes data dff from search graph. """
|
||||
#Data dffs are only for writing so are not useful for evaluating read delay.
|
||||
for inst in self.data_dff_insts:
|
||||
self.graph_inst_exclude.add(inst)
|
||||
|
||||
def graph_exclude_addr_dff(self):
|
||||
"""Removes data dff from search graph. """
|
||||
#Address is considered not part of the critical path, subjectively removed
|
||||
for inst in self.row_addr_dff_insts:
|
||||
self.graph_inst_exclude.add(inst)
|
||||
|
||||
if self.col_addr_dff:
|
||||
for inst in self.col_addr_dff_insts:
|
||||
self.graph_inst_exclude.add(inst)
|
||||
|
||||
def graph_exclude_ctrl_dffs(self):
|
||||
"""Exclude dffs for CSB, WEB, etc from graph"""
|
||||
#Insts located in control logic, exclusion function called here
|
||||
for inst in self.control_logic_insts:
|
||||
inst.mod.graph_exclude_dffs()
|
||||
|
||||
def get_sen_name(self, sram_name, port=0):
|
||||
"""Returns the s_en spice name."""
|
||||
#Naming scheme is hardcoded using this function, should be built into the
|
||||
#graph in someway.
|
||||
sen_name = "s_en{}".format(port)
|
||||
control_conns = self.get_conns(self.control_logic_insts[port])
|
||||
#Sanity checks
|
||||
if sen_name not in control_conns:
|
||||
debug.error("Signal={} not contained in control logic connections={}"\
|
||||
.format(sen_name, control_conns))
|
||||
if sen_name in self.pins:
|
||||
debug.error("Internal signal={} contained in port list. Name defined by the parent.")
|
||||
return "X{}.{}".format(sram_name, sen_name)
|
||||
|
||||
def get_cell_name(self, inst_name, row, col):
|
||||
"""Gets the spice name of the target bitcell."""
|
||||
#Sanity check in case it was forgotten
|
||||
if inst_name.find('x') != 0:
|
||||
inst_name = 'x'+inst_name
|
||||
return self.bank_inst.mod.get_cell_name(inst_name+'.x'+self.bank_inst.name, row, col)
|
||||
|
|
@ -543,6 +543,7 @@ class sram_base(design, verilog, lef):
|
|||
elif port in self.readwrite_ports:
|
||||
control_logic = self.control_logic_rw
|
||||
else:
|
||||
delays[port] = self.return_delay(0,0) #Write ports do not have a lib defined delay, marked as 0
|
||||
continue
|
||||
clk_to_wlen_delays = control_logic.analytical_delay(corner, slew, load)
|
||||
wlen_to_dout_delays = self.bank.analytical_delay(corner,slew,load,port) #port should probably be specified...
|
||||
|
|
|
|||
|
|
@ -44,17 +44,15 @@ class sram_factory:
|
|||
if hasattr(OPTS, module_type):
|
||||
# Retrieve the name from OPTS if it exists,
|
||||
# otherwise just use the name
|
||||
module_name = getattr(OPTS, module_type)
|
||||
else:
|
||||
module_name = module_type
|
||||
module_type = getattr(OPTS, module_type)
|
||||
|
||||
# Either retrieve the already loaded module or load it
|
||||
try:
|
||||
mod = self.modules[module_type]
|
||||
except KeyError:
|
||||
import importlib
|
||||
c = importlib.reload(__import__(module_name))
|
||||
mod = getattr(c, module_name)
|
||||
c = importlib.reload(__import__(module_type))
|
||||
mod = getattr(c, module_type)
|
||||
self.modules[module_type] = mod
|
||||
self.module_indices[module_type] = 0
|
||||
self.objects[module_type] = []
|
||||
|
|
@ -74,14 +72,28 @@ class sram_factory:
|
|||
# This is especially for library cells so that the spice and gds files can be found.
|
||||
if len(kwargs)>0:
|
||||
# Create a unique name and increment the index
|
||||
module_name = "{0}_{1}".format(module_name, self.module_indices[module_type])
|
||||
module_name = "{0}_{1}".format(module_type, self.module_indices[module_type])
|
||||
self.module_indices[module_type] += 1
|
||||
else:
|
||||
module_name = module_type
|
||||
|
||||
#debug.info(0, "New module: type={0} name={1} kwargs={2}".format(module_type,module_name,str(kwargs)))
|
||||
obj = mod(name=module_name,**kwargs)
|
||||
self.objects[module_type].append((kwargs,obj))
|
||||
return obj
|
||||
|
||||
def get_mods(self, module_type):
|
||||
"""Returns list of all objects of module name's type."""
|
||||
if hasattr(OPTS, module_type):
|
||||
# Retrieve the name from OPTS if it exists,
|
||||
# otherwise just use the input
|
||||
module_type = getattr(OPTS, module_type)
|
||||
try:
|
||||
mod_tuples = self.objects[module_type]
|
||||
mods = [mod for kwargs,mod in mod_tuples]
|
||||
except KeyError:
|
||||
mods = []
|
||||
return mods
|
||||
|
||||
# Make a factory
|
||||
factory = sram_factory()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
#Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||
#of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
#(acting for and on behalf of Oklahoma State University)
|
||||
#All rights reserved.
|
||||
#
|
||||
"""
|
||||
Run a regression test on a control_logic
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from testutils import header,openram_test
|
||||
import sys,os
|
||||
sys.path.append(os.path.join(sys.path[0],".."))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import debug
|
||||
|
||||
class control_logic_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
globals.init_openram("config_{0}".format(OPTS.tech_name))
|
||||
import control_logic
|
||||
import tech
|
||||
|
||||
# check control logic for multi-port
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell = "replica_pbitcell"
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw")
|
||||
self.local_check(a)
|
||||
|
||||
# OPTS.num_rw_ports = 0
|
||||
# OPTS.num_w_ports = 1
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w")
|
||||
self.local_check(a)
|
||||
|
||||
# OPTS.num_w_ports = 0
|
||||
# OPTS.num_r_ports = 1
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only read control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r")
|
||||
self.local_check(a)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main()
|
||||
|
|
@ -31,40 +31,6 @@ class control_logic_test(openram_test):
|
|||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=32)
|
||||
self.local_check(a)
|
||||
|
||||
# check control logic for multi-port
|
||||
OPTS.bitcell = "pbitcell"
|
||||
OPTS.replica_bitcell = "replica_pbitcell"
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 0
|
||||
|
||||
debug.info(1, "Testing sample for control_logic for multiport")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8)
|
||||
self.local_check(a)
|
||||
|
||||
# Check port specific control logic
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 0
|
||||
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="rw")
|
||||
self.local_check(a)
|
||||
|
||||
OPTS.num_rw_ports = 0
|
||||
OPTS.num_w_ports = 1
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only write control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="w")
|
||||
self.local_check(a)
|
||||
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 1
|
||||
debug.info(1, "Testing sample for control_logic for multiport, only read control logic")
|
||||
a = factory.create(module_type="control_logic", num_rows=128, words_per_row=1, word_size=8, port_type="r")
|
||||
self.local_check(a)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
# run the test from the command line
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
|
|
|
|||
|
|
@ -37,9 +37,16 @@ class timing_sram_test(openram_test):
|
|||
num_words=16,
|
||||
num_banks=1)
|
||||
c.words_per_row=1
|
||||
# c = sram_config(word_size=32,
|
||||
# num_words=256,
|
||||
# num_banks=1)
|
||||
# c.words_per_row=2
|
||||
# OPTS.use_tech_delay_chain_size = True
|
||||
c.recompute_sizes()
|
||||
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
#import sys
|
||||
#sys.exit(1)
|
||||
|
||||
tempspice = OPTS.openram_temp + "temp.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
|
|
|||
|
|
@ -70,11 +70,8 @@ class openram_test(unittest.TestCase):
|
|||
"""
|
||||
debug.info(1, "Finding feasible period for current test.")
|
||||
delay_obj.set_load_slew(load, slew)
|
||||
delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1))
|
||||
test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period.
|
||||
delay_obj.create_signal_names()
|
||||
delay_obj.create_measurement_names()
|
||||
delay_obj.create_measurement_objects()
|
||||
delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1))
|
||||
delay_obj.find_feasible_period_one_port(test_port)
|
||||
return delay_obj.period
|
||||
|
||||
|
|
|
|||
|
|
@ -354,9 +354,6 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no
|
|||
#Parameters related to sense amp enable timing and delay chain/RBL sizing
|
||||
parameter['le_tau'] = 2.25 #In pico-seconds.
|
||||
parameter['cap_relative_per_ff'] = 7.5 #Units of Relative Capacitance/ Femto-Farad
|
||||
parameter["static_delay_stages"] = 4
|
||||
parameter["static_fanout_per_stage"] = 3
|
||||
parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
|
||||
parameter["dff_clk_cin"] = 30.6 #relative capacitance
|
||||
parameter["6tcell_wl_cin"] = 3 #relative capacitance
|
||||
parameter["min_inv_para_delay"] = 2.4 #Tau delay units
|
||||
|
|
@ -364,7 +361,6 @@ parameter["sa_en_pmos_size"] = .72 #micro-meters
|
|||
parameter["sa_en_nmos_size"] = .27 #micro-meters
|
||||
parameter["sa_inv_pmos_size"] = .54 #micro-meters
|
||||
parameter["sa_inv_nmos_size"] = .27 #micro-meters
|
||||
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
|
||||
parameter['bitcell_drain_cap'] = 0.1 #In Femto-Farad, approximation of drain capacitance
|
||||
|
||||
###################################################
|
||||
|
|
|
|||
|
|
@ -321,16 +321,12 @@ spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input no
|
|||
parameter['le_tau'] = 23 #In pico-seconds.
|
||||
parameter["min_inv_para_delay"] = .73 #In relative delay units
|
||||
parameter['cap_relative_per_ff'] = .91 #Units of Relative Capacitance/ Femto-Farad
|
||||
parameter["static_delay_stages"] = 4
|
||||
parameter["static_fanout_per_stage"] = 3
|
||||
parameter["static_fanout_list"] = parameter["static_delay_stages"]*[parameter["static_fanout_per_stage"]]
|
||||
parameter["dff_clk_cin"] = 27.5 #In relative capacitance units
|
||||
parameter["6tcell_wl_cin"] = 2 #In relative capacitance units
|
||||
parameter["sa_en_pmos_size"] = 24*_lambda_
|
||||
parameter["sa_en_nmos_size"] = 9*_lambda_
|
||||
parameter["sa_inv_pmos_size"] = 18*_lambda_
|
||||
parameter["sa_inv_nmos_size"] = 9*_lambda_
|
||||
parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array
|
||||
parameter['bitcell_drain_cap'] = 0.2 #In Femto-Farad, approximation of drain capacitance
|
||||
|
||||
###################################################
|
||||
|
|
|
|||
Loading…
Reference in New Issue