diff --git a/.gitignore b/.gitignore index c4e15e9d..b16e3d0b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.out *.toc *.synctex.gz +**/model_data \ No newline at end of file diff --git a/README.md b/README.md index f70a2d87..2141c480 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,7 @@ If I forgot to add you, please let me know! [Github issues]: https://github.com/VLSIDA/PrivateRAM/issues [Github pull request]: https://github.com/VLSIDA/PrivateRAM/pulls -[Github projects]: https://github.com/VLSIDA/PrivateRAM/projects +[Github projects]: https://github.com/VLSIDA/PrivateRAM [email me]: mailto:mrg+openram@ucsc.edu [dev-group]: mailto:openram-dev-group@ucsc.edu diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 19da7d59..fc42a889 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -9,6 +9,8 @@ from .functional import * from .worst_case import * from .simulation import * from .bitline_delay import * +from .measurements import * +from .model_check import * debug.info(1,"Initializing characterizer...") OPTS.spice_exe = "" diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py index e5a50c16..c85f06bc 100644 --- a/compiler/characterizer/bitline_delay.py +++ b/compiler/characterizer/bitline_delay.py @@ -22,11 +22,24 @@ class bitline_delay(delay): self.period = tech.spice["feasible_period"] self.is_bitline_measure = True + def create_signal_names(self): + delay.create_signal_names(self) + self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"] + self.sen_name = "Xsram.s_en" + 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.bitline_meas_names = ["bl_volt", "br_volt"] + self.bl_volt_meas_names = ["volt_bl", "volt_br"] + self.bl_delay_meas_names = ["delay_bl", "delay_br"] #only used in SPICE simulation + self.bl_delay_result_name = "delay_bl_vth" #Used in the return value + 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.""" + delay.set_probe(self,probe_address, probe_data) + self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) + def write_delay_measures(self): """ Write the measure statements to quantify the bitline voltage at sense amp enable 50%. @@ -38,26 +51,52 @@ class bitline_delay(delay): self.sf.write("* {}\n".format(comment)) for read_port in self.targ_read_ports: - self.write_bitline_measures_read_port(read_port) + self.write_bitline_voltage_measures(read_port) + self.write_bitline_delay_measures(read_port) - def write_bitline_measures_read_port(self, port): + def write_bitline_voltage_measures(self, port): + """ + Add measurments to capture the bitline voltages at 50% Sense amp enable + """ + debug.info(2, "Measuring bitline column={}, port={}".format(self.bitline_column,port)) + if len(self.all_ports) == 1: #special naming case for single port sram bitlines + bitline_port = "" + else: + bitline_port = str(port) + + sen_port_name = "{}{}".format(self.sen_name,port) + for (measure_name, bl_signal_name) in zip(self.bl_volt_meas_names, self.bl_signal_names): + bl_port_name = "{}{}_{}".format(bl_signal_name, bitline_port, self.bitline_column) + measure_port_name = "{}{}".format(measure_name,port) + self.stim.gen_meas_find_voltage(measure_port_name, sen_port_name, bl_port_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) + + def write_bitline_delay_measures(self, port): """ Write the measure statements to quantify the delay and power results for a read port. """ # add measure statements for delays/slews - measure_bitline = self.get_data_bit_column_number(self.probe_address, self.probe_data) - debug.info(2, "Measuring bitline column={}".format(measure_bitline)) - for port in self.targ_read_ports: - if len(self.all_ports) == 1: #special naming case for single port sram bitlines - bitline_port = "" - else: - bitline_port = str(port) - - sen_name = "Xsram.s_en{}".format(port) - bl_name = "Xsram.Xbank0.bl{}_{}".format(bitline_port, measure_bitline) - br_name = "Xsram.Xbank0.br{}_{}".format(bitline_port, measure_bitline) - self.stim.gen_meas_find_voltage("bl_volt", sen_name, bl_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) - self.stim.gen_meas_find_voltage("br_volt", sen_name, br_name, .5, "RISE", self.cycle_times[self.measure_cycles[port]["read0"]]) + for (measure_name, bl_signal_name) in zip(self.bl_delay_meas_names, self.bl_signal_names): + meas_values = self.get_delay_meas_values(measure_name, bl_signal_name, port) + self.stim.gen_meas_delay(*meas_values) + + def get_delay_meas_values(self, delay_name, bitline_name, port): + """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" + if len(self.all_ports) == 1: #special naming case for single port sram bitlines + bitline_port = "" + else: + bitline_port = str(port) + + meas_name="{0}{1}".format(delay_name, port) + targ_name = "{0}{1}_{2}".format(bitline_name,bitline_port,self.bitline_column) + half_vdd = 0.5 * self.vdd_voltage + trig_val = half_vdd + targ_val = self.vdd_voltage-tech.spice["v_threshold_typical"] + trig_name = "clk{0}".format(port) + trig_dir="FALL" + targ_dir="FALL" + #Half period added to delay measurement to negative clock edge + trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 + return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) def gen_test_cycles_one_port(self, read_port, write_port): """Sets a list of key time-points [ns] of the waveform (each rising edge) @@ -89,6 +128,7 @@ class bitline_delay(delay): self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), self.probe_address,data_zeros,read_port) self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1 + def get_data_bit_column_number(self, probe_address, probe_data): """Calculates bitline column number of data bit under test using bit position and mux size""" if self.sram.col_addr_size>0: @@ -115,19 +155,70 @@ class bitline_delay(delay): self.stim.run_sim() #running sim prodoces spice output file. for port in self.targ_read_ports: - bitlines_meas_vals = {} - for mname in self.bitline_meas_names: - bitlines_meas_vals[mname] = parse_spice_list("timing", mname) - #Check that power parsing worked. - for name, val in bitlines_meas_vals.items(): - if type(val)!=float: - debug.error("Failed to Parse Bitline Values:\n\t\t{0}".format(bitlines_meas_vals),1) #Printing the entire dict looks bad. - result[port].update(bitlines_meas_vals) + #Parse and check the voltage measurements + bl_volt_meas_dict = {} + for mname in self.bl_volt_meas_names: + mname_port = "{}{}".format(mname,port) + volt_meas_val = parse_spice_list("timing", mname_port) + if type(volt_meas_val)!=float: + debug.error("Failed to Parse Bitline Voltage:\n\t\t{0}={1}".format(mname,volt_meas_val),1) + bl_volt_meas_dict[mname] = volt_meas_val + result[port].update(bl_volt_meas_dict) + + #Parse and check the delay measurements. Intended that one measurement will fail, save the delay that did not fail. + bl_delay_meas_dict = {} + values_added = 0 #For error checking + for mname in self.bl_delay_meas_names: #Parse + mname_port = "{}{}".format(mname,port) + delay_meas_val = parse_spice_list("timing", mname_port) + if type(delay_meas_val)==float: #Only add if value is float, do not error. + bl_delay_meas_dict[self.bl_delay_result_name] = delay_meas_val * 1e9 #convert to ns + values_added+=1 + debug.check(values_added>0, "Bitline delay measurements failed in SPICE simulation.") + debug.check(values_added<2, "Both bitlines experienced a Vth drop, check simulation results.") + result[port].update(bl_delay_meas_dict) - # The delay is from the negative edge for our SRAM return (True,result) + def check_bitline_all_results(self, results): + """Checks the bitline values measured for each tested port""" + for port in self.targ_read_ports: + self.check_bitline_port_results(results[port]) + + def check_bitline_port_results(self, port_results): + """Performs three different checks for the bitline values: functionality, bitline swing from vdd, and differential bit swing""" + bl_volt, br_volt = port_results["volt_bl"], port_results["volt_br"] + self.check_functionality(bl_volt,br_volt) + self.check_swing_from_vdd(bl_volt,br_volt) + self.check_differential_swing(bl_volt,br_volt) + + def check_functionality(self, bl_volt, br_volt): + """Checks whether the read failed or not. Measured values are hardcoded with the intention of reading a 0.""" + if bl_volt > br_volt: + debug.error("Read failure. Value 1 was read instead of 0.",1) + + def check_swing_from_vdd(self, bl_volt, br_volt): + """Checks difference on discharging bitline from VDD to see if it is within margin of the RBL height parameter.""" + if bl_volt < br_volt: + discharge_volt = bl_volt + else: + discharge_volt = br_volt + desired_bl_volt = tech.parameter["rbl_height_percentage"]*self.vdd_voltage + debug.info(1, "Active bitline={:.3f}v, Desired bitline={:.3f}v".format(discharge_volt,desired_bl_volt)) + vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. + if abs(discharge_volt - desired_bl_volt) > vdd_error_margin*self.vdd_voltage: + debug.warning("Bitline voltage is not within {}% Vdd margin. Delay chain/RBL could need resizing.".format(vdd_error_margin*100)) + + def check_differential_swing(self, bl_volt, br_volt): + """This check looks at the difference between the bitline voltages. This needs to be large enough to prevent + sensing errors.""" + bitline_swing = abs(bl_volt-br_volt) + debug.info(1,"Bitline swing={:.3f}v".format(bitline_swing)) + vdd_error_margin = .2 #20% of vdd margin for bitline, a little high for now. + if bitline_swing < vdd_error_margin*self.vdd_voltage: + debug.warning("Bitline swing less than {}% Vdd margin. Sensing errors more likely to occur.".format(vdd_error_margin)) + def analyze(self, probe_address, probe_data, slews, loads): """Measures the bitline swing of the differential bitlines (bl/br) at 50% s_en """ self.set_probe(probe_address, probe_data) @@ -141,10 +232,10 @@ class bitline_delay(delay): debug.info(1,"Bitline swing test: corner {}".format(self.corner)) (success, results)=self.run_delay_simulation() debug.check(success, "Bitline Failed: period {}".format(self.period)) - for mname in self.bitline_meas_names: - bitline_swings[mname] = results[read_port][mname] - debug.info(1,"Bitline values (bl/br): {}".format(bitline_swings)) - return bitline_swings + debug.info(1,"Bitline values (voltages/delays):\n\t {}".format(results[read_port])) + self.check_bitline_all_results(results) + + return results diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index a2140e51..061972cf 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -80,3 +80,10 @@ def convert_to_float(number): debug.error("Invalid number: {0}".format(number),1) return float_value + +def check_dict_values_is_float(dict): + """Checks if all the values are floats. Useful for checking failed Spice measurements.""" + for key, value in dict.items(): + if type(value)!=float: + return False + return True \ No newline at end of file diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 9e0543d1..7aa9641d 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -8,6 +8,7 @@ from .charutils import * import utils from globals import OPTS from .simulation import simulation +from .measurements import * class delay(simulation): """Functions to measure the delay and power of an SRAM at a given address and @@ -35,17 +36,92 @@ class delay(simulation): self.period = 0 self.set_load_slew(0,0) self.set_corner(corner) - 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"] + self.voltage_when_names = ["volt_bl", "volt_br"] + self.bitline_delay_names = ["delay_bl", "delay_br"] + + def create_measurement_objects(self): + """Create the measurements used for read and write ports""" + self.create_read_port_measurement_objects() + self.create_write_port_measurement_objects() + + def create_read_port_measurement_objects(self): + """Create the measurements used for read ports: delays, slews, powers""" + self.read_meas_objs = [] + trig_delay_name = "clk{0}" + targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit + self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. + self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read0" + + self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read1" + self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read0" + + self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3)) + self.read_meas_objs[-1].meta_str = "read1" + self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3)) + self.read_meas_objs[-1].meta_str = "read0" + + #This will later add a half-period to the spice time delay. Only for reading 0. + for obj in self.read_meas_objs: + if obj.meta_str is "read0": + obj.meta_add_delay = True + + trig_name = "Xsram.s_en{}" #Sense amp enable + if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name + port_format = "" + else: + port_format = "{}" + + bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) + self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) + self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) + + #These are read values but need to be separated for unique error checking. + self.create_bitline_delay_measurement_objects() + + def create_bitline_delay_measurement_objects(self): + """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements. + These measurements are only associated with read values + """ + self.bitline_delay_objs = [] + trig_name = "clk{0}" + if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name + port_format = "" + else: + port_format = "{}" + bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) + targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd + + targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit + self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + self.bitline_delay_objs[-1].meta_str = "read0" + self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + self.bitline_delay_objs[-1].meta_str = "read1" + #Enforces the time delay on the bitline measurements for read0 or read1 + for obj in self.bitline_delay_objs: + obj.meta_add_delay = True + + def create_write_port_measurement_objects(self): + """Create the measurements used for read ports: delays, slews, powers""" + self.write_meas_objs = [] + + self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3)) + self.write_meas_objs[-1].meta_str = "write1" + self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3)) + self.write_meas_objs[-1].meta_str = "write0" + def create_signal_names(self): self.addr_name = "A" self.din_name = "DIN" @@ -198,86 +274,75 @@ class delay(simulation): 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_val = half_vdd - targ_val = half_vdd - trig_name = trig_clk_name - if 'lh' in delay_name: - trig_dir="RISE" - targ_dir="RISE" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]] - else: - trig_dir="FALL" - targ_dir="FALL" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] - - 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[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[port]["read0"]] + def get_read_measure_variants(self, port, measure_obj): + """Checks the measurement object and calls respective function for related measurement inputs.""" + meas_type = type(measure_obj) + if meas_type is delay_measure or meas_type is slew_measure: + return self.get_delay_measure_variants(port, measure_obj) + elif meas_type is power_measure: + return self.get_power_measure_variants(port, measure_obj, "read") + elif meas_type is voltage_when_measure: + return self.get_volt_when_measure_variants(port, measure_obj) 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) - + debug.error("Input function not defined for measurement type={}".format(meas_type)) + + def get_delay_measure_variants(self, port, delay_obj): + """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)""" + #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port + #vdd is arguably constant as that is true for a single lib file. + if delay_obj.meta_str == "read0": + #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. + meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] + elif delay_obj.meta_str == "read1": + meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] + else: + debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1) + + if delay_obj.meta_add_delay: + meas_cycle_delay += self.period/2 + + return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) + + def get_power_measure_variants(self, port, power_obj, operation): + """Get the measurement values that can either vary port to port (time delays)""" + #Return value is intended to match the power measure format: t_initial, t_final, port + t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]] + t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1] + + return (t_initial, t_final, port) + + def get_volt_when_measure_variants(self, port, power_obj): + """Get the measurement values that can either vary port to port (time delays)""" + #Only checking 0 value reads for now. + t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + + return (t_trig, self.vdd_voltage, port) + 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) + for measure in self.read_meas_objs+self.bitline_delay_objs: + measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) + + def get_write_measure_variants(self, port, measure_obj): + """Checks the measurement object and calls respective function for related measurement inputs.""" + meas_type = type(measure_obj) + if meas_type is power_measure: + return self.get_power_measure_variants(port, measure_obj, "write") + else: + debug.error("Input function not defined for measurement type={}".format(meas_type)) - # 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[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[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) - 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[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[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, - t_final=t_final) + for measure in self.write_meas_objs: + measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): """ @@ -385,28 +450,7 @@ class delay(simulation): 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 @@ -427,33 +471,45 @@ class delay(simulation): #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 = [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(delays): - return (False,{}) - result[port].update(delays) + read_port_dict = {} + #Get measurements from output file + for measure in self.read_meas_objs: + read_port_dict[measure.name] = measure.retrieve_measure(port=port) + + #Check timing for read ports. Power is only checked if it was read correctly + if not self.check_valid_delays(read_port_dict): + return (False,{}) + if not check_dict_values_is_float(read_port_dict): + debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad. + + result[port].update(read_port_dict) + + bitline_delay_dict = self.evaluate_bitline_delay(port) + result[port].update(bitline_delay_dict) - 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) + write_port_dict = {} + for measure in self.write_meas_objs: + write_port_dict[measure.name] = measure.retrieve_measure(port=port) + + if not check_dict_values_is_float(write_port_dict): + debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) #Printing the entire dict looks bad. + result[port].update(write_port_dict) # The delay is from the negative edge for our SRAM return (True,result) - + def evaluate_bitline_delay(self, port): + """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function.""" + bl_delay_meas_dict = {} + values_added = 0 #For error checking + for measure in self.bitline_delay_objs: + bl_delay_val = measure.retrieve_measure(port=port) + if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error. + debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1) + bl_delay_meas_dict[measure.name] = bl_delay_val + return bl_delay_meas_dict + def run_power_simulation(self): """ This simulates a disabled SRAM to get the leakage power when it is off. @@ -478,13 +534,13 @@ class delay(simulation): #key=raw_input("press return to continue") return (leakage_power*1e3, trim_leakage_power*1e3) - def check_valid_delays(self, delay_dict): + def check_valid_delays(self, result_dict): """ Check if the measurements are defined and if they are valid. """ #Hard coded names currently - delay_hl = delay_dict["delay_hl"] - delay_lh = delay_dict["delay_lh"] - slew_hl = delay_dict["slew_hl"] - slew_lh = delay_dict["slew_lh"] + delay_hl = result_dict["delay_hl"] + delay_lh = result_dict["delay_lh"] + slew_hl = result_dict["slew_hl"] + slew_lh = result_dict["slew_lh"] 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 @@ -610,9 +666,22 @@ class delay(simulation): functions in this characterizer besides analyze.""" self.probe_address = probe_address self.probe_data = probe_data - + self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) + self.wordline_row = self.get_address_row_number(probe_address) self.prepare_netlist() + def get_data_bit_column_number(self, probe_address, probe_data): + """Calculates bitline column number of data bit under test using bit position and mux size""" + if self.sram.col_addr_size>0: + col_address = int(probe_address[0:self.sram.col_addr_size],2) + else: + col_address = 0 + bl_column = int(self.sram.words_per_row*probe_data + col_address) + return bl_column + + def get_address_row_number(self, probe_address): + """Calculates wordline row number of data bit under test using address and column mux size""" + return int(probe_address[self.sram.col_addr_size:],2) def prepare_netlist(self): """ Prepare a trimmed netlist and regular netlist. """ @@ -645,6 +714,9 @@ class delay(simulation): char_sram_data = {} self.set_probe(probe_address, probe_data) + self.create_signal_names() + self.create_measurement_names() + self.create_measurement_objects() self.load=max(loads) self.slew=max(slews) @@ -828,7 +900,8 @@ class delay(simulation): """ if OPTS.num_rw_ports > 1 or OPTS.num_w_ports > 0 and OPTS.num_r_ports > 0: debug.warning("Analytical characterization results are not supported for multiport.") - + self.create_signal_names() + self.create_measurement_names() power = self.analytical_power(slews, loads) port_data = self.get_empty_measure_data_dict() for slew in slews: @@ -845,7 +918,9 @@ class delay(simulation): port_data[port][mname].append(bank_delay[port].slew/1e3) else: debug.error("Measurement name not recognized: {}".format(mname),1) - sram_data = { "min_period": 0, + period_margin = 0.1 + risefall_delay = bank_delay[self.read_ports[0]].delay/1e3 + sram_data = { "min_period":risefall_delay*2*period_margin, "leakage_power": power.leakage} return (sram_data,port_data) @@ -890,7 +965,7 @@ class delay(simulation): 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 + measure_names = self.delay_meas_names + self.power_meas_names + self.voltage_when_names + self.bitline_delay_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 self.all_ports] return measure_data diff --git a/compiler/characterizer/logical_effort.py b/compiler/characterizer/logical_effort.py index bf8c1585..c80e69a2 100644 --- a/compiler/characterizer/logical_effort.py +++ b/compiler/characterizer/logical_effort.py @@ -10,7 +10,8 @@ class logical_effort(): min_inv_cin = 1+beta pinv=parameter["min_inv_para_delay"] - def __init__(self, size, cin, cout, parasitic, out_is_rise=True): + def __init__(self, name, size, cin, cout, parasitic, out_is_rise=True): + self.name = name self.cin = cin self.cout = cout self.logical_effort = (self.cin/size)/logical_effort.min_inv_cin @@ -19,8 +20,13 @@ class logical_effort(): self.is_rise = out_is_rise def __str__(self): - return "g=" + str(self.logical_effort) + ", h=" + str(self.eletrical_effort) + ", p=" + str(self.parasitic_scale)+"*pinv, rise_delay="+str(self.is_rise) - + return "Name={}, g={}, h={}, p={}*pinv, rise_delay={}".format(self.name, + self.logical_effort, + self.eletrical_effort, + self.parasitic_scale, + self.is_rise + ) + def get_stage_effort(self): return self.logical_effort*self.eletrical_effort @@ -29,6 +35,10 @@ class logical_effort(): def get_stage_delay(self, pinv): return self.get_stage_effort()+self.get_parasitic_delay(pinv) + +def calculate_delays(stage_effort_list, pinv): + """Convert stage effort objects to list of delay values""" + return [stage.get_stage_delay(pinv) for stage in stage_effort_list] def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]): """Calculates the total delay of a given delay path made of a list of logical effort objects.""" @@ -40,7 +50,7 @@ def calculate_relative_rise_fall_delays(stage_effort_list, pinv=parameter["min_i debug.info(2, "Calculating rise/fall relative delays") total_rise_delay, total_fall_delay = 0,0 for stage in stage_effort_list: - debug.info(3, stage) + debug.info(2, stage) if stage.is_rise: total_rise_delay += stage.get_stage_delay(pinv) else: diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py new file mode 100644 index 00000000..e3d16584 --- /dev/null +++ b/compiler/characterizer/measurements.py @@ -0,0 +1,159 @@ +import debug +from tech import drc, parameter, spice +from abc import ABC, abstractmethod +from .stimuli import * +from .charutils import * + +class spice_measurement(ABC): + """Base class for spice stimulus measurements.""" + def __init__(self, measure_name, measure_scale=None): + #Names must be unique for correct spice simulation, but not enforced here. + self.name = measure_name + self.measure_scale = measure_scale + #Some meta values used externally. variables are added here for consistency accross the objects + self.meta_str = None + self.meta_add_delay = False + @abstractmethod + def get_measure_function(self): + return None + + @abstractmethod + def get_measure_values(self): + return None + + def write_measure(self, stim_obj, input_tuple): + measure_func = self.get_measure_function() + if measure_func == None: + debug.error("Did not set measure function",1) + measure_vals = self.get_measure_values(*input_tuple) + measure_func(stim_obj, *measure_vals) + + def retrieve_measure(self, port=""): + value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + if type(value)!=float or self.measure_scale == None: + return value + else: + return value*self.measure_scale + +class delay_measure(spice_measurement): + """Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" + + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd=0.5, targ_vdd=0.5, measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd) + + def get_measure_function(self): + return stimuli.gen_meas_delay + + def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd): + """Set the constants for this measurement: signal names, directions, and trigger scales""" + self.trig_dir_str = trig_dir_str + self.targ_dir_str = targ_dir_str + + self.trig_val_of_vdd = trig_vdd + self.targ_val_of_vdd = targ_vdd + + self.trig_name_no_port = trig_name + self.targ_name_no_port = targ_name + + #Time delays and ports are variant and needed as inputs when writing the measurement + + def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + trig_val = self.trig_val_of_vdd * vdd_voltage + targ_val = self.targ_val_of_vdd * vdd_voltage + + if port != None: + #For dictionary indexing reasons, the name is formatted differently than the signals + meas_name = "{}{}".format(self.name, port) + trig_name = self.trig_name_no_port.format(port) + targ_name = self.targ_name_no_port.format(port) + else: + meas_name = self.name + trig_name = self.trig_name_no_port + targ_name = self.targ_name_no_port + + return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td) + +class slew_measure(delay_measure): + + def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(signal_name, slew_dir_str) + + def set_meas_constants(self, signal_name, slew_dir_str): + """Set the values needed to generate a Spice measurement statement based on the name of the measurement.""" + self.trig_dir_str = slew_dir_str + self.targ_dir_str = slew_dir_str + + if slew_dir_str == "RISE": + self.trig_val_of_vdd = 0.1 + self.targ_val_of_vdd = 0.9 + elif slew_dir_str == "FALL": + self.trig_val_of_vdd = 0.9 + self.targ_val_of_vdd = 0.1 + else: + debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1) + + self.trig_name_no_port = signal_name + self.targ_name_no_port = signal_name + + #Time delays and ports are variant and needed as inputs when writing the measurement + +class power_measure(spice_measurement): + """Generates a spice measurement for the average power between two time points.""" + + def __init__(self, measure_name, power_type="", measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(power_type) + + def get_measure_function(self): + return stimuli.gen_meas_power + + def set_meas_constants(self, power_type): + """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" + #Not needed for power simulation + self.power_type = power_type #Expected to be "RISE"/"FALL" + + def get_measure_values(self, t_initial, t_final, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + if port != None: + meas_name = "{}{}".format(self.name, port) + else: + meas_name = self.name + return (meas_name,t_initial,t_final) + +class voltage_when_measure(spice_measurement): + """Generates a spice measurement to measure the voltage of a signal based on the voltage of another.""" + + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd) + + def get_measure_function(self): + return stimuli.gen_meas_find_voltage + + def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd): + """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" + self.trig_dir_str = trig_dir_str + self.trig_val_of_vdd = trig_vdd + + self.trig_name_no_port = trig_name + self.targ_name_no_port = targ_name + + def get_measure_values(self, trig_td, vdd_voltage, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + + if port != None: + #For dictionary indexing reasons, the name is formatted differently than the signals + meas_name = "{}{}".format(self.name, port) + trig_name = self.trig_name_no_port.format(port) + targ_name = self.targ_name_no_port.format(port) + else: + meas_name = self.name + trig_name = self.trig_name_no_port + targ_name = self.targ_name_no_port + + trig_voltage = self.trig_val_of_vdd*vdd_voltage + + return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) \ No newline at end of file diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py new file mode 100644 index 00000000..88e0b96a --- /dev/null +++ b/compiler/characterizer/model_check.py @@ -0,0 +1,336 @@ +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 +from .delay import delay +from .measurements import * + +class model_check(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 + several bits and record the delay and differences between the bits. + + """ + + def __init__(self, sram, spfile, corner): + delay.__init__(self,sram,spfile,corner) + self.period = tech.spice["feasible_period"] + self.create_data_names() + + def create_data_names(self): + self.wl_meas_name, self.wl_model_name = "wl_measures", "wl_model" + self.sae_meas_name, self.sae_model_name = "sae_measures", "sae_model" + + def create_measurement_names(self): + """Create measurement names. The names themselves currently define the type of measurement""" + #Create delay measurement names + wl_en_driver_delay_names = ["delay_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] + wl_driver_delay_names = ["delay_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] + sen_driver_delay_names = ["delay_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + dc_delay_names = ["delay_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] + self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"] + self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"] + + self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names)) + #Create slew measurement names + wl_en_driver_slew_names = ["slew_wl_en_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] + wl_driver_slew_names = ["slew_wl_dvr{}_".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] + sen_driver_slew_names = ["slew_sen_dvr{}_".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + dc_slew_names = ["slew_delay_chain_stage{}_".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] + self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"] + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"] + + def create_signal_names(self): + """Creates list of the signal names used in the spice file along the wl and sen paths.""" + delay.create_signal_names(self) + #Signal names are all hardcoded, need to update to make it work for probe address and different configurations. + wl_en_driver_signals = ["Xsram.Xcontrol0.Xbuf_wl_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] + wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver0.Xwl_driver_inv{}.Zb{}_int".format(self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] + sen_driver_signals = ["Xsram.Xcontrol0.Xbuf_s_en.Zb{}_int".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + delay_chain_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_{}".format(stage) for stage in range(1,self.get_num_delay_stages())] + + self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar"]+\ + wl_en_driver_signals+\ + ["Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_{}".format(self.wordline_row)]+\ + wl_driver_signals+\ + ["Xsram.Xbank0.wl_{}".format(self.wordline_row)] + pre_delay_chain_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in"] + self.rbl_en_signal_names = pre_delay_chain_names+\ + delay_chain_signal_names+\ + ["Xsram.Xcontrol0.Xreplica_bitline.delayed_en"] + self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en"]+\ + sen_driver_signals+\ + ["Xsram.s_en0"] + + def create_measurement_objects(self): + """Create the measurements used for read and write ports""" + self.create_wordline_measurement_objects() + self.create_sae_measurement_objects() + self.all_measures = self.wl_meas_objs+self.sae_meas_objs + + def create_wordline_measurement_objects(self): + """Create the measurements to measure the wordline path from the gated_clk_bar signal""" + self.wl_meas_objs = [] + trig_dir = "RISE" + targ_dir = "FALL" + + for i in range(1, len(self.wl_signal_names)): + self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], + self.wl_signal_names[i-1], + self.wl_signal_names[i], + trig_dir, + targ_dir, + measure_scale=1e9)) + self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1], + self.wl_signal_names[i-1], + trig_dir, + measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[-1], self.wl_signal_names[-1], trig_dir, measure_scale=1e9)) + + def create_sae_measurement_objects(self): + """Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two.""" + + self.sae_meas_objs = [] + trig_dir = "RISE" + targ_dir = "FALL" + #Add measurements from gated_clk_bar to RBL + for i in range(1, len(self.rbl_en_signal_names)): + self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], + self.rbl_en_signal_names[i-1], + self.rbl_en_signal_names[i], + trig_dir, + targ_dir, + measure_scale=1e9)) + self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1], + self.rbl_en_signal_names[i-1], + trig_dir, + measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[-1], + self.rbl_en_signal_names[-1], + trig_dir, + measure_scale=1e9)) + + #Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. + trig_dir = "FALL" + targ_dir = "RISE" + #Add measurements from gated_clk_bar to RBL + for i in range(1, len(self.sae_signal_names)): + self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], + self.sae_signal_names[i-1], + self.sae_signal_names[i], + trig_dir, + targ_dir, + measure_scale=1e9)) + self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1], + self.sae_signal_names[i-1], + trig_dir, + measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[-1], + self.sae_signal_names[-1], + trig_dir, + measure_scale=1e9)) + + 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_measures_read_port(read_port) + + def get_delay_measure_variants(self, port, measure_obj): + """Get the measurement values that can either vary from simulation to simulation (vdd, address) + or port to port (time delays)""" + #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port + #Assuming only read 0 for now + if not (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure): + debug.error("Measurement not recognized by the model checker.",1) + meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 + return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) + + def write_measures_read_port(self, port): + """ + Write the measure statements for all nodes along the wordline path. + """ + # add measure statements for delays/slews + for measure in self.all_measures: + measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) + + def get_measurement_values(self, meas_objs, port): + """Gets the delays and slews from a specified port from the spice output file and returns them as lists.""" + delay_meas_list = [] + slew_meas_list = [] + for measure in meas_objs: + measure_value = measure.retrieve_measure(port=port) + if type(measure_value) != float: + debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, measure_value),1) + if type(measure) is delay_measure: + delay_meas_list.append(measure_value) + elif type(measure)is slew_measure: + slew_meas_list.append(measure_value) + else: + debug.error("Measurement object not recognized.",1) + return delay_meas_list, slew_meas_list + + 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") + + wl_delay_result = [[] for i in self.all_ports] + wl_slew_result = [[] for i in self.all_ports] + sae_delay_result = [[] for i in self.all_ports] + sae_slew_result = [[] for i in self.all_ports] + # Checking from not data_value to data_value + self.write_delay_stimulus() + + self.stim.run_sim() #running sim prodoces spice output file. + + #Retrieve the results from the output file + for port in self.targ_read_ports: + #Parse and check the voltage measurements + wl_delay_result[port], wl_slew_result[port] = self.get_measurement_values(self.wl_meas_objs, port) + sae_delay_result[port], sae_slew_result[port] = self.get_measurement_values(self.sae_meas_objs, port) + return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result) + + def get_model_delays(self, port): + """Get model delays based on port. Currently assumes single RW port.""" + return self.sram.control_logic_rw.get_wl_sen_delays() + + def get_num_delay_stages(self): + """Gets the number of stages in the delay chain from the control logic""" + return len(self.sram.control_logic_rw.replica_bitline.delay_fanout_list) + + def get_num_delay_stage_fanout(self): + """Gets fanout in each stage in the delay chain. Assumes each stage is the same""" + return self.sram.control_logic_rw.replica_bitline.delay_fanout_list[0] + + def get_num_wl_en_driver_stages(self): + """Gets the number of stages in the wl_en driver from the control logic""" + return self.sram.control_logic_rw.wl_en_driver.num_stages + + def get_num_sen_driver_stages(self): + """Gets the number of stages in the sen driver from the control logic""" + return self.sram.control_logic_rw.s_en_driver.num_stages + + def get_num_wl_driver_stages(self): + """Gets the number of stages in the wordline driver from the control logic""" + return self.sram.bank.wordline_driver.inv.num_stages + + def scale_delays(self, delay_list): + """Takes in a list of measured delays and convert it to simple units to easily compare to model values.""" + converted_values = [] + #Calculate average + total = 0 + for meas_value in delay_list: + total+=meas_value + average = total/len(delay_list) + + #Convert values + for meas_value in delay_list: + converted_values.append(meas_value/average) + return converted_values + + def min_max_normalization(self, value_list): + """Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1""" + scaled_values = [] + min_max_diff = max(value_list) - min(value_list) + average = sum(value_list)/len(value_list) + for value in value_list: + scaled_values.append((value-average)/(min_max_diff)) + return scaled_values + + def calculate_error_l2_norm(self, list_a, list_b): + """Calculates error between two lists using the l2 norm""" + error_list = [] + for val_a, val_b in zip(list_a, list_b): + error_list.append((val_a-val_b)**2) + return error_list + + def compare_measured_and_model(self, measured_vals, model_vals): + """First scales both inputs into similar ranges and then compares the error between both.""" + scaled_meas = self.min_max_normalization(measured_vals) + debug.info(1, "Scaled measurements:\n{}".format(scaled_meas)) + scaled_model = self.min_max_normalization(model_vals) + debug.info(1, "Scaled model:\n{}".format(scaled_model)) + errors = self.calculate_error_l2_norm(scaled_meas, scaled_model) + debug.info(1, "Errors:\n{}\n".format(errors)) + + def analyze(self, probe_address, probe_data, slews, loads): + """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays.""" + self.load=max(loads) + self.slew=max(slews) + self.set_probe(probe_address, probe_data) + self.create_signal_names() + self.create_measurement_names() + self.create_measurement_objects() + data_dict = {} + + read_port = self.read_ports[0] #only test the first read port + self.targ_read_ports = [read_port] + self.targ_write_ports = [self.write_ports[0]] + debug.info(1,"Model test: corner {}".format(self.corner)) + (success, wl_delays, sae_delays, wl_slews, sae_slews)=self.run_delay_simulation() + debug.check(success, "Model measurements Failed: period={}".format(self.period)) + wl_model_delays, sae_model_delays = self.get_model_delays(read_port) + + debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port])) + debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays)) + debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port])) + debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port])) + debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays)) + debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port])) + + data_dict[self.wl_meas_name] = wl_delays[read_port] + data_dict[self.wl_model_name] = wl_model_delays + data_dict[self.sae_meas_name] = sae_delays[read_port] + data_dict[self.sae_model_name] = sae_model_delays + + #Some evaluations of the model and measured values + debug.info(1, "Comparing wordline measurements and model.") + self.compare_measured_and_model(wl_delays[read_port], wl_model_delays) + debug.info(1, "Comparing SAE measurements and model") + self.compare_measured_and_model(sae_delays[read_port], sae_model_delays) + + return data_dict + + def get_all_signal_names(self): + """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV.""" + name_dict = {} + #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names. + name_dict[self.wl_meas_name] = self.wl_signal_names[1:] + name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured. + name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:] + name_dict[self.sae_model_name] = name_dict["sae_measures"] + return name_dict + + + \ No newline at end of file diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index d4597cf9..57e578f5 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -30,13 +30,15 @@ class control_logic(design.design): self.port_type = port_type self.num_cols = word_size*words_per_row - self.num_words = num_rows * words_per_row + self.num_words = num_rows*words_per_row - self.enable_delay_chain_resizing = False + self.enable_delay_chain_resizing = True - #self.sram=None #disable re-sizing for debugging, FIXME: resizing is not working, needs to be adjusted for new control logic. - self.wl_timing_tolerance = 1 #Determines how much larger the sen delay should be. Accounts for possible error in model. - self.parasitic_inv_delay = parameter["min_inv_para_delay"] #Keeping 0 for now until further testing. + #Determines how much larger the sen delay should be. Accounts for possible error in model. + self.wl_timing_tolerance = 1 + self.parasitic_inv_delay = parameter["min_inv_para_delay"] + self.wl_stage_efforts = None + self.sen_stage_efforts = None if self.port_type == "rw": self.num_control_signals = 2 @@ -130,43 +132,62 @@ class control_logic(design.design): self.add_mod(self.p_en_bar_driver) if (self.port_type == "rw") or (self.port_type == "r"): - delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size() - bitcell_loads = int(math.ceil(self.num_rows / 2.0)) - self.replica_bitline = factory.create(module_type="replica_bitline", - delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic, - bitcell_loads=bitcell_loads) - - - if self.sram != None: - self.set_sen_wl_delays() - - if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_total_timing_match(): #check condition based on resizing method - #This resizes to match fall and rise delays, can make the delay chain weird sizes. - # stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic) - # self.replica_bitline = replica_bitline(stage_list, bitcell_loads, name="replica_bitline_resized_"+self.port_type) - - #This resizes based on total delay. - delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic) + from importlib import reload + self.delay_chain_resized = False + c = reload(__import__(OPTS.replica_bitline)) + replica_bitline = getattr(c, OPTS.replica_bitline) + bitcell_loads = int(math.ceil(self.num_rows * parameter["rbl_height_percentage"])) + #Use a model to determine the delays with that heuristic + if OPTS.use_tech_delay_chain_size: #Use tech parameters if set. + delay_stages = parameter["static_delay_stages"] + delay_fanout = parameter["static_fanout_per_stage"] + debug.info(1, "Using tech parameters to size delay chain: stages={}, fanout={}".format(delay_stages,delay_fanout)) self.replica_bitline = factory.create(module_type="replica_bitline", delay_fanout_list=[delay_fanout]*delay_stages, bitcell_loads=bitcell_loads) - - self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing + if self.sram != None: #Calculate model value even for specified sizes + self.set_sen_wl_delays() + + else: #Otherwise, use a heuristic and/or model based sizing. + #First use a heuristic + delay_stages_heuristic, delay_fanout_heuristic = self.get_heuristic_delay_chain_size() + self.replica_bitline = factory.create(module_type="replica_bitline", + delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic, + bitcell_loads=bitcell_loads) + #Resize if necessary, condition depends on resizing method + if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match(): + #This resizes to match fall and rise delays, can make the delay chain weird sizes. + stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic) + self.replica_bitline = factory.create(module_type="replica_bitline", + delay_fanout_list=stage_list, + bitcell_loads=bitcell_loads) + + #This resizes based on total delay. + # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic) + # self.replica_bitline = factory.create(module_type="replica_bitline", + # delay_fanout_list=[delay_fanout]*delay_stages, + # bitcell_loads=bitcell_loads) + + self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing + self.delay_chain_resized = True self.add_mod(self.replica_bitline) def get_heuristic_delay_chain_size(self): """Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """ - # FIXME: These should be tuned according to the additional size parameters - delay_fanout = 3 # This can be anything >=2 - # Delay stages Must be non-inverting - if self.words_per_row >= 4: + #FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? + delay_fanout = 3 # This can be anything >=3 + # Model poorly captures delay of the column mux. Be pessismistic for column mux + if self.words_per_row >= 2: delay_stages = 8 - elif self.words_per_row == 2: - delay_stages = 6 else: - delay_stages = 4 - + delay_stages = 2 + + #Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference + #on certain sram configs. + if self.port_type == "r": + delay_stages+=2 + return (delay_stages, delay_fanout) def set_sen_wl_delays(self): @@ -227,14 +248,25 @@ class control_logic(design.design): required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2) debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise)) + #If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. + WARNING_FANOUT_DIFF = 5 + stages_close = False #The stages need to be equal (or at least a even number of stages with matching rise/fall delays) while True: stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall) stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise) debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise)) + if abs(stages_fall-stages_rise) == 1 and not stages_close: + stages_close = True + safe_fanout_rise = fanout_rise + safe_fanout_fall = fanout_fall + if stages_fall == stages_rise: break - elif abs(stages_fall-stages_rise) == 1: + elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise): + debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.") + fanout_rise = safe_fanout_rise + fanout_fall = safe_fanout_fall break #There should also be a condition to make sure the fanout does not get too large. #Otherwise, increase the fanout of delay with the most stages, calculate new stages @@ -819,8 +851,8 @@ class control_logic(design.design): def get_delays_to_wl(self): """Get the delay (in delay units) of the clk to a wordline in the bitcell array""" debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - stage_efforts = self.determine_wordline_stage_efforts() - clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay) + self.wl_stage_efforts = self.determine_wordline_stage_efforts() + clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts, self.parasitic_inv_delay) total_delay = clk_to_wl_rise + clk_to_wl_fall debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay)) return clk_to_wl_rise,clk_to_wl_fall @@ -849,8 +881,8 @@ class control_logic(design.design): This does not incorporate the delay of the replica bitline. """ debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - stage_efforts = self.determine_sa_enable_stage_efforts() - clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay) + self.sen_stage_efforts = self.determine_sa_enable_stage_efforts() + clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts, self.parasitic_inv_delay) total_delay = clk_to_sen_rise + clk_to_sen_fall debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay)) return clk_to_sen_rise, clk_to_sen_fall @@ -881,4 +913,12 @@ class control_logic(design.design): last_stage_rise = stage_effort_list[-1].is_rise return stage_effort_list - + + def get_wl_sen_delays(self): + """Gets a list of the stages and delays in order of their path.""" + if self.sen_stage_efforts == None or self.wl_stage_efforts == None: + debug.error("Model delays not calculated for SRAM.", 1) + wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts, self.parasitic_inv_delay) + sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts, self.parasitic_inv_delay) + return wl_delays, sen_delays + diff --git a/compiler/modules/sense_amp.py b/compiler/modules/sense_amp.py index 8553e42c..9601259c 100644 --- a/compiler/modules/sense_amp.py +++ b/compiler/modules/sense_amp.py @@ -48,4 +48,5 @@ class sense_amp(design.design): """Get the relative capacitance of sense amp enable gate cin""" pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx") nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx") + #sen is connected to 2 pmos isolation TX and 1 nmos per sense amp. return 2*pmos_cin + nmos_cin \ No newline at end of file diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index ebf95c95..dc80a13b 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -142,4 +142,4 @@ class sense_amp_array(design.design): def get_en_cin(self): """Get the relative capacitance of all the sense amp enable connections in the array""" sense_amp_en_cin = self.amp.get_en_cin() - return sense_amp_en_cin * self.words_per_row + return sense_amp_en_cin * self.word_size diff --git a/compiler/options.py b/compiler/options.py index fc1eb7d1..4d3461e6 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -71,6 +71,9 @@ class options(optparse.Values): # You can manually specify banks, but it is better to auto-detect it. num_banks = 1 + #Uses the delay chain size in the tech.py file rather automatic sizing. + use_tech_delay_chain_size = False + # These are the default modules that can be over-riden bank_select = "bank_select" bitcell_array = "bitcell_array" diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index 3a0e4e78..23f3f4f8 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -196,17 +196,16 @@ class pdriver(pgate.pgate): def get_stage_efforts(self, external_cout, inp_is_rise=False): """Get the stage efforts of the A -> Z path""" - - cout_list = {} + cout_list = [] for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]): - cout_list[prev_inv]=inv.get_cin() - - cout_list[self.inv_list[-1]]=external_cout + cout_list.append(inv.get_cin()) + + cout_list.append(external_cout) stage_effort_list = [] last_inp_is_rise = inp_is_rise - for inv in self.inv_list: - stage = inv.get_stage_effort(cout_list[inv], last_inp_is_rise) + for inv,cout in zip(self.inv_list,cout_list): + stage = inv.get_stage_effort(cout, last_inp_is_rise) stage_effort_list.append(stage) last_inp_is_rise = stage.is_rise diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index aa16ab54..f1ba89f4 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -292,4 +292,4 @@ class pinv(pgate.pgate): Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ parasitic_delay = 1 - return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index d196bf15..fa24dc1f 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -258,4 +258,4 @@ class pnand2(pgate.pgate): Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ parasitic_delay = 2 - return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 29ae98c0..d4554863 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -4,6 +4,7 @@ import debug from tech import drc, parameter, spice from vector import vector from globals import OPTS +import logical_effort from sram_factory import factory class pnand3(pgate.pgate): @@ -270,4 +271,4 @@ class pnand3(pgate.pgate): Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ parasitic_delay = 3 - return logical_effort.logical_effort(self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) + return logical_effort.logical_effort(self.name, self.size, self.get_cin(), cout, parasitic_delay, not inp_is_rise) diff --git a/compiler/sram_config.py b/compiler/sram_config.py index 24e3cbc9..5edf9282 100644 --- a/compiler/sram_config.py +++ b/compiler/sram_config.py @@ -7,13 +7,13 @@ from sram_factory import factory class sram_config: """ This is a structure that is used to hold the SRAM configuration options. """ - def __init__(self, word_size, num_words, num_banks=1): + def __init__(self, word_size, num_words, num_banks=1, words_per_row=None): self.word_size = word_size self.num_words = num_words self.num_banks = num_banks # This will get over-written when we determine the organization - self.words_per_row = None + self.words_per_row = words_per_row self.compute_sizes() diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py index 91ad0500..e0545af4 100755 --- a/compiler/tests/16_control_logic_test.py +++ b/compiler/tests/16_control_logic_test.py @@ -36,17 +36,21 @@ class control_logic_test(openram_test): # Check port specific control logic OPTS.num_rw_ports = 1 - OPTS.num_w_ports = 1 - OPTS.num_r_ports = 1 + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 0 debug.info(1, "Testing sample for control_logic for multiport, only write control logic") a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="rw") self.local_check(a) + OPTS.num_rw_ports = 0 + OPTS.num_w_ports = 1 debug.info(1, "Testing sample for control_logic for multiport, only write control logic") a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="w") self.local_check(a) + OPTS.num_w_ports = 0 + OPTS.num_r_ports = 1 debug.info(1, "Testing sample for control_logic for multiport, only read control logic") a = control_logic.control_logic(num_rows=128, words_per_row=1, word_size=8, port_type="r") self.local_check(a) diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index d412d42f..fa7fc35f 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -23,7 +23,7 @@ class timing_sram_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import delay, bitline_delay + from characterizer import delay from sram import sram from sram_config import sram_config c = sram_config(word_size=1, @@ -43,37 +43,44 @@ class timing_sram_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) d = delay(s.s, tempspice, corner) - bl = bitline_delay(s.s, tempspice, corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] data, port_data = d.analyze(probe_address, probe_data, slews, loads) - #bitline_swing = bl.analyze(probe_address, probe_data, slews, loads) #Combine info about port into all data data.update(port_data[0]) - + if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.2152017], - 'delay_lh': [0.2152017], - 'leakage_power': 0.0022907, - 'min_period': 0.488, - 'read0_power': [0.47437749999999995], - 'read1_power': [0.45026109999999997], - 'slew_hl': [0.0846786], - 'slew_lh': [0.0846786], - 'write0_power': [0.40809259999999997], - 'write1_power': [0.4078904]} + golden_data = {'delay_bl': [0.1980959], + 'delay_br': [0.1946091], + 'delay_hl': [0.2121267], + 'delay_lh': [0.2121267], + 'leakage_power': 0.0023761999999999998, + 'min_period': 0.43, + 'read0_power': [0.5139368], + 'read1_power': [0.48940979999999995], + 'slew_hl': [0.0516745], + 'slew_lh': [0.0516745], + 'volt_bl': [0.5374525], + 'volt_br': [1.1058], + 'write0_power': [0.46267169999999996], + 'write1_power': [0.4670826]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.4333000000000002], - 'delay_lh': [1.4333000000000002], - 'leakage_power': 0.0271847, - 'min_period': 2.891, - 'read0_power': [15.714200000000002], - 'read1_power': [14.9848], - 'slew_hl': [0.6819276999999999], - 'slew_lh': [0.6819276999999999], - 'write0_power': [13.9658], - 'write1_power': [14.8422]} + golden_data = {'delay_bl': [1.1029], + 'delay_br': [0.9656455999999999], + 'delay_hl': [1.288], + 'delay_lh': [1.288], + 'leakage_power': 0.0273896, + 'min_period': 2.578, + 'read0_power': [16.9996], + 'read1_power': [16.2616], + 'slew_hl': [0.47891700000000004], + 'slew_lh': [0.47891700000000004], + 'volt_bl': [4.2155], + 'volt_br': [5.8142], + 'write0_power': [16.0656], + 'write1_power': [16.2616]} + else: self.assertTrue(False) # other techs fail # Check if no too many or too few results diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index aac42fa6..5af44e69 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -51,27 +51,36 @@ class timing_sram_test(openram_test): data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.221699], - 'delay_lh': [0.221699], - 'leakage_power': 0.001467648, - 'min_period': 0.605, - 'read0_power': [0.3879335], - 'read1_power': [0.3662724], - 'slew_hl': [0.08562444999999999], - 'slew_lh': [0.08562444999999999], - 'write0_power': [0.3362456], - 'write1_power': [0.3372035]} + golden_data = {'delay_bl': [0.2003652], + 'delay_br': [0.198698], + 'delay_hl': [0.2108836], + 'delay_lh': [0.2108836], + 'leakage_power': 0.001564799, + 'min_period': 0.508, + 'read0_power': [0.43916689999999997], + 'read1_power': [0.4198608], + 'slew_hl': [0.0455126], + 'slew_lh': [0.0455126], + 'volt_bl': [0.6472883], + 'volt_br': [1.114024], + 'write0_power': [0.40681890000000004], + 'write1_power': [0.4198608]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.7951730000000001], - 'delay_lh': [1.7951730000000001], - 'leakage_power': 0.001669513, - 'min_period': 3.594, - 'read0_power': [17.03022], - 'read1_power': [16.55897], - 'slew_hl': [0.7079951], - 'slew_lh': [0.7079951], - 'write0_power': [15.16726], - 'write1_power': [16.13527]} + golden_data = {'delay_bl': [1.3937359999999999], + 'delay_br': [1.2596429999999998], + 'delay_hl': [1.5747600000000002], + 'delay_lh': [1.5747600000000002], + 'leakage_power': 0.00195795, + 'min_period': 3.281, + 'read0_power': [14.92874], + 'read1_power': [14.369810000000001], + 'slew_hl': [0.49631959999999997], + 'slew_lh': [0.49631959999999997], + 'volt_bl': [4.132618], + 'volt_br': [5.573099], + 'write0_power': [13.79953], + 'write1_power': [14.369810000000001]} + else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/28_delay_model_test.py new file mode 100755 index 00000000..a02a42e9 --- /dev/null +++ b/compiler/tests/28_delay_model_test.py @@ -0,0 +1,64 @@ +#!/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 delay_model_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.spice_name="hspice" + OPTS.analytical_delay = False + OPTS.netlist_only = True + OPTS.trim_netlist = False + debug.info(1, "Trimming disabled for this test. Simulation could be slow.") + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + + from characterizer import model_check + from sram import sram + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=16, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") + s = sram(c, name="sram1") + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + probe_address = "1" * s.s.addr_size + probe_data = s.s.word_size - 1 + debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + mc = model_check(s.s, tempspice, corner) + import tech + loads = [tech.spice["msflop_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + sram_data = mc.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + + #debug.info(1,"Data:\n{}".format(wl_data)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/config_data.py b/compiler/tests/config_data.py new file mode 100755 index 00000000..849d441d --- /dev/null +++ b/compiler/tests/config_data.py @@ -0,0 +1,11 @@ +#Config file used for collecting data. +word_size = 1 +num_words = 16 + +tech_name = "freepdk45" +#process_corners = ["TT", "FF", "SS", "SF", "FS"] +process_corners = ["TT", "FF", "SS"] +supply_voltages = [1.0] +temperatures = [25] + + diff --git a/compiler/tests/delay_data_collection.py b/compiler/tests/delay_data_collection.py new file mode 100644 index 00000000..e893d8ec --- /dev/null +++ b/compiler/tests/delay_data_collection.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 +""" +Run a regression test on various srams +""" +import csv,sys,os +import pandas as pd +import matplotlib.pyplot as plt + +import unittest +from testutils import header,openram_test +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug +from sram import sram +from sram_config import sram_config + +MODEL_DIR = "model_data/" + +class data_collection(openram_test): + + def runTest(self): + + #Uncomment this for model evaluation + # ratio_data = self.calculate_delay_ratios_of_srams() + # self.display_data(ratio_data) + + self.run_delay_chain_analysis() + + globals.end_openram() + + def run_delay_chain_analysis(self): + """Generates sram with different delay chain configs over different corners and + analyzes delay average and variation.""" + OPTS.use_tech_delay_chain_size = True + #Constant sram config for this test + word_size, num_words, words_per_row = 1, 16, 1 + #Only change delay chain + dc_config_list = [(2,3), (3,3), (3,4), (4,2), (4,3), (4,4), (2,4), (2,5)] + #dc_config_list = [(2,3), (3,3)] + dc_avgs = [] + dc_vars = [] + for stages,fanout in dc_config_list: + self.init_data_gen() + self.set_delay_chain(stages,fanout) + self.save_data_sram_corners(word_size, num_words, words_per_row) + wl_dataframe, sae_dataframe = self.get_csv_data() + delay_sums = self.get_delay_chain_sums(sae_dataframe) + dc_avgs.append(self.get_average(delay_sums)) + dc_vars.append(self.get_variance(delay_sums)) + debug.info(1,"DC config={}: avg={} variance={}".format((stages,fanout), dc_avgs[-1], dc_vars[-1])) + + #plot data + self.plot_two_data_sets(dc_config_list, dc_avgs, dc_vars) + #self.plot_data(dc_config_list, dc_avgs) + #self.plot_data(dc_config_list, dc_vars) + + def get_delay_chain_sums(self, sae_dataframe): + """Calculate the total delay of the delay chain over different corners""" + (start_dc, end_dc) = self.delay_obj.delay_chain_indices + start_data_pos = len(self.config_fields)+1 #items before this point are configuration related + delay_sums = [] + row_count = 0 + #Get delay sums over different corners + for sae_row in sae_dataframe.itertuples(): + dc_delays = sae_row[start_data_pos+start_dc:start_data_pos+end_dc] + delay_sums.append(sum(dc_delays)) + return delay_sums + + def get_variance(self, nums): + avg = self.get_average(nums) + delay_variance = sum((xi - avg) ** 2 for xi in nums) / len(nums) + return delay_variance + + def get_average(self,nums): + return sum(nums) / len(nums) + + def plot_data(self, x_labels, y_values): + """Display a plot using matplot lib. + Assumes input x values are just labels and y values are actual data.""" + data_range = [i+1 for i in range(len(x_labels))] + plt.xticks(data_range, x_labels) + plt.plot(data_range, y_values, 'ro') + plt.show() + + def plot_two_data_sets(self, x_labels, y1_values, y2_values): + """Plots two data sets on the same x-axis.""" + data_range = [i for i in range(len(x_labels))] + fig, ax1 = plt.subplots() + + color = 'tab:red' + ax1.set_xlabel('DC (Stages,Fanout)') + ax1.set_ylabel('Average Delay (ns)', color=color) + ax1.plot(data_range, y1_values, marker='o', color=color, linestyle='') + ax1.tick_params(axis='y', labelcolor=color) + + ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis + + color = 'tab:blue' + #ax2.set_xticks(data_range, x_labels) + ax2.set_ylabel('Delay Variance (ns)', color=color) # we already handled the x-label with ax1 + ax2.plot(data_range, y2_values, marker='*', color=color, linestyle='') + ax2.tick_params(axis='y', labelcolor=color) + + fig.tight_layout() # otherwise the right y-label is slightly clipped + plt.xticks(data_range, x_labels) + plt.show() + + def calculate_delay_ratios_of_srams(self): + """Runs delay measurements on several sram configurations. + Computes the delay ratio for each one.""" + delay_ratio_data = {} + config_tuple_list = [(32, 1024, None)] + #config_tuple_list = [(1, 16, 1),(4, 16, 1), (16, 16, 1), (32, 32, 1)] + for sram_config in config_tuple_list: + word_size, num_words, words_per_row = sram_config + self.init_data_gen() + self.save_data_sram_corners(word_size, num_words, words_per_row) + model_delay_ratios, meas_delay_ratios, ratio_error = self.compare_model_to_measure() + delay_ratio_data[sram_config] = ratio_error + debug.info(1, "Ratio percentage error={}".format(ratio_error)) + return delay_ratio_data + + def get_csv_data(self): + """Hardcoded Hack to get the measurement data from the csv into lists. """ + wl_files_name = [file_name for file_name in self.file_names if "wl_measures" in file_name][0] + sae_files_name = [file_name for file_name in self.file_names if "sae_measures" in file_name][0] + wl_dataframe = pd.read_csv(wl_files_name,encoding='utf-8') + sae_dataframe = pd.read_csv(sae_files_name,encoding='utf-8') + return wl_dataframe,sae_dataframe + + def evaluate_data(self, wl_dataframe, sae_dataframe): + """Analyze the delay error and variation error""" + delay_error = self.calculate_delay_error(wl_dataframe, sae_dataframe) + debug.info(1, "Delay errors:{}".format(delay_error)) + variation_error = self.calculate_delay_variation_error(wl_dataframe, sae_dataframe) + debug.info(1, "Variation errors:{}".format(variation_error)) + + def compare_model_to_measure(self): + """Uses the last 4 recent data sets (wl_meas, sen_meas, wl_model, sen_model) + and compare the wl-sen delay ratio between model and measured. + """ + model_delay_ratios = {} + meas_delay_ratios = {} + ratio_error = {} + #The full file name contains unrelated portions, separate them into the four that are needed + wl_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_measures" in file_name][0] + sae_meas_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_measures" in file_name][0] + wl_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "wl_model" in file_name][0] + sae_model_df = [pd.read_csv(file_name,encoding='utf-8') for file_name in self.file_names if "sae_model" in file_name][0] + + #Assume each csv has the same corners (and the same row order), use one of the dfs for corners + proc_pos, volt_pos, temp_pos = wl_meas_df.columns.get_loc('process'), wl_meas_df.columns.get_loc('voltage'), wl_meas_df.columns.get_loc('temp') + wl_sum_pos = wl_meas_df.columns.get_loc('sum') + sae_sum_pos = sae_meas_df.columns.get_loc('sum') + + df_zip = zip(wl_meas_df.itertuples(),sae_meas_df.itertuples(),wl_model_df.itertuples(),sae_model_df.itertuples()) + for wl_meas,sae_meas,wl_model,sae_model in df_zip: + #Use previously calculated position to index the df row. + corner = (wl_meas[proc_pos+1], wl_meas[volt_pos+1], wl_meas[temp_pos+1]) + meas_delay_ratios[corner] = wl_meas[wl_sum_pos+1]/sae_meas[sae_sum_pos+1] + model_delay_ratios[corner] = wl_model[wl_sum_pos+1]/sae_model[sae_sum_pos+1] + #Not using absolute error, positive error means model was larger, negative error means it was smaller. + ratio_error[corner] = 100*(model_delay_ratios[corner]-meas_delay_ratios[corner])/meas_delay_ratios[corner] + + return model_delay_ratios, meas_delay_ratios, ratio_error + + def display_data(self, data): + """Displays the ratio data using matplotlib (requires graphics)""" + config_data = [] + xticks = [] + #Organize data + #First key level if the sram configuration (wordsize, num words, words per row) + for config,corner_data_dict in data.items(): + #Second level is the corner data for that configuration. + for corner, corner_data in corner_data_dict.items(): + #Right now I am only testing with a single corner, will not work with more than 1 corner + config_data.append(corner_data) + xticks.append("{}b,{}w,{}wpr".format(*config)) + #plot data + data_range = [i+1 for i in range(len(data))] + shapes = ['ro', 'bo', 'go', 'co', 'mo'] + plt.xticks(data_range, xticks) + plt.plot(data_range, config_data, 'ro') + plt.show() + + def calculate_delay_error(self, wl_dataframe, sae_dataframe): + """Calculates the percentage difference in delays between the wordline and sense amp enable""" + start_data_pos = len(self.config_fields) #items before this point are configuration related + error_list = [] + row_count = 0 + for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()): + debug.info(2, "wl_row:{}".format(wl_row)) + wl_sum = sum(wl_row[start_data_pos+1:]) + debug.info(2, "wl_sum:{}".format(wl_sum)) + sae_sum = sum(sae_row[start_data_pos+1:]) + error_list.append(abs((wl_sum-sae_sum)/wl_sum)) + return error_list + + def calculate_delay_variation_error(self, wl_dataframe, sae_dataframe): + """Measures a base delay from the first corner then the variations from that base""" + start_data_pos = len(self.config_fields) + variation_error_list = [] + count = 0 + for wl_row, sae_row in zip(wl_dataframe.itertuples(), sae_dataframe.itertuples()): + if count == 0: + #Create a base delay, variation is defined as the difference between this base + wl_base = sum(wl_row[start_data_pos+1:]) + debug.info(1, "wl_sum base:{}".format(wl_base)) + sae_base = sum(sae_row[start_data_pos+1:]) + variation_error_list.append(0.0) + else: + #Calculate the variation from the respective base and then difference between the variations + wl_sum = sum(wl_row[start_data_pos+1:]) + wl_base_diff = abs((wl_base-wl_sum)/wl_base) + sae_sum = sum(sae_row[start_data_pos+1:]) + sae_base_diff = abs((sae_base-sae_sum)/sae_base) + variation_diff = abs((wl_base_diff-sae_base_diff)/wl_base_diff) + variation_error_list.append(variation_diff) + count+=1 + return variation_error_list + + def save_data_sram_corners(self, word_size, num_words, words_per_row): + """Performs corner analysis on a single SRAM configuration""" + self.create_sram(word_size, num_words, words_per_row) + #Setting to none forces SRAM to determine the value. Must be checked after sram creation + if not words_per_row: + words_per_row = self.sram.s.words_per_row + #Run on one size to initialize CSV writing (csv names come from return value). Strange, but it is okay for now. + corner_gen = self.corner_combination_generator() + init_corner = next(corner_gen) + sram_data = self.get_sram_data(init_corner) + dc_resized = self.was_delay_chain_resized() + self.initialize_csv_file(word_size, num_words, words_per_row) + self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, init_corner) + + #Run openRAM for all corners + for corner in corner_gen: + sram_data = self.get_sram_data(corner) + self.add_sram_data_to_csv(sram_data, word_size, num_words, words_per_row, dc_resized, corner) + + self.close_files() + debug.info(1,"Data Generated") + + def init_data_gen(self): + """Initialization for the data test to run""" + globals.init_openram("config_data") + from tech import parameter + global parameter + if OPTS.tech_name == "scmos": + debug.warning("Device models not up to date with scn4m technology.") + OPTS.spice_name="hspice" #Much faster than ngspice. + OPTS.trim_netlist = False + OPTS.netlist_only = True + OPTS.analytical_delay = False + #OPTS.use_tech_delay_chain_size = True + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + + def set_delay_chain(self, stages, fanout): + """Force change the parameter in the tech file to specify a delay chain configuration""" + parameter["static_delay_stages"] = stages + parameter["static_fanout_per_stage"] = fanout + + def close_files(self): + """Closes all files stored in the file dict""" + for key,file in self.csv_files.items(): + file.close() + + def corner_combination_generator(self): + processes = OPTS.process_corners + voltages = OPTS.supply_voltages + temperatures = OPTS.temperatures + """Generates corner using a combination of values from config file""" + for proc in processes: + for volt in voltages: + for temp in temperatures: + yield (proc, volt, temp) + + + def get_sram_configs(self): + """Generate lists of wordsizes, number of words, and column mux size (words per row) to be tested.""" + min_word_size = 1 + max_word_size = 16 + min_num_words_log2 = 4 + max_num_words_log2 = 8 + word_sizes = [i for i in range(min_word_size,max_word_size+1)] + num_words = [2**i for i in range(min_num_words_log2,max_num_words_log2+1)] + words_per_row = [1] + return word_sizes, num_words, words_per_row + + def add_sram_data_to_csv(self, sram_data, word_size, num_words, words_per_row, dc_resized, corner): + """Writes data to its respective CSV file. There is a CSV for each measurement target + (wordline, sense amp enable, and models)""" + sram_specs = [word_size,num_words,words_per_row,dc_resized,*corner] + for data_name, data_values in sram_data.items(): + other_values = self.calculate_other_data_values(data_values) + self.csv_writers[data_name].writerow(sram_specs+sram_data[data_name]+other_values) + debug.info(2,"Data Added to CSV file.") + + def calculate_other_data_values(self, sram_data_list): + """A function to calculate extra values related to the data. Only does the sum for now""" + data_sum = sum(sram_data_list) + return [data_sum] + + def initialize_csv_file(self, word_size, num_words, words_per_row): + """Opens a CSV file and writer for every data set being written (wl/sae measurements and model values)""" + #CSV File writing + header_dict = self.delay_obj.get_all_signal_names() + self.csv_files = {} + self.csv_writers = {} + self.file_names = [] + delay_stages = self.delay_obj.get_num_delay_stages() + delay_stage_fanout = self.delay_obj.get_num_delay_stage_fanout() + + for data_name, header_list in header_dict.items(): + file_name = '{}data_{}b_{}word_{}way_dc{}x{}_{}.csv'.format(MODEL_DIR, + word_size, + num_words, + words_per_row, + delay_stages, + delay_stage_fanout, + data_name) + self.file_names.append(file_name) + self.csv_files[data_name] = open(file_name, 'w') + self.config_fields = ['word_size', 'num_words', 'words_per_row', 'dc_resized', 'process', 'voltage', 'temp'] + self.other_data_fields = ['sum'] + fields = (*self.config_fields, *header_list, *self.other_data_fields) + self.csv_writers[data_name] = csv.writer(self.csv_files[data_name], lineterminator = '\n') + self.csv_writers[data_name].writerow(fields) + + def create_sram(self, word_size, num_words, words_per_row): + """Generates the SRAM based on input configuration.""" + c = sram_config(word_size=word_size, + num_words=num_words, + num_banks=1, + words_per_row=words_per_row) + + debug.info(1, "Creating SRAM: {} bit, {} words, with 1 bank".format(word_size, num_words)) + self.sram = sram(c, name="sram_{}ws_{}words".format(word_size, num_words)) + + self.sram_spice = OPTS.openram_temp + "temp.sp" + self.sram.sp_write(self.sram_spice) + + def get_sram_data(self, corner): + """Generates the delay object using the corner and runs a simulation for data.""" + from characterizer import model_check + self.delay_obj = model_check(self.sram.s, self.sram_spice, corner) + + import tech + #Only 1 at a time + probe_address = "1" * self.sram.s.addr_size + probe_data = self.sram.s.word_size - 1 + loads = [tech.spice["msflop_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + + sram_data = self.delay_obj.analyze(probe_address,probe_data,slews,loads) + + return sram_data + + def remove_lists_from_dict(self, dict): + """Check all the values in the dict and replaces the list items with its first value.""" + #This is useful because the tests performed here only generate 1 value but a list + #with 1 item makes writing it to a csv later harder. + for key in dict.keys(): + if type(dict[key]) is list: + if len(dict[key]) > 0: + dict[key] = dict[key][0] + else: + del dict[key] + + def was_delay_chain_resized(self): + """Accesses the dc resize boolean in the control logic module.""" + #FIXME:assumes read/write port only + return self.sram.s.control_logic_rw.delay_chain_resized + +# 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/tests/testutils.py b/compiler/tests/testutils.py index 6b2a7dcf..4340bce6 100755 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -21,7 +21,8 @@ class openram_test(unittest.TestCase): if result != 0: self.fail("DRC failed: {}".format(w.name)) - self.cleanup() + if OPTS.purge_temp: + self.cleanup() def local_check(self, a, final_verification=False): @@ -62,6 +63,9 @@ class openram_test(unittest.TestCase): delay_obj.set_load_slew(load, slew) delay_obj.set_probe(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. + delay_obj.create_signal_names() + delay_obj.create_measurement_names() + delay_obj.create_measurement_objects() delay_obj.find_feasible_period_one_port(test_port) return delay_obj.period diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 14ce1953..a0ba8cbc 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -271,7 +271,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE "FF" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"], "SF" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"], "FS" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"], - "SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"]} + "SS" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"], + "ST" : [SPICE_MODEL_DIR+"/models_ss/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"], + "TS" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ss/NMOS_VTG.inc"], + "FT" : [SPICE_MODEL_DIR+"/models_ff/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_nom/NMOS_VTG.inc"], + "TF" : [SPICE_MODEL_DIR+"/models_nom/PMOS_VTG.inc",SPICE_MODEL_DIR+"/models_ff/NMOS_VTG.inc"], + } #spice stimulus related variables spice["feasible_period"] = 5 # estimated feasible period in ns @@ -328,12 +333,15 @@ spice["nand2_transition_prob"] = .1875 # Transition probability of 2-input na spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input nand. spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. -#Logical Effort relative values for the Handmade cells +#Parameters related to sense amp enable timing and delay chain/RBL sizing +parameter["static_delay_stages"] = 4 +parameter["static_fanout_per_stage"] = 3 parameter["dff_clk_cin"] = 30.6 #relative capacitance parameter["6tcell_wl_cin"] = 3 #relative capacitance -parameter["min_inv_para_delay"] = .5 #Tau delay units +parameter["min_inv_para_delay"] = 2.4 #Tau delay units parameter["sa_en_pmos_size"] = .72 #micro-meters parameter["sa_en_nmos_size"] = .27 #micro-meters +parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array ################################################### ##END Spice Simulation Parameters diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 3b7e2142..1add9937 100755 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -236,7 +236,12 @@ spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+" "FF" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"], "FS" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"], "SF" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"], - "SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"] } + "SS" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"], + "ST" : [SPICE_MODEL_DIR+"/ss/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"], + "TS" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ss/nmos.sp"], + "FT" : [SPICE_MODEL_DIR+"/ff/pmos.sp",SPICE_MODEL_DIR+"/nom/nmos.sp"], + "TF" : [SPICE_MODEL_DIR+"/nom/pmos.sp",SPICE_MODEL_DIR+"/ff/nmos.sp"], + } #spice stimulus related variables @@ -295,11 +300,14 @@ spice["nand3_transition_prob"] = .1094 # Transition probability of 3-input na spice["nor2_transition_prob"] = .1875 # Transition probability of 2-input nor. #Logical Effort relative values for the Handmade cells +parameter["static_delay_stages"] = 4 +parameter["static_fanout_per_stage"] = 3 parameter["dff_clk_cin"] = 27.5 parameter["6tcell_wl_cin"] = 2 parameter["min_inv_para_delay"] = .5 parameter["sa_en_pmos_size"] = 24*_lambda_ parameter["sa_en_nmos_size"] = 9*_lambda_ +parameter["rbl_height_percentage"] = .5 #Height of RBL compared to bitcell array ################################################### ##END Spice Simulation Parameters