sphinx-svdomain is a Sphinx extension that adds a SystemVerilog domain for
documenting hardware APIs.
It supports hand-written domain directives and an experimental source-driven autodoc flow.
Status: alpha. The manual domain is intended to be usable for the supported object types below. Autodoc is useful, but still limited to the extracted declaration forms listed in this README.
sphinx-svdomain requires Python 3.12 or newer and Sphinx 9.1.x.
Install the current alpha into the Python environment used by Sphinx after the release has been published to PyPI. Either select the exact prerelease or allow prereleases:
pip install sphinx-svdomain==0.1.0-alpha.2
pip install --pre sphinx-svdomainTo install an unpublished local checkout instead:
pip install .Source-driven autodoc uses pyslang>=11 as its default backend. It is installed
automatically as a normal package dependency.
When developing this repository, use uv:
uv sync --group test --group docsEnable the extension in conf.py:
extensions = ["sphinx_svdomain"]No extra configuration is required for the default manual and source-driven documentation flows.
The default autodoc backend is pyslang; most projects do not need to set
sv_autodoc_backend.
Manual directives are the stable core of the extension. They register
SystemVerilog objects with Sphinx so they appear in generated HTML, search
results, indexes, and objects.inv.
.. sv:module:: uart_rx
UART receiver.
.. sv:parameter:: DATA_WIDTH
:type: int
:default: 8
Width of the received data.
.. sv:localparam:: COUNTER_WIDTH
:type: int
:default: 4
Width of the internal counter.
.. sv:typedef:: state_t
:type: logic [1:0]
Receiver state encoding.
.. sv:port:: clk
:direction: input
:type: logic
.. sv:port:: rx
:direction: input
:type: logic
.. sv:port:: data
:direction: output
:type: logic [:sv:param:`DATA_WIDTH` - 1:0]
.. sv:function:: parity
:lifetime: automatic
:type: logic
:arguments: input logic [DATA_WIDTH-1:0] value
Calculate parity for one data word.
The :sv:mod:`uart_rx` module samples :sv:port:`uart_rx.rx`.
The data width is configured by :sv:param:`uart_rx.DATA_WIDTH`.
Parity is calculated by :sv:func:`uart_rx.parity`.Function content can use normal RST fields such as :param value: and
:returns:. Autodoc emits those fields from @param and @return tags.
Nested declarations inherit the surrounding scope. In the example above,
DATA_WIDTH is registered as uart_rx.DATA_WIDTH, and data is registered as
uart_rx.data.
.. sv:package:: uart_cfg
Shared UART configuration declarations.
.. sv:parameter:: DEFAULT_DATA_WIDTH
:type: int
:default: 8
.. sv:typedef:: byte_t
:type: logic [7:0]
.. sv:enum:: parity_t
:type: logic [1:0]
.. sv:enumerator:: NONE
No parity bit.
.. sv:enumerator:: EVEN
Even parity.
.. sv:struct:: config_t
:type: packed
.. sv:field:: data_width
:type: int unsigned
.. sv:field:: parity
:type: :sv:enum:`parity_t`
The byte type is :sv:type:`uart_cfg::byte_t`.
The parity field is :sv:field:`uart_cfg::config_t.parity`.Package-scoped names use ::, so byte_t inside uart_cfg is registered as
uart_cfg::byte_t. Members of enum, struct, and union types use ., so the
field above is registered as uart_cfg::config_t.parity.
.. sv:interface:: uart_line_if
UART serial line interface.
.. sv:parameter:: DATA_WIDTH
:type: int
:default: 8
.. sv:typedef:: payload_t
:type: logic [:sv:param:`DATA_WIDTH` - 1:0]
.. sv:port:: rx_line
:direction: input
:type: logic
.. sv:port:: tx_line
:direction: output
:type: logicSupported manual directives:
| Object | Directive | Reference role |
|---|---|---|
| Interface | .. sv:interface:: name |
:sv:intf: |
| Module | .. sv:module:: name |
:sv:mod: |
| Package | .. sv:package:: name |
:sv:pkg: |
| Parameter | .. sv:parameter:: name |
:sv:param: |
| Localparam | .. sv:localparam:: name |
:sv:lparam: |
| Port | .. sv:port:: name |
:sv:port: |
| Typedef | .. sv:typedef:: name |
:sv:type: |
| Enum | .. sv:enum:: name |
:sv:enum: and :sv:type: |
| Enumerator | .. sv:enumerator:: name |
:sv:enumval: |
| Struct | .. sv:struct:: name |
:sv:struct: and :sv:type: |
| Union | .. sv:union:: name |
:sv:union: and :sv:type: |
| Field | .. sv:field:: name |
:sv:field: |
| Function | .. sv:function:: name |
:sv:func: |
The generic Sphinx :any: role also resolves SystemVerilog objects using the
same lookup rules.
Supported directive options:
sv:parameterandsv:localparam::type:,:unpacked-dimensions:,:default:sv:port::direction:,:type:,:unpacked-dimensions:sv:typedef,sv:enum,sv:struct,sv:union::type:,:unpacked-dimensions:sv:enumerator::value:sv:field::type:,:unpacked-dimensions:sv:function::lifetime:,:type:,:arguments:
Manual object directives also inherit Sphinx's standard object options,
including :no-index:, :no-index-entry:, :no-contents-entry:, and
:no-typesetting:.
Expression-bearing options can contain inline RST roles when an expression
needs manual links. This applies to :type:, :unpacked-dimensions:,
:default:, :value:, and :arguments: where those options are supported:
.. sv:port:: data
:direction: output
:type: logic [:sv:param:`DATA_WIDTH` - 1:0]
.. sv:field:: payload
:type: :sv:type:`uart_cfg::byte_t`The :direction: and :lifetime: options are rendered as plain text.
Canonical names use :: for package-style namespaces and . for members:
uart_cfg::DEFAULT_DATA_WIDTHuart_cfg::byte_tuart_cfg::parity_t.NONEuart_cfg::config_t.parityuart_line_if.rx_lineuart_rx.DATA_WIDTHuart_rx.datauart_rx.parity
Inside module, interface, package, enum, struct, and union content, references can use scope-relative names:
.. sv:module:: uart_rx
.. sv:parameter:: DATA_WIDTH
:type: int
:sv:param:`DATA_WIDTH` controls :sv:port:`data`.Outside a scope, use the fully qualified name unless the short name is unique:
The receiver output is :sv:port:`uart_rx.data`.
The shared byte type is :sv:type:`uart_cfg::byte_t`.If an unqualified reference is ambiguous, Sphinx emits a warning and leaves the reference unresolved.
HTML anchor IDs are generated by Sphinx from the canonical object name. The same
canonical names are exported in objects.inv for intersphinx.
Autodoc reads SystemVerilog source and renders the extracted declarations through the same domain directives used for manual documentation.
Document every supported top-level declaration in one file:
.. sv:autofile:: rtl/uart_rx.svDocument one top-level declaration from a source file:
.. sv:autopackage:: uart_cfg
:source: rtl/uart_cfg.sv
.. sv:autointerface:: uart_line_if
:source: rtl/uart_line_if.sv
.. sv:automodule:: uart_rx
:source: rtl/uart_rx.svSource paths are resolved by Sphinx relative to the document containing the autodoc directive.
Autodoc does not require the design to elaborate. Unresolved imports or package references can remain as source text in generated signatures. Syntax problems are reported as Sphinx warnings, and the affected file is skipped. Non-error parser diagnostics, such as style warnings, do not prevent declaration extraction.
Autodoc-generated :type: text links known parameters, localparams, and types
automatically. For example, logic [DATA_WIDTH-1:0] links DATA_WIDTH to the
generated parameter object when that parameter is known.
Autodoc extracts Doxygen-style comments into declaration descriptions.
Leading block comments attach to the next supported declaration:
/**
* UART receiver.
*
* Receives serial data and emits parallel words.
*/
module uart_rx;
endmoduleLeading line comments work too:
/// UART receiver.
///
/// Receives serial data and emits parallel words.
module uart_rx;
endmoduleTrailing member comments attach to declarations on the same line:
typedef struct packed {
logic [7:0] payload; ///< Payload byte.
} packet_t;Supported markers are /**, /*!, ///, and //!. Trailing member comments
use ///< or //!<. Decorative comments beginning with four or more slashes
are treated as ordinary comments rather than documentation.
Supported tags:
@brief TEXT: short description for the current declaration@details TEXT: additional description for the current declaration@param NAME TEXT: description for a parameter or localparam, or for a function argument when attached to a function@port NAME TEXT: description for a port@field NAME TEXT: description for a struct or union field@enumval NAME TEXT: description for an enum value@return TEXT: description of a function's return value
Example:
/**
* UART receiver.
*
* @param DATA_WIDTH Width of the received data.
* @port clk Clock input.
* @port rx Serial input.
*/
module uart_rx #(
parameter int DATA_WIDTH = 8
) (
input logic clk,
input logic rx
);
endmoduleComment text is interpreted as reStructuredText after extraction, so explicit SV roles can be used in comments:
/// Receives data from :sv:port:`uart_rx.rx`.
module uart_rx;
endmoduleSource comments become normal Sphinx object descriptions.
For example, this source comment:
/**
* UART block documented from SystemVerilog comments.
*
* @param DATA_WIDTH Width of the received data.
* @port clk Clock input.
* @port rx Serial receive input.
*/
module autodoc_uart #(
parameter int DATA_WIDTH = autodoc_pkg::AUTO_DATA_WIDTH
) (
input logic clk,
input logic rx,
output logic [DATA_WIDTH-1:0] data ///< Parallel receive data.
);
endmoduleis rendered as module, parameter, and port documentation:
Trailing field comments are rendered on nested field objects:
typedef union packed {
logic [DATA_WIDTH-1:0] raw; ///< Raw sampled payload.
autodoc_pkg::auto_byte_t byte_view; ///< Byte-sized view.
} sample_u;Manual documentation supports the object types listed in Directives And Roles.
Autodoc currently extracts:
- modules, interfaces, and packages
- ANSI module/interface parameters and ports
- non-ANSI body port declarations
- parameter and localparam declarations
- typedefs
- typedef enum, struct, and union declarations
- enum values and struct/union fields
- module-, interface-, and package-scoped functions with ANSI arguments
- unpacked declarator dimensions and explicit enum values
- supported documentation comments and member tags
Known limitations:
- tasks and non-ANSI function argument declarations are not implemented yet
- classes, programs, modports, clocking blocks, covergroups, assertions, properties, sequences, constraints, macros, UDPs, and instances are not documented as first-class objects yet
- autodoc does not elaborate designs or resolve imported symbols
- autodoc does not recursively discover a directory; list source files with
sv:autofileor generate those directives in the consuming project - unsupported source declarations are ignored by the current extractor
- source-location diagnostics do not always point to the exact unsupported declaration
- there is no custom SystemVerilog index page yet; objects still appear in Sphinx's general index, search, and inventory
sphinx_svdomain/: extension implementationsphinx_svdomain/backends/: source parser integrationtest/: pytest suitetest/doc_gen/: example Sphinx project used as an integration test
Install all development dependencies:
uv sync --group test --group docsRun the test suite:
uv run --group test pytestBuild the example documentation with warnings treated as errors:
uv run --group docs --group test sphinx-build -E -W --keep-going test/doc_gen test/doc_gen/_build/htmlBuild the package:
uv buildMost users do not need to configure the autodoc parser. The default is selected automatically. For extension development, the internal JSON autodoc backend is used for deterministic frontend tests and can be selected explicitly:
sv_autodoc_backend = "json"Recommended release checks:
uv run --group test pytest
uv run --group docs --group test sphinx-build -E -W --keep-going test/doc_gen test/doc_gen/_build/html
uv buildThe project uses Semantic Versioning 2.0.0 for release tags and release names:
v0.1.0-alpha.1
v0.1.0-beta.1
v0.1.0-rc.1
v0.1.0
Python package tooling uses PEP 440 and normalizes SemVer prereleases in built
artifacts. For example, the SemVer release 0.1.0-alpha.1 is accepted by the
source metadata and is normalized by Python packaging tools as 0.1.0a1 in
wheel filenames and package metadata.
sphinx-svdomain is distributed under the GNU LGPL version 2.1 or later. See
LICENSE.

