skywater-pdk/scripts/python-skywater-pdk/skywater_pdk/cells/generate
wgryncewicz 37fb6cdb25
Merge branch 'main' into 86-cell_cross_index
2023-05-10 10:22:34 +02:00
..
readme.py cell readme generator extension changed from Directive to event 2020-11-27 20:14:43 +01:00
vcd2wavedrom.py Added option for reduction of long clock sequences in wavedrom converter 2020-12-01 15:26:39 +01:00
waveform.py Improved handling of testbenches with infinite clock 2020-12-01 13:44:17 +01:00

readme.py

#!/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 prototype of cell documentation generation script.
'''

import csv
import json
import os
import sys
import argparse
import pathlib
import glob
import subprocess
import textwrap
from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList
from sphinx.util.nodes import nested_parse_with_titles

verbose = False

readme_template ="""\
{header}

**{description}**

*This is a stub of cell description file*

-  **Cell name**: {name}
-  **Type**: {deftype}
-  **Verilog name**: {verilog_name}
-  **Library**: {library}
-  **Inputs**:  {inputs}
-  **Outputs**: {outputs}

{subheader_sym}

.. list-table:: 

    * - .. figure:: {symbol1}
      -
      - .. figure:: {symbol2}

{subheader_sch}

.. figure:: {schematic}
    :align: center

{subheader_gds}

"""

figure_template ="""

.. figure:: {fig}
    :align: center
    :width: 50%

    {name}
"""

def write_readme(cellpath, define_data):
    ''' Generates README for a given cell.

    Args:
        cellpath - path to a cell [str of pathlib.Path]
        define_data - cell data from json [dic]

    '''
    outpath = os.path.join(cellpath, 'README.rst')
    prefix = define_data['file_prefix'] 
    header = f':cell:`{prefix}`'
    subheader_sym = header + ' symbols'
    subheader_sch = header + ' schematic'
    subheader_gds = header + ' GDSII layouts'

    header += '\n' + '=' * len(header)
    subheader_sym += '\n' + '-' * len(subheader_sym)
    subheader_sch += '\n' + '-' * len(subheader_sch)
    subheader_gds += '\n' + '-' * len(subheader_gds)


    symbol1 = prefix + '.symbol.svg'
    symbol2 = prefix + '.pp.symbol.svg'
    schematic = prefix + '.schematic.svg'
    inputs = []
    outputs = []
    for p in define_data['ports']:
        try:
            if p[0]=='signal' and p[2]=='input':
                inputs.append(p[1])
            if p[0]=='signal' and p[2]=='output':
                outputs.append(p[1])
        except: 
            pass
    gdssvg = []       
    svglist = list(pathlib.Path(cellpath).glob('*.svg'))
    for s in svglist:
        gdsfile = pathlib.Path(os.path.join(cellpath, s.stem +'.gds'))
        if gdsfile.is_file():
            gdssvg.append(s)

    with open(outpath, 'w') as f:
        f.write (readme_template.format (
            header = header,
            subheader_sym = subheader_sym,
            subheader_sch = subheader_sch,
            subheader_gds = subheader_gds,
            description = define_data['description'].rstrip('.'),
            name = ':cell:`' + prefix +'`',
            deftype = define_data['type'],
            verilog_name = define_data['verilog_name'],
            library = define_data['library'],
            inputs  = f'{len(inputs)} ('  + ', '.join(inputs) + ')',
            outputs = f'{len(outputs)} (' + ', '.join(outputs) + ')',
            symbol1 = symbol1,
            symbol2 = symbol2,
            schematic = schematic,
        ))
        for gs in sorted(gdssvg):
            f.write (figure_template.format (
                fig  = gs.name,
                name = gs.stem
        ))

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]
    '''
    if verbose:
        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_readme(cellpath, define_data)

    return

# --- Sphinx extension wrapper ----------------

def GenerateCellReadme (app, cellpath):

        print (f'GenerateCellReadme: generating files for {cellpath}')
        path = pathlib.Path(cellpath).expanduser()
        parts = path.parts[1:] if path.is_absolute() else path.parts
        paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts)))
        paths = list(paths)    
        cell_dirs = [d.resolve() for d in paths if d.is_dir()]

        errors = 0
        for d in cell_dirs:
            try:
                process(d)
            except (AssertionError, FileNotFoundError, ChildProcessError) as ex:
                print (f'GenerateCellReadme: {type(ex).__name__}')
                print (f'{ex.args}')
                errors +=1
        print (f'GenerateCellReadme: {len(cell_dirs)} files processed, {errors} errors.')

def setup(app):
    app.add_event("cells_generate_readme")
    app.connect('cells_generate_readme', GenerateCellReadme)

    return {
        'version': '0.1',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
           }

# ----------------------------------------------      

def main():
    ''' Generates README.rst for cell.'''

    prereq_txt = ''
    output_txt = 'output:\n  generates README.rst'
    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())