From be0e2809e9b9facc137f74782e6282b0d54796ef Mon Sep 17 00:00:00 2001 From: Jorge Marques <2892061+gastmaier@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:32:04 -0300 Subject: [PATCH] docs: Use doctools (#1258) The extensions have been moved to docs tools. The source code is available at https://github.com/analogdevicesinc/doctools And is installed as before: (cd docs ; pip install -r requirements.txt --upgrade) Since the package is listed on the requirements.txt file. Also, add index for library and projects Signed-off-by: Jorge Marques --- README.md | 6 +- docs/conf.py | 45 +- docs/extensions/adi_hdl_parser.py | 994 ---------------------------- docs/extensions/adi_hdl_render.py | 230 ------- docs/extensions/adi_hdl_static.py | 40 -- docs/extensions/adi_links.py | 255 ------- docs/index.rst | 53 +- docs/library/index.rst | 18 + docs/projects/ad469x_fmc/index.rst | 7 +- docs/projects/index.rst | 36 + docs/requirements.txt | 10 +- docs/sources/custom.css | 155 ----- docs/user_guide/docs_guidelines.rst | 554 +--------------- docs/user_guide/git_repository.rst | 3 +- docs/user_guide/index.rst | 2 +- 15 files changed, 115 insertions(+), 2293 deletions(-) delete mode 100644 docs/extensions/adi_hdl_parser.py delete mode 100644 docs/extensions/adi_hdl_render.py delete mode 100644 docs/extensions/adi_hdl_static.py delete mode 100644 docs/extensions/adi_links.py create mode 100644 docs/library/index.rst create mode 100644 docs/projects/index.rst delete mode 100755 docs/sources/custom.css diff --git a/README.md b/README.md index f8827d923..acdfd1c50 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,13 @@ This repository supports reference designs for different [Analog Devices boards] ### Building documentation +Ensure pip is newer than version 23. +``` +pip install pip --upgrade +``` Install the documentation tools. ``` -(cd docs ; pip install -r requirements.txt) +(cd docs ; pip install -r requirements.txt --upgrade) ``` Build the libraries (recommended). ``` diff --git a/docs/conf.py b/docs/conf.py index 3fe227e85..840ad6c0c 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,33 +1,33 @@ -# Configuration file for the Sphinx documentation builder. -# -# https://www.sphinx-doc.org/en/master/usage/configuration.html +# -- Project information ------------------------------------------------------ -# -- Project information ----------------------------------------------------- +repository = 'hdl' +project = 'HDL' +copyright = '2024, Analog Devices, Inc.' +author = 'Analog Devices, Inc.' -project = 'HDL, Analog Devices' -copyright = '2023, Analog Devices Inc' -author = 'Analog Devices Inc' -release = 'v0.1' - -# -- General configuration --------------------------------------------------- - -import os, sys - -sys.path.append(os.path.abspath("./extensions")) +# -- General configuration ---------------------------------------------------- extensions = [ - "sphinx.ext.todo", - "sphinx.ext.viewcode", - "sphinxcontrib.wavedrom", - "adi_links", - "adi_hdl_parser" + "sphinx.ext.todo", + "sphinx.ext.intersphinx", + "sphinxcontrib.wavedrom", + "adi_doctools" ] -templates_path = ['sources/template'] +needs_extensions = { + 'adi_doctools': '0.3' +} exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +source_suffix = '.rst' -# -- Custom extensions configuration ------------------------------------------- +# -- External docs configuration ---------------------------------------------- + +intersphinx_mapping = { + 'doctools': ('https://analogdevicesinc.github.io/doctools', None) +} + +# -- Custom extensions configuration ------------------------------------------ hide_collapsible_content = True validate_links = False @@ -39,8 +39,7 @@ todo_emit_warnings = True # -- Options for HTML output -------------------------------------------------- -html_theme = 'furo' +html_theme = 'cosmic' html_static_path = ['sources'] -source_suffix = '.rst' html_css_files = ["custom.css"] html_favicon = "sources/icon.svg" diff --git a/docs/extensions/adi_hdl_parser.py b/docs/extensions/adi_hdl_parser.py deleted file mode 100644 index f97f29a6c..000000000 --- a/docs/extensions/adi_hdl_parser.py +++ /dev/null @@ -1,994 +0,0 @@ -############################################################################### -## Copyright (C) 2022-2023 Analog Devices, Inc. All rights reserved. -### SPDX short identifier: ADIBSD -############################################################################### - -import os.path -import contextlib -import re -from docutils import nodes -from docutils.statemachine import ViewList -from docutils.parsers.rst import Directive, directives -from sphinx.util.nodes import nested_parse_with_titles -from sphinx.util import logging -from lxml import etree -from adi_hdl_static import hdl_strings -from adi_hdl_render import hdl_component -from uuid import uuid4 -from hashlib import sha1 - -logger = logging.getLogger(__name__) - -dft_hide_collapsible_content = True - -class node_base(nodes.Element, nodes.General): - """ - Adapted from - https://github.com/pradyunsg/sphinx-inline-tabs - https://github.com/dgarcia360/sphinx-collapse - """ - - @staticmethod - def visit(translator, node): - attributes = node.attributes.copy() - - attributes.pop("ids") - attributes.pop("classes") - attributes.pop("names") - attributes.pop("dupnames") - attributes.pop("backrefs") - - text = translator.starttag(node, node.tagname, **attributes) - translator.body.append(text.strip()) - - @staticmethod - def depart(translator, node): - if node.endtag: - translator.body.append(f"") - - @staticmethod - def default(translator, node): - pass - -class node_div(node_base): - tagname = 'div' - endtag = 'true' - -class node_input(node_base): - tagname = 'input' - endtag = 'false' - -class node_label(node_base): - tagname = 'label' - endtag = 'true' - -class node_icon(node_base): - tagname = 'div' - endtag = 'false' - -def dot_fix(string): - if (string.rfind('.') != len(string)-1): - return string + '.' - else: - return string - -def pretty_dep(string): - if string is None: - return '' - return string.replace("'MODELPARAM_VALUE.",'').replace("'",'') - -class directive_base(Directive): - has_content = True - add_index = True - current_doc = '' - final_argument_whitespace = True - - @staticmethod - def get_descriptions(content): - items = {} - key = '' - for line in content: - if line.startswith('* -'): - key = line[line.find('* -')+3:].split()[0] - items[key] = [] - else: - items[key].append(line) - for key in items: - items[key] = ' '.join(items[key]).replace('-', '', 1).strip() - return items - - def column_entry(self, row, text, node_type, classes=[]): - entry = nodes.entry(classes=classes) - if node_type == 'literal': - entry += nodes.literal(text=text) - elif node_type == 'paragraph': - entry += nodes.paragraph(text=text) - elif node_type == 'reST': - rst = ViewList() - rst.append(text, f"virtual_{str(uuid4())}", 0) - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, rst, node) - entry += node - elif node_type == 'default_value': - if text[0:2] != '0x': - rst = ViewList() - rst.append(text, f"virtual_{str(uuid4())}", 0) - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, rst, node) - entry += node - else: - entry += nodes.literal(text=text) - else: - return - row += entry - - def column_entries(self, rows, items): - row = nodes.row() - for item in items: - if len(item) == 3: - self.column_entry(row, item[0], item[1], classes=item[2]) - else: - self.column_entry(row, item[0], item[1]) - rows.append(row) - - def generic_table(self, description): - tgroup = nodes.tgroup(cols=2) - for _ in range(2): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table() - table += tgroup - - self.table_header(tgroup, ["Name", "Description"]) - - rows = [] - for key in description: - row = nodes.row() - entry = nodes.entry() - entry += nodes.literal(text="{:s}".format(key)) - row += entry - entry = nodes.entry() - rst = ViewList() - rst.append(description[key], f"virtual_{str(uuid4())}", 0) - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, rst, node) - entry += node - row += entry - rows.append(row) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - - return table - - @staticmethod - def table_header(tgroup, columns): - thead = nodes.thead() - tgroup += thead - row = nodes.row() - - for header_name in columns: - entry = nodes.entry() - entry += nodes.paragraph(text=header_name) - row += entry - - thead.append(row) - - def collapsible(self, section, text=""): - env = self.state.document.settings.env - - _id = sha1(text.encode('utf-8')).hexdigest() - container = nodes.container( - "", - is_div=True, - classes=['collapsible'] - ) - checked = {"checked": ''} if not env.config.hide_collapsible_content else {} - input_ = node_input( - type="checkbox", - **checked, - ids=[_id], - name=_id, - classes=['collapsible_input'] - ) - label = node_label( - **{"for": _id} - ) - icon = node_icon( - classes=['icon'] - ) - content = nodes.container( - "", - is_div=True, - classes=['collapsible_content'] - ) - label += nodes.paragraph(text=text) - label += icon - - container += input_ - container += label - container += content - - section += container - - return (content, label) - -class directive_collapsible(directive_base): - option_spec = {'path': directives.unchanged} - required_arguments = 1 - optional_arguments = 0 - - def run(self): - self.assert_has_content() - - env = self.state.document.settings.env - self.current_doc = env.doc2path(env.docname) - - node = node_div() - - content, _ = self.collapsible(node, self.arguments[0].strip()) - self.state.nested_parse(self.content, self.content_offset, content) - - return [ node ] - -class directive_interfaces(directive_base): - option_spec = {'path': directives.unchanged} - required_arguments = 0 - optional_arguments = 0 - - def tables(self, subnode, content, component): - description = self.get_descriptions(content) - - if component is None: - return self.generic_table(description) - - bs = component['bus_interface'] - for tag in bs: - section = nodes.section( - ids=[f"bus-interface-{tag}"] - ) - title = nodes.title(text=tag) - section += title - - if bs[tag]['dependency'] is not None: - section += [nodes.inline(text="Enabled if "), - nodes.literal(text=pretty_dep(bs[tag]['dependency'])), - nodes.inline(text=".")] - if tag in description: - rst = ViewList() - rst.append(description[tag], f"virtual_{str(uuid4())}", 0) - node = nodes.section() - node.document = self.state.document - nested_parse_with_titles(self.state, rst, node) - section += node - - content, _ = self.collapsible(section, f"Ports of {tag} bus.") - - tgroup = nodes.tgroup(cols=3) - for _ in range(3): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table() - table += tgroup - - self.table_header(tgroup, ["Physical Port", "Logical Port", "Direction"]) - - rows = [] - pm = bs[tag]['port_map'] - for key in pm: - self.column_entries(rows, [ - [key, 'literal'], - [pm[key]['logical_port'], 'literal'], - [pm[key]['direction'], 'paragraph'], - ]) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - content += table - - subnode += section - - section = nodes.section(ids=[f"ports"]) - title = nodes.title(text="Ports") - section += title - content, _ = self.collapsible(section, f"Ports table.") - - tgroup = nodes.tgroup(cols=4) - for _ in range(4): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table() - table += tgroup - - self.table_header(tgroup, ["Physical Port", "Direction", "Dependency", "Description"]) - - rows = [] - pr = component['ports'] - dm = component['bus_domain'] - for key in pr: - row = nodes.row() - self.column_entry(row, key, 'literal') - self.column_entry(row, pr[key]['direction'], 'paragraph') - self.column_entry(row, pretty_dep(pr[key]['dependency']), 'paragraph') - if 'clk' in key or 'clock' in key: - domain = 'clock domain' - elif 'reset': - domain = 'reset signal' - else: - domain = 'domain' - if key in dm: - bus = 'Buses' if len(dm[key]) > 1 else 'Bus' - plr = 'are' if len(dm[key]) > 1 else 'is' - in_domain = f"{bus} ``{'``, ``'.join(dm[key])}`` {plr} synchronous to this {domain}." - else: - in_domain = "" - if key in description: - self.column_entry(row, " ".join([description[key], in_domain]), 'reST', classes=['description']) - else: - self.column_entry(row, in_domain, 'reST', classes=['description']) - rows.append(row) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - content += table - - subnode += section - - for tag in description: - if tag not in bs and tag not in pr: - logger.warning(f"Signal {tag} defined in the directive does not exist in the IP-XACT (component.xml)!") - - return subnode - - def run(self): - env = self.state.document.settings.env - self.current_doc = env.doc2path(env.docname) - - node = node_div() - - if 'path' in self.options: - lib_name = self.options['path'] - else: - lib_name = env.docname.replace('/index', '') - - if lib_name in env.component: - self.tables(node, self.content, env.component[lib_name]) - else: - self.tables(node, self.content, None) - - return [ node ] - -class directive_regmap(directive_base): - option_spec = {'name': directives.unchanged, 'no-type-info': directives.unchanged} - required_arguments = 0 - optional_arguments = 0 - - def tables(self, subnode, obj): - section = nodes.section(ids=[f"register-map-{obj['title']}"]) - title = nodes.title(text=f"{obj['title']} ({obj['title']})") - - section += title - content, _ = self.collapsible(section, f"Register map table.") - tgroup = nodes.tgroup(cols=7) - for _ in range(7): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table(classes=['regmap']) - table += tgroup - - self.table_header(tgroup, ["DWORD", "BYTE", "BITS", "Name", "Type", "Default Value", "Description"]) - - rows = [] - for reg in obj['regmap']: - self.column_entries(rows, [ - [reg['address'][0], 'literal'], - [reg['address'][1], 'literal'], - ['', 'literal'], - [reg['name'], 'literal'], - ['', 'literal'], - ['', 'literal'], - [reg['description'], 'reST', ['description']], - ]) - - for field in reg['fields']: - self.column_entries(rows, [ - ['', 'literal'], - ['', 'literal'], - [f"[{field['bits']}]", 'literal'], - [field['name'], 'literal'], - [field['rw'], 'literal'], - [field['default'], 'default_value', ['default']], - [field['description'], 'reST', ['description']], - ]) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - content += table - - subnode += section - - if 'no-type-info' in self.options: - return subnode - - tgroup = nodes.tgroup(cols=3) - for _ in range(3): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table() - table += tgroup - - self.table_header(tgroup, ["Access Type", "Name", "Description"]) - - rows = [] - for at in obj['access_type']: - self.column_entries(rows, [ - [at, 'paragraph'], - [hdl_strings.access_type[at]['name'], 'paragraph'], - [hdl_strings.access_type[at]['description'], 'paragraph'] - ]) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - section += table - - return subnode - - def run(self): - env = self.state.document.settings.env - self.current_doc = env.doc2path(env.docname) - if os.getcwd() not in self.current_doc: - raise Exception(f"Inconsistent paths, {os.getcwd()} not in {self.current_doc}") - owner = self.current_doc[len(os.getcwd())+1:-4] - - node = node_div() - - if 'name' in self.options: - lib_name = self.options['name'] - else: - logger.warning("hdl-regmap directive without name option, skipped!") - return [ node ] - - subnode = nodes.section(ids=["hdl-regmap"]) - - # Have to search all because it is allowed to have more than one regmap per file... - file = None - for f in env.regmaps: - if lib_name in env.regmaps[f]['subregmap']: - file = f - break - - if file is None: - logger.warning(f"Title tool {lib_name} not-found in any regmap file, skipped!") - return [ node ] - - if owner not in env.regmaps[f]['owners']: - env.regmaps[f]['owners'].append(owner) - self.tables(subnode, env.regmaps[f]['subregmap'][lib_name]) - - node += subnode - return [ node ] - -class directive_parameters(directive_base): - option_spec = {'path': directives.unchanged} - required_arguments = 0 - optional_arguments = 0 - - def tables(self, content, parameter): - description = self.get_descriptions(content) - - if parameter is None: - return self.generic_table(description) - - tgroup = nodes.tgroup(cols=5) - for _ in range(5): - colspec = nodes.colspec(colwidth=1) - tgroup.append(colspec) - table = nodes.table() - table += tgroup - - self.table_header(tgroup, ["Name", "Description", "Data Type", "Default Value", "Choices/Range"]) - - rows = [] - for key in parameter: - row = nodes.row() - self.column_entry(row, "{:s}".format(key), 'literal') - if key in description: - self.column_entry(row, description[key], 'reST', classes=['description']) - else: - self.column_entry(row, dot_fix(parameter[key]['description']), 'paragraph', classes=['description']) - for tag, ty in zip(['type', 'default'], ['paragraph', 'literal']): - if parameter[key][tag] is not None: - self.column_entry(row, parameter[key][tag], ty, classes=[tag]) - else: - logger.warning(f"Got empty {tag} at parameter {key}!") - self.column_entry(row, "", 'paragraph') - crange = [] - for tag in ['choices', 'range']: - if parameter[key][tag] is not None: - crange.append(parameter[key][tag]) - crange = '. '.join(crange) - self.column_entry(row, crange, 'paragraph') - - rows.append(row) - - tbody = nodes.tbody() - tbody.extend(rows) - tgroup += tbody - - for tag in description: - if tag not in parameter: - logger.warning(f"{tag} defined in the directive does not exist in the IP-XACT (component.xml)!") - - return table - - def run(self): - env = self.state.document.settings.env - self.current_doc = env.doc2path(env.docname) - - node = node_div() - - if 'path' not in self.options: - self.options['path'] = env.docname.replace('/index', '') - lib_name = self.options['path'] - - subnode = nodes.section(ids=["hdl-parameters"]) - if lib_name in env.component: - subnode += self.tables(self.content, env.component[lib_name]['parameters']) - else: - subnode += self.tables(self.content, None) - - node += subnode - - return [ node ] - -class directive_component_diagram(directive_base): - option_spec = {'path': directives.unchanged} - required_arguments = 0 - optional_arguments = 0 - - def missing_diagram(self): - svg_raw = hdl_component.render_placeholder(self.options['path']) - - svg = nodes.raw('', svg_raw, format='html') - return [ svg ] - - def diagram(self): - name = hdl_component.get_name(self.options['path']) - path = '_build/managed' - f = open(os.path.join(path, name)) - svg_raw = f.read() - - svg = nodes.raw('', svg_raw, format='html') - return [ svg ] - - def run(self): - env = self.state.document.settings.env - self.current_doc = env.doc2path(env.docname) - - node = node_div() - - if 'path' not in self.options: - self.options['path'] = env.docname.replace('/index', '') - lib_name = self.options['path'] - - subnode = nodes.section(ids=["hdl-component-diagram"]) - if lib_name in env.component: - subnode += self.diagram() - else: - subnode += self.missing_diagram() - - node += subnode - - return [ node ] - - -def parse_hdl_component(path, ctime): - component = { - 'bus_interface':{}, - 'bus_domain':{}, - 'ports': {}, - 'parameters': {}, - 'ctime': ctime - } - - def get_namespaces(item): - nsmap = item.nsmap - for i in ['spirit', 'xilinx', 'xsi']: - if i not in nsmap: - raise Exception(f"Required namespace {i} not in file!") - - return (nsmap['spirit'], nsmap['xilinx'], nsmap['xsi']) - - def get(item, local_name): - items = get_all(item, local_name) - if len(items) == 0: - return None - else: - return items[0] - - def get_all(item, local_name): - template = "/*[local-name()='%s']" - if not isinstance(local_name, str): - raise Exception("Got wrong type, only Strings are allowed") - local_name = local_name.split('/') - return item.xpath('.' + ''.join([template % ln for ln in local_name])) - - def sattrib(item, attr): - nonlocal spirit - return item.get(f"{{{spirit}}}{attr}") - - def xattrib(item, attr): - nonlocal xilinx - return item.get(f"{{{xilinx}}}{attr}") - - def stag(item): - nonlocal spirit - return item.tag.replace(f"{{{spirit}}}",'') - - def xtag(item): - nonlocal xilinx - return item.tag.replace(f"{{{xilinx}}}",'') - - def clean_dependency(string): - return string[string.find("'"): string.rfind(')')].replace(')','') - - def get_dependency(item, type_=None): - if type_ is None: - type_ = stag(item) - - dependency = get(item, f"vendorExtensions/{type_}Info/enablement/isEnabled") - if dependency is None: - return None - else: - return clean_dependency(xattrib(dependency, 'dependency')) - - def get_range(item): - min_ = sattrib(item, 'minimum') - max_ = sattrib(item, 'maximum') - if max_ == None or min_ == None: - return None - else: - return f"From {min_} to {max_}." - - def get_choice_type(name): - return name[name.find('_')+1:name.rfind('_')] - - def format_default_value(value, fmt): - if fmt == "bitString" and value[0:2].lower() == "0x": - return f"'h{value[2:].upper()}" - if fmt == "bitString" and value[0] == '"' and value[-1] == '"': - return f"'b{value[1:-1]}" - if fmt == "bool": - return value.title() - return value - - root = etree.parse(path).getroot() - spirit, xilinx, _ = get_namespaces(root) - vendor = get(root, 'vendor').text - name = get(root, 'name').text - - bs = component['bus_interface'] - dm = component['bus_domain'] - for bus_interface in get_all(root, 'busInterfaces/busInterface'): - bus_name = get(bus_interface, 'name').text - if '_signal_clock' in bus_name: - signal_name = get(get(bus_interface, 'portMaps/portMap'), 'physicalPort/name').text - if signal_name not in dm: - dm[signal_name] = [] - dm[signal_name].append(bus_name[0:bus_name.find('_signal_clock')]) - continue - if '_signal_reset' in bus_name: - signal_name = get(get(bus_interface, 'portMaps/portMap'), 'physicalPort/name').text - if signal_name not in dm: - dm[signal_name] = [] - dm[signal_name].append(bus_name[0:bus_name.find('_signal_reset')]) - continue - - if get(bus_interface, 'slave') is not None: - bus_role = 'slave' - elif get(bus_interface, 'master') is not None: - bus_role = 'master' - else: - bus_role = None - - bs[bus_name] = { - 'name': sattrib(get(bus_interface, 'busType'), 'name'), - 'role': bus_role, - 'dependency': get_dependency(bus_interface, 'busInterface'), - 'port_map': {} - } - - pm = bs[bus_name]['port_map'] - for port_map in get_all(bus_interface, 'portMaps/portMap'): - pm[get(port_map, 'physicalPort/name').text] = { - 'logical_port': get(port_map, 'logicalPort/name').text, - 'direction': '' - } - - lport = component['ports'] - for port in get_all(root, 'model/ports/port'): - port_name = get(port, 'name').text - port_direction = get(port, 'wire/direction').text - - found = False - for bus in bs: - if port_name in bs[bus]['port_map']: - found = True - bs[bus]['port_map'][port_name]['direction'] = port_direction - break; - - if found == False: - lport[port_name] = { - 'direction': port_direction, - 'dependency': get_dependency(port, 'port') - } - pr = component['parameters'] - for parameter in get_all(root, 'parameters/parameter'): - param_description = get(parameter, 'displayName') - if param_description is not None: - param_name = get(parameter, 'name').text - param_value = get(parameter, 'value') - param_format = sattrib(param_value, 'format') - pr[param_name] = { - 'description': param_description.text, - 'default': format_default_value(param_value.text, param_format), - 'type': param_format, - '_choice_ref': sattrib(param_value, 'choiceRef'), - 'choices': None, - 'range': get_range(param_value) - } - - for parameter in get_all(root, 'model/modelParameters/modelParameter'): - param_name = get(parameter, 'name').text - param_type = sattrib(parameter, 'dataType') - if param_type == "std_logic_vector": - param_type = "logic vector" - if param_type is not None and param_name in pr: - if pr[param_name]['type'] is None: - pr[param_name]['type'] = param_type.capitalize() - else: - param_format = pr[param_name]['type'] - pr[param_name]['type'] = param_format[0].upper()+param_format[1:] - - for choice in get_all(root, 'choices/choice'): - name = get(choice, 'name').text - for key in pr: - if pr[key]['_choice_ref'] == name: - type_ = get_choice_type(name) - values = get_all(choice, 'enumeration') - string = [] - if type_ == 'pairs': - for v in values: - string.append(f"{sattrib(v, 'text')} ({v.text})") - elif type_ == 'list': - for v in values: - string.append(v.text) - else: - break - pr[key]['choices'] = ', '.join(string) - break - - return component - -def manage_hdl_components(env, docnames, libraries): - if not hasattr(env, 'component'): - env.component = {} - cp = env.component - for lib in list(cp): - f = f"../{lib}/component.xml" - if not os.path.isfile(f): - del cp[lib] - - for lib, doc in libraries: - f = f"../{lib}/component.xml" - if not os.path.isfile(f): - continue - ctime = os.path.getctime(f) - if lib in cp and cp[lib]['ctime'] >= ctime: - pass - else: - cp[lib] = parse_hdl_component(f, ctime) - hdl_component.render(env, lib, cp[lib]) - docnames.append(doc) - -# From https://github.com/tfcollins/vger/blob/main/vger/hdl_reg_map.py -def parse_hdl_regmap(reg, ctime): - regmap = { - 'subregmap': {}, - 'owners':[], - 'ctime': ctime - } - - with open(f"regmap/adi_regmap_{reg}.txt", "r") as f: - data = f.readlines() - data = [d.replace("\n", "") for d in data] - - while "TITLE" in data: - # Get title - tit = data.index("TITLE") - - title = str(data[tit + 1].strip()) - title_tool = str(data[tit + 2].strip()) - data = data[tit + 2 :] - - if 'ENDTITLE' in [title_tool, title]: - logger.warning(f"Malformed title fields at file regmap/adi_regmap_{reg}.txt, skipped!") - continue - - regmap['subregmap'][title_tool] = { - 'title': title, - 'regmap': [], - 'access_type': [] - } - - # Get registers - access_type = [] - while "REG" in data: - regi = data.index("REG") - rfi = data.index("ENDREG") - - if not regi: - break - - reg_addr = data[regi + 1].strip() - reg_name = data[regi + 2].strip() - reg_desc = [data[fi].strip() for fi in range(regi + 3, rfi)] - reg_desc = " ".join(reg_desc) - - with contextlib.suppress(ValueError): - if tet := data.index("TITLE"): - if regi > tet: - # into next regmap - break - data = data[regi + 1 :] - - # Get fields - fields = [] - while "FIELD" in data: - fi = data.index("FIELD") - efi = data.index("ENDFIELD") - - if not fi: - break - - with contextlib.suppress(ValueError): - if rege := data.index("REG"): - if fi > rege: - # into next register - break - - field_loc = data[fi + 1].strip() - field_loc = field_loc.split(" ") - field_bits = field_loc[0].replace("[", "").replace("]", "") - field_default = ' '.join(field_loc[1:]) if len(field_loc) > 1 else "NA" - - field_name = data[fi + 2].strip() - field_rw = data[fi + 3].strip() - - if field_rw == 'R': - field_rw = 'RO' - elif field_rw == 'W': - field_rw = 'WO' - if '-V' in field_rw: - if 'V' not in access_type: - access_type.append('V') - field_rw_ = field_rw.replace('-V','') - if field_rw_ not in access_type: - if field_rw_ not in hdl_strings.access_type: - logger.warning(f"Malformed access type {field_rw} for register {field_name}, file regmap/adi_regmap_{reg}.txt.") - else: - access_type.append(field_rw) - - field_desc = [data[fi].strip() for fi in range(fi + 4, efi)] - field_desc = " ".join(field_desc) - - # TODO Remove dokuwiki scaping support - # Temporary dokuwiki scaping convert to not break current dokuwiki tables - field_default = field_default.replace("''", "``") - field_desc = field_desc.replace("''", "``") - - fields.append( - { - "name": field_name, - "bits": field_bits, - "default": field_default, - "rw": field_rw, - "description": field_desc, - } - ) - - data = data[fi + 1 :] - - try: - if '+' in reg_addr: - reg_addr_ = reg_addr.split('+') - reg_addr_[0] = int(reg_addr_[0], 16) - reg_addr_[1] = int(reg_addr_[1].replace('*n',''), 16) - reg_addr_dword = f"{hex(reg_addr_[0])} + {hex(reg_addr_[1])}*n" - reg_addr_byte = f"{hex(reg_addr_[0]<<2)} + {hex(reg_addr_[1]<<2)}*n" - else: - reg_addr_ = int(reg_addr, 16) - reg_addr_dword = f"{hex(reg_addr_)}" - reg_addr_byte = f"{hex(reg_addr_<<2)}" - except: - logger.warning(f"Got malformed register address {reg_addr} for register {reg_name}, file regmap/adi_regmap_{reg}.txt.") - reg_addr_dword = "" - reg_addr_byte = "" - - regmap['subregmap'][title_tool]['regmap'].append( - { - 'name': reg_name, - 'address': [reg_addr_dword, reg_addr_byte], - 'description': reg_desc, - 'fields': fields - } - ) - regmap['subregmap'][title_tool]['access_type'] = access_type - return regmap - -def manage_hdl_regmaps(env, docnames): - if not hasattr(env, 'regmaps'): - env.regmaps = {} - - rm = env.regmaps - for lib in list(rm): - f = f"regmap/adi_regmap_{lib}.txt" - if not os.path.isfile(f): - del rm[lib] - # Inconsistent naming convention, need to parse all in directory. - regmaps = [] - for (dirpath, dirnames, filenames) in os.walk("regmap"): - for file in filenames: - m = re.search("adi_regmap_(\w+)\.txt", file) - if not bool(m): - continue - - reg_name = m.group(1) - regmaps.extend(reg_name) - - ctime = os.path.getctime(f"regmap/{file}") - if reg_name in rm and rm[reg_name]['ctime'] < ctime: - for o in rm[reg_name]['owners']: - if o not in docnames: - docnames.append(o) - if reg_name in rm and rm[reg_name]['ctime'] >= ctime: - pass - else: - rm[reg_name] = parse_hdl_regmap(reg_name, ctime) - -def manage_hdl_artifacts(app, env, docnames): - libraries = [[k.replace('/index',''), k] for k in env.found_docs if k.find('library/') == 0] - - manage_hdl_components(env, docnames, libraries) - manage_hdl_regmaps(env, docnames) - -def setup(app): - app.add_directive('collapsible', directive_collapsible) - app.add_directive('hdl-parameters', directive_parameters) - app.add_directive('hdl-component-diagram', directive_component_diagram) - app.add_directive('hdl-interfaces', directive_interfaces) - app.add_directive('hdl-regmap', directive_regmap) - - for node in [node_div, node_input, node_label, node_icon]: - app.add_node(node, - html =(node.visit, node.depart), - latex=(node.visit, node.depart), - text =(node.visit, node.depart)) - - app.connect('env-before-read-docs', manage_hdl_artifacts) - - app.add_config_value('hide_collapsible_content', dft_hide_collapsible_content, 'env') - - return { - 'version': '0.1', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/docs/extensions/adi_hdl_render.py b/docs/extensions/adi_hdl_render.py deleted file mode 100644 index da6d7eeba..000000000 --- a/docs/extensions/adi_hdl_render.py +++ /dev/null @@ -1,230 +0,0 @@ -import os.path -from lxml import etree - -font_size = 16 -margin = 18 -line_length = 16 -text_vertical_margin = 8 -color_main = '#0067b9' -color_bg1 = '#c4e5ff' -color_bg2 = '#ebf6ff' -stroke_width = 3 - -class hdl_component(): - - @staticmethod - def get_name(lib_name): - return f"{lib_name.replace('/','-')}.svg" - - @staticmethod - def render(env, lib_name, item): - #ports, bus_interface - dest_dir = os.path.join(env.srcdir, '_build/managed') - dest_file = os.path.join(dest_dir, hdl_component.get_name(lib_name)) - - def make_style(parent): - style = etree.SubElement(parent, 'style').text = """ - a { - text-decoration: none; - } - """ - def make_gradient(parent): - defs = etree.SubElement(parent, 'defs') - gradient = etree.SubElement(defs, 'linearGradient', attrib={ - 'id':'ip_background', - 'x1':'0', 'x2':'1', 'y1':'0', 'y2':'1' - }) - etree.SubElement(gradient, 'stop', attrib={'offset':'0%', 'stop-color':color_bg1}) - etree.SubElement(gradient, 'stop', attrib={'offset':'100%', 'stop-color':color_bg2}) - - def symbol_bus(parent): - rect_height = font_size - one_fifth_x = line_length/5 - one_fifth_y = rect_height/5 - x = 0; y = 0 - pattern = [[0,1,0,1,0],[0,0,0,0,0],[0,1,0,1,0],[0,0,0,0,0],[0,1,0,1,0]] - etree.SubElement(parent, "rect", attrib={ - 'x':'0', 'y':'0', - 'width':str(line_length), 'height':str(font_size), - 'fill':color_bg1, - }) - for i in pattern: - for j in i: - if j: - etree.SubElement(parent, "rect", attrib={ - 'x':str(x), 'y':str(y), - 'width':str(one_fifth_x), 'height':str(one_fifth_y), - 'fill':color_main, - }) - x = x+one_fifth_x - x = 0 - y = y+one_fifth_y - def symbol_port(parent): - etree.SubElement(parent, "line", attrib={ - 'stroke':'black', - 'stroke-width':str(stroke_width), - 'x1':'0', 'y1':str(font_size/2), - 'x2':str(line_length), 'y2':str(font_size/2) - }) - - def create_text(items, side): - y_pos = margin*4 - if side == 'out': - text_anchor = 'end' - x_pos = margin*4 + aux_width - line_x1 = x_pos+(margin) - line_x2 = x_pos+(margin+line_length) - x_pos_group = x_pos+margin - scale_group = 'scale(1,1)' - else: - text_anchor = 'start' - x_pos = margin*3 - line_x1 = x_pos-(margin+line_length) - line_x2 = x_pos-(margin) - x_pos_group = x_pos-margin - scale_group = 'scale(-1,1)' - - for elem in items: - if elem[1] == 'bus': - link_anchor = f"#bus-interface-{elem[0]}" - else: - link_anchor = "#ports" - link = etree.SubElement(root, "a", attrib={ - 'href':link_anchor, - }) - etree.SubElement(link, "text", attrib={ - 'style':f"font: {font_size}px sans-serif", - 'text-anchor':text_anchor, - 'dominant-baseline':'middle', - 'x':str(x_pos), 'y':str(y_pos) - }).text = elem[0] - group = etree.SubElement(root, "g", attrib={ - 'transform':f"translate({x_pos_group},{y_pos-font_size/2}) {scale_group}"} - ) - if elem[1] == 'bus': - symbol_bus(group) - else: - symbol_port(group) - y_pos += font_size+text_vertical_margin - - ins = [] - outs = [] - for key in item['bus_interface']: - if item['bus_interface'][key]['role'] == 'master': - outs.append((key, 'bus')) - else: - ins.append((key, 'bus')) - for key in item['ports']: - if item['ports'][key]['direction'] == 'out': - outs.append((key, 'port')) - else: - ins.append((key, 'port')) - - max_len_in = 0 - for elem in ins: - max_len_in = len(elem[0]) if len(elem[0]) > max_len_in else max_len_in - - max_len_out = 0 - for elem in outs: - max_len_out = len(elem[0]) if len(elem[0]) > max_len_out else max_len_out - - aux_width = (max_len_in+max_len_out)*font_size*.6 - - num_outs = len(outs) - num_ins = len(ins) - max_num = max(num_outs, num_ins) - - root = etree.Element('svg', xmlns="http://www.w3.org/2000/svg") - - make_style(root) - make_gradient(root) - - ip_width = aux_width + margin*3 - ip_height = max_num*(font_size+text_vertical_margin) + margin*2 - etree.SubElement(root, "rect", attrib={ - 'x':str(margin*2), 'y':str(margin*2), - 'width':str(ip_width), 'height':str(ip_height), - 'rx':str(margin), - 'fill':'url(#ip_background)' - }) - - create_text(ins,'in') - create_text(outs,'out') - - viewbox_x = margin*7 + aux_width - viewbox_y = margin*7 + max_num*(font_size+text_vertical_margin) - root.set('viewBox', f"0 0 {viewbox_x} {viewbox_y}") - root.set('width', str(viewbox_x)) - root.set('height', str(viewbox_y)) - - etree.SubElement(root, "rect", attrib={ - 'x':str(margin*2), 'y':str(margin*2), - 'width':str(ip_width), 'height':str(ip_height), - 'rx':str(margin), - 'fill':'none', - 'stroke':color_main, - 'stroke-width':str(stroke_width) - }) - - ipname_y = viewbox_y-font_size-margin - ipname_x = viewbox_x/2 - etree.SubElement(root, "text", attrib={ - 'style':f"font: {font_size}px sans-serif", - 'fill': color_main, - 'text-anchor':'middle', - 'dominant-baseline':'middle', - 'x':str(ipname_x), 'y':str(ipname_y) - }).text = lib_name[lib_name.rfind('/')+1:] - - tree = etree.ElementTree(root) - - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) - tree.write(dest_file) - - @staticmethod - def render_placeholder(lib_name): - root = etree.Element('svg', xmlns="http://www.w3.org/2000/svg") - - def text_element(text, fs, x, y, font='sans-serif'): - etree.SubElement(root, "text", attrib={ - 'style':f"font: {font_size*fs}px {font}", - 'fill': '#666', - 'text-anchor':'middle', - 'dominant-baseline':'middle', - 'x':str(x), 'y':str(y) - }).text = text - - ip_width = font_size*30 - ip_height = font_size*15 - etree.SubElement(root, "rect", attrib={ - 'x':str(margin*2), 'y':str(margin*2), - 'width':str(ip_width), 'height':str(ip_height), - 'rx':str(margin), - 'stroke':'#666', - 'fill':'none', - 'stroke-width':str(stroke_width), - 'stroke-dasharray':'8 16', - 'stroke-linecap':'round' - }) - - viewbox_x = ip_width + 4*margin - viewbox_y = ip_height + 4*margin - root.set('viewBox', f"0 0 {viewbox_x} {viewbox_y}") - root.set('width', str(viewbox_x)) - root.set('height', str(viewbox_y)) - - text_y = viewbox_y/2.5 - text_x = viewbox_x/2 - text_element("🧐", 4, text_x, text_y-text_vertical_margin*2) - text_element(f"{lib_name[lib_name.rfind('/')+1:]} IP-XACT not found.", - 1, text_x, text_y+font_size*2+text_vertical_margin) - text_element("Generate it and the documentation:", - .75, text_x, text_y+font_size*3+text_vertical_margin*2.5) - text_element(f"(cd {lib_name}; make)", - .75, text_x, text_y+font_size*4+text_vertical_margin*3, 'monospace') - text_element("(cd docs; make html)", - .75, text_x, text_y+font_size*5+text_vertical_margin*3.5, 'monospace') - - tree = etree.ElementTree(root) - return etree.tostring(tree, encoding="utf-8", method="xml").decode("utf-8") diff --git a/docs/extensions/adi_hdl_static.py b/docs/extensions/adi_hdl_static.py deleted file mode 100644 index 8e80a45af..000000000 --- a/docs/extensions/adi_hdl_static.py +++ /dev/null @@ -1,40 +0,0 @@ -############################################################################### -## Copyright (C) 2022-2023 Analog Devices, Inc. All rights reserved. -### SPDX short identifier: ADIBSD -############################################################################### - -class hdl_strings (): - access_type = { - 'RO':{ - 'name': 'Read-only', - 'description': 'Reads will return the current register value. Writes have no effect.' - }, - 'WO':{ - 'name': 'Write-only', - 'description': 'Writes will change the current register value. Reads have no effect.' - }, - 'RW':{ - 'name': 'Read-write', - 'description': 'Reads will return the current register value. Writes will change the current register value.' - }, - 'RW1C':{ - 'name': 'Read,write-1-to-clear', - 'description': 'Reads will return the current register value. Writing the register will clear those bits of the register which were set to 1 in the value written. Bits are set by hardware.' - }, - 'RW1S':{ - 'name': 'Read,write-1-to-set', - 'description': 'Reads will return the current register value. Writing the register will set those bits of the register which were set to 1 in the value written. Bits are cleared by hardware.' - }, - 'W1S':{ - 'name': 'Write-1-to-set', - 'description': 'Writing the register will set those bits of the register which were set to 1 in the value written. Bits are cleared by hardware. Reads have no effect.' - }, - 'V':{ - 'name': 'Volatile', - 'description': 'The V suffix indicates that the register is volatile and its content might change without software interaction. The value of registers without the volatile designation will not change without an explicit write done by software.' - }, - 'NA':{ - 'name': 'No access', - 'description': 'Do not read or write the register.' - } - } diff --git a/docs/extensions/adi_links.py b/docs/extensions/adi_links.py deleted file mode 100644 index 8f4303607..000000000 --- a/docs/extensions/adi_links.py +++ /dev/null @@ -1,255 +0,0 @@ -############################################################################### -## Copyright (C) 2023 Analog Devices, Inc. All rights reserved. -### SPDX short identifier: ADIBSD -############################################################################### - -import subprocess -from docutils import nodes -from sphinx.util import logging - -logger = logging.getLogger(__name__) -validate_links_user_agent = 'Status resolver (Python/Sphinx)' - -# Default values -dft_url_datasheet = 'https://www.analog.com/media/en/technical-documentation/data-sheets/' -dft_url_dokuwiki = 'https://wiki.analog.com' -dft_url_ez = 'https://ez.analog.com' -dft_url_mw = 'https://www.mathworks.com' -dft_url_git = 'https://github.com/analogdevicesinc' -dft_url_adi = 'https://www.analog.com' -dft_url_xilinx = 'https://www.xilinx.com' -dft_url_intel = 'https://www.intel.com' - -dft_validate_links = False - -git_repos = [ - # url_path name - ['hdl', "HDL"], - ['testbenches', "Testbenches"], - ['linux', "Linux"], - ['no-os', "no-OS"], - ['libiio', "libiio"], - ['scopy', "Scopy"], - ['iio-oscilloscope', "IIO Oscilloscope"], - ['pyadi-iio', "PyADI-IIO"] -] -vendors = ['xilinx', 'intel', 'mw'] - -def get_url_config(name, inliner): - app = inliner.document.settings.env.app - return getattr(app.config, "url_"+name) - -def get_outer_inner(text): - """ - Extract 'outer ' fields. - """ - pos = text.find('<') - if pos != -1 and text[len(text)-1] == '>': - return (text[0:pos].strip(), text[pos+1:-1]) - else: - return (None, text) - -def datasheet(): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - if text.find(':') in [0, -1]: - url = get_url_config('datasheet', inliner) + '/' + part_id + '.pdf' - else: - anchor = text[text.find(':')+1:] - part_id = text[0:text.find(':')] - url = get_url_config('datasheet', inliner) + '/' + part_id + '.pdf#' + anchor - node = nodes.reference(rawtext, part_id + " datasheet", refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def dokuwiki(): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - text, path = get_outer_inner(text) - if text is None: - text = path[path.rfind('/')+1:] - url = get_url_config('dokuwiki', inliner) + '/' + path - node = nodes.reference(rawtext, text, refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def ez(): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - text, path = get_outer_inner(text) - if path == '/': - path = '' - url = get_url_config('ez', inliner) + '/' + path - if text is None: - text = "EngineerZone" - node = nodes.reference(rawtext, text, refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def get_active_branch_name(): - branch = subprocess.run(['git', 'branch', '--show-current'], capture_output=True) - return branch.stdout.decode('utf-8').replace('\n','') - -def git(repo, alt_name): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - url = get_url_config('git', inliner) + '/' + repo - if text == '/': - name = "ADI " + alt_name + " repository" - node = nodes.reference(rawtext, name, refuri=url, **options) - else: - text, path = get_outer_inner(text) - pos = path.find(':') - branch = get_active_branch_name() if pos in [0, -1] else path[0:pos] - path = path[pos+1:] - if text is None: - text = path - url = url + '/blob/' + branch + '/' + path - node = nodes.reference(rawtext, text, refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def adi(): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - name, adi_id = get_outer_inner(text) - if name is None: - name = adi_id - url = get_url_config('adi', inliner) + '/' + adi_id - node = nodes.reference(rawtext, name, refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def vendor(vendor_name): - def role(name, rawtext, text, lineno, inliner, options={}, content=[]): - text, path = get_outer_inner(text) - if text is None: - text = path[path.rfind('/')+1:] - url = get_url_config(vendor_name, inliner) + '/' + path - node = nodes.reference(rawtext, text, refuri=url, **options) - add_link(inliner, lineno, url) - return [node], [] - - return role - -def prepare_validade_links(app, env, docnames): - # Not managing links, so checking only changed files per build. - # A user can run a build with validate_links False, touch the - # desired files then run with validate_links True to check the links - # from only these files. - env.links = {} - -def validate_links(app, env): - if not env.config.validate_links: - logger.info(f"Skipping {len(env.links)} URLs checks-ups. Set validate_links to True to enable this.") - return - - global asyncio, aiohttp - import asyncio - import aiohttp - - asyncio.run( - async_validate_links(app, env) - ) - -async def validate_link(link, headers): - session_timeout = aiohttp.ClientTimeout(total=None, sock_connect=10, sock_read=10) - try: - async with aiohttp.ClientSession(timeout=session_timeout) as session: - async with session.get(link, headers=headers, timeout=10) as response: - return link, response.status - except aiohttp.ClientError as e: - return link, e - except asyncio.TimeoutError as e: - return link, e - -async def async_validate_links(app, env): - headers = {'User-Agent': validate_links_user_agent} - - fail_count = 0 - total = len(env.links) - completed = 0 - tasks = [] - results = [] - step = 25 - - links = list(env.links) - leng = int(total/step)+1 if total%step != 0 else int(total/step) - for i in range(0, leng): - cur = i*step - end = total if (i+1)*step > total else (i+1)*step - _links = links[cur:end] - for link in _links: - task = asyncio.create_task(validate_link(link, headers)) - tasks.append(task) - - for task in asyncio.as_completed(tasks): - results.append(await task) - completed += 1 - print(f'Validated URL {completed} out of {total}, bundle {i+1} of {leng}...', end='\r') - del tasks - tasks = [] - - for link, error in results: - if isinstance(error, asyncio.TimeoutError): - error = 'Timeout Error' - if error != 200: - fail_count += 1 - if len(env.links[link]) > 1: - extended_error = f"Resolved {len(env.links[link])} times, path shown is the first instance." - else: - extended_error = "" - logger.warning(f"URL {link} returned {error}! {extended_error}", location=env.links[link][0]) - - if fail_count: - logger.warning(f"{fail_count} out of {len(env.links)} URLs resolved with an error ({fail_count/(len(env.links))*100:0.2f}%)!") - else: - if total == 0: - extended_info = "\nAt every build, only the links at files that changed are checked, consider touching them to re-check." - else: - extended_info = "" - logger.info(f"All {total} URLs resolved successfully.{extended_info}") - - -def add_link(inliner, lineno, link): - links = inliner.document.settings.env.links - docname = (inliner.document.current_source[:-4],lineno) - if link not in links: - links[link] = [docname] - else: - links[link].append(docname) - -def setup(app): - app.add_role("datasheet", datasheet()) - app.add_role("dokuwiki", dokuwiki()) - app.add_role("ez", ez()) - app.add_role("adi", adi()) - for name in vendors: - app.add_role(name, vendor(name)) - for path, name in git_repos: - app.add_role("git-"+path, git(path, name)) - - app.connect('env-before-read-docs', prepare_validade_links) - app.connect('env-updated', validate_links) - - app.add_config_value('url_datasheet', dft_url_datasheet, 'env') - app.add_config_value('url_dokuwiki', dft_url_dokuwiki, 'env') - app.add_config_value('url_ez', dft_url_ez, 'env') - app.add_config_value('url_mw', dft_url_mw, 'env') - app.add_config_value('url_git', dft_url_git, 'env') - app.add_config_value('url_adi', dft_url_adi, 'env') - app.add_config_value('url_xilinx', dft_url_xilinx, 'env') - app.add_config_value('url_intel', dft_url_intel, 'env') - - app.add_config_value('validate_links',dft_validate_links,'env') - - return { - 'version': '0.1', - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/docs/index.rst b/docs/index.rst index ce3d7999d..6ee60698b 100755 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,50 +1,11 @@ -:hide-toc: - HDL Reference Designs =============================================================================== -.. toctree:: - :caption: User guide - :hidden: - - user_guide/index - -.. toctree:: - :caption: Libraries - :hidden: - - library/axi_dmac/index - library/spi_engine/index - -.. toctree:: - :caption: Projects - :hidden: - - AD4134-FMC - AD4630-FMC - AD469X-FMC - AD5766-SDZ - AD7134-FMC - AD719X-ASDZ - AD738X-FMC - AD7616-SDZ - AD9081-FMCA-EBZ/AD9082-FMCA-EBZ - AD9434-FMC - AD9783-EBZ - ADAQ7980-SDZ - CN0363 - CN0540 - CN0561 - PULSAR-ADC - -.. role:: red -.. role:: green - .. attention:: Work-in-progress, not all content available at the `wiki `_ - have been imported yet. + has been imported yet. .. image:: sources/HDL_logo.svg :align: center @@ -55,3 +16,15 @@ HDL libraries and projects for various reference design and prototyping systems. This repository contains HDL code (Verilog or VHDL) and the required Tcl scripts to create and build a specific FPGA example design using Xilinx and/or Intel tool chain. + +.. hdl-build-status:: + +Contents +=============================================================================== + +.. toctree:: + :maxdepth: 1 + + user_guide/index + library/index + projects/index diff --git a/docs/library/index.rst b/docs/library/index.rst new file mode 100644 index 000000000..f33b30d0a --- /dev/null +++ b/docs/library/index.rst @@ -0,0 +1,18 @@ +Libraries +=============================================================================== + +.. note:: + + This page lists only the IPs that have been ported to the new documentation + format. + + See :ref:`ip_cores` for a complete list of IP cores. + +Contents +------------------------------------------------------------------------------- + +.. toctree:: + :maxdepth: 1 + + axi_dmac/index + spi_engine/index diff --git a/docs/projects/ad469x_fmc/index.rst b/docs/projects/ad469x_fmc/index.rst index dc56141aa..c12a503e7 100755 --- a/docs/projects/ad469x_fmc/index.rst +++ b/docs/projects/ad469x_fmc/index.rst @@ -36,9 +36,10 @@ Block design ------------------------------------------------------------------------------- The reference design uses the standard :ref:`SPI Engine Framework ` -to interface the :adi:`AD4696` ADC in single SDO Mode. The :ref:`SPI Engine -Offload module `, which can be used to capture continuous -data stream at maximum data rate, is triggered by the BUSY signal of the device. +to interface the :adi:`AD4696` ADC in single SDO Mode. +The :ref:`SPI Engine Offload module `, which can be used to +capture continuous data stream at maximum data rate, is triggered by the BUSY +signal of the device. Block diagram ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/projects/index.rst b/docs/projects/index.rst new file mode 100644 index 000000000..49434dd44 --- /dev/null +++ b/docs/projects/index.rst @@ -0,0 +1,36 @@ +Projects +=============================================================================== + +.. note:: + + This page lists only reference designs that have been ported to the new + documentation format. + + See + :dokuwiki:`AMD Xilinx Reference Designs ` + and + :dokuwiki:`Intel Reference Designs ` + for a complete list of reference designs. + +Contents +------------------------------------------------------------------------------- + +.. toctree:: + :maxdepth: 1 + + AD4134-FMC + AD4630-FMC + AD469X-FMC + AD5766-SDZ + AD7134-FMC + AD719X-ASDZ + AD738X-FMC + AD7616-SDZ + AD9081-FMCA-EBZ/AD9082-FMCA-EBZ + AD9434-FMC + AD9783-EBZ + ADAQ7980-SDZ + CN0363 + CN0540 + CN0561 + PULSAR-ADC diff --git a/docs/requirements.txt b/docs/requirements.txt index 809a44fe8..25985289d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,9 +1,3 @@ sphinx -regex -lxml -furo -wavedrom -aiohttp -aiodns -sphinxcontrib-wavedrom -sphinxcontrib-svg2pdfconverter +https://github.com/analogdevicesinc/doctools/releases/download/latest/adi-doctools.tar.gz +sphinxcontrib.wavedrom diff --git a/docs/sources/custom.css b/docs/sources/custom.css deleted file mode 100755 index dd26bff01..000000000 --- a/docs/sources/custom.css +++ /dev/null @@ -1,155 +0,0 @@ -.sidebar-brand { - background-color: #f8f9fb; - border-radius: .25em; - margin: var(--sidebar-item-spacing-vertical) - calc(var(--sidebar-item-spacing-horizontal)/2) - 0 - calc(var(--sidebar-item-spacing-horizontal)/2); - padding: 0; - width: 11em; - margin-left: 2.25em; -} -.sidebar-brand::before { - content: ""; - display: block; - height: 4.5em; - background-size: auto 5.625em; - background-image: url(HDL_logo.svg); - background-position: -10em -.5em; - background-repeat: no-repeat; -} -.sidebar-brand-text { - display: none; -} -body[data-theme="dark"] .sidebar-brand { - color: #fff; -} -@media (min-width: 56em){ - .content { - width: 52em; - } -} -figure > object { - max-width: 100%; -} -img { - background: #fff; - border-radius: .25em; - padding: .5em 0; -} -iframe { - width: calc(50.625rem - 1rem); - height: calc(28.47rem - 0.5625rem); - margin-left: -1rem; -} -@media (max-width:50.625rem) { - iframe { - width: calc(100vw - 1rem); - height: calc(56.25vw - 1rem); - margin-left: -1rem; - } -} -.WaveDrom { - background: #fff; - border-radius: .25em; - padding: .5em 0; -} -table.regmap { - font-size: .9em; -} -table.regmap .caption-text{ - font-size: 1rem; -} -th, td.type { - white-space: nowrap; -} -td.description { - width: 45%; - font-size:.8em; -} -.red { - color: red; -} -.green { - color: green; -} -#signal-and-interface-pins h3 { - font-weight: normal; -} -.table-wrapper { - overflow: visible; -} -.collapsible { - border: 1px solid var(--color-table-border); - border-radius: .25em; - margin-bottom: -1px; -} -.collapsible_input { - display: none; -} -.collapsible_content { - border-top: 1px solid var(--color-table-border); - overflow: hidden; - height: 0; - padding: 0 .75em !important; - transition: ease opacity .25s; - opacity: 0; -} -.collapsible label { - width: 100%; - padding: 0.75em 1em 0.75em .75em; - user-select: none; - box-sizing: border-box; - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - font-weight: bold; - color: var(--color-toc-item-text); -} -.collapsible label .icon { - border: solid var(--color-toc-item-text); - border-width: 0 2px 2px 0; - display: block; - transition: transform ease .125s, margin-top ease .125s; - width: .6em; - height: .6em; - transform: rotate(-45deg); -} -.collapsible label p { - display: inline-block; - margin: 0; -} -.collapsible label, .collapsible div { - transition: box-shadow ease .25s; -} -.collapsible label:hover ~ div, .collapsible label:hover { - box-shadow: 0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1); -} -.collapsible_input:checked ~ label .icon { - transform: rotate(45deg); - margin-top: -.5em; -} -.collapsible_content { - overflow: auto; -} -.collapsible_input:checked ~ .collapsible_content { - height: 100%; - opacity: 1; -} -.default { - min-width: 5em; - max-width: 11em; - line-break: anywhere; -} -.default .pre { - white-space: pre; -} - -#hdl-component-diagram { - text-align: center; -} - -#hdl-component-diagram svg { - max-width: 100%; -} diff --git a/docs/user_guide/docs_guidelines.rst b/docs/user_guide/docs_guidelines.rst index cbe2334e3..99b93b018 100644 --- a/docs/user_guide/docs_guidelines.rst +++ b/docs/user_guide/docs_guidelines.rst @@ -3,12 +3,13 @@ Documentation guidelines ================================================================================ -A brief set-of-rules for the documentation. +This documentation is built with `Sphinx `_ and +all source code is available at the path :git-hdl:`docs`. -.. note:: - The old wiki uses `dokuwiki `_. When - importing text from there, consider the automated options that are provided - in this page to convert it to reST. +To contribute to it, open a pull request with the changes to +:git-hdl:`this repository `, just make sure to read the general +:ref:`doctools:docs_guidelines` first **and** the additional guidelines +below specific to the HDL repository. Templates -------------------------------------------------------------------------------- @@ -20,546 +21,17 @@ Templates are available: * :git-hdl:`docs/projects/template` (:ref:`rendered `). Remove the ``:orphan:`` in the first line, it is to hide the templates from the -`TOC tree `_. - -Indentation --------------------------------------------------------------------------------- - -Directives are indented with 3 space, which is Sphinx's default. -At code directives, the code keeps its original indentation (e.g. 2 spaces for -Verilog code), but is offset by 3 spaces at the beginning of every line, to -instruct Sphinx the beginning and end of the code directive. - -Table of contents --------------------------------------------------------------------------------- - -The relation between pages are created with the ``toctree`` directive, -which allows to generate the table of contents and navigation bars. -The ``toctree`` directive has the following format: - -.. code:: rst - - .. toctree:: - :caption: Caption - :hidden: - - Custom title - -For pages with shorter titles, such as libraries, the label is inherited from -the page itself, for example: - -.. code:: rst - - .. toctree:: - :caption: Libraries - :hidden: - - library/axi_dmac/index - library/spi_engine/index - -And for pages with long titles, such as projects, overwrite the full title with -a custom title, e.g: - -.. code:: rst - - .. toctree:: - :caption: Projects - :hidden: - - AD7616-SDZ - -This way, only "AD7616-SDZ" will be displayed in the page navigation bar instead -of "AD7616-SDZ HDL project". - -References --------------------------------------------------------------------------------- - -References have the format ``library/project context``, e.g. -:code:`:ref:\`spi_engine execution\`` renders as :ref:`spi_engine execution`. -Notice how neither *library* nor *project* are present in the label, since there is no -naming collision between libraries or projects (no project will ever be named -*axi_dmac*). - -Also, for project, libraries and IPs, the names should be exactly the -name of its folders, e.g. ``axi_pwm_gen`` and not ``axi-pwm-gen`` or ``AXI_PWM_GEN``, -this helps avoid broken references. - -For resources without a particular source code file/folder, prefer hyphen ``-`` -separation, for example, ``spi_engine control-interface`` instead of -``spi_engine control_interface``. - -Text width --------------------------------------------------------------------------------- - -Each line must be less than 80 columns wide. -You can use the :code:`fold` command to break the lines of the imported text -while respecting word-breaks: - -.. code:: bash - - cat imported.txt | fold -sw 80 > imported.rst - -Or use :code:`pandoc`: - -.. code:: bash - - pandoc imported.txt -f dokuwiki -t rst --columns=80 -s -o imported.rst - - -Tables --------------------------------------------------------------------------------- - -Prefer -`list-tables `_ -and imported -`csv-tables `_ -(using the file option), because they are faster to create, easier to maintain -and the 80 column-width rule can be respected with list-tables. - -You can use the following command: - -.. code:: bash - - pandoc imported.txt -f dokuwiki -t rst --columns=80 -s -o imported.rst --list-tables - -The :code:`list-tables` parameter requires *pandoc-types* >= 1.23, if it is not -an option, you shall remove it and export in the *grid* table format. - -Now you only have to adjust the widths and give the final touches, like using -the correct directives and roles. - -Lists --------------------------------------------------------------------------------- - -Unordered lists use ``*`` or ``-`` and ordered lists ``#.``. - -Child items must be aligned with the first letter of the parent item, that means, -2 spaces for unordered list and 3 spaces for ordered lists, for example: - -.. code-block:: rst - - #. Parent ordered item. - - * Child unordeded item. - - #. Child ordered item. - #. Child ordered item. - -Renders as: - -#. Parent numbered item. - - * Child unordered item. - - #. Child ordered item. - #. Child ordered item. - -Code --------------------------------------------------------------------------------- - -Prefer -`code-blocks `_ -to -`code `_ -directives, because code-blocks have more options, such as showing line numbers -and emphasizing lines. - -For example, - -.. code:: rst - - .. code-block:: python - :linenos: - :emphasize-lines: 2 - - def hello_world(): - string = "Hello world" - print(string) - -Renders as - -.. code-block:: python - :linenos: - :emphasize-lines: 2 - - def hello_world(): - string = "Hello world" - print(string) - -Images --------------------------------------------------------------------------------- - -Prefer the SVG format for images, and save it as *Optimized SVG* in -`inkscape `_ to use less space. - -Store them in a hierarchically, do not use ``images`` subdirectories. -The idea is to have simpler relative paths, for example, e.g.: - -.. code:: rst - - .. image: ad2234_sdz_schematic.svg - - -Instead of overly complicated paths like: - -.. code:: rst - - .. image: ../../project/images/ad2234_sdz/ad2234_sdz_schematic.svg - -In general, this avoids dangling artifacts and keeps the documentation simple. - -Vivado block-diagrams -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Vivado block-diagrams can be exported as PDF and then converted to SVG with -Inkscape. See :ref:`spi_engine tutorial` for a "final result" example. - -Vivado waveform data -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There is no way to export Vivado waveform data as vectors. -Therefore, the recommended method is to take a PNG screenshot and use -`GIMP `_ to export as **8bpc RGB** with all metadata options -disabled. +`TOC tree `_, +and make sure to remove any placeholder text and instructive comment. .. note:: - Always use the *Export As..* ``Ctrl+Shift+E`` option. - -To reduce even further the size, you can use *Color > Dither..* to reduce the -number of colors in the PNG. -Saving as greyscale also reduces the PNG size, but might reduce readability and -it is not recommended. - -Third-party directives and roles --------------------------------------------------------------------------------- - -Third-party tools are used to expand Sphinx functionality, if you haven't already, -do: - -.. code:: bash - - pip install -r requirements.txt - -Custom directives and roles --------------------------------------------------------------------------------- - -To expand Sphinx functionality beyond existing tools, custom directives and roles -have been written, which are located in the *docs/extensions* folder. -Extensions are straight forward to create, if some functionality is missing, -consider requesting or creating one. - -.. note:: - - Link-like roles use the :code:`:role:\`text \`` synthax, like external - links, but without the undescore in the end. - -Color role -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To print text in red or green, use :code:`:red:\`text\`` and :code:`:green:\`text\``. - -Link roles -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The link roles are a group of roles defined by ``adi_links.py``. - -The ``validate_links`` global option is used to validate each link during build. -These links are not managed, that means, only links from changed files are checked. -You can run a build with it set to False, then touch the desired files to check -the links of only these files. - -Git role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The Git role allows to create links to the Git repository with a shorter syntax. -The role syntax is :code:`:git-repo:\`text \``, for example: - -* :code:`:git-hdl:\`main:docs/user_guide/docs_guidelines.rst\`` - renders as :git-hdl:`main:docs/user_guide/docs_guidelines.rst`. -* :code:`:git-hdl:\`Guidelines \`` - renders as :git-hdl:`Guidelines `. - -When the branch field is not present, it will be filled with the current branch. -It is recommended to not provide this field when it is a link to its own repository, -because it is useful to auto-fill it for documentation releases -(e.g. ``hdl_2023_r2``). -A scenario where it is recommended to provide the branch is when linking others -repositories. - -The text field is optional and will be filled with the full path. - -Finally, you can do :code:`:git-repo:\`/\`` for a link to the root of the -repository with pretty naming, for example, :code:`:git-hdl:\`/\`` is rendered -as :git-hdl:`/`. - -ADI role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The adi role creates links for a webpage to the Analog Devices Inc. website. - -The role syntax is :code:`:adi:\`text \``, for example, -:code:`:adi:\`AD7175-2 \``. -Since links are case insensitive, you can also reduce it to -:code:`:adi:\`AD7175-2\``, when *webpage* is the same as *text* and will render -as :adi:`AD7175-2`. - -Datasheet role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The datasheet role creates links for a datasheet in the Analog Devices Inc. website. - -The role syntax is :code:`:datasheet:\`part_id:anchor\``, for example, -:code:`:datasheet:\`AD7984:[{"num"%3A51%2C"gen"%3A0}%2C{"name"%3A"XYZ"}%2C52%2C713%2C0]\`` -is rendered as -:datasheet:`AD7984:[{"num"%3A51%2C"gen"%3A0}%2C{"name"%3A"XYZ"}%2C52%2C713%2C0]`. -The anchor is optional and is a link to a section of the PDF, and can be obtained -by just copying the link in the table of contents. - -.. caution:: - - Since not all PDF readers support anchors, always provide the page and/or - figure number! - -Dokuwiki role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The dokuwiki role creates links to the Analog Devices Inc. wiki website. -The role syntax is :code:`:dokuwiki:\`text \``, for example, -:code:`:dokuwiki:\`pulsar-adc-pmods \`` -gets rendered as -:dokuwiki:`pulsar-adc-pmods `. - -EngineerZone role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The ez role creates links to the Analog Devices Inc. EngineerZone support website. -The role syntax is :code:`:ez:\`community\``, for example, :code:`:ez:\`fpga\`` -gets rendered as :ez:`fpga`. - -For Linux Software Drivers, it is :code:`:ez:\`linux-software-drivers\``. - -For Microcontroller no-OS Drivers it is :code:`:ez:\`microcontroller-no-os-drivers\``. - -Vendor role -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The vendor role creates links to the vendor's website. -The role syntax is :code:`:vendor:\`text \``, for example, -:code:`:xilinx:\`Zynq-7000 SoC Overview \`` -gets rendered -:xilinx:`Zynq-7000 SoC Overview `. - -The text parameter is optional, if absent, the file name will be used as the text, -for example, -:code:`:intel:\`content/www/us/en/docs/programmable/683780/22-4/general-purpose-i-o-overview.html\`` -gets rendered -:intel:`content/www/us/en/docs/programmable/683780/22-4/general-purpose-i-o-overview.html` -(not very readable). - -Supported vendors are: ``xilinx`` (AMD Xilinx), ``intel`` (Intel Altera) and -``mw`` (MathWorks). - -HDL parameters directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The HDL parameters directive gets information parsed from IP-XACT (*component.xml*) -library and generates a table with the IP parameters. - -.. note:: - - The IP-XACT files are generated by Vivado during the library build and not by - the documentation tooling. - -The directive syntax is: - -.. code:: rst - - .. hdl-parameters:: - :path: - - * - - - - -For example: - -.. code:: rst - - .. hdl-parameters:: - :path: library/spi_engine/spi_engine_interconnect - - * - DATA_WIDTH - - Data width of the parallel SDI/SDO data interfaces. - * - NUM_OF_SDI - - Number of SDI lines on the physical SPI interface. - -Renders as: - -.. hdl-parameters:: - :path: library/spi_engine/spi_engine_interconnect - - * - DATA_WIDTH - - Data width of the parallel SDI/SDO data interfaces. - * - NUM_OF_SDI - - Number of SDI lines on the physical SPI interface. - -Descriptions in the directive have higher precedence than in the *component.xml* -file. - -The ``:path:`` option is optional, and should **not** be included if the -documentation file path matches the *component.xml* hierarchically. - -HDL interface directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The HDL interfaces directive gets information parsed from *component.xml* library -and generates tables with the IP interfaces, both buses and ports. - -.. note:: - - The *component.xml* files are generated by Vivado during the library build - and not by the documentation tooling. - -The directive syntax is: - -.. code:: rst - - .. hdl-interfaces:: - :path: - - * - - - - -For example: - -.. code:: rst - - .. hdl-interfaces:: - :path: library/spi_engine/spi_engine_interconnect - -Descriptions in the directive have higher precedence than in the *component.xml* -file. -You can provide description to a port or a bus, but not for a bus port. - -The ``:path:`` option is optional, and should **not** be included if the -documentation file path matches the *component.xml* hierarchically. - -HDL component diagram directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The HDL component diagram directive gets information parsed from *component.xml* -library and generates a component diagram for the IP with buses and ports -information. - -.. note:: - - The *component.xml* files are generated by Vivado during the library build - and not by the documentation tooling. - -The directive syntax is: - -.. code:: rst - - .. hdl-component-diagram:: - :path: - -For example: - -.. code:: rst - - .. hdl-component-diagram:: - :path: library/spi_engine/spi_engine_interconnect - -The ``:path:`` option is optional, and should **not** be included if the -documentation file path matches the *component.xml* hierarchically. - -.. note:: - - This directive replaces the deprecated ``symbolator`` directive. - -HDL regmap directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The HDL regmap directive gets information from *docs/regmap/adi_regmap_\*.txt* files -and generates tables with the register maps. - -The directive syntax is: - -.. code:: rst - - .. hdl-regmap:: - :name: - :no-type-info: - -For example: - -.. code:: rst - - .. hdl-regmap:: - :name: DMAC - -.. note:: - - The register map name is the title-tool, the value above ``ENDTITLE`` in the - source file. - -This directive does not support content for descriptions, since the source file -already have proper descriptions. - -The ``:name:`` option is **required**, because the title tool does not match -the IP name and one single *docs/regmap/adi_regmap_\*.txt* file can have more than -one register map. -The ``:no-type-info:`` option is optional, and should **not** be included if it is -in the main IP documentation page. It appends an auxiliary table explaining the -register access types. - -Collapsible directive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The collapsible directive creates a collapsible/dropdown/"HTML details". - -The directive syntax is: - -.. code:: rst - - .. collapsible::