soc: doc: use sphinx toctree as it was intended

The sphinx toctree was behaving oddly, and so previously we were
ignoring it completely.  This patch causes it to be used correctly,
which removes the need for double-including various sections.

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2020-02-04 20:34:10 +08:00
parent 7c3bc0b09f
commit 73ed7e564c
1 changed files with 102 additions and 61 deletions

View File

@ -10,7 +10,7 @@ from .csr import DocumentedCSRRegion
from .module import gather_submodules, ModuleNotDocumented, DocumentedModule, DocumentedInterrupts from .module import gather_submodules, ModuleNotDocumented, DocumentedModule, DocumentedInterrupts
from .rst import reflow from .rst import reflow
sphinx_configuration = """ default_sphinx_configuration = """
project = '{}' project = '{}'
copyright = '{}, {}' copyright = '{}, {}'
author = '{}' author = '{}'
@ -26,6 +26,7 @@ html_theme = 'alabaster'
html_static_path = ['_static'] html_static_path = ['_static']
""" """
def sub_csr_bit_range(busword, csr, offset): def sub_csr_bit_range(busword, csr, offset):
nwords = (csr.size + busword - 1)//busword nwords = (csr.size + busword - 1)//busword
i = nwords - offset - 1 i = nwords - offset - 1
@ -34,13 +35,17 @@ def sub_csr_bit_range(busword, csr, offset):
origin = i*busword origin = i*busword
return (origin, nbits, name) return (origin, nbits, name)
def print_svd_register(csr, csr_address, description, length, svd): def print_svd_register(csr, csr_address, description, length, svd):
print(' <register>', file=svd) print(' <register>', file=svd)
print(' <name>{}</name>'.format(csr.short_numbered_name), file=svd) print(' <name>{}</name>'.format(csr.short_numbered_name), file=svd)
if description is not None: if description is not None:
print(' <description><![CDATA[{}]]></description>'.format(description), file=svd) print(
print(' <addressOffset>0x{:04x}</addressOffset>'.format(csr_address), file=svd) ' <description><![CDATA[{}]]></description>'.format(description), file=svd)
print(' <resetValue>0x{:02x}</resetValue>'.format(csr.reset_value), file=svd) print(
' <addressOffset>0x{:04x}</addressOffset>'.format(csr_address), file=svd)
print(
' <resetValue>0x{:02x}</resetValue>'.format(csr.reset_value), file=svd)
print(' <size>{}</size>'.format(length), file=svd) print(' <size>{}</size>'.format(length), file=svd)
print(' <access>{}</access>'.format(csr.access), file=svd) print(' <access>{}</access>'.format(csr.access), file=svd)
csr_address = csr_address + 4 csr_address = csr_address + 4
@ -48,11 +53,16 @@ def print_svd_register(csr, csr_address, description, length, svd):
if hasattr(csr, "fields") and len(csr.fields) > 0: if hasattr(csr, "fields") and len(csr.fields) > 0:
for field in csr.fields: for field in csr.fields:
print(' <field>', file=svd) print(' <field>', file=svd)
print(' <name>{}</name>'.format(field.name), file=svd) print(
print(' <msb>{}</msb>'.format(field.offset + field.size - 1), file=svd) ' <name>{}</name>'.format(field.name), file=svd)
print(' <bitRange>[{}:{}]</bitRange>'.format(field.offset + field.size - 1, field.offset), file=svd) print(' <msb>{}</msb>'.format(field.offset +
print(' <lsb>{}</lsb>'.format(field.offset), file=svd) field.size - 1), file=svd)
print(' <description><![CDATA[{}]]></description>'.format(reflow(field.description)), file=svd) print(' <bitRange>[{}:{}]</bitRange>'.format(
field.offset + field.size - 1, field.offset), file=svd)
print(
' <lsb>{}</lsb>'.format(field.offset), file=svd)
print(' <description><![CDATA[{}]]></description>'.format(
reflow(field.description)), file=svd)
print(' </field>', file=svd) print(' </field>', file=svd)
else: else:
field_size = csr.size field_size = csr.size
@ -67,12 +77,14 @@ def print_svd_register(csr, csr_address, description, length, svd):
print(' <field>', file=svd) print(' <field>', file=svd)
print(' <name>{}</name>'.format(field_name), file=svd) print(' <name>{}</name>'.format(field_name), file=svd)
print(' <msb>{}</msb>'.format(field_size - 1), file=svd) print(' <msb>{}</msb>'.format(field_size - 1), file=svd)
print(' <bitRange>[{}:{}]</bitRange>'.format(field_size - 1, 0), file=svd) print(
' <bitRange>[{}:{}]</bitRange>'.format(field_size - 1, 0), file=svd)
print(' <lsb>{}</lsb>'.format(0), file=svd) print(' <lsb>{}</lsb>'.format(0), file=svd)
print(' </field>', file=svd) print(' </field>', file=svd)
print(' </fields>', file=svd) print(' </fields>', file=svd)
print(' </register>', file=svd) print(' </register>', file=svd)
def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, description=None): def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, description=None):
interrupts = {} interrupts = {}
for csr, irq in sorted(soc.soc_interrupt_map.items()): for csr, irq in sorted(soc.soc_interrupt_map.items()):
@ -85,9 +97,11 @@ def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, desc
raw_regions = soc.get_csr_regions() raw_regions = soc.get_csr_regions()
else: else:
for region_name, region in soc.csr_regions.items(): for region_name, region in soc.csr_regions.items():
raw_regions.append((region_name, region.origin, region.busword, region.obj)) raw_regions.append((region_name, region.origin,
region.busword, region.obj))
for csr_region in raw_regions: for csr_region in raw_regions:
documented_regions.append(DocumentedCSRRegion(csr_region, csr_data_width=soc.csr_data_width)) documented_regions.append(DocumentedCSRRegion(
csr_region, csr_data_width=soc.csr_data_width))
if filename is None: if filename is None:
filename = name + ".svd" filename = name + ".svd"
@ -98,7 +112,8 @@ def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, desc
print(' <vendor>{}</vendor>'.format(vendor), file=svd) print(' <vendor>{}</vendor>'.format(vendor), file=svd)
print(' <name>{}</name>'.format(name.upper()), file=svd) print(' <name>{}</name>'.format(name.upper()), file=svd)
if description is not None: if description is not None:
print(' <description><![CDATA[{}]]></description>'.format(reflow(description)), file=svd) print(
' <description><![CDATA[{}]]></description>'.format(reflow(description)), file=svd)
print('', file=svd) print('', file=svd)
print(' <addressUnitBits>8</addressUnitBits>', file=svd) print(' <addressUnitBits>8</addressUnitBits>', file=svd)
print(' <width>32</width>', file=svd) print(' <width>32</width>', file=svd)
@ -113,10 +128,13 @@ def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, desc
csr_address = 0 csr_address = 0
print(' <peripheral>', file=svd) print(' <peripheral>', file=svd)
print(' <name>{}</name>'.format(region.name.upper()), file=svd) print(' <name>{}</name>'.format(region.name.upper()), file=svd)
print(' <baseAddress>0x{:08X}</baseAddress>'.format(region.origin), file=svd) print(
print(' <groupName>{}</groupName>'.format(region.name.upper()), file=svd) ' <baseAddress>0x{:08X}</baseAddress>'.format(region.origin), file=svd)
print(
' <groupName>{}</groupName>'.format(region.name.upper()), file=svd)
if len(region.sections) > 0: if len(region.sections) > 0:
print(' <description><![CDATA[{}]]></description>'.format(reflow(region.sections[0].body())), file=svd) print(' <description><![CDATA[{}]]></description>'.format(
reflow(region.sections[0].body())), file=svd)
print(' <registers>', file=svd) print(' <registers>', file=svd)
for csr in region.csrs: for csr in region.csrs:
description = None description = None
@ -125,42 +143,53 @@ def generate_svd(soc, buildpath, vendor="litex", name="soc", filename=None, desc
if isinstance(csr, _CompoundCSR) and len(csr.simple_csrs) > 1: if isinstance(csr, _CompoundCSR) and len(csr.simple_csrs) > 1:
is_first = True is_first = True
for i in range(len(csr.simple_csrs)): for i in range(len(csr.simple_csrs)):
(start, length, name) = sub_csr_bit_range(region.busword, csr, i) (start, length, name) = sub_csr_bit_range(
sub_name = csr.name.upper() + "_" + name region.busword, csr, i)
if length > 0: if length > 0:
bits_str = "Bits {}-{} of `{}`.".format(start, start+length, csr.name) bits_str = "Bits {}-{} of `{}`.".format(
start, start+length, csr.name)
else: else:
bits_str = "Bit {} of `{}`.".format(start, csr.name) bits_str = "Bit {} of `{}`.".format(
start, csr.name)
if is_first: if is_first:
if description is not None: if description is not None:
print_svd_register(csr.simple_csrs[i], csr_address, bits_str + " " + description, length, svd) print_svd_register(
csr.simple_csrs[i], csr_address, bits_str + " " + description, length, svd)
else: else:
print_svd_register(csr.simple_csrs[i], csr_address, bits_str, length, svd) print_svd_register(
csr.simple_csrs[i], csr_address, bits_str, length, svd)
is_first = False is_first = False
else: else:
print_svd_register(csr.simple_csrs[i], csr_address, bits_str, length, svd) print_svd_register(
csr.simple_csrs[i], csr_address, bits_str, length, svd)
csr_address = csr_address + 4 csr_address = csr_address + 4
else: else:
length = ((csr.size + region.busword - 1)//region.busword) * region.busword length = ((csr.size + region.busword - 1) //
print_svd_register(csr, csr_address, description, length, svd) region.busword) * region.busword
print_svd_register(
csr, csr_address, description, length, svd)
csr_address = csr_address + 4 csr_address = csr_address + 4
print(' </registers>', file=svd) print(' </registers>', file=svd)
print(' <addressBlock>', file=svd) print(' <addressBlock>', file=svd)
print(' <offset>0</offset>', file=svd) print(' <offset>0</offset>', file=svd)
print(' <size>0x{:x}</size>'.format(csr_address), file=svd) print(
' <size>0x{:x}</size>'.format(csr_address), file=svd)
print(' <usage>registers</usage>', file=svd) print(' <usage>registers</usage>', file=svd)
print(' </addressBlock>', file=svd) print(' </addressBlock>', file=svd)
if region.name in interrupts: if region.name in interrupts:
print(' <interrupt>', file=svd) print(' <interrupt>', file=svd)
print(' <name>{}</name>'.format(region.name), file=svd) print(' <name>{}</name>'.format(region.name), file=svd)
print(' <value>{}</value>'.format(interrupts[region.name]), file=svd) print(
' <value>{}</value>'.format(interrupts[region.name]), file=svd)
print(' </interrupt>', file=svd) print(' </interrupt>', file=svd)
print(' </peripheral>', file=svd) print(' </peripheral>', file=svd)
print(' </peripherals>', file=svd) print(' </peripherals>', file=svd)
print('</device>', file=svd) print('</device>', file=svd)
def generate_docs(soc, base_dir, project_name="LiteX SoC Project", def generate_docs(soc, base_dir, project_name="LiteX SoC Project",
author="Anonymous", sphinx_extensions=[], quiet=False, note_pulses=False): author="Anonymous", sphinx_extensions=[], quiet=False, note_pulses=False,
from_scratch=True):
"""Possible extra extensions: """Possible extra extensions:
[ [
'm2r', 'm2r',
@ -177,13 +206,17 @@ def generate_docs(soc, base_dir, project_name="LiteX SoC Project",
# Ensure the output directory exists # Ensure the output directory exists
pathlib.Path(base_dir + "/_static").mkdir(parents=True, exist_ok=True) pathlib.Path(base_dir + "/_static").mkdir(parents=True, exist_ok=True)
# Create various Sphinx plumbing # Create the sphinx configuration file if the user has requested,
with open(base_dir + "conf.py", "w", encoding="utf-8") as conf: # or if it doesn't exist already.
year = datetime.datetime.now().year if from_scratch or not os.path.isfile(base_dir + "conf.py"):
sphinx_ext_str = "" with open(base_dir + "conf.py", "w", encoding="utf-8") as conf:
for ext in sphinx_extensions: year = datetime.datetime.now().year
sphinx_ext_str += "\n \"{}\",".format(ext) sphinx_ext_str = ""
print(sphinx_configuration.format(project_name, year, author, author, sphinx_ext_str), file=conf) for ext in sphinx_extensions:
sphinx_ext_str += "\n \"{}\",".format(ext)
print(default_sphinx_configuration.format(project_name, year,
author, author, sphinx_ext_str), file=conf)
if not quiet: if not quiet:
print("Generate the documentation by running `sphinx-build -M html {} {}_build`".format(base_dir, base_dir)) print("Generate the documentation by running `sphinx-build -M html {} {}_build`".format(base_dir, base_dir))
@ -200,13 +233,16 @@ def generate_docs(soc, base_dir, project_name="LiteX SoC Project",
documented_regions = [] documented_regions = []
seen_modules = set() seen_modules = set()
regions = [] regions = []
# Previously, litex contained a function to gather csr regions. # Previously, litex contained a function to gather csr regions.
if hasattr(soc, "get_csr_regions"): if hasattr(soc, "get_csr_regions"):
regions = soc.get_csr_regions() regions = soc.get_csr_regions()
else: else:
# Now we just access the regions directly. # Now we just access the regions directly.
for region_name, region in soc.csr_regions.items(): for region_name, region in soc.csr_regions.items():
regions.append((region_name, region.origin, region.busword, region.obj)) regions.append((region_name, region.origin,
region.busword, region.obj))
for csr_region in regions: for csr_region in regions:
module = None module = None
if hasattr(soc, csr_region[0]): if hasattr(soc, csr_region[0]):
@ -214,9 +250,11 @@ def generate_docs(soc, base_dir, project_name="LiteX SoC Project",
seen_modules.add(module) seen_modules.add(module)
submodules = gather_submodules(module) submodules = gather_submodules(module)
documented_region = DocumentedCSRRegion(csr_region, module, submodules, csr_data_width=soc.csr_data_width) documented_region = DocumentedCSRRegion(
csr_region, module, submodules, csr_data_width=soc.csr_data_width)
if documented_region.name in interrupts: if documented_region.name in interrupts:
documented_region.document_interrupt(soc, submodules, interrupts[documented_region.name]) documented_region.document_interrupt(
soc, submodules, interrupts[documented_region.name])
documented_regions.append(documented_region) documented_regions.append(documented_region)
# Document any modules that are not CSRs. # Document any modules that are not CSRs.
@ -231,38 +269,41 @@ def generate_docs(soc, base_dir, project_name="LiteX SoC Project",
except ModuleNotDocumented: except ModuleNotDocumented:
pass pass
with open(base_dir + "index.rst", "w", encoding="utf-8") as index: # Create index.rst containing links to all of the generated files.
print(""" # If the user has set `from_scratch=False`, then skip this step.
if from_scratch or not os.path.isfile(base_dir + "index.rst"):
with open(base_dir + "index.rst", "w", encoding="utf-8") as index:
print("""
Documentation for {} Documentation for {}
{} {}
.. toctree::
:hidden:
""".format(project_name, "="*len("Documentation for " + project_name)), file=index) """.format(project_name, "="*len("Documentation for " + project_name)), file=index)
for module in additional_modules:
print(" {}".format(module.name), file=index)
for region in documented_regions:
print(" {}".format(region.name), file=index)
if len(additional_modules) > 0: if len(additional_modules) > 0:
print(""" print("""
Modules Modules
======= -------
""", file=index)
for module in additional_modules:
print("* :doc:`{} <{}>`".format(module.name.upper(), module.name), file=index)
if len(documented_regions) > 0: .. toctree::
print(""" :maxdepth: 1
""", file=index)
for module in additional_modules:
print(" {}".format(module.name), file=index)
if len(documented_regions) > 0:
print("""
Register Groups Register Groups
=============== ---------------
""", file=index)
for region in documented_regions:
print("* :doc:`{} <{}>`".format(region.name.upper(), region.name), file=index)
print(""" .. toctree::
:maxdepth: 1
""", file=index)
for region in documented_regions:
print(" {}".format(region.name), file=index)
print("""
Indices and tables Indices and tables
================== ------------------
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
@ -279,10 +320,10 @@ Indices and tables
with open(base_dir + region.name + ".rst", "w", encoding="utf-8") as outfile: with open(base_dir + region.name + ".rst", "w", encoding="utf-8") as outfile:
region.print_region(outfile, base_dir, note_pulses) region.print_region(outfile, base_dir, note_pulses)
# Copy over wavedrom javascript and configuration files
with open(os.path.dirname(__file__) + "/static/WaveDrom.js", "r") as wd_in: with open(os.path.dirname(__file__) + "/static/WaveDrom.js", "r") as wd_in:
with open(base_dir + "/_static/WaveDrom.js", "w") as wd_out: with open(base_dir + "/_static/WaveDrom.js", "w") as wd_out:
wd_out.write(wd_in.read()) wd_out.write(wd_in.read())
with open(os.path.dirname(__file__) + "/static/default.js", "r") as wd_in: with open(os.path.dirname(__file__) + "/static/default.js", "r") as wd_in:
with open(base_dir + "/_static/default.js", "w") as wd_out: with open(base_dir + "/_static/default.js", "w") as wd_out:
wd_out.write(wd_in.read()) wd_out.write(wd_in.read())