#!/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 insertdumpvars = True insertfinish = True 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 insertdumpvars and prvline.strip(' \n\r')=='begin': line = line[:-len(line.lstrip())] + \ '$dumpfile("' + outfile + '");\n' + \ line[:-len(line.lstrip())] + \ '$dumpvars(1,top);\n' + \ line insertdumpvars = False # add finish command, to stop paraller threads if insertfinish and line.strip(' \n\r')=='end' and not '$finish' in prvline: line = prvline[:-len(prvline.lstrip())] + '$finish;\n' + line insertfinish = False # 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())