diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index 9ddbe655..a3ef902a 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -111,6 +111,7 @@ class trim_spice(): match of the line with a term so you can search for a single net connection, the instance name, anything.. """ + removed_insts = 0 #Expects keep_inst_list are regex patterns. Compile them here. compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list] @@ -127,11 +128,14 @@ class trim_spice(): new_buffer.append(line) in_subckt=False elif in_subckt: + removed_insts += 1 for pattern in compiled_patterns: if pattern.search(line) != None: new_buffer.append(line) + removed_insts -= 1 break else: new_buffer.append(line) self.sp_buffer = new_buffer + debug.info(2, "Removed {} instances from {} subcircuit.".format(removed_insts, subckt_name)) diff --git a/compiler/characterizer/worst_case.py b/compiler/characterizer/worst_case.py index 351b24e4..c0058dda 100644 --- a/compiler/characterizer/worst_case.py +++ b/compiler/characterizer/worst_case.py @@ -7,8 +7,9 @@ from .trim_spice import * from .charutils import * import utils from globals import OPTS +from .delay import delay -class worst_case(): +class worst_case(delay): """Functions to test for the worst case delay in a target SRAM The current worst case determines a feasible period for the SRAM then tests @@ -17,650 +18,9 @@ class worst_case(): """ def __init__(self, sram, spfile, corner): - self.sram = sram - self.name = sram.name - self.word_size = self.sram.word_size - self.addr_size = self.sram.addr_size - self.num_cols = self.sram.num_cols - self.num_rows = self.sram.num_rows - self.num_banks = self.sram.num_banks - self.sp_file = spfile + delay.__init__(self,sram,spfile,corner) - self.total_ports = self.sram.total_ports - self.total_write = self.sram.total_write - self.total_read = self.sram.total_read - self.read_index = self.sram.read_index - self.write_index = self.sram.write_index - self.port_id = self.sram.port_id - - # These are the member variables for a simulation - self.period = 0 - self.set_load_slew(0,0) - self.set_corner(corner) - self.create_port_names() - self.create_signal_names() - - #Create global measure names. Should maybe be an input at some point. - self.create_measurement_names() - - def create_measurement_names(self): - """Create measurement names. The names themselves currently define the type of measurement""" - #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. - self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"] - self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] - - def create_signal_names(self): - self.addr_name = "A" - self.din_name = "DIN" - self.dout_name = "DOUT" - - #This is TODO once multiport control has been finalized. - #self.control_name = "CSB" - - def create_port_names(self): - """Generates the port names to be used in characterization and sets default simulation target ports""" - self.write_ports = [] - self.read_ports = [] - self.total_port_num = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports - - #save a member variable to avoid accessing global. readwrite ports have different control signals. - self.readwrite_port_num = OPTS.num_rw_ports - - #Generate the port names. readwrite ports are required to be added first for this to work. - for readwrite_port_num in range(OPTS.num_rw_ports): - self.read_ports.append(readwrite_port_num) - self.write_ports.append(readwrite_port_num) - #This placement is intentional. It makes indexing input data easier. See self.data_values - for write_port_num in range(OPTS.num_rw_ports, OPTS.num_rw_ports+OPTS.num_w_ports): - self.write_ports.append(write_port_num) - for read_port_num in range(OPTS.num_rw_ports+OPTS.num_w_ports, OPTS.num_rw_ports+OPTS.num_w_ports+OPTS.num_r_ports): - self.read_ports.append(read_port_num) - - #Set the default target ports for simulation. Default is all the ports. - self.targ_read_ports = self.read_ports - self.targ_write_ports = self.write_ports - - def set_corner(self,corner): - """ Set the corner values """ - self.corner = corner - (self.process, self.vdd_voltage, self.temperature) = corner - - def set_load_slew(self,load,slew): - """ Set the load and slew """ - self.load = load - self.slew = slew - - def check_arguments(self): - """Checks if arguments given for write_stimulus() meets requirements""" - try: - int(self.probe_address, 2) - except ValueError: - debug.error("Probe Address is not of binary form: {0}".format(self.probe_address),1) - - if len(self.probe_address) != self.addr_size: - debug.error("Probe Address's number of bits does not correspond to given SRAM",1) - - if not isinstance(self.probe_data, int) or self.probe_data>self.word_size or self.probe_data<0: - debug.error("Given probe_data is not an integer to specify a data bit",1) - - #Adding port options here which the characterizer cannot handle. Some may be added later like ROM - if len(self.read_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without read ports.",1) - if len(self.write_ports) == 0: - debug.error("Characterizer does not currently support SRAMs without write ports.",1) - - def write_generic_stimulus(self): - """ Create the instance, supplies, loads, and access transistors. """ - - # add vdd/gnd statements - self.sf.write("\n* Global Power Supplies\n") - self.stim.write_supply() - - # instantiate the sram - self.sf.write("\n* Instantiation of the SRAM\n") - self.stim.inst_sram(sram=self.sram, - port_signal_names=(self.addr_name,self.din_name,self.dout_name), - port_info=(self.total_port_num,self.write_ports,self.read_ports), - abits=self.addr_size, - dbits=self.word_size, - sram_name=self.name) - self.sf.write("\n* SRAM output loads\n") - for port in self.read_ports: - for i in range(self.word_size): - self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port,i,self.dout_name,self.load)) - - - def write_delay_stimulus(self): - """ Creates a stimulus file for simulations to probe a bitcell at a given clock period. - Address and bit were previously set with set_probe(). - Input slew (in ns) and output capacitive load (in fF) are required for charaterization. - """ - self.check_arguments() - - # obtains list of time-points for each rising clk edge - self.create_test_cycles() - - # creates and opens stimulus file for writing - temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) - self.sf = open(temp_stim, "w") - self.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period, - self.load, - self.slew)) - self.stim = stimuli(self.sf, self.corner) - # include files in stimulus file - self.stim.write_include(self.trim_sp_file) - - self.write_generic_stimulus() - - # generate data and addr signals - self.sf.write("\n* Generation of data and address signals\n") - self.gen_data() - self.gen_addr() - - - # generate control signals - self.sf.write("\n* Generation of control signals\n") - self.gen_control() - - self.sf.write("\n* Generation of Port clock signal\n") - for port in range(self.total_port_num): - self.stim.gen_pulse(sig_name="CLK{0}".format(port), - v1=0, - v2=self.vdd_voltage, - offset=self.period, - period=self.period, - t_rise=self.slew, - t_fall=self.slew) - - self.write_delay_measures() - - # run until the end of the cycle time - self.stim.write_control(self.cycle_times[-1] + self.period) - - self.sf.close() - - - def write_power_stimulus(self, trim): - """ Creates a stimulus file to measure leakage power only. - This works on the *untrimmed netlist*. - """ - self.check_arguments() - - # obtains list of time-points for each rising clk edge - #self.create_test_cycles() - - # creates and opens stimulus file for writing - temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) - self.sf = open(temp_stim, "w") - self.sf.write("* Power stimulus for period of {0}n\n\n".format(self.period)) - self.stim = stimuli(self.sf, self.corner) - - # include UNTRIMMED files in stimulus file - if trim: - self.stim.write_include(self.trim_sp_file) - else: - self.stim.write_include(self.sim_sp_file) - - self.write_generic_stimulus() - - # generate data and addr signals - self.sf.write("\n* Generation of data and address signals\n") - for write_port in self.write_ports: - for i in range(self.word_size): - self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i), - v_val=0) - for port in range(self.total_port_num): - for i in range(self.addr_size): - self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name,port, i), - v_val=0) - - # generate control signals - self.sf.write("\n* Generation of control signals\n") - for port in range(self.total_port_num): - self.stim.gen_constant(sig_name="CSB{0}".format(port), v_val=self.vdd_voltage) - if port in self.write_ports and port in self.read_ports: - self.stim.gen_constant(sig_name="WEB{0}".format(port), v_val=self.vdd_voltage) - - self.sf.write("\n* Generation of global clock signal\n") - for port in range(self.total_port_num): - self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0) - - self.write_power_measures() - - # run until the end of the cycle time - self.stim.write_control(2*self.period) - - 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{0}".format(port) - meas_name="{0}{1}".format(delay_name, port) - targ_name = "{0}".format("{0}{1}_{2}".format(self.dout_name,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. - """ - # add measure statements for delays/slews - for dname in self.delay_meas_names: - meas_values = self.get_delay_meas_values(dname, port) - self.stim.gen_meas_delay(*meas_values) - - # add measure statements for power - for pname in self.power_meas_names: - if "read" not in pname: - continue - #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) - - def write_delay_measures_write_port(self, port): - """ - Write the measure statements to quantify the power results for a write port. - """ - # add measure statements for power - for pname in self.power_meas_names: - if "write" not in pname: - continue - t_initial = self.cycle_times[self.measure_cycles["write0_{0}".format(port)]] - t_final = self.cycle_times[self.measure_cycles["write0_{0}".format(port)]+1] - if '1' in pname: - t_initial = self.cycle_times[self.measure_cycles["write1_{0}".format(port)]] - t_final = self.cycle_times[self.measure_cycles["write1_{0}".format(port)]+1] - - self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), - t_initial=t_initial, - t_final=t_final) - - def write_delay_measures(self): - """ - Write the measure statements to quantify the delay and power results for all targeted ports. - """ - self.sf.write("\n* Measure statements for delay and power\n") - - # Output some comments to aid where cycles start and - # what is happening - for comment in self.cycle_comments: - self.sf.write("* {}\n".format(comment)) - - for read_port in self.targ_read_ports: - self.write_delay_measures_read_port(read_port) - for write_port in self.targ_write_ports: - self.write_delay_measures_write_port(write_port) - - - def write_power_measures(self): - """ - Write the measure statements to quantify the leakage power only. - """ - - self.sf.write("\n* Measure statements for idle leakage power\n") - - # add measure statements for power - t_initial = self.period - t_final = 2*self.period - self.stim.gen_meas_power(meas_name="leakage_power", - t_initial=t_initial, - t_final=t_final) - - def find_feasible_period_one_port(self, port): - """ - Uses an initial period and finds a feasible period before we - run the binary search algorithm to find min period. We check if - the given clock period is valid and if it's not, we continue to - double the period until we find a valid period to use as a - starting point. - """ - debug.check(port in self.read_ports, "Characterizer requires a read port to determine a period.") - - feasible_period = float(tech.spice["feasible_period"]) - #feasible_period = float(2.5)#What happens if feasible starting point is wrong? - time_out = 9 - while True: - time_out -= 1 - if (time_out <= 0): - debug.error("Timed out, could not find a feasible period.",2) - - #Clear any write target ports and set read port - self.targ_write_ports = [] - self.targ_read_ports = [port] - success = False - - debug.info(1, "Trying feasible period: {0}ns on Port {1}".format(feasible_period, port)) - self.period = feasible_period - (success, results)=self.run_delay_simulation() - #Clear these target ports after simulation - self.targ_read_ports = [] - - if not success: - feasible_period = 2 * feasible_period - continue - - #Positions of measurements currently hardcoded. First 2 are delays, next 2 are slews - feasible_delays = [results[port][mname] for mname in self.delay_meas_names if "delay" in mname] - feasible_slews = [results[port][mname] for mname in self.delay_meas_names if "slew" in mname] - delay_str = "feasible_delay {0:.4f}ns/{1:.4f}ns".format(*feasible_delays) - slew_str = "slew {0:.4f}ns/{1:.4f}ns".format(*feasible_slews) - debug.info(2, "feasible_period passed for Port {3}: {0}ns {1} {2} ".format(feasible_period, - delay_str, - slew_str, - port)) - - if success: - debug.info(2, "Found feasible_period for port {0}: {1}ns".format(port, feasible_period)) - self.period = feasible_period - #Only return results related to input port. - return results[port] - - def find_feasible_period(self): - """ - Loops through all read ports determining the feasible period and collecting - delay information from each port. - """ - feasible_delays = [{} for i in range(self.total_port_num)] - self.period = float(tech.spice["feasible_period"]) - - #Get initial feasible delays from first port - feasible_delays[self.read_ports[0]] = self.find_feasible_period_one_port(self.read_ports[0]) - previous_period = self.period - - - #Loops through all the ports checks if the feasible period works. Everything restarts it if does not. - #Write ports do not produce delays which is why they are not included here. - i = 1 - while i < len(self.read_ports): - port = self.read_ports[i] - #Only extract port values from the specified port, not the entire results. - feasible_delays[port].update(self.find_feasible_period_one_port(port)) - #Function sets the period. Restart the entire process if period changes to collect accurate delays - if self.period > previous_period: - i = 0 - else: - i+=1 - previous_period = self.period - debug.info(1, "Found feasible_period: {0}ns".format(self.period)) - return feasible_delays - - - def parse_values(self, values_names, port, mult = 1.0): - """Parse multiple values in the timing output file. Optional multiplier. - Return a dict of the input names and values. Port used for parsing file. - """ - values = [] - all_values_floats = True - for vname in values_names: - #ngspice converts all measure characters to lowercase, not tested on other sims - value = parse_spice_list("timing", "{0}{1}".format(vname.lower(), port)) - #Check if any of the values fail to parse - if type(value)!=float: - all_values_floats = False - values.append(value) - - #Apply Multiplier only if all values are floats. Let other check functions handle this error. - if all_values_floats: - return {values_names[i]:values[i]*mult for i in range(len(values))} - else: - return {values_names[i]:values[i] for i in range(len(values))} - - def run_delay_simulation(self): - """ - This tries to simulate a period and checks if the result works. If - so, it returns True and the delays, slews, and powers. It - works on the trimmed netlist by default, so powers do not - include leakage of all cells. - """ - #Sanity Check - 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 - self.write_delay_stimulus() - - self.stim.run_sim() - - #Loop through all targeted ports and collect delays and powers. - #Too much duplicate code here. Try reducing - for port in self.targ_read_ports: - debug.info(2, "Check delay values for port {}".format(port)) - delay_names = ["{0}{1}".format(mname,port) for mname in self.delay_meas_names] - delay_names = [mname for mname in self.delay_meas_names] - delays = self.parse_values(delay_names, port, 1e9) # scale delays to ns - if not self.check_valid_delays(tuple(delays.values())): - return (False,{}) - result[port].update(delays) - - power_names = [mname for mname in self.power_meas_names if 'read' in mname] - powers = self.parse_values(power_names, port, 1e3) # scale power to mw - #Check that power parsing worked. - for name, power in powers.items(): - if type(power)!=float: - debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad. - result[port].update(powers) - - for port in self.targ_write_ports: - power_names = [mname for mname in self.power_meas_names if 'write' in mname] - powers = self.parse_values(power_names, port, 1e3) # scale power to mw - #Check that power parsing worked. - for name, power in powers.items(): - if type(power)!=float: - debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad. - result[port].update(powers) - - # The delay is from the negative edge for our SRAM - return (True,result) - - - def run_power_simulation(self): - """ - This simulates a disabled SRAM to get the leakage power when it is off. - - """ - debug.info(1, "Performing leakage power simulations.") - self.write_power_stimulus(trim=False) - self.stim.run_sim() - leakage_power=parse_spice_list("timing", "leakage_power") - debug.check(leakage_power!="Failed","Could not measure leakage power.") - debug.info(1, "Leakage power of full array is {0} mW".format(leakage_power*1e3)) - #debug - #sys.exit(1) - - self.write_power_stimulus(trim=True) - self.stim.run_sim() - trim_leakage_power=parse_spice_list("timing", "leakage_power") - debug.check(trim_leakage_power!="Failed","Could not measure leakage power.") - debug.info(1, "Leakage power of trimmed array is {0} mW".format(trim_leakage_power*1e3)) - - # For debug, you sometimes want to inspect each simulation. - #key=raw_input("press return to continue") - return (leakage_power*1e3, trim_leakage_power*1e3) - - def check_valid_delays(self, delay_tuple): - """ Check if the measurements are defined and if they are valid. """ - - (delay_hl, delay_lh, slew_hl, slew_lh) = delay_tuple - period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew) - - # if it failed or the read was longer than a period - if type(delay_hl)!=float or type(delay_lh)!=float or type(slew_lh)!=float or type(slew_hl)!=float: - delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh) - slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh) - debug.info(2,"Failed simulation (in sec):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) - return False - - delays_str = "delay_hl={0} delay_lh={1}".format(delay_hl, delay_lh) - slews_str = "slew_hl={0} slew_lh={1}".format(slew_hl,slew_lh) - if delay_hl>self.period or delay_lh>self.period or slew_hl>self.period or slew_lh>self.period: - debug.info(2,"UNsuccessful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) - return False - else: - debug.info(2,"Successful simulation (in ns):\n\t\t{0}\n\t\t{1}\n\t\t{2}".format(period_load_slew_str, - delays_str, - slews_str)) - - return True - - def find_min_period(self, feasible_delays): - """ - Determine a single minimum period for all ports. - """ - - feasible_period = ub_period = self.period - lb_period = 0.0 - target_period = 0.5 * (ub_period + lb_period) - - #Find the minimum period for all ports. Start at one port and perform binary search then use that delay as a starting position. - #For testing purposes, only checks read ports. - for port in self.read_ports: - target_period = self.find_min_period_one_port(feasible_delays, port, lb_period, ub_period, target_period) - #The min period of one port becomes the new lower bound. Reset the upper_bound. - lb_period = target_period - ub_period = feasible_period - - #Clear the target ports before leaving - self.targ_read_ports = [] - self.targ_write_ports = [] - return target_period - - def find_min_period_one_port(self, feasible_delays, port, lb_period, ub_period, target_period): - """ - Searches for the smallest period with output delays being within 5% of - long period. For the current logic to characterize multiport, bounds are required as an input. - """ - - #previous_period = ub_period = self.period - #ub_period = self.period - #lb_period = 0.0 - #target_period = 0.5 * (ub_period + lb_period) - - # Binary search algorithm to find the min period (max frequency) of input port - time_out = 25 - self.targ_read_ports = [port] - while True: - time_out -= 1 - if (time_out <= 0): - debug.error("Timed out, could not converge on minimum period.",2) - - self.period = target_period - debug.info(1, "MinPeriod Search Port {3}: {0}ns (ub: {1} lb: {2})".format(target_period, - ub_period, - lb_period, - port)) - - if self.try_period(feasible_delays): - ub_period = target_period - else: - lb_period = target_period - - if relative_compare(ub_period, lb_period, error_tolerance=0.05): - # ub_period is always feasible. - return ub_period - - #Update target - target_period = 0.5 * (ub_period + lb_period) - - - def try_period(self, feasible_delays): - """ - This tries to simulate a period and checks if the result - works. If it does and the delay is within 5% still, it returns True. - """ - # Run Delay simulation but Power results not used. - (success, results) = self.run_delay_simulation() - if not success: - return False - - #Check the values of target readwrite and read ports. Write ports do not produce delays in this current version - for port in self.targ_read_ports: - delay_port_names = [mname for mname in self.delay_meas_names if "delay" in mname] - for dname in delay_port_names: - if not relative_compare(results[port][dname],feasible_delays[port][dname],error_tolerance=0.05): - debug.info(2,"Delay too big {0} vs {1}".format(results[port][dname],feasible_delays[port][dname])) - return False - - #key=raw_input("press return to continue") - - #Dynamic way to build string. A bit messy though. - delay_str = ', '.join("{0}={1}ns".format(mname, results[port][mname]) for mname in self.delay_meas_names) - debug.info(2,"Successful period {0}, Port {2}, {1}".format(self.period, - delay_str, - port)) - return True - - def set_probe(self,probe_address, probe_data): - """ Probe address and data can be set separately to utilize other - functions in this characterizer besides analyze.""" - self.probe_address = probe_address - self.probe_data = probe_data - - - - def prepare_netlist(self): - """ Prepare a trimmed netlist and regular netlist. """ - - # Set up to trim the netlist here if that is enabled - if OPTS.trim_netlist: - self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp) - self.trimsp=trim_spice(self.sp_file, self.trim_sp_file) - self.trimsp.set_configuration(self.num_banks, - self.num_rows, - self.num_cols, - self.word_size) - self.trimsp.trim(self.probe_address,self.probe_data) - else: - # The non-reduced netlist file when it is disabled - self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) - - # The non-reduced netlist file for power simulation - self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp) - # Make a copy in temp for debugging - shutil.copy(self.sp_file, self.sim_sp_file) - - - + def analyze(self,probe_address, probe_data, slews, loads): """ Main function to test the delays of different bits. @@ -671,7 +31,7 @@ class worst_case(): char_sram_data = {} self.set_probe(probe_address, probe_data) - self.prepare_netlist() + #self.prepare_netlist() self.load=max(loads) self.slew=max(slews) @@ -708,340 +68,10 @@ class worst_case(): def get_test_bits(self): """Statically determines address and bit values to test""" - #First and last address, first and last bit - bit_addrs = ["0"*self.addr_size, "1"*self.addr_size] - data_positions = [0, self.word_size-1] + #First and last address, first middle, and last bit. Last bit is repeated twice with different data position. + bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size] + data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1] #Return them in a tuple form return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))] - def simulate_loads_and_slews(self, slews, loads, leakage_offset): - """Simulate all specified output loads and input slews pairs of all ports""" - measure_data = self.get_empty_measure_data_dict() - #Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways. - self.targ_read_ports = self.read_ports - self.targ_write_ports = self.write_ports - for slew in slews: - for load in loads: - self.set_load_slew(load,slew) - # Find the delay, dynamic power, and leakage power of the trimmed array. - (success, delay_results) = self.run_delay_simulation() - debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(self.slew,self.load)) - debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew,self.load)) - #The results has a dict for every port but dicts can be empty (e.g. ports were not targeted). - for port in range(self.total_port_num): - for mname,value in delay_results[port].items(): - if "power" in mname: - # Subtract partial array leakage and add full array leakage for the power measures - measure_data[port][mname].append(value + leakage_offset) - else: - measure_data[port][mname].append(value) - return measure_data - - def add_data(self, data, port): - """ Add the array of data values """ - debug.check(len(data)==self.word_size, "Invalid data word size.") - debug.check(port < len(self.data_values), "Port number cannot index data values.") - index = 0 - for c in data: - if c=="0": - self.data_values[port][index].append(0) - elif c=="1": - self.data_values[port][index].append(1) - else: - debug.error("Non-binary data string",1) - index += 1 - - def add_address(self, address, port): - """ Add the array of address values """ - debug.check(len(address)==self.addr_size, "Invalid address size.") - index = 0 - for c in address: - if c=="0": - self.addr_values[port][index].append(0) - elif c=="1": - self.addr_values[port][index].append(1) - else: - debug.error("Non-binary address string",1) - index += 1 - def add_noop_one_port(self, address, data, port): - """ Add the control values for a noop to a single port. """ - #This is to be used as a helper function for the other add functions. Cycle and comments are omitted. - self.add_control_one_port(port, "noop") - if port in self.write_ports: - self.add_data(data,port) - self.add_address(address, port) - - def add_noop_all_ports(self, comment, address, data): - """ Add the control values for a noop to all ports. """ - self.add_comment("All", comment) - self.cycle_times.append(self.t_current) - self.t_current += self.period - - for port in range(self.total_port_num): - self.add_noop_one_port(address, data, port) - - - def add_read(self, comment, address, data, port): - """ Add the control values for a read cycle. """ - debug.check(port in self.read_ports, "Cannot add read cycle to a write port.") - self.add_comment(port, comment) - self.cycle_times.append(self.t_current) - self.t_current += self.period - self.add_control_one_port(port, "read") - - #If the port is also a readwrite then add data. - if port in self.write_ports: - self.add_data(data,port) - self.add_address(address, port) - - #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port - noop_data = "0"*self.word_size - #Add noops to all other ports. - for unselected_port in range(self.total_port_num): - if unselected_port != port: - self.add_noop_one_port(address, noop_data, unselected_port) - - def add_write(self, comment, address, data, port): - """ Add the control values for a write cycle. """ - debug.check(port in self.write_ports, "Cannot add read cycle to a read port.") - self.add_comment(port, comment) - self.cycle_times.append(self.t_current) - self.t_current += self.period - - self.add_control_one_port(port, "write") - self.add_data(data,port) - self.add_address(address,port) - - #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port - noop_data = "0"*self.word_size - #Add noops to all other ports. - for unselected_port in range(self.total_port_num): - if unselected_port != port: - self.add_noop_one_port(address, noop_data, unselected_port) - - def add_control_one_port(self, port, op): - """Appends control signals for operation to a given port""" - #Determine values to write to port - web_val = 1 - csb_val = 1 - if op == "read": - csb_val = 0 - elif op == "write": - csb_val = 0 - web_val = 0 - elif op != "noop": - debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1) - - #Append the values depending on the type of port - self.csb_values[port].append(csb_val) - #If port is in both lists, add rw control signal. Condition indicates its a RW port. - if port in self.write_ports and port in self.read_ports: - self.web_values[port].append(web_val) - - def add_comment(self, port, comment): - """Add comment to list to be printed in stimulus file""" - #Clean up time before appending. Make spacing dynamic as well. - time = "{0:.2f} ns:".format(self.t_current) - time_spacing = len(time)+6 - self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times), - port, - time, - time_spacing, - comment)) - def gen_test_cycles_one_port(self, read_port, write_port): - """Intended but not implemented: Returns a list of key time-points [ns] of the waveform (each rising edge) - of the cycles to do a timing evaluation of a single port. Current: Values overwritten for multiple calls""" - - # Create the inverse address for a scratch address - inverse_address = "" - for c in self.probe_address: - if c=="0": - inverse_address += "1" - elif c=="1": - inverse_address += "0" - else: - debug.error("Non-binary address string",1) - - # For now, ignore data patterns and write ones or zeros - data_ones = "1"*self.word_size - data_zeros = "0"*self.word_size - - if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros) - - self.add_write("W data 1 address 0..00", - inverse_address,data_ones,write_port) - - self.add_write("W data 0 address 11..11 to write value", - self.probe_address,data_zeros,write_port) - self.measure_cycles["write0_{0}".format(write_port)] = len(self.cycle_times)-1 - #self.write0_cycle=len(self.cycle_times)-1 # Remember for power measure - - # This also ensures we will have a H->L transition on the next read - self.add_read("R data 1 address 00..00 to set DOUT caps", - inverse_address,data_zeros,read_port) - - self.add_read("R data 0 address 11..11 to check W0 worked", - self.probe_address,data_zeros,read_port) - self.measure_cycles["read0_{0}".format(read_port)] = len(self.cycle_times)-1 - #self.read0_cycle=len(self.cycle_times)-1 # Remember for power measure - - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)", - inverse_address,data_zeros) - #Does not seem like is is used anywhere commenting out for now. - #self.idle_cycle=len(self.cycle_times)-1 # Remember for power measure - - self.add_write("W data 1 address 11..11 to write value", - self.probe_address,data_ones,write_port) - self.measure_cycles["write1_{0}".format(write_port)] = len(self.cycle_times)-1 - #self.write1_cycle=len(self.cycle_times)-1 # Remember for power measure - - self.add_write("W data 0 address 00..00 to clear DIN caps", - inverse_address,data_zeros,write_port) - - # This also ensures we will have a L->H transition on the next read - self.add_read("R data 0 address 00..00 to clear DOUT caps", - inverse_address,data_zeros,read_port) - - self.add_read("R data 1 address 11..11 to check W1 worked", - self.probe_address,data_zeros,read_port) - self.measure_cycles["read1_{0}".format(read_port)] = len(self.cycle_times)-1 - #self.read1_cycle=len(self.cycle_times)-1 # Remember for power measure - - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))", - self.probe_address,data_zeros) - - def get_available_port(self,get_read_port): - """Returns the first accessible read or write port. """ - if get_read_port and len(self.read_ports) > 0: - return self.read_ports[0] - elif not get_read_port and len(self.write_ports) > 0: - return self.write_ports[0] - return None - - def create_test_cycles(self): - """Returns a list of key time-points [ns] of the waveform (each rising edge) - of the cycles to do a timing evaluation. The last time is the end of the simulation - and does not need a rising edge.""" - #Using this requires setting at least one port to target for simulation. - if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0: - debug.error("No ports selected for characterization.",1) - - # Start at time 0 - self.t_current = 0 - - # Cycle times (positive edge) with comment - self.cycle_comments = [] - self.cycle_times = [] - self.measure_cycles = {} - - # Control signals for ports. These are not the final signals and will likely be changed later. - #web is the enable for write ports. Dicts used for simplicity as ports are not necessarily incremental. - self.web_values = {port:[] for port in self.write_ports} - #csb acts as an enable for the read ports. - self.csb_values = {port:[] for port in range(self.total_port_num)} - - # Address and data values for each address/data bit. A 3d list of size #ports x bits x cycles. - self.data_values=[[[] for bit in range(self.word_size)] for port in range(len(self.write_ports))] - self.addr_values=[[[] for bit in range(self.addr_size)] for port in range(self.total_port_num)] - - #Get any available read/write port in case only a single write or read ports is being characterized. - cur_read_port = self.get_available_port(get_read_port=True) - cur_write_port = self.get_available_port(get_read_port=False) - - #These checks should be superceded by check_arguments which should have been called earlier, so this is a double check. - debug.check(cur_read_port != None, "Characterizer requires at least 1 read port") - debug.check(cur_write_port != None, "Characterizer requires at least 1 write port") - - #Characterizing the remaining target ports. Not the final design. - write_pos = 0 - read_pos = 0 - while True: - #Exit when all ports have been characterized - if write_pos >= len(self.targ_write_ports) and read_pos >= len(self.targ_read_ports): - break - - #Select new write and/or read ports for the next cycle. Use previous port if none remaining. - if write_pos < len(self.targ_write_ports): - cur_write_port = self.targ_write_ports[write_pos] - write_pos+=1 - if read_pos < len(self.targ_read_ports): - cur_read_port = self.targ_read_ports[read_pos] - read_pos+=1 - - #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,sram, slews, loads): - """ Return the analytical model results for the SRAM. - """ - debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 , - "Analytical characterization does not currently support multiport.") - - delay_lh = [] - delay_hl = [] - slew_lh = [] - slew_hl = [] - for slew in slews: - for load in loads: - self.set_load_slew(load,slew) - bank_delay = sram.analytical_delay(self.slew,self.load) - # Convert from ps to ns - delay_lh.append(bank_delay.delay/1e3) - delay_hl.append(bank_delay.delay/1e3) - slew_lh.append(bank_delay.slew/1e3) - slew_hl.append(bank_delay.slew/1e3) - - power = sram.analytical_power(self.process, self.vdd_voltage, self.temperature, 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)) - - 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. """ - for write_port in self.write_ports: - for i in range(self.word_size): - sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i) - self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[write_port][i], self.period, self.slew, 0.05) - - def gen_addr(self): - """ - Generates the address inputs for a simulation timing test. - This alternates between all 1's and all 0's for the address. - """ - for port in range(self.total_port_num): - for i in range(self.addr_size): - sig_name = "{0}{1}_{2}".format(self.addr_name,port,i) - self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05) - - def gen_control(self): - """ Generates the control signals """ - for port in range(self.total_port_num): - self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) - if port in self.read_ports and port in self.write_ports: - self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) - - - def get_empty_measure_data_dict(self): - """Make a dict of lists for each type of delay and power measurement to append results to""" - measure_names = self.delay_meas_names + self.power_meas_names - #Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. - measure_data = [{mname:[] for mname in measure_names} for i in range(self.total_port_num)] - return measure_data diff --git a/compiler/sram.py b/compiler/sram.py index eafdf80a..5cbf80a3 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -86,7 +86,6 @@ class sram(): # Save the extracted spice file if OPTS.use_pex: import verify - print(verify.__file__) start_time = datetime.datetime.now() # Output the extracted design if requested sp_file = OPTS.output_path + "temp_pex.sp" diff --git a/compiler/tests/27_worst_case_delay_test.py b/compiler/tests/27_worst_case_delay_test.py index 06283109..35d48b27 100755 --- a/compiler/tests/27_worst_case_delay_test.py +++ b/compiler/tests/27_worst_case_delay_test.py @@ -11,13 +11,17 @@ 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="ngspice" + OPTS.spice_name="hspice" OPTS.analytical_delay = False 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 @@ -27,21 +31,36 @@ class worst_case_timing_sram_test(openram_test): if not OPTS.spice_exe: debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) + word_size, num_words, num_banks = 32, 32, 1 from sram import sram from sram_config import sram_config - c = sram_config(word_size=4, - num_words=32, - num_banks=1) - c.words_per_row=1 - debug.info(1, "Testing the timing for 2 bits inside a 2bit, 16words SRAM with 1 bank") + c = sram_config(word_size=word_size, + num_words=num_words, + num_banks=num_banks) + #c.words_per_row=1 + c.compute_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") - - tempspice = OPTS.openram_temp + "temp.sp" - s.sp_write(tempspice) - - + + 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 + else: + sp_sim_file = sp_netlist_file + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - wc = worst_case(s.s, tempspice, corner) + wc = worst_case(s.s, sp_sim_file, corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] diff --git a/compiler/tests/sram1.gds b/compiler/tests/sram1.gds deleted file mode 100644 index d6ed28eb..00000000 Binary files a/compiler/tests/sram1.gds and /dev/null differ diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 4ddd966c..1f0ffaab 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -54,7 +54,6 @@ else: if OPTS.pex_exe == None: from .none import run_pex,print_pex_stats - print("why god why") elif "calibre"==OPTS.pex_exe[0]: from .calibre import run_pex,print_pex_stats elif "magic"==OPTS.pex_exe[0]: