diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 854eeb75..ef3c6ce9 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -14,16 +14,21 @@ class delay(): data bit. """ - def __init__(self,sram,spfile): + def __init__(self,sram,spfile, corner): self.name = sram.name self.num_words = sram.num_words self.word_size = sram.word_size self.addr_size = sram.addr_size self.sram_sp_file = spfile - self.vdd = tech.spice["supply_voltage"] - self.gnd = tech.spice["gnd_voltage"] - + self.set_corner(corner) + + def set_corner(self,corner): + """ Set the corner values """ + self.corner = corner + (self.process, self.vdd_voltage, self.temperature) = corner + self.gnd_voltage = 0 + def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" @@ -53,22 +58,20 @@ class delay(): temp_stim = "{0}/stim.sp".format(OPTS.openram_temp) self.sf = open(temp_stim, "w") self.sf.write("* Stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(period,load,slew)) - + self.stim = stimuli.stimuli(self.sf, self.corner) # include files in stimulus file - model_list = tech.spice["fet_models"] + [self.sram_sp_file] - stimuli.write_include(stim_file=self.sf, models=model_list) + self.stim.write_include(self.sram_sp_file) # add vdd/gnd statements self.sf.write("\n* Global Power Supplies\n") - stimuli.write_supply(self.sf) + self.stim.write_supply() # instantiate the sram self.sf.write("\n* Instantiation of the SRAM\n") - stimuli.inst_sram(stim_file=self.sf, - abits=self.addr_size, - dbits=self.word_size, - sram_name=self.name) + self.stim.inst_sram(abits=self.addr_size, + dbits=self.word_size, + sram_name=self.name) self.sf.write("\n* SRAM output loads\n") for i in range(self.word_size): @@ -76,7 +79,7 @@ class delay(): # add access transistors for data-bus self.sf.write("\n* Transmission Gates for data-bus and control signals\n") - stimuli.inst_accesstx(stim_file=self.sf, dbits=self.word_size) + self.stim.inst_accesstx(dbits=self.word_size) # generate data and addr signals self.sf.write("\n* Generation of data and address signals\n") @@ -87,14 +90,13 @@ class delay(): period=period, slew=slew) else: - stimuli.gen_constant(stim_file=self.sf, - sig_name="d[{0}]".format(i), - v_val=self.gnd) + self.stim.gen_constant(sig_name="d[{0}]".format(i), + v_val=self.gnd_voltage) self.gen_addr(clk_times=self.cycle_times, - addr=self.probe_address, - period=period, - slew=slew) + addr=self.probe_address, + period=period, + slew=slew) # generate control signals self.sf.write("\n* Generation of control signals\n") @@ -103,19 +105,18 @@ class delay(): self.gen_oeb(self.cycle_times, period, slew) self.sf.write("\n* Generation of global clock signal\n") - stimuli.gen_pulse(stim_file=self.sf, - sig_name="CLK", - v1=self.gnd, - v2=self.vdd, - offset=period, - period=period, - t_rise=slew, - t_fall=slew) + self.stim.gen_pulse(sig_name="CLK", + v1=self.gnd_voltage, + v2=self.vdd_voltage, + offset=period, + period=period, + t_rise=slew, + t_fall=slew) self.write_measures(period) # run until the end of the cycle time - stimuli.write_control(self.sf,self.cycle_times[-1] + period) + self.stim.write_control(self.cycle_times[-1] + period) self.sf.close() @@ -134,81 +135,73 @@ class delay(): # Trigger on the clk of the appropriate cycle trig_name = "clk" targ_name = "{0}".format("d[{0}]".format(self.probe_data)) - trig_val = targ_val = 0.5 * self.vdd + trig_val = targ_val = 0.5 * self.vdd_voltage # Delay the target to measure after the negative edge - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="DELAY0", - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir="FALL", - targ_dir="FALL", - trig_td=self.cycle_times[self.read0_cycle], - targ_td=self.cycle_times[self.read0_cycle]+0.5*period) + self.stim.gen_meas_delay(meas_name="DELAY0", + trig_name=trig_name, + targ_name=targ_name, + trig_val=trig_val, + targ_val=targ_val, + trig_dir="FALL", + targ_dir="FALL", + trig_td=self.cycle_times[self.read0_cycle], + targ_td=self.cycle_times[self.read0_cycle]+0.5*period) - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="DELAY1", - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir="FALL", - targ_dir="RISE", - trig_td=self.cycle_times[self.read1_cycle], - targ_td=self.cycle_times[self.read1_cycle]+0.5*period) + self.stim.gen_meas_delay(meas_name="DELAY1", + trig_name=trig_name, + targ_name=targ_name, + trig_val=trig_val, + targ_val=targ_val, + trig_dir="FALL", + targ_dir="RISE", + trig_td=self.cycle_times[self.read1_cycle], + targ_td=self.cycle_times[self.read1_cycle]+0.5*period) - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="SLEW0", - trig_name=targ_name, - targ_name=targ_name, - trig_val=0.9*self.vdd, - targ_val=0.1*self.vdd, - trig_dir="FALL", - targ_dir="FALL", - trig_td=self.cycle_times[self.read0_cycle], - targ_td=self.cycle_times[self.read0_cycle]+0.5*period) + self.stim.gen_meas_delay(meas_name="SLEW0", + trig_name=targ_name, + targ_name=targ_name, + trig_val=0.9*self.vdd_voltage, + targ_val=0.1*self.vdd_voltage, + trig_dir="FALL", + targ_dir="FALL", + trig_td=self.cycle_times[self.read0_cycle], + targ_td=self.cycle_times[self.read0_cycle]+0.5*period) - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="SLEW1", - trig_name=targ_name, - targ_name=targ_name, - trig_val=0.1*self.vdd, - targ_val=0.9*self.vdd, - trig_dir="RISE", - targ_dir="RISE", - trig_td=self.cycle_times[self.read1_cycle], - targ_td=self.cycle_times[self.read1_cycle]+0.5*period) + self.stim.gen_meas_delay(meas_name="SLEW1", + trig_name=targ_name, + targ_name=targ_name, + trig_val=0.1*self.vdd_voltage, + targ_val=0.9*self.vdd_voltage, + trig_dir="RISE", + targ_dir="RISE", + trig_td=self.cycle_times[self.read1_cycle], + targ_td=self.cycle_times[self.read1_cycle]+0.5*period) # add measure statements for power t_initial = self.cycle_times[self.write0_cycle] t_final = self.cycle_times[self.write0_cycle+1] - stimuli.gen_meas_power(stim_file=self.sf, - meas_name="WRITE0_POWER", - t_initial=t_initial, - t_final=t_final) + self.stim.gen_meas_power(meas_name="WRITE0_POWER", + t_initial=t_initial, + t_final=t_final) t_initial = self.cycle_times[self.write1_cycle] t_final = self.cycle_times[self.write1_cycle+1] - stimuli.gen_meas_power(stim_file=self.sf, - meas_name="WRITE1_POWER", - t_initial=t_initial, - t_final=t_final) + self.stim.gen_meas_power(meas_name="WRITE1_POWER", + t_initial=t_initial, + t_final=t_final) t_initial = self.cycle_times[self.read0_cycle] t_final = self.cycle_times[self.read0_cycle+1] - stimuli.gen_meas_power(stim_file=self.sf, - meas_name="READ0_POWER", - t_initial=t_initial, - t_final=t_final) + self.stim.gen_meas_power(meas_name="READ0_POWER", + t_initial=t_initial, + t_final=t_final) t_initial = self.cycle_times[self.read1_cycle] t_final = self.cycle_times[self.read1_cycle+1] - stimuli.gen_meas_power(stim_file=self.sf, - meas_name="READ1_POWER", - t_initial=t_initial, - t_final=t_final) + self.stim.gen_meas_power(meas_name="READ1_POWER", + t_initial=t_initial, + t_final=t_final) def find_feasible_period(self, load, slew): """ @@ -249,7 +242,7 @@ class delay(): # Checking from not data_value to data_value self.write_stimulus(period, load, slew) - stimuli.run_sim() + self.stim.run_sim() delay0 = ch.convert_to_float(ch.parse_output("timing", "delay0")) delay1 = ch.convert_to_float(ch.parse_output("timing", "delay1")) slew0 = ch.convert_to_float(ch.parse_output("timing", "slew0")) @@ -334,7 +327,7 @@ class delay(): # Checking from not data_value to data_value self.write_stimulus(period,load,slew) - stimuli.run_sim() + self.stim.run_sim() delay0 = ch.convert_to_float(ch.parse_output("timing", "delay0")) delay1 = ch.convert_to_float(ch.parse_output("timing", "delay1")) slew0 = ch.convert_to_float(ch.parse_output("timing", "slew0")) @@ -575,7 +568,7 @@ class delay(): # we are asserting the opposite value on the other side of the tx gate during # the read to be "worst case". Otherwise, it can actually assist the read. values = [0, 1, 0, 1, 1, 1, 1, 0, 0, 0 ] - stimuli.gen_pwl(self.sf, sig_name, clk_times, values, period, slew, 0.05) + self.stim.gen_pwl(sig_name, clk_times, values, period, slew, 0.05) def gen_addr(self, clk_times, addr, period, slew): """ @@ -589,9 +582,9 @@ class delay(): for i in range(len(addr)): sig_name = "A[{0}]".format(i) if addr[i]=="1": - stimuli.gen_pwl(self.sf, sig_name, clk_times, ones_values, period, slew, 0.05) + self.stim.gen_pwl(sig_name, clk_times, ones_values, period, slew, 0.05) else: - stimuli.gen_pwl(self.sf, sig_name, clk_times, zero_values, period, slew, 0.05) + self.stim.gen_pwl(sig_name, clk_times, zero_values, period, slew, 0.05) def gen_csb(self, clk_times, period, slew): @@ -599,24 +592,24 @@ class delay(): # values for NOP, W1, W0, W1, R0, NOP, W1, W0, R1, NOP # Keep CSb asserted in NOP for measuring >1 period values = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] - stimuli.gen_pwl(self.sf, "csb", clk_times, values, period, slew, 0.05) + self.stim.gen_pwl("csb", clk_times, values, period, slew, 0.05) def gen_web(self, clk_times, period, slew): """ Generates the PWL WEb signal """ # values for NOP, W1, W0, W1, R0, NOP, W1, W0, R1, NOP # Keep WEb deasserted in NOP for measuring >1 period values = [1, 0, 0, 0, 1, 1, 0, 0, 1, 1] - stimuli.gen_pwl(self.sf, "web", clk_times, values, period, slew, 0.05) + self.stim.gen_pwl("web", clk_times, values, period, slew, 0.05) # Keep acc_en deasserted in NOP for measuring >1 period values = [1, 0, 0, 0, 1, 1, 0, 0, 1, 1] - stimuli.gen_pwl(self.sf, "acc_en", clk_times, values, period, slew, 0) + self.stim.gen_pwl("acc_en", clk_times, values, period, slew, 0) values = [0, 1, 1, 1, 0, 0, 1, 1, 0, 0] - stimuli.gen_pwl(self.sf, "acc_en_inv", clk_times, values, period, slew, 0) + self.stim.gen_pwl("acc_en_inv", clk_times, values, period, slew, 0) def gen_oeb(self, clk_times, period, slew): """ Generates the PWL WEb signal """ # values for NOP, W1, W0, W1, R0, W1, W0, R1, NOP # Keep OEb asserted in NOP for measuring >1 period values = [1, 1, 1, 1, 0, 0, 1, 1, 0, 0] - stimuli.gen_pwl(self.sf, "oeb", clk_times, values, period, slew, 0.05) + self.stim.gen_pwl("oeb", clk_times, values, period, slew, 0.05) diff --git a/compiler/characterizer/lib.py b/compiler/characterizer/lib.py index 7bab820b..61b13004 100644 --- a/compiler/characterizer/lib.py +++ b/compiler/characterizer/lib.py @@ -12,15 +12,23 @@ from globals import OPTS class lib: """ lib file generation.""" - def __init__(self, libname, sram, sp_file, use_model=OPTS.analytical_delay): + def __init__(self, out_dir, sram, sp_file, use_model=OPTS.analytical_delay): + self.out_dir = out_dir self.sram = sram self.sp_file = sp_file self.use_model = use_model - self.name = sram.name - self.num_words = sram.num_words - self.word_size = sram.word_size - self.addr_size = sram.addr_size + self.prepare_netlist() + + self.prepare_tables() + + self.create_corners() + + self.characterize_corners() + + def prepare_netlist(self): + """ Determine whether to use regular or trimmed netlist. """ + # Set up to trim the netlist here if that is enabled if OPTS.trim_netlist: self.sim_sp_file = "{}reduced.sp".format(OPTS.openram_temp) @@ -34,12 +42,14 @@ class lib: self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp) # Make a copy in temp for debugging shutil.copy(self.sp_file, self.sim_sp_file) - + + def prepare_tables(self): + """ Determine the load/slews if they aren't specified in the config file. """ # These are the parameters to determine the table sizes #self.load_scales = np.array([0.1, 0.25, 0.5, 1, 2, 4, 8]) self.load_scales = np.array([0.25, 1, 8]) #self.load_scales = np.array([0.25, 1]) - self.load = tech.spice["FF_in_cap"] + self.load = tech.spice["msflop_in_cap"] self.loads = self.load_scales*self.load debug.info(1,"Loads: {0}".format(self.loads)) @@ -49,9 +59,43 @@ class lib: self.slew = tech.spice["rise_time"] self.slews = self.slew_scales*self.slew debug.info(1,"Slews: {0}".format(self.slews)) - - debug.info(1,"Writing to {0}".format(libname)) - self.lib = open(libname, "w") + + + def create_corners(self): + """ Create corners for characterization. """ + # Get the corners from the options file + self.temperatures = OPTS.temperatures + self.supply_voltages = OPTS.supply_voltages + self.process_corners = OPTS.process_corners + + # Enumerate all possible corners + self.corners = [] + self.lib_files = [] + for proc in self.process_corners: + for temp in self.temperatures: + for volt in self.supply_voltages: + self.corner_name = "{0}_{1}_{2}_{3}".format(self.sram.name, + proc, + volt, + temp) + self.corner_name = self.corner_name.replace(".","") # Remove decimals + lib_name = self.out_dir+"{}.lib".format(self.corner_name) + + # A corner is a tuple of PVT + self.corners.append((proc, volt, temp)) + self.lib_files.append(lib_name) + + def characterize_corners(self): + """ Characterize the list of corners. """ + for (self.corner,lib_name) in zip(self.corners,self.lib_files): + debug.info(1,"Corner: " + str(self.corner)) + (self.process, self.voltage, self.temperature) = self.corner + self.lib = open(lib_name, "w") + debug.info(1,"Writing to {0}".format(lib_name)) + self.characterize() + + def characterize(self): + """ Characterize the current corner. """ self.write_header() @@ -67,7 +111,7 @@ class lib: def write_header(self): """ Write the header information """ - self.lib.write("library ({0}_lib)".format(self.name)) + self.lib.write("library ({0}_lib)".format(self.corner_name)) self.lib.write("{\n") self.lib.write(" delay_model : \"table_lookup\";\n") @@ -79,12 +123,12 @@ class lib: self.write_bus() - self.lib.write("cell ({0})".format(self.name)) + self.lib.write("cell ({0})".format(self.sram.name)) self.lib.write("{\n") self.lib.write(" memory(){ \n") self.lib.write(" type : ram;\n") - self.lib.write(" address_width : {0};\n".format(self.addr_size)) - self.lib.write(" word_width : {0};\n".format(self.word_size)) + self.lib.write(" address_width : {0};\n".format(self.sram.addr_size)) + self.lib.write(" word_width : {0};\n".format(self.sram.word_size)) self.lib.write(" }\n") self.lib.write(" interface_timing : true;\n") self.lib.write(" dont_use : true;\n") @@ -103,9 +147,9 @@ class lib: self.lib.write(" capacitive_load_unit(1 ,fF) ;\n") self.lib.write(" leakage_power_unit : \"1mW\" ;\n") self.lib.write(" pulling_resistance_unit :\"1kohm\" ;\n") - self.lib.write(" operating_conditions(TT){\n") - self.lib.write(" voltage : {0} ;\n".format(tech.spice["supply_voltage"])) - self.lib.write(" temperature : 25.000 ;\n") + self.lib.write(" operating_conditions({}){{\n".format(self.process)) + self.lib.write(" voltage : {} ;\n".format(self.voltage)) + self.lib.write(" temperature : {};\n".format(self.temperature)) self.lib.write(" }\n\n") def write_defaults(self): @@ -206,17 +250,17 @@ class lib: self.lib.write(" type (DATA){\n") self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") - self.lib.write(" bit_width : {0};\n".format(self.word_size)) + self.lib.write(" bit_width : {0};\n".format(self.sram.word_size)) self.lib.write(" bit_from : 0;\n") - self.lib.write(" bit_to : {0};\n".format(self.word_size - 1)) + self.lib.write(" bit_to : {0};\n".format(self.sram.word_size - 1)) self.lib.write(" }\n\n") self.lib.write(" type (ADDR){\n") self.lib.write(" base_type : array;\n") self.lib.write(" data_type : bit;\n") - self.lib.write(" bit_width : {0};\n".format(self.addr_size)) + self.lib.write(" bit_width : {0};\n".format(self.sram.addr_size)) self.lib.write(" bit_from : 0;\n") - self.lib.write(" bit_to : {0};\n".format(self.addr_size - 1)) + self.lib.write(" bit_to : {0};\n".format(self.sram.addr_size - 1)) self.lib.write(" }\n\n") @@ -260,7 +304,7 @@ class lib: self.lib.write(" bus(DATA){\n") self.lib.write(" bus_type : DATA; \n") self.lib.write(" direction : inout; \n") - self.lib.write(" max_capacitance : {0}; \n".format(8*tech.spice["FF_in_cap"])) + self.lib.write(" max_capacitance : {0}; \n".format(8*tech.spice["msflop_in_cap"])) self.lib.write(" three_state : \"!OEb & !clk\"; \n") self.lib.write(" memory_write(){ \n") self.lib.write(" address : ADDR; \n") @@ -269,7 +313,7 @@ class lib: self.lib.write(" memory_read(){ \n") self.lib.write(" address : ADDR; \n") self.lib.write(" }\n") - self.lib.write(" pin(DATA[{0}:0])".format(self.word_size - 1)) + self.lib.write(" pin(DATA[{0}:0])".format(self.sram.word_size - 1)) self.lib.write("{\n") self.lib.write(" internal_power(){\n") @@ -324,10 +368,10 @@ class lib: self.lib.write(" bus(ADDR){\n") self.lib.write(" bus_type : ADDR; \n") self.lib.write(" direction : input; \n") - self.lib.write(" capacitance : {0}; \n".format(tech.spice["FF_in_cap"])) + self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"])) self.lib.write(" max_transition : {0};\n".format(self.slews[-1])) self.lib.write(" fanout_load : 1.000000;\n") - self.lib.write(" pin(ADDR[{0}:0])".format(self.addr_size - 1)) + self.lib.write(" pin(ADDR[{0}:0])".format(self.sram.addr_size - 1)) self.lib.write("{\n") self.write_FF_setuphold() @@ -343,7 +387,7 @@ class lib: self.lib.write(" pin({0})".format(i)) self.lib.write("{\n") self.lib.write(" direction : input; \n") - self.lib.write(" capacitance : {0}; \n".format(tech.spice["FF_in_cap"])) + self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"])) self.write_FF_setuphold() self.lib.write(" }\n\n") @@ -356,7 +400,7 @@ class lib: self.lib.write(" pin(clk){\n") self.lib.write(" clock : true;\n") self.lib.write(" direction : input; \n") - self.lib.write(" capacitance : {0}; \n".format(tech.spice["FF_in_cap"])) + self.lib.write(" capacitance : {0}; \n".format(tech.spice["msflop_in_cap"])) min_pulse_width = ch.round_time(self.delay["min_period"])/2.0 min_period = ch.round_time(self.delay["min_period"]) self.lib.write(" timing(){ \n") @@ -388,12 +432,12 @@ class lib: try: self.d except AttributeError: - self.d = delay.delay(self.sram, self.sim_sp_file) + self.d = delay.delay(self.sram, self.sim_sp_file, self.corner) if self.use_model: self.delay = self.d.analytical_model(self.sram,self.slews,self.loads) else: - probe_address = "1" * self.addr_size - probe_data = self.word_size - 1 + probe_address = "1" * self.sram.addr_size + probe_data = self.sram.word_size - 1 # We must trim based on a specific address and data bit if OPTS.trim_netlist: self.trimsp.trim(probe_address,probe_data) @@ -405,7 +449,7 @@ class lib: try: self.sh except AttributeError: - self.sh = setup_hold.setup_hold() + self.sh = setup_hold.setup_hold(self.corner) if self.use_model: self.times = self.sh.analytical_model(self.slews,self.loads) else: diff --git a/compiler/characterizer/setup_hold.py b/compiler/characterizer/setup_hold.py index f9c1161b..5a3bfdb4 100644 --- a/compiler/characterizer/setup_hold.py +++ b/compiler/characterizer/setup_hold.py @@ -13,18 +13,23 @@ class setup_hold(): (Bisection Methodology) """ - def __init__(self): + def __init__(self, corner): # This must match the spice model order self.pins = ["data", "dout", "dout_bar", "clk", "vdd", "gnd"] self.model_name = "ms_flop" self.model_location = OPTS.openram_tech + "sp_lib/ms_flop.sp" self.period = tech.spice["feasible_period"] - self.vdd = tech.spice["supply_voltage"] - self.gnd = tech.spice["gnd_voltage"] debug.info(2,"Feasible period from technology file: {0} ".format(self.period)) + self.set_corner(corner) + + def set_corner(self,corner): + """ Set the corner values """ + self.corner = corner + (self.process, self.vdd_voltage, self.temperature) = corner + self.gnd_voltage = 0 def write_stimulus(self, mode, target_time, correct_value): @@ -33,14 +38,14 @@ class setup_hold(): # creates and opens the stimulus file for writing temp_stim = OPTS.openram_temp + "stim.sp" self.sf = open(temp_stim, "w") + self.stim = stimuli.stimuli(self.sf, self.corner) self.write_header(correct_value) # instantiate the master-slave d-flip-flop self.sf.write("\n* Instantiation of the Master-Slave D-flip-flop\n") - stimuli.inst_model(stim_file=self.sf, - pins=self.pins, - model_name=self.model_name) + self.stim.inst_model(pins=self.pins, + model_name=self.model_name) self.write_data(mode=mode, target_time=target_time, @@ -52,7 +57,7 @@ class setup_hold(): correct_value=correct_value) - stimuli.write_control(self.sf,4*self.period) + self.stim.write_control(4*self.period) self.sf.close() @@ -61,13 +66,11 @@ class setup_hold(): self.sf.write("\n* Stimulus for setup/hold: data {0} period {1}n\n".format(correct_value, self.period)) # include files in stimulus file - self.model_list = tech.spice["fet_models"] + [self.model_location] - stimuli.write_include(stim_file=self.sf, - models=self.model_list) + self.stim.write_include(self.model_location) # add vdd/gnd statements self.sf.write("\n* Global Power Supplies\n") - stimuli.write_supply(self.sf) + self.stim.write_supply() def write_data(self, mode, target_time, correct_value): @@ -77,7 +80,7 @@ class setup_hold(): """ self.sf.write("\n* Generation of the data and clk signals\n") - incorrect_value = stimuli.get_inverse_value(correct_value) + incorrect_value = self.stim.get_inverse_value(correct_value) if mode=="HOLD": init_value = incorrect_value start_value = correct_value @@ -87,29 +90,27 @@ class setup_hold(): start_value = incorrect_value end_value = correct_value - stimuli.gen_pwl(stim_file=self.sf, - sig_name="data", - clk_times=[0, self.period, target_time], - data_values=[init_value, start_value, end_value], - period=target_time, - slew=self.constrained_input_slew, - setup=0) + self.stim.gen_pwl(sig_name="data", + clk_times=[0, self.period, target_time], + data_values=[init_value, start_value, end_value], + period=target_time, + slew=self.constrained_input_slew, + setup=0) def write_clock(self): """ Create the clock signal for setup/hold analysis. First period initializes the FF while the second is used for characterization.""" - stimuli.gen_pwl(stim_file=self.sf, - sig_name="clk", - # initial clk edge is right after the 0 time to initialize a flop - # without using .IC on an internal node. - # Return input to value after one period. - # The second pulse is the characterization one at 2*period - clk_times=[0, 0.1*self.period,self.period,2*self.period], - data_values=[0, 1, 0, 1], - period=2*self.period, - slew=self.constrained_input_slew, - setup=0) + self.stim.gen_pwl(sig_name="clk", + # initial clk edge is right after the 0 time to initialize a flop + # without using .IC on an internal node. + # Return input to value after one period. + # The second pulse is the characterization one at 2*period + clk_times=[0, 0.1*self.period,self.period,2*self.period], + data_values=[0, 1, 0, 1], + period=2*self.period, + slew=self.constrained_input_slew, + setup=0) @@ -135,32 +136,30 @@ class setup_hold(): self.sf.write("\n* Measure statements for pass/fail verification\n") trig_name = "clk" targ_name = "dout" - trig_val = targ_val = 0.5 * self.vdd + trig_val = targ_val = 0.5 * self.vdd_voltage # Start triggers right before the clock edge at 2*period - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="clk2q_delay", - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir="RISE", - targ_dir=dout_rise_or_fall, - trig_td=1.9*self.period, - targ_td=1.9*self.period) - + self.stim.gen_meas_delay(meas_name="clk2q_delay", + trig_name=trig_name, + targ_name=targ_name, + trig_val=trig_val, + targ_val=targ_val, + trig_dir="RISE", + targ_dir=dout_rise_or_fall, + trig_td=1.9*self.period, + targ_td=1.9*self.period) + targ_name = "data" # Start triggers right after initialize value is returned to normal # at one period - stimuli.gen_meas_delay(stim_file=self.sf, - meas_name="setup_hold_time", - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir="RISE", - targ_dir=din_rise_or_fall, - trig_td=1.2*self.period, - targ_td=1.2*self.period) + self.stim.gen_meas_delay(meas_name="setup_hold_time", + trig_name=trig_name, + targ_name=targ_name, + trig_val=trig_val, + targ_val=targ_val, + trig_dir="RISE", + targ_dir=din_rise_or_fall, + trig_td=1.2*self.period, + targ_td=1.2*self.period) @@ -186,7 +185,7 @@ class setup_hold(): self.write_stimulus(mode=mode, target_time=feasible_bound, correct_value=correct_value) - stimuli.run_sim() + self.stim.run_sim() ideal_clk_to_q = ch.convert_to_float(ch.parse_output("timing", "clk2q_delay")) setuphold_time = ch.convert_to_float(ch.parse_output("timing", "setup_hold_time")) debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time)) @@ -219,7 +218,7 @@ class setup_hold(): feasible_bound)) - stimuli.run_sim() + self.stim.run_sim() clk_to_q = ch.convert_to_float(ch.parse_output("timing", "clk2q_delay")) setuphold_time = ch.convert_to_float(ch.parse_output("timing", "setup_hold_time")) if type(clk_to_q)==float and (clk_to_q<1.1*ideal_clk_to_q) and type(setuphold_time)==float: diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index b82d6aa7..c253f32b 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -12,290 +12,302 @@ import sys import numpy as np from globals import OPTS -vdd_voltage = tech.spice["supply_voltage"] -gnd_voltage = tech.spice["gnd_voltage"] -vdd_name = tech.spice["vdd_name"] -gnd_name = tech.spice["gnd_name"] -pmos_name = tech.spice["pmos_name"] -nmos_name = tech.spice["nmos_name"] -tx_width = tech.spice["minwidth_tx"] -tx_length = tech.spice["channel"] -def inst_sram(stim_file, abits, dbits, sram_name): - """ Function to instatiate an SRAM subckt. """ - stim_file.write("Xsram ") - for i in range(dbits): - stim_file.write("D[{0}] ".format(i)) - for i in range(abits): - stim_file.write("A[{0}] ".format(i)) - for i in tech.spice["control_signals"]: - stim_file.write("{0} ".format(i)) - stim_file.write("{0} ".format(tech.spice["clk"])) - stim_file.write("{0} {1} ".format(vdd_name, gnd_name)) - stim_file.write("{0}\n".format(sram_name)) +class stimuli(): + """ Class for providing stimuli functions """ + def __init__(self, stim_file, corner): + self.vdd_name = tech.spice["vdd_name"] + self.gnd_name = tech.spice["gnd_name"] + self.pmos_name = tech.spice["pmos"] + self.nmos_name = tech.spice["nmos"] + self.tx_width = tech.spice["minwidth_tx"] + self.tx_length = tech.spice["channel"] -def inst_model(stim_file, pins, model_name): - """ Function to instantiate a generic model with a set of pins """ - stim_file.write("X{0} ".format(model_name)) - for pin in pins: - stim_file.write("{0} ".format(pin)) - stim_file.write("{0}\n".format(model_name)) - - -def create_inverter(stim_file, size=1, beta=2.5): - """ Generates inverter for the top level signals (only for sim purposes) """ - stim_file.write(".SUBCKT test_inv in out {0} {1}\n".format(vdd_name, gnd_name)) - stim_file.write("mpinv out in {0} {0} {1} w={2}u l={3}u\n".format(vdd_name, - pmos_name, - beta * size * tx_width, - tx_length)) - stim_file.write("mninv out in {0} {0} {1} w={2}u l={3}u\n".format(gnd_name, - nmos_name, - size * tx_width, - tx_length)) - stim_file.write(".ENDS test_inv\n") - - -def create_buffer(stim_file, buffer_name, size=[1,3], beta=2.5): - """ - Generates buffer for top level signals (only for sim - purposes). Size is pair for PMOS, NMOS width multiple. - """ - - stim_file.write(".SUBCKT test_{2} in out {0} {1}\n".format(vdd_name, - gnd_name, - buffer_name)) - stim_file.write("mpinv1 out_inv in {0} {0} {1} w={2}u l={3}u\n".format(vdd_name, - pmos_name, - beta * size[0] * tx_width, - tx_length)) - stim_file.write("mninv1 out_inv in {0} {0} {1} w={2}u l={3}u\n".format(gnd_name, - nmos_name, - size[0] * tx_width, - tx_length)) - stim_file.write("mpinv2 out out_inv {0} {0} {1} w={2}u l={3}u\n".format(vdd_name, - pmos_name, - beta * size[1] * tx_width, - tx_length)) - stim_file.write("mninv2 out out_inv {0} {0} {1} w={2}u l={3}u\n".format(gnd_name, - nmos_name, - size[1] * tx_width, - tx_length)) - stim_file.write(".ENDS test_{0}\n\n".format(buffer_name)) - - -def inst_buffer(stim_file, buffer_name, signal_list): - """ Adds buffers to each top level signal that is in signal_list (only for sim purposes) """ - for signal in signal_list: - stim_file.write("X{0}_buffer {0} {0}_buf {1} {2} test_{3}\n".format(signal, - "test"+vdd_name, - "test"+gnd_name, - buffer_name)) - - -def inst_inverter(stim_file, signal_list): - """ Adds inv for each signal that needs its inverted version (only for sim purposes) """ - for signal in signal_list: - stim_file.write("X{0}_inv {0} {0}_inv {1} {2} test_inv\n".format(signal, - "test"+vdd_name, - "test"+gnd_name)) - - -def inst_accesstx(stim_file, dbits): - """ Adds transmission gate for inputs to data-bus (only for sim purposes) """ - stim_file.write("* Tx Pin-list: Drain Gate Source Body\n") - for i in range(dbits): - pmos_access_string="mp{0} DATA[{0}] acc_en D[{0}] {1} {2} w={3}u l={4}u\n" - stim_file.write(pmos_access_string.format(i, - "test"+vdd_name, - pmos_name, - 2 * tx_width, - tx_length)) - nmos_access_string="mn{0} DATA[{0}] acc_en_inv D[{0}] {1} {2} w={3}u l={4}u\n" - stim_file.write(nmos_access_string.format(i, - "test"+gnd_name, - nmos_name, - 2 * tx_width, - tx_length)) - -def gen_pulse(stim_file, sig_name, v1=gnd_voltage, v2=vdd_voltage, offset=0, period=1, t_rise=0, t_fall=0): - """ - Generates a periodic signal with 50% duty cycle and slew rates. Period is measured - from 50% to 50%. - """ - stim_file.write("* PULSE: period={0}\n".format(period)) - pulse_string="V{0} {0} 0 PULSE ({1} {2} {3}n {4}n {5}n {6}n {7}n)\n" - stim_file.write(pulse_string.format(sig_name, - v1, - v2, - offset, - t_rise, - t_fall, - 0.5*period-0.5*t_rise-0.5*t_fall, - period)) - - -def gen_pwl(stim_file, sig_name, clk_times, data_values, period, slew, setup): - """ - Generate a PWL stimulus given a signal name and data values at each period. - Automatically creates slews and ensures each data occurs a setup before the clock - edge. The first clk_time should be 0 and is the initial time that corresponds - to the initial value. - """ - # the initial value is not a clock time - debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match.") - - # shift signal times earlier for setup time - times = np.array(clk_times) - setup*period - values = np.array(data_values) * vdd_voltage - half_slew = 0.5 * slew - stim_file.write("* (time, data): {}\n".format(zip(clk_times, data_values))) - stim_file.write("V{0} {0} 0 PWL (0n {1}v ".format(sig_name, values[0])) - for i in range(1,len(times)): - stim_file.write("{0}n {1}v {2}n {3}v ".format(times[i]-half_slew, - values[i-1], - times[i]+half_slew, - values[i])) - stim_file.write(")\n") - -def gen_constant(stim_file, sig_name, v_val): - """ Generates a constant signal with reference voltage and the voltage value """ - stim_file.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val)) - -def get_inverse_voltage(value): - if value > 0.5*vdd_voltage: - return gnd_voltage - elif value <= 0.5*vdd_voltage: - return vdd_voltage - else: - debug.error("Invalid value to get an inverse of: {0}".format(value)) - -def get_inverse_value(value): - if value > 0.5: - return 0 - elif value <= 0.5: - return 1 - else: - debug.error("Invalid value to get an inverse of: {0}".format(value)) + self.sf = stim_file + (self.process, self.vdd_voltage, self.temperature) = corner + self.gnd_voltage = 0 + self.device_models = tech.spice["fet_models"][self.process] -def gen_meas_delay(stim_file, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): - """ Creates the .meas statement for the measurement of delay """ - measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n" - stim_file.write(measure_string.format(meas_name, - trig_name, - trig_val, - trig_dir, - trig_td, - targ_name, - targ_val, - targ_dir, - targ_td)) -def gen_meas_power(stim_file, meas_name, t_initial, t_final): - """ Creates the .meas statement for the measurement of avg power """ - # power mea cmd is different in different spice: - if OPTS.spice_name == "hspice": - power_exp = "power" - else: - power_exp = "par('(-1*v(" + str(vdd_name) + ")*I(v" + str(vdd_name) + "))')" - stim_file.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name, - power_exp, - t_initial, - t_final)) - -def write_control(stim_file, end_time): - """ Write the control cards to run and end the simulation """ - # UIC is needed for ngspice to converge - stim_file.write(".TRAN 5p {0}n UIC\n".format(end_time)) - if OPTS.spice_name == "ngspice": - # ngspice sometimes has convergence problems if not using gear method - # which is more accurate, but slower than the default trapezoid method - # Do not remove this or it may not converge due to some "pa_00" nodes - # unless you figure out what these are. - stim_file.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear\n") - else: - stim_file.write(".OPTIONS POST=1 RUNLVL=4 PROBE\n") - - # create plots for all signals - stim_file.write("* probe is used for hspice/xa, while plot is used in ngspice\n") - if OPTS.debug_level>0: - if OPTS.spice_name in ["hspice","xa"]: - stim_file.write(".probe V(*)\n") - else: - stim_file.write(".plot V(*)\n") - else: - stim_file.write("*.probe V(*)\n") - stim_file.write("*.plot V(*)\n") - - # end the stimulus file - stim_file.write(".end\n\n") + def inst_sram(self, abits, dbits, sram_name): + """ Function to instatiate an SRAM subckt. """ + self.sf.write("Xsram ") + for i in range(dbits): + self.sf.write("D[{0}] ".format(i)) + for i in range(abits): + self.sf.write("A[{0}] ".format(i)) + for i in tech.spice["control_signals"]: + self.sf.write("{0} ".format(i)) + self.sf.write("{0} ".format(tech.spice["clk"])) + self.sf.write("{0} {1} ".format(self.vdd_name, self.gnd_name)) + self.sf.write("{0}\n".format(sram_name)) -def write_include(stim_file, models): - """Writes include statements, inputs are lists of model files""" - for item in list(models): - if os.path.isfile(item): - stim_file.write(".include \"{0}\"\n".format(item)) - else: - debug.error("Could not find spice model: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item)) + def inst_model(self, pins, model_name): + """ Function to instantiate a generic model with a set of pins """ + self.sf.write("X{0} ".format(model_name)) + for pin in pins: + self.sf.write("{0} ".format(pin)) + self.sf.write("{0}\n".format(model_name)) -def write_supply(stim_file): - """ Writes supply voltage statements """ - stim_file.write("V{0} {0} 0.0 {1}\n".format(vdd_name, vdd_voltage)) - stim_file.write("V{0} {0} 0.0 {1}\n".format(gnd_name, gnd_voltage)) - # This is for the test power supply - stim_file.write("V{0} {0} 0.0 {1}\n".format("test"+vdd_name, vdd_voltage)) - stim_file.write("V{0} {0} 0.0 {1}\n".format("test"+gnd_name, gnd_voltage)) + def create_inverter(self, size=1, beta=2.5): + """ Generates inverter for the top level signals (only for sim purposes) """ + self.sf.write(".SUBCKT test_inv in out {0} {1}\n".format(self.vdd_name, self.gnd_name)) + self.sf.write("mpinv out in {0} {0} {1} w={2}u l={3}u\n".format(self.vdd_name, + self.pmos_name, + beta * size * self.tx_width, + self.tx_length)) + self.sf.write("mninv out in {0} {0} {1} w={2}u l={3}u\n".format(self.gnd_name, + self.nmos_name, + size * self.tx_width, + self.tx_length)) + self.sf.write(".ENDS test_inv\n") -def run_sim(): - """ Run hspice in batch mode and output rawfile to parse. """ - temp_stim = "{0}stim.sp".format(OPTS.openram_temp) - import datetime - start_time = datetime.datetime.now() - debug.check(OPTS.spice_exe!="","No spice simulator has been found.") - - if OPTS.spice_name == "xa": - # Output the xa configurations here. FIXME: Move this to write it once. - xa_cfg = open("{}xa.cfg".format(OPTS.openram_temp), "w") - xa_cfg.write("set_sim_level -level 7\n") - xa_cfg.write("set_powernet_level 7 -node vdd\n") - xa_cfg.close() - cmd = "{0} {1} -c {2}xa.cfg -o {2}xa -mt 2".format(OPTS.spice_exe, - temp_stim, - OPTS.openram_temp) - valid_retcode=0 - elif OPTS.spice_name == "hspice": - # TODO: Should make multithreading parameter a configuration option - cmd = "{0} -mt 2 -i {1} -o {2}timing".format(OPTS.spice_exe, - temp_stim, - OPTS.openram_temp) - valid_retcode=0 - else: - cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, - temp_stim, - OPTS.openram_temp) - # for some reason, ngspice-25 returns 1 when it only has acceptable warnings - valid_retcode=1 + def create_buffer(self, buffer_name, size=[1,3], beta=2.5): + """ + Generates buffer for top level signals (only for sim + purposes). Size is pair for PMOS, NMOS width multiple. + """ + + self.sf.write(".SUBCKT test_{2} in out {0} {1}\n".format(self.vdd_name, + self.gnd_name, + buffer_name)) + self.sf.write("mpinv1 out_inv in {0} {0} {1} w={2}u l={3}u\n".format(self.vdd_name, + self.pmos_name, + beta * size[0] * self.tx_width, + self.tx_length)) + self.sf.write("mninv1 out_inv in {0} {0} {1} w={2}u l={3}u\n".format(self.gnd_name, + self.nmos_name, + size[0] * self.tx_width, + self.tx_length)) + self.sf.write("mpinv2 out out_inv {0} {0} {1} w={2}u l={3}u\n".format(self.vdd_name, + self.pmos_name, + beta * size[1] * self.tx_width, + self.tx_length)) + self.sf.write("mninv2 out out_inv {0} {0} {1} w={2}u l={3}u\n".format(self.gnd_name, + self.nmos_name, + size[1] * self.tx_width, + self.tx_length)) + self.sf.write(".ENDS test_{0}\n\n".format(buffer_name)) + + + def inst_buffer(self, buffer_name, signal_list): + """ Adds buffers to each top level signal that is in signal_list (only for sim purposes) """ + for signal in signal_list: + self.sf.write("X{0}_buffer {0} {0}_buf {1} {2} test_{3}\n".format(signal, + "test"+self.vdd_name, + "test"+self.gnd_name, + buffer_name)) + + + def inst_inverter(self, signal_list): + """ Adds inv for each signal that needs its inverted version (only for sim purposes) """ + for signal in signal_list: + self.sf.write("X{0}_inv {0} {0}_inv {1} {2} test_inv\n".format(signal, + "test"+self.vdd_name, + "test"+self.gnd_name)) - spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w') - spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w') + def inst_accesstx(self, dbits): + """ Adds transmission gate for inputs to data-bus (only for sim purposes) """ + self.sf.write("* Tx Pin-list: Drain Gate Source Body\n") + for i in range(dbits): + pmos_access_string="mp{0} DATA[{0}] acc_en D[{0}] {1} {2} w={3}u l={4}u\n" + self.sf.write(pmos_access_string.format(i, + "test"+self.vdd_name, + self.pmos_name, + 2 * self.tx_width, + self.tx_length)) + nmos_access_string="mn{0} DATA[{0}] acc_en_inv D[{0}] {1} {2} w={3}u l={4}u\n" + self.sf.write(nmos_access_string.format(i, + "test"+self.gnd_name, + self.nmos_name, + 2 * self.tx_width, + self.tx_length)) - debug.info(3, cmd) - retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True) + def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall): + """ + Generates a periodic signal with 50% duty cycle and slew rates. Period is measured + from 50% to 50%. + """ + self.sf.write("* PULSE: period={0}\n".format(period)) + pulse_string="V{0} {0} 0 PULSE ({1} {2} {3}n {4}n {5}n {6}n {7}n)\n" + self.sf.write(pulse_string.format(sig_name, + v1, + v2, + offset, + t_rise, + t_fall, + 0.5*period-0.5*t_rise-0.5*t_fall, + period)) - spice_stdout.close() - spice_stderr.close() + + def gen_pwl(self, sig_name, clk_times, data_values, period, slew, setup): + """ + Generate a PWL stimulus given a signal name and data values at each period. + Automatically creates slews and ensures each data occurs a setup before the clock + edge. The first clk_time should be 0 and is the initial time that corresponds + to the initial value. + """ + # the initial value is not a clock time + debug.check(len(clk_times)==len(data_values),"Clock and data value lengths don't match.") - if (retcode > valid_retcode): - debug.error("Spice simulation error: " + cmd, -1) - else: - end_time = datetime.datetime.now() - delta_time = round((end_time-start_time).total_seconds(),1) - debug.info(2,"*** Spice: {} seconds".format(delta_time)) + # shift signal times earlier for setup time + times = np.array(clk_times) - setup*period + values = np.array(data_values) * self.vdd_voltage + half_slew = 0.5 * slew + self.sf.write("* (time, data): {}\n".format(zip(clk_times, data_values))) + self.sf.write("V{0} {0} 0 PWL (0n {1}v ".format(sig_name, values[0])) + for i in range(1,len(times)): + self.sf.write("{0}n {1}v {2}n {3}v ".format(times[i]-half_slew, + values[i-1], + times[i]+half_slew, + values[i])) + self.sf.write(")\n") + + def gen_constant(self, sig_name, v_val): + """ Generates a constant signal with reference voltage and the voltage value """ + self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val)) + + def get_inverse_voltage(self, value): + if value > 0.5*self.vdd_voltage: + return self.gnd_voltage + elif value <= 0.5*self.vdd_voltage: + return self.vdd_voltage + else: + debug.error("Invalid value to get an inverse of: {0}".format(value)) + + def get_inverse_value(self, value): + if value > 0.5: + return 0 + elif value <= 0.5: + return 1 + else: + debug.error("Invalid value to get an inverse of: {0}".format(value)) + + + def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): + """ Creates the .meas statement for the measurement of delay """ + measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n" + self.sf.write(measure_string.format(meas_name, + trig_name, + trig_val, + trig_dir, + trig_td, + targ_name, + targ_val, + targ_dir, + targ_td)) + + def gen_meas_power(self, meas_name, t_initial, t_final): + """ Creates the .meas statement for the measurement of avg power """ + # power mea cmd is different in different spice: + if OPTS.spice_name == "hspice": + power_exp = "power" + else: + power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')" + self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name, + power_exp, + t_initial, + t_final)) + + def write_control(self, end_time): + """ Write the control cards to run and end the simulation """ + # UIC is needed for ngspice to converge + self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time)) + if OPTS.spice_name == "ngspice": + # ngspice sometimes has convergence problems if not using gear method + # which is more accurate, but slower than the default trapezoid method + # Do not remove this or it may not converge due to some "pa_00" nodes + # unless you figure out what these are. + self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature)) + else: + self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature)) + + # create plots for all signals + self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") + if OPTS.debug_level>0: + if OPTS.spice_name in ["hspice","xa"]: + self.sf.write(".probe V(*)\n") + else: + self.sf.write(".plot V(*)\n") + else: + self.sf.write("*.probe V(*)\n") + self.sf.write("*.plot V(*)\n") + + # end the stimulus file + self.sf.write(".end\n\n") + + + def write_include(self, circuit): + """Writes include statements, inputs are lists of model files""" + includes = self.device_models + [circuit] + self.sf.write("* {} process corner\n".format(self.process)) + for item in list(includes): + if os.path.isfile(item): + self.sf.write(".include \"{0}\"\n".format(item)) + else: + debug.error("Could not find spice model: {0}\nSet SPICE_MODEL_DIR to over-ride path.\n".format(item)) + + + def write_supply(self): + """ Writes supply voltage statements """ + self.sf.write("V{0} {0} 0.0 {1}\n".format(self.vdd_name, self.vdd_voltage)) + self.sf.write("V{0} {0} 0.0 {1}\n".format(self.gnd_name, self.gnd_voltage)) + # This is for the test power supply + self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.vdd_name, self.vdd_voltage)) + self.sf.write("V{0} {0} 0.0 {1}\n".format("test"+self.gnd_name, self.gnd_voltage)) + + + def run_sim(self): + """ Run hspice in batch mode and output rawfile to parse. """ + temp_stim = "{0}stim.sp".format(OPTS.openram_temp) + import datetime + start_time = datetime.datetime.now() + debug.check(OPTS.spice_exe!="","No spice simulator has been found.") + + if OPTS.spice_name == "xa": + # Output the xa configurations here. FIXME: Move this to write it once. + xa_cfg = open("{}xa.cfg".format(OPTS.openram_temp), "w") + xa_cfg.write("set_sim_level -level 7\n") + xa_cfg.write("set_powernet_level 7 -node vdd\n") + xa_cfg.close() + cmd = "{0} {1} -c {2}xa.cfg -o {2}xa -mt 2".format(OPTS.spice_exe, + temp_stim, + OPTS.openram_temp) + valid_retcode=0 + elif OPTS.spice_name == "hspice": + # TODO: Should make multithreading parameter a configuration option + cmd = "{0} -mt 2 -i {1} -o {2}timing".format(OPTS.spice_exe, + temp_stim, + OPTS.openram_temp) + valid_retcode=0 + else: + cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, + temp_stim, + OPTS.openram_temp) + # for some reason, ngspice-25 returns 1 when it only has acceptable warnings + valid_retcode=1 + + + spice_stdout = open("{0}spice_stdout.log".format(OPTS.openram_temp), 'w') + spice_stderr = open("{0}spice_stderr.log".format(OPTS.openram_temp), 'w') + + debug.info(3, cmd) + retcode = subprocess.call(cmd, stdout=spice_stdout, stderr=spice_stderr, shell=True) + + spice_stdout.close() + spice_stderr.close() + + if (retcode > valid_retcode): + debug.error("Spice simulation error: " + cmd, -1) + else: + end_time = datetime.datetime.now() + delta_time = round((end_time-start_time).total_seconds(),1) + debug.info(2,"*** Spice: {} seconds".format(delta_time)) diff --git a/compiler/example_config_scn3me_subm.py b/compiler/example_config_scn3me_subm.py index 1145c5b2..5ba52bb0 100644 --- a/compiler/example_config_scn3me_subm.py +++ b/compiler/example_config_scn3me_subm.py @@ -3,6 +3,9 @@ num_words = 16 num_banks = 1 tech_name = "scn3me_subm" +supply_voltages = [ 5.0 ] +temperatures = [ 25.0 ] + output_path = "temp" output_name = "sram_2_16_1_scn3me_subm" diff --git a/compiler/options.py b/compiler/options.py index 6393a545..738b1907 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -44,6 +44,13 @@ class options(optparse.Values): analytical_delay = True # Purge the temp directory after a successful run (doesn't purge on errors, anyhow) purge_temp = True + + # These are the configuration parameters + rw_ports = 1 + r_ports = 0 + supply_voltages = [3] + temperatures = [25] + process_corners = ["TT"] # These are the default modules that can be over-riden diff --git a/compiler/sram.py b/compiler/sram.py index 72815dd5..951a5ea3 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -1040,8 +1040,7 @@ class sram(design.design): # Characterize the design start_time = datetime.datetime.now() from characterizer import lib - libname = OPTS.output_path + self.name + ".lib" - print("LIB: Writing to {0}".format(libname)) + print("LIB: Characterizing... ") if OPTS.analytical_delay: print("Using analytical delay models (no characterization)") else: @@ -1049,7 +1048,7 @@ class sram(design.design): print("Performing simulation-based characterization with {}".format(OPTS.spice_name)) if OPTS.trim_netlist: print("Trimming netlist to speed up characterization.") - lib.lib(libname=libname,sram=self,sp_file=sp_file) + lib.lib(out_dir=OPTS.output_path, sram=self, sp_file=sp_file) print_time("Characterization", datetime.datetime.now(), start_time) # Write the layout diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index aa9212b3..855711c5 100644 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -27,7 +27,7 @@ class timing_sram_test(openram_test): debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) import sram - + import tech debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = sram.sram(word_size=OPTS.word_size, num_words=OPTS.num_words, @@ -43,9 +43,9 @@ class timing_sram_test(openram_test): probe_data = s.word_size - 1 debug.info(1, "Probe address {0} probe data {1}".format(probe_address, probe_data)) - d = delay.delay(s,tempspice) + d = delay.delay(s,tempspice,tech.spice["nom_delay"]) import tech - loads = [tech.spice["FF_in_cap"]*4] + loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] data = d.analyze(probe_address, probe_data,slews,loads) #print data diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py index 5ec3b77e..8e4bbf1f 100644 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -31,7 +31,7 @@ class timing_setup_test(openram_test): import tech slews = [tech.spice["rise_time"]*2] - sh = setup_hold.setup_hold() + sh = setup_hold.setup_hold(tech.spice["nom_corner"])) data = sh.analyze(slews,slews) if OPTS.tech_name == "freepdk45": diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index be2c04d6..e857fca1 100644 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -27,7 +27,7 @@ class timing_sram_test(openram_test): debug.error("Could not find {} simulator.".format(OPTS.spice_name),-1) import sram - + import tech debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") s = sram.sram(word_size=OPTS.word_size, num_words=OPTS.num_words, @@ -41,9 +41,9 @@ class timing_sram_test(openram_test): probe_data = s.word_size - 1 debug.info(1, "Probe address {0} probe data {1}".format(probe_address, probe_data)) - d = delay.delay(s,tempspice) + d = delay.delay(s,tempspice,tech.spice["nom_corner"]) import tech - loads = [tech.spice["FF_in_cap"]*4] + loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] data = d.analyze(probe_address, probe_data,slews,loads) #print data diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py index 5fa383fc..e3c90914 100644 --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -30,7 +30,7 @@ class timing_setup_test(openram_test): import tech slews = [tech.spice["rise_time"]*2] - sh = setup_hold.setup_hold() + sh = setup_hold.setup_hold(tech.spice["nom_corner"]) data = sh.analyze(slews,slews) if OPTS.tech_name == "freepdk45": diff --git a/compiler/tests/23_lib_sram_model_test.py b/compiler/tests/23_lib_sram_model_test.py index 5d47c287..9e4734e2 100644 --- a/compiler/tests/23_lib_sram_model_test.py +++ b/compiler/tests/23_lib_sram_model_test.py @@ -5,7 +5,7 @@ Check the .lib file for an SRAM import unittest from testutils import header,openram_test -import sys,os +import sys,os,re sys.path.append(os.path.join(sys.path[0],"..")) import globals from globals import OPTS @@ -30,14 +30,20 @@ class lib_test(openram_test): tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) - filename = s.name + "_analytical.lib" - libname = OPTS.openram_temp + filename - lib.lib(libname=libname,sram=s,sp_file=tempspice,use_model=True) + lib.lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=True) - # let's diff the result with a golden model - golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename) - self.isapproxdiff(libname,golden,0.15) + # get all of the .lib files generated + files = os.listdir(OPTS.openram_temp) + nametest = re.compile("\.lib$", re.IGNORECASE) + lib_files = filter(nametest.search, files) + # and compare them with the golden model + for filename in lib_files: + newname = filename.replace(".lib","_analytical.lib") + libname = "{0}/{1}".format(OPTS.openram_temp,filename) + golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),newname) + self.isapproxdiff(libname,golden,0.15) + globals.end_openram() # instantiate a copdsay of the class to actually run the test diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py index 722854b3..d58c50dd 100644 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -5,7 +5,7 @@ Check the .lib file for an SRAM import unittest from testutils import header,openram_test -import sys,os +import sys,os,re sys.path.append(os.path.join(sys.path[0],"..")) import globals from globals import OPTS @@ -39,13 +39,18 @@ class lib_test(openram_test): tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) - filename = s.name + ".lib" - libname = OPTS.openram_temp + filename - lib.lib(libname=libname,sram=s,sp_file=tempspice,use_model=False) - - # let's diff the result with a golden model - golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename) - self.isapproxdiff(libname,golden,0.15) + lib.lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=False) + + # get all of the .lib files generated + files = os.listdir(OPTS.openram_temp) + nametest = re.compile("\.lib$", re.IGNORECASE) + lib_files = filter(nametest.search, files) + + # and compare them with the golden model + for filename in lib_files: + libname = "{0}/{1}".format(OPTS.openram_temp,filename) + golden = "{0}/golden/{1}".format(os.path.dirname(os.path.realpath(__file__)),filename) + self.isapproxdiff(libname,golden,0.15) OPTS.analytical_delay = True OPTS.trim_netlist = True diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index 1947fcc7..c58f0ef5 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -237,26 +237,16 @@ drc["metal4_extend_via4"] = 0.07 spice = {} spice["nmos"] = "nmos_vtg" spice["pmos"] = "pmos_vtg" +# This is a map of corners to model files SPICE_MODEL_DIR=os.environ.get("SPICE_MODEL_DIR") -spice["fet_models"] = [SPICE_MODEL_DIR+"/NMOS_VTG.inc", - SPICE_MODEL_DIR+"/PMOS_VTG.inc"] +spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/NMOS_VTG.inc",SPICE_MODEL_DIR+"/PMOS_VTG.inc"]} #spice stimulus related variables spice["feasible_period"] = 5 # estimated feasible period in ns -spice["supply_voltage"] = 1.0 #vdd in [Volts] -spice["gnd_voltage"] = 0.0 #gnd in [Volts] -spice["rise_time"] = 0.005 #rise time in [Nano-seconds] -spice["fall_time"] = 0.005 #fall time in [Nano-seconds] -spice["temp"] = 25 #temperature in [Celsius] - -#parasitics of metal for bit/word lines -spice["bitline_res"] = 0.1 #bitline resistance in [Ohms/micro-meter] -spice["bitline_cap"] = 0.2 #bitline capacitance in [Femto-farad/micro-meter] -spice["wordline_res"] = 0.1 #wordline resistance in [Ohms/micro-meter] -spice["wordline_cap"] = 0.2 #wordline capacitance in [Femto-farad/micro-meter] -spice["FF_in_cap"] = 0.2091 #Input capacitance of ms_flop (Din) [Femto-farad] -spice["tri_gate_out_cap"] = 0.41256 #Output capacitance of tri_gate (tri_out) [Femto-farad] - +spice["supply_voltage"] = 1.0 # ideal vdd in [Volts] +spice["rise_time"] = 0.005 # rise time in [Nano-seconds] +spice["fall_time"] = 0.005 # fall time in [Nano-seconds] +spice["nom_corner"] = ("TT", 1.0, 25) # Nominal process corner #sram signal names spice["vdd_name"] = "vdd" @@ -264,8 +254,6 @@ spice["gnd_name"] = "gnd" spice["control_signals"] = ["CSb", "WEb", "OEb"] spice["data_name"] = "DATA" spice["addr_name"] = "ADDR" -spice["pmos_name"] = spice["pmos"] -spice["nmos_name"] = spice["nmos"] spice["minwidth_tx"] = drc["minwidth_tx"] spice["channel"] = drc["minlength_channel"] spice["clk"] = "clk" @@ -280,6 +268,7 @@ spice["msflop_setup"] = 9 # DFF setup time in ps spice["msflop_hold"] = 1 # DFF hold time in ps spice["msflop_delay"] = 20.5 # DFF Clk-to-q delay in ps spice["msflop_slew"] = 13.1 # DFF output slew in ps w/ no load +spice["msflop_in_cap"] = 0.2091 # Input capacitance of ms_flop (Din) [Femto-farad] ################################################### diff --git a/technology/scn3me_subm/tech/tech.py b/technology/scn3me_subm/tech/tech.py index d3064c34..c89f5613 100755 --- a/technology/scn3me_subm/tech/tech.py +++ b/technology/scn3me_subm/tech/tech.py @@ -197,24 +197,16 @@ drc["minarea_metal3"] = 0 spice={} spice["nmos"]="n" spice["pmos"]="p" -spice["fet_models"] = [os.environ.get("SPICE_MODEL_DIR")+"/on_c5n.sp"] +# This is a map of corners to model files +SPICE_MODEL_DIR=os.environ.get("SPICE_MODEL_DIR") +spice["fet_models"] = { "TT" : [SPICE_MODEL_DIR+"/on_c5n.sp"] } #spice stimulus related variables spice["feasible_period"] = 5 # estimated feasible period in ns -spice["supply_voltage"] = 5.0 #vdd in [Volts] -spice["gnd_voltage"] = 0.0 #gnd in [Volts] -spice["rise_time"] = 0.05 #rise time in [Nano-seconds] -spice["fall_time"] = 0.05 #fall time in [Nano-seconds] -spice["temp"] = 25 #temperature in [Celsius] - -#parasitics of metal for bit/word lines -spice["bitline_res"] = 0.1 #bitline resistance in [Ohms/micro-meter] -spice["bitline_cap"] = 0.2 #bitline capacitance in [Femto-farad/micro-meter] -spice["wordline_res"] = 0.1 #wordline resistance in [Ohms/micro-meter] -spice["wordline_cap"] = 0.2 #wordline capacitance in [Femto-farad/micro-meter] -spice["FF_in_cap"] = 9.8242 #Input capacitance of ms_flop (Din) [Femto-farad] -spice["tri_gate_out_cap"] = 1.4980 #Output capacitance of tri_gate (tri_out) [Femto-farad] - +spice["supply_voltage"] = 5.0 # ideal vdd in [Volts] +spice["rise_time"] = 0.05 # rise time in [Nano-seconds] +spice["fall_time"] = 0.05 # fall time in [Nano-seconds] +spice["nom_corner"] = ("TT", 5.0, 25) # Nominal process corner #sram signal names spice["vdd_name"] = "vdd" @@ -222,8 +214,6 @@ spice["gnd_name"] = "gnd" spice["control_signals"] = ["CSb", "WEb", "OEb"] spice["data_name"] = "DATA" spice["addr_name"] = "ADDR" -spice["pmos_name"] = spice["pmos"] -spice["nmos_name"] = spice["nmos"] spice["minwidth_tx"] = drc["minwidth_tx"] spice["channel"] = drc["minlength_channel"] spice["clk"] = "clk" @@ -239,6 +229,7 @@ spice["msflop_setup"] = 9 # DFF setup time in ps spice["msflop_hold"] = 1 # DFF hold time in ps spice["msflop_delay"] = 20.5 # DFF Clk-to-q delay in ps spice["msflop_slew"] = 13.1 # DFF output slew in ps w/ no load +spice["msflop_in_cap"] = 9.8242 # Input capacitance of ms_flop (Din) [Femto-farad] ###################################################