Added initial conversion from CSV to binned PM3

Signed-off-by: Grzegorz Latosinski <glatosinski@antmicro.com>
diff --git a/convert_pm3_to_csv.py b/convert_pm3_to_csv.py
index 2ff9521..a9889db 100644
--- a/convert_pm3_to_csv.py
+++ b/convert_pm3_to_csv.py
@@ -10,14 +10,15 @@
 import sys
 from prettytable import PrettyTable
 import json
+import clean_spice_files
 
 sourcetodests = defaultdict(list)
 
 RE_SUBCKT = re.compile(r'\.subckt\s+(?P<subcktname>[^ ]+)\s?(?P<nodenames>.*)')
 # RE_MODEL = re.compile(r'^[\.]*model\s+(?P<modelname>[^ ]+)\s+(?P<modeltype>[^ ]+)')
 
-RE_MODEL_END = re.compile(r'^[\s\n]*ends(?P<end>.*)(\n|$)')
-RE_SUBCKT_END = re.compile(r'^.ends')
+# RE_MODEL_END = re.compile(r'^[\s\n]*ends(?P<end>.*)(\n|$)')
+# RE_SUBCKT_END = re.compile(r'^.ends')
 RE_GROUP = re.compile(r'[*\s]*?(?P<group>[^*\n]+?)[*\s]*(\n|$)')
 RE_MODE_LINE = re.compile(r'(?P<modeid>\d+)\s*:\s*type=\s*(?P<modetype>.*)(\n|$)')
 RE_INLINE_SUBCKT_MODEL = re.compile(r'^(?P<subcktname>[^ ]+)\s*\((?P<nodenames>.*)\)\s*(?P<modelname>[^ ]+)\s*(?P<parameters>.*)($|\n)')
@@ -27,26 +28,99 @@
 def order_parameters(paramname):
     priority = {
         'subckt': 0,
-        'nodes': 1,
-        'model': 2,
-        'type': 3,
-        'mode': 4,
-        'modetype': 5,
-        'lmin': 6,
-        'lmax': 7,
-        'wmin': 8,
-        'wmax': 9,
-        'level': 10,
-        'tnom': 11,
-        'version': 12,
-        'tox': 13,
-        'toxm': 14,
+        'inlinesubckt': 1,
+        'nodes': 2,
+        'model': 3,
+        'type': 4,
+        'mode': 5,
+        'modetype': 6,
+        'lmin': 7,
+        'lmax': 8,
+        'wmin': 9,
+        'wmax': 10,
+        'level': 11,
+        'tnom': 12,
+        'version': 13,
+        'tox': 14,
+        'toxm': 15,
     }
     if paramname in priority:
         return (priority[paramname], paramname)
     return (1000, paramname)
 
 
+def csv_to_pm3(parameters, csventry):
+    # we have following orders
+    # inlinesubckt nodes model type
+    # inlinesubckt nodes model type mode modetype
+    # model type
+    # subckt nodes
+    # subckt nodes model type
+    result = []
+    if parameters[0] == 'model':
+        for entry in csventry:
+            assert 'N/A' not in entry[:2], entry
+            result.append(f'.model {entry[0]} {entry[1]}')
+            for parameter, value in zip(parameters[2:], entry[2:]):
+                if value != 'N/A':
+                    result.append(f'+{parameter}= {value}')
+    elif parameters[0] == 'subckt':
+        lastsubckt = None
+        firstsubckt = True
+        for entry in csventry:
+            assert 'N/A' not in entry[:2], entry
+            if lastsubckt != entry[0]:
+                if not firstsubckt:
+                    result.append('.ends')
+                firstsubckt = False
+                result.append(f'.subckt {entry[0]} {" ".join(entry[1].split(","))}')
+                lastsubckt = entry[0]
+            startpoint = 2
+            if entry[2] == 'model':
+                assert 'N/A' not in entry[2:4], entry
+                result.append(f'.model {entry[2]} {entry[3]}')
+                startpoint = 4
+            for parameter, value in zip(parameters[startpoint:], entry[startpoint:]):
+                if value != 'N/A':
+                    result.append(f'+{parameter}= {value}')
+        if not firstsubckt:
+            result.append('.ends')
+    elif parameters[0] == 'inlinesubckt':
+        lastsubcktdef = None
+        lastmodel = None
+        firstsubcktdef = True
+        firstmodel = True
+        for entry in csventry:
+            assert 'N/A' not in entry[:4], entry
+            if not entry[0].endswith('_dummy') and entry[0] != lastsubcktdef:
+                if not firstsubcktdef:
+                    result.append(f'ends {lastsubcktdef}')
+                lastsubcktdef = entry[0]
+                firstsubcktdef = False
+                result.append(f'inline subckt {entry[0]} ({" ".join(entry[1].split(","))})')
+            if 'mode' in set(parameters):
+                if entry[2] != lastmodel:
+                    if not firstmodel:
+                        result.append('}')
+                    firstmodel = False
+                    result.append(f'model {entry[2]} {entry[3]} {{')
+                    lastmodel = entry[2]
+                result.append(f'{entry[4]}: type={entry[5]}')
+                for parameter, value in zip(parameters[6:], entry[6:]):
+                    result.append(f'+{parameter}= {value}')
+            else:
+                result.append(f'{entry[0]} ({" ".join(entry[1].split(","))}) {entry[2]}')
+                for parameter, value in zip(parameters[3:], entry[3:]):
+                    if value != 'N/A':
+                        result.append(f'+{parameter}= {value}')
+        if not firstmodel:
+            result.append('}')
+        if not firstsubcktdef:
+            result.append(f'ends {lastsubcktdef}')
+    result = clean_spice_files.basic_cleaning(result)
+    result = clean_spice_files.makefixedwidthcolumn(result)
+    return result
+
 def pm3_to_csv(lines):
     result = []
     params = {}
@@ -54,6 +128,7 @@
     params['model'] = {}
     params['mode'] = {}
     scopetype = None
+    leftovers = []
     def produce_line():
         finparams = {}
         finparams.update(params['subckt'])
@@ -66,6 +141,7 @@
         try:
             if not line.strip():
                 continue
+            origline = line
             line = line.strip()
             elements = shlex.split(line, posix=False)
             if line.startswith('*'):
@@ -153,14 +229,17 @@
                     params['model'] = {}
                     params['mode'] = {}
                     scopetype = 'subckt'
-                    params['subckt']['subckt'] = m.group('subcktname')
+                    params['subckt']['inlinesubckt'] = m.group('subcktname')
                     params['subckt']['nodes'] = ','.join(m.group('nodenames').split(' '))
                     params['model']['model'] = m.group('modelname')
-                    params['model']['type'] = params['subckt']['subckt']
+                    params['model']['type'] = params['subckt']['inlinesubckt']
                     elements = m.group('parameters').split()
                     parse_parameters()
                 else:
-                    print(colored(line, 'yellow'), file=sys.stderr)
+                    if not line.strip().startswith('ends') and not line.strip().startswith('inline subckt'):
+                        if scopetype is None or not line.strip() in ['{', '}']:
+                            leftovers.append(origline.rstrip())
+                            print(colored(line, 'yellow'), file=sys.stderr)
         except:
             print(colored(line, 'red'), file=sys.stderr)
             raise
@@ -182,7 +261,7 @@
                 row.append('N/A')
         csvcontent.append(row)
 
-    return csvcontent
+    return csvcontent, leftovers
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser()
@@ -218,7 +297,7 @@
             for line in data.readlines():
                 lines.append(line)
 
-            result = pm3_to_csv(lines)
+            result, leftovers = pm3_to_csv(lines)
             if len(result) > 1:
                 with open(filename.with_suffix('.csv'), 'w', newline='') as csvfile:
                     writer = csv.writer(csvfile, delimiter=';')
@@ -233,5 +312,13 @@
                         t.add_row(line)
                     tablefile.write(str(t))
                     sourcetodests[str(filename)].append(str(Path(filename).with_suffix('.table')))
+                # print(colored('\n'.join(csv_to_pm3(result[0], result[1:])), 'green'))
+                binnedspice = csv_to_pm3(result[0], result[1:])
+                csv2bsimname = filename.with_suffix(f'.csv2bsim{filename.suffix}')
+                with open(csv2bsimname, 'w') as csv2bsimfile:
+                    csv2bsimfile.write('\n'.join(binnedspice))
+                with open(filename.with_suffix(f'.csvwrap{filename.suffix}'), 'w') as csvwrapfile:
+                    leftovers.append(f'.include "{csv2bsimname}"')
+                    csvwrapfile.write('\n'.join(leftovers))
     with open(args.sourcetodests, 'w') as srctodst:
         json.dump(sourcetodests, srctodst, indent=2)