diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 8ce96207..5fa13baa 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -203,76 +203,64 @@ class delay(): self.sf.close() + def get_delay_meas_values(self, delay_name, port): + """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" + debug.check('lh' in delay_name or 'hl' in delay_name, "Measure command {0} does not contain direction (lh/hl)") + trig_clk_name = "clk" + meas_name="{0}{1}".format(delay_name, port) + targ_name = "{0}".format("DOUT{0}[{1}]".format(port,self.probe_data)) + half_vdd = 0.5 * self.vdd_voltage + trig_slew_low = 0.1 * self.vdd_voltage + targ_slew_high = 0.9 * self.vdd_voltage + if 'delay' in delay_name: + trig_dir="RISE" + trig_val = half_vdd + targ_val = half_vdd + trig_name = trig_clk_name + if 'lh' in delay_name: + targ_dir="RISE" + trig_td = targ_td = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]] + else: + targ_dir="FALL" + trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] + + elif 'slew' in delay_name: + trig_name = targ_name + if 'lh' in delay_name: + trig_val = trig_slew_low + targ_val = targ_slew_high + targ_dir = trig_dir = "RISE" + trig_td = targ_td = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]] + else: + trig_val = targ_slew_high + targ_val = trig_slew_low + targ_dir = trig_dir = "FALL" + trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] + else: + debug.error(1, "Measure command {0} not recognized".format(delay_name)) + return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) + def write_delay_measures_read_port(self, port): """ Write the measure statements to quantify the delay and power results for a read port. """ - - # Trigger on the clk of the appropriate cycle - trig_clk_name = trig_name = "clk" - #Target name should be an input to the function or a member variable. That way, the ports can be singled out for testing - targ_name = "{0}".format("DOUT{0}[{1}]".format(port,self.probe_data)) - trig_val = targ_val = 0.5 * self.vdd_voltage - trig_delay_val = targ_delay_val = 0.5 * self.vdd_voltage - trig_slew_low = 0.1 * self.vdd_voltage - targ_slew_high = 0.9 * self.vdd_voltage - trig_dir = "FALL" - targ_dir = "RISE" - trig_td = targ_td = 0 - parse_error = False - - # Delay the target to measure after the negative edge + # add measure statements for delays/slews for dname in self.delay_meas_names: - if 'delay' in dname: - trig_dir="RISE" - trig_val = trig_delay_val - targ_val = targ_val - trig_name = trig_clk_name - if 'lh' in dname: - targ_dir="RISE" - else: - targ_dir="FALL" - - elif 'slew' in dname: - trig_name = targ_name - if 'lh' in dname: - trig_val = trig_slew_low - targ_val = targ_slew_high - targ_dir = trig_dir = "RISE" - else: - trig_val = targ_slew_high - targ_val = trig_slew_low - targ_dir = trig_dir = "FALL" - else: - debug.error(1, "Measure command {0} not recognized".format(dname)) + meas_values = self.get_delay_meas_values(dname, port) + self.stim.gen_meas_delay(*meas_values) - if 'lh' in dname: - trig_td = targ_td = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]] - elif 'hl' in dname: - trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] - else: - debug.error(1, "Measure command {0} does not contain direction (lh/hl)".format(dname)) - - self.stim.gen_meas_delay(meas_name="{0}{1}".format(dname, port), - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir=trig_dir, - targ_dir=targ_dir, - trig_td=trig_td, - targ_td=targ_td) - # add measure statements for power for pname in self.power_meas_names: if "read" not in pname: continue - t_initial = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] - t_final = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]+1] + #Different naming schemes are used for the measure cycle dict and measurement names. + #TODO: make them the same so they can be indexed the same. if '1' in pname: t_initial = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]] t_final = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]+1] - + elif '0' in pname: + t_initial = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] + t_final = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]+1] self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), t_initial=t_initial, t_final=t_final) @@ -429,7 +417,7 @@ class delay(): include leakage of all cells. """ #Sanity Check - debug.check(self.period > 0, "Initial starting period not defined") + debug.check(self.period > 0, "Target simulation period non-positive") result = [{} for i in range(self.total_port_num)] # Checking from not data_value to data_value @@ -645,7 +633,7 @@ class delay(): Main function to characterize an SRAM for a table. Computes both delay and power characterization. """ #Dict to hold all characterization values - char_data = {} + char_sram_data = {} self.set_probe(probe_address, probe_data) @@ -681,18 +669,17 @@ class delay(): min_period = self.find_min_period(feasible_delays) debug.check(type(min_period)==float,"Couldn't find minimum period.") debug.info(1, "Min Period Found: {0}ns".format(min_period)) - char_data["min_period"] = round_time(min_period) + char_sram_data["min_period"] = round_time(min_period) # 3) Find the leakage power of the trimmmed and UNtrimmed arrays. (full_array_leakage, trim_array_leakage)=self.run_power_simulation() - char_data["leakage_power"]=full_array_leakage + char_sram_data["leakage_power"]=full_array_leakage leakage_offset = full_array_leakage - trim_array_leakage # 4) At the minimum period, measure the delay, slew and power for all slew/load pairs. - load_slew_data = self.simulate_loads_and_slews(slews, loads, leakage_offset) - #char_data.update(load_slew_data) + char_port_data = self.simulate_loads_and_slews(slews, loads, leakage_offset) - return (char_data, load_slew_data) + return (char_sram_data, char_port_data) def simulate_loads_and_slews(self, slews, loads, leakage_offset): """Simulate all specified output loads and input slews pairs of all ports""" @@ -979,18 +966,18 @@ class delay(): debug.info(1,"Dynamic Power: {0} mW".format(power.dynamic)) debug.info(1,"Leakage Power: {0} mW".format(power.leakage)) - data = {"min_period": 0, - "delay_lh0": delay_lh, - "delay_hl0": delay_hl, - "slew_lh0": slew_lh, - "slew_hl0": slew_hl, - "read0_power0": power.dynamic, - "read1_power0": power.dynamic, - "write0_power0": power.dynamic, - "write1_power0": power.dynamic, - "leakage_power": power.leakage - } - return data + sram_data = { "min_period": 0, + "leakage_power": power.leakage} + port_data = [{"delay_lh": delay_lh, + "delay_hl": delay_hl, + "slew_lh": slew_lh, + "slew_hl": slew_hl, + "read0_power": power.dynamic, + "read1_power": power.dynamic, + "write0_power": power.dynamic, + "write1_power": power.dynamic, + }] + return (sram_data,port_data) def gen_data(self): """ Generates the PWL data inputs for a simulation timing test. """ diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 9f04fd7f..29c86084 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -161,7 +161,7 @@ class lib: # Leakage is included in dynamic when macro is enabled self.lib.write(" leakage_power () {\n") self.lib.write(" when : \"{0}\";\n".format(control_str)) - self.lib.write(" value : {};\n".format(self.char_results["leakage_power"])) + self.lib.write(" value : {};\n".format(self.char_sram_results["leakage_power"])) self.lib.write(" }\n") self.lib.write(" cell_leakage_power : {};\n".format(0)) @@ -347,16 +347,16 @@ class lib: self.lib.write(" related_pin : \"clk\"; \n") self.lib.write(" timing_type : rising_edge; \n") self.lib.write(" cell_rise(CELL_TABLE) {\n") - self.write_values(self.char_results["delay_lh{0}".format(read_port)],len(self.loads)," ") + self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ") self.lib.write(" }\n") # rise delay self.lib.write(" cell_fall(CELL_TABLE) {\n") - self.write_values(self.char_results["delay_hl{0}".format(read_port)],len(self.loads)," ") + self.write_values(self.char_port_results[read_port]["delay_hl"],len(self.loads)," ") self.lib.write(" }\n") # fall delay self.lib.write(" rise_transition(CELL_TABLE) {\n") - self.write_values(self.char_results["slew_lh{0}".format(read_port)],len(self.loads)," ") + self.write_values(self.char_port_results[read_port]["slew_lh"],len(self.loads)," ") self.lib.write(" }\n") # rise trans self.lib.write(" fall_transition(CELL_TABLE) {\n") - self.write_values(self.char_results["slew_hl{0}".format(read_port)],len(self.loads)," ") + self.write_values(self.char_port_results[read_port]["slew_hl"],len(self.loads)," ") self.lib.write(" }\n") # fall trans self.lib.write(" }\n") # timing self.lib.write(" }\n") # pin @@ -428,8 +428,8 @@ class lib: for port in range(self.total_port_num): self.add_clk_control_power(port) - min_pulse_width = round_time(self.char_results["min_period"])/2.0 - min_period = round_time(self.char_results["min_period"]) + min_pulse_width = round_time(self.char_sram_results["min_period"])/2.0 + min_period = round_time(self.char_sram_results["min_period"]) self.lib.write(" timing(){ \n") self.lib.write(" timing_type :\"min_pulse_width\"; \n") self.lib.write(" related_pin : clk; \n") @@ -461,7 +461,7 @@ class lib: if port in self.write_ports: if port in self.read_ports: web_name = " & !WEb{0}".format(port) - avg_write_power = np.mean(self.char_results["write1_power{0}".format(port)] + self.char_results["write0_power{0}".format(port)]) + avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"]) self.lib.write(" internal_power(){\n") self.lib.write(" when : \"!CSb{0} & clk{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") @@ -475,7 +475,7 @@ class lib: if port in self.read_ports: if port in self.write_ports: web_name = " & WEb{0}".format(port) - avg_read_power = np.mean(self.char_results["read1_power{0}".format(port)] + self.char_results["read0_power{0}".format(port)]) + avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"]) self.lib.write(" internal_power(){\n") self.lib.write(" when : \"!CSb{0} & !clk{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") @@ -502,13 +502,14 @@ class lib: if not hasattr(self,"d"): self.d = delay(self.sram, self.sp_file, self.corner) if self.use_model: - self.char_results = self.d.analytical_delay(self.sram,self.slews,self.loads) + char_results = self.d.analytical_delay(self.sram,self.slews,self.loads) + self.char_sram_results, self.char_port_results = char_results else: probe_address = "1" * self.sram.addr_size probe_data = self.sram.word_size - 1 - self.char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) - - + char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) + self.char_sram_results, self.char_port_results = char_results + def compute_setup_hold(self): """ Do the analysis if we haven't characterized a FF yet """ # Do the analysis if we haven't characterized a FF yet diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index da59c447..be8fb5ad 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -48,12 +48,14 @@ class timing_sram_test(openram_test): import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] - data = d.analyze(probe_address, probe_data, slews, loads) - + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + data.update(port_data[0]) + #Assumes single rw port (6t sram) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl0': [2.5829000000000004], - 'delay_lh0': [0.2255964], + golden_data = {'delay_hl': [2.5829000000000004], + 'delay_lh': [0.2255964], 'leakage_power': 0.0019498999999999996, 'min_period': 4.844, 'read0_power0': [0.055371399999999994], @@ -63,16 +65,16 @@ class timing_sram_test(openram_test): 'write0_power0': [0.06545659999999999], 'write1_power0': [0.057846299999999996]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl0': [3.452], - 'delay_lh0': [1.3792000000000002], + golden_data = {'delay_hl': [3.452], + 'delay_lh': [1.3792000000000002], 'leakage_power': 0.0257065, 'min_period': 4.688, - 'read0_power0': [15.0755], - 'read1_power0': [14.4526], - 'slew_hl0': [0.6137363], - 'slew_lh0': [0.3381045], - 'write0_power0': [16.9203], - 'write1_power0': [15.367]} + 'read0_power': [15.0755], + 'read1_power': [14.4526], + 'slew_hl': [0.6137363], + 'slew_lh': [0.3381045], + 'write0_power': [16.9203], + 'write1_power': [15.367]} else: self.assertTrue(False) # other techs fail # Check if no too many or too few results diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index cf7f096b..1d1fa3eb 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -48,11 +48,13 @@ class timing_sram_test(openram_test): import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] - data = d.analyze(probe_address, probe_data, slews, loads) + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl0': [2.584251], - 'delay_lh0': [0.22870469999999998], + golden_data = {'delay_hl': [2.584251], + 'delay_lh': [0.22870469999999998], 'leakage_power': 0.0009567935, 'min_period': 4.844, 'read0_power0': [0.0547588], @@ -62,16 +64,16 @@ class timing_sram_test(openram_test): 'write0_power0': [0.06513271999999999], 'write1_power0': [0.058057000000000004]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl0': [3.644147], - 'delay_lh0': [1.629815], + golden_data = {'delay_hl': [3.644147], + 'delay_lh': [1.629815], 'leakage_power': 0.0009299118999999999, 'min_period': 4.688, - 'read0_power0': [16.28732], - 'read1_power0': [15.75155], - 'slew_hl0': [0.6722473], - 'slew_lh0': [0.3386347], - 'write0_power0': [18.545450000000002], - 'write1_power0': [16.81084]} + 'read0_power': [16.28732], + 'read1_power': [15.75155], + 'slew_hl': [0.6722473], + 'slew_lh': [0.3386347], + 'write0_power': [18.545450000000002], + 'write1_power': [16.81084]} else: self.assertTrue(False) # other techs fail