Added graph functions to compute analytical delay based on graph path.

This commit is contained in:
Hunter Nichols 2019-08-07 01:50:48 -07:00
parent 2ce7323838
commit 6860d3258e
15 changed files with 141 additions and 49 deletions

View File

@ -18,13 +18,16 @@ class timing_graph():
def __init__(self): def __init__(self):
self.graph = defaultdict(set) self.graph = defaultdict(set)
self.all_paths = [] self.all_paths = []
self.edge_mods = {}
def add_edge(self, src_node, dest_node): def add_edge(self, src_node, dest_node, edge_mod):
"""Adds edge to graph. Nodes added as well if they do not exist.""" """Adds edge to graph. Nodes added as well if they do not exist.
Module which defines the edge must be provided for timing information."""
src_node = src_node.lower() src_node = src_node.lower()
dest_node = dest_node.lower() dest_node = dest_node.lower()
self.graph[src_node].add(dest_node) self.graph[src_node].add(dest_node)
self.edge_mods[(src_node, dest_node)] = edge_mod
def add_node(self, node): def add_node(self, node):
"""Add node to graph with no edges""" """Add node to graph with no edges"""
@ -34,8 +37,8 @@ class timing_graph():
self.graph[node] = set() self.graph[node] = set()
def remove_edges(self, node): def remove_edges(self, node):
"""Helper function to remove edges, useful for removing vdd/gnd""" """Helper function to remove edges, useful for removing vdd/gnd"""
node = node.lower() node = node.lower()
self.graph[node] = set() self.graph[node] = set()
@ -93,7 +96,32 @@ class timing_graph():
# Remove current vertex from path[] and mark it as unvisited # Remove current vertex from path[] and mark it as unvisited
path.pop() path.pop()
visited.remove(cur_node) visited.remove(cur_node)
def get_timing(self, path, corner, slew, load):
"""Returns the analytical delays in the input path"""
if len(path) == 0:
return []
delays = []
for i in range(len(path)-1):
path_edge_mod = self.edge_mods[(path[i], path[i+1])]
# On the output of the current stage, get COUT from all other mods connected
cout = 0
for node in self.graph[path[i+1]]:
output_edge_mode = self.edge_mods[(path[i+1], node)]
cout+=output_edge_mode.input_load()
# If at the last output, include the final output load
if i == len(path)-2:
cout+=load
delays.append(path_edge_mod.analytical_delay(corner, slew, cout))
slew = delays[-1].slew
return delays
def __str__(self): def __str__(self):
""" override print function output """ """ override print function output """

View File

@ -219,7 +219,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
for inp in input_pins+inout_pins: for inp in input_pins+inout_pins:
for out in output_pins+inout_pins: for out in output_pins+inout_pins:
if inp != out: #do not add self loops if inp != out: #do not add self loops
graph.add_edge(pin_dict[inp], pin_dict[out]) graph.add_edge(pin_dict[inp], pin_dict[out], self)
def __str__(self): def __str__(self):
""" override print function output """ """ override print function output """

View File

@ -13,7 +13,7 @@ import tech
from delay_data import * from delay_data import *
from wire_spice_model import * from wire_spice_model import *
from power_data import * from power_data import *
import logical_effort
class spice(): class spice():
""" """
@ -304,14 +304,28 @@ class spice():
def analytical_delay(self, corner, slew, load=0.0): def analytical_delay(self, corner, slew, load=0.0):
"""Inform users undefined delay module while building new modules""" """Inform users undefined delay module while building new modules"""
debug.warning("Design Class {0} delay function needs to be defined" relative_cap = logical_effort.convert_farad_to_relative_c(load)
stage_effort = self.get_stage_effort(relative_cap)
# If it fails, then keep running with a valid object.
if stage_effort == None:
return delay_data(0.0, 0.0)
abs_delay = stage_effort.get_absolute_delay()
corner_delay = self.apply_corners_analytically(abs_delay, corner)
SLEW_APPROXIMATION = 0.1
corner_slew = SLEW_APPROXIMATION*corner_delay
return delay_data(corner_delay, corner_slew)
def get_stage_effort(self, corner, slew, load=0.0):
"""Inform users undefined delay module while building new modules"""
debug.warning("Design Class {0} logical effort function needs to be defined"
.format(self.__class__.__name__)) .format(self.__class__.__name__))
debug.warning("Class {0} name {1}" debug.warning("Class {0} name {1}"
.format(self.__class__.__name__, .format(self.__class__.__name__,
self.name)) self.name))
# return 0 to keep code running while building return None
return delay_data(0.0, 0.0)
def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5): def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5):
""" """
Calculate the delay of a mosfet by Calculate the delay of a mosfet by

View File

@ -36,12 +36,22 @@ class bitcell(design.design):
self.add_pin_types(self.type_list) self.add_pin_types(self.type_list)
self.nets_match = self.do_nets_exist(self.storage_nets) self.nets_match = self.do_nets_exist(self.storage_nets)
def analytical_delay(self, corner, slew, load=0, swing = 0.5): def get_stage_effort(self, load):
parasitic_delay = 1 parasitic_delay = 1
size = 0.5 #This accounts for bitline being drained thought the access TX and internal node size = 0.5 #This accounts for bitline being drained thought the access TX and internal node
cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file.
return logical_effort.logical_effort('bitline', size, cin, load, parasitic_delay, False) return logical_effort.logical_effort('bitline', size, cin, load, parasitic_delay, False)
def input_load(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.
#FIXME: The graph algorithm will apply this capacitance to the bitline load as they cannot be
# distinguished currently
access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"]
return 2*access_tx_cin
def get_all_wl_names(self): def get_all_wl_names(self):
""" Creates a list of all wordline pin names """ """ Creates a list of all wordline pin names """
row_pins = ["wl"] row_pins = ["wl"]

View File

@ -141,8 +141,8 @@ class bitcell_1rw_1r(design.design):
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
#Edges hardcoded here. Essentially wl->bl/br for both ports. #Edges hardcoded here. Essentially wl->bl/br for both ports.
# Port 0 edges # Port 0 edges
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
# Port 1 edges # Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)

View File

@ -139,6 +139,6 @@ class bitcell_1w_1r(design.design):
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
#Edges hardcoded here. Essentially wl->bl/br for both ports. #Edges hardcoded here. Essentially wl->bl/br for both ports.
# Port 0 edges # Port 0 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
# Port 1 is a write port, so its timing is not considered here. # Port 1 is a write port, so its timing is not considered here.

View File

@ -973,6 +973,6 @@ class pbitcell(design.design):
for pin_zip in [rw_pin_names, r_pin_names]: for pin_zip in [rw_pin_names, r_pin_names]:
for wl,bl,br in pin_zip: for wl,bl,br in pin_zip:
graph.add_edge(pin_dict[wl],pin_dict[bl]) graph.add_edge(pin_dict[wl],pin_dict[bl], self)
graph.add_edge(pin_dict[wl],pin_dict[br]) graph.add_edge(pin_dict[wl],pin_dict[br], self)

View File

@ -46,8 +46,8 @@ class replica_bitcell_1rw_1r(design.design):
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
#Edges hardcoded here. Essentially wl->bl/br for both ports. #Edges hardcoded here. Essentially wl->bl/br for both ports.
# Port 0 edges # Port 0 edges
graph.add_edge(pin_dict["wl0"], pin_dict["bl0"]) graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self)
graph.add_edge(pin_dict["wl0"], pin_dict["br0"]) graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self)
# Port 1 edges # Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"])
graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) graph.add_edge(pin_dict["wl1"], pin_dict["br1"])

View File

@ -47,6 +47,6 @@ class replica_bitcell_1w_1r(design.design):
pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)}
#Edges hardcoded here. Essentially wl->bl/br for the read port. #Edges hardcoded here. Essentially wl->bl/br for the read port.
# Port 1 edges # Port 1 edges
graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self)
graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self)
# Port 0 is a write port, so its timing is not considered here. # Port 0 is a write port, so its timing is not considered here.

View File

@ -1275,36 +1275,65 @@ class delay(simulation):
# Add test cycle of read/write port pair. One port could have been used already, but the other has not. # Add test cycle of read/write port pair. One port could have been used already, but the other has not.
self.gen_test_cycles_one_port(cur_read_port, cur_write_port) self.gen_test_cycles_one_port(cur_read_port, cur_write_port)
def sum_delays(self, delays):
"""Adds the delays (delay_data objects) so the correct slew is maintained"""
delay = delays[0]
for i in range(1, len(delays)):
delay+=delays[i]
return delay
def analytical_delay(self, slews, loads): def analytical_delay(self, slews, loads):
""" """
Return the analytical model results for the SRAM. Return the analytical model results for the SRAM.
""" """
if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0: 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.") debug.warning("Analytical characterization results are not supported for multiport.")
# Probe set to 0th bit, does not matter for analytical delay.
self.set_probe('0', 0)
self.create_graph()
self.set_internal_spice_names()
self.create_measurement_names() self.create_measurement_names()
power = self.analytical_power(slews, loads)
port_data = self.get_empty_measure_data_dict() port = self.read_ports[0]
relative_loads = [logical_effort.convert_farad_to_relative_c(c_farad) for c_farad in loads] self.graph.get_all_paths('{}{}'.format(tech.spice["clk"], port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
# Select the path with the bitline (bl)
bl_name,br_name = self.get_bl_name(self.graph.all_paths, port)
bl_path = [path for path in self.graph.all_paths if bl_name in path][0]
debug.info(1,'Slew, Load, Delay(ns), Slew(ns)')
for slew in slews: for slew in slews:
for load in relative_loads: for load in loads:
self.set_load_slew(load,slew) path_delays = self.graph.get_timing(bl_path, self.corner, slew, load)
bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load) total_delay = self.sum_delays(path_delays)
for port in self.all_ports: debug.info(1,'{}, {}, {}, {}'.format(slew,load,total_delay.delay/1e3, total_delay.slew/1e3))
for mname in self.delay_meas_names+self.power_meas_names:
if "power" in mname: debug.error('Not finished with graph delay characterization.', 1)
port_data[port][mname].append(power.dynamic) # power = self.analytical_power(slews, loads)
elif "delay" in mname: # port_data = self.get_empty_measure_data_dict()
port_data[port][mname].append(bank_delay[port].delay/1e3) # relative_loads = [logical_effort.convert_farad_to_relative_c(c_farad) for c_farad in loads]
elif "slew" in mname: # for slew in slews:
port_data[port][mname].append(bank_delay[port].slew/1e3) # for load in relative_loads:
else: # self.set_load_slew(load,slew)
debug.error("Measurement name not recognized: {}".format(mname),1) # bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load)
period_margin = 0.1 # for port in self.all_ports:
risefall_delay = bank_delay[self.read_ports[0]].delay/1e3 # for mname in self.delay_meas_names+self.power_meas_names:
sram_data = { "min_period":risefall_delay*2*period_margin, # if "power" in mname:
"leakage_power": power.leakage} # port_data[port][mname].append(power.dynamic)
debug.info(2,"SRAM Data:\n{}".format(sram_data)) # elif "delay" in mname:
debug.info(2,"Port Data:\n{}".format(port_data)) # port_data[port][mname].append(bank_delay[port].delay/1e3)
# elif "slew" in mname:
# port_data[port][mname].append(bank_delay[port].slew/1e3)
# else:
# debug.error("Measurement name not recognized: {}".format(mname),1)
# period_margin = 0.1
# risefall_delay = bank_delay[self.read_ports[0]].delay/1e3
# sram_data = { "min_period":risefall_delay*2*period_margin,
# "leakage_power": power.leakage}
# debug.info(2,"SRAM Data:\n{}".format(sram_data))
# debug.info(2,"Port Data:\n{}".format(port_data))
return (sram_data,port_data) return (sram_data,port_data)

View File

@ -15,3 +15,5 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
drc_name = "magic" drc_name = "magic"
lvs_name = "netgen" lvs_name = "netgen"
pex_name = "magic" pex_name = "magic"
netlist_only = True

View File

@ -41,7 +41,7 @@ class sense_amp(design.design):
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file. bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff
def analytical_delay(self, corner, slew, load): def get_stage_effort(self, load):
#Delay of the sense amp will depend on the size of the amp and the output load. #Delay of the sense amp will depend on the size of the amp and the output load.
parasitic_delay = 1 parasitic_delay = 1
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")

View File

@ -276,7 +276,12 @@ class pinv(pgate.pgate):
"""Return the capacitance of the gate connection in generic capacitive """Return the capacitance of the gate connection in generic capacitive
units relative to the minimum width of a transistor""" units relative to the minimum width of a transistor"""
return self.nmos_size + self.pmos_size return self.nmos_size + self.pmos_size
def input_load(self):
"""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): def get_stage_effort(self, cout, inp_is_rise=True):
"""Returns an object representing the parameters for delay in tau units. """Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.

View File

@ -253,7 +253,11 @@ class pnand2(pgate.pgate):
def get_cin(self): def get_cin(self):
"""Return the relative input capacitance of a single input""" """Return the relative input capacitance of a single input"""
return self.nmos_size+self.pmos_size return self.nmos_size+self.pmos_size
def input_load(self):
"""Return the relative input capacitance of a single input"""
return self.nmos_size+self.pmos_size
def get_stage_effort(self, cout, inp_is_rise=True): def get_stage_effort(self, cout, inp_is_rise=True):
"""Returns an object representing the parameters for delay in tau units. """Returns an object representing the parameters for delay in tau units.
Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. Optional is_rise refers to the input direction rise/fall. Input inverted by this stage.

View File

@ -189,7 +189,7 @@ class single_level_column_mux(pgate.pgate):
width=self.bitcell.width, width=self.bitcell.width,
height=self.height) height=self.height)
def analytical_delay(self, corner, slew, load): def get_stage_effort(self, corner, slew, load):
"""Returns relative delay that the column mux. Difficult to convert to LE model.""" """Returns relative delay that the column mux. Difficult to convert to LE model."""
parasitic_delay = 1 parasitic_delay = 1
cin = 2*self.tx_size #This is not CMOS, so using this may be incorrect. cin = 2*self.tx_size #This is not CMOS, so using this may be incorrect.