Added scripts to convert the statistics blocks to monte carlo parameters that are compatible with ngspice syntax. 1st attempt; application of the scripts is commented out in the makefile. Under test and review.
diff --git a/VERSION b/VERSION index 9730da8..5ea67cd 100644 --- a/VERSION +++ b/VERSION
@@ -1 +1 @@ -1.0.154 +1.0.155
diff --git a/sky130/Makefile.in b/sky130/Makefile.in index 71af99c..34354fa 100644 --- a/sky130/Makefile.in +++ b/sky130/Makefile.in
@@ -747,6 +747,17 @@ patch -p2 -f -d ${STAGING_PATH}/${SKY130A}/libs.tech/ngspice \ < custom/patches/sky130_fd_pr_5.patch \ 2>&1 | tee -a ${SKY130A}_make.log || true + # Custom: Parse the (commented out) statistics blocks and generate + # ngspice-compatible monte carlo parameters for mismatch and process + # (currently under test) + # ./custom/scripts/mismatch_params.py \ + # 2>&1 | tee -a ${SKY130A}_make.log || true + # ./custom/scripts/mismatch_params.py \ + # ${STAGING_PATH}/${SKY130A}/libs.tech/ngspice \ + # 2>&1 | tee -a ${SKY130A}_make.log || true + # ./custom/scripts/process_params.py \ + # 2>&1 | tee -a ${SKY130A}_make.log || true + # Custom: Add "spinit" file cat ./custom/models/spinit >> \ ${STAGING_PATH}/${SKY130A}/libs.tech/ngspice/spinit
diff --git a/sky130/custom/scripts/mismatch_params.py b/sky130/custom/scripts/mismatch_params.py new file mode 100755 index 0000000..1cb5ad8 --- /dev/null +++ b/sky130/custom/scripts/mismatch_params.py
@@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# +#-------------------------------------------------------------------- +# Parse the SPICE model files from sky130 and use the (commented out) +# statistics block to generate the correct monte carlo expressions +# in the device models for mismatch. +#-------------------------------------------------------------------- + +import os +import re +import sys + +mm_switch_param = 'MC_MM_SWITCH' + +walkpath = 'sky130A/libs.ref/sky130_fd_pr/spice' + +if len(sys.argv) > 1: + walkpath = sys.argv[1] + +mismatch_params = [] + +mmrex = re.compile('^\*[ \t]*mismatch[ \t]+\{') +endrex = re.compile('^\*[ \t]*\}') + +filelist = [] + +#-------------------------------------------------------------------- +# Step 1. Gather variables +#-------------------------------------------------------------------- + +for dirpath, dirnames, filenames in os.walk(walkpath): + for filename in filenames: + if os.path.splitext(filename)[1] == '.spice': + infile_name = os.path.join(dirpath, filename) + filelist.append(infile_name) + + infile = open(infile_name, 'r') + + state = 'before_mismatch' + line_number = 0 + replaced_something = False + + for line in infile: + line_number += 1 + + if state == 'before_mismatch': + mmatch = mmrex.match(line) + if mmatch: + state = 'in_mismatch' + elif state == 'in_mismatch': + ematch = endrex.match(line) + if ematch: + state = 'after_mismatch' + else: + tokens = line.split() + if 'vary' in tokens: + if ('dist=gauss' in tokens) or ('gauss' in tokens): + mismatch_param = tokens[2] + std_dev = float(tokens[-1].split('=')[-1]) + replacement = '{}*AGAUSS(0,{!s},1)'.format(mm_switch_param, std_dev) + mismatch_params.append((mismatch_param, replacement)) + + infile.close() + +print('') +print('Mismatch parameters found:') +for (mismatch_param, replacement) in mismatch_params: + print(mismatch_param + ' : ' + replacement) +print('') + +#-------------------------------------------------------------------- +# Step 2. Make replacements +#-------------------------------------------------------------------- + +for infile_name in filelist: + + filepath = os.path.split(infile_name)[0] + outfile_name = os.path.join(filepath, 'temp') + + infile = open(infile_name, 'r') + outfile = open(outfile_name, 'w') + + state = 'before_mismatch' + line_number = 0 + replaced_something = False + + for line in infile: + line_number += 1 + + if state == 'before_mismatch': + outfile.write(line) + mmatch = mmrex.match(line) + if mmatch: + state = 'in_mismatch' + elif state == 'in_mismatch': + outfile.write(line) + ematch = endrex.match(line) + if ematch: + state = 'after_mismatch' + elif state == 'after_mismatch': + newline = line + for (param, replacement) in mismatch_params: + if param in newline: + newline = newline.replace(param, replacement) + replaced_something = True + print(" Line {}: Found mismatch parameter '{}' and replaced with '{}'.".format(line_number, param, replacement)) + outfile.write(newline) + + infile.close() + outfile.close() + if replaced_something: + print("Something was replaced in '{}', backed up original file and replaced with processed one.".format(infile_name)) + os.rename(infile_name, infile_name + '.orig') + os.rename(outfile_name, infile_name) + else: + print("Nothing was replaced in '{}'.".format(infile_name)) + os.remove(outfile_name) +
diff --git a/sky130/custom/scripts/process_params.py b/sky130/custom/scripts/process_params.py new file mode 100755 index 0000000..1e59136 --- /dev/null +++ b/sky130/custom/scripts/process_params.py
@@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +# +#-------------------------------------------------------------------- +# Parse the SPICE model files from sky130 and use the (commented out) +# statistics block to generate the correct monte carlo expressions +# in the parameters for process variation. +#-------------------------------------------------------------------- + +import os +import re +import sys + +pr_switch_param = 'MC_PR_SWITCH' + +walkpath = 'sky130A/libs.tech/ngspice' + +if len(sys.argv) > 1: + walkpath = sys.argv[1] + +process_params = [] + +parmrex = re.compile('^\.param[ \t]+') +prrex = re.compile('^\*[ \t]*process[ \t]+\{') +endrex = re.compile('^\*[ \t]*\}') + +filelist = [] + +#-------------------------------------------------------------------- +# Step 1. Gather variables +#-------------------------------------------------------------------- + +for dirpath, dirnames, filenames in os.walk(walkpath): + for filename in filenames: + if os.path.splitext(filename)[1] == '.spice': + infile_name = os.path.join(dirpath, filename) + filelist.append(infile_name) + + infile = open(infile_name, 'r') + + state = 'before_process' + line_number = 0 + replaced_something = False + + for line in infile: + line_number += 1 + + if state == 'before_process': + pmatch = prrex.match(line) + if pmatch: + state = 'in_process' + elif state == 'in_process': + ematch = endrex.match(line) + if ematch: + state = 'after_process' + else: + tokens = line.split() + if 'vary' in tokens: + if ('dist=gauss' in tokens) or ('gauss' in tokens): + process_param = tokens[2] + std_dev = float(tokens[-1].split('=')[-1]) + replacement = ' + {}*AGAUSS(0,{!s},1)'.format(pr_switch_param, std_dev) + process_params.append((process_param, replacement)) + + infile.close() + +print('') +print('Process parameters found:') +for (process_param, addendum) in process_params: + print(process_param + ' :' + addendum) +print('') + +#-------------------------------------------------------------------- +# Step 2. Make replacements +#-------------------------------------------------------------------- + +for infile_name in filelist: + + filepath = os.path.split(infile_name)[0] + outfile_name = os.path.join(filepath, 'temp') + + infile = open(infile_name, 'r') + outfile = open(outfile_name, 'w') + + state = 'before_process' + line_number = 0 + replaced_something = False + + for line in infile: + line_number += 1 + + if state == 'before_process': + pmatch = prrex.match(line) + if pmatch: + state = 'in_process' + outfile.write(line) + else: + pmatch = parmrex.match(line) + if pmatch: + newline = line + for (param, replacement) in process_params: + if param in newline: + newline = newline.strip('\n') + replacement + '\n' + replaced_something = True + print(" Line {}: Found process parameter '{}' and appended '{}'.".format(line_number, param, replacement)) + outfile.write(newline) + else: + outfile.write(line) + elif state == 'in_process': + outfile.write(line) + ematch = endrex.match(line) + if ematch: + state = 'after_process' + elif state == 'after_process': + outfile.write(line) + + infile.close() + outfile.close() + if replaced_something: + print("Something was replaced in '{}', backed up original file and replaced with processed one.".format(infile_name)) + os.rename(infile_name, infile_name + '.orig') + os.rename(outfile_name, infile_name) + else: + print("Nothing was replaced in '{}'.".format(infile_name)) + os.remove(outfile_name) +