diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..ef40fab --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 50c5a471c65279fad615ad2a6e72e794 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/_images/osvb.png b/_images/osvb.png new file mode 100644 index 0000000..01e833a Binary files /dev/null and b/_images/osvb.png differ diff --git a/_images/osvde.png b/_images/osvde.png new file mode 100644 index 0000000..ceed4a7 Binary files /dev/null and b/_images/osvde.png differ diff --git a/_images/pyCAPI.png b/_images/pyCAPI.png new file mode 100644 index 0000000..af263de Binary files /dev/null and b/_images/pyCAPI.png differ diff --git a/_images/runner_cfg.png b/_images/runner_cfg.png new file mode 100644 index 0000000..3e60f8a Binary files /dev/null and b/_images/runner_cfg.png differ diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..2f9fedb --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,271 @@ + + +
+ + + + +
+# Python Data Model for CAPI 3 core files
+
+# Authors:
+# Unai Martinez-Corral
+#
+# Copyright 2021 Unai Martinez-Corral <unai.martinezcorral@ehu.eus>
+#
+# 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
+#
+# http://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
+
+
+from typing import Dict, List, Union
+from pathlib import Path
+from dataclasses import dataclass, field
+from yamldataclassconfig.config import YamlDataClassConfig
+
+
+
+[docs]
+@dataclass
+class IpCoreConfig(YamlDataClassConfig):
+ """
+ Each core file must contain an 'ipcore' field with the metadata of the design.
+ """
+
+ namespace: str = None
+ name: str = None
+ version: str = None
+
+
+
+
+[docs]
+@dataclass
+class FilesetConfig(YamlDataClassConfig):
+ """
+ A Fileset is a group of sources of the same type, to be used together.
+ """
+
+ path: str = field(default_factory=str)
+ file_type: str = field(default_factory=str)
+ logical_name: str = field(default_factory=str)
+ files: List[str] = None
+
+
+
+
+[docs]
+@dataclass
+class Config(YamlDataClassConfig):
+ """
+ Definition of the CAPI 3 format for core configuration files.
+ """
+
+ CAPI: int = None
+
+ ipcore: IpCoreConfig = None
+
+ # TODO Should be a Union for supporting a global 'file_type'
+ # filesets: Dict[str, Union[str, FilesetConfig]] = None
+ filesets: Dict[str, FilesetConfig] = None
+
+
+
+
+[docs]
+def LoadCoreFile(coreFilePath):
+ """
+ Load a CAPI 3 compliant core configuration file and unmarshal it.
+
+ :param coreFilePath: location of the ``*.core`` file to be loaded.
+ """
+ _cpath = Path(coreFilePath)
+ _core = Config()
+ _core.load(_cpath)
+ _core.FILE_PATH = _cpath
+ print(_core.FILE_PATH)
+ print("CAPI:", _core.CAPI)
+ print("IpCore:")
+ print(" namespace:", _core.ipcore.namespace)
+ print(" name:", _core.ipcore.name)
+ print(" version:", _core.ipcore.version)
+ print("Filesets:")
+ for key, val in _core.filesets.items():
+ print(" - %s:" % key)
+ print(" path:", val.path)
+ print(" file_type:", val.file_type)
+ print(" logical_name:", val.logical_name)
+ print(" files:", val.files)
+
+ return _core
+
+
+# Python API for using CAPI 3 core files from VUnit
+
+# Authors:
+# Unai Martinez-Corral
+#
+# Copyright 2021 Unai Martinez-Corral <unai.martinezcorral@ehu.eus>
+#
+# 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
+#
+# http://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
+
+
+
+[docs]
+def AddCoreFilesets(vunitHandle, core, filesets):
+ """
+ Add the sources of some filesets from a CAPI 3 compliant core, into a VUnit
+ handle through VUnit's API (``add_library`` and ``add_source_files``).
+
+ :param vunitHandle: handle to the VUnit object instance.
+ :param core: CAPI 3 compliant object (coming from LoadCoreFile).
+ :param filesets: name of the filesets to be added into the VUnit handle.
+ """
+ _root = core.FILE_PATH.parent
+
+ _defaultLib = "VUnitUserLib"
+ _sources = {_defaultLib: []}
+
+ for fsetname in filesets:
+ if fsetname in core.filesets:
+ fset = core.filesets[fsetname]
+ _lib = _defaultLib if fset.logical_name == "" else fset.logical_name
+ if _lib not in _sources:
+ _sources[_lib] = []
+ _sources[_lib] += [_root / fset.path / fstr for fstr in fset.files]
+
+ for _lib, _files in _sources.items():
+ vunitHandle.add_library(_lib).add_source_files(_files)
+
+
+#!/usr/bin/env python3
+
+# Authors:
+# Unai Martinez-Corral
+#
+# Copyright 2021 Unai Martinez-Corral <unai.martinezcorral@ehu.eus>
+#
+# 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
+#
+# http://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
+
+
+from sys import exit as sys_exit
+
+import tkinter as tk
+from tkinter import messagebox as mbox
+from tkinter import ttk as ttk, filedialog as fd
+
+from pathlib import Path
+from textwrap import dedent
+
+from pyGHDL.dom.InterfaceItem import GenericConstantInterfaceItem, GenericTypeInterfaceItem
+from pyGHDL.dom.NonStandard import Design, Document
+from pyGHDL.dom.Concurrent import (
+ ConcurrentBlockStatement,
+ ProcessStatement,
+ IfGenerateStatement,
+ CaseGenerateStatement,
+ ForGenerateStatement,
+ GenerateCase,
+ OthersGenerateCase,
+)
+from pyVHDLModel.SyntaxModel import (
+ Instantiation,
+ Mode,
+)
+from pyVHDLModelUtils.resolve import Symbols as resolve_Symbols
+
+from gitignore_parser import parse_gitignore as ignoreParser
+
+
+
+[docs]
+class OSVDE(tk.Frame):
+ def __init__(self, parent):
+ super().__init__(parent)
+ parent.title("[OSVDE] Open Source VHDL Design Explorer")
+
+ parent.state("zoomed")
+
+ self.Frame = tk.Frame(parent)
+ self.FileTreeView = ttk.Treeview(self)
+ self.DesignTreeView = ttk.Treeview(self)
+
+ self.FileTreeView.pack(fill="both", side=tk.TOP, expand=True)
+ self.DesignTreeView.pack(fill="both", side=tk.TOP, expand=True)
+ self.Frame.pack(fill=tk.X, side=tk.BOTTOM, expand=False)
+ self.pack(fill="both", expand=True)
+
+ self._initImages()
+ parent.iconphoto(False, self.ImageLogo)
+
+ self._initMenu(parent)
+ self._initFileTreeView()
+ self._initDesignTreeView()
+
+ # tk.Button(self.Frame, text="Click to Open File", command=self.cbFile).pack(
+ # fill=tk.X, side=tk.LEFT, expand=True
+ # )
+ # tk.Button(self.Frame, text="Click to Open Directory", command=self.cbDir).pack(
+ # fill=tk.X, side=tk.LEFT, expand=True
+ # )
+
+ self.Design = Design()
+
+ self.cbDir()
+
+ def _initImages(self):
+ """
+ Load images for icons and logo.
+ """
+ # https://www.flaticon.com/packs/file-folder-13
+ imgDir = Path(__file__).parent / "img"
+ self.ImageLogo = tk.PhotoImage(file=imgDir / "icon.png")
+ imgDir16 = imgDir / "16"
+ self.Image = {}
+ for item in (
+ "dir",
+ "file",
+ "lib",
+ "ent",
+ "generics",
+ "generic",
+ "ports",
+ "in",
+ "out",
+ "inout",
+ "archs",
+ "arch",
+ "inst",
+ ):
+ self.Image[item] = tk.PhotoImage(file=imgDir16 / "{}16.png".format(item))
+
+ def _initMenu(self, parent):
+ """
+ Initialise the main Menu.
+ """
+
+ def cbAbout():
+ # aboutWindow = tk.Toplevel(parent)
+ # aboutWindow.title("About Open Source VHDL Design Explorer (OSVDE)")
+ # aboutWindow.geometry("500x350")
+ # tk.Label(aboutWindow, text ="This is a new window").pack()
+ mbox.showinfo(
+ "About",
+ dedent(
+ """\
+ Open Source VHDL Design Explorer (OSVDE)
+ <github.com/umarcor/osvb>
+
+ Copyright 2021 Unai Martinez-Corral
+ <unai.martinezcorral@ehu.eus>
+
+ Licensed under the Apache License, Version 2.0
+ <apache.org/licenses/LICENSE-2.0>
+ """
+ ),
+ )
+
+ self.Menu = tk.Menu(parent)
+ parent.config(menu=self.Menu)
+
+ fileMenu = tk.Menu(self.Menu, tearoff=0)
+ fileMenu.add_command(label="Open File...", command=self.cbFile)
+ fileMenu.add_command(label="Open Directory...", command=self.cbDir)
+ fileMenu.add_separator()
+ fileMenu.add_command(label="About...", command=cbAbout)
+ fileMenu.add_separator()
+ fileMenu.add_command(label="Exit", command=parent.quit)
+ self.Menu.add_cascade(label="File", menu=fileMenu)
+
+ # editMenu = tk.Menu(self.Menu, tearoff=0)
+ # editMenu.add_command(label="Undo")
+ # editMenu.add_command(label="Redo")
+ # self.Menu.add_cascade(label="Edit", menu=editMenu)
+
+ def _initFileTreeView(self):
+ """
+ Initialise the File TreeView (define the columns).
+ """
+ ftw = self.FileTreeView
+ ftw["columns"] = "units"
+ ftw.heading("#0", text="Sources: **/*vhd")
+ ftw.heading("units", text="Units: entity(architecture,...)|<entity>(architecture,...)")
+
+ def _initDesignTreeView(self):
+ """
+ Initialise the Design TreeView (define the columns).
+ """
+ dtw = self.DesignTreeView
+ dtw.heading("#0", text="Design hierarchies")
+
+
+[docs]
+ def cbDir(self):
+ """
+ Callback for '*Open Directory...*'.
+ """
+ dirName = fd.askdirectory(mustexist=True)
+ if dirName != "":
+ self.parseDir(Path(dirName))
+ resolve_Symbols(self.Design)
+ self.loadFileTree()
+ self.loadDesignTree()
+
+
+
+[docs]
+ def cbFile(self):
+ """
+ Callback for '*Open File...*'.
+ """
+ fileName = fd.askopenfilename()
+ self.parseFile(Path(fileName))
+ resolve_Symbols(self.Design)
+ self.loadFileTree()
+ self.loadDesignTree()
+
+
+
+[docs]
+ def parseDir(self, dirName: Path):
+ """
+ Parse all the ``.vhd`` files found through a recursive glob, honoring ``osvdeignore`` files.
+ """
+ ignores = []
+ for item in dirName.glob("**/.osvdeignore"):
+ ignores.append((item.parent, ignoreParser(item)))
+
+ for item in dirName.glob("**/*.vhd*"):
+ ignoreItem = False
+ for match in ignores:
+ if str(match[0]) in str(item) and match[1](item):
+ ignoreItem = True
+ break
+ if not ignoreItem:
+ self.parseFile(item)
+
+
+
+[docs]
+ def parseFile(self, sourceFile: Path, library: str = "lib"):
+ """
+ Add a VHDL source to the pyVHDLModel Design.
+ """
+ print("parseFile: {}".format(sourceFile))
+ lib = self.Design.GetLibrary(library)
+ self.Design.AddDocument(Document(sourceFile), lib)
+
+
+
+[docs]
+ def loadFileTree(self, parent=""):
+ """
+ Populate the FileTreeView with the data from the pyVHDLModel Design.
+ """
+ ftw = self.FileTreeView
+
+ def addTreeItem(parent, text, isDir, data=()):
+ return ftw.insert(
+ parent,
+ tk.END,
+ text=text,
+ values=data,
+ open=isDir,
+ image=self.Image["dir"] if isDir else self.Image["file"],
+ )
+
+ def traversePath(document, parts, parent):
+ item = parts[0]
+ isDir = len(parts) > 1
+
+ children = [iid for iid in ftw.get_children(parent) if ftw.item(iid)["text"] == item]
+ childrenLen = len(children)
+
+ if isDir:
+ if childrenLen > 1:
+ raise Exception("Duplicated item text <{}>: {}".format(item, children))
+ return traversePath(
+ document,
+ parts[1:],
+ addTreeItem(parent, item, True, ()) if childrenLen == 0 else children[0],
+ )
+
+ if len(children) > 0:
+ raise Exception("Item text <{}> exists already: {}".format(item, children))
+
+ units = {}
+ for entity in document.Entities:
+ units["<{}>".format(entity.Identifier)] = [arch.Identifier for arch in entity.Architectures]
+
+ addTreeItem(
+ parent,
+ item,
+ False,
+ ("| ".join(["{}({})".format(key, ", ".join(val)) for key, val in units.items()])),
+ )
+
+ for document in self.Design.Documents:
+ traversePath(document, list(document.Path.parts), parent)
+
+
+
+[docs]
+ def loadDesignTree(self, parent=""):
+ """
+ Populate the DesignTreeView with the data from the pyVHDLModel Design.
+ """
+ dtw = self.DesignTreeView
+
+ def addTreeItem(parent, text, isOpen, image):
+ return dtw.insert(parent, tk.END, text=text, open=isOpen, image=self.Image[image])
+
+ def loadStatementsTree(item, statements):
+ for statement in statements:
+
+ # Note: the following share the same base class 'Instantiation'
+ # ComponentInstantiation, EntityInstantiation, ConfigurationInstantiation
+
+ if isinstance(statement, Instantiation):
+ addTreeItem(item, "{}: inst".format(statement.Label), False, "inst")
+
+ elif isinstance(statement, ConcurrentBlockStatement):
+ instItem = addTreeItem(item, "{}: block".format(statement.Label), False, "file")
+ innerstatements = statement.Statements
+ if len(innerstatements) != 0:
+ loadStatementsTree(instItem, innerstatements)
+
+ # Note: the following share the same base class 'GenerateStatement'
+ # ForGenerateStatement, CaseGenerateStatement, IfGenerateStatement
+
+ elif isinstance(statement, IfGenerateStatement):
+ instItem = addTreeItem(item, "{}: if .. generate".format(statement.Label), False, "file")
+ loadStatementsTree(
+ addTreeItem(
+ instItem,
+ "if: {}".format(statement.IfBranch.Condition),
+ False,
+ "file",
+ ),
+ statement.IfBranch.Statements,
+ )
+ for elsifbranch in statement.ElsifBranches:
+ loadStatementsTree(
+ addTreeItem(
+ instItem,
+ "elsif: {}".format(elsifbranch.Condition),
+ False,
+ "file",
+ ),
+ elsifbranch.Statements,
+ )
+ if statement.ElseBranch is not None:
+ loadStatementsTree(
+ addTreeItem(instItem, "else:", False, "file"),
+ statement.ElseBranch.Statements,
+ )
+
+ elif isinstance(statement, CaseGenerateStatement):
+ instItem = addTreeItem(item, "{}: case .. generate".format(statement.Label), False, "file")
+ for case in statement.Cases:
+ if isinstance(case, GenerateCase):
+ loadStatementsTree(
+ addTreeItem(
+ instItem,
+ "case: {}".format(" | ".join([str(c) for c in case.Choises])),
+ False,
+ "file",
+ ),
+ case.Statements,
+ )
+ elif isinstance(case, OthersGenerateCase):
+ loadStatementsTree(
+ addTreeItem(instItem, "others:", False, "file"),
+ case.Statements,
+ )
+
+ elif isinstance(statement, ForGenerateStatement):
+ instItem = addTreeItem(
+ item,
+ "{0}: for {1!s} generate".format(statement.Label, statement.Range),
+ False,
+ "file",
+ )
+ innerstatements = statement.Statements
+ if len(innerstatements) != 0:
+ loadStatementsTree(instItem, innerstatements)
+
+ elif isinstance(statement, ProcessStatement):
+ addTreeItem(
+ item,
+ "{}: process".format(statement.Label or "DefaultLabel"),
+ False,
+ "file",
+ )
+
+ for libName, lib in self.Design.Libraries.items():
+ LibItem = addTreeItem(parent, libName, True, "lib")
+ for entity in lib.Entities:
+ EntityItem = addTreeItem(LibItem, entity.Identifier, False, "ent")
+
+ generics = entity.GenericItems
+ if len(generics) != 0:
+ GenericsItem = addTreeItem(EntityItem, "Generics", False, "generics")
+ for generic in generics:
+ if isinstance(generic, GenericConstantInterfaceItem):
+ addTreeItem(
+ GenericsItem,
+ "{} [{}]".format(",".join(generic.Identifiers), generic.Subtype),
+ False,
+ "generic",
+ )
+ elif isinstance(generic, GenericTypeInterfaceItem):
+ addTreeItem(
+ GenericsItem,
+ "type: {}".format(generic.Identifier),
+ False,
+ "generic",
+ )
+ else:
+ print(
+ "[NOT IMPLEMENTED] Generic item class not supported yet: {0}",
+ generic.__class__.__name__,
+ )
+
+ ports = entity.PortItems
+ if len(ports) != 0:
+ PortsItem = addTreeItem(EntityItem, "Ports", False, "ports")
+ for port in ports:
+ addTreeItem(
+ PortsItem,
+ "{} [{}]".format(",".join(port.Identifiers), port.Subtype),
+ False,
+ "out" if port.Mode is Mode.Out else "inout" if port.Mode is Mode.InOut else "in",
+ )
+
+ architectures = entity.Architectures
+ if len(architectures) != 0:
+ ArchitecturesItem = addTreeItem(EntityItem, "Architectures", False, "archs")
+ for architecture in entity.Architectures:
+ loadStatementsTree(
+ addTreeItem(ArchitecturesItem, "{}".format(architecture.Identifier), False, "arch"),
+ architecture.Statements,
+ )
+
+
+
+ # TODO:
+ # - Add icons for process, block and generate(s)
+ # - https://icon-icons.com/icon/production-process/149888
+ # - Add check for 'null' in GHDL's Sanity?
+
+ # TODO:
+ # Strictly, elaboration is required for relating Entities and Architectures.
+ # However, there are three solutions:
+ # - Do a naive elaboration, by simple name matching.
+ # - Implement the elaboration in pyVHDLModel.
+ # - Use pyGHDL.libghdl for elaboration.
+ # The most sensible solution is to use the first one, skip the second one, and work on the last one.
+ # Currently, we are using `pyVHDLModelUtils.resolve.Symbols`, contributed by Patrick.
+ # In the near future, he might add those and other helper functions into an specific package, sibling
+ # to pyVHDLModel and pyGHDL.
+
+
+if __name__ == "__main__":
+ sys_exit(OSVDE(tk.Tk()).mainloop())
+
+from typing import Union
+from pyGHDL.dom.Symbol import (
+ SimpleSubtypeSymbol,
+ ConstrainedCompositeSubtypeSymbol,
+)
+
+
+# From pyGHDL's dom/formatting/prettyprint.py
+
+[docs]
+def SubtypeIndication(subtypeIndication: Union[SimpleSubtypeSymbol, ConstrainedCompositeSubtypeSymbol]) -> str:
+ """
+ Format the subtype indication of generics/ports as an string.
+ """
+ if isinstance(subtypeIndication, SimpleSubtypeSymbol):
+ return "{type}".format(type=subtypeIndication.SymbolName)
+ elif isinstance(subtypeIndication, ConstrainedCompositeSubtypeSymbol):
+ constraints = []
+ for constraint in subtypeIndication.Constraints:
+ constraints.append(str(constraint))
+
+ return "{type}({constraints})".format(type=subtypeIndication.SymbolName, constraints=", ".join(constraints))
+ else:
+ raise Exception("Unhandled subtype kind '{type}'.".format(type=subtypeIndication.__class__.__name__))
+
+
+from pyGHDL.dom.NonStandard import Design
+
+
+
+[docs]
+def Symbols(design: Design) -> None:
+ """
+ Resolve some symbols after parsing, but without complete analysis and elaboration.
+ """
+ ArchitecturesToEntities(design)
+
+
+
+
+[docs]
+def ArchitecturesToEntities(design: Design) -> None:
+ """
+ Resolve architectures to entities by simple name matching.
+ """
+ for library in design.Libraries.values():
+ for entityName, architectures in library.Architectures.items():
+ for entity in library.Entities:
+ if entity.Identifier == str(entityName):
+ for architecture in architectures:
+ entity.Architectures.append(architecture)
+
+
+from typing import List, Union
+from pathlib import Path
+from textwrap import indent
+from io import StringIO
+from contextlib import redirect_stdout
+from tabulate import tabulate
+
+from pyGHDL.dom.NonStandard import Design, Document, Library
+from pyGHDL.dom.DesignUnit import Entity
+
+from pyVHDLModelUtils.resolve import Symbols as resolve_Symbols
+from pyVHDLModelUtils.fmt import SubtypeIndication as format_SubtypeIndication
+
+
+DESIGN = Design()
+
+
+
+[docs]
+def initDesign(root: Union[str, Path], **kwargs) -> None:
+ """
+ Initialize a Design and analyze sources.
+
+ Each argument after `root` defines a library name and the type is expected to be a list of patterns.
+
+ :param root: Location of the root for all provided relative paths. This is relative to the base directory of the build.
+ """
+ root = Path(root) if isinstance(root, str) else root
+ print(".. NOTE:: Output of initDesign:\n")
+ for library, patterns in kwargs.items():
+ lib = DESIGN.GetLibrary(library)
+ print(" * {0}\n".format(library))
+ for pattern in patterns:
+ for item in root.glob(pattern):
+ # FIXME: is_relative_to is new in Python 3.9 (see https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to)
+ # Therefore, we use try..except for backwards compatibility.
+ # print(" * {0}\n".format(item.relative_to(root) if item.is_relative_to(root) else item))
+ try:
+ path = item.relative_to(root)
+ except e:
+ path = item
+ print(" * {0}\n".format(path))
+ f = StringIO()
+ with redirect_stdout(f):
+ DESIGN.AddDocument(Document(item), lib)
+ print(indent(f.getvalue(), " * "))
+ print("")
+ resolve_Symbols(DESIGN)
+
+
+
+STYLES = ["rst:list", "rst:table"]
+
+
+
+[docs]
+def printDocumentationOfEntity_rstList(entity: Entity, lib: Library = None) -> None:
+ """
+ Print documentation of an Entity using style 'rst:list'.
+ """
+ print("Entity *{0}* from Library *{1}*:\n".format(entity.Identifier, lib.Identifier))
+
+ print("* Generics:\n")
+ for generic in entity.GenericItems:
+ print(
+ " * *{0}* : ``{1}``{2}".format(
+ ", ".join(generic.Identifiers),
+ format_SubtypeIndication(generic.Subtype),
+ "" if generic.DefaultExpression is None else " := ``{0!s}``".format(generic.DefaultExpression),
+ )
+ )
+ print("\n* Ports:\n")
+ for port in entity.PortItems:
+ print(
+ " * *{0}* : {1} ``{2}``{3}".format(
+ ", ".join(port.Identifiers),
+ port.Mode,
+ format_SubtypeIndication(port.Subtype),
+ "" if port.DefaultExpression is None else " := ``{0!s}``".format(port.DefaultExpression),
+ )
+ )
+ print("\n* Architectures:\n")
+ for architecture in entity.Architectures:
+ print(" * *{0}*".format(architecture.Identifier))
+
+
+
+
+[docs]
+def printDocumentationOfEntity_rstTable(entity: Entity, lib: Library = None) -> None:
+ """
+ Print documentation of an Entity using style 'rst:table' (through 'tabulate').
+ """
+
+ def printTable(data, headers=None, caption=None):
+ table = tabulate(data, tablefmt="rst") if headers is None else tabulate(data, headers, tablefmt="rst")
+ if caption is not None:
+ print(".. table:: {0}\n :align: center\n".format(caption))
+ print(indent(table, " "))
+ else:
+ print(table)
+ print("\n")
+
+ name = "{0}{1}".format("" if lib is None else "{0}.".format(lib.Identifier), entity.Identifier)
+
+ printTable(
+ [
+ [
+ ", ".join(generic.Identifiers),
+ "``{0}``".format(format_SubtypeIndication(generic.Subtype)),
+ "" if generic.DefaultExpression is None else "``{0!s}``".format(generic.DefaultExpression),
+ ]
+ for generic in entity.GenericItems
+ ],
+ headers=["Identifiers", "Type", "Default"],
+ caption="{0} Generics".format(name),
+ )
+
+ printTable(
+ [
+ [
+ ", ".join(port.Identifiers),
+ port.Mode,
+ "``{0}``".format(format_SubtypeIndication(port.Subtype)),
+ "" if port.DefaultExpression is None else "``{0!s}``".format(port.DefaultExpression),
+ ]
+ for port in entity.PortItems
+ ],
+ headers=["Identifiers", "Mode", "Type", "Default"],
+ caption="{0} Ports".format(name),
+ )
+
+ printTable(
+ [
+ [
+ architecture.Identifier,
+ len(architecture.Statements),
+ ]
+ for architecture in entity.Architectures
+ ],
+ headers=["Identifier", "Number of statements"],
+ caption="{0} Architectures".format(name),
+ )
+
+
+
+
+[docs]
+def printDocumentationOf(targets: List[str] = None, style: str = "rst:list") -> None:
+ """
+ Generate documentation of resources (either a unit or a whole file).
+
+ Supported syntaxes:
+
+ * libraryName.entityName
+ * TODO: libraryName.entityName(architectureName)
+ * TODO: libraryName.packageName
+ * TODO: libraryName.configurationName
+ * TODO: libraryName.architectureName
+ * TODO: relative/path/to/file
+ * TODO: absolute/path/to/file
+
+ :param targets: list of resources to generate the documentation for.
+ :param style: format of the output. Supported values are: 'rst:list' and 'rst:table'.
+ """
+ if targets is None:
+ print("Design content:\n")
+ for libName, lib in DESIGN.Libraries.items():
+ ents = lib.Entities
+ print("* Library *{0}* [{1} entities]\n".format(libName, len(ents)))
+ for ent in ents:
+ print(" * Entity: *{0}*\n".format(ent.Identifier))
+ for arch in ent.Architectures:
+ print(" * Architecture: *{0}*\n".format(arch.Identifier))
+ print("")
+ return
+
+ if style not in STYLES:
+ raise Exception("Unknown style <{0}>".format(style))
+
+ for target in targets:
+ # TODO: support other argument types:
+ # - libName.packageName
+ # - path/to/file (print all the units in the file)
+ parts = target.split(".")
+ assert len(parts) == 2
+ libName, entityName = parts
+ lib = DESIGN.GetLibrary(libName)
+ entity = None
+ for entity in lib.Entities:
+ if entity.Identifier == entityName:
+ break
+ else:
+ raise Exception("Entity {0} not found in library {1}.".format(entityName, libraryName))
+
+ if style == "rst:list":
+ printDocumentationOfEntity_rstList(entity, lib)
+ continue
+
+ if style == "rst:table":
+ printDocumentationOfEntity_rstTable(entity, lib)
+ continue
+
+
' + + '' + + _("Hide Search Matches") + + "
" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/_static/styles/furo-extensions.css b/_static/styles/furo-extensions.css new file mode 100644 index 0000000..bc447f2 --- /dev/null +++ b/_static/styles/furo-extensions.css @@ -0,0 +1,2 @@ +#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;opacity:1;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)} +/*# sourceMappingURL=furo-extensions.css.map*/ \ No newline at end of file diff --git a/_static/styles/furo-extensions.css.map b/_static/styles/furo-extensions.css.map new file mode 100644 index 0000000..9ba5637 --- /dev/null +++ b/_static/styles/furo-extensions.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo-extensions.css","mappings":"AAGA,2BACE,oFACA,4CAKE,6CAHA,YACA,eAEA,CACA,kDACE,yCAEF,8CACE,sCAEJ,8CACE,kDAEJ,2BAGE,uBACA,cAHA,gBACA,UAEA,CAGA,yCACE,mBAEF,gDAEE,gDADA,YACA,CACA,sDACE,gDACF,yDACE,sCAEJ,+CACE,UACA,qDACE,UAGF,mDACE,eAEJ,yEAEE,4DAEA,mHASE,mBAPA,kBAEA,YADA,oBAGA,aADA,gBAIA,CAEA,qIAEE,WADA,UACA,CAEJ,uGACE,aAEF,iUAGE,cAEF,mHACE,aC1EJ,gCACE,mCAEF,0BAKE,mBAUA,8CACA,YAFA,mCAKA,eAZA,cALA,UASA,YADA,YAYA,iCAdA,YAcA,CAEA,gCAEE,8CADA,gCACA,CAEF,gCAGE,6BADA,mCADA,YAEA,CAEF,kCAEE,cADA,oBACA,CACA,wCACE,cAEJ,8BACE,UC5CN,KAEE,6CAA8C,CAC9C,uDAAwD,CACxD,uDAAwD,CAGxD,iCAAsC,CAGtC,+CAAgD,CAChD,uDAAwD,CACxD,uDAAwD,CACxD,oDAAqD,CACrD,6DAA8D,CAC9D,6DAA8D,CAG9D,uDAAwD,CACxD,yDAA0D,CAC1D,4DAA6D,CAC7D,2DAA4D,CAC5D,8DAA+D,CAC/D,iEAAkE,CAClE,uDAAwD,CACxD,wDAAyD,CAG3D,gBACE,qFAGF,SACE,6EAEF,cACE,uFAEF,cACE,uFAEF,cACE,uFAGF,qBACE,eAEF,mBACE,WACA,eChDF,KACE,gDAAiD,CACjD,uDAAwD,CACxD,qDAAsD,CACtD,4DAA6D,CAC7D,oCAAqC,CACrC,2CAA4C,CAC5C,4CAA6C,CAC7C,mDAAoD,CACpD,wBAAyB,CACzB,oBAAqB,CACrB,6CAA8C,CAC9C,gCAAiC,CACjC,yDAA0D,CAC1D,uDAAwD,CACxD,8DAA+D,CCbjE,uBACE,eACA,eACA,gBAGF,iBACE,YACA,+EAGF,iBACE,mDACA","sources":["webpack:///./src/furo/assets/styles/extensions/_readthedocs.sass","webpack:///./src/furo/assets/styles/extensions/_copybutton.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-design.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-inline-tabs.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-panels.sass"],"sourcesContent":["// This file contains the styles used for tweaking how ReadTheDoc's embedded\n// contents would show up inside the theme.\n\n#furo-sidebar-ad-placement\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n .ethical-sidebar\n // Remove the border and box-shadow.\n border: none\n box-shadow: none\n // Manage the background colors.\n background: var(--color-background-secondary)\n &:hover\n background: var(--color-background-hover)\n // Ensure the text is legible.\n a\n color: var(--color-foreground-primary)\n\n .ethical-callout a\n color: var(--color-foreground-secondary) !important\n\n#furo-readthedocs-versions\n position: static\n width: 100%\n background: transparent\n display: block\n\n // Make the background color fit with the theme's aesthetic.\n .rst-versions\n background: rgb(26, 28, 30)\n\n .rst-current-version\n cursor: unset\n background: var(--color-sidebar-item-background)\n &:hover\n background: var(--color-sidebar-item-background)\n .fa-book\n color: var(--color-foreground-primary)\n\n > .rst-other-versions\n padding: 0\n small\n opacity: 1\n\n .injected\n .rst-versions\n position: unset\n\n &:hover,\n &:focus-within\n box-shadow: 0 0 0 1px var(--color-sidebar-background-border)\n\n .rst-current-version\n // Undo the tweaks done in RTD's CSS\n font-size: inherit\n line-height: inherit\n height: auto\n text-align: right\n padding: 12px\n\n // Match the rest of the body\n background: #1a1c1e\n\n .fa-book\n float: left\n color: white\n\n .fa-caret-down\n display: none\n\n .rst-current-version,\n .rst-other-versions,\n .injected\n display: block\n\n > .rst-current-version\n display: none\n",".highlight\n &:hover button.copybtn\n color: var(--color-code-foreground)\n\n button.copybtn\n // Make it visible\n opacity: 1\n\n // Align things correctly\n align-items: center\n\n height: 1.25em\n width: 1.25em\n\n top: 0.625rem // $code-spacing-vertical\n right: 0.5rem\n\n // Make it look better\n color: var(--color-background-item)\n background-color: var(--color-code-background)\n border: none\n\n // Change to cursor to make it obvious that you can click on it\n cursor: pointer\n\n // Transition smoothly, for aesthetics\n transition: color 300ms, opacity 300ms\n\n &:hover\n color: var(--color-brand-content)\n background-color: var(--color-code-background)\n\n &::after\n display: none\n color: var(--color-code-foreground)\n background-color: transparent\n\n &.success\n transition: color 0ms\n color: #22863a\n &::after\n display: block\n\n svg\n padding: 0\n","body\n // Colors\n --sd-color-primary: var(--color-brand-primary)\n --sd-color-primary-highlight: var(--color-brand-content)\n --sd-color-primary-text: var(--color-background-primary)\n\n // Shadows\n --sd-color-shadow: rgba(0, 0, 0, 0.05)\n\n // Cards\n --sd-color-card-border: var(--color-card-border)\n --sd-color-card-border-hover: var(--color-brand-content)\n --sd-color-card-background: var(--color-card-background)\n --sd-color-card-text: var(--color-foreground-primary)\n --sd-color-card-header: var(--color-card-marginals-background)\n --sd-color-card-footer: var(--color-card-marginals-background)\n\n // Tabs\n --sd-color-tabs-label-active: var(--color-brand-content)\n --sd-color-tabs-label-hover: var(--color-foreground-muted)\n --sd-color-tabs-label-inactive: var(--color-foreground-muted)\n --sd-color-tabs-underline-active: var(--color-brand-content)\n --sd-color-tabs-underline-hover: var(--color-foreground-border)\n --sd-color-tabs-underline-inactive: var(--color-background-border)\n --sd-color-tabs-overline: var(--color-background-border)\n --sd-color-tabs-underline: var(--color-background-border)\n\n// Tabs\n.sd-tab-content\n box-shadow: 0 -2px var(--sd-color-tabs-overline), 0 1px var(--sd-color-tabs-underline)\n\n// Shadows\n.sd-card // Have a shadow by default\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n.sd-shadow-sm\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-md\n box-shadow: 0 0.3rem 0.75rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-lg\n box-shadow: 0 0.6rem 1.5rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Cards\n.sd-card-hover:hover // Don't change scale on hover\n transform: none\n\n.sd-cards-carousel // Have a bit of gap in the carousel by default\n gap: 0.25rem\n padding: 0.25rem\n","// This file contains styles to tweak sphinx-inline-tabs to work well with Furo.\n\nbody\n --tabs--label-text: var(--color-foreground-muted)\n --tabs--label-text--hover: var(--color-foreground-muted)\n --tabs--label-text--active: var(--color-brand-content)\n --tabs--label-text--active--hover: var(--color-brand-content)\n --tabs--label-background: transparent\n --tabs--label-background--hover: transparent\n --tabs--label-background--active: transparent\n --tabs--label-background--active--hover: transparent\n --tabs--padding-x: 0.25em\n --tabs--margin-x: 1em\n --tabs--border: var(--color-background-border)\n --tabs--label-border: transparent\n --tabs--label-border--hover: var(--color-foreground-muted)\n --tabs--label-border--active: var(--color-brand-content)\n --tabs--label-border--active--hover: var(--color-brand-content)\n","// This file contains styles to tweak sphinx-panels to work well with Furo.\n\n// sphinx-panels includes Bootstrap 4, which uses .container which can conflict\n// with docutils' `.. container::` directive.\n[role=\"main\"] .container\n max-width: initial\n padding-left: initial\n padding-right: initial\n\n// Make the panels look nicer!\n.shadow.docutils\n border: none\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Make panel colors respond to dark mode\n.sphinx-bs .card\n background-color: var(--color-background-secondary)\n color: var(--color-foreground)\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/_static/styles/furo.css b/_static/styles/furo.css new file mode 100644 index 0000000..3d29a21 --- /dev/null +++ b/_static/styles/furo.css @@ -0,0 +1,2 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{clip:rect(0,0,0,0)!important;border:0!important;height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:1px!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:"SFMono-Regular",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8,