From fd806077d2b32f94c2efc8745eb4442bd68b789d Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 8 Oct 2018 08:42:32 -0700 Subject: [PATCH 01/13] Added class and test for testing the delay of several bitcells. --- compiler/characterizer/__init__.py | 1 + compiler/characterizer/worst_case.py | 1047 ++++++++++++++++++++ compiler/globals.py | 2 +- compiler/sram.py | 32 +- compiler/tests/27_worst_case_delay_test.py | 59 ++ compiler/verify/__init__.py | 1 + 6 files changed, 1126 insertions(+), 16 deletions(-) create mode 100644 compiler/characterizer/worst_case.py create mode 100755 compiler/tests/27_worst_case_delay_test.py diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index ea99c51c..53155e09 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -6,6 +6,7 @@ from .lib import * from .delay import * from .setup_hold import * from .functional import * +from .worst_case import * from .simulation import * diff --git a/compiler/characterizer/worst_case.py b/compiler/characterizer/worst_case.py new file mode 100644 index 00000000..351b24e4 --- /dev/null +++ b/compiler/characterizer/worst_case.py @@ -0,0 +1,1047 @@ +import sys,re,shutil +import debug +import tech +import math +from .stimuli import * +from .trim_spice import * +from .charutils import * +import utils +from globals import OPTS + +class worst_case(): + """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 + several bits and record the delay and differences between the bits. + + """ + + 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 + + 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. + """ + debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 , + "Bit testing does not currently support multiport.") + #Dict to hold all characterization values + char_sram_data = {} + + self.set_probe(probe_address, probe_data) + self.prepare_netlist() + + self.load=max(loads) + self.slew=max(slews) + + # 1) Find a feasible period and it's corresponding delays using the trimmed array. + feasible_delays = self.find_feasible_period() + + # 2) Find the delays of several bits + test_bits = self.get_test_bits() + bit_delays = self.simulate_for_bit_delays(test_bits) + + for delay in bit_delays: + debug.info(1, "{}".format(delay)) + + def simulate_for_bit_delays(self, test_bits): + """Simulates the delay of the sram of over several bits.""" + bit_delays = [{} for i in range(len(test_bits))] + + #Assumes a bitcell with only 1 rw port. (6t, port 0) + port = 0 + self.targ_read_ports = [self.read_ports[port]] + self.targ_write_ports = [self.write_ports[port]] + + for i in range(len(test_bits)): + (bit_addr, bit_data) = test_bits[i] + self.set_probe(bit_addr, bit_data) + debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data)) + (success, results)=self.run_delay_simulation() + debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data)) + bit_delays[i] = results[port] + + return bit_delays + + + 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] + #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/globals.py b/compiler/globals.py index af89eaa4..a23c8563 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -24,7 +24,7 @@ def parse_args(): global OPTS option_list = { - optparse.make_option("-b", "--backannotated", action="store_true", dest="run_pex", + optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex", help="Back annotate simulation"), optparse.make_option("-o", "--output", dest="output_name", help="Base output file name(s) prefix", metavar="FILE"), diff --git a/compiler/sram.py b/compiler/sram.py index 0feea1b3..eafdf80a 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -61,6 +61,21 @@ class sram(): def save(self): """ Save all the output files while reporting time to do it as well. """ + if not OPTS.netlist_only: + # Write the layout + start_time = datetime.datetime.now() + gdsname = OPTS.output_path + self.s.name + ".gds" + print("GDS: Writing to {0}".format(gdsname)) + self.s.gds_write(gdsname) + print_time("GDS", datetime.datetime.now(), start_time) + + # Create a LEF physical model + start_time = datetime.datetime.now() + lefname = OPTS.output_path + self.s.name + ".lef" + print("LEF: Writing to {0}".format(lefname)) + self.s.lef_write(lefname) + print_time("LEF", datetime.datetime.now(), start_time) + # Save the spice file start_time = datetime.datetime.now() spname = OPTS.output_path + self.s.name + ".sp" @@ -70,6 +85,8 @@ 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" @@ -93,21 +110,6 @@ class sram(): lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file) print_time("Characterization", datetime.datetime.now(), start_time) - if not OPTS.netlist_only: - # Write the layout - start_time = datetime.datetime.now() - gdsname = OPTS.output_path + self.s.name + ".gds" - print("GDS: Writing to {0}".format(gdsname)) - self.s.gds_write(gdsname) - print_time("GDS", datetime.datetime.now(), start_time) - - # Create a LEF physical model - start_time = datetime.datetime.now() - lefname = OPTS.output_path + self.s.name + ".lef" - print("LEF: Writing to {0}".format(lefname)) - self.s.lef_write(lefname) - print_time("LEF", datetime.datetime.now(), start_time) - # Write a verilog model start_time = datetime.datetime.now() vname = OPTS.output_path + self.s.name + ".v" diff --git a/compiler/tests/27_worst_case_delay_test.py b/compiler/tests/27_worst_case_delay_test.py new file mode 100755 index 00000000..06283109 --- /dev/null +++ b/compiler/tests/27_worst_case_delay_test.py @@ -0,0 +1,59 @@ +#!/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 + +class worst_case_timing_sram_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + OPTS.trim_netlist = False + + # 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) + + 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") + s = sram(c, name="sram1") + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + wc = worst_case(s.s, tempspice, 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() + +# instantiate a copdsay of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 1f0ffaab..4ddd966c 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -54,6 +54,7 @@ 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]: From a3bec5518cbe4afd47d5ae6eed579fa88b4b0918 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 8 Oct 2018 15:45:54 -0700 Subject: [PATCH 02/13] Put worst case test under the hierarchy of a delay test. Added option for pex option to worst case test. --- compiler/characterizer/trim_spice.py | 4 + compiler/characterizer/worst_case.py | 986 +-------------------- compiler/sram.py | 1 - compiler/tests/27_worst_case_delay_test.py | 43 +- compiler/tests/sram1.gds | Bin 304042 -> 0 bytes compiler/verify/__init__.py | 1 - 6 files changed, 43 insertions(+), 992 deletions(-) delete mode 100644 compiler/tests/sram1.gds 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 d6ed28eb6391318f026a98274c50b90601202712..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304042 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 SUFciqRxbEw86)q=Nbi5xVB-z| 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]: From 3ac2d29940c3bc6f87e1e74fb839177070689ca7 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 9 Oct 2018 17:44:28 -0700 Subject: [PATCH 03/13] Made delay.py a child of simulation.py. Removed duplicate code in delay and changed some in simulation --- compiler/characterizer/delay.py | 249 ++++----------------- compiler/characterizer/simulation.py | 32 +-- compiler/characterizer/worst_case.py | 4 +- compiler/example_config_scn4m_subm.py | 12 +- compiler/tests/27_worst_case_delay_test.py | 8 +- 5 files changed, 71 insertions(+), 234 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 0d5b347b..b76d2821 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -7,8 +7,9 @@ from .trim_spice import * from .charutils import * import utils from globals import OPTS +from .simulation import simulation -class delay(): +class delay(simulation): """Functions to measure the delay and power of an SRAM at a given address and data bit. @@ -26,27 +27,14 @@ class delay(): """ 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 - - 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 + simulation.__init__(self, sram, spfile, corner) # These are the member variables for a simulation + self.targ_read_ports = [] + self.targ_write_ports = [] 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. @@ -66,34 +54,6 @@ class delay(): #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 @@ -113,9 +73,9 @@ class delay(): 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: + if len(self.read_index) == 0: debug.error("Characterizer does not currently support SRAMs without read ports.",1) - if len(self.write_ports) == 0: + if len(self.write_index) == 0: debug.error("Characterizer does not currently support SRAMs without write ports.",1) def write_generic_stimulus(self): @@ -129,12 +89,12 @@ class delay(): 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), + port_info=(self.total_ports,self.write_index,self.read_index), 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 port in self.read_index: 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)) @@ -172,7 +132,7 @@ class delay(): self.gen_control() self.sf.write("\n* Generation of Port clock signal\n") - for port in range(self.total_port_num): + for port in range(self.total_ports): self.stim.gen_pulse(sig_name="CLK{0}".format(port), v1=0, v2=self.vdd_voltage, @@ -214,24 +174,24 @@ class delay(): # generate data and addr signals self.sf.write("\n* Generation of data and address signals\n") - for write_port in self.write_ports: + for write_port in self.write_index: 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 port in range(self.total_ports): 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): + for port in range(self.total_ports): 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: + if port in self.write_index and port in self.read_index: 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): + for port in range(self.total_ports): self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0) self.write_power_measures() @@ -360,10 +320,9 @@ class delay(): 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.") + debug.check(port in self.read_index, "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 @@ -406,19 +365,18 @@ class delay(): 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"]) + feasible_delays = [{} for i in range(self.total_ports)] #Get initial feasible delays from first port - feasible_delays[self.read_ports[0]] = self.find_feasible_period_one_port(self.read_ports[0]) + feasible_delays[self.read_index[0]] = self.find_feasible_period_one_port(self.read_index[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] + while i < len(self.read_index): + port = self.read_index[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 @@ -461,7 +419,7 @@ class delay(): #Sanity Check debug.check(self.period > 0, "Target simulation period non-positive") - result = [{} for i in range(self.total_port_num)] + result = [{} for i in range(self.total_ports)] # Checking from not data_value to data_value self.write_delay_stimulus() @@ -563,7 +521,7 @@ class delay(): #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: + for port in self.read_index: 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 @@ -728,8 +686,8 @@ class delay(): """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 + self.targ_read_ports = self.read_index + self.targ_write_ports = self.write_index for slew in slews: for load in loads: self.set_load_slew(load,slew) @@ -738,7 +696,7 @@ class delay(): 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 port in range(self.total_ports): 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 @@ -746,119 +704,8 @@ class delay(): 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""" @@ -925,11 +772,15 @@ class delay(): 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] + if get_read_port and len(self.read_index) > 0: + return self.read_index[0] + elif not get_read_port and len(self.write_index) > 0: + return self.write_index[0] return None + + def set_stimulus_variables(self): + simulation.set_stimulus_variables(self) + self.measure_cycles = {} def create_test_cycles(self): """Returns a list of key time-points [ns] of the waveform (each rising edge) @@ -937,31 +788,13 @@ class delay(): 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)] - + debug.error("No port selected for characterization.",1) + self.set_stimulus_variables() + #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") @@ -1026,7 +859,7 @@ class delay(): def gen_data(self): """ Generates the PWL data inputs for a simulation timing test. """ - for write_port in self.write_ports: + for write_port in self.write_index: 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) @@ -1036,16 +869,16 @@ class delay(): 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 port in range(self.total_ports): 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): + for port in range(self.total_ports): 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: + if port in self.read_index and port in self.write_index: self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) @@ -1053,5 +886,5 @@ class delay(): """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)] + measure_data = [{mname:[] for mname in measure_names} for i in range(self.total_ports)] return measure_data diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index c76d702e..d48e90e3 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -81,7 +81,6 @@ class simulation(): 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.") bit = self.word_size - 1 for c in data: @@ -109,12 +108,9 @@ class simulation(): def add_write(self, comment, address, data, port): """ Add the control values for a write cycle. """ - debug.info(1, comment) + debug.info(2, comment) debug.check(port in self.write_index, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_index)) - self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), - self.t_current, - comment, - port)) + self.append_cycle_comment(port, comment) self.cycle_times.append(self.t_current) self.t_current += self.period @@ -131,12 +127,9 @@ class simulation(): def add_read(self, comment, address, data, port): """ Add the control values for a read cycle. """ - debug.info(1, comment) + debug.info(2, comment) debug.check(port in self.read_index, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_index)) - self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments), - self.t_current, - comment, - port)) + self.append_cycle_comment(port, comment) self.cycle_times.append(self.t_current) self.t_current += self.period self.add_control_one_port(port, "read") @@ -152,13 +145,22 @@ class simulation(): for unselected_port in range(self.total_ports): if unselected_port != port: self.add_noop_one_port(address, noop_data, unselected_port) + + def append_cycle_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 add_noop_all_ports(self, comment, address, data): """ Add the control values for a noop to all ports. """ - debug.info(1, comment) - self.cycle_comments.append("Cycle {0:2d}\tPort All\t{1:5.2f}ns:\t{2}".format(len(self.cycle_times), - self.t_current, - comment)) + debug.info(2, comment) + self.append_cycle_comment("All", comment) self.cycle_times.append(self.t_current) self.t_current += self.period diff --git a/compiler/characterizer/worst_case.py b/compiler/characterizer/worst_case.py index c0058dda..6dad95d9 100644 --- a/compiler/characterizer/worst_case.py +++ b/compiler/characterizer/worst_case.py @@ -43,8 +43,8 @@ class worst_case(delay): test_bits = self.get_test_bits() bit_delays = self.simulate_for_bit_delays(test_bits) - for delay in bit_delays: - debug.info(1, "{}".format(delay)) + for i in range(len(test_bits)): + debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i])) def simulate_for_bit_delays(self, test_bits): """Simulates the delay of the sram of over several bits.""" diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index 92332fd5..f8bc7437 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -11,9 +11,9 @@ output_path = "temp" output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name) #Setting for multiport -# netlist_only = True -# bitcell = "pbitcell" -# replica_bitcell="replica_pbitcell" -# num_rw_ports = 1 -# num_r_ports = 1 -# num_w_ports = 0 +netlist_only = True +bitcell = "pbitcell" +replica_bitcell="replica_pbitcell" +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 diff --git a/compiler/tests/27_worst_case_delay_test.py b/compiler/tests/27_worst_case_delay_test.py index 35d48b27..cf999e45 100755 --- a/compiler/tests/27_worst_case_delay_test.py +++ b/compiler/tests/27_worst_case_delay_test.py @@ -31,14 +31,14 @@ 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 + 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.compute_sizes() + 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") @@ -56,8 +56,10 @@ class worst_case_timing_sram_test(openram_test): 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) From f30e54f33c53ea9570c8ea391e1c1f79390b4d35 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 10 Oct 2018 00:02:03 -0700 Subject: [PATCH 04/13] Cleaned up indexing in variable that records cycle times. --- compiler/characterizer/delay.py | 46 +++++++++++++-------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index b76d2821..c44407bb 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -155,9 +155,6 @@ class delay(simulation): """ 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") @@ -217,10 +214,10 @@ class delay(simulation): 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)]] + trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]] else: targ_dir="FALL" - trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]] + trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] elif 'slew' in delay_name: trig_name = targ_name @@ -228,12 +225,12 @@ class delay(simulation): 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)]] + trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]] 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)]] + trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] 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) @@ -254,11 +251,11 @@ class delay(simulation): #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] + t_initial = self.cycle_times[self.measure_cycles[port]["read1"]] + t_final = self.cycle_times[self.measure_cycles[port]["read1"]+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] + t_initial = self.cycle_times[self.measure_cycles[port]["read0"]] + t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1] self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), t_initial=t_initial, t_final=t_final) @@ -271,11 +268,11 @@ class delay(simulation): 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] + t_initial = self.cycle_times[self.measure_cycles[port]["write0"]] + t_final = self.cycle_times[self.measure_cycles[port]["write0"]+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] + t_initial = self.cycle_times[self.measure_cycles[port]["write1"]] + t_final = self.cycle_times[self.measure_cycles[port]["write1"]+1] self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), t_initial=t_initial, @@ -733,8 +730,7 @@ class delay(simulation): 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 + self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1 # 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", @@ -742,18 +738,14 @@ class delay(simulation): 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.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 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.measure_cycles[write_port]["write1"] = len(self.cycle_times)-1 self.add_write("W data 0 address 00..00 to clear DIN caps", inverse_address,data_zeros,write_port) @@ -764,8 +756,7 @@ class delay(simulation): 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.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1 self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))", self.probe_address,data_zeros) @@ -780,7 +771,7 @@ class delay(simulation): def set_stimulus_variables(self): simulation.set_stimulus_variables(self) - self.measure_cycles = {} + self.measure_cycles = [{} for port in range(self.total_ports)] def create_test_cycles(self): """Returns a list of key time-points [ns] of the waveform (each rising edge) @@ -794,11 +785,10 @@ class delay(simulation): #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) - 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. + #Create test cycles for specified target ports. write_pos = 0 read_pos = 0 while True: From 4f0806226825823ba372e626d495230ce10799ff Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 22 Oct 2018 17:02:21 -0700 Subject: [PATCH 05/13] Added custom 1rw+1r bitcell. Testing are currently failing. --- compiler/base/utils.py | 3 + compiler/example_config_scn4m_subm.py | 12 +- compiler/gdsMill/gdsMill/vlsiLayout.py | 4 +- compiler/modules/bitcell_1rw_1r.py | 98 ++++++++++++ compiler/sram_config.py | 2 +- compiler/tests/04_bitcell_1rw_1r_test.py | 42 +++++ technology/freepdk45/gds_lib/cell_1rw_1r.gds | Bin 0 -> 16384 bytes technology/freepdk45/sp_lib/cell_1rw_1r.sp | 14 ++ technology/scn4m_subm/mag_lib/cell_1rw_1r.mag | 146 ++++++++++++++++++ 9 files changed, 312 insertions(+), 9 deletions(-) create mode 100644 compiler/modules/bitcell_1rw_1r.py create mode 100755 compiler/tests/04_bitcell_1rw_1r_test.py create mode 100644 technology/freepdk45/gds_lib/cell_1rw_1r.gds create mode 100644 technology/freepdk45/sp_lib/cell_1rw_1r.sp create mode 100644 technology/scn4m_subm/mag_lib/cell_1rw_1r.mag diff --git a/compiler/base/utils.py b/compiler/base/utils.py index b13f2f7e..28c5f997 100644 --- a/compiler/base/utils.py +++ b/compiler/base/utils.py @@ -3,6 +3,7 @@ import gdsMill import tech import math import globals +import debug from vector import vector from pin_layout import pin_layout @@ -65,6 +66,7 @@ def get_gds_size(name, gds_filename, units, layer): Open a GDS file and return the size from either the bounding box or a border layer. """ + debug.info(2,"Creating VLSI layout for {}".format(name)) cell_vlsi = gdsMill.VlsiLayout(units=units) reader = gdsMill.Gds2reader(cell_vlsi) reader.loadFromFile(gds_filename) @@ -72,6 +74,7 @@ def get_gds_size(name, gds_filename, units, layer): cell = {} measure_result = cell_vlsi.getLayoutBorder(layer) if measure_result == None: + debug.info(2,"Layout border failed. Trying to measure size for {}".format(name)) measure_result = cell_vlsi.measureSize(name) # returns width,height return measure_result diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index f8bc7437..92332fd5 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -11,9 +11,9 @@ output_path = "temp" output_name = "sram_{0}_{1}_{2}_{3}".format(word_size,num_words,num_banks,tech_name) #Setting for multiport -netlist_only = True -bitcell = "pbitcell" -replica_bitcell="replica_pbitcell" -num_rw_ports = 1 -num_r_ports = 1 -num_w_ports = 0 +# netlist_only = True +# bitcell = "pbitcell" +# replica_bitcell="replica_pbitcell" +# num_rw_ports = 1 +# num_r_ports = 1 +# num_w_ports = 0 diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 5e12619c..99a1b9e5 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -155,10 +155,10 @@ class VlsiLayout: def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)): #since this is a recursive function, must deal with the default - #parameters explicitly + #parameters explicitly if startingStructureName == None: startingStructureName = self.rootStructureName - + #set up the rotation matrix if(rotateAngle == None or rotateAngle == ""): angle = 0 diff --git a/compiler/modules/bitcell_1rw_1r.py b/compiler/modules/bitcell_1rw_1r.py new file mode 100644 index 00000000..9cec7d8d --- /dev/null +++ b/compiler/modules/bitcell_1rw_1r.py @@ -0,0 +1,98 @@ +import design +import debug +import utils +from tech import GDS,layer + +class bitcell_1rw_1r(design.design): + """ + A single bit cell (6T, 8T, etc.) This module implements the + single memory cell used in the design. It is a hand-made cell, so + the layout and netlist should be available in the technology + library. + """ + + pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] + (width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"], layer["boundary"]) + + def __init__(self): + design.design.__init__(self, "cell_1rw_1r") + debug.info(2, "Create bitcell with 1RW and 1R Port") + + self.width = bitcell.width + self.height = bitcell.height + self.pin_map = bitcell.pin_map + + def analytical_delay(self, 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(r = r, c = c_para+load, slew = slew, swing = swing) + return result + + + def list_bitcell_pins(self, col, row): + """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + bitcell_pins = ["bl0[{0}]".format(col), + "br0[{0}]".format(col), + "bl1[{0}]".format(col), + "br1[{0}]".format(col), + "wl0[{0}]".format(row), + "wl1[{0}]".format(row), + "vdd", + "gnd"] + return bitcell_pins + + def list_all_wl_names(self): + """ Creates a list of all wordline pin names """ + row_pins = ["wl0", "wl1"] + return row_pins + + def list_all_bitline_names(self): + """ Creates a list of all bitline pin names (both bl and br) """ + column_pins = ["bl0", "br0", "bl1", "br1"] + return column_pins + + def list_all_bl_names(self): + """ Creates a list of all bl pins names """ + column_pins = ["bl0", "bl1"] + return column_pins + + def list_all_br_names(self): + """ Creates a list of all br pins names """ + column_pins = ["br0", "br1"] + return column_pins + + def list_read_bl_names(self): + """ Creates a list of bl pin names associated with read ports """ + column_pins = ["bl0", "bl1"] + return column_pins + + def list_read_br_names(self): + """ Creates a list of br pin names associated with read ports """ + column_pins = ["br0", "br1"] + return column_pins + + def list_write_bl_names(self): + """ Creates a list of bl pin names associated with write ports """ + column_pins = ["bl0"] + return column_pins + + def list_write_br_names(self): + """ Creates a list of br pin names asscociated with write ports""" + column_pins = ["br0"] + return column_pins + + def analytical_power(self, proc, vdd, temp, load): + """Bitcell power in nW. Only characterizes leakage.""" + from tech import spice + leakage = spice["bitcell_leakage"] + dynamic = 0 #temporary + total_power = self.return_power(dynamic, leakage) + return total_power + diff --git a/compiler/sram_config.py b/compiler/sram_config.py index e7c80fd8..3c3892a5 100644 --- a/compiler/sram_config.py +++ b/compiler/sram_config.py @@ -53,7 +53,7 @@ class sram_config: # Estimate the number of rows given the tentative words per row self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row*self.word_size) self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, self.words_per_row) - + # Fix the number of columns and rows self.num_cols = int(self.words_per_row*self.word_size) self.num_rows = int(self.num_words_per_bank/self.words_per_row) diff --git a/compiler/tests/04_bitcell_1rw_1r_test.py b/compiler/tests/04_bitcell_1rw_1r_test.py new file mode 100755 index 00000000..567cd291 --- /dev/null +++ b/compiler/tests/04_bitcell_1rw_1r_test.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Run regresion tests on a parameterized bitcell +""" + +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 + +OPTS = globals.OPTS + +#@unittest.skip("SKIPPING 04_bitcell_1rw_1r_test") +class bitcell_1rw_1r_test(openram_test): + + def runTest(self): + OPTS.bitcell = "bitcell_1rw_1r" + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + from bitcell import bitcell + from bitcell_1rw_1r import bitcell_1rw_1r + import tech + OPTS.num_rw_ports=1 + OPTS.num_w_ports=0 + OPTS.num_r_ports=1 + debug.info(2, "Bitcell with 1 read/write and 1 read port") + #tx = bitcell_1rw_1r() + tx = bitcell() + self.local_check(tx) + + globals.end_openram() + + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/technology/freepdk45/gds_lib/cell_1rw_1r.gds b/technology/freepdk45/gds_lib/cell_1rw_1r.gds new file mode 100644 index 0000000000000000000000000000000000000000..9f4e0650acba732fa94328acdb17a17f8c615e56 GIT binary patch literal 16384 zcmeHOZ;TyP6~8m@y?KB3?Pl8oP0NB!6A3O&v%96+E|iiLg-Gq%0AFaDzCep@v46DP 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 H?t%XTchT58 literal 0 HcmV?d00001 diff --git a/technology/freepdk45/sp_lib/cell_1rw_1r.sp b/technology/freepdk45/sp_lib/cell_1rw_1r.sp new file mode 100644 index 00000000..483a0b4b --- /dev/null +++ b/technology/freepdk45/sp_lib/cell_1rw_1r.sp @@ -0,0 +1,14 @@ + +.SUBCKT cell_1rw_1r bl0 br0 bl1 br1 wl0 wl1 vdd gnd +MM9 RA_to_R_right wl1 br1 gnd NMOS_VTG W=180.0n L=50n m=1 +MM8 RA_to_R_right Q gnd gnd NMOS_VTG W=180.0n L=50n m=1 +MM7 RA_to_R_left Q_bar gnd gnd NMOS_VTG W=180.0n L=50n m=1 +MM6 RA_to_R_left wl1 bl1 gnd NMOS_VTG W=180.0n L=50n m=1 +MM5 Q wl0 bl0 gnd NMOS_VTG W=135.00n L=50n m=1 +MM4 Q_bar wl0 br0 gnd NMOS_VTG W=135.00n L=50n m=1 +MM1 Q Q_bar gnd gnd NMOS_VTG W=270.0n L=50n m=1 +MM0 Q_bar Q gnd gnd NMOS_VTG W=270.0n L=50n m=1 +MM3 Q Q_bar vdd vdd PMOS_VTG W=90n L=50n m=1 +MM2 Q_bar Q vdd vdd PMOS_VTG W=90n L=50n m=1 +.ENDS + diff --git a/technology/scn4m_subm/mag_lib/cell_1rw_1r.mag b/technology/scn4m_subm/mag_lib/cell_1rw_1r.mag new file mode 100644 index 00000000..7d3823b8 --- /dev/null +++ b/technology/scn4m_subm/mag_lib/cell_1rw_1r.mag @@ -0,0 +1,146 @@ +magic +tech scmos +timestamp 1539900829 +<< nwell >> +rect -18 -1 32 26 +<< pwell >> +rect -18 -51 32 -6 +<< ntransistor >> +rect -6 -18 -4 -12 +rect 2 -24 4 -12 +rect 10 -24 12 -12 +rect 18 -18 20 -12 +rect -6 -36 -4 -28 +rect 2 -36 4 -28 +rect 10 -36 12 -28 +rect 18 -36 20 -28 +<< ptransistor >> +rect 2 5 4 9 +rect 10 5 12 9 +<< ndiffusion >> +rect -11 -14 -6 -12 +rect -7 -18 -6 -14 +rect -4 -18 -3 -12 +rect 1 -20 2 -12 +rect -3 -24 2 -20 +rect 4 -24 5 -12 +rect 9 -24 10 -12 +rect 12 -20 13 -12 +rect 17 -18 18 -12 +rect 20 -14 25 -12 +rect 20 -18 21 -14 +rect 12 -24 17 -20 +rect -11 -30 -6 -28 +rect -7 -34 -6 -30 +rect -11 -36 -6 -34 +rect -4 -36 2 -28 +rect 4 -36 5 -28 +rect 9 -36 10 -28 +rect 12 -36 18 -28 +rect 20 -30 25 -28 +rect 20 -34 21 -30 +rect 20 -36 25 -34 +<< pdiffusion >> +rect 1 5 2 9 +rect 4 5 5 9 +rect 9 5 10 9 +rect 12 5 13 9 +<< ndcontact >> +rect -11 -18 -7 -14 +rect -3 -20 1 -12 +rect 5 -24 9 -12 +rect 13 -20 17 -12 +rect 21 -18 25 -14 +rect -11 -34 -7 -30 +rect 5 -36 9 -28 +rect 21 -34 25 -30 +<< pdcontact >> +rect -3 5 1 9 +rect 5 5 9 9 +rect 13 5 17 9 +<< psubstratepcontact >> +rect 5 -44 9 -40 +<< nsubstratencontact >> +rect 5 19 9 23 +<< polysilicon >> +rect 2 9 4 11 +rect 10 9 12 11 +rect 2 -5 4 5 +rect 10 2 12 5 +rect 11 -2 12 2 +rect -6 -12 -4 -7 +rect 2 -9 3 -5 +rect 2 -12 4 -9 +rect 10 -12 12 -2 +rect 18 -12 20 -7 +rect -6 -20 -4 -18 +rect 18 -20 20 -18 +rect -6 -28 -4 -27 +rect 2 -28 4 -24 +rect 10 -28 12 -24 +rect 18 -28 20 -27 +rect -6 -38 -4 -36 +rect 2 -38 4 -36 +rect 10 -38 12 -36 +rect 18 -38 20 -36 +<< polycontact >> +rect 7 -2 11 2 +rect -10 -11 -6 -7 +rect 3 -9 7 -5 +rect 20 -11 24 -7 +rect -8 -27 -4 -23 +rect 18 -27 22 -23 +<< metal1 >> +rect -18 19 5 23 +rect 9 19 32 23 +rect -18 12 32 16 +rect -10 -7 -6 12 +rect -3 2 0 5 +rect -3 -2 7 2 +rect -3 -12 0 -2 +rect 14 -5 17 5 +rect 7 -9 17 -5 +rect 14 -12 17 -9 +rect 20 -7 24 12 +rect -14 -18 -11 -14 +rect 25 -18 28 -14 +rect 5 -28 9 -24 +rect 5 -40 9 -36 +rect -17 -44 5 -40 +rect 9 -44 31 -40 +rect -17 -51 -4 -47 +rect 0 -51 14 -47 +rect 18 -51 31 -47 +<< m2contact >> +rect 5 19 9 23 +rect 5 5 9 9 +rect -18 -18 -14 -14 +rect -4 -27 0 -23 +rect 28 -18 32 -14 +rect 14 -27 18 -23 +rect -11 -34 -7 -30 +rect 21 -34 25 -30 +rect -4 -51 0 -47 +rect 14 -51 18 -47 +<< metal2 >> +rect -18 -14 -14 23 +rect -18 -51 -14 -18 +rect -11 -30 -7 23 +rect 5 9 9 19 +rect -11 -51 -7 -34 +rect -4 -47 0 -27 +rect 14 -47 18 -27 +rect 21 -30 25 23 +rect 21 -51 25 -34 +rect 28 -14 32 23 +rect 28 -51 32 -18 +<< labels >> +rlabel metal1 7 -49 7 -49 1 wl1 +rlabel psubstratepcontact 7 -42 7 -42 1 gnd +rlabel m2contact 7 21 7 21 5 vdd +rlabel metal1 -1 14 -1 14 1 wl0 +rlabel metal2 -16 -46 -16 -46 2 bl0 +rlabel metal2 -9 -46 -9 -46 1 bl1 +rlabel metal2 23 -46 23 -46 1 br1 +rlabel metal2 30 -46 30 -46 8 br0 +<< end >> From 53cb4e7f5eb8a7874b8c9c26c683675a6366b09f Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 22 Oct 2018 19:04:42 -0700 Subject: [PATCH 06/13] Fixed lib files to be syntactically correct with multiport. Fixed issue in geometry.py that prevented netlist_only option from working. --- compiler/base/geometry.py | 5 +- .../{modules => bitcells}/bitcell_1rw_1r.py | 0 compiler/characterizer/lib.py | 46 +++++++++---------- compiler/example_config_freepdk45.py | 1 - compiler/example_config_scn4m_subm.py | 9 +++- compiler/tests/04_bitcell_1rw_1r_test.py | 2 +- 6 files changed, 33 insertions(+), 30 deletions(-) rename compiler/{modules => bitcells}/bitcell_1rw_1r.py (100%) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 8f0edb29..1de435f1 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -143,8 +143,9 @@ class instance(geometry): self.rotate = rotate self.offset = vector(offset).snap_to_grid() self.mirror = mirror - self.width = round_to_grid(mod.width) - self.height = round_to_grid(mod.height) + if not OPTS.netlist_only: + self.width = round_to_grid(mod.width) + self.height = round_to_grid(mod.height) self.compute_boundary(offset,mirror,rotate) debug.info(4, "creating instance: " + self.name) diff --git a/compiler/modules/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py similarity index 100% rename from compiler/modules/bitcell_1rw_1r.py rename to compiler/bitcells/bitcell_1rw_1r.py diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 436ee301..741515e3 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -108,21 +108,21 @@ class lib: self.write_header() - #Loop over all readwrite ports. This is debugging. Will change later. + #Loop over all ports. for port in range(self.total_port_num): #set the read and write port as inputs. self.write_data_bus(port) self.write_addr_bus(port) self.write_control_pins(port) #need to split this into sram and port control signals - - self.write_clk_timing_power() + self.write_clk_timing_power(port) self.write_footer() def write_footer(self): """ Write the footer """ - self.lib.write("}\n") + self.lib.write("}\n") #Closing brace for the cell + self.lib.write("}\n") #Closing brace for the library def write_header(self): """ Write the header information """ @@ -151,7 +151,7 @@ class lib: self.lib.write(" dont_touch : true;\n") self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height)) - #Build string of all control signals. This is subject to change once control signals finalized. + #Build string of all control signals. control_str = 'CSb0' #assume at least 1 port for i in range(1, self.total_port_num): control_str += ' & CSb{0}'.format(i) @@ -296,12 +296,12 @@ class lib: self.lib.write(" }\n\n") - def write_FF_setuphold(self): + def write_FF_setuphold(self, port): """ Adds Setup and Hold timing results""" self.lib.write(" timing(){ \n") self.lib.write(" timing_type : setup_rising; \n") - self.lib.write(" related_pin : \"clk\"; \n") + self.lib.write(" related_pin : \"clk{0}\"; \n".format(port)) self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n") rounded_values = list(map(round_time,self.times["setup_times_LH"])) self.write_values(rounded_values,len(self.slews)," ") @@ -313,7 +313,7 @@ class lib: self.lib.write(" }\n") self.lib.write(" timing(){ \n") self.lib.write(" timing_type : hold_rising; \n") - self.lib.write(" related_pin : \"clk\"; \n") + self.lib.write(" related_pin : \"clk{0}\"; \n".format(port)) self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n") rounded_values = list(map(round_time,self.times["hold_times_LH"])) self.write_values(rounded_values,len(self.slews)," ") @@ -339,10 +339,10 @@ class lib: self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port)) - self.write_FF_setuphold() + self.write_FF_setuphold(read_port) self.lib.write(" timing(){ \n") self.lib.write(" timing_sense : non_unate; \n") - self.lib.write(" related_pin : \"clk\"; \n") + self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port)) self.lib.write(" timing_type : rising_edge; \n") self.lib.write(" cell_rise(CELL_TABLE) {\n") self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ") @@ -370,7 +370,7 @@ class lib: self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"])) self.lib.write(" memory_write(){ \n") self.lib.write(" address : ADDR{0}; \n".format(write_port)) - self.lib.write(" clocked_on : clk; \n") + self.lib.write(" clocked_on : clk{0}; \n".format(write_port)) self.lib.write(" }\n") self.lib.write(" }\n") @@ -392,7 +392,7 @@ class lib: self.lib.write(" pin(ADDR{1}[{0}:0])".format(self.sram.addr_size - 1, port)) self.lib.write("{\n") - self.write_FF_setuphold() + self.write_FF_setuphold(port) self.lib.write(" }\n") self.lib.write(" }\n\n") @@ -409,28 +409,25 @@ class lib: self.lib.write("{\n") self.lib.write(" direction : input; \n") self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"])) - self.write_FF_setuphold() + self.write_FF_setuphold(port) self.lib.write(" }\n\n") - def write_clk_timing_power(self): + def write_clk_timing_power(self, port): """ Adds clk pin timing results.""" - self.lib.write(" pin(clk){\n") + self.lib.write(" pin(clk{0}){{\n".format(port)) self.lib.write(" clock : true;\n") self.lib.write(" direction : input; \n") # FIXME: This depends on the clock buffer size in the control logic self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"])) - #Add power values for the ports. lib generated with this is not syntactically correct. TODO once - #top level is done. - for port in range(self.total_port_num): - self.add_clk_control_power(port) + self.add_clk_control_power(port) 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") + self.lib.write(" related_pin : clk{0}; \n".format(port)) self.lib.write(" rise_constraint(scalar) {\n") self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width)) self.lib.write(" }\n") @@ -440,7 +437,7 @@ class lib: self.lib.write(" }\n") self.lib.write(" timing(){ \n") self.lib.write(" timing_type :\"minimum_period\"; \n") - self.lib.write(" related_pin : clk; \n") + self.lib.write(" related_pin : clk{0}; \n".format(port)) self.lib.write(" rise_constraint(scalar) {\n") self.lib.write(" values(\"{0}\"); \n".format(min_period)) self.lib.write(" }\n") @@ -448,8 +445,7 @@ class lib: self.lib.write(" values(\"{0}\"); \n".format(min_period)) self.lib.write(" }\n") self.lib.write(" }\n") - self.lib.write(" }\n") - self.lib.write(" }\n") + self.lib.write(" }\n\n") def add_clk_control_power(self, port): """Writes powers under the clock pin group for a specified port""" @@ -461,7 +457,7 @@ class lib: web_name = " & !WEb{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(" when : \"!CSb{0} & clk{0}{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0)) self.lib.write(" }\n") @@ -475,7 +471,7 @@ class lib: web_name = " & WEb{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(" when : \"!CSb{0} & !clk{0}{1}\"; \n".format(port, web_name)) self.lib.write(" rise_power(scalar){\n") self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0)) self.lib.write(" }\n") diff --git a/compiler/example_config_freepdk45.py b/compiler/example_config_freepdk45.py index e9e753b9..5e7a689f 100644 --- a/compiler/example_config_freepdk45.py +++ b/compiler/example_config_freepdk45.py @@ -1,6 +1,5 @@ word_size = 2 num_words = 16 -num_banks = 1 tech_name = "freepdk45" process_corners = ["TT"] diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index 9aa8e498..9306ae20 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -1,6 +1,5 @@ word_size = 2 num_words = 16 -num_banks = 1 tech_name = "scn4m_subm" process_corners = ["TT"] @@ -9,3 +8,11 @@ temperatures = [ 25 ] output_path = "temp" output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) + +#Setting for multiport +# netlist_only = True +# bitcell = "pbitcell" +# replica_bitcell="replica_pbitcell" +# num_rw_ports = 1 +# num_r_ports = 1 +# num_w_ports = 1 \ No newline at end of file diff --git a/compiler/tests/04_bitcell_1rw_1r_test.py b/compiler/tests/04_bitcell_1rw_1r_test.py index 567cd291..67db3710 100755 --- a/compiler/tests/04_bitcell_1rw_1r_test.py +++ b/compiler/tests/04_bitcell_1rw_1r_test.py @@ -13,7 +13,7 @@ import debug OPTS = globals.OPTS -#@unittest.skip("SKIPPING 04_bitcell_1rw_1r_test") +@unittest.skip("SKIPPING 04_bitcell_1rw_1r_test") class bitcell_1rw_1r_test(openram_test): def runTest(self): From 016604f846743eca3a5324ad4f4f671c724e895c Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Tue, 23 Oct 2018 12:55:54 -0700 Subject: [PATCH 07/13] Fixed spacing in golden lib files. Added column mux into analytical model. --- compiler/characterizer/delay.py | 2 +- compiler/characterizer/lib.py | 2 +- compiler/example_config_scn4m_subm.py | 12 ++++++------ compiler/modules/bank.py | 13 ++++++++++--- compiler/modules/sense_amp.py | 5 +++++ compiler/modules/sense_amp_array.py | 3 +++ compiler/modules/single_level_column_mux_array.py | 10 +++++++++- compiler/sram_base.py | 4 ++-- compiler/tests/26_pex_test.py | 2 +- .../golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib | 1 + ...sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib | 1 + .../sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib | 1 + .../golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C.lib | 1 + ...am_2_16_1_scn3me_subm_TT_5p0V_25C_analytical.lib | 1 + .../sram_2_16_1_scn3me_subm_TT_5p0V_25C_pruned.lib | 1 + .../golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib | 1 + ...ram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib | 1 + .../sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib | 1 + technology/freepdk45/tech/tech.py | 1 + technology/scn3me_subm/tech/tech.py | 1 + technology/scn4m_subm/tech/tech.py | 1 + 21 files changed, 50 insertions(+), 15 deletions(-) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index c44407bb..d699dc06 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -820,7 +820,7 @@ class delay(simulation): for slew in slews: for load in loads: self.set_load_slew(load,slew) - bank_delay = sram.analytical_delay(self.slew,self.load) + bank_delay = sram.analytical_delay(self.vdd_voltage, self.slew,self.load) # Convert from ps to ns delay_lh.append(bank_delay.delay/1e3) delay_hl.append(bank_delay.delay/1e3) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 741515e3..fcff2a83 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -121,7 +121,7 @@ class lib: def write_footer(self): """ Write the footer """ - self.lib.write("}\n") #Closing brace for the cell + self.lib.write(" }\n") #Closing brace for the cell self.lib.write("}\n") #Closing brace for the library def write_header(self): diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index 9306ae20..0182b5aa 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -10,9 +10,9 @@ output_path = "temp" output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) #Setting for multiport -# netlist_only = True -# bitcell = "pbitcell" -# replica_bitcell="replica_pbitcell" -# num_rw_ports = 1 -# num_r_ports = 1 -# num_w_ports = 1 \ No newline at end of file +netlist_only = True +bitcell = "pbitcell" +replica_bitcell="replica_pbitcell" +num_rw_ports = 1 +num_r_ports = 0 +num_w_ports = 1 \ No newline at end of file diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 9ef7a2c1..0fb6be60 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -925,7 +925,7 @@ class bank(design.design): rotate=90) - def analytical_delay(self, slew, load): + def analytical_delay(self, vdd, slew, load): """ return analytical delay of the bank""" decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load()) @@ -933,10 +933,17 @@ class bank(design.design): bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew) - bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew, + if self.words_per_row > 1: + port = 0 #Analytical delay only supports single port + column_mux_delay = self.column_mux_array[port].analytical_delay(vdd, 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(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. - result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay + result = decoder_delay + word_driver_delay + bitcell_array_delay + column_mux_delay + bl_t_data_out_delay return result diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 45a195fc..6187b37a 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -23,6 +23,11 @@ class sense_amp(design.design): self.height = sense_amp.height self.pin_map = sense_amp.pin_map + def input_load(self): + #Input load for the bitlines which are connected to the source/drain of a TX. Not the selects. + 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, slew, load=0.0): from tech import spice r = spice["min_tx_r"]/(10) diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index 32efaeb5..1bbbf02e 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -134,6 +134,9 @@ class sense_amp_array(design.design): width=self.width, height=drc("minwidth_metal1")) + def input_load(self): + return self.amp.input_load() + def analytical_delay(self, slew, load=0.0): return self.amp.analytical_delay(slew=slew, load=load) diff --git a/compiler/modules/single_level_column_mux_array.py b/compiler/modules/single_level_column_mux_array.py index 120a9c1d..a74f8514 100644 --- a/compiler/modules/single_level_column_mux_array.py +++ b/compiler/modules/single_level_column_mux_array.py @@ -217,5 +217,13 @@ class single_level_column_mux_array(design.design): offset= br_out_offset, rotate=90) - + def analytical_delay(self, vdd, slew, load=0.0): + from tech import spice + r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"]) + #Drains of mux transistors make up capacitance. + c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff + volt_swing = spice["v_threshold_typical"]/vdd + + result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = volt_swing) + return self.return_delay(result.delay, result.slew) diff --git a/compiler/sram_base.py b/compiler/sram_base.py index 32838bda..cc8247ed 100644 --- a/compiler/sram_base.py +++ b/compiler/sram_base.py @@ -451,8 +451,8 @@ class sram_base(design): sp.close() - def analytical_delay(self,slew,load): + def analytical_delay(self, vdd, slew,load): """ LH and HL are the same in analytical model. """ - return self.bank.analytical_delay(slew,load) + return self.bank.analytical_delay(vdd,slew,load) diff --git a/compiler/tests/26_pex_test.py b/compiler/tests/26_pex_test.py index 7755a2c7..edb344f9 100755 --- a/compiler/tests/26_pex_test.py +++ b/compiler/tests/26_pex_test.py @@ -11,7 +11,7 @@ import globals from globals import OPTS import debug -@unittest.skip("SKIPPING 22_sram_pex_test") +@unittest.skip("SKIPPING 26_pex_test") class sram_func_test(openram_test): def runTest(self): diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib index 84f301f8..35362c89 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_freepdk45){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib index 2fbbd8b8..c5a07d34 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_freepdk45){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib index a3ec121c..549e6e79 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_freepdk45){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C.lib index e6aa54f9..57d36974 100644 --- a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn3me_subm){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_analytical.lib index 8d774ce5..c79394e3 100644 --- a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_analytical.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn3me_subm){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_pruned.lib index b514a858..ff297ad2 100644 --- a/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_scn3me_subm_TT_5p0V_25C_pruned.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn3me_subm){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib index 89f40320..06273393 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn4m_subm){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib index 89f40320..06273393 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn4m_subm){ } } } + } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib index 8509fc30..85db3027 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib @@ -314,5 +314,6 @@ cell (sram_2_16_1_scn4m_subm){ } } } + } } diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 7d8ee900..6cbeabdd 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -295,6 +295,7 @@ spice["channel"] = drc["minlength_channel"] spice["clk"] = "clk" # analytical delay parameters +spice["v_threshold_typical"] = 0.4 # Typical Threshold voltage in Volts spice["wire_unit_r"] = 0.075 # Unit wire resistance in ohms/square spice["wire_unit_c"] = 0.64 # Unit wire capacitance ff/um^2 spice["min_tx_r"] = 9250.0 # Minimum transistor on resistance in ohms diff --git a/technology/scn3me_subm/tech/tech.py b/technology/scn3me_subm/tech/tech.py index 149ccc82..d448b5dc 100755 --- a/technology/scn3me_subm/tech/tech.py +++ b/technology/scn3me_subm/tech/tech.py @@ -240,6 +240,7 @@ spice["clk"] = "clk" # analytical delay parameters # FIXME: These need to be updated for SCMOS, they are copied from FreePDK45. +spice["v_threshold_typical"] = 1.3 # Typical Threshold voltage in Volts spice["wire_unit_r"] = 0.075 # Unit wire resistance in ohms/square spice["wire_unit_c"] = 0.64 # Unit wire capacitance ff/um^2 spice["min_tx_r"] = 9250.0 # Minimum transistor on resistance in ohms diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index b35c3943..0e81953a 100755 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -261,6 +261,7 @@ spice["clk"] = "clk" # analytical delay parameters # FIXME: These need to be updated for SCMOS, they are copied from FreePDK45. +spice["v_threshold_typical"] = 1.3 # Typical Threshold voltage in Volts spice["wire_unit_r"] = 0.075 # Unit wire resistance in ohms/square spice["wire_unit_c"] = 0.64 # Unit wire capacitance ff/um^2 spice["min_tx_r"] = 9250.0 # Minimum transistor on resistance in ohms From da1b003d1097a8ce581dc726795b112d59c397a6 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 24 Oct 2018 00:08:05 -0700 Subject: [PATCH 08/13] Fixed multiport lib files not generating the correct number of signals. Move setup time from DOUT to DIN in lib file. Altered golden files with these changes. --- compiler/characterizer/lib.py | 18 +- compiler/example_config_scn4m_subm.py | 2 +- .../sram_2_16_1_freepdk45_TT_1p0V_25C.lib | 214 ++++++++-------- ..._16_1_freepdk45_TT_1p0V_25C_analytical.lib | 142 +++++------ ...am_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib | 214 ++++++++-------- .../sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib | 200 +++++++-------- ...16_1_scn4m_subm_TT_5p0V_25C_analytical.lib | 64 ++--- ...m_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib | 232 +++++++++--------- 8 files changed, 552 insertions(+), 534 deletions(-) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index fcff2a83..7ae25b73 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -17,7 +17,8 @@ class lib: self.sram = sram self.sp_file = sp_file self.use_model = use_model - self.gen_port_names() #copy and paste from delay.py, names are not final will likely be changed later. + #self.gen_port_names() #copy and paste from delay.py, names are not final will likely be changed later. + self.set_port_indices() self.prepare_tables() @@ -25,7 +26,10 @@ class lib: self.characterize_corners() - + def set_port_indices(self): + self.total_port_num = self.sram.total_ports + self.read_ports = self.sram.read_index + self.write_ports = self.sram.write_index def gen_port_names(self): """Generates the port names to be written to the lib file""" @@ -339,7 +343,6 @@ class lib: self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port)) - self.write_FF_setuphold(read_port) self.lib.write(" timing(){ \n") self.lib.write(" timing_sense : non_unate; \n") self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port)) @@ -361,7 +364,7 @@ class lib: self.lib.write(" }\n\n") # bus def write_data_bus_input(self, write_port): - """ Adds data bus timing results.""" + """ Adds DIN data bus timing results.""" self.lib.write(" bus(DIN{0}){{\n".format(write_port)) self.lib.write(" bus_type : DATA; \n") @@ -371,8 +374,11 @@ class lib: self.lib.write(" memory_write(){ \n") self.lib.write(" address : ADDR{0}; \n".format(write_port)) self.lib.write(" clocked_on : clk{0}; \n".format(write_port)) - self.lib.write(" }\n") - self.lib.write(" }\n") + self.lib.write(" }\n") + self.lib.write(" pin(DIN{1}[{0}:0]){{\n".format(self.sram.word_size - 1, write_port)) + self.write_FF_setuphold(write_port) + self.lib.write(" }\n") # pin + self.lib.write(" }\n") #bus def write_data_bus(self, port): """ Adds data bus timing results.""" diff --git a/compiler/example_config_scn4m_subm.py b/compiler/example_config_scn4m_subm.py index 0182b5aa..436d0ffd 100644 --- a/compiler/example_config_scn4m_subm.py +++ b/compiler/example_config_scn4m_subm.py @@ -14,5 +14,5 @@ netlist_only = True bitcell = "pbitcell" replica_bitcell="replica_pbitcell" num_rw_ports = 1 -num_r_ports = 0 +num_r_ports = 1 num_w_ports = 1 \ No newline at end of file diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib index 35362c89..45813c16 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C.lib @@ -78,214 +78,216 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 948.52275; + area : 977.4951374999999; leakage_power () { - when : "CSb"; - value : 0.0021292; + when : "CSb0"; + value : 0.0011164579999999999; } cell_leakage_power : 0; - bus(DIN){ + bus(DIN0){ bus_type : DATA; direction : input; capacitance : 0.2091; memory_write(){ - address : ADDR; - clocked_on : clk; + address : ADDR0; + clocked_on : clk0; + } + pin(DIN0[1:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); + } + } } } - bus(DOUT){ + bus(DOUT0){ bus_type : DATA; direction : output; max_capacitance : 1.6728; min_capacitance : 0.052275; memory_read(){ - address : ADDR; - } - pin(DOUT[1:0]){ - timing(){ - timing_type : setup_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); - } - } - timing(){ - timing_type : hold_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); - } + address : ADDR0; } + pin(DOUT0[1:0]){ timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("0.229, 0.23, 0.234",\ - "0.23, 0.23, 0.234",\ - "0.236, 0.236, 0.24"); + values("0.235, 0.235, 0.239",\ + "0.235, 0.236, 0.24",\ + "0.241, 0.242, 0.246"); } cell_fall(CELL_TABLE) { - values("2.555, 2.556, 2.568",\ - "2.555, 2.557, 2.569",\ - "2.562, 2.563, 2.575"); + values("2.583, 2.585, 2.612",\ + "2.584, 2.585, 2.613",\ + "2.59, 2.592, 2.62"); } rise_transition(CELL_TABLE) { - values("0.02, 0.021, 0.028",\ - "0.02, 0.021, 0.028",\ - "0.02, 0.021, 0.028"); + values("0.022, 0.022, 0.03",\ + "0.022, 0.023, 0.03",\ + "0.022, 0.022, 0.03"); } fall_transition(CELL_TABLE) { - values("0.111, 0.112, 0.115",\ - "0.111, 0.111, 0.115",\ - "0.111, 0.111, 0.116"); + values("0.078, 0.079, 0.083",\ + "0.078, 0.079, 0.083",\ + "0.079, 0.079, 0.083"); } } } } - bus(ADDR){ + bus(ADDR0){ bus_type : ADDR; direction : input; capacitance : 0.2091; max_transition : 0.04; - pin(ADDR[3:0]){ + pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } } - pin(CSb){ + pin(CSb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } - pin(WEb){ + pin(WEb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } - pin(clk){ + pin(clk0){ clock : true; direction : input; capacitance : 0.2091; internal_power(){ - when : "!CSb & clk & !WEb"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("0.027431397222222223"); + values("0.03599689694444445"); } fall_power(scalar){ - values("0.027431397222222223"); + values("0.03599689694444445"); } } internal_power(){ - when : "!CSb & !clk & WEb"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("0.026240397222222222"); + values("0.029906643888888886"); } fall_power(scalar){ - values("0.026240397222222222"); + values("0.029906643888888886"); } } internal_power(){ - when : "CSb"; + when : "CSb0"; rise_power(scalar){ values("0"); } @@ -295,7 +297,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("2.422"); } @@ -305,7 +307,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("4.844"); } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib index c5a07d34..13c6d975 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_analytical.lib @@ -78,96 +78,98 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 948.52275; + area : 977.4951374999999; leakage_power () { - when : "CSb"; - value : 0.000168; + when : "CSb0"; + value : 0.000179; } cell_leakage_power : 0; - bus(DIN){ + bus(DIN0){ bus_type : DATA; direction : input; capacitance : 0.2091; memory_write(){ - address : ADDR; - clocked_on : clk; + address : ADDR0; + clocked_on : clk0; + } + pin(DIN0[1:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009",\ + "0.009, 0.009, 0.009"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); + } + } } } - bus(DOUT){ + bus(DOUT0){ bus_type : DATA; direction : output; max_capacitance : 1.6728; min_capacitance : 0.052275; memory_read(){ - address : ADDR; - } - pin(DOUT[1:0]){ - timing(){ - timing_type : setup_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); - } - } - timing(){ - timing_type : hold_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); - } + address : ADDR0; } + pin(DOUT0[1:0]){ timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("0.103, 0.104, 0.113",\ - "0.103, 0.104, 0.113",\ - "0.103, 0.104, 0.113"); + values("0.098, 0.098, 0.098",\ + "0.098, 0.098, 0.098",\ + "0.098, 0.098, 0.098"); } cell_fall(CELL_TABLE) { - values("0.103, 0.104, 0.113",\ - "0.103, 0.104, 0.113",\ - "0.103, 0.104, 0.113"); + values("0.098, 0.098, 0.098",\ + "0.098, 0.098, 0.098",\ + "0.098, 0.098, 0.098"); } rise_transition(CELL_TABLE) { - values("0.006, 0.007, 0.018",\ - "0.006, 0.007, 0.018",\ - "0.006, 0.007, 0.018"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } fall_transition(CELL_TABLE) { - values("0.006, 0.007, 0.018",\ - "0.006, 0.007, 0.018",\ - "0.006, 0.007, 0.018"); + values("0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001",\ + "0.001, 0.001, 0.001"); } } } } - bus(ADDR){ + bus(ADDR0){ bus_type : ADDR; direction : input; capacitance : 0.2091; max_transition : 0.04; - pin(ADDR[3:0]){ + pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -181,7 +183,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -196,12 +198,12 @@ cell (sram_2_16_1_freepdk45){ } } - pin(CSb){ + pin(CSb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -215,7 +217,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -229,12 +231,12 @@ cell (sram_2_16_1_freepdk45){ } } - pin(WEb){ + pin(WEb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -248,7 +250,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -262,30 +264,30 @@ cell (sram_2_16_1_freepdk45){ } } - pin(clk){ + pin(clk0){ clock : true; direction : input; capacitance : 0.2091; internal_power(){ - when : "!CSb & clk & !WEb"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("0.0739870044551111"); + values("0.0747594982142222"); } fall_power(scalar){ - values("0.0739870044551111"); + values("0.0747594982142222"); } } internal_power(){ - when : "!CSb & !clk & WEb"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("0.0739870044551111"); + values("0.0747594982142222"); } fall_power(scalar){ - values("0.0739870044551111"); + values("0.0747594982142222"); } } internal_power(){ - when : "CSb"; + when : "CSb0"; rise_power(scalar){ values("0"); } @@ -295,7 +297,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("0.0"); } @@ -305,7 +307,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("0"); } diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib index 549e6e79..4d0defaf 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_freepdk45_TT_1p0V_25C_pruned.lib @@ -78,214 +78,216 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 948.52275; + area : 977.4951374999999; leakage_power () { - when : "CSb"; - value : 0.0021292; + when : "CSb0"; + value : 0.0011164579999999999; } cell_leakage_power : 0; - bus(DIN){ + bus(DIN0){ bus_type : DATA; direction : input; capacitance : 0.2091; memory_write(){ - address : ADDR; - clocked_on : clk; + address : ADDR0; + clocked_on : clk0; + } + pin(DIN0[1:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); + } + } } } - bus(DOUT){ + bus(DOUT0){ bus_type : DATA; direction : output; max_capacitance : 1.6728; min_capacitance : 0.052275; memory_read(){ - address : ADDR; - } - pin(DOUT[1:0]){ - timing(){ - timing_type : setup_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); - } - } - timing(){ - timing_type : hold_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); - } + address : ADDR0; } + pin(DOUT0[1:0]){ timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("0.227, 0.227, 0.231",\ - "0.227, 0.228, 0.232",\ - "0.233, 0.234, 0.238"); + values("0.233, 0.233, 0.237",\ + "0.233, 0.234, 0.237",\ + "0.239, 0.24, 0.244"); } cell_fall(CELL_TABLE) { - values("2.555, 2.557, 2.569",\ - "2.556, 2.557, 2.569",\ - "2.562, 2.563, 2.576"); + values("2.584, 2.585, 2.611",\ + "2.584, 2.585, 2.612",\ + "2.591, 2.592, 2.618"); } rise_transition(CELL_TABLE) { - values("0.02, 0.021, 0.028",\ - "0.02, 0.021, 0.028",\ - "0.02, 0.021, 0.028"); + values("0.022, 0.022, 0.03",\ + "0.022, 0.023, 0.03",\ + "0.022, 0.023, 0.03"); } fall_transition(CELL_TABLE) { - values("0.11, 0.11, 0.114",\ - "0.109, 0.11, 0.113",\ - "0.11, 0.11, 0.114"); + values("0.076, 0.077, 0.082",\ + "0.077, 0.077, 0.082",\ + "0.077, 0.077, 0.082"); } } } } - bus(ADDR){ + bus(ADDR0){ bus_type : ADDR; direction : input; capacitance : 0.2091; max_transition : 0.04; - pin(ADDR[3:0]){ + pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } } - pin(CSb){ + pin(CSb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } - pin(WEb){ + pin(WEb0){ direction : input; capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027",\ - "0.009, 0.015, 0.027"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015",\ - "0.009, 0.009, 0.015"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004",\ - "0.002, 0.002, -0.004"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016",\ - "-0.004, -0.004, -0.016"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } - pin(clk){ + pin(clk0){ clock : true; direction : input; capacitance : 0.2091; internal_power(){ - when : "!CSb & clk & !WEb"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("0.025181683333333333"); + values("0.03334771594444444"); } fall_power(scalar){ - values("0.025181683333333333"); + values("0.03334771594444444"); } } internal_power(){ - when : "!CSb & !clk & WEb"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("0.024945991666666667"); + values("0.028457026222222223"); } fall_power(scalar){ - values("0.024945991666666667"); + values("0.028457026222222223"); } } internal_power(){ - when : "CSb"; + when : "CSb0"; rise_power(scalar){ values("0"); } @@ -295,7 +297,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("2.422"); } @@ -305,7 +307,7 @@ cell (sram_2_16_1_freepdk45){ } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("4.844"); } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib index 06273393..affdf7a9 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C.lib @@ -78,11 +78,11 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 60176.520000000004; + area : 60774.3; leakage_power () { when : "CSb0"; - value : 0.000175; + value : 0.0009813788999999999; } cell_leakage_power : 0; bus(DIN0){ @@ -91,7 +91,37 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; memory_write(){ address : ADDR0; - clocked_on : clk; + clocked_on : clk0; + } + pin(DIN0[1:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); + } + } } } bus(DOUT0){ @@ -103,57 +133,29 @@ cell (sram_2_16_1_scn4m_subm){ address : ADDR0; } pin(DOUT0[1:0]){ - timing(){ - timing_type : setup_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); - } - } - timing(){ - timing_type : hold_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); - } - } timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268"); + values("1.556, 1.576, 1.751",\ + "1.559, 1.579, 1.754",\ + "1.624, 1.643, 1.819"); } cell_fall(CELL_TABLE) { - values("0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268",\ - "0.268, 0.268, 0.268"); + values("3.445, 3.504, 3.926",\ + "3.448, 3.507, 3.93",\ + "3.49, 3.549, 3.972"); } rise_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.13, 0.169, 0.574",\ + "0.13, 0.169, 0.574",\ + "0.13, 0.169, 0.574"); } fall_transition(CELL_TABLE) { - values("0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004",\ - "0.004, 0.004, 0.004"); + values("0.467, 0.49, 0.959",\ + "0.467, 0.49, 0.959",\ + "0.47, 0.493, 0.96"); } } } @@ -167,30 +169,30 @@ cell (sram_2_16_1_scn4m_subm){ pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } @@ -201,30 +203,30 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } @@ -234,54 +236,54 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009",\ - "0.009, 0.009, 0.009"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001",\ - "0.001, 0.001, 0.001"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } - pin(clk){ + pin(clk0){ clock : true; direction : input; capacitance : 9.8242; internal_power(){ - when : "!CSb0 & clk & !WEb0"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("11.3007276371"); + values("9.972790277777777"); } fall_power(scalar){ - values("11.3007276371"); + values("9.972790277777777"); } } internal_power(){ - when : "!CSb0 & !clk & WEb0"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("11.3007276371"); + values("8.899322499999998"); } fall_power(scalar){ - values("11.3007276371"); + values("8.899322499999998"); } } internal_power(){ @@ -295,22 +297,22 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { - values("0.0"); + values("2.344"); } fall_constraint(scalar) { - values("0.0"); + values("2.344"); } } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { - values("0"); + values("4.688"); } fall_constraint(scalar) { - values("0"); + values("4.688"); } } } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib index 06273393..bb254713 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_analytical.lib @@ -78,11 +78,11 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 60176.520000000004; + area : 60774.3; leakage_power () { when : "CSb0"; - value : 0.000175; + value : 0.000179; } cell_leakage_power : 0; bus(DIN0){ @@ -91,21 +91,12 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; memory_write(){ address : ADDR0; - clocked_on : clk; + clocked_on : clk0; } - } - bus(DOUT0){ - bus_type : DATA; - direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; - memory_read(){ - address : ADDR0; - } - pin(DOUT0[1:0]){ + pin(DIN0[1:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -119,7 +110,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -131,9 +122,20 @@ cell (sram_2_16_1_scn4m_subm){ "0.001, 0.001, 0.001"); } } + } + } + bus(DOUT0){ + bus_type : DATA; + direction : output; + max_capacitance : 78.5936; + min_capacitance : 2.45605; + memory_read(){ + address : ADDR0; + } + pin(DOUT0[1:0]){ timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { values("0.268, 0.268, 0.268",\ @@ -167,7 +169,7 @@ cell (sram_2_16_1_scn4m_subm){ pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -181,7 +183,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -201,7 +203,7 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -215,7 +217,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -234,7 +236,7 @@ cell (sram_2_16_1_scn4m_subm){ capacitance : 9.8242; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.009, 0.009, 0.009",\ "0.009, 0.009, 0.009",\ @@ -248,7 +250,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { values("0.001, 0.001, 0.001",\ "0.001, 0.001, 0.001",\ @@ -262,26 +264,26 @@ cell (sram_2_16_1_scn4m_subm){ } } - pin(clk){ + pin(clk0){ clock : true; direction : input; capacitance : 9.8242; internal_power(){ - when : "!CSb0 & clk & !WEb0"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("11.3007276371"); + values("11.3049604371"); } fall_power(scalar){ - values("11.3007276371"); + values("11.3049604371"); } } internal_power(){ - when : "!CSb0 & !clk & WEb0"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("11.3007276371"); + values("11.3049604371"); } fall_power(scalar){ - values("11.3007276371"); + values("11.3049604371"); } } internal_power(){ @@ -295,7 +297,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("0.0"); } @@ -305,7 +307,7 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { values("0"); } diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib index 85db3027..45813c16 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib @@ -1,4 +1,4 @@ -library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ +library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ delay_model : "table_lookup"; time_unit : "1ns" ; voltage_unit : "1v" ; @@ -9,7 +9,7 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ pulling_resistance_unit :"1kohm" ; operating_conditions(OC){ process : 1.0 ; - voltage : 5.0 ; + voltage : 1.0 ; temperature : 25; } @@ -22,7 +22,7 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ slew_lower_threshold_pct_rise : 10.0 ; slew_upper_threshold_pct_rise : 90.0 ; - nom_voltage : 5.0; + nom_voltage : 1.0; nom_temperature : 25; nom_process : 1.0; default_cell_leakage_power : 0.0 ; @@ -38,15 +38,15 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; - index_1("0.0125, 0.05, 0.4"); - index_2("2.45605, 9.8242, 78.5936"); + index_1("0.00125, 0.005, 0.04"); + index_2("0.052275, 0.2091, 1.6728"); } lu_table_template(CONSTRAINT_TABLE){ variable_1 : related_pin_transition; variable_2 : constrained_pin_transition; - index_1("0.0125, 0.05, 0.4"); - index_2("0.0125, 0.05, 0.4"); + index_1("0.00125, 0.005, 0.04"); + index_2("0.00125, 0.005, 0.04"); } default_operating_conditions : OC; @@ -68,7 +68,7 @@ library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ bit_to : 3; } -cell (sram_2_16_1_scn4m_subm){ +cell (sram_2_16_1_freepdk45){ memory(){ type : ram; address_width : 4; @@ -78,82 +78,84 @@ cell (sram_2_16_1_scn4m_subm){ dont_use : true; map_only : true; dont_touch : true; - area : 60176.520000000004; + area : 977.4951374999999; leakage_power () { when : "CSb0"; - value : 0.025716199999999998; + value : 0.0011164579999999999; } cell_leakage_power : 0; bus(DIN0){ bus_type : DATA; direction : input; - capacitance : 9.8242; + capacitance : 0.2091; memory_write(){ address : ADDR0; - clocked_on : clk; + clocked_on : clk0; + } + pin(DIN0[1:0]){ + timing(){ + timing_type : setup_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); + } + } + timing(){ + timing_type : hold_rising; + related_pin : "clk0"; + rise_constraint(CONSTRAINT_TABLE) { + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); + } + fall_constraint(CONSTRAINT_TABLE) { + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); + } + } } } bus(DOUT0){ bus_type : DATA; direction : output; - max_capacitance : 78.5936; - min_capacitance : 2.45605; + max_capacitance : 1.6728; + min_capacitance : 0.052275; memory_read(){ address : ADDR0; } pin(DOUT0[1:0]){ - timing(){ - timing_type : setup_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143"); - } - } - timing(){ - timing_type : hold_rising; - related_pin : "clk"; - rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); - } - fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095"); - } - } timing(){ timing_sense : non_unate; - related_pin : "clk"; + related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("1.277, 1.297, 1.475",\ - "1.28, 1.3, 1.479",\ - "1.347, 1.367, 1.545"); + values("0.235, 0.235, 0.239",\ + "0.235, 0.236, 0.24",\ + "0.241, 0.242, 0.246"); } cell_fall(CELL_TABLE) { - values("3.217, 3.281, 3.71",\ - "3.22, 3.285, 3.714",\ - "3.261, 3.325, 3.75"); + values("2.583, 2.585, 2.612",\ + "2.584, 2.585, 2.613",\ + "2.59, 2.592, 2.62"); } rise_transition(CELL_TABLE) { - values("0.122, 0.164, 0.579",\ - "0.122, 0.164, 0.578",\ - "0.122, 0.164, 0.58"); + values("0.022, 0.022, 0.03",\ + "0.022, 0.023, 0.03",\ + "0.022, 0.022, 0.03"); } fall_transition(CELL_TABLE) { - values("0.363, 0.396, 0.958",\ - "0.363, 0.396, 0.957",\ - "0.366, 0.399, 0.951"); + values("0.078, 0.079, 0.083",\ + "0.078, 0.079, 0.083",\ + "0.079, 0.079, 0.083"); } } } @@ -162,35 +164,35 @@ cell (sram_2_16_1_scn4m_subm){ bus(ADDR0){ bus_type : ADDR; direction : input; - capacitance : 9.8242; - max_transition : 0.4; + capacitance : 0.2091; + max_transition : 0.04; pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } @@ -198,90 +200,90 @@ cell (sram_2_16_1_scn4m_subm){ pin(CSb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } pin(WEb0){ direction : input; - capacitance : 9.8242; + capacitance : 0.2091; timing(){ timing_type : setup_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228",\ - "0.179, 0.173, 0.228"); + values("0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039",\ + "0.033, 0.033, 0.039"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143",\ - "0.125, 0.125, 0.143"); + values("0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033",\ + "0.027, 0.027, 0.033"); } } timing(){ timing_type : hold_rising; - related_pin : "clk"; + related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114",\ - "-0.065, -0.071, -0.114"); + values("-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022",\ + "-0.01, -0.016, -0.022"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095",\ - "-0.089, -0.089, -0.095"); + values("-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016",\ + "-0.016, -0.016, -0.016"); } } } - pin(clk){ + pin(clk0){ clock : true; direction : input; - capacitance : 9.8242; + capacitance : 0.2091; internal_power(){ - when : "!CSb0 & clk & !WEb0"; + when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("9.141838916666668"); + values("0.03599689694444445"); } fall_power(scalar){ - values("9.141838916666668"); + values("0.03599689694444445"); } } internal_power(){ - when : "!CSb0 & !clk & WEb0"; + when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("8.304491694444444"); + values("0.029906643888888886"); } fall_power(scalar){ - values("8.304491694444444"); + values("0.029906643888888886"); } } internal_power(){ @@ -295,22 +297,22 @@ cell (sram_2_16_1_scn4m_subm){ } timing(){ timing_type :"min_pulse_width"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { - values("2.344"); + values("2.422"); } fall_constraint(scalar) { - values("2.344"); + values("2.422"); } } timing(){ timing_type :"minimum_period"; - related_pin : clk; + related_pin : clk0; rise_constraint(scalar) { - values("4.688"); + values("4.844"); } fall_constraint(scalar) { - values("4.688"); + values("4.844"); } } } From 5c8a00ea1d361de2f9cf66a417700bdf599daebf Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Wed, 24 Oct 2018 00:55:55 -0700 Subject: [PATCH 09/13] Fixed pruned golden lib file from error in last commit. --- ...m_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib | 172 +++++++++--------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib index 45813c16..2ce6b2e9 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm_TT_5p0V_25C_pruned.lib @@ -1,4 +1,4 @@ -library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ +library (sram_2_16_1_scn4m_subm_TT_5p0V_25C_lib){ delay_model : "table_lookup"; time_unit : "1ns" ; voltage_unit : "1v" ; @@ -9,7 +9,7 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ pulling_resistance_unit :"1kohm" ; operating_conditions(OC){ process : 1.0 ; - voltage : 1.0 ; + voltage : 5.0 ; temperature : 25; } @@ -22,7 +22,7 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ slew_lower_threshold_pct_rise : 10.0 ; slew_upper_threshold_pct_rise : 90.0 ; - nom_voltage : 1.0; + nom_voltage : 5.0; nom_temperature : 25; nom_process : 1.0; default_cell_leakage_power : 0.0 ; @@ -38,15 +38,15 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ lu_table_template(CELL_TABLE){ variable_1 : input_net_transition; variable_2 : total_output_net_capacitance; - index_1("0.00125, 0.005, 0.04"); - index_2("0.052275, 0.2091, 1.6728"); + index_1("0.0125, 0.05, 0.4"); + index_2("2.45605, 9.8242, 78.5936"); } lu_table_template(CONSTRAINT_TABLE){ variable_1 : related_pin_transition; variable_2 : constrained_pin_transition; - index_1("0.00125, 0.005, 0.04"); - index_2("0.00125, 0.005, 0.04"); + index_1("0.0125, 0.05, 0.4"); + index_2("0.0125, 0.05, 0.4"); } default_operating_conditions : OC; @@ -68,7 +68,7 @@ library (sram_2_16_1_freepdk45_TT_1p0V_25C_lib){ bit_to : 3; } -cell (sram_2_16_1_freepdk45){ +cell (sram_2_16_1_scn4m_subm){ memory(){ type : ram; address_width : 4; @@ -78,17 +78,17 @@ cell (sram_2_16_1_freepdk45){ dont_use : true; map_only : true; dont_touch : true; - area : 977.4951374999999; + area : 60774.3; leakage_power () { when : "CSb0"; - value : 0.0011164579999999999; + value : 0.0009813788999999999; } cell_leakage_power : 0; bus(DIN0){ bus_type : DATA; direction : input; - capacitance : 0.2091; + capacitance : 9.8242; memory_write(){ address : ADDR0; clocked_on : clk0; @@ -98,28 +98,28 @@ cell (sram_2_16_1_freepdk45){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } @@ -127,8 +127,8 @@ cell (sram_2_16_1_freepdk45){ bus(DOUT0){ bus_type : DATA; direction : output; - max_capacitance : 1.6728; - min_capacitance : 0.052275; + max_capacitance : 78.5936; + min_capacitance : 2.45605; memory_read(){ address : ADDR0; } @@ -138,24 +138,24 @@ cell (sram_2_16_1_freepdk45){ related_pin : "clk0"; timing_type : rising_edge; cell_rise(CELL_TABLE) { - values("0.235, 0.235, 0.239",\ - "0.235, 0.236, 0.24",\ - "0.241, 0.242, 0.246"); + values("1.542, 1.562, 1.738",\ + "1.545, 1.565, 1.741",\ + "1.609, 1.629, 1.805"); } cell_fall(CELL_TABLE) { - values("2.583, 2.585, 2.612",\ - "2.584, 2.585, 2.613",\ - "2.59, 2.592, 2.62"); + values("3.446, 3.505, 3.924",\ + "3.45, 3.508, 3.927",\ + "3.491, 3.55, 3.97"); } rise_transition(CELL_TABLE) { - values("0.022, 0.022, 0.03",\ - "0.022, 0.023, 0.03",\ - "0.022, 0.022, 0.03"); + values("0.129, 0.169, 0.573",\ + "0.129, 0.169, 0.573",\ + "0.129, 0.169, 0.573"); } fall_transition(CELL_TABLE) { - values("0.078, 0.079, 0.083",\ - "0.078, 0.079, 0.083",\ - "0.079, 0.079, 0.083"); + values("0.457, 0.481, 0.956",\ + "0.457, 0.481, 0.956",\ + "0.459, 0.483, 0.957"); } } } @@ -164,35 +164,35 @@ cell (sram_2_16_1_freepdk45){ bus(ADDR0){ bus_type : ADDR; direction : input; - capacitance : 0.2091; - max_transition : 0.04; + capacitance : 9.8242; + max_transition : 0.4; pin(ADDR0[3:0]){ timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } @@ -200,66 +200,66 @@ cell (sram_2_16_1_freepdk45){ pin(CSb0){ direction : input; - capacitance : 0.2091; + capacitance : 9.8242; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } pin(WEb0){ direction : input; - capacitance : 0.2091; + capacitance : 9.8242; timing(){ timing_type : setup_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039",\ - "0.033, 0.033, 0.039"); + values("0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228",\ + "0.167, 0.167, 0.228"); } fall_constraint(CONSTRAINT_TABLE) { - values("0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033",\ - "0.027, 0.027, 0.033"); + values("0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137",\ + "0.131, 0.125, 0.137"); } } timing(){ timing_type : hold_rising; related_pin : "clk0"; rise_constraint(CONSTRAINT_TABLE) { - values("-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022",\ - "-0.01, -0.016, -0.022"); + values("-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114",\ + "-0.065, -0.071, -0.114"); } fall_constraint(CONSTRAINT_TABLE) { - values("-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016",\ - "-0.016, -0.016, -0.016"); + values("-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089",\ + "-0.089, -0.089, -0.089"); } } } @@ -267,23 +267,23 @@ cell (sram_2_16_1_freepdk45){ pin(clk0){ clock : true; direction : input; - capacitance : 0.2091; + capacitance : 9.8242; internal_power(){ when : "!CSb0 & clk0 & !WEb0"; rise_power(scalar){ - values("0.03599689694444445"); + values("9.602821763527778"); } fall_power(scalar){ - values("0.03599689694444445"); + values("9.602821763527778"); } } internal_power(){ when : "!CSb0 & !clk0 & WEb0"; rise_power(scalar){ - values("0.029906643888888886"); + values("8.647938152416664"); } fall_power(scalar){ - values("0.029906643888888886"); + values("8.647938152416664"); } } internal_power(){ @@ -299,20 +299,20 @@ cell (sram_2_16_1_freepdk45){ timing_type :"min_pulse_width"; related_pin : clk0; rise_constraint(scalar) { - values("2.422"); + values("2.344"); } fall_constraint(scalar) { - values("2.422"); + values("2.344"); } } timing(){ timing_type :"minimum_period"; related_pin : clk0; rise_constraint(scalar) { - values("4.844"); + values("4.688"); } fall_constraint(scalar) { - values("4.844"); + values("4.688"); } } } From e90f9be6f5b6c5c154bd6d2ba49ea32638cce058 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 24 Oct 2018 09:06:29 -0700 Subject: [PATCH 10/13] Move replica bitcells to new bitcells subdir --- compiler/{modules => bitcells}/replica_bitcell.py | 0 compiler/{modules => bitcells}/replica_pbitcell.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename compiler/{modules => bitcells}/replica_bitcell.py (100%) rename compiler/{modules => bitcells}/replica_pbitcell.py (100%) diff --git a/compiler/modules/replica_bitcell.py b/compiler/bitcells/replica_bitcell.py similarity index 100% rename from compiler/modules/replica_bitcell.py rename to compiler/bitcells/replica_bitcell.py diff --git a/compiler/modules/replica_pbitcell.py b/compiler/bitcells/replica_pbitcell.py similarity index 100% rename from compiler/modules/replica_pbitcell.py rename to compiler/bitcells/replica_pbitcell.py From 33c716eda8f72ee5f3acd5db3b9e21b6aeede044 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 24 Oct 2018 09:08:54 -0700 Subject: [PATCH 11/13] Rename psram bank test like sram bank testss --- .../{20_psram_1bank_test.py => 20_psram_1bank_nomux_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename compiler/tests/{20_psram_1bank_test.py => 20_psram_1bank_nomux_test.py} (100%) diff --git a/compiler/tests/20_psram_1bank_test.py b/compiler/tests/20_psram_1bank_nomux_test.py similarity index 100% rename from compiler/tests/20_psram_1bank_test.py rename to compiler/tests/20_psram_1bank_nomux_test.py From 5f17525501063673afc4a12a5aac45516dc134b4 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 24 Oct 2018 09:32:44 -0700 Subject: [PATCH 12/13] Added run-level option for write_control and enabled fast mode in functional tests --- compiler/characterizer/functional.py | 2 +- compiler/characterizer/stimuli.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 2fc7dcc4..34df6efb 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -267,7 +267,7 @@ class functional(simulation): t_intital=t_intital, t_final=t_final) - self.stim.write_control(self.cycle_times[-1] + self.period) + self.stim.write_control(self.cycle_times[-1] + self.period, runlvl=1) self.sf.close() diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 14f780c2..70351f2a 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -232,7 +232,7 @@ class stimuli(): measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final) self.sf.write(measure_string) - def write_control(self, end_time): + def write_control(self, end_time, runlvl=4): """ Write the control cards to run and end the simulation """ # UIC is needed for ngspice to converge self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time)) @@ -241,9 +241,9 @@ class stimuli(): # which is more accurate, but slower than the default trapezoid method # Do not remove this or it may not converge due to some "pa_00" nodes # unless you figure out what these are. - self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature)) + self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE method=gear TEMP={1}\n".format(runlvl,self.temperature)) else: - self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature)) + self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature)) # create plots for all signals self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") From cccde193d06c6098a3bf50e8fa7505efb1eec02d Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 24 Oct 2018 10:31:27 -0700 Subject: [PATCH 13/13] Add ngspice equivalents of RUNLVL --- compiler/characterizer/stimuli.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 70351f2a..6fa7a481 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -234,6 +234,17 @@ class stimuli(): def write_control(self, end_time, runlvl=4): """ Write the control cards to run and end the simulation """ + + # These are guesses... + if runlvl==1: + reltol = 0.02 # 2% + elif runlvl==2: + reltol = 0.01 # 1% + elif runlvl==3: + reltol = 0.005 # 0.5% + else: + reltol = 0.001 # 0.1% + # UIC is needed for ngspice to converge self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time)) if OPTS.spice_name == "ngspice": @@ -241,7 +252,7 @@ class stimuli(): # which is more accurate, but slower than the default trapezoid method # Do not remove this or it may not converge due to some "pa_00" nodes # unless you figure out what these are. - self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE method=gear TEMP={1}\n".format(runlvl,self.temperature)) + self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature)) else: self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))