From cc5b347f42c1ddbf0ca8910dcd4f7c8081d76958 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 3 Apr 2019 16:19:49 -0700 Subject: [PATCH] Added analyical model test which compares measured delay to model delay. --- compiler/bitcells/bitcell.py | 14 +--- compiler/characterizer/delay.py | 4 +- compiler/characterizer/logical_effort.py | 4 + compiler/modules/bank.py | 48 +++-------- compiler/modules/bitcell_array.py | 22 +---- compiler/modules/sense_amp_array.py | 7 ++ .../modules/single_level_column_mux_array.py | 11 ++- ...y_model_test.py => 21_model_delay_test.py} | 38 ++++++--- compiler/tests/27_worst_case_delay_test.py | 81 ------------------- technology/freepdk45/tech/tech.py | 3 +- technology/scn4m_subm/tech/tech.py | 9 ++- 11 files changed, 71 insertions(+), 170 deletions(-) rename compiler/tests/{28_delay_model_test.py => 21_model_delay_test.py} (57%) delete mode 100755 compiler/tests/27_worst_case_delay_test.py diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index d5712d7f..3f1d02d3 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -24,19 +24,7 @@ class bitcell(design.design): self.width = bitcell.width self.height = bitcell.height self.pin_map = bitcell.pin_map - - # def analytical_delay(self, corner, slew, load=0, swing = 0.5): - # # delay of bit cell is not like a driver(from WL) - # # so the slew used should be 0 - # # it should not be slew dependent? - # # because the value is there - # # the delay is only over half transsmission gate - # from tech import spice - # r = spice["min_tx_r"]*3 - # c_para = spice["min_tx_drain_c"] - # result = self.cal_delay_with_rc(corner, r = r, c = c_para+load, slew = slew, swing = swing) - # return result - + 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 diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 9c15cd2a..a80fc4dc 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -9,6 +9,7 @@ import utils from globals import OPTS from .simulation import simulation from .measurements import * +import logical_effort class delay(simulation): """Functions to measure the delay and power of an SRAM at a given address and @@ -904,8 +905,9 @@ class delay(simulation): 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] for slew in slews: - for load in loads: + 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: diff --git a/compiler/characterizer/logical_effort.py b/compiler/characterizer/logical_effort.py index 97c10d51..860ae68f 100644 --- a/compiler/characterizer/logical_effort.py +++ b/compiler/characterizer/logical_effort.py @@ -67,4 +67,8 @@ def calculate_relative_rise_fall_delays(stage_effort_list): else: total_fall_delay += stage.get_stage_delay() return total_rise_delay, total_fall_delay + +def convert_farad_to_relative_c(c_farad): + """Converts capacitance in Femto-Farads to relative capacitance.""" + return c_farad*parameter['cap_relative_per_ff'] \ No newline at end of file diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index e6d18e0b..36cfaf97 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1215,37 +1215,6 @@ class bank(design.design): offset=control_pos, rotate=90) - - # def analytical_delay(self, corner, slew, load): - # """ return analytical delay of the bank""" - # results = [] - - # decoder_delay = self.row_decoder.analytical_delay(corner, slew, self.wordline_driver.input_load()) - - # word_driver_delay = self.wordline_driver.analytical_delay(corner, - # decoder_delay.slew, - # self.bitcell_array.input_load()) - - # #FIXME: Array delay is the same for every port. - # bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_delay.slew) - - # #This also essentially creates the same delay for each port. Good structure, no substance - # for port in self.all_ports: - # if self.words_per_row > 1: - # column_mux_delay = self.column_mux_array[port].analytical_delay(corner, - # bitcell_array_delay.slew, - # self.sense_amp_array.input_load()) - # else: - # column_mux_delay = self.return_delay(delay = 0.0, slew=word_driver_delay.slew) - - # bl_t_data_out_delay = self.sense_amp_array.analytical_delay(corner, - # column_mux_delay.slew, - # self.bitcell_array.output_load()) - # # output load of bitcell_array is set to be only small part of bl for sense amp. - # results.append(decoder_delay + word_driver_delay + bitcell_array_delay + column_mux_delay + bl_t_data_out_delay) - - # return results - def analytical_delay(self, corner, slew, load): """ return analytical delay of the bank. This will track the clock to output path""" #FIXME: This delay is determined in the control logic. Should be moved here. @@ -1255,20 +1224,27 @@ class bank(design.design): #FIXME: Array delay is the same for every port. word_driver_slew = 0 - bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_slew) + if self.words_per_row > 1: + bitline_ext_load = self.column_mux_array[port].get_drain_cin() + else: + bitline_ext_load = self.sense_amp_array.get_drain_cin() + + bitcell_array_delay = self.bitcell_array.analytical_delay(corner, word_driver_slew, bitline_ext_load) + bitcell_array_slew = 0 #This also essentially creates the same delay for each port. Good structure, no substance if self.words_per_row > 1: + sa_load = self.sense_amp_array.get_drain_load() column_mux_delay = self.column_mux_array[port].analytical_delay(corner, - bitcell_array_delay.slew, - self.sense_amp_array.input_load()) + bitcell_array_slew, + sa_load) else: column_mux_delay = [] column_mux_slew = 0 sense_amp_delay = self.sense_amp_array.analytical_delay(corner, - column_mux_slew, - self.bitcell_array.output_load()) + column_mux_slew, + load) # output load of bitcell_array is set to be only small part of bl for sense amp. return bitcell_array_delay + column_mux_delay + sense_amp_delay diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 6aecfd6b..e2e98b5b 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -128,27 +128,9 @@ class bitcell_array(design.design): inst = self.cell_inst[row,col] for pin_name in ["vdd", "gnd"]: for pin in inst.get_pins(pin_name): - self.add_power_pin(pin_name, pin.center(), 0, pin.layer) - - - # def analytical_delay(self, corner, slew, load=0): - # from tech import drc - # wl_wire = self.gen_wl_wire() - # wl_wire.return_delay_over_wire(slew) - - # wl_to_cell_delay = wl_wire.return_delay_over_wire(slew) - # # hypothetical delay from cell to bl end without sense amp - # bl_wire = self.gen_bl_wire() - # cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r - # # hence just use the whole c - # bl_swing = 0.1 - # cell_delay = self.cell.analytical_delay(corner, wl_to_cell_delay.slew, cell_load, swing = bl_swing) - - # #we do not consider the delay over the wire for now - # return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay, - # wl_to_cell_delay.slew) + self.add_power_pin(pin_name, pin.center(), 0, pin.layer) - def analytical_delay(self, corner, slew, load=0): + def analytical_delay(self, corner, slew, load): """Returns relative delay of the bitline in the bitcell array""" #The load being driven/drained is mostly the bitline but could include the sense amp or the column mux. #The load from the bitlines is due to the drain capacitances from all the other bitlines and wire parasitics. diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 83e3d34a..9ba72025 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -4,6 +4,7 @@ from vector import vector from sram_factory import factory import debug from globals import OPTS +import logical_effort class sense_amp_array(design.design): """ @@ -143,3 +144,9 @@ class sense_amp_array(design.design): """Get the relative capacitance of all the sense amp enable connections in the array""" sense_amp_en_cin = self.amp.get_en_cin() return sense_amp_en_cin * self.word_size + + def get_drain_cin(self): + """Get the relative capacitance of the drain of the PMOS isolation TX""" + #Estimated as half a parasitic delay. + drain_parasitics = .5 + return drain_parasitics * logical_effort.logical_effort.pinv diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index c2414653..3247592e 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -7,6 +7,7 @@ import math from vector import vector from sram_factory import factory from globals import OPTS +import logical_effort class single_level_column_mux_array(design.design): """ @@ -232,5 +233,11 @@ class single_level_column_mux_array(design.design): """Returns relative delay that the column mux adds""" #Single level column mux will add parasitic loads from other mux pass transistors and the sense amp. drain_parasitics = .5 #Assumed parasitics from unused TXs - array_load = drain_parasitics*self.words_per_row*logical_effort.pinv - return [self.mux.analytical_delay(corner, slew, load+array_load)] \ No newline at end of file + array_load = drain_parasitics*self.words_per_row*logical_effort.logical_effort.pinv + return [self.mux.analytical_delay(corner, slew, load+array_load)] + + def get_drain_cin(self): + """Get the relative capacitance of the drain of the NMOS pass TX""" + #Estimated as half a parasitic delay. + drain_parasitics = .5 + return drain_parasitics * logical_effort.logical_effort.pinv \ No newline at end of file diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/21_model_delay_test.py similarity index 57% rename from compiler/tests/28_delay_model_test.py rename to compiler/tests/21_model_delay_test.py index a02a42e9..7e9c00e4 100755 --- a/compiler/tests/28_delay_model_test.py +++ b/compiler/tests/21_model_delay_test.py @@ -11,25 +11,22 @@ import globals from globals import OPTS import debug -class delay_model_test(openram_test): +class model_delay_sram_test(openram_test): def runTest(self): globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - OPTS.spice_name="hspice" + #OPTS.spice_name="hspice" OPTS.analytical_delay = False OPTS.netlist_only = True - OPTS.trim_netlist = False - debug.info(1, "Trimming disabled for this test. Simulation could be slow.") # This is a hack to reload the characterizer __init__ with the spice version from importlib import reload import characterizer reload(characterizer) - - from characterizer import model_check + from characterizer import delay from sram import sram from sram_config import sram_config - c = sram_config(word_size=4, + c = sram_config(word_size=1, num_words=16, num_banks=1) c.words_per_row=1 @@ -45,15 +42,32 @@ class delay_model_test(openram_test): debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - mc = model_check(s.s, tempspice, corner) + d = delay(s.s, tempspice, corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] - sram_data = mc.analyze(probe_address, probe_data, slews, loads) - #Combine info about port into all data - - #debug.info(1,"Data:\n{}".format(wl_data)) + spice_data, port_data = d.analyze(probe_address, probe_data, slews, loads) + spice_data.update(port_data[0]) + + model_data, port_data = d.analytical_delay(slews, loads) + model_data.update(port_data[0]) + + #Only compare the delays + spice_delays = {key:value for key, value in spice_data.items() if 'delay' in key} + model_delays = {key:value for key, value in model_data.items() if 'delay' in key} + debug.info(1,"Spice Delays={}".format(spice_delays)) + debug.info(1,"Model Delays={}".format(model_delays)) + if OPTS.tech_name == "freepdk45": + error_tolerance = .25 + elif OPTS.tech_name == "scn4m_subm": + error_tolerance = .25 + else: + self.assertTrue(False) # other techs fail + # Check if no too many or too few results + self.assertTrue(len(spice_delays.keys())==len(model_delays.keys())) + self.assertTrue(self.check_golden_data(spice_delays,model_delays,error_tolerance)) + globals.end_openram() # run the test from the command line diff --git a/compiler/tests/27_worst_case_delay_test.py b/compiler/tests/27_worst_case_delay_test.py deleted file mode 100755 index 834c6b69..00000000 --- a/compiler/tests/27_worst_case_delay_test.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env python3 -""" -Run a regression test on various srams -""" - -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 -import debug - -@unittest.skip("SKIPPING 27_worst_case_delay_test") -class worst_case_timing_sram_test(openram_test): - - def runTest(self): - OPTS.tech_name = "freepdk45" - globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - OPTS.spice_name="hspice" - OPTS.analytical_delay = False - OPTS.netlist_only = True - OPTS.trim_netlist = False - OPTS.check_lvsdrc = True - - - # This is a hack to reload the characterizer __init__ with the spice version - from importlib import reload - import characterizer - reload(characterizer) - from characterizer import worst_case - if not OPTS.spice_exe: - debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) - - word_size, num_words, num_banks = 2, 16, 1 - from sram import sram - from sram_config import sram_config - c = sram_config(word_size=word_size, - num_words=num_words, - num_banks=num_banks) - c.words_per_row=1 - c.recompute_sizes() - debug.info(1, "Testing the timing different bitecells inside a {}bit, {} words SRAM with {} bank".format( - word_size, num_words, num_banks)) - s = sram(c, name="sram1") - - sp_netlist_file = OPTS.openram_temp + "temp.sp" - s.sp_write(sp_netlist_file) - - if OPTS.use_pex: - gdsname = OPTS.output_path + s.name + ".gds" - s.gds_write(gdsname) - - import verify - reload(verify) - # Output the extracted design if requested - sp_pex_file = OPTS.output_path + s.name + "_pex.sp" - verify.run_pex(s.name, gdsname, sp_netlist_file, output=sp_pex_file) - sp_sim_file = sp_pex_file - debug.info(1, "Performing spice simulations with backannotated spice file.") - else: - sp_sim_file = sp_netlist_file - debug.info(1, "Performing spice simulations with spice netlist.") - - corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - wc = worst_case(s.s, sp_sim_file, corner) - import tech - loads = [tech.spice["msflop_in_cap"]*4] - slews = [tech.spice["rise_time"]*2] - probe_address = "1" * s.s.addr_size - probe_data = s.s.word_size - 1 - wc.analyze(probe_address, probe_data, slews, loads) - - 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() diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 738bc0ef..93f55637 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -336,7 +336,8 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. #Parameters related to sense amp enable timing and delay chain/RBL sizing -parameter['le_tau'] = 8 #In pico-seconds. FIXME:This is an assumed value, not measured. +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"]] diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index e127b1a7..53a3a255 100755 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -302,13 +302,14 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. #Logical Effort relative values for the Handmade cells -parameter['le_tau'] = 40 #In pico-seconds. FIXME:This is an assumed value, not measured. +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 -parameter["6tcell_wl_cin"] = 2 -parameter["min_inv_para_delay"] = .5 +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_