From 25544c3974f41762a7f593470c511ceb91859b09 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 14 Dec 2020 13:59:31 -0800 Subject: [PATCH] Added similar interface to linear regression as elmore --- compiler/characterizer/delay.py | 76 +--------------- compiler/characterizer/elmore.py | 2 +- compiler/characterizer/lib.py | 20 ++--- compiler/characterizer/linear_regression.py | 88 ++++++++++++++++--- .../sim_data/{data.csv => delay_data.csv} | 0 5 files changed, 85 insertions(+), 101 deletions(-) rename technology/scn4m_subm/sim_data/{data.csv => delay_data.csv} (100%) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 1535ebc5..49e68af7 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -510,7 +510,7 @@ class delay(simulation): 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) + debug.error("Unrecognized 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: @@ -1270,80 +1270,6 @@ 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 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("In analytical mode, all ports have the timing of the first read port.") - - # Probe set to 0th bit, does not matter for analytical delay. - self.set_probe('0' * self.addr_size, 0) - self.create_graph() - self.set_internal_spice_names() - self.create_measurement_names() - - port = self.read_ports[0] - self.graph.get_all_paths('{}{}'.format("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] - - # Set delay/power for slews and loads - port_data = self.get_empty_measure_data_dict() - power = self.analytical_power(slews, loads) - debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)') - max_delay = 0.0 - for slew in slews: - for load in loads: - # Calculate delay based on slew and load - path_delays = self.graph.get_timing(bl_path, self.corner, slew, load) - - total_delay = self.sum_delays(path_delays) - max_delay = max(max_delay, total_delay.delay) - debug.info(1, - '{}, {}, {}, {}'.format(slew, - load, - total_delay.delay / 1e3, - total_delay.slew / 1e3)) - - # Delay is only calculated on a single port and replicated for now. - 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 and port in self.read_ports: - port_data[port][mname].append(total_delay.delay / 1e3) - elif "slew" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.slew / 1e3) - else: - debug.error("Measurement name not recognized: {}".format(mname), 1) - - # Estimate the period as double the delay with margin - period_margin = 0.1 - sram_data = {"min_period": (max_delay / 1e3) * 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) - - def analytical_power(self, slews, loads): - """Get the dynamic and leakage power from the SRAM""" - - # slews unused, only last load is used - load = loads[-1] - power = self.sram.analytical_power(self.corner, load) - # convert from nW to mW - power.dynamic /= 1e6 - power.leakage /= 1e6 - debug.info(1, "Dynamic Power: {0} mW".format(power.dynamic)) - debug.info(1, "Leakage Power: {0} mW".format(power.leakage)) - return power - def gen_data(self): """ Generates the PWL data inputs for a simulation timing test. """ diff --git a/compiler/characterizer/elmore.py b/compiler/characterizer/elmore.py index 80ced306..7dbb4abd 100644 --- a/compiler/characterizer/elmore.py +++ b/compiler/characterizer/elmore.py @@ -30,7 +30,7 @@ class elmore(simulation): self.create_signal_names() self.add_graph_exclusions() - def analytical_delay(self, slews, loads): + def get_lib_values(self, slews, loads): """ Return the analytical model results for the SRAM. """ diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 6fdcb1bf..679010fe 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -591,20 +591,10 @@ class lib: else: debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name)) import math - #FIXME: ML models only designed for delay. Cannot produce all values for Lib - m = model() - #temp_wpr = 2.0 #OPTS not working right now - log_num_words = math.log(OPTS.num_words, 2) - debug.info(1, "OPTS.words_per_row={}".format(OPTS.words_per_row)) - model_inputs = [log_num_words, - OPTS.word_size, - OPTS.words_per_row, - self.sram.width * self.sram.height] - char_results = m.get_predictions(model_inputs) - - #self.d = elmore(self.sram, self.sp_file, self.corner) - # char_results = self.d.analytical_delay(self.slews,self.loads) - # self.char_sram_results, self.char_port_results = char_results + + m = model(self.sram, self.sp_file, self.corner) + char_results = m.get_lib_values(self.slews,self.loads) + else: self.d = delay(self.sram, self.sp_file, self.corner) if (self.sram.num_spare_rows == 0): @@ -613,7 +603,7 @@ class lib: probe_address = "0" + "1" * (self.sram.addr_size - 1) probe_data = self.sram.word_size - 1 char_results = self.d.analyze(probe_address, probe_data, self.slews, self.loads) - self.char_sram_results, self.char_port_results = char_results + 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 """ diff --git a/compiler/characterizer/linear_regression.py b/compiler/characterizer/linear_regression.py index 6f63c3c4..2e02bd7b 100644 --- a/compiler/characterizer/linear_regression.py +++ b/compiler/characterizer/linear_regression.py @@ -6,32 +6,100 @@ # All rights reserved. # -import os -from sklearn.linear_model import LinearRegression from .analytical_util import * +from .simulation import simulation from globals import OPTS import debug +import os +from sklearn.linear_model import LinearRegression +import math + relative_data_path = "/sim_data" -delay_data_filename = "data.csv" -power_data_filename = "power_data.csv" +data_fnames = ["delay_data.csv", + "power_data.csv", + "leakage_data.csv", + "slew_data.csv"] tech_path = os.environ.get('OPENRAM_TECH') data_dir = tech_path+'/'+OPTS.tech_name+relative_data_path +data_paths = [data_dir +'/'+fname for fname in data_fnames] -class linear_regression(): +class linear_regression(simulation): - def __init__(self): + def __init__(self, sram, spfile, corner): + super().__init__(sram, spfile, corner) + self.set_corner(corner) + self.create_signal_names() + self.add_graph_exclusions() self.delay_model = None + self.slew_model = None self.power_model = None + self.leakage_model = None + + def get_lib_values(self, slews, loads): + """ + A model and prediction is created for each output needed for the LIB + """ + + log_num_words = math.log(OPTS.num_words, 2) + debug.info(1, "OPTS.words_per_row={}".format(OPTS.words_per_row)) + model_inputs = [log_num_words, + OPTS.word_size, + OPTS.words_per_row, + self.sram.width * self.sram.height] + + # List returned with value order being delay, power, leakage, slew + # FIXME: make order less hard coded + sram_vals = self.get_predictions(model_inputs) + + self.create_measurement_names() + + + # Set delay/power for slews and loads + port_data = self.get_empty_measure_data_dict() + debug.info(1, 'Slew, Load, Delay(ns), Slew(ns)') + max_delay = 0.0 + for slew in slews: + for load in loads: + # Calculate delay based on slew and load + debug.info(1, + '{}, {}, {}, {}'.format(slew, + load, + total_delay.delay / 1e3, + total_delay.slew / 1e3)) + + # Delay is only calculated on a single port and replicated for now. + for port in self.all_ports: + for mname in self.delay_meas_names + self.power_meas_names: + #FIXME: fix magic for indexing the data + if "power" in mname: + port_data[port][mname].append(sram_vals[1]) + elif "delay" in mname and port in self.read_ports: + port_data[port][mname].append(sram_vals[0]) + elif "slew" in mname and port in self.read_ports: + port_data[port][mname].append(sram_vals[3]) + else: + debug.error("Measurement name not recognized: {}".format(mname), 1) + + # Estimate the period as double the delay with margin + period_margin = 0.1 + sram_data = {"min_period": sram_vals[0] * 2, + "leakage_power": sram_vals[2]} + + debug.info(2, "SRAM Data:\n{}".format(sram_data)) + debug.info(2, "Port Data:\n{}".format(port_data)) + + return (sram_data, port_data) def get_predictions(self, model_inputs): + """ + Generate a model and prediction for LIB output + """ - delay_file_path = data_dir +'/'+delay_data_filename - power_file_path = data_dir +'/'+power_data_filename - scaled_inputs = np.asarray([scale_input_datapoint(model_inputs, delay_file_path)]) + scaled_inputs = np.asarray([scale_input_datapoint(model_inputs, data_paths[0])]) predictions = [] - for path, model in zip([delay_file_path, power_file_path], [self.delay_model, self.power_model]): + for path in data_paths: features, labels = get_scaled_data(path) model = self.generate_model(features, labels) scaled_pred = self.model_prediction(model, scaled_inputs) diff --git a/technology/scn4m_subm/sim_data/data.csv b/technology/scn4m_subm/sim_data/delay_data.csv similarity index 100% rename from technology/scn4m_subm/sim_data/data.csv rename to technology/scn4m_subm/sim_data/delay_data.csv