Merge cc862390c7
into 49d3c73c2c
This commit is contained in:
commit
2f7a7d242d
|
@ -0,0 +1,35 @@
|
|||
name: Generate cells docs
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
generate-cells:
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Generate cells READMEs
|
||||
run: |
|
||||
SUBMODULE_VERSION=latest make submodules -j3 || make submodules -j1
|
||||
|
||||
git submodule foreach 'git checkout master -q'
|
||||
|
||||
make env
|
||||
source env/conda/bin/activate skywater-pdk-scripts
|
||||
which python
|
||||
python scripts/python-skywater-pdk/skywater_pdk/cells/list.py libraries/*/latest/
|
||||
python scripts/python-skywater-pdk/skywater_pdk/cells/generate/readme.py libraries/*/latest/cells/*
|
||||
|
||||
git submodule foreach 'git add .'
|
||||
export DAT=`date +"%Y-%m-%d %k:%M"`
|
||||
|
||||
# TODO replace below with appropriate credentials
|
||||
git config --global user.email "action@github.com"
|
||||
git config --global user.name "Github Action"
|
||||
git submodule foreach 'git commit -q -m "$DAT" || true'
|
|
@ -3,6 +3,10 @@ flake8
|
|||
# rst_include tool as GitHub doesn't support `.. include::` when rendering
|
||||
# previews.
|
||||
rst_include
|
||||
docutils
|
||||
dataclasses
|
||||
dataclasses_json
|
||||
sphinx
|
||||
|
||||
# The Python API for the SkyWater PDK.
|
||||
-e scripts/python-skywater-pdk
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
#!/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
|
||||
|
||||
|
||||
readme_template ="""\
|
||||
{header}
|
||||
{headerUL}
|
||||
|
||||
**{description}**
|
||||
|
||||
*This is a stub of cell description file*
|
||||
|
||||
- **Cell name**: {name}
|
||||
- **Type**: {deftype}
|
||||
- **Verilog name**: {verilog_name}
|
||||
- **Library**: {library}
|
||||
- **Inputs**: {inputs}
|
||||
- **Outputs**: {outputs}
|
||||
|
||||
Symbols
|
||||
-------
|
||||
|
||||
.. list-table::
|
||||
|
||||
* - .. figure:: {symbol1}
|
||||
-
|
||||
- .. figure:: {symbol2}
|
||||
|
||||
Schematic
|
||||
---------
|
||||
|
||||
.. figure:: {schematic}
|
||||
:align: center
|
||||
|
||||
GDSII Layouts
|
||||
-------------
|
||||
|
||||
"""
|
||||
|
||||
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]
|
||||
|
||||
'''
|
||||
netlist_json = os.path.join(cellpath, define_data['file_prefix']+'.json')
|
||||
assert os.path.exists(netlist_json), netlist_json
|
||||
outpath = os.path.join(cellpath, 'README.rst')
|
||||
|
||||
prefix = define_data['file_prefix']
|
||||
header = prefix
|
||||
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,
|
||||
headerUL = '=' * len(header),
|
||||
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]
|
||||
'''
|
||||
|
||||
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
|
||||
|
||||
|
||||
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())
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright 2020 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
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import sys
|
||||
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
|
||||
|
||||
from typing import Tuple, List, Dict
|
||||
|
||||
# using a list-table here to allow for easier line breaks in description
|
||||
rst_header_line_char = '-'
|
||||
rst_header = 'List of cells in :lib:`{libname}`'
|
||||
rst_template ="""\
|
||||
{header_line}
|
||||
{header_underline}
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Cell name
|
||||
- Description
|
||||
- Type
|
||||
- Verilog name
|
||||
{cell_list}
|
||||
"""
|
||||
|
||||
|
||||
cell_template = """\
|
||||
* - :doc:`{cell_name} <{link}>`
|
||||
- {description}
|
||||
- {type}
|
||||
- {verilog_name}
|
||||
"""
|
||||
|
||||
|
||||
def collect(library_dir) -> Tuple[str, List[str]]:
|
||||
"""Collect the available definitions for cells in a library
|
||||
|
||||
Parameters
|
||||
----------
|
||||
library_dir: str or pathlib.Path
|
||||
Path to a library.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lib : str
|
||||
Library name
|
||||
|
||||
cells : list of pathlib.Path
|
||||
definition files for cells in the library.
|
||||
"""
|
||||
|
||||
if not isinstance(library_dir, pathlib.Path):
|
||||
library_dir = pathlib.Path(library_dir)
|
||||
|
||||
libname = None
|
||||
cells = set()
|
||||
|
||||
for p in library_dir.rglob("definition.json"):
|
||||
if not p.is_file():
|
||||
continue
|
||||
cells.add(p)
|
||||
if libname is None:
|
||||
with open(str(p), "r") as sample_json:
|
||||
sample_def = json.load(sample_json)
|
||||
libname = sample_def['library']
|
||||
|
||||
assert len(libname) > 0
|
||||
cells = list(sorted(cells))
|
||||
return libname, cells
|
||||
|
||||
|
||||
def generate_rst(library_dir, library_name, cells):
|
||||
"""Generate the RST paragraph containing basic information about cells
|
||||
|
||||
Parameters
|
||||
----------
|
||||
library_dir: str or pathlib.Path
|
||||
Path to a library.
|
||||
|
||||
library_name: str
|
||||
Name of the library
|
||||
|
||||
cells: list of pathlib.Path
|
||||
List of paths to JSON description files
|
||||
|
||||
Returns
|
||||
-------
|
||||
paragraph: str
|
||||
Generated paragraph
|
||||
"""
|
||||
|
||||
if not isinstance(library_dir, pathlib.Path):
|
||||
library_dir = pathlib.Path(library_dir)
|
||||
|
||||
paragraph = ""
|
||||
cell_list = ""
|
||||
|
||||
for cell in cells:
|
||||
with open(str(cell), "r") as c:
|
||||
cell_json = json.load(c)
|
||||
cell_list = cell_list + cell_template.format(
|
||||
cell_name = cell_json['name'],
|
||||
#description = cell_json['description'].replace("\n", "\n "),
|
||||
description = textwrap.indent(cell_json['description'], ' ').lstrip(),
|
||||
type = cell_json['type'],
|
||||
verilog_name = cell_json['verilog_name'],
|
||||
#link = str(cell.resolve()).rpartition('/')[0] + '/README'
|
||||
link = 'cells/' + str(cell).rpartition('/')[0].rpartition('/')[2] + '/README'
|
||||
)
|
||||
|
||||
header = rst_header.format(libname = library_name)
|
||||
paragraph = rst_template.format(
|
||||
header_line = header,
|
||||
header_underline = rst_header_line_char * len(header),
|
||||
cell_list = cell_list
|
||||
)
|
||||
return paragraph
|
||||
|
||||
|
||||
def AppendToReadme (celllistfile):
|
||||
''' Prototype of library README builder '''
|
||||
readmefile = pathlib.Path(celllistfile.parents[0], 'README.rst')
|
||||
old = ''
|
||||
if readmefile.exists():
|
||||
with open(str(readmefile), "r") as f:
|
||||
for i, l in enumerate(f):
|
||||
if i<5: old += l
|
||||
|
||||
with open(str(readmefile), "w+") as f:
|
||||
f.write(old)
|
||||
tableinc = f'.. include:: {celllistfile.name}\n'
|
||||
|
||||
if not tableinc in old:
|
||||
f.write(tableinc)
|
||||
|
||||
|
||||
# --- Sphinx extension wrapper ---
|
||||
|
||||
class CellList(Directive):
|
||||
|
||||
def run(self):
|
||||
env = self.state.document.settings.env
|
||||
dirname = env.docname.rpartition('/')[0]
|
||||
libname, cells = collect(dirname)
|
||||
paragraph = generate_rst(dirname, libname, cells)
|
||||
# parse rst string to docutils nodes
|
||||
rst = ViewList()
|
||||
for i,line in enumerate(paragraph.split('\n')):
|
||||
rst.append(line, libname+"-cell-list.rst", i+1)
|
||||
node = nodes.section()
|
||||
node.document = self.state.document
|
||||
nested_parse_with_titles(self.state, rst, node)
|
||||
return node.children
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_directive("cell_list", CellList)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
# --- stand alone, command line operation ---
|
||||
|
||||
def main():
|
||||
|
||||
alllibpath = '../../../libraries/*/latest'
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--all_libs",
|
||||
help="process all libs in "+alllibpath,
|
||||
action="store_true")
|
||||
parser.add_argument(
|
||||
"library_dir",
|
||||
help="Path to the library.",
|
||||
type=pathlib.Path,
|
||||
nargs='*')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.all_libs:
|
||||
path = pathlib.Path(alllibpath).expanduser()
|
||||
parts = path.parts[1:] if path.is_absolute() else path.parts
|
||||
paths = pathlib.Path(path.root).glob(str(pathlib.Path("").joinpath(*parts)))
|
||||
args.library_dir = list(paths)
|
||||
|
||||
|
||||
libs = [pathlib.Path(d) for d in args.library_dir]
|
||||
libs = [d for d in libs if d.is_dir()]
|
||||
|
||||
for l in libs: print (str(l))
|
||||
|
||||
for lib in libs:
|
||||
|
||||
print(f'\nAnalysing {lib}')
|
||||
try:
|
||||
libname, cells = collect(lib)
|
||||
print(f"Library name: {libname}, found {len(cells)} cells")
|
||||
paragraph = generate_rst(lib, libname, cells)
|
||||
library_dir = pathlib.Path(lib)
|
||||
cell_list_file = pathlib.Path(library_dir, "cell-list.rst")
|
||||
except:
|
||||
print(f' ERROR: failed to fetch cell list')
|
||||
continue
|
||||
try:
|
||||
with(open(str(cell_list_file), "w")) as c:
|
||||
c.write(paragraph)
|
||||
print(f'Generated {cell_list_file}')
|
||||
AppendToReadme(cell_list_file)
|
||||
print(f'Appended to README')
|
||||
except FileNotFoundError:
|
||||
print(f" ERROR: Failed to create {str(cell_list_file)}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
Loading…
Reference in New Issue