diff --git a/compiler/tests/26_hspice_pex_pinv_test.py b/compiler/tests/26_hspice_pex_pinv_test.py new file mode 100755 index 00000000..f0cccba3 --- /dev/null +++ b/compiler/tests/26_hspice_pex_pinv_test.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +""" +Run regression tests/pex test on an extracted pinv to ensure pex functionality +with HSPICE. +""" +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 hspice_pex_pinv_test(openram_test): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import pinv + + # load the hspice + OPTS.spice_name="hspice" + OPTS.analytical_delay = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + + # generate the pinv + prev_purge_value = OPTS.purge_temp + OPTS.purge_temp = False # force set purge to false to save the sp file + debug.info(2, "Checking 1x size inverter") + tx = pinv.pinv(name="pinv", size=1) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tx.gds_write(tempgds) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tx.sp_write(tempsp) + + # make sure that the library simulation is successful\ + sp_delay = self.simulate_delay(test_module = tempsp, + top_level_name = tx.name) + if sp_delay is "Failed": + self.fail('Library Spice module did not behave as expected') + + # now generate its pex file + pex_file = self.run_pex(tx) + OPTS.purge_temp = prev_purge_value # restore the old purge value + # generate simulation for pex, make sure the simulation is successful + pex_delay = self.simulate_delay(test_module = pex_file, + top_level_name = tx.name) + # make sure the extracted spice simulated + if pex_delay is "Failed": + self.fail('Pex file did not behave as expected') + + # if pex data is bigger than original spice file then result is ok + # However this may not always be true depending on the netlist provided + # comment out for now + #debug.info(2,"pex_delay: {0}".format(pex_delay)) + #debug.info(2,"sp_delay: {0}".format(sp_delay)) + + #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + #.format(pex_delay,sp_delay) + + globals.end_openram() + + def simulate_delay(self, test_module, top_level_name): + from characterizer import charutils + from charutils import parse_spice_list + # setup simulation + sim_file = OPTS.openram_temp + "stim.sp" + log_file_name = "timing" + test_sim = self.write_simulation(sim_file, test_module, top_level_name) + test_sim.run_sim() + delay = parse_spice_list(log_file_name, "pinv_delay") + return delay + + def write_simulation(self, sim_file, cir_file, top_module_name): + """ write pex spice simulation for a pinv test""" + import tech + from characterizer import measurements, stimuli + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + sim_file = open(sim_file, "w") + simulation = stimuli(sim_file,corner) + + # library files + simulation.write_include(cir_file) + + # supply voltages + simulation.gen_constant(sig_name ="vdd", + v_val = tech.spice["nom_supply_voltage"]) + simulation.gen_constant(sig_name = "gnd", + v_val = "0v") + + run_time = tech.spice["feasible_period"] * 4 + # input voltage + clk_period = tech.spice["feasible_period"] + simulation.gen_pwl(sig_name ="input", + clk_times = [clk_period,clk_period], + data_values = [1,0], + period = clk_period, + slew = 0.001*tech.spice["feasible_period"], + setup = 0) + + # instantiation of simulated pinv + simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], + model_name = top_module_name) + + # delay measurement + delay_measure = measurements.delay_measure(measure_name = "pinv_delay", + trig_name = "input", + targ_name = "output", + trig_dir_str = "FALL", + targ_dir_str = "RISE", + has_port = False) + trig_td = trag_td = 0.01 * run_time + rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + delay_measure.write_measure(simulation, rest_info) + + simulation.write_control(end_time = run_time) + sim_file.close() + return simulation + + +# 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/26_ngspice_pex_pinv_test.py b/compiler/tests/26_ngspice_pex_pinv_test.py new file mode 100755 index 00000000..2eb95948 --- /dev/null +++ b/compiler/tests/26_ngspice_pex_pinv_test.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California +# All rights reserved. +# +""" +Run regression tests/pex test on an extracted pinv to ensure pex functionality +with Ngspice. +""" +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 ngspice_pex_pinv_test(openram_test): + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + import pinv + + # load the ngspice + OPTS.spice_name="ngspice" + OPTS.analytical_delay = False + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + + # generate the pinv module + prev_purge_value = OPTS.purge_temp + OPTS.purge_temp = False # force set purge to false to save the sp file + debug.info(2, "Checking 1x size inverter") + tx = pinv.pinv(name="pinv", size=1) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,tx.name) + tx.gds_write(tempgds) + tempsp = "{0}{1}.sp".format(OPTS.openram_temp,tx.name) + tx.sp_write(tempsp) + + # make sure that the library simulation is successful + sp_delay = self.simulate_delay(test_module = tempsp, + top_level_name = tx.name) + if sp_delay is "Failed": + self.fail('Library Spice module did not behave as expected') + + # now generate its pex file + pex_file = self.run_pex(tx) + OPTS.purge_temp = prev_purge_value # restore the old purge value + # generate simulation for pex, make sure the simulation is successful + pex_delay = self.simulate_delay(test_module = pex_file, + top_level_name = tx.name) + # make sure the extracted spice simulated + if pex_delay is "Failed": + self.fail('Pex file did not behave as expected') + + # if pex data is bigger than original spice file then result is ok + # However this may not always be true depending on the netlist provided + # comment out for now + #debug.info(2,"pex_delay: {0}".format(pex_delay)) + #debug.info(2,"sp_delay: {0}".format(sp_delay)) + + #assert pex_delay > sp_delay, "pex delay {0} is smaller than sp_delay {1}"\ + #.format(pex_delay,sp_delay) + + globals.end_openram() + + def simulate_delay(self, test_module, top_level_name): + from characterizer import charutils + from charutils import parse_spice_list + # setup simulation + sim_file = OPTS.openram_temp + "stim.sp" + log_file_name = "timing" + test_sim = self.write_simulation(sim_file, test_module, top_level_name) + test_sim.run_sim() + delay = parse_spice_list(log_file_name, "pinv_delay") + return delay + + def write_simulation(self, sim_file, cir_file, top_module_name): + """ write pex spice simulation for a pinv test""" + import tech + from characterizer import measurements, stimuli + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + sim_file = open(sim_file, "w") + simulation = stimuli(sim_file,corner) + + # library files + simulation.write_include(cir_file) + + # supply voltages + simulation.gen_constant(sig_name ="vdd", + v_val = tech.spice["nom_supply_voltage"]) + # The scn4m_subm and ngspice combination will have a gnd source error: + # "Fatal error: instance vgnd is a shorted VSRC" + # However, remove gnd power for all techa pass for this test + # simulation.gen_constant(sig_name = "gnd", + # v_val = "0v") + + + run_time = tech.spice["feasible_period"] * 4 + # input voltage + clk_period = tech.spice["feasible_period"] + simulation.gen_pwl(sig_name ="input", + clk_times = [clk_period,clk_period], + data_values = [1,0], + period = clk_period, + slew = 0.001*tech.spice["feasible_period"], + setup = 0) + + # instantiation of simulated pinv + simulation.inst_model(pins = ["input", "output", "vdd", "gnd"], + model_name = top_module_name) + + # delay measurement + delay_measure = measurements.delay_measure(measure_name = "pinv_delay", + trig_name = "input", + targ_name = "output", + trig_dir_str = "FALL", + targ_dir_str = "RISE", + has_port = False) + trig_td = trag_td = 0.01 * run_time + rest_info = trig_td,trag_td,tech.spice["nom_supply_voltage"] + delay_measure.write_measure(simulation, rest_info) + + simulation.write_control(end_time = run_time) + sim_file.close() + return simulation + +# 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/testutils.py b/compiler/tests/testutils.py index d4904e09..057fd7bf 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -16,7 +16,7 @@ import debug class openram_test(unittest.TestCase): """ Base unit test that we have some shared classes in. """ - + def local_drc_check(self, w): self.reset() @@ -31,11 +31,11 @@ class openram_test(unittest.TestCase): if OPTS.purge_temp: self.cleanup() - + def local_check(self, a, final_verification=False): self.reset() - + tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) @@ -52,7 +52,7 @@ class openram_test(unittest.TestCase): #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("DRC failed: {}".format(a.name)) - + result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) if result != 0: #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) @@ -62,7 +62,18 @@ class openram_test(unittest.TestCase): if OPTS.purge_temp: self.cleanup() - + + def run_pex(self, a, output=None): + if output == None: + output = OPTS.openram_temp + a.name + ".pex.netlist" + tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) + tempgds = "{0}{1}.gds".format(OPTS.openram_temp,a.name) + + import verify + result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) + if result != 0: + self.fail("PEX ERROR: {}".format(a.name)) + return output def find_feasible_test_period(self, delay_obj, sram, load, slew): """Creates a delay simulation to determine a feasible period for the functional tests to run. @@ -72,19 +83,19 @@ class openram_test(unittest.TestCase): delay_obj.set_load_slew(load, slew) test_port = delay_obj.read_ports[0] #Only test one port, assumes other ports have similar period. delay_obj.analysis_init(probe_address="1"*sram.addr_size, probe_data=(sram.word_size-1)) - delay_obj.find_feasible_period_one_port(test_port) - return delay_obj.period - + delay_obj.find_feasible_period_one_port(test_port) + return delay_obj.period + def cleanup(self): """ Reset the duplicate checker and cleanup files. """ files = glob.glob(OPTS.openram_temp + '*') for f in files: # Only remove the files if os.path.isfile(f): - os.remove(f) + os.remove(f) def reset(self): - """ + """ Reset everything after each test. """ # Reset the static duplicate name checker for unit tests. @@ -113,7 +124,7 @@ class openram_test(unittest.TestCase): data_string=pprint.pformat(data) debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string) return data_matches - + def isclose(self,key,value,actual_value,error_tolerance=1e-2): @@ -129,7 +140,7 @@ class openram_test(unittest.TestCase): return False def relative_diff(self, value1, value2): - """ Compute the relative difference of two values and normalize to the largest. + """ Compute the relative difference of two values and normalize to the largest. If largest value is 0, just return the difference.""" # Edge case to avoid divide by zero @@ -145,7 +156,7 @@ class openram_test(unittest.TestCase): # Edge case where greater is a zero if norm_value == 0: min_value = abs(min(value1, value2)) - + return abs(value1 - value2) / norm_value @@ -159,15 +170,15 @@ class openram_test(unittest.TestCase): """Compare two files. Arguments: - + filename1 -- First file name - + filename2 -- Second file name Return value: - + True if the files are the same, False otherwise. - + """ import re import debug @@ -200,7 +211,7 @@ class openram_test(unittest.TestCase): debug.info(3,"line1_floats: "+str(line1_floats)) debug.info(3,"line2_floats: "+str(line2_floats)) - + # 2. Remove the floats from the string for f in line1_floats: line1=line1.replace(f,"",1) @@ -212,10 +223,10 @@ class openram_test(unittest.TestCase): # 3. Convert to floats rather than strings line1_floats = [float(x) for x in line1_floats] line2_floats = [float(x) for x in line1_floats] - + # 4. Check if remaining string matches if line1 != line2: - #Uncomment if you want to see all the individual chars of the two lines + #Uncomment if you want to see all the individual chars of the two lines #print(str([i for i in line1])) #print(str([i for i in line2])) if mismatches==0: @@ -278,13 +289,13 @@ class openram_test(unittest.TestCase): debug.info(2,"MATCH {0} {1}".format(filename1,filename2)) return True - + def header(filename, technology): # Skip the header for gitlab regression import getpass if getpass.getuser() == "gitlab-runner": return - + tst = "Running Test for:" print("\n") print(" ______________________________________________________________________________ ") @@ -298,16 +309,18 @@ def header(filename, technology): def debugTestRunner(post_mortem=None): """unittest runner doing post mortem debugging on failing tests""" - if post_mortem is None: + if post_mortem is None and not OPTS.purge_temp: post_mortem = pdb.post_mortem class DebugTestResult(unittest.TextTestResult): def addError(self, test, err): # called before tearDown() traceback.print_exception(*err) - post_mortem(err[2]) + if post_mortem: + post_mortem(err[2]) super(DebugTestResult, self).addError(test, err) def addFailure(self, test, err): traceback.print_exception(*err) - post_mortem(err[2]) + if post_mortem: + post_mortem(err[2]) super(DebugTestResult, self).addFailure(test, err) return unittest.TextTestRunner(resultclass=DebugTestResult) diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index 8698b248..8abca448 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -93,11 +93,11 @@ def write_calibre_lvs_script(cell_name, final_verification): 'cmnFDIUseLayerMap': 1, 'cmnTranscriptFile': './lvs.log', 'cmnTranscriptEchoToFile': 1, - 'lvsRecognizeGates': 'NONE', + 'lvsRecognizeGates': 'NONE', } # FIXME: Remove when vdd/gnd connected #'cmnVConnectNamesState' : 'ALL', #connects all nets with the same namee - # FIXME: Remove when vdd/gnd connected + # FIXME: Remove when vdd/gnd connected #'lvsAbortOnSupplyError' : 0 if not final_verification: @@ -106,7 +106,7 @@ def write_calibre_lvs_script(cell_name, final_verification): lvs_runset['cmnVConnectNames']='vdd gnd' else: lvs_runset['lvsAbortOnSupplyError']=1 - + # write the runset file @@ -127,20 +127,23 @@ def write_calibre_lvs_script(cell_name, final_verification): f.write("\n") f.close() os.system("chmod u+x {}".format(run_file)) - + return lvs_runset def write_calibre_pex_script(cell_name, extract, output, final_verification): - + if output == None: output = name + ".pex.netlist" # check if lvs report has been done # if not run drc and lvs if not os.path.isfile(cell_name + ".lvs.report"): + gds_name = OPTS.openram_temp +"/"+ cell_name + ".gds" + sp_name = OPTS.openram_temp +"/"+ cell_name + ".sp" run_drc(cell_name, gds_name) run_lvs(cell_name, gds_name, sp_name) + from tech import drc pex_rules = drc["xrc_rules"] pex_runset = { 'pexRulesFile': pex_rules, @@ -175,18 +178,18 @@ def write_calibre_pex_script(cell_name, extract, output, final_verification): os.system("chmod u+x {}".format(run_file)) return pex_runset - + def run_drc(cell_name, gds_name, extract=False, final_verification=False): """Run DRC check on a given top-level name which is implemented in gds_name.""" - + global num_drc_runs num_drc_runs += 1 # Copy file to local dir if it isn't already if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(gds_name, OPTS.openram_temp) - + drc_runset = write_calibre_drc_script(cell_name, extract, final_verification) (outfile, errfile, resultsfile) = run_script(cell_name, "drc") @@ -209,12 +212,12 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False): # always display this summary if errors > 0: - debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, errors)) else: - debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, + debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name, geometries, rulechecks, errors)) @@ -225,10 +228,10 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): """Run LVS check on a given top-level name which is implemented in gds_name and sp_name. Final verification will ensure that there are no remaining virtual conections. """ - + global num_lvs_runs num_lvs_runs += 1 - + lvs_runset = write_calibre_lvs_script(cell_name, final_verification) # Copy file to local dir if it isn't already @@ -280,7 +283,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): # MRG - 9/26/17 - Change this to exclude warnings because of # multiple labels on different pins in column mux. ext_errors = len(exterrors) - ext_warnings = len(extwarnings) + ext_warnings = len(extwarnings) # also check the output file f = open(outfile, "r") @@ -297,16 +300,16 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): total_errors = summary_errors + out_errors + ext_errors if total_errors > 0: - debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, ext_errors)) else: - debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, + debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name, summary_errors, out_errors, ext_errors)) - + return total_errors @@ -317,10 +320,10 @@ def run_pex(cell_name, gds_name, sp_name, output=None, final_verification=False) global num_pex_runs num_pex_runs += 1 - write_calibre_pex_script() + write_calibre_pex_script(cell_name,True,output,final_verification) (outfile, errfile, resultsfile) = run_script(cell_name, "pex") - + # also check the output file f = open(outfile, "r") diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index fd082a45..63aeaabe 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -6,11 +6,11 @@ # All rights reserved. # """ -This is a DRC/LVS/PEX interface file for magic + netgen. +This is a DRC/LVS/PEX interface file for magic + netgen. We include the tech file for SCN4M_SUBM in the tech directory, -that is included in OpenRAM during DRC. -You can use this interactively by appending the magic system path in +that is included in OpenRAM during DRC. +You can use this interactively by appending the magic system path in your .magicrc file path sys /Users/mrg/openram/technology/scn3me_subm/tech @@ -33,7 +33,7 @@ num_drc_runs = 0 num_lvs_runs = 0 num_pex_runs = 0 - + def write_magic_script(cell_name, extract=False, final_verification=False): """ Write a magic script to perform DRC and optionally extraction. """ @@ -69,7 +69,7 @@ def write_magic_script(cell_name, extract=False, final_verification=False): if final_verification: f.write(pre+"extract unique all\n".format(cell_name)) f.write(pre+"extract\n".format(cell_name)) - #f.write(pre+"ext2spice hierarchy on\n") + #f.write(pre+"ext2spice hierarchy on\n") #f.write(pre+"ext2spice scale off\n") # lvs exists in 8.2.79, but be backword compatible for now #f.write(pre+"ext2spice lvs\n") @@ -82,18 +82,18 @@ def write_magic_script(cell_name, extract=False, final_verification=False): f.write(pre+"ext2spice blackbox on\n") f.write(pre+"ext2spice subcircuit top auto\n") f.write(pre+"ext2spice global off\n") - + # Can choose hspice, ngspice, or spice3, # but they all seem compatible enough. #f.write(pre+"ext2spice format ngspice\n") f.write(pre+"ext2spice {}\n".format(cell_name)) f.write("quit -noprompt\n") f.write("EOF\n") - + f.close() os.system("chmod u+x {}".format(run_file)) - + def write_netgen_script(cell_name): """ Write a netgen script to perform LVS. """ @@ -119,7 +119,7 @@ def write_netgen_script(cell_name): f.close() os.system("chmod u+x {}".format(run_file)) - + def run_drc(cell_name, gds_name, extract=True, final_verification=False): """Run DRC check on a cell which is implemented in gds_name.""" @@ -129,7 +129,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): # Copy file to local dir if it isn't already if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(gds_name, OPTS.openram_temp) - + # Copy .magicrc file into temp dir magic_file = OPTS.openram_tech + "mag_lib/.magicrc" if os.path.exists(magic_file): @@ -151,7 +151,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): f = open(outfile, "r") except FileNotFoundError: debug.error("Unable to load DRC results file from {}. Is magic set up?".format(outfile),1) - + results = f.readlines() f.close() errors=1 @@ -162,7 +162,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False): break else: debug.error("Unable to find the total error line in Magic output.",1) - + # always display this summary if errors > 0: @@ -189,19 +189,19 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): shutil.copy(gds_name, OPTS.openram_temp) if os.path.dirname(sp_name)!=OPTS.openram_temp.rstrip('/'): shutil.copy(sp_name, OPTS.openram_temp) - + write_netgen_script(cell_name) (outfile, errfile, resultsfile) = run_script(cell_name, "lvs") - + total_errors = 0 - + # check the result for these lines in the summary: try: f = open(resultsfile, "r") except FileNotFoundError: debug.error("Unable to load LVS results from {}".format(resultsfile),1) - + results = f.readlines() f.close() # Look for the results after the final "Subcircuit summary:" @@ -217,14 +217,14 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): test = re.compile("Property errors were found.") propertyerrors = list(filter(test.search, results)) total_errors += len(propertyerrors) - + # Require pins to match? # Cell pin lists for pnand2_1.spice and pnand2_1 altered to match. # test = re.compile(".*altered to match.") # pinerrors = list(filter(test.search, results)) # if len(pinerrors)>0: # debug.warning("Pins altered to match in {}.".format(cell_name)) - + #if len(propertyerrors)>0: # debug.warning("Property errors found, but not checking them.") @@ -232,7 +232,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): test = re.compile("Netlists do not match.") incorrect = list(filter(test.search, final_results)) total_errors += len(incorrect) - + # Netlists match uniquely. test = re.compile("match uniquely.") correct = list(filter(test.search, final_results)) @@ -244,7 +244,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): # Just print out the whole file, it is short. for e in results: debug.info(1,e.strip("\n")) - debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name,resultsfile)) + debug.error("{0}\tLVS mismatch (results in {1})".format(cell_name,resultsfile)) else: debug.info(1, "{0}\tLVS matches".format(cell_name)) @@ -257,9 +257,9 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): global num_pex_runs num_pex_runs += 1 - - debug.warning("PEX using magic not implemented.") - return 1 + #debug.warning("PEX using magic not implemented.") + #return 1 + os.chdir(OPTS.openram_temp) from tech import drc if output == None: @@ -271,25 +271,67 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): run_drc(name, gds_name) run_lvs(name, gds_name, sp_name) - """ - 2. magic can perform extraction with the following: - #!/bin/sh - rm -f $1.ext - rm -f $1.spice - magic -dnull -noconsole << EOF - tech load SCN3ME_SUBM.30 - #scalegrid 1 2 - gds rescale no - gds polygon subcell true - gds warning default - gds read $1 - extract - ext2spice scale off - ext2spice - quit -noprompt - EOF - """ - + # pex_fix did run the pex using a script while dev orignial method + # use batch mode. + # the dev old code using batch mode does not run and is split into functions + #pex_runset = write_batch_pex_rule(gds_name,name,sp_name,output) + pex_runset = write_script_pex_rule(gds_name,name,output) + + errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) + outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) + + # bash mode command from dev branch + #batch_cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe, + # OPTS.openram_temp, + # errfile, + # outfile) + script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset, + errfile, + outfile) + cmd = script_cmd + debug.info(2, cmd) + os.system(cmd) + + # rename technology models + pex_nelist = open(output, 'r') + s = pex_nelist.read() + pex_nelist.close() + s = s.replace('pfet','p') + s = s.replace('nfet','n') + f = open(output, 'w') + f.write(s) + f.close() + + # also check the output file + f = open(outfile, "r") + results = f.readlines() + f.close() + out_errors = find_error(results) + debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.") + + correct_port(name,output,sp_name) + return out_errors + +def write_batch_pex_rule(gds_name,name,sp_name,output): + """ + The dev branch old batch mode runset + 2. magic can perform extraction with the following: + #!/bin/sh + rm -f $1.ext + rm -f $1.spice + magic -dnull -noconsole << EOF + tech load SCN3ME_SUBM.30 + #scalegrid 1 2 + gds rescale no + gds polygon subcell true + gds warning default + gds read $1 + extract + ext2spice scale off + ext2spice + quit -noprompt + EOF + """ pex_rules = drc["xrc_rules"] pex_runset = { 'pexRulesFile': pex_rules, @@ -307,42 +349,89 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False): } # write the runset file - f = open(OPTS.openram_temp + "pex_runset", "w") - for k in sorted(pex_runset.iterkeys()): + file = OPTS.openram_temp + "pex_runset" + f = open(file, "w") + for k in sorted(pex_runset.keys()): f.write("*{0}: {1}\n".format(k, pex_runset[k])) f.close() + return file - # run pex - cwd = os.getcwd() - os.chdir(OPTS.openram_temp) - errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) - outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) +def write_script_pex_rule(gds_name,cell_name,output): + global OPTS + run_file = OPTS.openram_temp + "run_pex.sh" + f = open(run_file, "w") + f.write("#!/bin/sh\n") + f.write("{} -dnull -noconsole << eof\n".format(OPTS.drc_exe[1])) + f.write("gds polygon subcell true\n") + f.write("gds warning default\n") + f.write("gds read {}\n".format(gds_name)) + f.write("load {}\n".format(cell_name)) + f.write("select top cell\n") + f.write("expand\n") + f.write("port makeall\n") + extract = True + if not extract: + pre = "#" + else: + pre = "" + f.write(pre+"extract\n".format(cell_name)) + #f.write(pre+"ext2spice hierarchy on\n") + #f.write(pre+"ext2spice format ngspice\n") + #f.write(pre+"ext2spice renumber off\n") + #f.write(pre+"ext2spice scale off\n") + #f.write(pre+"ext2spice blackbox on\n") + f.write(pre+"ext2spice subcircuit top on\n") + #f.write(pre+"ext2spice global off\n") + f.write(pre+"ext2spice {}\n".format(cell_name)) + f.write("quit -noprompt\n") + f.write("eof\n") + f.write("mv {0}.spice {1}\n".format(cell_name,output)) - cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe, - OPTS.openram_temp, - errfile, - outfile) - debug.info(2, cmd) - os.system(cmd) - os.chdir(cwd) - - # also check the output file - f = open(outfile, "r") - results = f.readlines() f.close() + os.system("chmod u+x {}".format(run_file)) + return run_file +def find_error(results): # Errors begin with "ERROR:" test = re.compile("ERROR:") stdouterrors = list(filter(test.search, results)) for e in stdouterrors: debug.error(e.strip("\n")) - out_errors = len(stdouterrors) - - debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.") - return out_errors +def correct_port(name, output_file_name, ref_file_name): + pex_file = open(output_file_name, "r") + contents = pex_file.read() + # locate the start of circuit definition line + match = re.search(".subckt " + str(name) + ".*", contents) + match_index_start = match.start() + pex_file.seek(match_index_start) + rest_text = pex_file.read() + # locate the end of circuit definition line + match = re.search(r'\n', rest_text) + match_index_end = match.start() + # store the unchanged part of pex file in memory + pex_file.seek(0) + part1 = pex_file.read(match_index_start) + pex_file.seek(match_index_start + match_index_end) + part2 = pex_file.read() + pex_file.close() + + # obtain the correct definition line from the original spice file + sp_file = open(ref_file_name, "r") + contents = sp_file.read() + circuit_title = re.search(".SUBCKT " + str(name) + ".*\n", contents) + circuit_title = circuit_title.group() + sp_file.close() + + # write the new pex file with info in the memory + output_file = open(output_file_name, "w") + output_file.write(part1) + output_file.write(circuit_title) + output_file.write(part2) + output_file.close() + def print_drc_stats(): debug.info(1,"DRC runs: {0}".format(num_drc_runs)) def print_lvs_stats():