Added cell VCD waveform generator script
diff --git a/environment.yml b/environment.yml
index ee13527..08e754a 100644
--- a/environment.yml
+++ b/environment.yml
@@ -20,6 +20,9 @@
dependencies:
- python=3.8
- pip
+- yosys
+- netlistsvg
+- verilog
# Packages installed from PyPI
- pip:
- -r file:requirements.txt
diff --git a/scripts/python-skywater-pdk/skywater_pdk/cells/generate/waveform.py b/scripts/python-skywater-pdk/skywater_pdk/cells/generate/waveform.py
new file mode 100755
index 0000000..ec3b865
--- /dev/null
+++ b/scripts/python-skywater-pdk/skywater_pdk/cells/generate/waveform.py
@@ -0,0 +1,162 @@
+#!/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
+
+''' This is a cell VCD waveform generation script.
+'''
+
+import csv
+import json
+import os
+import sys
+import argparse
+import pathlib
+import glob
+import subprocess
+import textwrap
+import re
+
+
+def write_vcd (cellpath, define_data, use_power_pins=False):
+ ''' Generates vcd for a given cell.
+
+ Args:
+ cellpath - path to a cell [str of pathlib.Path]
+ define_data - cell data from json [dic]
+ use_power_pins - include power pins toggling in simulation [bool]
+ '''
+
+ # collect power port names
+ pp = []
+ for p in define_data['ports']:
+ if len(p)>2 and p[0]=='power':
+ pp.append(p[1])
+
+ # define output file(s)
+ ppsuffix = '.pp' if use_power_pins else ''
+ outfile = os.path.join(cellpath, define_data['file_prefix'] + ppsuffix + '.vcd')
+ vppfile = os.path.join(cellpath, define_data['file_prefix'] + '.vpp.tmp')
+ tmptestbed = os.path.join(cellpath, define_data['file_prefix'] + '.tb.v.tmp')
+
+ # find and patch Verilog testbed file
+ testbedfile = os.path.join(cellpath, define_data['file_prefix'] + '.tb.v')
+ assert os.path.exists(testbedfile), testbedfile
+ insertppdefine = use_power_pins
+ prvline=''
+ with open(tmptestbed,'w') as ttb:
+ with open(testbedfile,'r') as tbf:
+ for line in tbf:
+ # add use_power_pins define
+ if insertppdefine and line.startswith('`include'):
+ line = '`define USE_POWER_PINS\n' + line
+ insertppdefine = False
+ # add dumpfile define
+ if prvline.strip(' \n\r')=='begin':
+ line = line[:-len(line.lstrip())] + \
+ '$dumpfile("' + outfile + '");\n' + \
+ line[:-len(line.lstrip())] + \
+ '$dumpvars(1,top);\n' + \
+ line
+ # remove power pins from reg - optinal, but makes output more readable
+ if not use_power_pins:
+ for p in pp:
+ if re.search( 'reg\s+'+p, line ) is not None or \
+ re.search( p+'\s+\=', line ) is not None :
+ line=''
+ break
+ # remove power pins from dut
+ if not use_power_pins and define_data['file_prefix']+' dut' in line:
+ for p in pp:
+ line = line.replace(f'.{p}({p}),','')
+ line = line.replace(f'.{p}({p}))',')')
+ prvline = line
+ ttb.write(line)
+
+ # generate vpp code and vcd recording
+ if subprocess.call(['iverilog', '-o', vppfile, tmptestbed], cwd=cellpath):
+ raise ChildProcessError("Icarus Verilog compilation failed")
+ if subprocess.call(['vvp', vppfile], cwd=cellpath):
+ raise ChildProcessError("Icarus Verilog runtime failed")
+
+ # remove temporary files
+ os.remove(tmptestbed)
+ os.remove(vppfile)
+
+
+def process(cellpath):
+ ''' Processes cell indicated by path.
+ Opens cell definiton and calls further processing
+
+ Args:
+ cellpath - path to a cell [str of pathlib.Path]
+ '''
+
+ print()
+ print(cellpath)
+ define_json = os.path.join(cellpath, 'definition.json')
+ if not os.path.exists(define_json):
+ print("No definition.json in", cellpath)
+ assert os.path.exists(define_json), define_json
+ define_data = json.load(open(define_json))
+
+ if define_data['type'] == 'cell':
+ write_vcd(cellpath, define_data, use_power_pins = False)
+ write_vcd(cellpath, define_data, use_power_pins = True)
+
+ return
+
+
+def main():
+ ''' Generates VCD waveform for cell.'''
+
+ prereq_txt = ''
+ output_txt = 'output:\n generates [fullcellname].vcd'
+ allcellpath = '../../../libraries/*/latest/cells/*'
+
+ parser = argparse.ArgumentParser(
+ description = main.__doc__,
+ epilog = prereq_txt +'\n\n'+ output_txt,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument(
+ "--all_libs",
+ help="process all cells in "+allcellpath,
+ action="store_true")
+ parser.add_argument(
+ "cell_dir",
+ help="path to the cell directory",
+ type=pathlib.Path,
+ nargs="*")
+
+ args = parser.parse_args()
+
+ if args.all_libs:
+ path = pathlib.Path(allcellpath).expanduser()
+ parts = path.parts[1:] if path.is_absolute() else path.parts
+ paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts)))
+ args.cell_dir = list(paths)
+
+ cell_dirs = [d.resolve() for d in args.cell_dir if d.is_dir()]
+
+ errors = 0
+ for d in cell_dirs:
+ try:
+ process(d)
+ except KeyboardInterrupt:
+ sys.exit(1)
+ except (AssertionError, FileNotFoundError, ChildProcessError) as ex:
+ print (f'Error: {type(ex).__name__}')
+ print (f'{ex.args}')
+ errors +=1
+ print (f'\n{len(cell_dirs)} files processed, {errors} errors.')
+ return 0 if errors else 1
+
+if __name__ == "__main__":
+ sys.exit(main())
+