Doxygen is the industry standard in SW development for auto-generating documentation from code. It works pretty well for VHDL, I’ll cover the basics, share some tips and tricks and provide a few warnings in this post.



Table of Contents



Basics

Generate a base Doxyfile with doxygen -g Doxyfile

Set OPTIMIZE_OUTPUT_VHDL to YES in the Doxyfile.

Documenting for Doxygen

Use --! comments to include your documentation in the docs generated by Doxygen. Beware that any regular -- comment within a block of Doxygen comments will split the blocks in two.

--! \file testbench.vhdl
-- <--- oops, missing exclamation mark
--! \author Oh no separate block!

An index of Doxygen’s \commands (which can also be @commands) can be found here.

Here is an example from https://github.com/Sturla22/pltbutils/tree/doxygen:

doxygen-example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--! \overload
--!
--! \param num               Test number. Optional, default is to increment
--!                          the current test number.
--! \param name              Test name.
--!
--! \param pltbv, pltbs      PlTbUtils' status- and control variable and
--!                          -signal.
--!
--! If the test number is omitted, a new test number is automatically
--! computed by incrementing the current test number.
--! Manually setting the test number may make it easier to find the test code
--! in the testbench code, though.
--!
--! **Examples**
--! \`\`\`vhdl
--! starttest(1, "Reset test", pltbv, pltbs);
--! \`\`\`
procedure starttest(
  constant num                : in    integer := -1;
  constant name               : in    string;
  variable pltbv              : inout pltbv_t;
  signal   pltbs              : out   pltbs_t
);
Listing 1: Doxygen Example

The result is this representation of the procedure starttest:

starttest result

Configuring Doxygen

If you want to hit the ground running you can set EXTRACT_ALL to YES and Doxygen will generate documentation based on code and not only on the code that has been documented with --! comments. Another setting to ensure you get as much documentation as fast as possible is RECURSIVE which you also want to set to YES so that Doxygen will go through all subdirectories to find your code.

For graphical representations of the code’s hierarchy I set UML_LOOK to YES and I found COLLABORATION_GRAPH to be too noisy for VHDL, so I set that to NO. DOT_IMAGE_FORMAT = svg is handy when you have large hierarchies since you can zoom and pan around, setting INTERACTIVE_SVG to YES also increases the usefulness of the graphics since it enables you to traverse the hierarchy by clicking around in the graphics. If Doxygen does not generate graphics, check the HAVE_DOT setting and it’s documentation.

Click on ‘Expand Doxyfile diff’ below for an example of the changes I made to the default generated Doxyfile when preparing to generate https://sturla22.github.io/pltbutils

Expand Doxyfile diff

diff --git a/Doxyfile b/Doxyfile
index 082cccc..301fcb5 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -32,33 +32,33 @@ DOXYFILE_ENCODING      = UTF-8
 # title of most generated pages and in a few other places.
 # The default value is: My Project.
 
-PROJECT_NAME           = "My Project"
+PROJECT_NAME           = "PlTbUtils"
 
 # The PROJECT_NUMBER tag can be used to enter a project or revision number. This
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         =
+PROJECT_NUMBER         = 1.3
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
 # quick idea about the purpose of the project. Keep the description short.
 
-PROJECT_BRIEF          =
+PROJECT_BRIEF          = "PlTbUtils makes it easy to create automatic, self-checking simulation testbenches, and to locate bugs during a simulation. It is a collection of functions, procedures and testbench components that simplifies creation of stimuli and checking results of a device under test."
 
 # With the PROJECT_LOGO tag one can specify a logo or an icon that is included
 # in the documentation. The maximum height of the logo should not exceed 55
 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
 # the logo to the output directory.
 
-PROJECT_LOGO           =
+PROJECT_LOGO           = doc/src/media/image1.jpeg
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
 # into which the generated documentation will be written. If a relative path is
 # entered, it will be relative to the location where doxygen was started. If
 # left blank the current directory will be used.
 
-OUTPUT_DIRECTORY       =
+OUTPUT_DIRECTORY       = .
 
 # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
 # directories (in 2 levels) under the output directory of each output format and
@@ -231,7 +231,7 @@ MULTILINE_CPP_IS_BRIEF = NO
 # documentation from any documented member that it re-implements.
 # The default value is: YES.
 
-INHERIT_DOCS           = YES
+INHERIT_DOCS           = NO
 
 # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
 # page for each member. If set to NO, the documentation of a member will be part
@@ -261,7 +261,9 @@ TAB_SIZE               = 4
 # commands \{ and \} for these it is advised to use the version @{ and @} or use
 # a double escape (\\{ and \\})
 
-ALIASES                =
+ALIASES  =
+ALIASES += licenseblock="\par License^^\parblock"
+ALIASES += endlicenseblock="\endparblock^^"
 
 # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
 # only. Doxygen will then generate output that is more tailored for C. For
@@ -289,7 +291,7 @@ OPTIMIZE_FOR_FORTRAN   = NO
 # sources. Doxygen will then generate output that is tailored for VHDL.
 # The default value is: NO.
 
-OPTIMIZE_OUTPUT_VHDL   = NO
+OPTIMIZE_OUTPUT_VHDL   = YES
 
 # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
 # sources only. Doxygen will then generate output that is more tailored for that
@@ -317,7 +319,7 @@ OPTIMIZE_OUTPUT_SLICE  = NO
 # Note that for custom extensions you also need to set FILE_PATTERNS otherwise
 # the files are not read by doxygen.
 
-EXTENSION_MAPPING      =
+EXTENSION_MAPPING      = ucf=vhdl
 
 # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
 # according to the Markdown format, which allows for more readable
@@ -461,13 +463,13 @@ LOOKUP_CACHE_SIZE      = 0
 # normally produced when WARNINGS is set to YES.
 # The default value is: NO.
 
-EXTRACT_ALL            = NO
+EXTRACT_ALL            = YES
 
 # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
 # be included in the documentation.
 # The default value is: NO.
 
-EXTRACT_PRIVATE        = NO
+EXTRACT_PRIVATE        = YES
 
 # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
 # methods of a class will be included in the documentation.
@@ -479,13 +481,13 @@ EXTRACT_PRIV_VIRTUAL   = NO
 # scope will be included in the documentation.
 # The default value is: NO.
 
-EXTRACT_PACKAGE        = NO
+EXTRACT_PACKAGE        = YES
 
 # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
 # included in the documentation.
 # The default value is: NO.
 
-EXTRACT_STATIC         = NO
+EXTRACT_STATIC         = YES
 
 # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
 # locally in source files will be included in the documentation. If set to NO,
@@ -501,7 +503,7 @@ EXTRACT_LOCAL_CLASSES  = YES
 # included.
 # The default value is: NO.
 
-EXTRACT_LOCAL_METHODS  = NO
+EXTRACT_LOCAL_METHODS  = YES
 
 # If this flag is set to YES, the members of anonymous namespaces will be
 # extracted and appear in the documentation as a namespace called
@@ -563,7 +565,7 @@ CASE_SENSE_NAMES       = YES
 # scope will be hidden.
 # The default value is: NO.
 
-HIDE_SCOPE_NAMES       = NO
+HIDE_SCOPE_NAMES       = YES
 
 # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
 # append additional text to a page's title, such as Class Reference. If set to
@@ -574,9 +576,10 @@ HIDE_COMPOUND_REFERENCE= NO
 
 # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
 # the files that are included by a file in the documentation of that file.
+# Note(sl): Setting this to no ignores libraries in vhdl.
 # The default value is: YES.
 
-SHOW_INCLUDE_FILES     = YES
+SHOW_INCLUDE_FILES     = NO
 
 # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
 # grouped member an include statement to the documentation, telling the reader
@@ -779,8 +782,10 @@ WARN_IF_UNDOCUMENTED   = YES
 # in a documented function, or documenting parameters that don't exist or using
 # markup commands wrongly.
 # The default value is: YES.
+# TODO(sl): Enable again when https://github.com/doxygen/doxygen/issues/8450
+# is fixed.
 
-WARN_IF_DOC_ERROR      = YES
+WARN_IF_DOC_ERROR      = NO
 
 # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
 # are documented, but have no documentation for their parameters or return
@@ -850,59 +855,20 @@ INPUT_ENCODING         = UTF-8
 # C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
 # *.vhdl, *.ucf, *.qsf and *.ice.
 
-FILE_PATTERNS          = *.c \
-                         *.cc \
-                         *.cxx \
-                         *.cpp \
-                         *.c++ \
-                         *.java \
-                         *.ii \
-                         *.ixx \
-                         *.ipp \
-                         *.i++ \
-                         *.inl \
-                         *.idl \
-                         *.ddl \
-                         *.odl \
-                         *.h \
-                         *.hh \
-                         *.hxx \
-                         *.hpp \
-                         *.h++ \
-                         *.cs \
-                         *.d \
-                         *.php \
-                         *.php4 \
-                         *.php5 \
-                         *.phtml \
-                         *.inc \
-                         *.m \
-                         *.markdown \
-                         *.md \
-                         *.mm \
-                         *.dox \
-                         *.doc \
-                         *.txt \
-                         *.py \
-                         *.pyw \
-                         *.f90 \
-                         *.f95 \
-                         *.f03 \
-                         *.f08 \
-                         *.f18 \
-                         *.f \
-                         *.for \
+FILE_PATTERNS          = *.md \
                          *.vhd \
                          *.vhdl \
                          *.ucf \
                          *.qsf \
-                         *.ice
+                         *.ice \
+                         *.lst \
+                         *.do
 
 # The RECURSIVE tag can be used to specify whether or not subdirectories should
 # be searched for input files as well.
 # The default value is: NO.
 
-RECURSIVE              = NO
+RECURSIVE              = YES
 
 # The EXCLUDE tag can be used to specify files and/or directories that should be
 # excluded from the INPUT source files. This way you can easily exclude a
@@ -944,7 +910,7 @@ EXCLUDE_SYMBOLS        =
 # that contain example code fragments that are included (see the \include
 # command).
 
-EXAMPLE_PATH           =
+EXAMPLE_PATH           = examples/vhdl
 
 # If the value of the EXAMPLE_PATH tag contains directories, you can use the
 # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
@@ -958,13 +924,13 @@ EXAMPLE_PATTERNS       = *
 # irrespective of the value of the RECURSIVE tag.
 # The default value is: NO.
 
-EXAMPLE_RECURSIVE      = NO
+EXAMPLE_RECURSIVE      = YES
 
 # The IMAGE_PATH tag can be used to specify one or more files or directories
 # that contain images that are to be included in the documentation (see the
 # \image command).
 
-IMAGE_PATH             =
+IMAGE_PATH             = .
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program
@@ -1020,7 +986,7 @@ FILTER_SOURCE_PATTERNS =
 # (index.html). This can be useful if you have a project on for instance GitHub
 # and want to reuse the introduction page also for the doxygen output.
 
-USE_MDFILE_AS_MAINPAGE =
+USE_MDFILE_AS_MAINPAGE = README.md
 
 #---------------------------------------------------------------------------
 # Configuration options related to source browsing
@@ -1033,7 +999,7 @@ USE_MDFILE_AS_MAINPAGE =
 # also VERBATIM_HEADERS is set to NO.
 # The default value is: NO.
 
-SOURCE_BROWSER         = NO
+SOURCE_BROWSER         = YES
 
 # Setting the INLINE_SOURCES tag to YES will include the body of functions,
 # classes and enums directly into the documentation.
@@ -1537,7 +1503,7 @@ DISABLE_INDEX          = NO
 # The default value is: NO.
 # This tag requires that the tag GENERATE_HTML is set to YES.
 
-GENERATE_TREEVIEW      = NO
+GENERATE_TREEVIEW      = YES
 
 # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
 # doxygen will group on one line in the generated HTML documentation.
@@ -1742,7 +1708,7 @@ EXTRA_SEARCH_MAPPINGS  =
 # If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
 # The default value is: YES.
 
-GENERATE_LATEX         = YES
+GENERATE_LATEX         = NO
 
 # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
 # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
@@ -2155,7 +2121,7 @@ PERLMOD_MAKEVAR_PREFIX =
 # C-preprocessor directives found in the sources and include files.
 # The default value is: YES.
 
-ENABLE_PREPROCESSING   = YES
+ENABLE_PREPROCESSING   = NO
 
 # If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
 # in the source code. If set to NO, only conditional compilation will be
@@ -2355,7 +2321,7 @@ CLASS_GRAPH            = YES
 # The default value is: YES.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-COLLABORATION_GRAPH    = YES
+COLLABORATION_GRAPH    = NO
 
 # If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
 # groups, showing the direct groups dependencies.
@@ -2370,7 +2336,7 @@ GROUP_GRAPHS           = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-UML_LOOK               = NO
+UML_LOOK               = YES
 
 # If the UML_LOOK tag is enabled, the fields and methods are shown inside the
 # class node. If there are many fields or methods and many nodes the graph may
@@ -2391,7 +2357,7 @@ UML_LIMIT_NUM_FIELDS   = 10
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-TEMPLATE_RELATIONS     = NO
+TEMPLATE_RELATIONS     = YES
 
 # If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
 # YES then doxygen will generate a graph for each documented file showing the
@@ -2421,7 +2387,7 @@ INCLUDED_BY_GRAPH      = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALL_GRAPH             = NO
+CALL_GRAPH             = YES
 
 # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
 # dependency graph for every global function or class method.
@@ -2433,7 +2399,7 @@ CALL_GRAPH             = NO
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALLER_GRAPH           = NO
+CALLER_GRAPH           = YES
 
 # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
 # hierarchy of all classes instead of a textual one.
@@ -2466,7 +2432,7 @@ DIRECTORY_GRAPH        = YES
 # The default value is: png.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-DOT_IMAGE_FORMAT       = png
+DOT_IMAGE_FORMAT       = svg
 
 # If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
 # enable generation of interactive SVG images that allow zooming and panning.
@@ -2478,7 +2444,7 @@ DOT_IMAGE_FORMAT       = png
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-INTERACTIVE_SVG        = NO
+INTERACTIVE_SVG        = YES
 
 # The DOT_PATH tag can be used to specify the path where the dot tool can be
 # found. If left blank, it is assumed the dot tool can be found in the path.

  • Since before VHDL-2008 there are no multiline comments available one needs to be careful about including a ! after every -- in the documentation blocks that Doxygen is supposed to parse, otherwise the documentation blocks will be split and the results will not match your intention.
  • Problem with procedures that have file interface parameters: https://github.com/doxygen/doxygen/issues/8450
  • The \code and \endcode seem to cause the entire documentation block to disappear: https://github.com/doxygen/doxygen/issues/8231

    In my experience using the following snippet works well as a replacement.

    --! ```vhdl
    --! insert code here...
    --! ```
    

    You can replace vhdl in that snippet with another language like python or tcl.

  • Types declared in a procedure preceding another procedure will be assumed to be the return type of the second procedure: https://github.com/doxygen/doxygen/issues/8517

If you run into other issues, take a look at Doxygen’s issue tracker in case there are known workarounds.

Continuous Integration

You can get GitLab to build your documentation (pdf or html) with its CI features:

doxygen:
  image: ubuntu:latest
  stage: deploy
  before_script:
    - export DEBIAN_FRONTEND=noninteractive TZ=Europe/Stockholm
    - apt update && apt install -y graphviz build-essential texlive-latex-base texlive-fonts-recommended texlive-fonts-extra texlive-latex-extra texlive-font-utils doxygen
  script:
    - doxygen Doxyfile && cd doc/latex && make
  artifacts:
    paths:
      - doc/latex/refman.pdf
      - doc/html
    expire_in: 1 week

This will give you access to the pdf and html representations of your documentation through the job artifact browser in GitLab. If you are only interested in the html output then you can remove most of the packages installed in the before_script, only doxygen is needed, that way your jobs will be much faster.

Conclusion

Doxygen is a useful tool in the VHDL toolbox, even though there are some issues at the moment, but that is the beauty of open source software: we can influence the development of these applications and in case we don’t get the response we hoped for we can take matters into our own hands!

Changelog

2021-04-24

  • Add information about CI.
  • Correct wrong statement about no multiline comments in VHDL, they were added in VHDL-2008.
  • Add a new Doxygen for VHDL issue.


Comments? You are welcome to start a discussion on Github.