Skip to content

Commit

Permalink
Merge pull request #266 from antmicro/86-cell_cross_index
Browse files Browse the repository at this point in the history
86 cell cross index
  • Loading branch information
mithro committed May 10, 2023
2 parents 8327221 + 37fb6cd commit f77cbc2
Show file tree
Hide file tree
Showing 7 changed files with 561 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .readthedocs.yml
Expand Up @@ -31,6 +31,11 @@ conda:
submodules:
include:
- libraries/sky130_fd_io/latest
- libraries/sky130_fd_sc_hd/latest
- libraries/sky130_fd_sc_hdll/latest
- libraries/sky130_fd_sc_hs/latest
- libraries/sky130_fd_sc_ls/latest
- libraries/sky130_fd_sc_ms/latest
recursive: false

formats:
Expand Down
1 change: 1 addition & 0 deletions docs/_ext/skywater_pdk
9 changes: 7 additions & 2 deletions docs/conf.py
Expand Up @@ -31,9 +31,9 @@
import docutils
import os
import re
# import sys
import sys
# sys.path.insert(0, os.path.abspath('.'))

sys.path.insert(0, os.path.abspath('./_ext'))

# -- Project information -----------------------------------------------------

Expand Down Expand Up @@ -67,6 +67,8 @@
'sphinx.ext.todo',
'sphinxcontrib_hdl_diagrams',
'sphinxcontrib.bibtex',
'skywater_pdk.cells.cross_index',
'skywater_pdk.cells.generate.readme',
]

bibtex_default_style = 'plain'
Expand Down Expand Up @@ -410,3 +412,6 @@ def setup(app):
app.add_role('lib', lib_role)
app.add_role('cell', cell_role)
app.add_role('model', cell_role)

app.emit("cells_generate_readme", 'contents/libraries/*/cells/*')

1 change: 1 addition & 0 deletions docs/contents/cell-index.rst
@@ -0,0 +1 @@
.. cross_index:: libraries/*
5 changes: 5 additions & 0 deletions docs/contents/libraries.rst
Expand Up @@ -146,3 +146,8 @@ The SKY130 currently offers two :lib_type:`build space` libraries. Build space l

libraries/sky130_ef_io/README

.. toctree::
:maxdepth: 1
:name: Cells in libraries cross-index

cell-index
307 changes: 307 additions & 0 deletions scripts/python-skywater-pdk/skywater_pdk/cells/cross_index.py
@@ -0,0 +1,307 @@
#!/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

verbose = False

# using a list-table here to allow for easier line breaks in description
rst_header_line_char = '-'
rst_header = 'Cells in libraries cross-index'
rst_template ="""\
{header_line}
{header_underline}
.. list-table::
:header-rows: 1
* - Cell name
- {lib_suffixes}
- Number of libraries
{cell_list}
"""

cell_template = """\
* - {cell_name}
- {lib_suffixes_match}
- {lib_count}
"""

tab_entry = '\n - '

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
define_data = json.load(open(p))
if not define_data['type'] == 'cell':
continue
cells.add(p)
if libname is None:
libname = define_data['library']

cells = list(sorted(cells))
if not len(cells):
raise FileNotFoundError("No cell definitions found")
assert len(libname) > 0
return libname, cells

def get_cell_names(cells):
"""Get cell names from definition filess
Parameters
----------
cells: list of pathlib.Path
List of paths to JSON description files
Returns
-------
cell_list: list of str
List of cell names
"""

cell_list = []

for cell in cells:
with open(str(cell), "r") as c:
cell_json = json.load(c)
cell_list.append( cell_json['name'] )
return cell_list


def generate_crosstable (cells_lib, link_template=''):
"""Generate the RST paragraph containing cell cross reference table
Parameters:
cells_lib: dictionary with list of libraries per cell name [dict]
link_template: cell README generic path (with {lib} and {cell} tags) [str]
Returns:
paragraph: Generated paragraph [str]
"""

assert isinstance (cells_lib, dict)

paragraph = ""
cell_list = ""

lib_suffixes = set()
for v in cells_lib.values():
lib_suffixes.update( [lib.rpartition('_')[2] for lib in v] )
lib_suffixes = list(lib_suffixes)
lib_suffixes.sort()
#print (lib_suffixes)

for c in sorted(cells_lib):
ls = {} # dictionary of cell library shorts (suffixes)
for lib in cells_lib[c]:
ls [lib.rpartition('_')[2]] = lib
mark = ' :doc:`x <' + link_template + '>`' # lib match mark with link
suff_match = [ mark.format(cell=c,lib=ls[s]) if s in ls else '' for s in lib_suffixes ]
cell_list += cell_template.format(
cell_name = c,
lib_suffixes_match = tab_entry.join(suff_match),
lib_count = str (len(ls))
)

paragraph = rst_template.format(
header_line = rst_header,
header_underline = rst_header_line_char * len(rst_header),
lib_suffixes = tab_entry.join(lib_suffixes),
cell_list = cell_list
)
return paragraph


def cells_in_libs (libpaths):
"""Generate the RST paragraph containing cell cross reference table
Parameters:
libpaths: list of cell library paths [list of pathlib.Path]
Returns:
cells_lib: dictionary with list of libraries containing each cell name [dict]
"""

lib_dirs = [pathlib.Path(d) for d in libpaths]
lib_dirs = [d for d in lib_dirs if d.is_dir()]
libs_toc = dict()

for lib in lib_dirs:
try:
libname, cells = collect(lib)
if verbose:
print(f"{lib} \tLibrary name: {libname}, found {len(cells)} cells")
libs_toc[libname] = get_cell_names(cells)
except FileNotFoundError:
if verbose:
print (f'{lib} \t- no cells found')

all_cells = set()
cells_lib = {}
for lib,cells in libs_toc.items():
all_cells.update(set(cells))
for c in cells:
cells_lib[c] = cells_lib.get(c, []) + [lib]

return cells_lib



# --- Sphinx extension wrapper ---

class CellCrossIndex(Directive):

required_arguments = 1
optional_arguments = 1
has_content = True

def run(self):
env = self.state.document.settings.env
dirname = env.docname.rpartition('/')[0]
arg = self.arguments[0]
arg = dirname + '/' + arg
output = dirname + '/' + self.arguments[1] if len(self.arguments)>2 else None

path = pathlib.Path(arg).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)
paths = [d.resolve() for d in paths if d.is_dir()]

cells_lib = cells_in_libs ( list(paths) )
celllink = self.arguments[0].replace('*','{lib}') + '/cells/{cell}/README'
paragraph = generate_crosstable (cells_lib,celllink)

if output is None: # dynamic output
# parse rst string to docutils nodes
rst = ViewList()
for i,line in enumerate(paragraph.split('\n')):
rst.append(line, "cell-index-tmp.rst", i+1)
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, rst, node)
return node.children
else: # file output
if not output.endswith('.rst'):
output += '.rst'
with open(str(output),'w') as f:
f.write(paragraph)
paragraph_node = nodes.paragraph()
return [paragraph_node]

def setup(app):
app.add_directive("cross_index", CellCrossIndex)

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

# --- stand alone, command line operation ---

def main():
global verbose
parser = argparse.ArgumentParser()
alllibpath = '../../../libraries/*/latest'
celllink = 'libraries/{lib}/cells/{cell}/README'

parser.add_argument(
"-v",
"--verbose",
help="increase verbosity",
action="store_true"
)
parser.add_argument(
"--all_libs",
help="process all libs in "+alllibpath,
action="store_true")
parser.add_argument(
"libraries_dirs",
help="Paths to the library directories. Eg. " + alllibpath,
type=pathlib.Path,
nargs="*")
parser.add_argument(
"-o",
"--outfile",
help="Output file name",
type=pathlib.Path,
default=pathlib.Path('./cell-index.rst'))
parser.add_argument(
"-c",
"--celllink",
help="Specify cell link template. Default: '" + celllink +"'",
type=str,
default=celllink)

args = parser.parse_args()
verbose = args.verbose

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.libraries_dirs = list(paths)


cells_lib = cells_in_libs (args.libraries_dirs)
par = generate_crosstable (cells_lib,args.celllink)

with open(str(args.outfile),'w') as f:
f.write(par)


if __name__ == "__main__":
sys.exit(main())

0 comments on commit f77cbc2

Please sign in to comment.