add pex function for magic and openram test

This commit is contained in:
Bin Wu 2019-06-25 11:24:25 -07:00
parent 91febec3a2
commit 3f3ee9b885
2 changed files with 185 additions and 85 deletions

View File

@ -16,7 +16,7 @@ import debug
class openram_test(unittest.TestCase): class openram_test(unittest.TestCase):
""" Base unit test that we have some shared classes in. """ """ Base unit test that we have some shared classes in. """
def local_drc_check(self, w): def local_drc_check(self, w):
self.reset() self.reset()
@ -31,11 +31,11 @@ class openram_test(unittest.TestCase):
if OPTS.purge_temp: if OPTS.purge_temp:
self.cleanup() self.cleanup()
def local_check(self, a, final_verification=False): def local_check(self, a, final_verification=False):
self.reset() self.reset()
tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name) tempspice = "{0}{1}.sp".format(OPTS.openram_temp,a.name)
tempgds = "{0}{1}.gds".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) #shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
self.fail("DRC failed: {}".format(a.name)) self.fail("DRC failed: {}".format(a.name))
result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification) result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification)
if result != 0: if result != 0:
#zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid()) #zip_file = "/tmp/{0}_{1}".format(a.name,os.getpid())
@ -63,6 +63,17 @@ class openram_test(unittest.TestCase):
if OPTS.purge_temp: if OPTS.purge_temp:
self.cleanup() 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): 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. """Creates a delay simulation to determine a feasible period for the functional tests to run.
@ -75,19 +86,19 @@ class openram_test(unittest.TestCase):
delay_obj.create_signal_names() delay_obj.create_signal_names()
delay_obj.create_measurement_names() delay_obj.create_measurement_names()
delay_obj.create_measurement_objects() delay_obj.create_measurement_objects()
delay_obj.find_feasible_period_one_port(test_port) delay_obj.find_feasible_period_one_port(test_port)
return delay_obj.period return delay_obj.period
def cleanup(self): def cleanup(self):
""" Reset the duplicate checker and cleanup files. """ """ Reset the duplicate checker and cleanup files. """
files = glob.glob(OPTS.openram_temp + '*') files = glob.glob(OPTS.openram_temp + '*')
for f in files: for f in files:
# Only remove the files # Only remove the files
if os.path.isfile(f): if os.path.isfile(f):
os.remove(f) os.remove(f)
def reset(self): def reset(self):
""" """
Reset everything after each test. Reset everything after each test.
""" """
# Reset the static duplicate name checker for unit tests. # Reset the static duplicate name checker for unit tests.
@ -116,7 +127,7 @@ class openram_test(unittest.TestCase):
data_string=pprint.pformat(data) data_string=pprint.pformat(data)
debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string) debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string)
return data_matches return data_matches
def isclose(self,key,value,actual_value,error_tolerance=1e-2): def isclose(self,key,value,actual_value,error_tolerance=1e-2):
@ -132,7 +143,7 @@ class openram_test(unittest.TestCase):
return False return False
def relative_diff(self, value1, value2): 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.""" If largest value is 0, just return the difference."""
# Edge case to avoid divide by zero # Edge case to avoid divide by zero
@ -148,7 +159,7 @@ class openram_test(unittest.TestCase):
# Edge case where greater is a zero # Edge case where greater is a zero
if norm_value == 0: if norm_value == 0:
min_value = abs(min(value1, value2)) min_value = abs(min(value1, value2))
return abs(value1 - value2) / norm_value return abs(value1 - value2) / norm_value
@ -162,15 +173,15 @@ class openram_test(unittest.TestCase):
"""Compare two files. """Compare two files.
Arguments: Arguments:
filename1 -- First file name filename1 -- First file name
filename2 -- Second file name filename2 -- Second file name
Return value: Return value:
True if the files are the same, False otherwise. True if the files are the same, False otherwise.
""" """
import re import re
import debug import debug
@ -203,7 +214,7 @@ class openram_test(unittest.TestCase):
debug.info(3,"line1_floats: "+str(line1_floats)) debug.info(3,"line1_floats: "+str(line1_floats))
debug.info(3,"line2_floats: "+str(line2_floats)) debug.info(3,"line2_floats: "+str(line2_floats))
# 2. Remove the floats from the string # 2. Remove the floats from the string
for f in line1_floats: for f in line1_floats:
line1=line1.replace(f,"",1) line1=line1.replace(f,"",1)
@ -215,10 +226,10 @@ class openram_test(unittest.TestCase):
# 3. Convert to floats rather than strings # 3. Convert to floats rather than strings
line1_floats = [float(x) for x in line1_floats] line1_floats = [float(x) for x in line1_floats]
line2_floats = [float(x) for x in line1_floats] line2_floats = [float(x) for x in line1_floats]
# 4. Check if remaining string matches # 4. Check if remaining string matches
if line1 != line2: 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 line1]))
#print(str([i for i in line2])) #print(str([i for i in line2]))
if mismatches==0: if mismatches==0:
@ -281,13 +292,13 @@ class openram_test(unittest.TestCase):
debug.info(2,"MATCH {0} {1}".format(filename1,filename2)) debug.info(2,"MATCH {0} {1}".format(filename1,filename2))
return True return True
def header(filename, technology): def header(filename, technology):
# Skip the header for gitlab regression # Skip the header for gitlab regression
import getpass import getpass
if getpass.getuser() == "gitlab-runner": if getpass.getuser() == "gitlab-runner":
return return
tst = "Running Test for:" tst = "Running Test for:"
print("\n") print("\n")
print(" ______________________________________________________________________________ ") print(" ______________________________________________________________________________ ")

View File

@ -6,11 +6,11 @@
# All rights reserved. # 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, We include the tech file for SCN4M_SUBM in the tech directory,
that is included in OpenRAM during DRC. that is included in OpenRAM during DRC.
You can use this interactively by appending the magic system path in You can use this interactively by appending the magic system path in
your .magicrc file your .magicrc file
path sys /Users/mrg/openram/technology/scn3me_subm/tech path sys /Users/mrg/openram/technology/scn3me_subm/tech
@ -33,7 +33,7 @@ num_drc_runs = 0
num_lvs_runs = 0 num_lvs_runs = 0
num_pex_runs = 0 num_pex_runs = 0
def write_magic_script(cell_name, extract=False, final_verification=False): def write_magic_script(cell_name, extract=False, final_verification=False):
""" Write a magic script to perform DRC and optionally extraction. """ """ 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: if final_verification:
f.write(pre+"extract unique all\n".format(cell_name)) f.write(pre+"extract unique all\n".format(cell_name))
f.write(pre+"extract\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") #f.write(pre+"ext2spice scale off\n")
# lvs exists in 8.2.79, but be backword compatible for now # lvs exists in 8.2.79, but be backword compatible for now
#f.write(pre+"ext2spice lvs\n") #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 blackbox on\n")
f.write(pre+"ext2spice subcircuit top auto\n") f.write(pre+"ext2spice subcircuit top auto\n")
f.write(pre+"ext2spice global off\n") f.write(pre+"ext2spice global off\n")
# Can choose hspice, ngspice, or spice3, # Can choose hspice, ngspice, or spice3,
# but they all seem compatible enough. # but they all seem compatible enough.
#f.write(pre+"ext2spice format ngspice\n") #f.write(pre+"ext2spice format ngspice\n")
f.write(pre+"ext2spice {}\n".format(cell_name)) f.write(pre+"ext2spice {}\n".format(cell_name))
f.write("quit -noprompt\n") f.write("quit -noprompt\n")
f.write("EOF\n") f.write("EOF\n")
f.close() f.close()
os.system("chmod u+x {}".format(run_file)) os.system("chmod u+x {}".format(run_file))
def write_netgen_script(cell_name): def write_netgen_script(cell_name):
""" Write a netgen script to perform LVS. """ """ Write a netgen script to perform LVS. """
@ -119,7 +119,7 @@ def write_netgen_script(cell_name):
f.close() f.close()
os.system("chmod u+x {}".format(run_file)) os.system("chmod u+x {}".format(run_file))
def run_drc(cell_name, gds_name, extract=True, final_verification=False): def run_drc(cell_name, gds_name, extract=True, final_verification=False):
"""Run DRC check on a cell which is implemented in gds_name.""" """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 # Copy file to local dir if it isn't already
if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'): if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'):
shutil.copy(gds_name, OPTS.openram_temp) shutil.copy(gds_name, OPTS.openram_temp)
# Copy .magicrc file into temp dir # Copy .magicrc file into temp dir
magic_file = OPTS.openram_tech + "mag_lib/.magicrc" magic_file = OPTS.openram_tech + "mag_lib/.magicrc"
if os.path.exists(magic_file): 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") f = open(outfile, "r")
except FileNotFoundError: except FileNotFoundError:
debug.error("Unable to load DRC results file from {}. Is magic set up?".format(outfile),1) debug.error("Unable to load DRC results file from {}. Is magic set up?".format(outfile),1)
results = f.readlines() results = f.readlines()
f.close() f.close()
errors=1 errors=1
@ -162,7 +162,7 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False):
break break
else: else:
debug.error("Unable to find the total error line in Magic output.",1) debug.error("Unable to find the total error line in Magic output.",1)
# always display this summary # always display this summary
if errors > 0: 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) shutil.copy(gds_name, OPTS.openram_temp)
if os.path.dirname(sp_name)!=OPTS.openram_temp.rstrip('/'): if os.path.dirname(sp_name)!=OPTS.openram_temp.rstrip('/'):
shutil.copy(sp_name, OPTS.openram_temp) shutil.copy(sp_name, OPTS.openram_temp)
write_netgen_script(cell_name) write_netgen_script(cell_name)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs") (outfile, errfile, resultsfile) = run_script(cell_name, "lvs")
total_errors = 0 total_errors = 0
# check the result for these lines in the summary: # check the result for these lines in the summary:
try: try:
f = open(resultsfile, "r") f = open(resultsfile, "r")
except FileNotFoundError: except FileNotFoundError:
debug.error("Unable to load LVS results from {}".format(resultsfile),1) debug.error("Unable to load LVS results from {}".format(resultsfile),1)
results = f.readlines() results = f.readlines()
f.close() f.close()
# Look for the results after the final "Subcircuit summary:" # 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.") test = re.compile("Property errors were found.")
propertyerrors = list(filter(test.search, results)) propertyerrors = list(filter(test.search, results))
total_errors += len(propertyerrors) total_errors += len(propertyerrors)
# Require pins to match? # Require pins to match?
# Cell pin lists for pnand2_1.spice and pnand2_1 altered to match. # Cell pin lists for pnand2_1.spice and pnand2_1 altered to match.
# test = re.compile(".*altered to match.") # test = re.compile(".*altered to match.")
# pinerrors = list(filter(test.search, results)) # pinerrors = list(filter(test.search, results))
# if len(pinerrors)>0: # if len(pinerrors)>0:
# debug.warning("Pins altered to match in {}.".format(cell_name)) # debug.warning("Pins altered to match in {}.".format(cell_name))
#if len(propertyerrors)>0: #if len(propertyerrors)>0:
# debug.warning("Property errors found, but not checking them.") # 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.") test = re.compile("Netlists do not match.")
incorrect = list(filter(test.search, final_results)) incorrect = list(filter(test.search, final_results))
total_errors += len(incorrect) total_errors += len(incorrect)
# Netlists match uniquely. # Netlists match uniquely.
test = re.compile("match uniquely.") test = re.compile("match uniquely.")
correct = list(filter(test.search, final_results)) 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. # Just print out the whole file, it is short.
for e in results: for e in results:
debug.info(1,e.strip("\n")) 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: else:
debug.info(1, "{0}\tLVS matches".format(cell_name)) 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 global num_pex_runs
num_pex_runs += 1 num_pex_runs += 1
#debug.warning("PEX using magic not implemented.")
debug.warning("PEX using magic not implemented.") #return 1
return 1 os.chdir(OPTS.openram_temp)
from tech import drc from tech import drc
if output == None: 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_drc(name, gds_name)
run_lvs(name, gds_name, sp_name) run_lvs(name, gds_name, sp_name)
""" # pex_fix did run the pex using a script while dev orignial method
2. magic can perform extraction with the following: # use batch mode.
#!/bin/sh # the dev old code using batch mode does not run and is split into functions
rm -f $1.ext #pex_runset = write_batch_pex_rule(gds_name,name,sp_name,output)
rm -f $1.spice pex_runset = write_script_pex_rule(gds_name,name,output)
magic -dnull -noconsole << EOF
tech load SCN3ME_SUBM.30 errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name)
#scalegrid 1 2 outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name)
gds rescale no
gds polygon subcell true # bash mode command from dev branch
gds warning default #batch_cmd = "{0} -gui -pex {1}pex_runset -batch 2> {2} 1> {3}".format(OPTS.pex_exe,
gds read $1 # OPTS.openram_temp,
extract # errfile,
ext2spice scale off # outfile)
ext2spice script_cmd = "{0} 2> {1} 1> {2}".format(pex_runset,
quit -noprompt errfile,
EOF 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_rules = drc["xrc_rules"]
pex_runset = { pex_runset = {
'pexRulesFile': pex_rules, 'pexRulesFile': pex_rules,
@ -307,42 +349,89 @@ def run_pex(name, gds_name, sp_name, output=None, final_verification=False):
} }
# write the runset file # write the runset file
f = open(OPTS.openram_temp + "pex_runset", "w") file = OPTS.openram_temp + "pex_runset"
for k in sorted(pex_runset.iterkeys()): f = open(file, "w")
for k in sorted(pex_runset.keys()):
f.write("*{0}: {1}\n".format(k, pex_runset[k])) f.write("*{0}: {1}\n".format(k, pex_runset[k]))
f.close() f.close()
return file
# run pex def write_script_pex_rule(gds_name,cell_name,output):
cwd = os.getcwd() global OPTS
os.chdir(OPTS.openram_temp) run_file = OPTS.openram_temp + "run_pex.sh"
errfile = "{0}{1}.pex.err".format(OPTS.openram_temp, name) f = open(run_file, "w")
outfile = "{0}{1}.pex.out".format(OPTS.openram_temp, name) 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() f.close()
os.system("chmod u+x {}".format(run_file))
return run_file
def find_error(results):
# Errors begin with "ERROR:" # Errors begin with "ERROR:"
test = re.compile("ERROR:") test = re.compile("ERROR:")
stdouterrors = list(filter(test.search, results)) stdouterrors = list(filter(test.search, results))
for e in stdouterrors: for e in stdouterrors:
debug.error(e.strip("\n")) debug.error(e.strip("\n"))
out_errors = len(stdouterrors) out_errors = len(stdouterrors)
debug.check(os.path.isfile(output),"Couldn't find PEX extracted output.")
return out_errors 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(): def print_drc_stats():
debug.info(1,"DRC runs: {0}".format(num_drc_runs)) debug.info(1,"DRC runs: {0}".format(num_drc_runs))
def print_lvs_stats(): def print_lvs_stats():