From 6860d3258ecedfe595e7dae0cda0335525b82cf4 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 7 Aug 2019 01:50:48 -0700 Subject: [PATCH] Added graph functions to compute analytical delay based on graph path. --- compiler/base/graph_util.py | 36 ++++++++- compiler/base/hierarchy_design.py | 2 +- compiler/base/hierarchy_spice.py | 24 ++++-- compiler/bitcells/bitcell.py | 12 ++- compiler/bitcells/bitcell_1rw_1r.py | 8 +- compiler/bitcells/bitcell_1w_1r.py | 4 +- compiler/bitcells/pbitcell.py | 4 +- compiler/bitcells/replica_bitcell_1rw_1r.py | 4 +- compiler/bitcells/replica_bitcell_1w_1r.py | 4 +- compiler/characterizer/delay.py | 73 +++++++++++++------ .../example_config_scn4m_subm.py | 2 + compiler/modules/sense_amp.py | 2 +- compiler/pgates/pinv.py | 7 +- compiler/pgates/pnand2.py | 6 +- compiler/pgates/single_level_column_mux.py | 2 +- 15 files changed, 141 insertions(+), 49 deletions(-) diff --git a/compiler/base/graph_util.py b/compiler/base/graph_util.py index 7cf8ee6f..8a6588af 100644 --- a/compiler/base/graph_util.py +++ b/compiler/base/graph_util.py @@ -18,13 +18,16 @@ class timing_graph(): def __init__(self): self.graph = defaultdict(set) self.all_paths = [] + self.edge_mods = {} - def add_edge(self, src_node, dest_node): - """Adds edge to graph. Nodes added as well if they do not exist.""" + def add_edge(self, src_node, dest_node, edge_mod): + """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() dest_node = dest_node.lower() self.graph[src_node].add(dest_node) + self.edge_mods[(src_node, dest_node)] = edge_mod def add_node(self, node): """Add node to graph with no edges""" @@ -34,8 +37,8 @@ class timing_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() @@ -93,7 +96,32 @@ class timing_graph(): # Remove current vertex from path[] and mark it as unvisited 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): """ override print function output """ diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index 05b2182f..ec0b4e96 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -219,7 +219,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): 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]) + graph.add_edge(pin_dict[inp], pin_dict[out], self) def __str__(self): """ override print function output """ diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 2dcc3d73..c8ffb151 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -13,7 +13,7 @@ import tech from delay_data import * from wire_spice_model import * from power_data import * - +import logical_effort class spice(): """ @@ -304,14 +304,28 @@ class spice(): def analytical_delay(self, corner, slew, load=0.0): """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__)) debug.warning("Class {0} name {1}" .format(self.__class__.__name__, self.name)) - # return 0 to keep code running while building - return delay_data(0.0, 0.0) - + return None + def cal_delay_with_rc(self, corner, r, c ,slew, swing = 0.5): """ Calculate the delay of a mosfet by diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 96432cea..360e08de 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -36,12 +36,22 @@ class bitcell(design.design): 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): + def get_stage_effort(self, load): parasitic_delay = 1 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. 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): """ Creates a list of all wordline pin names """ row_pins = ["wl"] diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index f627a4ac..42bd3855 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -141,8 +141,8 @@ class bitcell_1rw_1r(design.design): 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"]) + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) # Port 1 edges - 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["bl1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index 6063cf86..b8f0e49b 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -139,6 +139,6 @@ class bitcell_1w_1r(design.design): 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["wl1"], pin_dict["bl1"]) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 3c59a9e3..b09e81c2 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -973,6 +973,6 @@ class pbitcell(design.design): for pin_zip in [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]) + graph.add_edge(pin_dict[wl],pin_dict[bl], self) + graph.add_edge(pin_dict[wl],pin_dict[br], self) diff --git a/compiler/bitcells/replica_bitcell_1rw_1r.py b/compiler/bitcells/replica_bitcell_1rw_1r.py index e5c33317..9737669d 100644 --- a/compiler/bitcells/replica_bitcell_1rw_1r.py +++ b/compiler/bitcells/replica_bitcell_1rw_1r.py @@ -46,8 +46,8 @@ class replica_bitcell_1rw_1r(design.design): 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"]) + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) # Port 1 edges graph.add_edge(pin_dict["wl1"], pin_dict["bl1"]) graph.add_edge(pin_dict["wl1"], pin_dict["br1"]) \ No newline at end of file diff --git a/compiler/bitcells/replica_bitcell_1w_1r.py b/compiler/bitcells/replica_bitcell_1w_1r.py index 79171bf5..fad5032d 100644 --- a/compiler/bitcells/replica_bitcell_1w_1r.py +++ b/compiler/bitcells/replica_bitcell_1w_1r.py @@ -47,6 +47,6 @@ class replica_bitcell_1w_1r(design.design): pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} #Edges hardcoded here. Essentially wl->bl/br for the read port. # Port 1 edges - 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["bl1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) # Port 0 is a write port, so its timing is not considered here. \ No newline at end of file diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index bb48564e..90e1561e 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -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. 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): """ 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: 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() - power = self.analytical_power(slews, loads) - port_data = self.get_empty_measure_data_dict() - relative_loads = [logical_effort.convert_farad_to_relative_c(c_farad) for c_farad in loads] + + port = self.read_ports[0] + 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 load in relative_loads: - self.set_load_slew(load,slew) - bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load) - for port in self.all_ports: - for mname in self.delay_meas_names+self.power_meas_names: - if "power" in mname: - port_data[port][mname].append(power.dynamic) - elif "delay" in mname: - 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)) + for load in loads: + path_delays = self.graph.get_timing(bl_path, self.corner, slew, load) + total_delay = self.sum_delays(path_delays) + debug.info(1,'{}, {}, {}, {}'.format(slew,load,total_delay.delay/1e3, total_delay.slew/1e3)) + + debug.error('Not finished with graph delay characterization.', 1) + # power = self.analytical_power(slews, loads) + # port_data = self.get_empty_measure_data_dict() + # relative_loads = [logical_effort.convert_farad_to_relative_c(c_farad) for c_farad in loads] + # for slew in slews: + # for load in relative_loads: + # self.set_load_slew(load,slew) + # bank_delay = self.sram.analytical_delay(self.corner, self.slew,self.load) + # for port in self.all_ports: + # for mname in self.delay_meas_names+self.power_meas_names: + # if "power" in mname: + # port_data[port][mname].append(power.dynamic) + # elif "delay" in mname: + # 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) diff --git a/compiler/example_configs/example_config_scn4m_subm.py b/compiler/example_configs/example_config_scn4m_subm.py index cf973225..657eceda 100644 --- a/compiler/example_configs/example_config_scn4m_subm.py +++ b/compiler/example_configs/example_config_scn4m_subm.py @@ -15,3 +15,5 @@ output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) drc_name = "magic" lvs_name = "netgen" pex_name = "magic" + +netlist_only = True \ No newline at end of file diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index e104356f..a053aff8 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -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. 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. parasitic_delay = 1 cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx") diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 28eb03e8..d841b318 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -276,7 +276,12 @@ class pinv(pgate.pgate): """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 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): """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. diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index b0d30b7b..2275a336 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -253,7 +253,11 @@ class pnand2(pgate.pgate): def get_cin(self): """Return the relative input capacitance of a single input""" 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): """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. diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 7c21c0a8..999d3ccd 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -189,7 +189,7 @@ class single_level_column_mux(pgate.pgate): width=self.bitcell.width, 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.""" parasitic_delay = 1 cin = 2*self.tx_size #This is not CMOS, so using this may be incorrect.