diff --git a/docs/conf.py b/docs/conf.py
index c08410e5d..6a9cb98eb 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -20,7 +20,6 @@ extensions = [
"sphinx.ext.todo",
"sphinx.ext.viewcode",
"sphinxcontrib.wavedrom",
- "symbolator_sphinx",
"adi_links",
"adi_hdl_parser"
]
diff --git a/docs/extensions/adi_hdl_parser.py b/docs/extensions/adi_hdl_parser.py
index f5df14794..f97f29a6c 100644
--- a/docs/extensions/adi_hdl_parser.py
+++ b/docs/extensions/adi_hdl_parser.py
@@ -13,6 +13,7 @@ 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
@@ -343,7 +344,7 @@ class directive_interfaces(directive_base):
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
+ return subnode
def run(self):
env = self.state.document.settings.env
@@ -414,7 +415,7 @@ class directive_regmap(directive_base):
subnode += section
if 'no-type-info' in self.options:
- return subnode
+ return subnode
tgroup = nodes.tgroup(cols=3)
for _ in range(3):
@@ -438,7 +439,7 @@ class directive_regmap(directive_base):
tgroup += tbody
section += table
- return subnode
+ return subnode
def run(self):
env = self.state.document.settings.env
@@ -534,10 +535,9 @@ class directive_parameters(directive_base):
node = node_div()
- if 'path' in self.options:
- lib_name = self.options['path']
- else:
- lib_name = env.docname.replace('/index', '')
+ 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:
@@ -549,6 +549,47 @@ class directive_parameters(directive_base):
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':{},
@@ -651,8 +692,16 @@ def parse_hdl_component(path, ctime):
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': {}
}
@@ -747,6 +796,7 @@ def manage_hdl_components(env, docnames, libraries):
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
@@ -923,6 +973,7 @@ def manage_hdl_artifacts(app, 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)
diff --git a/docs/extensions/adi_hdl_render.py b/docs/extensions/adi_hdl_render.py
new file mode 100644
index 000000000..da6d7eeba
--- /dev/null
+++ b/docs/extensions/adi_hdl_render.py
@@ -0,0 +1,230 @@
+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/library/axi_dmac/index.rst b/docs/library/axi_dmac/index.rst
index d28dc5bae..071980dc5 100644
--- a/docs/library/axi_dmac/index.rst
+++ b/docs/library/axi_dmac/index.rst
@@ -3,8 +3,7 @@
High-Speed DMA Controller
================================================================================
-.. symbolator:: ../../../library/axi_dmac/axi_dmac.v
- :caption: axi_dmac
+.. hdl-component-diagram::
The AXI DMAC is a high-speed, high-throughput, general purpose DMA controller
intended to be used to transfer data between system memory and other peripherals
@@ -71,7 +70,6 @@ Configuration Parameters
--------------------------------------------------------------------------------
.. hdl-parameters::
- :path: library/axi_dmac
* - ID
- Instance identification number.
diff --git a/docs/library/spi_engine/axi_spi_engine.rst b/docs/library/spi_engine/axi_spi_engine.rst
index 527c670bd..67a5456f8 100644
--- a/docs/library/spi_engine/axi_spi_engine.rst
+++ b/docs/library/spi_engine/axi_spi_engine.rst
@@ -3,8 +3,7 @@
AXI SPI Engine Module
================================================================================
-.. symbolator:: ../../../library/spi_engine/axi_spi_engine/axi_spi_engine.v
- :caption: axi_spi_engine
+.. hdl-component-diagram::
The AXI SPI Engine peripheral allows asynchronous interrupt-driven memory-mapped
access to a SPI Engine Control Interface.
diff --git a/docs/library/spi_engine/spi_engine_execution.rst b/docs/library/spi_engine/spi_engine_execution.rst
index 6ef0fe884..cfc40d6af 100644
--- a/docs/library/spi_engine/spi_engine_execution.rst
+++ b/docs/library/spi_engine/spi_engine_execution.rst
@@ -3,8 +3,7 @@
SPI Engine Execution Module
================================================================================
-.. symbolator:: ../../../library/spi_engine/spi_engine_execution/spi_engine_execution.v
- :caption: spi_engine_execution
+.. hdl-component-diagram::
The SPI Engine Execution peripheral forms the heart of the SPI Engine framework.
It is responsible for handling a SPI Engine control stream and translates it
diff --git a/docs/library/spi_engine/spi_engine_interconnect.rst b/docs/library/spi_engine/spi_engine_interconnect.rst
index bd31706ad..ff6d52f89 100644
--- a/docs/library/spi_engine/spi_engine_interconnect.rst
+++ b/docs/library/spi_engine/spi_engine_interconnect.rst
@@ -3,8 +3,7 @@
SPI Engine Interconnect Module
================================================================================
-.. symbolator:: ../../../library/spi_engine/spi_engine_interconnect/spi_engine_interconnect.v
- :caption: axi_spi_engine
+.. hdl-component-diagram::
The SPI Engine Interconnect module allows connecting multiple
:ref:`spi_engine control-interface` masters to a single
@@ -38,7 +37,6 @@ Configuration Parameters
--------------------------------------------------------------------------------
.. hdl-parameters::
- :path: library/spi_engine/spi_engine_interconnect
* - DATA_WIDTH
- Data width of the parallel SDI/SDO data interfaces.
diff --git a/docs/library/spi_engine/spi_engine_offload.rst b/docs/library/spi_engine/spi_engine_offload.rst
index 966fcea35..a5772e89f 100644
--- a/docs/library/spi_engine/spi_engine_offload.rst
+++ b/docs/library/spi_engine/spi_engine_offload.rst
@@ -3,7 +3,7 @@
SPI Engine Offload Module
================================================================================
-.. symbolator:: ../../../library/spi_engine/spi_engine_offload/spi_engine_offload.v
+.. hdl-component-diagram::
The SPI Engine Offload peripheral allows to store a SPI Engine command and SDO
data stream in a RAM or ROM module. The command stream is executed when the
diff --git a/docs/library/template_framework/template_module.rst b/docs/library/template_framework/template_module.rst
index 56d5da509..aa38e0e1f 100644
--- a/docs/library/template_framework/template_module.rst
+++ b/docs/library/template_framework/template_module.rst
@@ -3,8 +3,8 @@
Template Module
================================================================================
-.. symbolator:: ../../../library/spi_engine/spi_engine_execution/spi_engine_execution.v
- :caption: spi_engine_execution
+.. hdl-component-diagram::
+ :path: library/spi_engine/spi_engine_execution
The {module name} is responsible for {brief description}.
diff --git a/docs/library/template_ip/index.rst b/docs/library/template_ip/index.rst
index 95fb6ca4e..e3d30c9c8 100644
--- a/docs/library/template_ip/index.rst
+++ b/docs/library/template_ip/index.rst
@@ -5,8 +5,8 @@
IP Template
================================================================================
-.. symbolator:: ../../../library/spi_engine/spi_engine_execution/spi_engine_execution.v
- :caption: spi_engine_execution
+.. hdl-component-diagram::
+ :path: library/spi_engine/spi_engine_execution
Features
--------------------------------------------------------------------------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 1e2a4a33a..809a44fe8 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -7,5 +7,3 @@ aiohttp
aiodns
sphinxcontrib-wavedrom
sphinxcontrib-svg2pdfconverter
-https://github.com/hdl/pyhdlparser/tarball/master
-https://github.com/hdl/symbolator/tarball/master
diff --git a/docs/sources/custom.css b/docs/sources/custom.css
index f127e42b4..dd26bff01 100755
--- a/docs/sources/custom.css
+++ b/docs/sources/custom.css
@@ -145,3 +145,11 @@ td.description {
.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 55d10b276..cbe2334e3 100644
--- a/docs/user_guide/docs_guidelines.rst
+++ b/docs/user_guide/docs_guidelines.rst
@@ -241,12 +241,6 @@ do:
pip install -r requirements.txt
-Symbolator directive
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-`Symbolator `_ is a tool to generate
-component diagrams.
-
Custom directives and roles
--------------------------------------------------------------------------------
@@ -450,6 +444,39 @@ 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~