diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 4c7f8f8f..081400a7 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -220,7 +220,7 @@ class delay(simulation): storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" "supported for characterization. Storage nets={}").format(storage_names)) - if not OPTS.use_pex: + if not OPTS.use_pex or OPTS.calibre_pex: q_name = cell_name + '.' + str(storage_names[0]) qbar_name = cell_name + '.' + str(storage_names[1]) else: @@ -418,7 +418,9 @@ class delay(simulation): t_rise=self.slew, t_fall=self.slew) + self.load_all_measure_nets() self.write_delay_measures() + self.write_simulation_saves() # run until the end of the cycle time self.stim.write_control(self.cycle_times[-1] + self.period) @@ -596,6 +598,69 @@ class delay(simulation): self.sf.write("* Write ports {}\n".format(write_port)) self.write_delay_measures_write_port(write_port) + def load_pex_net(self, net: str): + from subprocess import check_output, CalledProcessError + prefix = (self.sram_instance_name + ".").lower() + if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex: + return net + original_net = net + net = net[len(prefix):] + net = net.replace(".", "_").replace("[", "\[").replace("]", "\]") + for pattern in ["\sN_{}_[MXmx]\S+_[gsd]".format(net), net]: + try: + match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file]) + return prefix + match.decode().strip() + except CalledProcessError: + pass + return original_net + + def load_all_measure_nets(self): + measurement_nets = set() + for port, meas in zip(self.targ_read_ports*len(self.read_meas_lists) + + self.targ_write_ports * len(self.write_meas_lists), + self.read_meas_lists + self.write_meas_lists): + for measurement in meas: + visited = getattr(measurement, 'pex_visited', False) + for prop in ["trig_name_no_port", "targ_name_no_port"]: + if hasattr(measurement, prop): + net = getattr(measurement, prop).format(port) + if not visited: + net = self.load_pex_net(net) + setattr(measurement, prop, net) + measurement_nets.add(net) + measurement.pex_visited = True + self.measurement_nets = measurement_nets + return measurement_nets + + def write_simulation_saves(self): + for net in self.measurement_nets: + self.sf.write(".plot V({0}) \n".format(net)) + probe_nets = set() + sram_name = self.sram_instance_name + col = self.bitline_column + row = self.wordline_row + for port in set(self.targ_read_ports + self.targ_write_ports): + probe_nets.add("WEB{}".format(port)) + probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port)) + probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format( + self.sram_instance_name, port, self.bitline_column)) + probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port, + self.bitline_column)) + if not OPTS.use_pex: + continue + probe_nets.add( + "{0}.vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, row, col - 1)) + probe_nets.add( + "{0}.p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, port, col)) + probe_nets.add( + "{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col)) + probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name, + port, col)) + probe_nets.update(self.measurement_nets) + for net in probe_nets: + debug.info(2, "Probe: {}".format(net)) + self.sf.write(".plot V({}) \n".format(self.load_pex_net(net))) + def write_power_measures(self): """ Write the measure statements to quantify the leakage power only. diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 0617bfcd..6327348f 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -467,7 +467,7 @@ class simulation(): """ port = self.read_ports[0] - if not OPTS.use_pex: + if not OPTS.use_pex or OPTS.calibre_pex: # pex names handled post extraction self.graph.get_all_paths('{}{}'.format("clk", port), '{}{}_{}'.format(self.dout_name, port, self.probe_data)) @@ -523,7 +523,7 @@ class simulation(): debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.") enable_name = sa_mods[0].get_enable_name() sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) - if OPTS.use_pex: + if OPTS.use_pex and not OPTS.calibre_pex: sen_name = sen_name.split('.')[-1] return sen_name @@ -581,7 +581,7 @@ class simulation(): exclude_set = self.get_bl_name_search_exclusions() for int_net in [cell_bl, cell_br]: bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) - if OPTS.use_pex: + if OPTS.use_pex and not OPTS.calibre_pex: for i in range(len(bl_names)): bl_names[i] = bl_names[i].split('.')[-1] return bl_names[0], bl_names[1] diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 5855f236..4ab1fe4f 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -52,7 +52,7 @@ class stimuli(): def inst_model(self, pins, model_name): """ Function to instantiate a generic model with a set of pins """ - if OPTS.use_pex: + if OPTS.use_pex and not OPTS.calibre_pex: self.inst_pex_model(pins, model_name) else: self.sf.write("X{0} ".format(model_name)) @@ -282,15 +282,16 @@ class stimuli(): self.sf.write(".OPTIONS HIER_DELIM=1 \n") # create plots for all signals - self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") - if OPTS.verbose_level>0: - if OPTS.spice_name in ["hspice", "xa"]: - self.sf.write(".probe V(*)\n") + if not OPTS.use_pex: # Don't save all for extracted simulations + self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") + if OPTS.verbose_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(".plot V(*)\n") - else: - self.sf.write("*.probe V(*)\n") - self.sf.write("*.plot V(*)\n") + self.sf.write("*.probe V(*)\n") + self.sf.write("*.plot V(*)\n") # end the stimulus file self.sf.write(".end\n\n") @@ -349,7 +350,7 @@ class stimuli(): valid_retcode = 0 elif OPTS.spice_name == "hspice": # TODO: Should make multithreading parameter a configuration option - cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe, + cmd = "{0} -d -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe, OPTS.num_sim_threads, temp_stim, OPTS.openram_temp) diff --git a/compiler/example_configs/test_45.py b/compiler/example_configs/test_45.py new file mode 100644 index 00000000..732186d7 --- /dev/null +++ b/compiler/example_configs/test_45.py @@ -0,0 +1,28 @@ +word_size = 64 +num_words = 64 + +num_rw_ports = 1 +num_r_ports = 0 +num_w_ports = 0 +num_banks = 1 +words_per_row = 1 +spice_name = "hspice" + + +tech_name = "freepdk45" +process_corners = ["TT"] +supply_voltages = [1.0] +temperatures = [25] + +route_supplies = True +perimeter_pins = False +check_lvsdrc = True +nominal_corner_only = True +load_scales = [0.5] +slew_scales = [0.5] +use_pex = False +analytical_delay = False + +output_name = "sram_w_{0}_{1}_{2}".format(word_size, num_words, tech_name) +output_path = "macro/{}".format(output_name) + diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 883b16dc..852eca46 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -201,7 +201,7 @@ class sram_base(design, verilog, lef): highest_coord = self.find_highest_coords() self.width = highest_coord[0] self.height = highest_coord[1] - if OPTS.use_pex: + if OPTS.use_pex and not OPTS.calibre_pex: self.add_global_pex_labels() self.add_boundary(ll=vector(0, 0), ur=vector(self.width, self.height)) diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 326771f8..b8e5175c 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -72,6 +72,7 @@ elif "magic"==OPTS.pex_exe[0]: else: debug.warning("Did not find a supported PEX tool." + "Disable DRC/LVS with check_lvsdrc=False to ignore.", 2) +OPTS.calibre_pex = len(OPTS.pex_exe) > 0 and OPTS.pex_exe[0] == "calibre" # if OPTS.tech_name == "sky130": # if OPTS.magic_exe and "magic"==OPTS.magic_exe[0]: diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 700815cd..053ee916 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -21,6 +21,7 @@ import os import shutil import re import debug +import utils from globals import OPTS from run_script import run_script @@ -166,6 +167,17 @@ def write_pex_script(cell_name, extract, output, final_verification=False, outpu 'pexPexReportFile': cell_name + ".pex.report", 'pexMaskDBFile': cell_name + ".maskdb", 'cmnFDIDEFLayoutPath': cell_name + ".def", + 'cmnRunMT': "1", + 'cmnNumTurbo': "16", + 'pexPowerNames': "vdd", + 'pexGroundNames': "gnd", + 'pexPexGroundName': "1", + 'pexPexGroundNameValue': "gnd", + 'pexPexSeparator': "1", + 'pexPexSeparatorValue': "_", + 'pexPexNetlistNameSource': 'SOURCENAMES', + 'pexSVRFCmds': '{SOURCE CASE YES} {LAYOUT CASE YES}', + 'pexIncludeCmdsType': 'SVRF', } # write the runset file