siliconcompiler 0.26.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- siliconcompiler/__init__.py +24 -0
- siliconcompiler/__main__.py +12 -0
- siliconcompiler/_common.py +49 -0
- siliconcompiler/_metadata.py +36 -0
- siliconcompiler/apps/__init__.py +0 -0
- siliconcompiler/apps/_common.py +76 -0
- siliconcompiler/apps/sc.py +92 -0
- siliconcompiler/apps/sc_dashboard.py +94 -0
- siliconcompiler/apps/sc_issue.py +178 -0
- siliconcompiler/apps/sc_remote.py +199 -0
- siliconcompiler/apps/sc_server.py +39 -0
- siliconcompiler/apps/sc_show.py +142 -0
- siliconcompiler/apps/smake.py +232 -0
- siliconcompiler/checklists/__init__.py +0 -0
- siliconcompiler/checklists/oh_tapeout.py +41 -0
- siliconcompiler/core.py +3221 -0
- siliconcompiler/data/RobotoMono/LICENSE.txt +202 -0
- siliconcompiler/data/RobotoMono/RobotoMono-Regular.ttf +0 -0
- siliconcompiler/data/heartbeat.v +18 -0
- siliconcompiler/data/logo.png +0 -0
- siliconcompiler/flowgraph.py +570 -0
- siliconcompiler/flows/__init__.py +0 -0
- siliconcompiler/flows/_common.py +67 -0
- siliconcompiler/flows/asicflow.py +180 -0
- siliconcompiler/flows/asictopflow.py +38 -0
- siliconcompiler/flows/dvflow.py +86 -0
- siliconcompiler/flows/fpgaflow.py +202 -0
- siliconcompiler/flows/generate_openroad_rcx.py +66 -0
- siliconcompiler/flows/lintflow.py +35 -0
- siliconcompiler/flows/screenshotflow.py +51 -0
- siliconcompiler/flows/showflow.py +59 -0
- siliconcompiler/flows/signoffflow.py +53 -0
- siliconcompiler/flows/synflow.py +128 -0
- siliconcompiler/fpgas/__init__.py +0 -0
- siliconcompiler/fpgas/lattice_ice40.py +42 -0
- siliconcompiler/fpgas/vpr_example.py +109 -0
- siliconcompiler/issue.py +300 -0
- siliconcompiler/libs/__init__.py +0 -0
- siliconcompiler/libs/asap7sc7p5t.py +8 -0
- siliconcompiler/libs/gf180mcu.py +8 -0
- siliconcompiler/libs/nangate45.py +8 -0
- siliconcompiler/libs/sky130hd.py +8 -0
- siliconcompiler/libs/sky130io.py +8 -0
- siliconcompiler/package.py +412 -0
- siliconcompiler/pdks/__init__.py +0 -0
- siliconcompiler/pdks/asap7.py +8 -0
- siliconcompiler/pdks/freepdk45.py +8 -0
- siliconcompiler/pdks/gf180.py +8 -0
- siliconcompiler/pdks/skywater130.py +8 -0
- siliconcompiler/remote/__init__.py +36 -0
- siliconcompiler/remote/client.py +891 -0
- siliconcompiler/remote/schema.py +106 -0
- siliconcompiler/remote/server.py +507 -0
- siliconcompiler/remote/server_schema/requests/cancel_job.json +51 -0
- siliconcompiler/remote/server_schema/requests/check_progress.json +61 -0
- siliconcompiler/remote/server_schema/requests/check_server.json +38 -0
- siliconcompiler/remote/server_schema/requests/delete_job.json +51 -0
- siliconcompiler/remote/server_schema/requests/get_results.json +48 -0
- siliconcompiler/remote/server_schema/requests/remote_run.json +40 -0
- siliconcompiler/remote/server_schema/responses/cancel_job.json +18 -0
- siliconcompiler/remote/server_schema/responses/check_progress.json +30 -0
- siliconcompiler/remote/server_schema/responses/check_server.json +32 -0
- siliconcompiler/remote/server_schema/responses/delete_job.json +18 -0
- siliconcompiler/remote/server_schema/responses/get_results.json +21 -0
- siliconcompiler/remote/server_schema/responses/remote_run.json +25 -0
- siliconcompiler/report/__init__.py +13 -0
- siliconcompiler/report/html_report.py +74 -0
- siliconcompiler/report/report.py +355 -0
- siliconcompiler/report/streamlit_report.py +137 -0
- siliconcompiler/report/streamlit_viewer.py +944 -0
- siliconcompiler/report/summary_image.py +117 -0
- siliconcompiler/report/summary_table.py +105 -0
- siliconcompiler/report/utils.py +163 -0
- siliconcompiler/scheduler/__init__.py +2092 -0
- siliconcompiler/scheduler/docker_runner.py +253 -0
- siliconcompiler/scheduler/run_node.py +138 -0
- siliconcompiler/scheduler/send_messages.py +178 -0
- siliconcompiler/scheduler/slurm.py +208 -0
- siliconcompiler/scheduler/validation/email_credentials.json +54 -0
- siliconcompiler/schema/__init__.py +7 -0
- siliconcompiler/schema/schema_cfg.py +4014 -0
- siliconcompiler/schema/schema_obj.py +1841 -0
- siliconcompiler/schema/utils.py +93 -0
- siliconcompiler/sphinx_ext/__init__.py +0 -0
- siliconcompiler/sphinx_ext/dynamicgen.py +1006 -0
- siliconcompiler/sphinx_ext/schemagen.py +221 -0
- siliconcompiler/sphinx_ext/utils.py +166 -0
- siliconcompiler/targets/__init__.py +0 -0
- siliconcompiler/targets/asap7_demo.py +68 -0
- siliconcompiler/targets/asic_demo.py +38 -0
- siliconcompiler/targets/fpgaflow_demo.py +47 -0
- siliconcompiler/targets/freepdk45_demo.py +59 -0
- siliconcompiler/targets/gf180_demo.py +77 -0
- siliconcompiler/targets/skywater130_demo.py +70 -0
- siliconcompiler/templates/email/general.j2 +66 -0
- siliconcompiler/templates/email/summary.j2 +43 -0
- siliconcompiler/templates/issue/README.txt +26 -0
- siliconcompiler/templates/issue/run.sh +6 -0
- siliconcompiler/templates/report/bootstrap.min.css +7 -0
- siliconcompiler/templates/report/bootstrap.min.js +7 -0
- siliconcompiler/templates/report/bootstrap_LICENSE.md +24 -0
- siliconcompiler/templates/report/sc_report.j2 +427 -0
- siliconcompiler/templates/slurm/run.sh +9 -0
- siliconcompiler/templates/tcl/manifest.tcl.j2 +137 -0
- siliconcompiler/tools/__init__.py +0 -0
- siliconcompiler/tools/_common/__init__.py +432 -0
- siliconcompiler/tools/_common/asic.py +115 -0
- siliconcompiler/tools/_common/sdc/sc_constraints.sdc +76 -0
- siliconcompiler/tools/_common/tcl/sc_pin_constraints.tcl +63 -0
- siliconcompiler/tools/bambu/bambu.py +32 -0
- siliconcompiler/tools/bambu/convert.py +77 -0
- siliconcompiler/tools/bluespec/bluespec.py +40 -0
- siliconcompiler/tools/bluespec/convert.py +103 -0
- siliconcompiler/tools/builtin/_common.py +155 -0
- siliconcompiler/tools/builtin/builtin.py +26 -0
- siliconcompiler/tools/builtin/concatenate.py +85 -0
- siliconcompiler/tools/builtin/join.py +27 -0
- siliconcompiler/tools/builtin/maximum.py +46 -0
- siliconcompiler/tools/builtin/minimum.py +57 -0
- siliconcompiler/tools/builtin/mux.py +70 -0
- siliconcompiler/tools/builtin/nop.py +38 -0
- siliconcompiler/tools/builtin/verify.py +83 -0
- siliconcompiler/tools/chisel/SCDriver.scala +10 -0
- siliconcompiler/tools/chisel/build.sbt +27 -0
- siliconcompiler/tools/chisel/chisel.py +37 -0
- siliconcompiler/tools/chisel/convert.py +140 -0
- siliconcompiler/tools/execute/exec_input.py +41 -0
- siliconcompiler/tools/execute/execute.py +17 -0
- siliconcompiler/tools/genfasm/bitstream.py +61 -0
- siliconcompiler/tools/genfasm/genfasm.py +40 -0
- siliconcompiler/tools/ghdl/convert.py +87 -0
- siliconcompiler/tools/ghdl/ghdl.py +41 -0
- siliconcompiler/tools/icarus/compile.py +87 -0
- siliconcompiler/tools/icarus/icarus.py +36 -0
- siliconcompiler/tools/icepack/bitstream.py +20 -0
- siliconcompiler/tools/icepack/icepack.py +43 -0
- siliconcompiler/tools/klayout/export.py +117 -0
- siliconcompiler/tools/klayout/klayout.py +119 -0
- siliconcompiler/tools/klayout/klayout_export.py +205 -0
- siliconcompiler/tools/klayout/klayout_operations.py +363 -0
- siliconcompiler/tools/klayout/klayout_show.py +242 -0
- siliconcompiler/tools/klayout/klayout_utils.py +176 -0
- siliconcompiler/tools/klayout/operations.py +194 -0
- siliconcompiler/tools/klayout/screenshot.py +98 -0
- siliconcompiler/tools/klayout/show.py +101 -0
- siliconcompiler/tools/magic/drc.py +49 -0
- siliconcompiler/tools/magic/extspice.py +19 -0
- siliconcompiler/tools/magic/magic.py +85 -0
- siliconcompiler/tools/magic/sc_drc.tcl +96 -0
- siliconcompiler/tools/magic/sc_extspice.tcl +54 -0
- siliconcompiler/tools/magic/sc_magic.tcl +47 -0
- siliconcompiler/tools/montage/montage.py +30 -0
- siliconcompiler/tools/montage/tile.py +66 -0
- siliconcompiler/tools/netgen/count_lvs.py +132 -0
- siliconcompiler/tools/netgen/lvs.py +90 -0
- siliconcompiler/tools/netgen/netgen.py +36 -0
- siliconcompiler/tools/netgen/sc_lvs.tcl +46 -0
- siliconcompiler/tools/nextpnr/apr.py +24 -0
- siliconcompiler/tools/nextpnr/nextpnr.py +59 -0
- siliconcompiler/tools/openfpgaloader/openfpgaloader.py +39 -0
- siliconcompiler/tools/openroad/__init__.py +0 -0
- siliconcompiler/tools/openroad/cts.py +45 -0
- siliconcompiler/tools/openroad/dfm.py +66 -0
- siliconcompiler/tools/openroad/export.py +131 -0
- siliconcompiler/tools/openroad/floorplan.py +70 -0
- siliconcompiler/tools/openroad/openroad.py +977 -0
- siliconcompiler/tools/openroad/physyn.py +27 -0
- siliconcompiler/tools/openroad/place.py +41 -0
- siliconcompiler/tools/openroad/rcx_bench.py +95 -0
- siliconcompiler/tools/openroad/rcx_extract.py +34 -0
- siliconcompiler/tools/openroad/route.py +45 -0
- siliconcompiler/tools/openroad/screenshot.py +60 -0
- siliconcompiler/tools/openroad/scripts/sc_apr.tcl +499 -0
- siliconcompiler/tools/openroad/scripts/sc_cts.tcl +64 -0
- siliconcompiler/tools/openroad/scripts/sc_dfm.tcl +20 -0
- siliconcompiler/tools/openroad/scripts/sc_export.tcl +98 -0
- siliconcompiler/tools/openroad/scripts/sc_floorplan.tcl +413 -0
- siliconcompiler/tools/openroad/scripts/sc_metrics.tcl +158 -0
- siliconcompiler/tools/openroad/scripts/sc_physyn.tcl +7 -0
- siliconcompiler/tools/openroad/scripts/sc_place.tcl +84 -0
- siliconcompiler/tools/openroad/scripts/sc_procs.tcl +423 -0
- siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +63 -0
- siliconcompiler/tools/openroad/scripts/sc_rcx_bench.tcl +20 -0
- siliconcompiler/tools/openroad/scripts/sc_rcx_extract.tcl +12 -0
- siliconcompiler/tools/openroad/scripts/sc_route.tcl +133 -0
- siliconcompiler/tools/openroad/scripts/sc_screenshot.tcl +21 -0
- siliconcompiler/tools/openroad/scripts/sc_write.tcl +5 -0
- siliconcompiler/tools/openroad/scripts/sc_write_images.tcl +361 -0
- siliconcompiler/tools/openroad/show.py +94 -0
- siliconcompiler/tools/openroad/templates/pex.tcl +8 -0
- siliconcompiler/tools/opensta/__init__.py +101 -0
- siliconcompiler/tools/opensta/report_libraries.py +28 -0
- siliconcompiler/tools/opensta/scripts/sc_procs.tcl +47 -0
- siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +74 -0
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +268 -0
- siliconcompiler/tools/opensta/timing.py +214 -0
- siliconcompiler/tools/slang/__init__.py +49 -0
- siliconcompiler/tools/slang/lint.py +101 -0
- siliconcompiler/tools/surelog/__init__.py +123 -0
- siliconcompiler/tools/surelog/parse.py +183 -0
- siliconcompiler/tools/surelog/templates/output.v +7 -0
- siliconcompiler/tools/sv2v/convert.py +46 -0
- siliconcompiler/tools/sv2v/sv2v.py +37 -0
- siliconcompiler/tools/template/template.py +125 -0
- siliconcompiler/tools/verilator/compile.py +139 -0
- siliconcompiler/tools/verilator/lint.py +19 -0
- siliconcompiler/tools/verilator/parse.py +27 -0
- siliconcompiler/tools/verilator/verilator.py +172 -0
- siliconcompiler/tools/vivado/__init__.py +7 -0
- siliconcompiler/tools/vivado/bitstream.py +21 -0
- siliconcompiler/tools/vivado/place.py +21 -0
- siliconcompiler/tools/vivado/route.py +21 -0
- siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +6 -0
- siliconcompiler/tools/vivado/scripts/sc_place.tcl +2 -0
- siliconcompiler/tools/vivado/scripts/sc_route.tcl +4 -0
- siliconcompiler/tools/vivado/scripts/sc_run.tcl +45 -0
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +25 -0
- siliconcompiler/tools/vivado/syn_fpga.py +20 -0
- siliconcompiler/tools/vivado/vivado.py +147 -0
- siliconcompiler/tools/vpr/_json_constraint.py +63 -0
- siliconcompiler/tools/vpr/_xml_constraint.py +109 -0
- siliconcompiler/tools/vpr/place.py +137 -0
- siliconcompiler/tools/vpr/route.py +124 -0
- siliconcompiler/tools/vpr/screenshot.py +54 -0
- siliconcompiler/tools/vpr/show.py +88 -0
- siliconcompiler/tools/vpr/vpr.py +357 -0
- siliconcompiler/tools/xyce/xyce.py +36 -0
- siliconcompiler/tools/yosys/lec.py +56 -0
- siliconcompiler/tools/yosys/prepareLib.py +59 -0
- siliconcompiler/tools/yosys/sc_lec.tcl +84 -0
- siliconcompiler/tools/yosys/sc_syn.tcl +79 -0
- siliconcompiler/tools/yosys/syn_asic.py +565 -0
- siliconcompiler/tools/yosys/syn_asic.tcl +377 -0
- siliconcompiler/tools/yosys/syn_asic_fpga_shared.tcl +31 -0
- siliconcompiler/tools/yosys/syn_fpga.py +146 -0
- siliconcompiler/tools/yosys/syn_fpga.tcl +233 -0
- siliconcompiler/tools/yosys/syn_strategies.tcl +81 -0
- siliconcompiler/tools/yosys/techmaps/lcu_kogge_stone.v +39 -0
- siliconcompiler/tools/yosys/templates/abc.const +2 -0
- siliconcompiler/tools/yosys/yosys.py +147 -0
- siliconcompiler/units.py +259 -0
- siliconcompiler/use.py +177 -0
- siliconcompiler/utils/__init__.py +423 -0
- siliconcompiler/utils/asic.py +158 -0
- siliconcompiler/utils/showtools.py +25 -0
- siliconcompiler-0.26.5.dist-info/LICENSE +190 -0
- siliconcompiler-0.26.5.dist-info/METADATA +195 -0
- siliconcompiler-0.26.5.dist-info/RECORD +251 -0
- siliconcompiler-0.26.5.dist-info/WHEEL +5 -0
- siliconcompiler-0.26.5.dist-info/entry_points.txt +12 -0
- siliconcompiler-0.26.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1006 @@
|
|
|
1
|
+
'''Sphinx extension that provides directives for automatically generating
|
|
2
|
+
documentation for dynamically loaded modules used by SC.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from docutils import nodes
|
|
6
|
+
from sphinx.util.nodes import nested_parse_with_titles
|
|
7
|
+
from docutils.statemachine import ViewList
|
|
8
|
+
from sphinx.util.docutils import SphinxDirective
|
|
9
|
+
from sphinx.domains.std import StandardDomain
|
|
10
|
+
from sphinx.addnodes import pending_xref
|
|
11
|
+
import docutils
|
|
12
|
+
|
|
13
|
+
import importlib
|
|
14
|
+
import pkgutil
|
|
15
|
+
import os
|
|
16
|
+
import subprocess
|
|
17
|
+
|
|
18
|
+
import siliconcompiler
|
|
19
|
+
from siliconcompiler.schema import Schema, utils
|
|
20
|
+
from siliconcompiler.sphinx_ext.utils import (
|
|
21
|
+
strong,
|
|
22
|
+
code,
|
|
23
|
+
para,
|
|
24
|
+
keypath,
|
|
25
|
+
build_table,
|
|
26
|
+
build_list,
|
|
27
|
+
build_section,
|
|
28
|
+
build_section_with_target,
|
|
29
|
+
link,
|
|
30
|
+
image,
|
|
31
|
+
get_ref_id,
|
|
32
|
+
literalblock
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
#############
|
|
36
|
+
# Helpers
|
|
37
|
+
#############
|
|
38
|
+
|
|
39
|
+
# We need this in a few places, so just make it global
|
|
40
|
+
SC_ROOT = os.path.abspath(f'{__file__}/../../../')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_schema_value_table(cfg, refdoc, keypath_prefix=None, skip_zero_weight=False):
|
|
44
|
+
'''Helper function for displaying values set in schema as a docutils table.'''
|
|
45
|
+
table = [[strong('Keypath'), strong('Value')]]
|
|
46
|
+
|
|
47
|
+
# Nest received dictionary under keypath_prefix
|
|
48
|
+
rooted_cfg = cfg
|
|
49
|
+
if keypath_prefix:
|
|
50
|
+
for key in reversed(keypath_prefix):
|
|
51
|
+
rooted_cfg = {key: rooted_cfg}
|
|
52
|
+
|
|
53
|
+
def format_value(is_list, value):
|
|
54
|
+
if is_list:
|
|
55
|
+
if len(value) > 1:
|
|
56
|
+
val_node = build_list([code(v) for v in value])
|
|
57
|
+
elif len(value) > 0:
|
|
58
|
+
val_node = code(value[0])
|
|
59
|
+
else:
|
|
60
|
+
val_node = para('')
|
|
61
|
+
else:
|
|
62
|
+
val_node = code(value)
|
|
63
|
+
return val_node
|
|
64
|
+
|
|
65
|
+
def format_single_value_file(value, package):
|
|
66
|
+
val_list = [code(value)]
|
|
67
|
+
if package:
|
|
68
|
+
val_list.append(nodes.inline(text=', '))
|
|
69
|
+
val_list.append(code(package))
|
|
70
|
+
return nodes.paragraph('', '', *val_list)
|
|
71
|
+
|
|
72
|
+
def format_value_file(is_list, value, package):
|
|
73
|
+
if is_list:
|
|
74
|
+
if len(value) > 1:
|
|
75
|
+
val_node = build_list([
|
|
76
|
+
format_single_value_file(v, p) for v, p in zip(value, package)])
|
|
77
|
+
elif len(value) > 0:
|
|
78
|
+
return format_single_value_file(value[0], package[0])
|
|
79
|
+
else:
|
|
80
|
+
val_node = para('')
|
|
81
|
+
else:
|
|
82
|
+
val_node = format_single_value_file(value, package)
|
|
83
|
+
return val_node
|
|
84
|
+
|
|
85
|
+
schema = Schema(rooted_cfg)
|
|
86
|
+
for kp in schema.allkeys():
|
|
87
|
+
if skip_zero_weight and \
|
|
88
|
+
len(kp) == 6 and kp[0] == 'flowgraph' and kp[-2] == 'weight' and \
|
|
89
|
+
schema.get(*kp) == 0:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
values = schema._getvals(*kp, return_defvalue=False)
|
|
93
|
+
if values:
|
|
94
|
+
# take first of multiple possible values
|
|
95
|
+
value, step, index = values[0]
|
|
96
|
+
val_type = schema.get(*kp, field='type')
|
|
97
|
+
is_filedir = 'file' in val_type or 'dir' in val_type
|
|
98
|
+
# Don't display false booleans
|
|
99
|
+
if val_type == 'bool' and value is False:
|
|
100
|
+
continue
|
|
101
|
+
if is_filedir:
|
|
102
|
+
val_node = format_value_file(val_type.startswith('['), value,
|
|
103
|
+
schema.get(*kp, field='package',
|
|
104
|
+
step=step, index=index))
|
|
105
|
+
else:
|
|
106
|
+
val_node = format_value(val_type.startswith('['), value)
|
|
107
|
+
|
|
108
|
+
# HTML builder fails if we don't make a text node the parent of the
|
|
109
|
+
# reference node returned by keypath()
|
|
110
|
+
p = nodes.paragraph()
|
|
111
|
+
p += keypath(kp, refdoc)
|
|
112
|
+
table.append([p, val_node])
|
|
113
|
+
|
|
114
|
+
if len(table) > 1:
|
|
115
|
+
# This colspec creates two columns of equal width that fill the entire
|
|
116
|
+
# page, and adds line breaks if table cell contents are longer than one
|
|
117
|
+
# line. "\X" is defined by Sphinx, otherwise this is standard LaTeX.
|
|
118
|
+
colspec = r'{|\X{1}{2}|\X{1}{2}|}'
|
|
119
|
+
return build_table(table, colspec=colspec)
|
|
120
|
+
else:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def build_package_table(schema):
|
|
125
|
+
def collect_packages(cfg):
|
|
126
|
+
packages = []
|
|
127
|
+
if Schema._is_leaf(cfg):
|
|
128
|
+
if 'dir' in cfg['type'] or 'file' in cfg['type']:
|
|
129
|
+
for _, index_data in cfg['node'].items():
|
|
130
|
+
for _, data in index_data.items():
|
|
131
|
+
packages.extend(data['package'])
|
|
132
|
+
else:
|
|
133
|
+
for key in cfg:
|
|
134
|
+
packages.extend(collect_packages(cfg[key]))
|
|
135
|
+
packages = [p for p in packages if p]
|
|
136
|
+
return list(set(packages))
|
|
137
|
+
|
|
138
|
+
schema = Schema(cfg=schema)
|
|
139
|
+
packages = collect_packages(schema.cfg)
|
|
140
|
+
|
|
141
|
+
if not packages:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
# This colspec creates two columns of equal width that fill the entire
|
|
145
|
+
# page, and adds line breaks if table cell contents are longer than one
|
|
146
|
+
# line. "\X" is defined by Sphinx, otherwise this is standard LaTeX.
|
|
147
|
+
colspec = r'{|\X{1}{2}|\X{1}{2}|}'
|
|
148
|
+
|
|
149
|
+
table = [[strong('Package'), strong('Specifications')]]
|
|
150
|
+
for package in packages:
|
|
151
|
+
path = schema.get('package', 'source', package, 'path')
|
|
152
|
+
ref = schema.get('package', 'source', package, 'ref')
|
|
153
|
+
|
|
154
|
+
specs = [nodes.paragraph('', 'Path: ', code(path))]
|
|
155
|
+
if ref:
|
|
156
|
+
specs.append(nodes.paragraph('', 'Reference: ', code(ref)))
|
|
157
|
+
|
|
158
|
+
table.append([para(package), build_list(specs)])
|
|
159
|
+
|
|
160
|
+
return build_table(table, colspec=colspec)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
#############
|
|
164
|
+
# Base class
|
|
165
|
+
#############
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def flag_opt(argument):
|
|
169
|
+
if argument is not None:
|
|
170
|
+
raise ValueError('Flag should not have content')
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class DynamicGen(SphinxDirective):
|
|
175
|
+
'''Base class for all three directives provided by this extension.
|
|
176
|
+
|
|
177
|
+
Each child class implements a directive by overriding the display_config()
|
|
178
|
+
method and setting a PATH member variable.
|
|
179
|
+
'''
|
|
180
|
+
|
|
181
|
+
option_spec = {'nobuiltins': flag_opt}
|
|
182
|
+
|
|
183
|
+
def document_module(self, module, modname, path):
|
|
184
|
+
'''Build section documenting given module and name.'''
|
|
185
|
+
print(f'Generating docs for module {modname}...')
|
|
186
|
+
|
|
187
|
+
s = build_section_with_target(modname, self.get_document_ref_key(modname),
|
|
188
|
+
self.state.document)
|
|
189
|
+
|
|
190
|
+
# Attempt to use module doc string first
|
|
191
|
+
if not self.generate_documentation_from_object(module, path, s):
|
|
192
|
+
setup = self.get_setup_method(module)
|
|
193
|
+
# Then use setup doc string
|
|
194
|
+
self.generate_documentation_from_object(setup, path, s)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
chips = self.configure_chip_for_docs(module)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print("Failed:", e)
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
if not chips:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
if not isinstance(chips, list):
|
|
206
|
+
chips = [chips]
|
|
207
|
+
|
|
208
|
+
for chip in chips:
|
|
209
|
+
extra_content = self.extra_content(chip, modname)
|
|
210
|
+
if extra_content is not None:
|
|
211
|
+
s += extra_content
|
|
212
|
+
|
|
213
|
+
package_info = self.package_information(chip, modname)
|
|
214
|
+
if package_info is not None:
|
|
215
|
+
s += [package_info]
|
|
216
|
+
|
|
217
|
+
disp = self.display_config(chip, modname)
|
|
218
|
+
if disp:
|
|
219
|
+
s += disp
|
|
220
|
+
|
|
221
|
+
child_content = self.child_content(path, module, modname)
|
|
222
|
+
if child_content is not None:
|
|
223
|
+
s += child_content
|
|
224
|
+
|
|
225
|
+
return s
|
|
226
|
+
|
|
227
|
+
def run(self):
|
|
228
|
+
'''Main entry point of directive.'''
|
|
229
|
+
sections = []
|
|
230
|
+
self.env.note_dependency(__file__)
|
|
231
|
+
self.env.note_dependency(utils.__file__)
|
|
232
|
+
|
|
233
|
+
for module, modname in self.get_modules():
|
|
234
|
+
path = module.__file__
|
|
235
|
+
self.env.note_dependency(path)
|
|
236
|
+
docs = self.document_module(module, modname, path)
|
|
237
|
+
if docs is not None:
|
|
238
|
+
sections.append((docs, modname))
|
|
239
|
+
|
|
240
|
+
if len(sections) > 0:
|
|
241
|
+
# Sort sections by module name
|
|
242
|
+
sections = sorted(sections, key=lambda t: t[1])
|
|
243
|
+
# Strip off modname so we just return list of docutils sections
|
|
244
|
+
sections, _ = zip(*sections)
|
|
245
|
+
|
|
246
|
+
return list(sections)
|
|
247
|
+
|
|
248
|
+
def get_modules(self):
|
|
249
|
+
'''Gets dynamic modules under `self.PATH`.
|
|
250
|
+
|
|
251
|
+
This function explicitly searches builtins.
|
|
252
|
+
'''
|
|
253
|
+
builtins_dir = f'{SC_ROOT}/siliconcompiler/{self.PATH}'
|
|
254
|
+
if 'nobuiltins' not in self.options:
|
|
255
|
+
modules = self.get_modules_in_dir(builtins_dir)
|
|
256
|
+
else:
|
|
257
|
+
modules = []
|
|
258
|
+
|
|
259
|
+
external_paths = os.getenv(self.SEARCH_ENV, "").split(':')
|
|
260
|
+
for scpath in external_paths:
|
|
261
|
+
if not scpath:
|
|
262
|
+
continue
|
|
263
|
+
if not os.path.isdir(scpath):
|
|
264
|
+
print(f'{scpath} not found')
|
|
265
|
+
raise FileNotFoundError(scpath)
|
|
266
|
+
if builtins_dir == scpath:
|
|
267
|
+
continue
|
|
268
|
+
modules.extend(self.get_modules_in_dir(scpath))
|
|
269
|
+
|
|
270
|
+
return modules
|
|
271
|
+
|
|
272
|
+
def get_modules_in_dir(self, module_dir):
|
|
273
|
+
'''Routine for getting modules and their names from a certain
|
|
274
|
+
directory.'''
|
|
275
|
+
modules = []
|
|
276
|
+
for importer, modname, _ in pkgutil.iter_modules([module_dir]):
|
|
277
|
+
module = importer.find_module(modname).load_module(modname)
|
|
278
|
+
modules.append((module, modname))
|
|
279
|
+
|
|
280
|
+
return modules
|
|
281
|
+
|
|
282
|
+
def parse_rst(self, content, s):
|
|
283
|
+
'''Helper for parsing reStructuredText content, adding it directly to
|
|
284
|
+
section `s`.'''
|
|
285
|
+
rst = ViewList()
|
|
286
|
+
# use fake filename 'inline' for error # reporting
|
|
287
|
+
for i, line in enumerate(content.split('\n')):
|
|
288
|
+
rst.append(line, 'inline', i)
|
|
289
|
+
nested_parse_with_titles(self.state, rst, s)
|
|
290
|
+
|
|
291
|
+
def package_information(self, chip, modname):
|
|
292
|
+
packages = build_package_table(chip.schema.cfg)
|
|
293
|
+
if packages:
|
|
294
|
+
sec = build_section('Data sources', self.get_data_source_ref_key(modname, chip.design))
|
|
295
|
+
sec += packages
|
|
296
|
+
return sec
|
|
297
|
+
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
def extra_content(self, chip, modname):
|
|
301
|
+
'''Adds extra content to documentation.
|
|
302
|
+
|
|
303
|
+
May return a list of docutils nodes that will be added to the
|
|
304
|
+
documentation in between a module's docstring and configuration table.
|
|
305
|
+
Otherwise, if return value is None, don't add anything.
|
|
306
|
+
'''
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
def child_content(self, path, module, modname):
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
def generate_documentation_from_object(self, func, path, s):
|
|
313
|
+
# raw docstrings have funky indentation (basically, each line is already
|
|
314
|
+
# indented as much as the function), so we call trim() helper function
|
|
315
|
+
# to clean it up
|
|
316
|
+
docstr = utils.trim(func.__doc__)
|
|
317
|
+
|
|
318
|
+
if docstr:
|
|
319
|
+
self.parse_rst(docstr, s)
|
|
320
|
+
else:
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
builtin = os.path.abspath(path).startswith(SC_ROOT)
|
|
324
|
+
|
|
325
|
+
if builtin:
|
|
326
|
+
relpath = path[len(SC_ROOT) + 1:]
|
|
327
|
+
gh_root = 'https://github.com/siliconcompiler/siliconcompiler/blob/main'
|
|
328
|
+
gh_link = f'{gh_root}/{relpath}'
|
|
329
|
+
filename = os.path.basename(relpath)
|
|
330
|
+
p = para('Setup file: ')
|
|
331
|
+
p += link(gh_link, text=filename)
|
|
332
|
+
s += p
|
|
333
|
+
|
|
334
|
+
return True
|
|
335
|
+
|
|
336
|
+
def _document_free_params(self, cfg, type, key_path, reference_prefix, s):
|
|
337
|
+
if type in cfg:
|
|
338
|
+
cfg = cfg[type]
|
|
339
|
+
else:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
if type == "var":
|
|
343
|
+
type_heading = "Variables"
|
|
344
|
+
elif type == "file":
|
|
345
|
+
type_heading = "Files"
|
|
346
|
+
|
|
347
|
+
table = [[strong('Parameters'), strong('Help')]]
|
|
348
|
+
for key, params in cfg.items():
|
|
349
|
+
if key == "default":
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
key_node = nodes.paragraph()
|
|
353
|
+
key_node += keypath(key_path + [key], self.env.docname,
|
|
354
|
+
key_text=["...", f"'{type}'", f"'{key}'"])
|
|
355
|
+
table.append([key_node, para(params["help"])])
|
|
356
|
+
|
|
357
|
+
if len(table) > 1:
|
|
358
|
+
s += build_section(type_heading, self.get_ref(*reference_prefix, type))
|
|
359
|
+
colspec = r'{|\X{1}{2}|\X{1}{2}|}'
|
|
360
|
+
s += build_table(table, colspec=colspec)
|
|
361
|
+
|
|
362
|
+
def get_make_docs_method(self, module):
|
|
363
|
+
return getattr(module, 'make_docs', None)
|
|
364
|
+
|
|
365
|
+
def get_configure_docs_method(self, module):
|
|
366
|
+
return getattr(module, 'configure_docs', None)
|
|
367
|
+
|
|
368
|
+
def get_setup_method(self, module):
|
|
369
|
+
return getattr(module, 'setup', None)
|
|
370
|
+
|
|
371
|
+
def make_chip(self):
|
|
372
|
+
return siliconcompiler.Chip('<design>')
|
|
373
|
+
|
|
374
|
+
def _handle_make_docs(self, chip, module):
|
|
375
|
+
make_docs = self.get_make_docs_method(module)
|
|
376
|
+
if make_docs:
|
|
377
|
+
new_chip = make_docs(chip)
|
|
378
|
+
if new_chip:
|
|
379
|
+
# make_docs returned something so it's fully configured
|
|
380
|
+
return (new_chip, True)
|
|
381
|
+
else:
|
|
382
|
+
return (chip, False)
|
|
383
|
+
return (None, False)
|
|
384
|
+
|
|
385
|
+
def _handle_setup(self, chip, module):
|
|
386
|
+
setup = self.get_setup_method(module)
|
|
387
|
+
if not setup:
|
|
388
|
+
return None
|
|
389
|
+
new_chip = setup(chip)
|
|
390
|
+
if new_chip:
|
|
391
|
+
return new_chip
|
|
392
|
+
else:
|
|
393
|
+
# Setup didn't return anything so return the Chip object
|
|
394
|
+
return chip
|
|
395
|
+
|
|
396
|
+
def configure_chip_for_docs(self, module):
|
|
397
|
+
chip = self.make_chip()
|
|
398
|
+
docs_chip, docs_configured = self._handle_make_docs(chip, module)
|
|
399
|
+
if docs_chip and docs_configured:
|
|
400
|
+
return docs_chip
|
|
401
|
+
|
|
402
|
+
return self._handle_setup(chip, module)
|
|
403
|
+
|
|
404
|
+
def get_ref_prefix(self):
|
|
405
|
+
return self.REF_PREFIX
|
|
406
|
+
|
|
407
|
+
@staticmethod
|
|
408
|
+
def get_ref_key(*path):
|
|
409
|
+
return '-'.join(path)
|
|
410
|
+
|
|
411
|
+
def get_ref(self, *sections):
|
|
412
|
+
return DynamicGen.get_ref_key(self.get_ref_prefix(), *sections)
|
|
413
|
+
|
|
414
|
+
def get_configuration_ref_key(self, *modname):
|
|
415
|
+
return self.get_ref(*modname, 'configuration')
|
|
416
|
+
|
|
417
|
+
def get_data_source_ref_key(self, *modname):
|
|
418
|
+
return self.get_ref(*modname, 'data_source')
|
|
419
|
+
|
|
420
|
+
def get_document_ref_key(self, *modname):
|
|
421
|
+
return self.get_ref(*modname)
|
|
422
|
+
|
|
423
|
+
def build_config_recursive(self, schema, refdoc, keypath=None, sec_key_prefix=None):
|
|
424
|
+
'''Helper function for displaying schema at each level as tables under nested
|
|
425
|
+
sections.
|
|
426
|
+
|
|
427
|
+
For each item:
|
|
428
|
+
- If it's a leaf, collect it into a table we will display at this
|
|
429
|
+
level
|
|
430
|
+
- Otherwise, recurse and collect sections of lower levels
|
|
431
|
+
'''
|
|
432
|
+
if keypath is None:
|
|
433
|
+
keypath = []
|
|
434
|
+
if sec_key_prefix is None:
|
|
435
|
+
sec_key_prefix = []
|
|
436
|
+
|
|
437
|
+
leaves = {}
|
|
438
|
+
child_sections = []
|
|
439
|
+
for key in schema.getkeys(*keypath):
|
|
440
|
+
if Schema._is_leaf(schema.getdict(*keypath, key)):
|
|
441
|
+
val = schema.getdict(*keypath, key)
|
|
442
|
+
leaves.update({key: val})
|
|
443
|
+
else:
|
|
444
|
+
children = self.build_config_recursive(
|
|
445
|
+
schema,
|
|
446
|
+
refdoc,
|
|
447
|
+
keypath=keypath + [key],
|
|
448
|
+
sec_key_prefix=sec_key_prefix)
|
|
449
|
+
child_sections.extend(children)
|
|
450
|
+
|
|
451
|
+
schema_table = None
|
|
452
|
+
if len(leaves) > 0:
|
|
453
|
+
# Might return None is none of the leaves are displayable
|
|
454
|
+
schema_table = build_schema_value_table(leaves, refdoc, keypath_prefix=keypath)
|
|
455
|
+
|
|
456
|
+
if schema_table is not None:
|
|
457
|
+
# If we've found leaves, create a new section where we'll display a
|
|
458
|
+
# table plus all child sections.
|
|
459
|
+
keypathstr = ', '.join(keypath)
|
|
460
|
+
top = build_section(keypathstr, self.get_ref(*sec_key_prefix, 'key', *keypath))
|
|
461
|
+
top += schema_table
|
|
462
|
+
top += child_sections
|
|
463
|
+
return [top]
|
|
464
|
+
|
|
465
|
+
# Otherwise, just pass on the child sections -- we don't want to
|
|
466
|
+
# create an extra level of section hierarchy for levels of the
|
|
467
|
+
# schema without leaves.
|
|
468
|
+
return child_sections
|
|
469
|
+
|
|
470
|
+
#########################
|
|
471
|
+
# Specialized extensions
|
|
472
|
+
#########################
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
class FlowGen(DynamicGen):
|
|
476
|
+
PATH = 'flows'
|
|
477
|
+
REF_PREFIX = 'flows'
|
|
478
|
+
SEARCH_ENV = "SC_DOCS_FLOWS"
|
|
479
|
+
|
|
480
|
+
def extra_content(self, chip, modname):
|
|
481
|
+
flow_path = os.path.join(self.env.app.outdir, f'_images/gen/{modname}.svg')
|
|
482
|
+
chip.write_flowgraph(flow_path, flow=chip.design)
|
|
483
|
+
return [image(flow_path, center=True)]
|
|
484
|
+
|
|
485
|
+
def display_config(self, chip, modname):
|
|
486
|
+
'''Display parameters under `flowgraph, <step>`, `metric, <step>` and
|
|
487
|
+
`showtool`. Parameters are grouped into sections by step, with an
|
|
488
|
+
additional table for non-step items.
|
|
489
|
+
'''
|
|
490
|
+
|
|
491
|
+
name = chip.design
|
|
492
|
+
|
|
493
|
+
settings = build_section('Configuration', self.get_configuration_ref_key(name))
|
|
494
|
+
|
|
495
|
+
steps = chip.getkeys('flowgraph', chip.design)
|
|
496
|
+
# TODO: should try to order?
|
|
497
|
+
|
|
498
|
+
# Build section + table for each step (combining entries under flowgraph
|
|
499
|
+
# and metric)
|
|
500
|
+
for step in steps:
|
|
501
|
+
section = build_section(step, self.get_ref(name, 'step', step))
|
|
502
|
+
step_cfg = {}
|
|
503
|
+
cfg = chip.getdict('flowgraph', chip.design, step)
|
|
504
|
+
if cfg is None:
|
|
505
|
+
continue
|
|
506
|
+
schema = Schema(cfg=cfg)
|
|
507
|
+
schema.prune()
|
|
508
|
+
pruned = schema.cfg
|
|
509
|
+
if chip.design not in step_cfg:
|
|
510
|
+
step_cfg[chip.design] = {}
|
|
511
|
+
step_cfg[chip.design][step] = pruned
|
|
512
|
+
|
|
513
|
+
section += build_schema_value_table(step_cfg,
|
|
514
|
+
self.env.docname,
|
|
515
|
+
keypath_prefix=['flowgraph'],
|
|
516
|
+
skip_zero_weight=True)
|
|
517
|
+
settings += section
|
|
518
|
+
|
|
519
|
+
return settings
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
class PDKGen(DynamicGen):
|
|
523
|
+
PATH = 'pdks'
|
|
524
|
+
REF_PREFIX = 'pdks'
|
|
525
|
+
SEARCH_ENV = "SC_DOCS_PDKS"
|
|
526
|
+
|
|
527
|
+
def display_config(self, chip, modname):
|
|
528
|
+
'''Display parameters under `pdk`, `asic`, and `library` in nested form.'''
|
|
529
|
+
|
|
530
|
+
name = chip.design
|
|
531
|
+
|
|
532
|
+
settings = build_section('Configuration', self.get_configuration_ref_key(name))
|
|
533
|
+
|
|
534
|
+
config_settings = self.build_config_recursive(
|
|
535
|
+
chip.schema,
|
|
536
|
+
self.env.docname,
|
|
537
|
+
keypath=['pdk'],
|
|
538
|
+
sec_key_prefix=[name])
|
|
539
|
+
|
|
540
|
+
if config_settings:
|
|
541
|
+
settings += config_settings
|
|
542
|
+
return settings
|
|
543
|
+
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class LibGen(DynamicGen):
|
|
548
|
+
PATH = 'libs'
|
|
549
|
+
REF_PREFIX = 'libs'
|
|
550
|
+
SEARCH_ENV = "SC_DOCS_LIBS"
|
|
551
|
+
|
|
552
|
+
def extra_content(self, chip, modname):
|
|
553
|
+
# assume same pdk for all libraries configured by this module
|
|
554
|
+
pdk = chip.get('option', 'pdk')
|
|
555
|
+
|
|
556
|
+
p = docutils.nodes.inline('')
|
|
557
|
+
pdk_ref = "None"
|
|
558
|
+
if pdk:
|
|
559
|
+
pdkid = get_ref_id(DynamicGen.get_ref_key(PDKGen.REF_PREFIX, pdk))
|
|
560
|
+
pdk_ref = f":ref:`{pdk}<{pdkid}>`"
|
|
561
|
+
self.parse_rst(f'Associated PDK: {pdk_ref}', p)
|
|
562
|
+
|
|
563
|
+
return [p]
|
|
564
|
+
|
|
565
|
+
def display_config(self, chip, modname):
|
|
566
|
+
'''Display parameters under in nested form.'''
|
|
567
|
+
|
|
568
|
+
sections = []
|
|
569
|
+
|
|
570
|
+
libname = chip.design
|
|
571
|
+
|
|
572
|
+
settings = build_section_with_target(libname, self.get_ref(libname),
|
|
573
|
+
self.state.document)
|
|
574
|
+
|
|
575
|
+
for key in ('asic', 'input', 'output', 'option'):
|
|
576
|
+
settings += self.build_config_recursive(
|
|
577
|
+
chip.schema,
|
|
578
|
+
self.env.docname,
|
|
579
|
+
keypath=[key],
|
|
580
|
+
sec_key_prefix=[libname, key])
|
|
581
|
+
|
|
582
|
+
sections.append(settings)
|
|
583
|
+
|
|
584
|
+
return sections
|
|
585
|
+
|
|
586
|
+
def get_document_ref_key(self, *modname):
|
|
587
|
+
return super().get_document_ref_key(*modname, "grouping")
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class ToolGen(DynamicGen):
|
|
591
|
+
PATH = 'tools'
|
|
592
|
+
REF_PREFIX = 'tools'
|
|
593
|
+
SEARCH_ENV = "SC_DOCS_TOOLS"
|
|
594
|
+
|
|
595
|
+
def make_chip(self):
|
|
596
|
+
chip = super().make_chip()
|
|
597
|
+
self._setup_chip(chip, '<tool>', '<task>')
|
|
598
|
+
|
|
599
|
+
return chip
|
|
600
|
+
|
|
601
|
+
def _setup_chip(self, chip, tool_name, task_name):
|
|
602
|
+
step = chip.get('arg', 'step')
|
|
603
|
+
if not step:
|
|
604
|
+
step = '<step>'
|
|
605
|
+
index = chip.get('arg', 'index')
|
|
606
|
+
if not index:
|
|
607
|
+
index = '<index>'
|
|
608
|
+
chip.set('arg', 'step', step)
|
|
609
|
+
chip.set('arg', 'index', index)
|
|
610
|
+
|
|
611
|
+
flow = chip.get('option', 'flow')
|
|
612
|
+
if not flow:
|
|
613
|
+
flow = '<flow>'
|
|
614
|
+
chip.set('option', 'flow', flow)
|
|
615
|
+
chip.set('flowgraph', flow, step, index, 'tool', tool_name)
|
|
616
|
+
chip.set('flowgraph', flow, step, index, 'task', task_name)
|
|
617
|
+
|
|
618
|
+
def configure_chip_for_docs(self, module, toolmodule=None):
|
|
619
|
+
chip = self.make_chip()
|
|
620
|
+
docs_chip, docs_configured = self._handle_make_docs(chip, module)
|
|
621
|
+
if not docs_chip and toolmodule:
|
|
622
|
+
docs_chip, docs_configured = self._handle_make_docs(chip, toolmodule)
|
|
623
|
+
if docs_configured:
|
|
624
|
+
return docs_chip
|
|
625
|
+
|
|
626
|
+
# set values for current step
|
|
627
|
+
toolname = module.__name__
|
|
628
|
+
if self.__tool:
|
|
629
|
+
toolname = self.__tool
|
|
630
|
+
taskname = '<task>'
|
|
631
|
+
if self.__task:
|
|
632
|
+
taskname = self.__task
|
|
633
|
+
self._setup_chip(chip, toolname, taskname)
|
|
634
|
+
|
|
635
|
+
if toolmodule:
|
|
636
|
+
return chip
|
|
637
|
+
else:
|
|
638
|
+
return self._handle_setup(chip, module)
|
|
639
|
+
|
|
640
|
+
def display_config(self, chip, modname):
|
|
641
|
+
'''Display config under `eda, <modname>` in a single table.'''
|
|
642
|
+
cfg = chip.getdict('tool', modname)
|
|
643
|
+
schema = Schema(cfg=cfg)
|
|
644
|
+
schema.prune()
|
|
645
|
+
pruned = schema.cfg
|
|
646
|
+
if 'task' in pruned:
|
|
647
|
+
# Remove task specific items since they will be documented
|
|
648
|
+
# by the task documentation
|
|
649
|
+
del pruned['task']
|
|
650
|
+
table = build_schema_value_table(pruned, self.env.docname, keypath_prefix=['tool', modname])
|
|
651
|
+
if table is not None:
|
|
652
|
+
return table
|
|
653
|
+
else:
|
|
654
|
+
return []
|
|
655
|
+
|
|
656
|
+
def task_display_config(self, chip, toolname, taskname):
|
|
657
|
+
'''Display config under `eda, <modname>` in a single table.'''
|
|
658
|
+
cfg = chip.getdict('tool', toolname, 'task', taskname)
|
|
659
|
+
schema = Schema(cfg=cfg)
|
|
660
|
+
schema.prune()
|
|
661
|
+
pruned = schema.cfg
|
|
662
|
+
table = build_schema_value_table(pruned, self.env.docname,
|
|
663
|
+
keypath_prefix=['tool', toolname, 'task', taskname])
|
|
664
|
+
if table is not None:
|
|
665
|
+
return table
|
|
666
|
+
else:
|
|
667
|
+
return []
|
|
668
|
+
|
|
669
|
+
def get_modules_in_dir(self, module_dir):
|
|
670
|
+
self.__tool = None
|
|
671
|
+
self.__task = None
|
|
672
|
+
'''Custom implementation for ToolGen since the tool setup modules are
|
|
673
|
+
under an extra directory, and this way we don't have to force users to
|
|
674
|
+
add an __init__.py to make the directory a module itself.
|
|
675
|
+
'''
|
|
676
|
+
modules = []
|
|
677
|
+
for toolname in os.listdir(module_dir):
|
|
678
|
+
if (toolname == "template" or toolname.startswith('_')):
|
|
679
|
+
# No need to include empty template in documentation
|
|
680
|
+
continue
|
|
681
|
+
# skip over directories/files that don't match the structure of tool
|
|
682
|
+
# directories (otherwise we'll get confused by Python metadata like
|
|
683
|
+
# __pycache__/)
|
|
684
|
+
if not os.path.isdir(f'{module_dir}/{toolname}'):
|
|
685
|
+
continue
|
|
686
|
+
path = f'{module_dir}/{toolname}/{toolname}.py'
|
|
687
|
+
if not os.path.exists(path):
|
|
688
|
+
path = f'{module_dir}/{toolname}/__init__.py'
|
|
689
|
+
if not os.path.exists(path):
|
|
690
|
+
continue
|
|
691
|
+
|
|
692
|
+
spec = importlib.util.spec_from_file_location(toolname, path)
|
|
693
|
+
module = importlib.util.module_from_spec(spec)
|
|
694
|
+
spec.loader.exec_module(module)
|
|
695
|
+
|
|
696
|
+
modules.append((module, toolname))
|
|
697
|
+
|
|
698
|
+
return modules
|
|
699
|
+
|
|
700
|
+
def document_task(self, path, toolmodule, taskmodule, taskname, toolname):
|
|
701
|
+
self.env.note_dependency(path)
|
|
702
|
+
s = build_section_with_target(taskname, self.get_ref(toolname, taskname),
|
|
703
|
+
self.state.document)
|
|
704
|
+
|
|
705
|
+
# Find setup function
|
|
706
|
+
task_setup = self.get_setup_method(taskmodule)
|
|
707
|
+
if not task_setup:
|
|
708
|
+
return None
|
|
709
|
+
|
|
710
|
+
print(f"Generating docs for task {toolname}/{taskname}...")
|
|
711
|
+
|
|
712
|
+
self.generate_documentation_from_object(task_setup, path, s)
|
|
713
|
+
|
|
714
|
+
self.__tool = toolname
|
|
715
|
+
self.__task = taskname
|
|
716
|
+
chip = self.configure_chip_for_docs(taskmodule, toolmodule=toolmodule)
|
|
717
|
+
self.__tool = None
|
|
718
|
+
self.__task = None
|
|
719
|
+
|
|
720
|
+
# Annotate the target used for default values
|
|
721
|
+
if chip.valid('option', 'target') and chip.get('option', 'target'):
|
|
722
|
+
p = docutils.nodes.inline('')
|
|
723
|
+
target = chip.get('option', 'target').split('.')[-1]
|
|
724
|
+
targetid = get_ref_id(DynamicGen.get_ref_key(TargetGen.REF_PREFIX, target))
|
|
725
|
+
self.parse_rst(f"Built using target: :ref:`{target}<{targetid}>`", p)
|
|
726
|
+
s += p
|
|
727
|
+
|
|
728
|
+
try:
|
|
729
|
+
task_setup(chip)
|
|
730
|
+
|
|
731
|
+
config = build_section("Configuration", self.get_configuration_ref_key(toolname,
|
|
732
|
+
taskname))
|
|
733
|
+
config_table = self.task_display_config(chip, toolname, taskname)
|
|
734
|
+
if config_table:
|
|
735
|
+
s += config
|
|
736
|
+
s += config_table
|
|
737
|
+
self.document_free_params(chip.getdict('tool', toolname, 'task', taskname),
|
|
738
|
+
[toolname, taskname, 'params'],
|
|
739
|
+
s)
|
|
740
|
+
except Exception as e:
|
|
741
|
+
print('Failed to document task, Chip object probably not configured correctly.')
|
|
742
|
+
print(e)
|
|
743
|
+
return None
|
|
744
|
+
|
|
745
|
+
return s
|
|
746
|
+
|
|
747
|
+
def child_content(self, path, module, modname):
|
|
748
|
+
sections = []
|
|
749
|
+
path = os.path.abspath(path)
|
|
750
|
+
module_dir = os.path.dirname(path)
|
|
751
|
+
for taskfile in os.listdir(module_dir):
|
|
752
|
+
if taskfile == "__init__.py":
|
|
753
|
+
# skip init
|
|
754
|
+
continue
|
|
755
|
+
|
|
756
|
+
task_path = os.path.join(module_dir, taskfile)
|
|
757
|
+
if path == task_path:
|
|
758
|
+
# skip tool module
|
|
759
|
+
continue
|
|
760
|
+
|
|
761
|
+
if not os.path.isfile(task_path):
|
|
762
|
+
# skip if not a file
|
|
763
|
+
continue
|
|
764
|
+
|
|
765
|
+
spec = importlib.util.spec_from_file_location(taskfile, task_path)
|
|
766
|
+
if not spec:
|
|
767
|
+
# unable to load, probably not a python file
|
|
768
|
+
continue
|
|
769
|
+
taskmodule = importlib.util.module_from_spec(spec)
|
|
770
|
+
try:
|
|
771
|
+
spec.loader.exec_module(taskmodule)
|
|
772
|
+
except Exception:
|
|
773
|
+
# Module failed to load
|
|
774
|
+
# klayout imports pya which is only defined in klayout
|
|
775
|
+
continue
|
|
776
|
+
|
|
777
|
+
taskname = os.path.splitext(os.path.basename(task_path))[0]
|
|
778
|
+
|
|
779
|
+
task_doc = self.document_task(task_path, module, taskmodule, taskname, modname)
|
|
780
|
+
if task_doc:
|
|
781
|
+
sections.append((taskname, task_doc))
|
|
782
|
+
|
|
783
|
+
if len(sections) > 0:
|
|
784
|
+
sections = sorted(sections, key=lambda t: t[0])
|
|
785
|
+
# Strip off modname so we just return list of docutils sections
|
|
786
|
+
_, sections = zip(*sections)
|
|
787
|
+
return sections
|
|
788
|
+
|
|
789
|
+
def document_free_params(self, cfg, reference_prefix, s):
|
|
790
|
+
key_path = ['tool', '<tool>', 'task', '<task>']
|
|
791
|
+
self._document_free_params(cfg, 'var', key_path + ['var'], reference_prefix, s)
|
|
792
|
+
self._document_free_params(cfg, 'file', key_path + ['file'], reference_prefix, s)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
class TargetGen(DynamicGen):
|
|
796
|
+
PATH = 'targets'
|
|
797
|
+
REF_PREFIX = 'targets'
|
|
798
|
+
SEARCH_ENV = "SC_DOCS_TARGETS"
|
|
799
|
+
|
|
800
|
+
def build_module_list(self, chip, header, modtype, targetname, *refprefix):
|
|
801
|
+
modules = chip._loaded_modules[modtype]
|
|
802
|
+
if len(modules) > 0:
|
|
803
|
+
section = build_section(header, self.get_ref(targetname, modtype))
|
|
804
|
+
modlist = nodes.bullet_list()
|
|
805
|
+
for module in modules:
|
|
806
|
+
list_item = nodes.list_item()
|
|
807
|
+
# TODO: replace with proper docutils nodes: sphinx.addnodes.pending_xref
|
|
808
|
+
modkey = get_ref_id(DynamicGen.get_ref_key(*refprefix, module))
|
|
809
|
+
self.parse_rst(f':ref:`{module}<{modkey}>`', list_item)
|
|
810
|
+
modlist += list_item
|
|
811
|
+
|
|
812
|
+
section += modlist
|
|
813
|
+
return section
|
|
814
|
+
return None
|
|
815
|
+
|
|
816
|
+
def display_config(self, chip, modname):
|
|
817
|
+
sections = []
|
|
818
|
+
|
|
819
|
+
flow_section = self.build_module_list(chip, 'Flows', 'flows', modname, FlowGen.REF_PREFIX)
|
|
820
|
+
if flow_section is not None:
|
|
821
|
+
sections.append(flow_section)
|
|
822
|
+
|
|
823
|
+
pdk_section = self.build_module_list(chip, 'PDK', 'pdks', modname, PDKGen.REF_PREFIX)
|
|
824
|
+
if pdk_section is not None:
|
|
825
|
+
sections.append(pdk_section)
|
|
826
|
+
|
|
827
|
+
libs_section = self.build_module_list(chip, 'Libraries', 'libs', modname, LibGen.REF_PREFIX)
|
|
828
|
+
if libs_section is not None:
|
|
829
|
+
sections.append(libs_section)
|
|
830
|
+
|
|
831
|
+
checklist_section = self.build_module_list(chip, 'Checklists', 'checklists', modname,
|
|
832
|
+
ChecklistGen.REF_PREFIX)
|
|
833
|
+
if checklist_section is not None:
|
|
834
|
+
sections.append(checklist_section)
|
|
835
|
+
|
|
836
|
+
filtered_cfg = {}
|
|
837
|
+
for key in ('asic', 'constraint', 'option'):
|
|
838
|
+
filtered_cfg[key] = chip.getdict(key)
|
|
839
|
+
schema = Schema(cfg=filtered_cfg)
|
|
840
|
+
schema.prune()
|
|
841
|
+
pruned_cfg = schema.cfg
|
|
842
|
+
|
|
843
|
+
if len(pruned_cfg) > 0:
|
|
844
|
+
schema_section = build_section('Configuration', self.get_configuration_ref_key(modname))
|
|
845
|
+
schema_section += build_schema_value_table(pruned_cfg, self.env.docname)
|
|
846
|
+
sections.append(schema_section)
|
|
847
|
+
|
|
848
|
+
return sections
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
class AppGen(DynamicGen):
|
|
852
|
+
PATH = 'apps'
|
|
853
|
+
REF_PREFIX = 'apps'
|
|
854
|
+
SEARCH_ENV = "SC_DOCS_APPS"
|
|
855
|
+
|
|
856
|
+
def document_module(self, module, modname, path):
|
|
857
|
+
if modname[0] == "_":
|
|
858
|
+
return None
|
|
859
|
+
|
|
860
|
+
cmd_name = modname.replace('_', '-')
|
|
861
|
+
cmd = [cmd_name, '--help']
|
|
862
|
+
|
|
863
|
+
output = subprocess.check_output(cmd).decode('utf-8')
|
|
864
|
+
|
|
865
|
+
section = build_section(cmd_name, self.get_ref(cmd_name))
|
|
866
|
+
section += literalblock(output)
|
|
867
|
+
|
|
868
|
+
return section
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
class ChecklistGen(DynamicGen):
|
|
872
|
+
PATH = 'checklists'
|
|
873
|
+
REF_PREFIX = 'checklists'
|
|
874
|
+
SEARCH_ENV = "SC_DOCS_CHECKLISTS"
|
|
875
|
+
|
|
876
|
+
def display_config(self, chip, modname):
|
|
877
|
+
'''Display parameters under in nested form.'''
|
|
878
|
+
|
|
879
|
+
sections = []
|
|
880
|
+
|
|
881
|
+
name = chip.design
|
|
882
|
+
|
|
883
|
+
keypath_prefix = ['checklist', name]
|
|
884
|
+
cfg = chip.getdict(*keypath_prefix)
|
|
885
|
+
|
|
886
|
+
settings = build_section('Configuration', self.get_configuration_ref_key(name))
|
|
887
|
+
|
|
888
|
+
for key in cfg.keys():
|
|
889
|
+
if key == 'default':
|
|
890
|
+
continue
|
|
891
|
+
settings += build_section(key, self.get_ref(name, 'key', key))
|
|
892
|
+
settings += build_schema_value_table(cfg[key], self.env.docname,
|
|
893
|
+
keypath_prefix=[*keypath_prefix, key])
|
|
894
|
+
|
|
895
|
+
sections.append(settings)
|
|
896
|
+
|
|
897
|
+
return sections
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
class ExampleGen(DynamicGen):
|
|
901
|
+
|
|
902
|
+
def get_modules(self):
|
|
903
|
+
examples_dir = f'{SC_ROOT}/examples'
|
|
904
|
+
|
|
905
|
+
modules = []
|
|
906
|
+
for example in os.listdir(examples_dir):
|
|
907
|
+
if not os.path.isdir(f'{examples_dir}/{example}'):
|
|
908
|
+
continue
|
|
909
|
+
path = f'{examples_dir}/{example}/{example}.py'
|
|
910
|
+
if not os.path.exists(path):
|
|
911
|
+
continue
|
|
912
|
+
|
|
913
|
+
spec = importlib.util.spec_from_file_location(example, path)
|
|
914
|
+
module = importlib.util.module_from_spec(spec)
|
|
915
|
+
spec.loader.exec_module(module)
|
|
916
|
+
|
|
917
|
+
modules.append((module, example))
|
|
918
|
+
|
|
919
|
+
return modules
|
|
920
|
+
|
|
921
|
+
def document_module(self, module, modname, path):
|
|
922
|
+
section = build_section(modname, modname)
|
|
923
|
+
|
|
924
|
+
if not hasattr(module, 'main'):
|
|
925
|
+
return None
|
|
926
|
+
|
|
927
|
+
main = getattr(module, 'main')
|
|
928
|
+
|
|
929
|
+
# raw docstrings have funky indentation (basically, each line is already
|
|
930
|
+
# indented as much as the function), so we call trim() helper function
|
|
931
|
+
# to clean it up
|
|
932
|
+
docstr = utils.trim(main.__doc__)
|
|
933
|
+
|
|
934
|
+
if docstr:
|
|
935
|
+
self.parse_rst(docstr, section)
|
|
936
|
+
|
|
937
|
+
return section
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def keypath_role(name, rawtext, text, lineno, inliner, options=None, content=None):
|
|
941
|
+
doc = inliner.document
|
|
942
|
+
env = doc.settings.env
|
|
943
|
+
|
|
944
|
+
# Split and clean up keypath
|
|
945
|
+
keys = [key.strip() for key in text.split(',')]
|
|
946
|
+
try:
|
|
947
|
+
return [keypath(keys, env.docname)], []
|
|
948
|
+
except ValueError as e:
|
|
949
|
+
msg = inliner.reporter.error(f'{rawtext}: {e}', line=lineno)
|
|
950
|
+
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
951
|
+
return [prb], [msg]
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
class SCDomain(StandardDomain):
|
|
955
|
+
name = 'sc'
|
|
956
|
+
|
|
957
|
+
# Override in StandardDomain so xref is literal instead of inline
|
|
958
|
+
# https://github.com/sphinx-doc/sphinx/blob/ba080286b06cb9e0cadec59a6cf1f96aa11aef5a/sphinx/domains/std.py#L789
|
|
959
|
+
def build_reference_node(self,
|
|
960
|
+
fromdocname,
|
|
961
|
+
builder,
|
|
962
|
+
docname,
|
|
963
|
+
labelid,
|
|
964
|
+
sectname,
|
|
965
|
+
rolename,
|
|
966
|
+
**options):
|
|
967
|
+
nodeclass = options.pop('nodeclass', nodes.reference)
|
|
968
|
+
newnode = nodeclass('', '', internal=True, **options)
|
|
969
|
+
innernode = nodes.literal(sectname, sectname)
|
|
970
|
+
if innernode.get('classes') is not None:
|
|
971
|
+
innernode['classes'].append('std')
|
|
972
|
+
innernode['classes'].append('std-' + rolename)
|
|
973
|
+
if docname == fromdocname:
|
|
974
|
+
newnode['refid'] = labelid
|
|
975
|
+
else:
|
|
976
|
+
# set more info in contnode; in case the
|
|
977
|
+
# get_relative_uri call raises NoUri,
|
|
978
|
+
# the builder will then have to resolve these
|
|
979
|
+
contnode = pending_xref('')
|
|
980
|
+
contnode['refdocname'] = docname
|
|
981
|
+
contnode['refsectname'] = sectname
|
|
982
|
+
newnode['refuri'] = builder.get_relative_uri(
|
|
983
|
+
fromdocname, docname)
|
|
984
|
+
if labelid:
|
|
985
|
+
newnode['refuri'] += '#' + labelid
|
|
986
|
+
newnode.append(innernode)
|
|
987
|
+
return newnode
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
def setup(app):
|
|
991
|
+
app.add_domain(SCDomain)
|
|
992
|
+
app.add_directive('flowgen', FlowGen)
|
|
993
|
+
app.add_directive('pdkgen', PDKGen)
|
|
994
|
+
app.add_directive('libgen', LibGen)
|
|
995
|
+
app.add_directive('toolgen', ToolGen)
|
|
996
|
+
app.add_directive('appgen', AppGen)
|
|
997
|
+
app.add_directive('examplegen', ExampleGen)
|
|
998
|
+
app.add_directive('targetgen', TargetGen)
|
|
999
|
+
app.add_directive('checklistgen', ChecklistGen)
|
|
1000
|
+
app.add_role('keypath', keypath_role)
|
|
1001
|
+
|
|
1002
|
+
return {
|
|
1003
|
+
'version': siliconcompiler.__version__,
|
|
1004
|
+
'parallel_read_safe': True,
|
|
1005
|
+
'parallel_write_safe': True,
|
|
1006
|
+
}
|