Adding tool to generate blackbox verilog output.
diff --git a/scripts/python-skywater-pdk/generate_verilog_blackbox.py b/scripts/python-skywater-pdk/generate_verilog_blackbox.py
new file mode 100755
index 0000000..82d7257
--- /dev/null
+++ b/scripts/python-skywater-pdk/generate_verilog_blackbox.py
@@ -0,0 +1,780 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020 The SkyWater PDK Authors.
+#
+# Use of this source code is governed by the Apache 2.0
+# license that can be found in the LICENSE file or at
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# SPDX-License-Identifier: Apache-2.0
+
+
+import json
+import os
+import pprint
+import re
+import sys
+import textwrap
+
+
+copyright_header = """\
+// Copyright 2020 The SkyWater PDK Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// SPDX-License-Identifier: Apache-2.0
+"""
+
+from skywater_pdk.utils import OrderedEnum
+
+class PortGroup(OrderedEnum):
+    DATA_IN_CHAR   = 10  # A, A1, D, etc
+    DATA_IN_WORD   = 11  # IN
+
+    DATA_OUT_CHAR  = 20  # X, Y, Q
+    DATA_OUT_WORD  = 21  # OUT
+
+    DATA_IO_CHAR   = 30  # Inout?
+    DATA_IO_WORD   = 31  # Inout?
+
+    DATA_CONTROL   = 40  # SET / RESET / etc
+
+    SCAN_CHAIN     = 44  # SCD, SCE, etc
+
+    CLOCK          = 50  # Clock
+    CLOCK_CONTROL  = 51  # Clock enable
+
+    POWER_CONTROL  = 78  # SLEEP
+    POWER_OTHER    = 79  # KAPWR
+    POWER_POSITIVE = 80  # VPWR
+    POWER_NEGATIVE = 81  # VGND
+
+    @property
+    def type(self):
+        pg = self
+        if pg in (self.DATA_IN_CHAR,  self.DATA_IN_WORD,
+                  self.DATA_OUT_CHAR, self.DATA_OUT_WORD,
+                  self.DATA_IO_CHAR,  self.DATA_IO_WORD,):
+            return 'data'
+        if pg in (self.DATA_CONTROL,):
+            return 'control'
+        if pg in (self.CLOCK, self.CLOCK_CONTROL,):
+            return 'clocking'
+        if pg in (self.SCAN_CHAIN,):
+            return 'scanchain'
+        if pg in (self.POWER_CONTROL,  self.POWER_OTHER,
+                  self.POWER_POSITIVE, self.POWER_NEGATIVE,):
+            return 'power'
+        assert False, self
+
+    @property
+    def desc(self):
+        pg = self
+        if pg in (self.DATA_IN_CHAR,  self.DATA_IN_WORD,
+                  self.DATA_OUT_CHAR, self.DATA_OUT_WORD,
+                  self.DATA_IO_CHAR,  self.DATA_IO_WORD,):
+            return 'Data Signals'
+        if pg in (self.DATA_CONTROL,):
+            return 'Control Signals'
+        if pg in (self.SCAN_CHAIN,):
+            return 'Scan Chain'
+        if pg in (self.CLOCK, self.CLOCK_CONTROL,):
+            return 'Clocking'
+        if pg in (self.POWER_CONTROL,  self.POWER_OTHER,
+                  self.POWER_POSITIVE, self.POWER_NEGATIVE,):
+            return 'Power'
+        assert False, self
+
+
+    @classmethod
+    def classify(cls, name, modname=None):
+        """
+
+        Data Input Signals
+        ++++++++++++++++++
+
+        >>> PortGroup.classify('A')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('A1')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('A1_N')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('A_N')
+        <PortGroup.DATA_IN_CHAR: 10>
+
+        >>> PortGroup.classify('B')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('B1')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('B1_N')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('B_N')
+        <PortGroup.DATA_IN_CHAR: 10>
+
+        >>> PortGroup.classify('J')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('K')
+        <PortGroup.DATA_IN_CHAR: 10>
+
+        >>> PortGroup.classify('IN')
+        <PortGroup.DATA_IN_WORD: 11>
+
+        >>> PortGroup.classify('D')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('D_N')
+        <PortGroup.DATA_IN_CHAR: 10>
+
+        >>> PortGroup.classify('P0')
+        <PortGroup.DATA_IN_CHAR: 10>
+        >>> PortGroup.classify('N1')
+        <PortGroup.DATA_IN_CHAR: 10>
+
+        >>> PortGroup.classify('CI')
+        <PortGroup.DATA_IN_WORD: 11>
+        >>> PortGroup.classify('CIN')
+        <PortGroup.DATA_IN_WORD: 11>
+
+        SCD
+        Scan Chain Data
+        >>> PortGroup.classify('SCD')
+        <PortGroup.SCAN_CHAIN: 44>
+
+        SCE
+        Scan Chain Enable
+        >>> PortGroup.classify('SCE')
+        <PortGroup.SCAN_CHAIN: 44>
+
+        Data Output Signals
+        +++++++++++++++++++
+
+        >>> PortGroup.classify('X')
+        <PortGroup.DATA_OUT_CHAR: 20>
+        >>> PortGroup.classify('X_N')
+        <PortGroup.DATA_OUT_CHAR: 20>
+
+        >>> PortGroup.classify('Y')
+        <PortGroup.DATA_OUT_CHAR: 20>
+        >>> PortGroup.classify('Y_N')
+        <PortGroup.DATA_OUT_CHAR: 20>
+
+        >>> PortGroup.classify('Z')
+        <PortGroup.DATA_OUT_CHAR: 20>
+        >>> PortGroup.classify('Z_N')
+        <PortGroup.DATA_OUT_CHAR: 20>
+
+        >>> PortGroup.classify('Q')
+        <PortGroup.DATA_OUT_CHAR: 20>
+
+        >>> PortGroup.classify('OUT')
+        <PortGroup.DATA_OUT_WORD: 21>
+
+        >>> PortGroup.classify('HI')
+        <PortGroup.DATA_OUT_WORD: 21>
+        >>> PortGroup.classify('LO')
+        <PortGroup.DATA_OUT_WORD: 21>
+
+        >>> PortGroup.classify('COUT')
+        <PortGroup.DATA_OUT_WORD: 21>
+
+        >>> PortGroup.classify('SUM')
+        <PortGroup.DATA_OUT_WORD: 21>
+
+        Data Control Signals
+        ++++++++++++++++++++
+
+        >>> PortGroup.classify('SET')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('SET_N')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('SET_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        >>> PortGroup.classify('S')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('S_N')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('S_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        >>> PortGroup.classify('R')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('R_N')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('R_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        >>> PortGroup.classify('RESET')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('RESET_N')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('RESET_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        >>> PortGroup.classify('RESET')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('RESET_N')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('RESET_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        Data Enable
+        >>> PortGroup.classify('DE')
+        <PortGroup.DATA_CONTROL: 40>
+
+        Tristate Enable
+        >>> PortGroup.classify('TE_B')
+        <PortGroup.DATA_CONTROL: 40>
+
+        Select lines on muxes
+        >>> PortGroup.classify('S0')
+        <PortGroup.DATA_CONTROL: 40>
+        >>> PortGroup.classify('S4')
+        <PortGroup.DATA_CONTROL: 40>
+
+        Clock Signals
+        +++++++++++++
+
+        >>> PortGroup.classify('C')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('CN')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('C_N')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('CLK')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('GCLK')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('CLK_N')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('G')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('GN')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('G_N')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('GATE')
+        <PortGroup.CLOCK: 50>
+
+        >>> PortGroup.classify('GATE_N')
+        <PortGroup.CLOCK: 50>
+
+
+        Clock Control Signals
+        +++++++++++++++++++++
+
+        >>> PortGroup.classify('CLK_EN')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        >>> PortGroup.classify('CE')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        >>> PortGroup.classify('CEN')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        >>> PortGroup.classify('GATE_EN')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        >>> PortGroup.classify('GEN')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        >>> PortGroup.classify('GE')
+        <PortGroup.CLOCK_CONTROL: 51>
+
+        Positive Power Supplies
+        +++++++++++++++++++++++
+
+        >>> PortGroup.classify('VPWR')
+        <PortGroup.POWER_POSITIVE: 80>
+
+        >>> PortGroup.classify('VPB')
+        <PortGroup.POWER_POSITIVE: 80>
+
+        Negative Power Supplies
+        +++++++++++++++++++++++
+
+        >>> PortGroup.classify('VGND')
+        <PortGroup.POWER_NEGATIVE: 81>
+
+        >>> PortGroup.classify('VNB')
+        <PortGroup.POWER_NEGATIVE: 81>
+
+        >>> PortGroup.classify('DEST')
+        <PortGroup.POWER_OTHER: 79>
+
+        Power Control Signals
+        +++++++++++++++++++++
+
+        >>> PortGroup.classify('SLEEP')
+        <PortGroup.POWER_CONTROL: 78>
+
+        >>> PortGroup.classify('SLEEP_B')
+        <PortGroup.POWER_CONTROL: 78>
+
+        >>> PortGroup.classify('SLEEP_N')
+        <PortGroup.POWER_CONTROL: 78>
+
+        Other Power Suppliers
+        +++++++++++++++++++++
+
+        >>> PortGroup.classify('KAPWR')
+        <PortGroup.POWER_OTHER: 79>
+
+        >>> PortGroup.classify('KAGND')
+        <PortGroup.POWER_OTHER: 79>
+
+        >>> PortGroup.classify('DIODE')
+        <PortGroup.POWER_OTHER: 79>
+
+        >>> PortGroup.classify('LOWLVPWRA')
+        <PortGroup.POWER_OTHER: 79>
+
+        """
+        # Override for csw module
+        if modname and 'csw' in modname:
+            if name == 'VPB':
+                return cls.POWER_POSITIVE
+            elif name == 'VNB':
+                return cls.POWER_NEGATIVE
+            elif name in ('S', 'D', 'GN', 'GP'):
+                return cls.POWER_OTHER
+
+        name = name.upper()
+        if re.search('^[A-FJKPN][0-9]?(_[NB])?$', name):
+            if name not in ('C', 'C_N'):
+                return cls.DATA_IN_CHAR
+
+        if re.search('^[XYZQ][0-9]?(_[NB])?$', name):
+            return cls.DATA_OUT_CHAR
+
+        if re.search('^IN[0-9]?_?', name):
+            return cls.DATA_IN_WORD
+        if re.search('^CI[0-9]?_?', name):
+            return cls.DATA_IN_WORD
+        if re.search('^CIN[0-9]?_?', name):
+            return cls.DATA_IN_WORD
+        if re.search('^OUT[0-9]?_?', name):
+            return cls.DATA_OUT_WORD
+        if re.search('^COUT[0-9]?_?', name):
+            return cls.DATA_OUT_WORD
+        if re.search('^SUM[0-9]?_?', name):
+            return cls.DATA_OUT_WORD
+        if name in ('HI', 'LO'):
+            return cls.DATA_OUT_WORD
+
+        if re.search('^S[0-9]+$', name):
+            return cls.DATA_CONTROL
+
+        if re.search('^((SET)|(RESET)|[SR])(_[NB])?$', name):
+            return cls.DATA_CONTROL
+
+        if re.search('^((T)|(TE))(_[NB])?$', name):
+            return cls.DATA_CONTROL
+
+        if re.search('^(DE)(_[NB])?$', name):
+            return cls.DATA_CONTROL
+
+        if re.search('^((CLK)|(GCLK)|(GATE))_EN$', name):
+            return cls.CLOCK_CONTROL
+        if re.search('^[CG]EN$', name):
+            return cls.CLOCK_CONTROL
+        if re.search('^[CG]E$', name):
+            return cls.CLOCK_CONTROL
+        if re.search('^((CLK)|(GCLK)|(GATE))$', name):
+            return cls.CLOCK
+        if re.search('^((CLK)|(GCLK)|(GATE))_N$', name):
+            return cls.CLOCK
+        if re.search('^[CG]_?N?$', name):
+            return cls.CLOCK
+
+        if re.search('^SLEEP(_[NB])?$', name):
+            return cls.POWER_CONTROL
+
+        if re.search('^VPWR', name):
+            return cls.POWER_POSITIVE
+        if re.search('^VPB$', name):
+            return cls.POWER_POSITIVE
+        if re.search('^VGND', name):
+            return cls.POWER_NEGATIVE
+        if re.search('^VNB$', name):
+            return cls.POWER_NEGATIVE
+        if name.startswith('DEST'):
+            return cls.POWER_OTHER
+        if 'DIODE' in name:
+            return cls.POWER_OTHER
+        if 'PWR' in name:
+            return cls.POWER_OTHER
+        if 'GND' in name:
+            return cls.POWER_OTHER
+        if 'SHORT' in name:
+            return cls.POWER_OTHER
+
+        if 'MET' in name:
+            return cls.POWER_OTHER
+        if re.search('^[M][0-1]$', name):
+            return cls.POWER_OTHER
+        if 'SRC' == name:
+            return cls.DATA_IN_WORD
+        if 'DST' == name:
+            return cls.DATA_OUT_WORD
+        if 'NETA' == name:
+            return cls.DATA_IN_WORD
+        if 'NETB' == name:
+            return cls.DATA_OUT_WORD
+
+        if name.startswith('SC'):
+            return cls.SCAN_CHAIN
+        if name.startswith('ASYNC'):
+            return cls.SCAN_CHAIN
+
+
+
+def write_verilog(define_data, outfile, drive=None):
+    with open(outfile, 'w') as f:
+        f.write(copyright_header)
+        f.write('\n')
+        f.write(include_header.format(define_data['fullname']))
+        f.write('\n')
+        if not drive:
+            return
+        drive_name, drive_value = drive
+
+        if not 'ports' in define_data:
+            return
+
+        module_signal_defports = []
+        module_signal_ports = []
+        for pname, ptype in define_data['ports']['signal']:
+            module_signal_defports.append("{} {}, ".format(ptype, pname))
+            module_signal_ports.append(pname)
+
+        module_signal_defports = "".join(module_signal_defports)
+        assert module_signal_defports.endswith(", "), module_signal_defports
+        module_signal_defports = module_signal_defports[:-2]
+        module_signal_ports = ", ".join(module_signal_ports)
+
+        module_power_defports = []
+        module_power_ports = []
+        for pname, ptype in define_data['ports']['power']:
+            module_power_defports.append(", {} {}".format('input', pname))
+            module_power_ports.append(", {}".format(pname))
+        module_power_defports = "".join(module_power_defports)
+        module_power_ports = "".join(module_power_ports)
+
+        library_name = "{} {}".format(
+            define_data['library']['name'].upper(), define_data['library']['type'])
+
+        f.write(module_header.format(
+            module_base_name = define_data['fullname'],
+            cell_name = define_data['name'],
+            library_name = library_name,
+            drive_name = drive_name,
+            drive_value = drive_value,
+            description = define_data.get('description', ''),
+            module_signal_defports = module_signal_defports,
+            module_signal_ports = module_signal_ports,
+            module_power_defports = module_power_defports,
+            module_power_ports = module_power_ports,
+        ))
+
+
+def echo_file(fname):
+    with open(fname) as f:
+        sys.stdout.write('\n')
+        sys.stdout.write('File: ')
+        sys.stdout.write(fname)
+        sys.stdout.write('\n')
+        sys.stdout.write('------\n')
+        sys.stdout.write(f.read())
+        sys.stdout.write('------\n')
+        sys.stdout.flush()
+
+
+def drive_strengths(basename, cellpath):
+    drives = []
+    for f in os.listdir(cellpath):
+        if not f.endswith('.gds'):
+            continue
+        f = f.split('.', 1)[0]
+
+        libname, modname = f.split('__', 1)
+        drive = modname.replace(basename, '')
+        if not drive:
+            continue
+        drives.append(drive)
+    return drives
+
+def wrap(s):
+    return "\n".join(textwrap.wrap(s, initial_indent=' * ', subsequent_indent=' * '))
+
+
+
+def write_verilog_header(f, define_data):
+    f.write(copyright_header)
+    f.write('\n')
+    f.write(f"/**\n")
+    f.write(wrap(f"{define_data['name']}: {define_data['description']}"))
+    f.write('\n')
+    f.write(f" */\n")
+    f.write('(* blackbox *)\n')
+    f.write(f"module {define_data['library']}__{define_data['name']} (\n")
+
+
+def write_blackbox(cellpath, define_data):
+    outpath = os.path.join(cellpath, f"{define_data['library']}__{define_data['name']}.blackbox.v")
+    assert not os.path.exists(outpath), outpath
+    print("Creating", outpath)
+    with open(outpath, "w+") as f:
+        write_verilog_header(f, define_data)
+
+        maxlen = {}
+        maxlen['pname'] = max([0]+[len(p[1]) for p in define_data['ports'] if p[0] == 'signal'])
+        maxlen['pdir']  = max([0]+[len(p[2]) for p in define_data['ports'] if p[0] == 'signal'])
+        maxlen['ptype'] = max([0]+[len(p[3]) for p in define_data['ports'] if p[0] == 'signal'])
+
+        for pclass, pname, pdir, ptype in define_data['ports']:
+            if pclass != 'signal':
+                continue
+
+            pname = pname.ljust(maxlen['pname'])
+
+            if maxlen['pdir']:
+                pdir = pdir.ljust(maxlen['pdir'])+' '
+            else:
+                pdir = ''
+
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+
+            f.write(f"    {pdir}{ptype}{pname},\n")
+        f.seek(f.tell()-2)
+        f.write("\n);\n")
+
+        maxlen['pname'] = max([0]+[len(p[0]) for p in define_data['parameters']])
+        maxlen['ptype'] = max([0]+[len(p[1]) for p in define_data['parameters']])
+        if maxlen['pname']:
+            f.write('\n    // Parameters\n')
+        for pname, ptype in define_data['parameters']:
+            pname = pname.ljust(maxlen['pname'])
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+            f.write(f'    parameter {ptype}{pname};\n')
+
+        maxlen['pname'] = max([0]+[len(p[1]) for p in define_data['ports'] if p[0] == 'power'])
+        maxlen['ptype'] = max([0]+[len(p[3]) for p in define_data['ports'] if p[0] == 'power'])
+        if maxlen['pname']:
+            f.write('\n    // Voltage supply signals\n')
+        for pclass, pname, pdir, ptype in define_data['ports']:
+            if pclass != 'power':
+                continue
+            assert ptype, (pclass, pname, pdir, ptype)
+
+            pname = pname.ljust(maxlen['pname'])
+            ptype = ptype.ljust(maxlen['ptype'])
+
+            f.write(f'    {ptype} {pname};\n')
+
+        f.write('endmodule\n')
+
+
+def write_blackbox_pp(cellpath, define_data):
+    outpath = os.path.join(cellpath, f"{define_data['library']}__{define_data['name']}.pp.blackbox.v")
+    assert not os.path.exists(outpath), outpath
+    print("Creating", outpath)
+    with open(outpath, "w+") as f:
+        write_verilog_header(f, define_data)
+
+        maxlen = {}
+        maxlen['pname'] = max([0]+[len(p[1]) for p in define_data['ports']])
+        maxlen['pdir']  = max([0]+[len(p[2]) for p in define_data['ports']])
+        maxlen['ptype'] = max([0]+[len(p[3]) for p in define_data['ports'] if p[0] == 'signal'])
+
+        for pclass, pname, pdir, ptype in define_data['ports']:
+            if pclass == 'power':
+                ptype = ''
+
+            pname = pname.ljust(maxlen['pname'])
+
+            if maxlen['pdir']:
+                pdir = pdir.ljust(maxlen['pdir'])+' '
+            else:
+                pdir = ''
+
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+
+            f.write(f"    {pdir}{ptype}{pname},\n")
+        f.seek(f.tell()-2)
+        f.write("\n);\n")
+
+        maxlen['pname'] = max([0]+[len(p[0]) for p in define_data['parameters']])
+        maxlen['ptype'] = max([0]+[len(p[1]) for p in define_data['parameters']])
+        if maxlen['pname']:
+            f.write('\n    // Parameters\n')
+        for pname, ptype in define_data['parameters']:
+            pname = pname.ljust(maxlen['pname'])
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+            f.write(f'    parameter {ptype}{pname};\n')
+        f.write('endmodule\n')
+
+
+def write_symbol_pp(cellpath, define_data):
+    outpath = os.path.join(cellpath, f"{define_data['library']}__{define_data['name']}.pp.symbol.v")
+    assert not os.path.exists(outpath), outpath
+
+    ports = []
+    for pclass, pname, pdir, ptype in define_data['ports']:
+        pg = PortGroup.classify(pname, define_data['name'])
+        ports.append((pg, pclass, pname, pdir, ptype))
+
+    ports.sort()
+
+    pports = [None,]
+    while len(ports) > 0:
+        if pports[-1] and pports[-1][0].type != ports[0][0].type:
+            pports.append(None)
+        pports.append(ports.pop(0))
+
+    for i, p in enumerate(pports):
+        if not p:
+            np = pports[i+1]
+            print('//# {{'+np[0].type+'|'+np[0].desc+'}}')
+            continue
+        print(p)
+
+    return
+
+
+    print("Creating", outpath)
+    with open(outpath, "w+") as f:
+        write_verilog_header(f, define_data)
+
+        maxlen = {}
+        maxlen['pname'] = max([0]+[len(p[1]) for p in define_data['ports']])
+        maxlen['pdir']  = max([0]+[len(p[2]) for p in define_data['ports']])
+        maxlen['ptype'] = max([0]+[len(p[3]) for p in define_data['ports'] if p[0] == 'signal'])
+
+        for pclass, pname, pdir, ptype in define_data['ports']:
+            if pclass == 'power':
+                ptype = ''
+
+            pname = pname.ljust(maxlen['pname'])
+
+            if maxlen['pdir']:
+                pdir = pdir.ljust(maxlen['pdir'])+' '
+            else:
+                pdir = ''
+
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+
+            f.write(f"    {pdir}{ptype}{pname},\n")
+        f.seek(f.tell()-2)
+        f.write("\n);\n")
+
+        maxlen['pname'] = max([0]+[len(p[0]) for p in define_data['parameters']])
+        maxlen['ptype'] = max([0]+[len(p[1]) for p in define_data['parameters']])
+        if maxlen['pname']:
+            f.write('\n    // Parameters\n')
+        for pname, ptype in define_data['parameters']:
+            pname = pname.ljust(maxlen['pname'])
+            if maxlen['ptype']:
+                ptype = ptype.ljust(maxlen['ptype'])+ ' '
+            else:
+                ptype = ''
+            f.write(f'    parameter {ptype}{pname};\n')
+        f.write('endmodule\n')
+
+
+
+def process(cellpath):
+    print()
+    print(cellpath)
+    define_json = os.path.join(cellpath, 'definition.json')
+    if not os.path.exists(define_json):
+        print("No definition.json in", cellpath)
+        return
+    assert os.path.exists(define_json), define_json
+    define_data = json.load(open(define_json))
+    pprint.pprint(define_data)
+
+    write_blackbox(cellpath, define_data)
+    write_blackbox_pp(cellpath, define_data)
+
+    write_symbol_pp(cellpath, define_data)
+    pprint.pprint(drive_strengths(define_data['name'], cellpath))
+
+    for pclass, pname, pdir, ptype in define_data['ports']:
+        print(pname, PortGroup.classify(pname, define_data['name']))
+
+    return
+    drives = define_data.get('drives', [])
+    for d in drives:
+        assert len(d) == 1, d
+        drive_name = list(d.keys())[0]
+        drive_value = list(d.values())[0]
+        if drive_name == 'units':
+            pass
+        elif drive_name == 'lp_variant':
+            if drive_value == 0:
+                drive_value = 'lp'
+            else:
+                drive_value = 'lp'+str(drive_value+1)
+        elif drive_name == "minimum":
+            assert drive_value is None
+            drive_value = "m"
+        else:
+            raise TypeError("Unknown drive:"+repr(d))
+
+        dvfile = os.path.join(cellpath, "{}_{}.v".format(define_data['fullname'], drive_value))
+        write_verilog(define_data, dvfile, list(d.items())[0])
+        echo_file(dvfile)
+
+    if not drives:
+        outfile = os.path.join(cellpath, "{}.v".format(define_data['fullname']))
+        write_verilog(define_data, outfile)
+        echo_file(outfile)
+
+
+def main(args):
+    for a in args:
+        process(a)
+
+
+
+if __name__ == "__main__":
+    import doctest
+    fails, _ = doctest.testmod()
+    if fails>0:
+        sys.exit()
+    sys.exit(main(sys.argv[1:]))
diff --git a/scripts/python-skywater-pdk/skywater_pdk/utils.py b/scripts/python-skywater-pdk/skywater_pdk/utils.py
index cc8bfc0..e3f2736 100644
--- a/scripts/python-skywater-pdk/skywater_pdk/utils.py
+++ b/scripts/python-skywater-pdk/skywater_pdk/utils.py
@@ -200,6 +200,41 @@
         return hash(self._name_)
 
 
+class OrderedEnum(Flag):
+    def __ge__(self, other):
+        if other is None:
+            return True
+        if self.__class__ is other.__class__:
+            return self.value >= other.value
+        return NotImplemented
+    def __gt__(self, other):
+        if other is None:
+            return True
+        if self.__class__ is other.__class__:
+            return self.value > other.value
+        return NotImplemented
+    def __le__(self, other):
+        if other is None:
+            return False
+        if self.__class__ is other.__class__:
+            return self.value <= other.value
+        return NotImplemented
+    def __lt__(self, other):
+        if other is None:
+            return False
+        if self.__class__ is other.__class__:
+            return self.value < other.value
+        return NotImplemented
+    def __eq__(self, other):
+        if other is None:
+            return False
+        if self.__class__ is other.__class__:
+            return self.value == other.value
+        return NotImplemented
+    def __hash__(self):
+        return hash(self._name_)
+
+
 if __name__ == "__main__":
     import doctest
     doctest.testmod()