[doc] Completely replace docgen with hugo

This change completely replaces docgen and replaces or removes
docgen-specific markdown in documentation.  It also does the following:

  * Updates all local links to use hugo relative references so that a
    broken link is a broken build.
  * Uses upstream wavedrom, which breaks at least one page that depends
    on local modifications.
  * Renames most hw/ip/**/ip_name.doc and dv_plan documents for a more
    aesthetic document tree layout.
  * Moves some doc/ pages into their own page bundle.
  * Updates util/build_docs.py to pre-generate registers, hwcfg, and
    dashboard fragments and invoke hugo.
diff --git a/README.md b/README.md
index aae0cde..ac919ca 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,12 @@
 ## Documentation
 
 The project contains comprehensive documentation of all IPs and tools. You can
-either access it [online](https://bubble.servers.lowrisc.org/) or build it
+either access it [online](https://docs.opentitan.org/) or build it
 locally by following the steps below.
 
-1. Ensure that you have the required Python modules installed (to be executed
+1. Download and install [`hugo-extended`](https://gohugo.io/getting-started/installing/).
+
+2. Ensure that you have the required Python modules installed (to be executed
 in the repository root):
 
 ```command
@@ -18,22 +20,23 @@
 $ pip3 install --user -r python-requirements.txt
 ```
 
-2. Execute the build script:
+3. Execute the build script:
 
 ```command
 $ ./util/build_docs.py --preview
 ```
 
-This compiles the documentation into `./opentitan-docs` and starts a local
+This compiles the documentation into `./build/docs` and starts a local
 server, which allows you to access the documentation at
-[http://127.0.0.1:5500](http://127.0.0.1:5500).
+[http://127.0.0.1:1313](http://127.0.0.1:1313).
 
 ## How to contribute
 
-Have a look at [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines how to
-contribute code to this repository.
+Have a look at [CONTRIBUTING](./CONTRIBUTING.md) for guidelines how
+to contribute code to this repository.
 
 ## Licensing
 
 Unless otherwise noted, everything in this repository is covered by the Apache
-License, Version 2.0 (see [LICENSE](LICENSE) for full text).
+License, Version 2.0 (see
+[LICENSE](./LICENSE) for full text).
diff --git a/index.md b/_index.md
similarity index 74%
rename from index.md
rename to _index.md
index c1f127e..ecff748 100644
--- a/index.md
+++ b/_index.md
@@ -2,16 +2,16 @@
 
 The OpenTitan project aims to design and ship an open source, industry-leading piece of secure silicon for Root of Trust applications.
 OpenTitan is administered by lowRISC CIC as a collaborative
-[project](doc/project.md)
+[project]({{< relref "doc/project.md" >}})
 to produce high quality, open IP for instantiation as a full-featured
-[product](doc/product.md).
+[product]({{< relref "doc/product.md" >}}).
 This repository exists to enable collaboration across partners participating in the OpenTitan project.
 
 To get started using or contributing to the OpenTitan codebase, see the
-[list of user guides](doc/ug/index.md).
+[list of user guides]({{< relref "doc/ug" >}}).
 For details on coding styles or how to use our project-specific tooling, see the
-[reference manuals](doc/rm/index.md).
-[This page](hw/index.md)
+[reference manuals]({{< relref "doc/rm" >}}).
+[This page]({{< relref "hw" >}})
 contains technical documentation on the SoC, the Ibex processor core, and the individual IP blocks.
 
 While the repository is currently private and the work embargoed at these early stages, it will eventually be released publicly.
@@ -22,34 +22,32 @@
 [repo](http://www.github.com/lowrisc/opentitan)
 is set up as a monolithic repository to contain RTL, helper scripts, technical documentation, and other software necessary to produce our hardware designs.
 
-See also [repository readme](README.mkd) for licensing information and the development methodology.
+See also [repository readme]({{< relref "README.md" >}}) for licensing information and the development methodology.
 
 ## Documentation Sections
 
-* [Project](doc/project.md)
+* [Project]({{< relref "doc/project.md" >}})
   * How the OpenTitan project is organized
   * Governance of the program, how to get involved
   * Progress tracking
-* [Product](doc/product.md)
+* [Product]({{< relref "doc/product.md" >}})
   * What is the OpenTitan product
   * Architecture and technical hardware specifications
   * Software roadmap
   * Security and manufacturing
-* [User Guides](doc/ug/index.md)
+* [User Guides]({{< relref "doc/ug" >}})
   * How to get started with the repo
   * How to emulate on an FPGA
   * How verification is done in OpenTitan
   * How validation is done on FPGA in the project
-* [Reference Manuals](doc/rm/index.md)
+* [Reference Manuals]({{< relref "doc/rm" >}})
   * Defining comportable IP peripherals
   * Coding style guides for Verilog, Python, and Markdown
   * OpenTitan tools
   * Working with vendor tools
-* [Hardware Specifications](hw/index.md)
+* [Hardware Specifications]({{< relref "hw" >}})
   * Top-level SoC
   * Ibex processor core
   * Comportable IP blocks
-* [Tools](util/index.md)
+* [Tools]({{< relref "util" >}})
   * Readme's of OpenTitan tools
-
-If you're not finding what you're looking for, check the [sitemap](sitemap.md).
diff --git a/doc/index.md b/doc/_index.md
similarity index 100%
rename from doc/index.md
rename to doc/_index.md
diff --git a/doc/project.md b/doc/project.md
index 2d0b1cd..0fd99ea 100644
--- a/doc/project.md
+++ b/doc/project.md
@@ -1,6 +1,6 @@
 # Introduction to the OpenTitan Project
 
-&lt;Deeper dive into what the project is all about, as compared to intro paragraph on bubble server.
+&lt;Deeper dive into what the project is all about, as compared to intro paragraph.
 Open source strategy; History; Who are we?; Etc (more deep from the intro paragraph)&gt;
 
 ## OpenTitan project governance
diff --git a/doc/project/hw_dashboard.md b/doc/project/hw_dashboard.md
index 63df7fc..9972262 100644
--- a/doc/project/hw_dashboard.md
+++ b/doc/project/hw_dashboard.md
@@ -1,8 +1,8 @@
 # Hardware Designs Dashboard
 
 This dashboard gives the current status of
-[Comportable](../rm/comportability_specification.md)
+[Comportable](/doc/rm/comportability_specification)
 designs within the OpenTitan project.
-See the [Hardware Development Stages](../ug/hw_stages.md) for description of the hardware stages and how they are determined.
+See the [Hardware Development Stages](/doc/ug/hw_stages) for description of the hardware stages and how they are determined.
 
-{{% dashboard ../../hw/ip }}
+{{< dashboard "hw/ip" >}}
diff --git a/doc/rm/_index.md b/doc/rm/_index.md
new file mode 100644
index 0000000..dd1b067
--- /dev/null
+++ b/doc/rm/_index.md
@@ -0,0 +1,12 @@
+# Reference Manuals
+
+* [Comportability Definition and Specification]({{< relref "comportability_specification" >}})
+* Tool Guides
+   * [Register Tool]({{< relref "register_tool" >}}): Describes `regtool.py` and its HJSON format source. Used to generate documentation, rtl, header files and validation files for IP Registers and toplevel.
+   * [Vendor-In Tool]({{< relref "vendor_hw_tool" >}}): Describes `vendor_hw.py` and its HJSON control file. Used to pull a local copy of code maintained in other upstream repositories and apply local patch sets.
+* Coding Style Guides
+  * [Verilog Coding Style](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
+  * [Python Coding Style]({{< relref "python_coding_style.md" >}})
+  * [Hjson Usage and Style Guide]({{< relref "hjson_usage_style.md" >}})
+  * [Markdown Usage and Style Guide]({{< relref "markdown_usage_style.md" >}})
+  * [C/C++ Style Guide]({{< relref "c_cpp_coding_style.md" >}})
diff --git a/doc/rm/c_cpp_coding_style.md b/doc/rm/c_cpp_coding_style.md
index 1558232..34be6b1 100644
--- a/doc/rm/c_cpp_coding_style.md
+++ b/doc/rm/c_cpp_coding_style.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr C and C++ Coding Style Guide }}
+---
+title: "C and C++ Coding Style Guide"
+---
 
 ## Basics
 
@@ -13,7 +15,6 @@
 *   promote best practices
 *   increase code sharing and re-use
 
-{{% toc 3 }}
 
 ### Terminology Conventions
 
diff --git a/doc/rm/comportability_diagram_alert_hw.svg b/doc/rm/comportability_specification/comportability_diagram_alert_hw.svg
similarity index 100%
rename from doc/rm/comportability_diagram_alert_hw.svg
rename to doc/rm/comportability_specification/comportability_diagram_alert_hw.svg
diff --git a/doc/rm/comportability_diagram_intr_hw.svg b/doc/rm/comportability_specification/comportability_diagram_intr_hw.svg
similarity index 100%
rename from doc/rm/comportability_diagram_intr_hw.svg
rename to doc/rm/comportability_specification/comportability_diagram_intr_hw.svg
diff --git a/doc/rm/comportability_diagram_peripheral.svg b/doc/rm/comportability_specification/comportability_diagram_peripheral.svg
similarity index 100%
rename from doc/rm/comportability_diagram_peripheral.svg
rename to doc/rm/comportability_specification/comportability_diagram_peripheral.svg
diff --git a/doc/rm/comportability_specification.md b/doc/rm/comportability_specification/index.md
similarity index 98%
rename from doc/rm/comportability_specification.md
rename to doc/rm/comportability_specification/index.md
index d2966d0..5096d89 100644
--- a/doc/rm/comportability_specification.md
+++ b/doc/rm/comportability_specification/index.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr Comportability Definition and Specification }}
+---
+title: "Comportability Definition and Specification"
+---
 
 ## Document Goals
 
@@ -14,7 +16,6 @@
 ![scan of definition on page 45](https://books.google.co.uk/books/content?id=JwC-GInMrW4C&pg=PA45&img=1&zoom=3&hl=en&sig=ACfU3U3-RHKNO-UV3r7xOGeK1VigzCl3-w&ci=31%2C225%2C415%2C42&edge=0)
 
 
-{{% toc 3 }}
 
 ## Definitions
 
@@ -169,7 +170,7 @@
 The working assumption is that a higher level full-chip configuration file will distribute address ranges to all of the included bus peripheral devices.
 
 The TileLink-UL protocol and its usage within lowRISC devices is given in the
-[TileLink-UL Bus Specification](../../hw/ip/tlul/doc/tlul.md).
+[TileLink-UL Bus Specification]({{< relref "hw/ip/tlul/doc" >}}).
 
 ### Bus Host
 
@@ -329,7 +330,7 @@
 The waveform below shows the timing of the event occurrence, its latched value, and the clearing by the processor.
 More details follow.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'Clock',             wave: 'p.............' },
@@ -347,7 +348,7 @@
     tock: 0
   },
 }
-```
+{{< /wavejson >}}
 
 ### Interrupts per module
 
@@ -450,7 +451,7 @@
 
 The implementation of the signaling is not finalized at this time, but the waveform below gives an example of the relationships.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'Clock',                 wave: 'p....................' },
@@ -469,6 +470,6 @@
     tock: 0
   },
 }
-```
+{{< /wavejson >}}
 
 **Figure 4**: Alert event signaling and timing
diff --git a/doc/rm/crossbar_example_1.svg b/doc/rm/crossbar_tool/crossbar_example_1.svg
similarity index 100%
rename from doc/rm/crossbar_example_1.svg
rename to doc/rm/crossbar_tool/crossbar_example_1.svg
diff --git a/doc/rm/crossbar_example_2.svg b/doc/rm/crossbar_tool/crossbar_example_2.svg
similarity index 100%
rename from doc/rm/crossbar_example_2.svg
rename to doc/rm/crossbar_tool/crossbar_example_2.svg
diff --git a/doc/rm/crossbar_example_3.svg b/doc/rm/crossbar_tool/crossbar_example_3.svg
similarity index 100%
rename from doc/rm/crossbar_example_3.svg
rename to doc/rm/crossbar_tool/crossbar_example_3.svg
diff --git a/doc/rm/crossbar_example_4.svg b/doc/rm/crossbar_tool/crossbar_example_4.svg
similarity index 100%
rename from doc/rm/crossbar_example_4.svg
rename to doc/rm/crossbar_tool/crossbar_example_4.svg
diff --git a/doc/rm/crossbar_example_5.svg b/doc/rm/crossbar_tool/crossbar_example_5.svg
similarity index 100%
rename from doc/rm/crossbar_example_5.svg
rename to doc/rm/crossbar_tool/crossbar_example_5.svg
diff --git a/doc/rm/crossbar_tool.md b/doc/rm/crossbar_tool/index.md
similarity index 96%
rename from doc/rm/crossbar_tool.md
rename to doc/rm/crossbar_tool/index.md
index f206a40..d43e301 100644
--- a/doc/rm/crossbar_tool.md
+++ b/doc/rm/crossbar_tool/index.md
@@ -1,14 +1,14 @@
-{{% lowrisc-doc-hdr Crossbar Generation Tool }}
+---
+title: "Crossbar Generation Tool"
+---
 
 The crossbar tool `tlgen.py` is used to build the TL-UL crossbar RTL. It is used
 standalone or invoked as part of top module generation process.
 `hw/top_earlgrey/ip/xbar/rtl/autogen/*.sv` RTLs are generated with the crossbar
 tool.  This document doesn't explain its internal blocks. [Bus
-Specification](../../hw/ip/tlul/doc/tlul.md) explains the protocol and the
+Specification]({{< relref "hw/ip/tlul/doc" >}}) explains the protocol and the
 components used in the crossbar.
 
-{{% toc 3 }}
-
 ## Standalone tlgen.py
 
 The standalone `tlgen.py` is a python3 script to read crossbar Hjson
@@ -41,14 +41,12 @@
 are derived from the top Hjson configuration module structure.
 
 A description of Hjson and the recommended style is in the [Hjson Usage and
-Style Guide](hjson_usage_style.md).
+Style Guide]({{< relref "hjson_usage_style" >}}).
 
 The tables below describe the keys for each context. The tool raises an error if
 *Required* keys are missing. *Optional* keys may be provided in the input files.
 The tool also may insert the optional keys with default value.
 
-{{% include !../../util/tlgen.py --doc }}
-
 ## Fabrication process
 
 The tool fabricates a sparse crossbar from the given Hjson configuration file.
diff --git a/doc/rm/hjson_usage_style.md b/doc/rm/hjson_usage_style.md
index 9d69fe3..477a0ba 100644
--- a/doc/rm/hjson_usage_style.md
+++ b/doc/rm/hjson_usage_style.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr Hjson Usage and Style Guide }}
+---
+title: "Hjson Usage and Style Guide"
+---
 
 ## Basics
 
@@ -15,7 +17,6 @@
 *   promote best practices
 *   increase code sharing and re-use
 
-{{% toc 3 }}
 
 ### Terminology Conventions
 
diff --git a/doc/rm/index.md b/doc/rm/index.md
deleted file mode 100644
index 47a08f0..0000000
--- a/doc/rm/index.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Reference Manuals
-
-* [Comportability Definition and Specification](comportability_specification.md)
-* Tool Guides
-   * [Register Tool](register_tool.md): Describes `regtool.py` and its Hjson format source. Used to generate documentation, rtl, header files and validation files for IP Registers and toplevel.
-   * [Vendor-In Tool](vendor_hw_tool.md): Describes `vendor_hw.py` and its Hjson control file. Used to pull a local copy of code maintained in other upstream repositories and apply local patch sets.
-   * [Crossbar Tool](crossbar_tool.md): Describes `tlgen.py` and its Hjson format source. Used to generate RTL and validation files for the crossbar.
-* Coding Style Guides
-  * [Verilog Coding Style](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
-  * [Python Coding Style](python_coding_style.md)
-  * [Hjson Usage and Style Guide](hjson_usage_style.md)
-  * [Markdown Usage and Style Guide](markdown_usage_style.md)
-  * [C/C++ Style Guide](c_cpp_coding_style.md)
-* [FPGA Manual](ref_manual_fpga.md)
diff --git a/doc/rm/markdown_usage_style.md b/doc/rm/markdown_usage_style.md
index 73732ef..a7ab246 100644
--- a/doc/rm/markdown_usage_style.md
+++ b/doc/rm/markdown_usage_style.md
@@ -1,25 +1,22 @@
-{{% lowrisc-doc-hdr Markdown Usage and Style Guide }}
+---
+title: "Markdown Usage and Style Guide"
+---
 
 ## Basics
 
 ### Summary
 
 Markdown files are used to write most documentation.
-The main markdown tool is based on [CommonMark](https://commonmark.org/) (a strongly defined, highly compatible specification of Markdown), parsed by [mistletoe](https://github.com/miyuchina/mistletoe) (a fast, extensible and spec-compliant Markdown parser in pure Python).
-Mistletoe adds support for tables using the Github markdown syntax.
+The main markdown tool is [Hugo](https://gohugo.io).
 
 The markdown processing is done using the `docgen.py` tool in the `util` directory.
-See the [examples README](https://github.com/lowRISC/opentitan/tree/master/util/docgen/examples) for details of running the tool.
-`docgen` provides extensions to the markdown syntax to support documenting [Comportable](comportability_specification.md) components.
 
-This guide covers the enhancements provided by the lowRISC markdown extensions that are used in the project along with a recommended style.
 As with all style guides the intention is to:
 
 *   promote consistency across projects
 *   promote best practices
 *   increase code sharing and re-use
 
-{{% toc 3 }}
 
 ### Terminology Conventions
 
@@ -42,66 +39,6 @@
 There are times when the best path to a working design, or for working around a tool issue, is to simply cut the Gordian Knot and create code that is at variance with this style guide.
 It is always okay to deviate from the style guide by necessity, as long as that necessity is clearly justified by a brief comment.
 
-## lowRISC Markdown extensions
-
-The following extensions have been made for the lowRISC version:
-
-*   `{{% lowrisc-doc-hdr Title Of Doc }}` Insert a standard title header and give the document a title.
-    **Note** that this header includes indicating copyright lowRISC contributors.
-    Eventually this will be extended to have lowrisc-doc-hdr=type (type could be component, core, guide,...) to allow the tool to validate required sections are in the document.
-
-*   `{{% regfile filename.hjson }}` Pointer to the comportable IP interface and register definition Hjson.
-    This is expected to go early in the document.
-    After this line the registers and hardware configuration are available as markup items.
-    Any path in the filename is relative to the directory containing the markdown file.
-
-*   `{{% toc <depth> }}` Insert the table of contents at this point in the document.
-    The `<depth>` must be an integer and indicates depth to generate the contents.
-    The table inserted contains pointers to all headings from level 2 to the depth.
-    Table of contents entries are generated for all markup headings (lines starting `#` for level 1 to `######` for level 6), from Section1 (equivalent to level 2) and Section2 (equivalent to level 3) directives (see below) and for register definitions in the register table.
-
-*   `{{% registers x }}` Insert the register tables that were generated from the Hjson file imported with the `regfile` directive.
-    This directive must occur later in the file than the regfile extension!
-    TODO: fix the need for `x`.
-
-*   `{{% hwcfg name }}` Insert the details of the comportable hardware configuration that was generated from the Hjson file imported with the `regfile` directive.
-    The `name` is used as the descriptive name in the generated text and would normally match the IP name.
-    This directive must occur later in the file than the regfile extension!
-
-*   `{{% Section1 Section Title }}` Similar to `##` but format the title using lowRISC section formatting.
-    See [discussion below](#headings-and-sections).
-    In the future the section title may be used to check the document contains the expected sections.
-
-*   `{{% Section2 Section Title }}` Similar to `###` but format the title using lowRISC section formatting.
-    See [discussion below](#headings-and-sections).
-    In the future the section title may be used to check the document contains the expected sections.
-
-*   `{{% include file }}` Insert the file into the markdown document.
-    Any other text on the same line as the include directive will be inserted, then a newline and then the included file.
-    The file is included before any other processing so the result is a single file processed by the markdown processor (thus all definitions like anchor links are global and not confined to the file they are in).
-    Includes may be nested.
-    The filename is relative to the directory that the markdown file currently being processed is in (so relative links work from inside included files).
-    If the include file is not found then an error is reported and a line indicating the error will be inserted in the markdown.
-
-*   `{{% include !command -options }}` Use the shell to cd to the directory that the markdown file is in and run the command with given options (everything from the `!` to the closing `}}` is used as the shell command).
-    Insert the output (stdout) from the command into the markdown document.
-    Any other text on the same line as the include directive will be inserted, then a newline and then the command output.
-    (As a result, if the triple back-tick to start a code block immediately follows the `}}` then the output from the command will be inserted inside that code block.)
-    Error returns from the command will be ignored, and any output on stderr will be reported in the docgen stderr output.
-    This can be used to include generated output in a document or to pull in things like example code.
-
-
-*   `!!Reg` or `!!Reg.Field` Insert Component.Reg or Component.Reg.Field in the output file as a hyperlink to the register table for Reg and tagged for special CSS decoration (currently makes them blue, monospace and a little smaller).
-    If Reg is not in the list of registers read from the regfile directive then a warning is printed and the output is not transformed.
-    (Note the use of period rather than underline as the separator was to avoid syntax highlighter issues because of markdown's use of underline for italics.)
-
-*   ` ```lang ` Code blocks are highlighted by [pygments](http://pygments.org/) (a generic syntax highlighter in python).
-    Background colour can be set using the {.good} and {.bad} tags after the lang.
-
-*   ` ```wavejson ` Code blocks describing waveforms are converted into an svg picture in the output file.
-    See more detailed [description below](#waveforms).
-    If the docgen tool is invoked with the `-j` or `--wavesvg-usejs` flag then instead of an inline svg this directive will generate the `<script>` output needed by the online WaveDrom javascript parser and include invocation of wavedrom in the output html.
-
 ## General Markdown Style
 
 ### Line length
@@ -119,19 +56,13 @@
 
 ### Headings and sections
 
-The title of the document should be provided using the `lowrisc-doc-hdr` directive.
-Therefore there should be no use of level 1 headings (lines starting `#`).
-
-Standard headings should use the `Section1` (a level 2 heading like `##`) or `Section2` (a level 3 heading like `###`) directive and will be decorated in the project style.
-
-Other headings should use standard markdown heading syntax (lines starting `##` though `######` for level 2-6).
+The title of the document should be provided using the `title` field in the frontmatter.
 
 Headings and sections are given ID tags to allow cross references.
 The ID is the text of the heading, converted to lower case and with spaces converted to `-`.
 Thus `### Headings and sections` gets the ID `headings-and-sections` and can be referenced using the markdown hyperlink syntax `[link text](#headings-and-sections)`.
 
 Headings and sections are added to the table of contents.
-When it is inserted the maximum depth can be specified.
 
 ### Images
 
@@ -140,15 +71,16 @@
 
 ### Waveforms
 
-Waveforms can be included by adding [wavejson](https://github.com/wavedrom/schema/blob/master/WaveJSON.md) code blocks introduced with ` ```wavejson `.
-The `docgen` markdown processor will convert these into an inline SVG image when it generates html.
+Waveforms can be included by adding [wavejson](https://github.com/wavedrom/schema/blob/master/WaveJSON.md) code surrounded by `{{</* wavejson */>}}` shortcode tags.
 
+<!-- TODO: This comment isn't correct, the WaveDrom library being used hasn't been modified.
 There is a standalone tool for wavejson to svg conversion.
 Details of the tool and a full description of the wavejson syntax that is supported can be found in the [README](https://github.com/lowRISC/opentitan/tree/master/util/wavegen) for the `wavegen.py` tool.
 Note that there are several incomplete descriptions of wavejson, the syntax supported is derived primarily from the examples in the [WaveDrom Tutorial](https://observablehq.com/@drom/wavedrom).
 
 An online editor for wavejson can be found on the [WaveDrom](https://wavedrom.com/) website.
 The processor built in to `docgen` should produce the identical output, but has one extension that `cdata` may be used in place of `data` to allow labeling all bit positions not just the `2345` ones.
+-->
 
 ### Text Format
 
@@ -160,11 +92,9 @@
 Comments are rare, but should be used where needed.
 Use the html `<!--` and `-->` as the comment delimiters.
 
-
 ### Markdown file extensions
 
-The markdown files should use the `.md` or `.mkd` file extension.
-
+The markdown files should use the `.md` file extension.
 
 ## Markdown file format for IP module descriptions
 
@@ -173,27 +103,23 @@
 The header instantiates the standard document header and reads the Hjson description of the module.
 
 ```
-{{% lowrisc-doc-hdr Name HWIP Technical Specification }}
-{{% regfile name_reg.hjson}}
-
-{{% section1 Overview }}
-
+---
+title: "Example IP Block"
+---
 ```
 
-This is followed by some boiler-plate comments and the table of contents.
+This is followed by some boiler-plate comments.
 
 ```
 This document specifies Name hardware IP functionality.
-This module conforms to the [Comportable guideline for peripheral functionality.](./comportability_specification.md)
+This module conforms to the [Comportable guideline for peripheral functionality.]({{</* relref "doc/rm/comportability_specification" */>}})
 See that document for integration overview within the broader top level system.
-
-{{% toc 3 }}
 ```
 
 The next section summarizes the feature set of the IP block.
 
 ```
-{{% section2 Features }}
+## Features
 
 * Bulleted list
 * Of main features
@@ -202,7 +128,7 @@
 There then follows a general description of the IP
 
 ```
-{{% section2 Description }}
+## Description
 
 Description of the IP.
 ```
@@ -212,7 +138,7 @@
 
 
 ```
-{{% section2 Compatibility }}
+## Compatability
 
 Notes on if the IP register interface is compatible with any existing register interface.
 Also note any differences.
@@ -222,7 +148,7 @@
 The next major section is a more detailed operational description of the module.
 
 ```
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 ```
 
@@ -233,7 +159,7 @@
 
 ```
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![Name Block Diagram](block_diagram.svg)
 
@@ -248,9 +174,8 @@
 
 ```
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg uart}}
 
 ```
 
@@ -258,7 +183,7 @@
 
 ```
 
-{{% section2 Design Details }}
+## Design Details
 
 Details of the design.
 
@@ -266,17 +191,17 @@
 ```
 There are probably waveforms embedded here:
 
-````
+```
 
-```wavejson
+{{</* wavejson */>}}
 {
   signal: [
     { name: 'Clock',        wave: 'p............' },
   ]
 }
-```
+{{</* /wavejson */>}}
 
-````
+```
 
 The final major section is the software user guide and describes using the IP and notes on writing device drivers.
 Code fragments are encouraged.
@@ -285,38 +210,37 @@
 
 ```
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
 ```
 
 One important thing here is to show the order of initialization that has been tested in the verification environment.
 In most cases other orders will work, and may be needed by the software environment, but it can be helpful in tracking down bugs for the validated sequence to be described!
 
-````
-{{% section2 Initialization }}
-
+```
+## Initialization
+```
 ```c
+
  if (...) {
    a = ...
  }
 ```
 
-````
-
 Other sections cover different use cases and example code fragments.
 
 ```
 
-{{% section2 Use case A (eg Transmission) }}
+## Use case A (eg Transmission)
 
-{{% section2 Use case B (eg Reception) }}
+## Use case B (eg Reception)
 
 ```
 
 It is important to include a discussion of error conditions.
 
 ```
-{{% section2 Error conditions }}
+## Error conditions
 
 ```
 
@@ -325,72 +249,71 @@
 
 ```
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 ```
 
 The document should end with the automatically generated register tables.
 
 ```
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{</* registers "hw/ip/component/data/component.hjson" */>}}
 
-````
+```
 
 To allow cut/paste of the default structure, here is an uncommented version:
 
-````
-{{% lowrisc-doc-hdr Name HWIP Technical Specification }}
-{{% regfile name_reg.hjson}}
+```
+---
+title: Name HWIP Technical Specification
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies Name hardware IP functionality.
-This module conforms to the [Comportable guideline for peripheral functionality.](./comportability_specification.md)
+This module conforms to the [Comportable guideline for peripheral functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader top level system.
 
-{{% toc 3 }}
-
-{{% section2 Features }}
+## Features
 
 * Bulleted list
 
-{{% section2 Description }}
+## Description
 
 
-{{% section2 Compatibility }}
+## Compatibility
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![Name Block Diagram](block_diagram.svg)
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg Name }}
+{{</* hwcfg "hw/ip/component/data/component.hjson" */>}}
 
-{{% section2 Design Details }}
+## Design Details
 
 ### Many third level headings
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
-{{% section2 Use case A (eg Transmission) }}
+## Use case A (eg Transmission)
 
-{{% section2 Use case B (eg Reception) }}
+## Use case B (eg Reception)
 
-{{% section2 Error conditions }}
+## Error conditions
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{</* registers "hw/ip/component/data/component.hjson" */>}}
 
-````
+```
diff --git a/doc/rm/python_coding_style.md b/doc/rm/python_coding_style.md
index 244c9f5..3f4dcc5 100644
--- a/doc/rm/python_coding_style.md
+++ b/doc/rm/python_coding_style.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr Python Coding Style Guide }}
+---
+title: "Python Coding Style Guide"
+---
 
 ## Basics
 
@@ -17,7 +19,6 @@
 *   promote best practices
 *   increase code sharing and re-use
 
-{{% toc 3 }}
 
 ### Terminology Conventions
 
diff --git a/doc/rm/ref_manual_fpga.md b/doc/rm/ref_manual_fpga.md
index 6d608e5..3dc0fae 100644
--- a/doc/rm/ref_manual_fpga.md
+++ b/doc/rm/ref_manual_fpga.md
@@ -1,17 +1,20 @@
+---
+title: "FPGA Reference Manual"
+---
+
 # FPGA Reference Manual
 
 This manual provides additional usage details about the FPGA.
 Specifically, it provides instructions on SW development flows, testing and bitfile release procedures.
 
-{{% toc 3 }}
 
 ## Usage Options
 
 There are two ways to use OpenTitan on the FPGA.
 - The first is to build the design from scratch using Vivado.
-  Refer to the [Getting Started FPGA](../ug/getting_started_fpga.md) guide for more information.
+  Refer to the [Getting Started FPGA]({{< relref "doc/ug/getting_started_fpga" >}}) guide for more information.
 - The second is to program the FPGA with a released bitfile using storage devices.
-  Refer to the [Quickstart Guide](../ug/quickstart.md) guide for instructions on this approach.
+  Refer to the [Quickstart Guide]({{< relref "doc/ug/quickstart" >}}) guide for instructions on this approach.
 
 ## FPGA SW Development Flow
 
@@ -41,7 +44,7 @@
 $ fusesoc --cores-root . pgm lowrisc:systems:top_earlgrey_nexysvideo
 ```
 
-The script assumes that there is an existing bitfile `build/lowrisc_systems_top_earlgrey_nexysvideo_0.1/synth-vivado/lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit` (this is created after following the steps in [getting_started_fpga](../ug/fpga/getting_started_fpga.md)).
+The script assumes that there is an existing bitfile `build/lowrisc_systems_top_earlgrey_nexysvideo_0.1/synth-vivado/lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit` (this is created after following the steps in [getting_started_fpga]({{< relref "doc/ug/getting_started_fpga" >}})).
 
 The script rebuilds the contents in `sw/devices/boot_rom` and then creates a new bitfile of the same name at the same location.
 The original input bitfile is moved to `build/lowrisc_systems_top_earlgrey_nexysvideo_0.1/synth-vivado/lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit.orig`.
@@ -52,7 +55,7 @@
 
 After building, the FPGA bitstream contains only the boot ROM.
 Using this boot ROM, the FPGA is able to load additional software to the emulated flash, such as software in the `sw/device/benchmark`, `sw/device/examples` and `sw/device/tests` directories.
-To load additional software, a custom load tool named [spiflash](../../sw/host/spiflash/README.md) is required.
+To load additional software, a custom load tool named [spiflash]({{< relref "sw/host/spiflash/README.md" >}}) is required.
 
 Once the tool is built, also build the binary you wish to load.
 For the purpose of this demonstration, we will use `sw/device/examples/hello_world`, but it applies to any software image that is able to fit in the emulated flash space.
@@ -75,11 +78,11 @@
 frame: 0x80000005 to offset: 0x00001338
 ```
 
-For more details on the exact operation of the loading flow and how the boot ROM processes incoming data, please refer to the [boot ROM readme](../../sw/device/boot_rom/README.md).
+For more details on the exact operation of the loading flow and how the boot ROM processes incoming data, please refer to the [boot ROM readme]({{< relref "sw/device/boot_rom/README.md" >}}).
 
 ## FPGA Testing and Release Procedure
 
-As mentioned in [quick_start](../ug/fpga/quick_start_fpga.md), golden bitfiles will be released.
+As mentioned in [quick_start]({{< relref "doc/ug/quickstart" >}}), golden bitfiles will be released.
 Before release, a check process will be performed on the release git-tag to ensure the FPGA bitstream is functional.
 The [checks to be performed are](link script later):
     * FPGA is built without issues.
diff --git a/doc/rm/register_tool.md b/doc/rm/register_tool/index.md
similarity index 98%
rename from doc/rm/register_tool.md
rename to doc/rm/register_tool/index.md
index de2d0fc..3f67f80 100644
--- a/doc/rm/register_tool.md
+++ b/doc/rm/register_tool/index.md
@@ -1,9 +1,10 @@
-{{% lowrisc-doc-hdr Register Tool }}
+---
+title: "Register Tool"
+---
 
 The register tool is used to construct register documentation, register RTL and header files.
 It is either used stand-alone or by being invoked as part of markdown processing.
 
-{{% toc 3 }}
 
 ## Running standalone regtool.py
 
@@ -21,15 +22,13 @@
 
 The tool input is an Hjson file containing the Comportable description of the IP block and its registers.
 
-A description of Hjson (a varient of json) and the recommended style is in the [Hjson Usage and Style Guide](hjson_usage_style.md).
+A description of Hjson (a varient of json) and the recommended style is in the [Hjson Usage and Style Guide]({{< relref "hjson_usage_style.md" >}}).
 
 The tables below describe valid keys for each context.
 It is an error if *required* keys are missing from the input json.
 *Optional* keys may be provided in the input file as needed, as noted in the tables the tool may insert them (with default or computed values) during validation so the output generators do not have to special case them.
 Keys marked as "inserted by tool" should not be in the input json (they will be silently overwritten if they are there), they are derived by the tool during validation of the input and available to the output generators.
 
-{{% include !../../util/regtool.py --doc }}
-
 The tool will normally generate the register address offset by starting from 0 and allocating the registers in the order they are in the input file.
 Between each register the offset is incremented by the number of bytes in the `regwidth` (4 bytes for the default 32-bit `regwidth`), so the registers end up packed into the smallest space.
 
@@ -219,7 +218,7 @@
 
 This section details the register generation for hardware instantiation.
 The input to the tool for this generation is the same `.hjson` file described above.
-The output is two verilog files that can be instantiated by a peripheral that follows the [Comportability Guidelines](comportability_specification.md).
+The output is two verilog files that can be instantiated by a peripheral that follows the [Comportability Guidelines]({{< relref "comportability_specification" >}}).
 
 The register generation tool will generate the RTL if it is invoked with the `-r` flag.
 The `-t <directory>` flag is used to specify the output directory where the two files will be written.
diff --git a/doc/rm/reg_top.svg b/doc/rm/register_tool/reg_top.svg
similarity index 100%
rename from doc/rm/reg_top.svg
rename to doc/rm/register_tool/reg_top.svg
diff --git a/doc/rm/subreg_ext.svg b/doc/rm/register_tool/subreg_ext.svg
similarity index 100%
rename from doc/rm/subreg_ext.svg
rename to doc/rm/register_tool/subreg_ext.svg
diff --git a/doc/rm/subreg_rw.svg b/doc/rm/register_tool/subreg_rw.svg
similarity index 100%
rename from doc/rm/subreg_rw.svg
rename to doc/rm/register_tool/subreg_rw.svg
diff --git a/doc/rm/subreg_rw0c.svg b/doc/rm/register_tool/subreg_rw0c.svg
similarity index 100%
rename from doc/rm/subreg_rw0c.svg
rename to doc/rm/register_tool/subreg_rw0c.svg
diff --git a/doc/rm/subreg_rw1c.svg b/doc/rm/register_tool/subreg_rw1c.svg
similarity index 100%
rename from doc/rm/subreg_rw1c.svg
rename to doc/rm/register_tool/subreg_rw1c.svg
diff --git a/doc/rm/subreg_rw1s.svg b/doc/rm/register_tool/subreg_rw1s.svg
similarity index 100%
rename from doc/rm/subreg_rw1s.svg
rename to doc/rm/register_tool/subreg_rw1s.svg
diff --git a/doc/rm/vendor_hw_tool.md b/doc/rm/vendor_hw_tool.md
index ad37f9d..a8b0746 100644
--- a/doc/rm/vendor_hw_tool.md
+++ b/doc/rm/vendor_hw_tool.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr: vendor_hw: vendor-in hardware components }}
+---
+title: "vendor_hw: vendor-in hardware components"
+---
 
 Not all hardware code contained in this repository is actually developed within this repository.
 Code which we include from external sources is placed in the `hw/vendor` directory and copied into this directory from its upstream source.
diff --git a/doc/rm/verilog_coding_style.md b/doc/rm/verilog_coding_style.md
index a86749c..8f321c4 100644
--- a/doc/rm/verilog_coding_style.md
+++ b/doc/rm/verilog_coding_style.md
@@ -1,4 +1,6 @@
-{{% lowrisc-doc-hdr Verilog Coding Style Guide }}
+---
+title: "Verilog Coding Style Guide"
+---
 
 ## Basics
 
@@ -22,7 +24,6 @@
 
 See the [Appendix](#appendix---condensed-style-guide) for a condensed tabular representation of this style guide.
 
-{{% toc 3 }}
 
 ### Terminology Conventions
 
diff --git a/doc/ug/_index.md b/doc/ug/_index.md
new file mode 100644
index 0000000..1ac9fff
--- /dev/null
+++ b/doc/ug/_index.md
@@ -0,0 +1,47 @@
+---
+title: "User Guides"
+---
+
+# User Guides
+
+* Getting Started
+  * [Getting started]({{< relref "getting_started.md" >}})
+  * [Quickstart]({{< relref "quickstart.md" >}})
+  * [Notes on using GitHub and local git]({{< relref "github_notes.md" >}})
+  * [Build software]({{< relref "getting_started_sw.md" >}})
+  * [Getting started with Verilator]({{< relref "getting_started_verilator.md" >}})
+  * [Getting started on FPGAs]({{< relref "getting_started_fpga.md" >}})
+    * [Obtaining an FPGA board]({{< relref "fpga_boards.md" >}})
+    * [Installing Xilinx Vivado]({{< relref "install_instructions#xilinx-vivado" >}})
+  * [Getting started with a design]({{< relref "getting_started_design.md" >}})
+  * *Getting started with verification* (TODO)
+* [Work with hardware code in external repositories]({{< relref "vendor_hw.md" >}})
+* [Hardware Development Stages]({{< relref "hw_stages.md" >}})
+* [Design Methodology]({{< relref "design.md" >}})
+  * Language and Tool Selection
+  * Comportability and the Importance of Architectural Conformity
+  * Defining Design Complete: stages and tracking
+  * Documentation
+  * Usage of Register Tool
+  * Linting Methodology
+  * Assertions Methodology
+  * CDC Methodology
+  * DFT
+  * Generated Code
+* *Verification Methodology* (TODO)
+  * Verification strategy overview
+  * How do we define Verification completion
+    * Current verification status of IP and definition of milestones
+  * Tools
+  * Test planning
+  * Progress and tracking
+  * Code coverage output
+    * How do we report status
+  * Overview of in-tree helper classes, test benches, etc.
+* *Validation Methodology* (TODO)
+  * How to download bit stream
+  * What tests exist today
+  * How to run tests
+  * How does this differ from verification
+  * How to add tests
+* [List of Top-Level Designs]({{< relref "system_list.md" >}})
diff --git a/doc/ug/design.md b/doc/ug/design.md
index 6369b01..99e9e3c 100644
--- a/doc/ug/design.md
+++ b/doc/ug/design.md
@@ -1,3 +1,7 @@
+---
+title: "Design Methodology within OpenTitan"
+---
+
 # Design Methodology within OpenTitan
 
 The design methodology within OpenTitan combines the challenges of industry-strength design methodologies with open source ambitions.
@@ -19,7 +23,7 @@
 ## Comportability and the Importance of Architectural Conformity
 
 The OpenTitan program is adopting a design methodology aimed at unifying as much as possible the interfaces between individual designs and the rest of the SOC.
-These are detailed in the [Comportability Specification](../rm/comportability_specification.md).
+These are detailed in the [Comportability Specification]({{< relref "doc/rm/comportability_specification" >}}).
 This document details how peripheral IP interconnects with the embedded processor, the chip IO, other designs, and the security infrastructure within the SOC.
 Not all of the details are complete at this time, but will be tracked and finalized within that specification.
 
@@ -33,7 +37,7 @@
 Designs within the OpenTitan project come in a variety of completion status levels.
 Some designs are "tapeout ready" while others are still a work in progress.
 Understanding the status of a design is important to gauge the confidence in its advertised feature set.
-To that end, we've designated a spectrum of design stages in the [OpenTitan Hardware Development Stages](hw_stages.md) document.
+To that end, we've designated a spectrum of design stages in the [OpenTitan Hardware Development Stages]({{< relref "hw_stages.md" >}}) document.
 This document defines the design stages and references where one can find the current status of each of the designs in the repository.
 
 ## Documentation
@@ -41,24 +45,24 @@
 Documentation is a critical part of any design methodology.
 Within the OpenTitan project there are two important tooling components to efficient and effective documentation.
 
-The first is the [docgen](../../util/docgen/README.md) tool, which converts an annotated markdown file into a rendered HTML file (including this document).
-See the linked docgen specification for information about the annotations and how to use it to create enhanced auto-generated additions to standard Markdown files.
+The first is the [Hugo](https://gohugo.io) tool, which converts an annotated markdown file into a rendered HTML file (including this document).
+See the linked manual for information about the annotations and how to use it to create enhanced auto-generated additions to standard Markdown files.
 
-The second is the [reggen](../rm/register_tool.md) register tool that helps define the methodology and description language for specifying hardware registers.
+The second is the [reggen]({{< relref "doc/rm/register_tool" >}}) register tool that helps define the methodology and description language for specifying hardware registers.
 These descriptions are fed into docgen through annotations and ensure that the technical specifications for the IP are accurate and up to date with the hardware being built.
 
 Underlying and critical to this tooling is the human-written content that goes into the source markdown and register descriptions.
 Clarity and consistency is key.
-See the [Markdown Style Guide](../rm/markdown_usage_style.md) for details and guidelines on the description language.
+See the [Markdown Style Guide]({{< relref "doc/rm/markdown_usage_style.md" >}}) for details and guidelines on the description language.
 
 ## Usage of Register Tool
 
 One design element that is prime for consistent definitions and usages is in the area of register definitions.
 Registers are critical, being at the intersection of hardware and software, where uniformity can reduce confusion and increase re-usability.
-The [register tool](../rm/register_tool.md) used within OpenTitan is custom for the project's needs, but flexible to add new features as they arise.
+The [register tool]({{< relref "doc/rm/register_tool" >}}) used within OpenTitan is custom for the project's needs, but flexible to add new features as they arise.
 It attempts to stay lightweight yet solve most of the needs in this space.
 The description language (using HJSON format) described within that specification also details other features described in the
-[Comportability Specification](../rm/comportability_specification.md).
+[Comportability Specification]({{< relref "doc/rm/comportability_specification" >}}).
 
 ## Linting Methodology
 
@@ -77,7 +81,7 @@
 In the current state of the project, all lint scripts, policy files, and waivers are **not** provided, but are being kept privately until we can suggest a workable open source solution.
 When this methodology is finalized the details will be given here. (TODO)
 
-Goals for linting closure per design milestone are given in the [OpenTitan Development Stages](hw_stages.md) document.
+Goals for linting closure per design milestone are given in the [OpenTitan Development Stages]({{< relref "hw_stages.md" >}}) document.
 
 ## Assertion Methodology
 
@@ -87,7 +91,7 @@
 
 Within OpenTitan we attempt to maintain uniformity in assertion style and syntax using SystemVerilog Assertions and a list of common macros.
 An overview of the included macros and how to use them is given in this
-[Design Assertion README file](../../hw/formal/README.md).
+[Design Assertion README file]({{< relref "hw/formal/README.md" >}}).
 This document also describes how to formally verify assertions using
 [JasperGold](https://www.cadence.com/content/cadence-www/global/en_US/home/tools/system-design-and-verification/formal-and-static-verification/jasper-gold-verification-platform/formal-property-verification-app.html)
 from the company Cadence.
@@ -132,7 +136,7 @@
 
 DFT in OpenTitan is particularly interesting for two primary reasons:
 the RTL in the OpenTitan repository is targeted towards an FPGA implementation, but must be prepared for a silicon implementation
-(see the FPGA vs Silicon discussion in the [OpenTitan Product](../../doc/product.md) document);
+(see the FPGA vs Silicon discussion in the [OpenTitan Product]({{< relref "doc/product.md" >}}) document);
 the whole purpose of a DFT methodology is full and efficient access to all logic and storage content,
 while the whole purpose of a security microcontroller is restricting access to private secured information.
 In light of the latter dilemma, special care must be taken in a security design to ensure DFT has access at only the appropriate times, but not while in use in production.
@@ -153,7 +157,7 @@
 At the moment, all generated code is checked in with the source files.
 The pros and cons of this decision are still being discussed, and the decision may be reversed, to be replaced with a master build-all script to prepare a final design as source files changed.
 Until that time, all generated files (see for example the output files from the
-[register generation tool](../rm/register_tool.md))
+[register generation tool]({{< relref "doc/rm/register_tool" >}}))
 are checked in.
 There is a master build file in the repository under `hw/Makefile` that builds all of the `regtool` content.
 This is used by an Azure Pipelines pre-submit check script to ensure that the source files produce a generated file that is identical to the one being submitted.
@@ -161,4 +165,4 @@
 ## Getting Started with a Design
 
 The process for getting started with a design involves many steps, including getting clarity on its purpose, its feature set, authorship assignments, documentation, etc.
-These are discussed in the [Getting Started with a Design](getting_started_design.md) document.
+These are discussed in the [Getting Started with a Design]({{< relref "getting_started_design.md" >}}) document.
diff --git a/doc/ug/fpga_boards.md b/doc/ug/fpga_boards.md
index 70b38b1..979ce5d 100644
--- a/doc/ug/fpga_boards.md
+++ b/doc/ug/fpga_boards.md
@@ -1,3 +1,7 @@
+---
+title: "Get an FPGA Board"
+---
+
 # Get an FPGA Board
 
 FPGA boards come at different price points, with the price being a good indicator for how much logic the FPGA can hold.
diff --git a/doc/ug/getting_started.md b/doc/ug/getting_started.md
index 7fded2a..ad582e9 100644
--- a/doc/ug/getting_started.md
+++ b/doc/ug/getting_started.md
@@ -1,3 +1,7 @@
+---
+title: "Getting Started"
+---
+
 # Getting started
 
 Welcome!
@@ -8,11 +12,11 @@
 
 This guide uses the environment variable `$REPO_TOP` to refer to the top-level of the git source tree.
 The master tree is held on GitHub, this should be forked to user trees from which Pull Requests can be made.
-There is a set of [Notes for using GitHub](github_notes.html).
+There is a set of [Notes for using GitHub]({{< relref "github_notes.md" >}}).
 
 ## Setup
 
-You can either follow the [install instructions](install_instructions.md) from start to end to install all software required to simulate the design with Verilator and build a bitstream for an FPGA with Xilinx Vivado or check the corresponding [design description](getting_started.md#choose-a-design-to-build) for install requirements.
+You can either follow the [install instructions]({{< relref "install_instructions" >}}) from start to end to install all software required to simulate the design with Verilator and build a bitstream for an FPGA with Xilinx Vivado or check the corresponding [design description]({{< relref "getting_started.md#choose-a-design-to-build" >}}) for install requirements.
 
 ## Choose a design to build
 
@@ -20,17 +24,17 @@
 A target can be a specific FPGA board, an ASIC technology, or a simulation tool.
 The hardware you need to obtain and the tools you need to install depend on the chosen top-level design and the target.
 
-In order to continue, choose a system from the [List of Systems](/doc/ug/system_list.html).
+In order to continue, choose a system from the [List of Systems]({{< relref "/doc/ug/system_list.md" >}}).
 Read the design documentation for the requirements on the specific design/target combination, and then follow the appropriate steps below.
 
-* [Build software](getting_started_sw.html)
-* [Getting started with Verilator](getting_started_verilator.html)
-* [Getting started on FPGAs](getting_started_fpga.html)
+* [Build software]({{< relref "getting_started_sw.md" >}})
+* [Getting started with Verilator]({{< relref "getting_started_verilator.md" >}})
+* [Getting started on FPGAs]({{< relref "getting_started_fpga.md" >}})
 
 ## Understanding device software flow
 This section discusses the general software operating flow.
 
-Under the sw directory, there are numerous sub-directories each containing code for [different purposes](directory_structure.md#directory-structure-underneath-`sw`).
+Under the sw directory, there are numerous sub-directories each containing code for different purposes.
 In general however, software execution can be divided into two execution stages - ROM and embedded memory (currently emulated embedded flash).
 
 The ROM stage software, built from `sw/boot_ROM` is always run first on all platforms (DV / Verilator / FPGA).
@@ -38,7 +42,7 @@
 ROM at the moment does not perform validation of the backdoor loaded code.
 
 On FPGA, we do not backdoor load the embedded memory.
-Instead, the ROM code proceeds through a code download process where a [host](../../host/spiflash/README.md) feeds an image frame by frame.
+Instead, the ROM code proceeds through a code download process where a [host]({{< relref "/sw/host/spiflash/README.md" >}}) feeds an image frame by frame.
 The ROM code then integrity checks each received image and programs it into the embedded memory.
 At the conclusion of this process, the ROM then jumps to the newly downloaded executable code.
 Again, just like the DV / Verilator case, there is currently no additional validation of the downloaded code.
diff --git a/doc/ug/getting_started_design.md b/doc/ug/getting_started_design.md
index e2cadde..70d2732 100644
--- a/doc/ug/getting_started_design.md
+++ b/doc/ug/getting_started_design.md
@@ -1,7 +1,11 @@
+---
+title: "Getting Started with an OpenTitan Hardware Design"
+---
+
 # Getting Started with an OpenTitan Hardware Design
 
 This document aims to clarify how to get started with a hardware design within the OpenTitan project.
-Design in this context nominally refers to a new [Comportable Peripheral](../rm/comportability_specification.md) but can include high level constructs like device reset strategy, etc.
+Design in this context nominally refers to a new [Comportable Peripheral]({{< relref "../rm/comportability_specification" >}}) but can include high level constructs like device reset strategy, etc.
 This is primarily aimed at creating a new design from scratch, but has sections on how to contribute to an existing design.
 It is targeted to RTL designers to give clarity on the typical process for declaring features, finding others to review, etc.
 We aim for a healthy balance between adding clarity while not over prescribing rules and process.
@@ -11,7 +15,7 @@
 
 ## Stages of a Design
 
-The life stages of a design within the OpenTitan are described in the [Hardware Development Stages](hw_stages.md) document.
+The life stages of a design within the OpenTitan are described in the [Hardware Development Stages]({{< relref "hw_stages.md" >}}) document.
 This separates the life of the design into three broad stages: Specification, In Development, and Signed off.
 This document attempts to give guidance on how to get going with the first stage and have a smooth transition into the Development stage.
 They are not hard and fast rules but methods we have seen work well in the project.
@@ -25,7 +29,7 @@
 This proposal should be in **Google Doc** medium for agile review capability.
 Ideally this proposal document would be created in the Team Drive, but if the author does not have access to the team drive, they can share a privately-created document.
 
-Design proposals should follow the recommended [RFC (Request for Comment)](../project/rfc_process.md) process, which handles all such proposals.
+Design proposals should follow the recommended [RFC (Request for Comment)]({{< relref "../project/rfc_process.md" >}}) process, which handles all such proposals.
 If the RFC may contain certification sensitive material (guidance to be shared), it should be first sent to
 [cert-sensitive-priv@lowrisc.org](mailto:cert-sensitive-priv@lowrisc.org)
 for clearance before sharing more widely.
@@ -37,7 +41,7 @@
 ## Detailed Specification
 
 Once past initial review of the feature set and high level description, as well as potential security review, the full detailed specification should be completed, still in Google Doc form.
-The content, form, and format are discussed in the [design methodology](design.md) and [documentation methodology](../rm/markdown_usage_style.md) guides.
+The content, form, and format are discussed in the [design methodology]({{< relref "design.md" >}}) and [documentation methodology]({{< relref "../rm/markdown_usage_style.md" >}}) guides.
 The outcome of this process should be a specification that is ready for further detailed review by other project members.
 The content and the status of the proposal can be shared with the team.
 
@@ -59,12 +63,12 @@
 One recommended method is as follows:
 * Start with a skeleton that includes a complete or near-complete definition of the register content in .hjson format
 * Combine with a basic markdown including that file
-* Combine with an IP-top-level verilog module that instantiates the auto-generated register model (see the [register tool documentation](../rm/register_tool.md)), includes all of the known IP-level IO, clocking and reset.
+* Combine with an IP-top-level verilog module that instantiates the auto-generated register model (see the [register tool documentation]({{< relref "../rm/register_tool" >}})), includes all of the known IP-level IO, clocking and reset.
 
 This is not mandatory but allows the basics to come in first with one review, and the full custom details over time.
 Regardless the first check-ins of course should be compilable, syntax error free,
 [coding style guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
-friendly, [Comportability](../rm/comportability_specification.md) equivalent, etc., as indicated by the [design methodology user guide](design.md).
+friendly, [Comportability]({{< relref "../rm/comportability_specification" >}}) equivalent, etc., as indicated by the [design methodology user guide]({{< relref "design.md" >}}).
 
 A good example of an initial skeleton design can be seen in
 [Pull Request #166](https://github.com/lowRISC/opentitan/pull/166)
@@ -78,7 +82,7 @@
 
 ## Full Design
 
-As the design develops within the OpenTitan repo, it transitions into "D0", "D1", etc., [design stages](hw_stages.md) and will be eventually plugged into the top level.
+As the design develops within the OpenTitan repo, it transitions into "D0", "D1", etc., [design stages]({{< relref "hw_stages.md" >}}) and will be eventually plugged into the top level.
 Following the recommended best practices for digestible pull requests suggests that continuing to stage the design from the initial skeleton into the full featured design is a good way to make steady progress without over-burdening the reviewers.
 
 ## Top Level Inclusion
diff --git a/doc/ug/getting_started_fpga.md b/doc/ug/getting_started_fpga.md
index 14ff727..d0c8100 100644
--- a/doc/ug/getting_started_fpga.md
+++ b/doc/ug/getting_started_fpga.md
@@ -1,3 +1,7 @@
+---
+title: "Getting started on FPGAs"
+---
+
 # Getting started on FPGAs
 
 Do you want to try out the lowRISC chip designs, but don't have a couple thousand or million dollars ready for an ASIC tapeout?
@@ -13,9 +17,9 @@
 Depending on the design/target combination that you want to synthesize you will need different tools and boards.
 Refer to the design documentation for information what exactly is needed.
 
-* [Obtain an FPGA board](fpga_boards.html)
+* [Obtain an FPGA board]({{< relref "fpga_boards.md" >}})
 
-Follow the install instructions to [prepare the system](install_instructions.md#system-preparation) and to install the [software development tools](install_instructions.md#software-development) and [Xilinx Vivado](install_instructions.md#xilinx-vivado).
+Follow the install instructions to [prepare the system]({{< relref "install_instructions#system-preparation" >}}) and to install the [software development tools]({{< relref "install_instructions#software-development" >}}) and [Xilinx Vivado]({{< relref "install_instructions#xilinx-vivado" >}}).
 
 ## Create an FPGA bitstream
 
@@ -23,7 +27,7 @@
 
 The FPGA build will pull in a program to act as the boot ROM.
 This is pulled in from the `sw/device/boot_rom` directory (see the `parameters:` section of the `hw/top_earlgrey/top_earlgrey_nexysvideo.core` file).
-At the moment there is no check that the `rom.vmem` file is up to date, so it is best to follow the instructions to [Build software](getting_started_sw.md) and understand the FPGA's overall software flow.
+At the moment there is no check that the `rom.vmem` file is up to date, so it is best to follow the instructions to [Build software]({{< relref "getting_started_sw.md" >}}) and understand the FPGA's overall software flow.
 
 In the following example we synthesize the Earl Grey design for the Nexys Video board using Xilinx Vivado 2018.3.
 
@@ -34,7 +38,7 @@
 ```
 
 The resulting bitstream is located at `build/lowrisc_systems_top_earlgrey_nexysvideo_0.1/synth-vivado/lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit`.
-See the [reference manual](ref_manual_fpga.md) for more information.
+See the [reference manual]({{< relref "ref_manual_fpga.md" >}}) for more information.
 
 
 ## Flash the bitstream onto the FPGA
@@ -68,7 +72,7 @@
 * In the navigation on the left, click on *PROGRAM AND DEBUG* > *Open Hardware Manager* > *Open Target* > *Auto Connect*.
 * Vivado now enumerates all boards and connects to it. (Note on Vivado 2018.1 you may get an error the first time and have to do auto connect twice.)
 * Click on *Program Device* in the menu on the left (or at the top of the screen).
-* A dialog titled *Program Device" pops up. Select the file *lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit* as *Bitstream file*, and leave the *Debug probes file* empty.
+* A dialog titled *Program Device* pops up. Select the file `lowrisc_systems_top_earlgrey_nexysvideo_0.1.bit` as *Bitstream file*, and leave the *Debug probes file* empty.
 * Click on *Program* to flash the FPGA with the bitstream.
 * The FPGA is ready as soon as the programming finishes.
 
@@ -76,7 +80,7 @@
 ## Testing the demo design
 
 The `hello_world` demo software shows off some capabilities of the design.
-In order to load `hello_world` into the FPGA, both the binary and the [loading tool](../../sw/host/spiflash) must be compiled.
+In order to load `hello_world` into the FPGA, both the binary and the [loading tool]({{< relref "/sw/host/spiflash/README.md" >}}) must be compiled.
 Please follow the steps below.
 
 ```console
diff --git a/doc/ug/getting_started_sw.md b/doc/ug/getting_started_sw.md
index d4b0c7c..fe3a52b 100644
--- a/doc/ug/getting_started_sw.md
+++ b/doc/ug/getting_started_sw.md
@@ -2,7 +2,7 @@
 
 ## Prerequisites
 
-_Make sure you followed the install instructions to [prepare the system](install_instructions.html#system-preparation) and install the [compiler toolchain](install_instructions.html#compiler-toolchain)._
+_Make sure you followed the install instructions to [prepare the system]({{< relref "install_instructions#system-preparation" >}}) and install the [compiler toolchain]({{< relref "install_instructions#compiler-toolchain" >}})._
 
 ## Building software
 
@@ -18,4 +18,4 @@
 * `.dis`: the disassembled program
 * `.vmem`: a Verilog memory file which can be read by `$readmemh()` in Verilog code
 
-Please see [SW build flow](../../sw/doc/sw_build_flow.md) for more details.
+Please see [SW build flow]("/sw/doc/sw_build_flow.md") for more details.
diff --git a/doc/ug/getting_started_verilator.md b/doc/ug/getting_started_verilator.md
index ce3915d..740505d 100644
--- a/doc/ug/getting_started_verilator.md
+++ b/doc/ug/getting_started_verilator.md
@@ -7,7 +7,7 @@
 
 ## Prerequisites
 
-_Make sure you followed the install instructions to [prepare the system](install_instructions.md#system-preparation) and to install the [software development tools](install_instructions.md#software-development) and [Verilator](install_instructions.md#verilator)._
+_Make sure you followed the install instructions to [prepare the system]({{< relref "install_instructions#system-preparation" >}}) and to install the [software development tools]({{< relref "doc/ug/install_instructions#software-development" >}}) and [Verilator]({{< relref "install_instructions#verilator" >}})._
 
 ## Simulating a design with Verilator
 
@@ -26,7 +26,7 @@
 For that purpose compile the demo program with "simulation" settings, which adjusts the frequencies to better match the simulation speed.
 In the instructions below, `SW_DIR` is a requirement argument, while `SW_BUILD_DIR` is not a required argument.
 If `SW_BUILD_DIR` argument is not supplied, the default location of the of output files are in `SW_DIR`
-Please see [SW build flow](../../sw/doc/sw_build_flow.md) for more details.
+Please see [SW build flow]({{< relref "sw/device/doc/sw_build_flow" >}}) for more details.
 
 ```console
 $ cd $REPO_TOP
diff --git a/doc/ug/index.md b/doc/ug/index.md
deleted file mode 100644
index abc5dec..0000000
--- a/doc/ug/index.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# User Guides
-
-* Getting Started
-  * [Getting started](getting_started.md)
-  * [Quickstart](quickstart.md)
-  * [Notes on using GitHub and local git](github_notes.md)
-  * [Build software](getting_started_sw.md)
-  * [Getting started with Verilator](getting_started_verilator.md)
-  * [Getting started on FPGAs](getting_started_fpga.md)
-    * [Obtaining an FPGA board](fpga_boards.md)
-    * [Installing Xilinx Vivado](install_instructions.md#xilinx-vivado)
-  * [Getting started with a design](getting_started_design.md)
-  * *Getting started with verification* (TODO)
-* [Work with hardware code in external repositories](vendor_hw.md)
-* [Hardware Development Stages](hw_stages.md)
-* [Design Methodology](design.md)
-  * Language and Tool Selection
-  * Comportability and the Importance of Architectural Conformity
-  * Defining Design Complete: stages and tracking
-  * Documentation
-  * Usage of Register Tool
-  * Linting Methodology
-  * Assertions Methodology
-  * CDC Methodology
-  * DFT
-  * Generated Code
-* *Verification Methodology* (TODO)
-  * Verification strategy overview
-  * How do we define Verification completion
-    * Current verification status of IP and definition of milestones
-  * Tools
-  * Test planning
-  * Progress and tracking
-  * Code coverage output
-    * How do we report status
-  * Overview of in-tree helper classes, test benches, etc.
-* *Validation Methodology* (TODO)
-  * How to download bit stream
-  * What tests exist today
-  * How to run tests
-  * How does this differ from verification
-  * How to add tests
-* [List of Top-Level Designs](system_list.md)
diff --git a/doc/ug/img/install_vivado/step1.png b/doc/ug/install_instructions/img/install_vivado/step1.png
similarity index 100%
rename from doc/ug/img/install_vivado/step1.png
rename to doc/ug/install_instructions/img/install_vivado/step1.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step2.png b/doc/ug/install_instructions/img/install_vivado/step2.png
similarity index 100%
rename from doc/ug/img/install_vivado/step2.png
rename to doc/ug/install_instructions/img/install_vivado/step2.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step3.png b/doc/ug/install_instructions/img/install_vivado/step3.png
similarity index 100%
rename from doc/ug/img/install_vivado/step3.png
rename to doc/ug/install_instructions/img/install_vivado/step3.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step4.png b/doc/ug/install_instructions/img/install_vivado/step4.png
similarity index 100%
rename from doc/ug/img/install_vivado/step4.png
rename to doc/ug/install_instructions/img/install_vivado/step4.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step5.png b/doc/ug/install_instructions/img/install_vivado/step5.png
similarity index 100%
rename from doc/ug/img/install_vivado/step5.png
rename to doc/ug/install_instructions/img/install_vivado/step5.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step6.png b/doc/ug/install_instructions/img/install_vivado/step6.png
similarity index 100%
rename from doc/ug/img/install_vivado/step6.png
rename to doc/ug/install_instructions/img/install_vivado/step6.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step7.png b/doc/ug/install_instructions/img/install_vivado/step7.png
similarity index 100%
rename from doc/ug/img/install_vivado/step7.png
rename to doc/ug/install_instructions/img/install_vivado/step7.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/step8.png b/doc/ug/install_instructions/img/install_vivado/step8.png
similarity index 100%
rename from doc/ug/img/install_vivado/step8.png
rename to doc/ug/install_instructions/img/install_vivado/step8.png
Binary files differ
diff --git a/doc/ug/img/install_vivado/vivado_download.png b/doc/ug/install_instructions/img/install_vivado/vivado_download.png
similarity index 100%
rename from doc/ug/img/install_vivado/vivado_download.png
rename to doc/ug/install_instructions/img/install_vivado/vivado_download.png
Binary files differ
diff --git a/doc/ug/install_instructions.md b/doc/ug/install_instructions/index.md
similarity index 99%
rename from doc/ug/install_instructions.md
rename to doc/ug/install_instructions/index.md
index 128d26f..d219735 100644
--- a/doc/ug/install_instructions.md
+++ b/doc/ug/install_instructions/index.md
@@ -1,6 +1,5 @@
 # Install Build Requirements
 
-{{% toc 3 }}
 
 ## System requirements
 
diff --git a/doc/ug/quickstart.md b/doc/ug/quickstart.md
index efdd3bf..d838ea7 100644
--- a/doc/ug/quickstart.md
+++ b/doc/ug/quickstart.md
@@ -1,10 +1,14 @@
+---
+title: "Quickstart"
+---
+
 # Quickstart
 
 The environment variable `$REPO_TOP` is the top-level of the git source tree.
 
 ## Simulation with Verilator
 
-_Make sure you followed the install instructions to [prepare the system](install_instructions.md#system-preparation) and to install the [software development tools](install_instructions.md#software-development) and [Verilator](install_instructions.md#verilator)._
+_Make sure you followed the install instructions to [prepare the system]({{< relref "install_instructions#system-preparation" >}}) and to install the [software development tools]({{< relref "install_instructions#software-development" >}}) and [Verilator]({{< relref "install_instructions#verilator" >}})._
 
 Build the simulator and the software and then run the simulation
 
@@ -17,11 +21,11 @@
 $ --flashinit=sw/device/examples/hello_world/sw.vmem
 ```
 
-See the [Getting Started with Verilator Guide](getting_started_verilator.md) for more information.
+See the [Getting Started with Verilator Guide]({{< relref "getting_started_verilator.md" >}}) for more information.
 
 ## Running on an FPGA
 
-_Make sure you followed the install instructions to [prepare the system](install_instructions.md#system-preparation) and to install the [software development tools](install_instructions.md#software-development)._
+_Make sure you followed the install instructions to [prepare the system]({{< relref "install_instructions#system-preparation" >}}) and to install the [software development tools]({{< relref "install_instructions#software-development" >}})._
 
 Do you want to try out the design without installing EDA tools and waiting for a long build?
 Then you have come to the right place!
@@ -41,4 +45,4 @@
 4. TODO: include steps on how to build software, spiflash it, and expectations on what should be seen on the board when the software works.
 
 
-See the [Getting Started on FPGAs Guide](getting_started_fpga.md) for full instructions how to build your own bitstream.
+See the [Getting Started on FPGAs Guide]({{< relref "getting_started_fpga.md" >}}) for full instructions how to build your own bitstream.
diff --git a/hw/_index.md b/hw/_index.md
new file mode 100644
index 0000000..846c4e4
--- /dev/null
+++ b/hw/_index.md
@@ -0,0 +1,33 @@
+# Hardware Specifications
+
+This is the landing spot for all hardware specifications within the OpenTitan project.
+This includes: top level specification(s); processor core(s) specifications; and [Comportable IP]({{< relref "doc/rm/comportability_specification" >}}) specifications.
+
+## Available Top Level Specifications
+
+* [`top_earlgrey` design specification]({{< relref "hw/top_earlgrey/doc" >}})
+
+## Available Processor Core Specifications
+
+* [`core_ibex` user manual](https://ibex-core.readthedocs.io/en/latest)
+
+## Available Comportable IP Block Design Specifications and Verification Plans
+
+| Module | Design Spec | DV Plan |
+|--------|-------------|---------|
+| aes            | [design spec]({{< relref "hw/ip/aes/doc" >}})
+| alert\_handler | [design spec]({{< relref "hw/ip/alert_handler/doc" >}})
+| entropy\_src   |
+| flash\_ctrl    | [design spec]({{< relref "hw/ip/flash_ctrl/doc" >}})
+| gpio           | [design spec]({{< relref "hw/ip/gpio/doc" >}}) | [DV plan]({{< relref "hw/ip/gpio/doc/dv_plan" >}})
+| hmac           | [design spec]({{< relref "hw/ip/hmac/doc" >}}) | [DV plan]({{< relref "hw/ip/hmac/doc/dv_plan" >}})
+| i2c            | | [DV plan]({{< relref "hw/ip/i2c/doc/dv_plan" >}})
+| padctrl        |[design spec]({{< relref "hw/ip/padctrl/doc" >}})
+| pinmux         |[design spec]({{< relref "hw/ip/pinmux/doc" >}})
+| rv\_core\_ibex |[design spec]({{< relref "hw/ip/rv_core_ibex/doc" >}})
+| rv\_dm         |[design spec]({{< relref "hw/ip/rv_dm/doc" >}})
+| rv\_plic       |[design spec]({{< relref "hw/ip/rv_plic/doc" >}})
+| rv\_timer      |[design spec]({{< relref "hw/ip/rv_timer/doc" >}}) | [DV plan]({{< relref "hw/ip/rv_timer/doc/dv_plan" >}})
+| spi\_device    |[design spec]({{< relref "hw/ip/spi_device/doc" >}})
+| tlul           |[design spec]({{< relref "hw/ip/tlul/doc" >}})
+| uart           |[design spec]({{< relref "hw/ip/uart/doc" >}}) | [DV plan]({{< relref "hw/ip/uart/doc/dv_plan" >}})
diff --git a/hw/dv/doc/dv_plan_template.md b/hw/dv/doc/dv_plan_template.md
index 8ae9e81..fa0a2eb 100644
--- a/hw/dv/doc/dv_plan_template.md
+++ b/hw/dv/doc/dv_plan_template.md
@@ -7,11 +7,10 @@
 diagram section below. Please update / modify / remove sections below as
 applicable. Once done, remove this comment before making a PR. -->
 
-{{% lowrisc-doc-hdr FOO DV Plan }}
-<!-- TODO: uncomment the line below after adding the testplan -->
-<!-- {{% import_testplan ../data/foo_testplan.hjson }} -->
+---
+title: "FOO DV Plan"
+---
 
-{{% toc 4 }}
 
 ## Goals
 * **DV**
@@ -21,15 +20,15 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
 For detailed information on FOO design features, please see the [FOO design specification](foo.md).
 
 ## Testbench architecture
-FOO testbench has been constructed based on the [CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+FOO testbench has been constructed based on the [CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -37,17 +36,17 @@
 ### Top level testbench
 Top level testbench is located at `hw/ip/foo/dv/tb/tb.sv`. It instantiates the FOO DUT module `hw/ip/foo/rtl/foo.sv`.
 In addition, it instantiates the following interfaces and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 * FOO IOs
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
-* Alerts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
-* Devmode ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
+* Alerts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
+* Devmode ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Compile-time configurations
 [list compile time configurations, if any and what are they used for]
@@ -59,7 +58,7 @@
 [list a few parameters, types & methods; no need to mention all]
 ```
 ### TL_agent
-FOO testbench instantiates (already handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md)
+FOO testbench instantiates (already handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into FOO device.
 
@@ -70,7 +69,7 @@
 [Describe here or add link to its README]
 
 ### RAL
-The FOO RAL model is constructed using the [regtool.py script](../../../../util/reggen/README.md) and is placed at `env/foo_reg_block.sv`.
+The FOO RAL model is constructed using the [regtool.py script]({{< relref "util/reggen/README.md" >}}) and is placed at `env/foo_reg_block.sv`.
 
 ### Reference models
 [Describe reference models in use if applicable, example: SHA256/HMAC]
@@ -100,13 +99,13 @@
 <!-- explain inputs monitored, flow of data and outputs checked -->
 
 #### Assertions
-* TLUL assertions: The `tb/foo_bind.sv` binds the `tlul_assert` [assertions](../../tlul/doc/TlulProtocolChecker.md) to the IP to ensure TileLink interface protocol compliance.
+* TLUL assertions: The `tb/foo_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance.
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset.
 * assert prop 1:
 * assert prop 2:
 
 ## Building and running tests
-We are using our in-house developed [regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
 Here's how to run a basic sanity test:
 ```console
@@ -116,4 +115,4 @@
 
 ## Testplan
 <!-- TODO: uncomment the line below after adding the testplan -->
-<!-- {{% insert_testplan x }} -->
+{{</* testplan "../data/foo_testplan.hjson" */>}}
diff --git a/hw/dv/sv/cip_lib/env.svg b/hw/dv/sv/cip_lib/doc/env.svg
similarity index 100%
rename from hw/dv/sv/cip_lib/env.svg
rename to hw/dv/sv/cip_lib/doc/env.svg
diff --git a/hw/dv/sv/cip_lib/README.md b/hw/dv/sv/cip_lib/doc/index.md
similarity index 98%
rename from hw/dv/sv/cip_lib/README.md
rename to hw/dv/sv/cip_lib/doc/index.md
index 287f40a1..ab0dff4 100644
--- a/hw/dv/sv/cip_lib/README.md
+++ b/hw/dv/sv/cip_lib/doc/index.md
@@ -1,10 +1,9 @@
 # Comportable IP Testbench Architecture
 
-{{% toc 4 }}
 
 ## Overview
 Going along the lines of what it takes to design an IP that adheres to the
-[Comportability Specifications](../../../../doc/rm/comportability_specification.md),
+[Comportability Specifications](/doc/rm/comportability_specification),
 we attempt to standardize the DV methodology for developing the IP level
 testbench environment as well by following the same approach. This document describes
 the Comportable IP (CIP) library, which is a complete UVM enviromnent framework that
diff --git a/hw/dv/sv/cip_lib/tb.svg b/hw/dv/sv/cip_lib/doc/tb.svg
similarity index 100%
rename from hw/dv/sv/cip_lib/tb.svg
rename to hw/dv/sv/cip_lib/doc/tb.svg
diff --git a/hw/dv/sv/common_ifs/README.md b/hw/dv/sv/common_ifs/README.md
index ba63dc5..15a595b 100644
--- a/hw/dv/sv/common_ifs/README.md
+++ b/hw/dv/sv/common_ifs/README.md
@@ -1,6 +1,5 @@
 # Common interfaces
 
-{{% toc 4 }}
 
 ## Overview
 In this directory, we provide commonly used interfaces to construct testbenches for DV.
diff --git a/hw/dv/sv/csr_utils/README.md b/hw/dv/sv/csr_utils/README.md
index a6193f6..66b84af 100644
--- a/hw/dv/sv/csr_utils/README.md
+++ b/hw/dv/sv/csr_utils/README.md
@@ -1,6 +1,5 @@
 # CSR utilities
 
-{{% toc 4 }}
 
 This csr_utils folder intends to implement CSR related methods and test sequences for DV
 to share across all testbenches.
@@ -117,6 +116,6 @@
   and is not intended to use externally
 
 ### CSR sequence framework
-The [cip_lib](../cip_lib/README.md) includes a virtual sequence named `cip_base_vseq`,
+The [cip_lib]({{< relref "hw/dv/sv/cip_lib/doc" >}}) includes a virtual sequence named `cip_base_vseq`,
 that provides a common framework for all testbenchs to run these CSR test sequences and
 add exclusions.
diff --git a/hw/dv/sv/dv_lib/README.md b/hw/dv/sv/dv_lib/README.md
index 15f983c..a67a839 100644
--- a/hw/dv/sv/dv_lib/README.md
+++ b/hw/dv/sv/dv_lib/README.md
@@ -1,6 +1,8 @@
-# DV library classes
+---
+title: "DV Library Classes"
+---
 
-{{% toc 4 }}
+# DV library classes
 
 ## Overview
 The DV library classes form the base layer / framework for constructing UVM
@@ -16,7 +18,7 @@
 layer), UVM agent, and UVM environment extensions.
 
 ### UVM RAL extensions
-The RAL model generated using the [reggen](../../../util/reggen/README.md) tool
+The RAL model generated using the [reggen]({{< relref "util/reggen/README.md" >}}) tool
 extend from these classes. These themselves extend from the corresponding RAL
 classes provided in UVM.
 
diff --git a/hw/dv/sv/i2c_agent/README.md b/hw/dv/sv/i2c_agent/README.md
index c3f27ab..5da5bcf 100644
--- a/hw/dv/sv/i2c_agent/README.md
+++ b/hw/dv/sv/i2c_agent/README.md
@@ -1,5 +1,5 @@
-# I2C UVM Agent
-
-{{% toc 4 }}
+---
+title: "I2C DV UVM Agent"
+---
 
 I2C UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/jtag_agent/README.md b/hw/dv/sv/jtag_agent/README.md
index aacda54..92f17b6 100644
--- a/hw/dv/sv/jtag_agent/README.md
+++ b/hw/dv/sv/jtag_agent/README.md
@@ -1,5 +1,5 @@
-# JTAG UVM Agent
-
-{{% toc 4 }}
+---
+title: "JTAG DV UVM Agent"
+---
 
 JTAG UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/spi_agent/README.md b/hw/dv/sv/spi_agent/README.md
index 2850f37..30e7899 100644
--- a/hw/dv/sv/spi_agent/README.md
+++ b/hw/dv/sv/spi_agent/README.md
@@ -1,5 +1,5 @@
-# SPI UVM Agent
-
-{{% toc 4 }}
+---
+title: "SPI UVM Agent"
+---
 
 SPI UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/tl_agent/README.md b/hw/dv/sv/tl_agent/README.md
index 1ec0990..f3fb3ed 100644
--- a/hw/dv/sv/tl_agent/README.md
+++ b/hw/dv/sv/tl_agent/README.md
@@ -1,4 +1,3 @@
-# TileLink UVM Agent
-
-{{% toc 4 }}
-
+---
+title: "TileLink UVM Agent"
+---
diff --git a/hw/dv/tools/README.md b/hw/dv/tools/README.md
index 4b25797..3c0a5db 100644
--- a/hw/dv/tools/README.md
+++ b/hw/dv/tools/README.md
@@ -69,7 +69,7 @@
 #### RAL generation tool specific mk file
 This lists tools and options to generate the ral model by simply running the
 command `make ral` from the same directory as the Test Makefile. For generating
-the UVM REG based RAL model, we use the same [in-house tool](../../../doc/rm/register_tool.md) for autogenerating
+the UVM REG based RAL model, we use the same [in-house tool]({{< relref "doc/rm/register_tool" >}}) for autogenerating
 RTL with mako template.
 
 ### fusesoc.mk
diff --git a/hw/formal/README.md b/hw/formal/README.md
index 7751939..aa79572 100644
--- a/hw/formal/README.md
+++ b/hw/formal/README.md
@@ -1,8 +1,9 @@
-{{% lowrisc-doc-hdr OpenTitan Assertions }}
+---
+title: "OpenTitan Assertions"
+---
 
 # OpenTitan Assertions
 
-{{% toc 3 }}
 
 ## What Are Assertions?
 Assertions are statements about your design that are expected to be always
@@ -23,7 +24,7 @@
 also be added in a separate module, see for example
 [tlul_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/tlul/rtl/tlul_assert.sv)
 and its
-[documentation](../ip/tlul/doc/TlulProtocolChecker.md),
+[documentation]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}),
 which contains a generic protocol checker for the
 TileLink-UL standard.
 
@@ -91,7 +92,7 @@
 *   Also see
 [tlul_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/tlul/rtl/tlul_assert.sv)
 and its
-[documentation](../ip/tlul/doc/TlulProtocolChecker.md).
+[documentation]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}).
 
 ## Useful SVA System Functions
 Below table lists useful SVA (system verilog assertion) functions that can be
diff --git a/hw/index.md b/hw/index.md
deleted file mode 100644
index ad5c1c5..0000000
--- a/hw/index.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Hardware Specifications
-
-This is the landing spot for all hardware specifications within the OpenTitan project.
-This includes: top level specification(s); processor core(s) specifications; and [Comportable IP](../doc/rm/comportability_specification.md) specifications.
-
-## Available Top Level Specifications
-
-* [`top_earlgrey` design specification](top_earlgrey/doc/top_earlgrey.md)
-
-## Available Processor Core Specifications
-
-* [`core_ibex` user manual](https://ibex-core.readthedocs.io/en/latest)
-
-## Available Comportable IP Block Design Specifications and Verification Plans
-
-{{% specboard ip }}
diff --git a/hw/ip/aes/doc/aes.md b/hw/ip/aes/doc/index.md
similarity index 96%
rename from hw/ip/aes/doc/aes.md
rename to hw/ip/aes/doc/index.md
index 4b1df15..333ef98 100644
--- a/hw/ip/aes/doc/aes.md
+++ b/hw/ip/aes/doc/index.md
@@ -1,20 +1,18 @@
-{{% lowrisc-doc-hdr AES HWIP Technical Specification }}
-{{% regfile ../data/aes.hjson}}
+---
+title: "AES HWIP Technical Specification"
+---
 
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the AES hardware IP functionality.
 [Advanced Encryption Standard (AES)](https://www.nist.gov/publications/advanced-encryption-standard-aes) is the primary symmetric encryption and decryption mechanism used in OpenTitan protocols.
 The AES unit is a cryptographic accelerator that accepts requests from the processor to encrypt or decrypt 16B blocks of data.
-It is attached to the chip interconnect bus as a peripheral module and conforms to the [Comportable guideline for peripheral functionality.](../../../../doc/rm/comportability_specification.md)
+It is attached to the chip interconnect bus as a peripheral module and conforms to the [Comportable guideline for peripheral functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader top level system.
 
 
-{{% toc 3 }}
-
-
-{{% section2 Features }}
+## Features
 
 The AES unit supports the following features:
 
@@ -31,7 +29,7 @@
 Cipher modes other than ECB are beyond this version of the AES unit but might be supported in future versions.
 
 
-{{% section2 Description }}
+## Description
 
 The AES unit is a cryptographic accelerator that accepts requests from the processor to encrypt or decrypt 16B blocks of data.
 It supports AES-128/192/256 in Electronic Codebook (ECB) mode.
@@ -43,7 +41,7 @@
 Future versions of the AES unit might include a separate interface through which a possible system key manager can update the initial key without exposing the key to the processor or other hosts attached to the system bus interconnect.
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 The AES unit supports both encryption and decryption for AES-128/192/256 in ECB mode using a single, shared data path.
 That is, it can either do encryption or decryption but not both at the same time.
@@ -76,7 +74,7 @@
 It only continues once the previous output data has been read and the corresponding registers can be safely overwritten.
 
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 This AES unit targets medium performance (\~1 cycle per round).
 For reasons of security, all data in OpenTitan is considered to be on chip.
@@ -173,7 +171,7 @@
 
 The timing diagram below visualizes this process.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     {    name: 'clk',         wave: 'p........|.......'},
@@ -194,7 +192,7 @@
     ]
   ]
 }
-```
+{{< /wavejson >}}
 
 The AES unit is configured to do decryption (`Config mode` = DECRYPT).
 Once the new key has been provided via the control and status registers (top), this new key is loaded into the Full Key register (`key_full` = K0-3) and the KEM starts performing encryption (`KEM mode`=ENCRYPT).
@@ -215,12 +213,12 @@
 Typically, systems requiring security above AES-128 go directly for AES-256.
 
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg Name }}
+{{< hwcfg "hw/ip/aes/data/aes.hjson" >}}
 
 
-{{% section2 Design Details }}
+## Design Details
 
 This section discusses different design details of the AES module.
 
@@ -241,21 +239,22 @@
 Future versions of this AES unit thus might employ different means at architectural, microarchitectural and physical levels to reduce information leakage (e.g. power and electromagnetic) and aggravate potential side-channel attacks.
 
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
 This section discusses how software can interface with the AES unit.
 
 
-{{% section2 Initialization }}
+## Initialization
 
 The initialize the AES unit, software must write the initial key to the Initial Key register.
 Independent of the selected key length, software must always write all 8 32-bit registers.
 For AES-128 and AES-192, the actual initial key used for encryption is formed by taking the values in Initial Key register 0-3 and 0-5, respectively.
 
 
-{{% section2 Block Operation }}
+## Block Operation
 
 For block operation, software must initialize the AES unit as described in the previous section and then:
+
 1. Make sure that the AES unit:
    1. Automatically starts encryption/decryption when new input data indicated by the MANUAL_START_TRIGGER bit in the Control register being 1'b0.
    2. Does not overwrite previous output data that has not been read by the processor indicated by the FORCE_DATA_OVERWRITE bit in the Control register being 1'b0.
@@ -264,33 +263,37 @@
 4. Write Input Data Block 1 to the Input Data register.
 
 Then for every Data Block I=0,..N-1, software must:
+
 1. Wait for the OUTPUT_VALID bit in the Status register to become 1'b1, i.e., wait for the AES unit to finish encryption/decryption of Block I.
 2. Read Output Data Block I from the Output Data register.
 3. Write Input Data Block I+1 into the Input Data register.
 
 For the last Data Block N, software must:
+
 1. Wait for the OUTPUT_VALID bit in the Status register to become 1'b1, i.e., wait for the AES unit to finish encryption/decryption of Block N.
 2. Read Output Data Block N from the Output Data register.
 
 
-{{% section2 De-Initialization}}
+## De-Initialization
 
 After finishing operation, software must:
+
 1. Disable the AES unit to no longer automatically start encryption/decryption by setting the MANUAL_START_TRIGGER bit in the Control register to 1'b1.
 2. Clear the configured initial key by overwriting the Initial Key register.
 3. Clear the previous input data by overwriting the Input Data register.
 4. Clear the internal key registers and the Output Data register by setting the KEY_CLEAR and DATA_OUT_CLEAR bits in the TRIGGER register to 1'b1.
 
 
-{{% section2 Register Table }}
+## Register Table
 
 The AES unit uses 8 and 4 separate write-only registers for the initial key and input data, as well as 4 separate read-only registers for the output data.
 Compared to first-in, first-out (FIFO) interfaces, having separate registers has a couple of advantages:
+
 - Supported out-of-the-box by the register tool (the FIFO would have to be implemented separately).
 - Usability: critical corner cases where software updates input data or the key partially only are easier to avoid using separate registers and the `hwqe`-signals provided by the Register Tool.
 - Easier interaction with DMA engines
 Also, using a FIFO interface for something that is not actually FIFO (internally, 16B of input/output data are consumed/produced at once) is less natural.
 
-For a detailed overview of the register tool, please refer to the [Register Tool documentation.](../../../../doc/rm/register_tool.md)
+For a detailed overview of the register tool, please refer to the [Register Tool documentation.]({{< relref "doc/rm/register_tool" >}})
 
-{{% registers x }}
+{{< registers "hw/ip/aes/data/aes.hjson" >}}
diff --git a/hw/ip/alert_handler/doc/alert_handler.md b/hw/ip/alert_handler/doc/index.md
similarity index 92%
rename from hw/ip/alert_handler/doc/alert_handler.md
rename to hw/ip/alert_handler/doc/index.md
index 3890998..1af9c4c 100644
--- a/hw/ip/alert_handler/doc/alert_handler.md
+++ b/hw/ip/alert_handler/doc/index.md
@@ -1,12 +1,13 @@
-{{% lowrisc-doc-hdr Alert Handler Technical Specification }}
-{{% regfile ../data/alert_handler.hjson }}
+---
+title: "Alert Handler Technical Specification"
+---
 
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the functionality of the alert handler mechanism. The
 alert handler is a module that is a peripheral on the chip interconnect bus,
-and thus follows the [Comportability Specification](../../../../doc/rm/comportability_specification.md).
+and thus follows the [Comportability Specification]({{< relref "doc/rm/comportability_specification" >}}).
 It gathers alerts - defined as interrupt-type signals from other peripherals
 that are designated as a potential security threat - from throughout the design,
 and converts them to interrupts that the processor can handle. If the processor
@@ -14,10 +15,9 @@
 responses to handle the threat.
 
 
-{{% toc 4 }}
 
 
-{{% section2 Features }}
+## Features
 
 - Differentially-signaled, asynchronous alert inputs from NAlerts peripheral
 sources, where NAlerts is a function of the requirements of the peripherals
@@ -53,7 +53,7 @@
     - Ping response from a source has failed
 
 
-{{% section2 Description }}
+## Description
 
 The alert handler module manages incoming alerts from throughout the system,
 classifies them, sends interrupts, and escalates interrupts to hardware
@@ -94,10 +94,10 @@
 the details section that follows.
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 
-{{% section2 Parameters }}
+## Parameters
 
 The following table lists the main parameters used throughout the alert handler
 design. Note that the alert handler is generated based on the system
@@ -120,7 +120,7 @@
 ---------------|------------------|---------------
 `AsyncOn`      | `1'b0` (`1'b1`)  | 0: Synchronous, 1: Asynchronous, determines whether additional synchronizer flops and logic need to be instantiated.
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 The figure below shows a block diagram of the alert handler module, as well as
 a few examples of alert senders in other peripheral modules. In this diagram,
@@ -144,7 +144,7 @@
 On the escalation sender / receiver side, the differential signaling blocks
 employ a fully synchronous clocking scheme throughout.
 
-{{% section2 Signals }}
+## Signals
 
 The table below lists the alert handler module signals. The number of alert
 instances is parametric and hence alert and ping diff pairs are grouped together
@@ -182,7 +182,7 @@
 circumstances). It is recommended for the top level to store this information in
 an always-on location.
 
-{{% section2 Design Details }}
+## Design Details
 
 This section gives the full design details of the alert handler module and its
 submodules.
@@ -220,7 +220,7 @@
 
 The wave pattern below illustrates differential full handshake mechanism.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p...............' },
@@ -245,7 +245,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 The repeat pattern for this alert is such that as long as the alert is true,
 the handshake is re-initiated after waiting pausing for 2 cycles on the sender
@@ -285,7 +285,7 @@
 
 The following wave diagram illustrates a correct ping sequences:
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p..............' },
@@ -311,7 +311,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 In the unlikely case that a ping request collides with a native alert at the
 sender side, the native alert is held back until the ping handshake has been
@@ -344,7 +344,7 @@
 
 Some of these failure patterns are illustrated in the wave diagram below:
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p..............' },
@@ -367,7 +367,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 Note that if signal integrity failures occur during ping or alert handshaking,
 it is possible that the protocol state-machines lock up and the alert sender and
@@ -382,7 +382,7 @@
 boundaries, it may thus happen that a level change appears in staggered manner
 after resynchronization, as illustrated below:
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',   wave: 'p...........' },
@@ -401,7 +401,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 This behavior is permissible, but needs to be accounted for in the protocol
 logic. Further, the skew within the differential pair should be constrained to
@@ -419,7 +419,7 @@
 next ping occurrence based on past observations.
 
 The ping timer is implemented using an
-[LFSR-based PRNG of Galois type](../../prim/doc/prim_lfsr.md). In order
+[LFSR-based PRNG of Galois type]({{< relref "hw/ip/prim/doc/prim_lfsr.md" >}}). In order
 to increase the entropy of the pseudo random sequence, 1 random bit from the
 TRNG is XOR'ed into the LFSR state every time a new random number is drawn
 (which happens every few 10k cycles). The LFSR is 32bits wide, but only 24bits
@@ -445,7 +445,7 @@
 
 The timeout value is a function of the ratios between the alert handler clock
 and peripheral clocks present in the system, and can be programmed at startup
-time via the register !!PING_TIMEOUT_CYC. Note that this register is locked in
+time via the register {{< regref "PING_TIMEOUT_CYC" >}}. Note that this register is locked in
 together with the alert enable and disable configuration.
 
 The ping timer starts as soon as the initial configuration phase is over and the
@@ -520,22 +520,22 @@
 
     - Accumulation max value. This is the total number (sum of all alerts
       classified in this group) of alerts required to enter escalation phase
-      (see below). Example register is !!CLASSA_ACCUM_THRESH.
+      (see below). Example register is {{< regref "CLASSA_ACCUM_THRESH" >}}.
     - Current accumulation register. This clearable register indicates how many
       alerts have been accumulated to date. Software should clear before it
       reaches the accumulation setting to avoid escalation. Example register is
-      !!CLASSA_ACCUM_CNT.
+      {{< regref "CLASSA_ACCUM_CNT" >}}.
 
 2. The second way is an interrupt timeout counter which triggers escalation
    if an alert interrupt is not handled within the programmable timeout
    window. Once the counter hits the timeout threshold, the escalation
    protocol is triggered. The corresponding CSRs are:
 
-    - Interrupt timeout value in cycles !!CLASSA_TIMEOUT_CYC. The
+    - Interrupt timeout value in cycles {{< regref "CLASSA_TIMEOUT_CYC" >}}. The
       interrupt timeout is disabled if this is set to 0 (default).
-    - The current interrupt timeout value can be read via !!CLASSA_ESC_CNT
-      if !!CLASSA_STATE is in the `Timeout` state. Software should clear the
-      corresponding interrupt state bit !!INTR_STATE.CLASSA before the
+    - The current interrupt timeout value can be read via {{< regref "CLASSA_ESC_CNT" >}}
+      if {{< regref "CLASSA_STATE" >}} is in the `Timeout` state. Software should clear the
+      corresponding interrupt state bit {{< regref "INTR_STATE.CLASSA" >}} before the
       timeout expires to avoid escalation.
 
 Technically, the interrupt timeout feature (2. above) is implemented using the
@@ -558,19 +558,19 @@
 
 Each class can be programmed with its own escalation protocol. If one of the two
 mechanisms described above fires, a timer for that particular class is started.
-The timer can be programmed with up to 4 delays (e.g., !!CLASSA_PHASE0_CYC),
+The timer can be programmed with up to 4 delays (e.g., {{< regref "CLASSA_PHASE0_CYC" >}}),
 each representing a distinct escalation phase (0 - 3). Each of the four
 escalation severity outputs (0 - 3) are by default configured to be asserted
 during the corresponding phase, e.g., severity 0 in phase 0,  severity 1 in
 phase 1, etc. However, this mapping can be freely reassigned by modifying the
-corresponding enable/phase mappings (e.g., !!CLASSA_CTRL.E0_MAP for enable bit
+corresponding enable/phase mappings (e.g., {{< regref "CLASSA_CTRL.E0_MAP" >}} for enable bit
 0 of class A). This mapping will be locked in together with the alert enable
 configuration after initial configuration.
 
 SW can stop a triggered escalation protocol by clearing the corresponding
-escalation counter (e.g., !!CLASSA_ESC_CNT). Protection of this clearing is up
+escalation counter (e.g., {{< regref "CLASSA_ESC_CNT" >}}). Protection of this clearing is up
 to software, see the register control section that follows for
-!!CLASSA_CTRL.LOCK.
+{{< regref "CLASSA_CTRL.LOCK" >}}.
 
 It should be noted that each of the escalation phases have a duration of at
 least 1 clock cycle, even if the cycle count of a particular phase has been
@@ -581,7 +581,7 @@
 shown for class A, and the gathering and escalation configuration values are
 shown.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',                wave: 'p...................' },
@@ -619,7 +619,7 @@
     tick: 0,
     }
 }
-```
+{{< /wavejson >}}
 
 In this diagram, the first alert triggers an interrupt to class A. The
 assumption is that the processor is wedged or taken over, in which case it
@@ -635,7 +635,7 @@
 The next wave shows a case where an interrupt remains unhandled and hence the
 interrupt timeout counter triggers escalation.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',                   wave: 'p.....................' },
@@ -664,7 +664,7 @@
     tick: 0,
     }
 }
-```
+{{< /wavejson >}}
 
 ### Escalation Signaling
 
@@ -690,7 +690,7 @@
 long as the escalation signal is asserted. Any failure to respond correctly
 will trigger a `integ_fail_o` alert, as illustrated below:
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p..................' },
@@ -717,7 +717,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 Further, any differential signal mismatch on both the `esc_p/n` and `resp_p/n`
 lines will trigger an `integ_fail_o` alert. Mismatches on `resp_p/n` can be
@@ -732,7 +732,7 @@
 
 Some signal integrity failure cases are illustrated in the wave diagram below:
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p...........' },
@@ -759,7 +759,7 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 
 ### Ping Testing of the Escalation Signals
@@ -775,7 +775,7 @@
 Otherwise the LFSR timer will raise a "pingfail" alert after the programmable
 timeout is reached.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',        wave: 'p..............' },
@@ -803,17 +803,17 @@
     tick: 0,
   }
 }
-```
+{{< /wavejson >}}
 
 Note that the escalation signal always takes precedence, and the `ping_en_i`
 will just be acknowledged with `ping_ok_o` in case `esc_en_i` is already
 asserted. An ongoing ping sequence will be aborted immediately.
 
 
-{{% section1 Programmer's Guide }}
+# Programmer's Guide
 
 
-{{% section2 Power-up and Reset Considerations }}
+## Power-up and Reset Considerations
 
 False alerts during power-up and reset are not possible since the alerts are
 disabled by default, and need to be configured and locked in by the firmware.
@@ -822,7 +822,7 @@
 are locked in.
 
 
-{{% section2 Initialization }}
+## Initialization
 
 To initialize the block, software running at a high permission level (early in
 the security settings process) should do the following:
@@ -830,66 +830,66 @@
 1. foreach alert and each local alert:
 
     - Determine if alert is enabled (should only be false if alert is known to
-      be faulty). Set !!ALERT_EN.EN_A0 and !!LOC_ALERT_EN.EN_LA0 accordingly.
+      be faulty). Set {{< regref "ALERT_EN.EN_A0" >}} and {{< regref "LOC_ALERT_EN.EN_LA0" >}} accordingly.
 
     - Determine which class (A..D) the alert is associated with. Set
-      !!ALERT_CLASS.CLASS_A and !!LOC_ALERT_CLASS.CLASS_LA accordingly.
+      {{< regref "ALERT_CLASS.CLASS_A" >}} and {{< regref "LOC_ALERT_CLASS.CLASS_LA" >}} accordingly.
 
-2. Set the ping timeout value !!PING_TIMEOUT_CYC. This value is dependent on
+2. Set the ping timeout value {{< regref "PING_TIMEOUT_CYC" >}}. This value is dependent on
    the clock ratios present in the system.
 
 3. foreach class (A..D):
 
     - Determine whether to enable escalation mechanisms (accumulation /
-      interrupt timeout) for this particular class. Set !!CLASSA_CTRL.EN
+      interrupt timeout) for this particular class. Set {{< regref "CLASSA_CTRL.EN" >}}
       accordingly.
 
     - Determine if this class of alerts allows clearing of
-      escalation once it has begun. Set !!CLASSA_CTRL.LOCK to true if clearing
+      escalation once it has begun. Set {{< regref "CLASSA_CTRL.LOCK" >}} to true if clearing
       should be disabled. If true, once escalation protocol begins, it can not
       be stopped, the assumption being that it ends in a chip reset else it
       will be rendered useless thenceforth.
 
     - Determine the number of alerts required to be accumulated before
-      escalation protocol kicks in. Set !!CLASSA_ACCUM_THRESH accordingly.
+      escalation protocol kicks in. Set {{< regref "CLASSA_ACCUM_THRESH" >}} accordingly.
 
     - Determine whether the interrupt associated with that class needs a
-      timeout. If yes, set !!CLASSA_TIMEOUT_CYC to an appropriate value greater
+      timeout. If yes, set {{< regref "CLASSA_TIMEOUT_CYC" >}} to an appropriate value greater
       than zero (zero corresponds to an infinite timeout and disables the
       mechanism).
 
     - Foreach escalation phase (0..3):
         - Determine length of each escalation phase by setting
-          !!CLASSA_PHASE0_CYC accordingly
+          {{< regref "CLASSA_PHASE0_CYC" >}} accordingly
 
     - Foreach escalation signal (0..3):
         - Determine whether to enable the escalation signal, and set the
-          !!CLASSA_CTRL.E0_EN bit accordingly (default is enabled).
+          {{< regref "CLASSA_CTRL.E0_EN" >}} bit accordingly (default is enabled).
         - Determine the phase -> escalation mapping of this class and
-          program it via the !!CLASSA_CTRL.E0_MAP values if it needs to be
+          program it via the {{< regref "CLASSA_CTRL.E0_MAP" >}} values if it needs to be
           changed from the default mapping (0->0, 1->1, 2->2, 3->3).
 
 4. After initial configuration at startup, lock the alert enable and escalation
-config registers by writing 1 to !!REGEN. This protects the registers from being
+config registers by writing 1 to {{< regref "REGEN" >}}. This protects the registers from being
 altered later on, and activates the ping mechanism for the enabled alerts and
 escalation signals.
 
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 For every alert that is enabled, an interrupt will be triggered on class A, B,
 C, or D. To handle an interrupt of a particular class, software should execute
 the following steps:
 
 1. If needed, check the escalation state of this class by reading
-   !!CLASSA_STATE. This reveals whether escalation protocol has been triggered
+   {{< regref "CLASSA_STATE" >}}. This reveals whether escalation protocol has been triggered
    and in which escalation phase the class is. In case interrupt timeouts are
    enabled the class will be in timeout state unless escalation has already been
    triggered. The current interrupt or escalation cycle counter can be read via
-   !!CLASSA_ESC_CNT.
+   {{< regref "CLASSA_ESC_CNT" >}}.
 
 2. Since the interrupt does not indicate which alert triggered, SW must read the
-   cause registers !!LOC_ALERT_CAUSE and !!ALERT_CAUSE etc. The cause bits of
+   cause registers {{< regref "LOC_ALERT_CAUSE" >}} and {{< regref "ALERT_CAUSE" >}} etc. The cause bits of
    all alerts are concatenated and chunked into 32bit words. Hence the register
    file contains as many cause words as needed to cover all alerts present in
    the system. Each cause register contains a sticky bit that is set by the
@@ -904,17 +904,17 @@
 3. After the event is cleared (if needed or possible), software should handle
 the interrupt as follows:
 
-    - Resetting the accumulation register for the class by writing !!CLASSA_CLR.
+    - Resetting the accumulation register for the class by writing {{< regref "CLASSA_CLR" >}}.
       This also aborts escalation protocol if it has been triggered. If for some
       reason it is desired to never allow the accumulator or escalation to be
-      cleared, software can initialize the !!CLASSA_CLREN register to zero.
-      If !!CLASSA_CLREN is already false when an alert interrupt is detected
+      cleared, software can initialize the {{< regref "CLASSA_CLREN" >}} register to zero.
+      If {{< regref "CLASSA_CLREN" >}} is already false when an alert interrupt is detected
       (either due to software control or hardware trigger via
-      !!CLASSA_CTRL.LOCK), then the accumulation counter can not be cleared and
+      {{< regref "CLASSA_CTRL.LOCK" >}}), then the accumulation counter can not be cleared and
       this step has no effect.
 
     - After the accumulation counter is reset (if applicable), software should
-      clear the class A interrupt state bit !!INTR_STATE.CLASSA. Clearing the
+      clear the class A interrupt state bit {{< regref "INTR_STATE.CLASSA" >}}. Clearing the
       class A interrupt state bit also clears and stops the interrupt timeout
       counter (if enabled).
 
@@ -924,7 +924,7 @@
 not be triggered by this testing mechanism.
 
 
-{{% section1 Additional Notes}}
+# Additional Notes
 
 
 ### Timing Constraints
@@ -992,7 +992,7 @@
   positives which have to be handled in SW.
 
 
-{{% section1 Register Table }}
+# Register Table
 
 The below register description can be generated with the `reg_alert_handler.py`
 script. The reason for having yet another script for register generation is
@@ -1019,5 +1019,5 @@
 ```
 
 
-{{% registers x }}
+{{< registers "hw/ip/alert_handler/data/alert_handler.hjson" >}}
 
diff --git a/hw/ip/flash_ctrl/doc/flash_ctrl.md b/hw/ip/flash_ctrl/doc/index.md
similarity index 92%
rename from hw/ip/flash_ctrl/doc/flash_ctrl.md
rename to hw/ip/flash_ctrl/doc/index.md
index e150c74..1b873f3 100644
--- a/hw/ip/flash_ctrl/doc/flash_ctrl.md
+++ b/hw/ip/flash_ctrl/doc/index.md
@@ -1,7 +1,7 @@
-{{% lowrisc-doc-hdr Flash Controller HWIP Technical Specification }}
-{{% regfile ../data/flash_ctrl.hjson }}
-{{% toc 3 }}
-{{% section1 Overview }}
+---
+title: "Flash Controller HWIP Technical Specification"
+---
+# Overview
 
 This document specifies the general functionality of flash.
 As the final feature set will largely depend on how similar vendor flash IPs are, it is at the moment unclear where the open-source / proprietary boundaries should lie.
@@ -24,7 +24,7 @@
 
 ![Flash High Level Abstraction](flash_abstraction.svg)
 
-{{% section2 Flash Protocol Controller Features }}
+## Flash Protocol Controller Features
 
 *  Support controller initiated read, program and erase of flash.  Erase can be either of a page, or an entire bank.
 *  Parameterized support for number of flash banks (default to 2)
@@ -48,7 +48,7 @@
       *  Flash may contain additional pages outside of the data banks to hold manufacturing information (such as wafer location).
       *  Extra logic may not be required if flash information pages is treated as just a separate address.
 
-{{% section2 Flash Physical Controller Features }}
+## Flash Physical Controller Features
 
 As the flash physical controller is highly dependent on flash memory selected, the default flash physical controller simply emulates real flash behavior with on-chip memories.
 The goal of the emulated flash is to provide software developers with a reasonable representation of a well-behaving flash operating under nominal conditions.
@@ -82,7 +82,7 @@
    * As the physical controller represents the final connecting logic to the actual flash memory, additional security considerations may be required to ensure backdoor access paths do not exist.
 
 
-{{% section2 Flash Protocol Controller Description }}
+## Flash Protocol Controller Description
 
 The flash protocol controller uses a simple FIFO interface to communicate between the software and flash physical controller.
 There is a read fifo for read operations, and a program fifo for program operations.
@@ -100,7 +100,7 @@
 
 ![Flash Protocol Controller](flash_protocol_controller.svg)
 
-{{% section2 Flash Physical Controller Description }}
+## Flash Physical Controller Description
 
 As the physical controller is IP specific, this section will only try to describe the connecting protocol signals, its function, and the memory allocation of configuration registers.
 
@@ -124,12 +124,13 @@
 | init_busy_o     | Physical controller reset release initialization in progress
 | rd_data_o       | Controller read data, 1 flash word wide
 
-{{% section2 Host Read}}
+## Host Read
 
 Unlike controller initiated reads, host reads have separate rdy / done signals to ensure transactions can be properly pipelined.
 As host reads are usually tied to host execution upstream, additional latency can severely harm performance and is not desired.
 The protocol expected waveform is shown below.
-```wavejson
+
+{{< wavejson >}}
 {signal: [
   {name: 'clk_i',           wave: 'p..............'},
   {name: 'rst_ni',          wave: '0.1............'},
@@ -139,20 +140,20 @@
   {name: 'host_req_done_o', wave: '0...10..1110...'},
   {name: 'host_rdata_o',    wave: 'x...4x..444x...',data: ['Dat0', 'Dat1', 'Dat2', 'Dat3']},
 ]}
-```
+{{< /wavejson >}}
 
 The `host_req_done_o` is always single cycle pulsed and upstream logic is expected to always accept and correctly handle the return.
 The same cycle the return data is posted a new command / address can be accepted.
 While the example shows flash reads completing in back to back cycles, this is typically not the case.
 
-{{% section2 Controller Read}}
+## Controller Read
 
 Unlike host reads, controller reads are not as performance critical and do not have command / data pipeline requirements.
 Instead, the protocol controller will hold the read request and address lines until the done is seen.
 Once the done is seen, the controller then transitions to the next read operation.
 See expected waveform below.
 
-```wavejson
+{{< wavejson >}}
 {signal: [
   {name: 'clk_i',        wave: 'p..............'},
   {name: 'rst_ni',       wave: '0.1............'},
@@ -162,14 +163,14 @@
   {name: 'rd_done_o',    wave: '0....10.10...10'},
   {name: 'rdata_o',      wave: 'x....4x.4x...4x', data: ['Dat0', 'Dat1', 'Dat2']},
 ]}
-```
+{{< /wavejson >}}
 
-{{% section2 Controller Program}}
+## Controller Program
 
 Program behavior is similar to reads.
 The protocol controller will hold the request, address and data lines until the programming is complete.
 
-```wavejson
+{{< wavejson >}}
 {signal: [
   {name: 'clk_i',        wave: 'p..............'},
   {name: 'rst_ni',       wave: '0.1............'},
@@ -179,18 +180,18 @@
   {name: 'prog_data_i',  wave: 'x..4..4..x.4..x', data: ['Dat0', 'Dat1', 'Dat2']},
   {name: 'prog_done_o',  wave: '0....10.10...10'},
 ]}
-```
+{{< /wavejson >}}
 
-{{% section1 Programmer's Guide}}
+# Programmer's Guide
 
-{{% section2 Issuing a Controller Read}}
+## Issuing a Controller Read
 
 To issue a flash read, the programmer must
 *  Specify the address of the first flash word to read
 *  Specify the number of total flash words to read, beginning at the supplied address
 *  Specify the operation to be 'READ' type
 *  Set the 'START' bit for the operation to begin
-The above fields can be set in the !!CONTROL and !!ADDR registers.
+The above fields can be set in the {{< regref "CONTROL" >}} and {{< regref "ADDR" >}} registers.
 See code below for example
 
 ```
@@ -212,19 +213,19 @@
 It is acceptable for total number of flash words to be significantly greater than the depth of the read FIFO.
 In this situation, the read FIFO will fill up (or hit programmable fill value), pause the flash read and trigger an interrupt to software.
 Once there is space inside the FIFO, the controller will resume reading until the appropriate number of words have been read.
-Once the total count has been reached, the flash controller will post OP_DONE in the !!OP_STATUS register.
+Once the total count has been reached, the flash controller will post OP_DONE in the {{< regref "OP_STATUS" >}} register.
 
-{{% section2 Issuing a Controller Program}}
+## Issuing a Controller Program
 
 To program flash, the same procedure as read is followed.
-However, instead of setting the !!CONTROL register for read operation, a program operation is selected instead.
+However, instead of setting the {{< regref "CONTROL" >}} register for read operation, a program operation is selected instead.
 Software will then fill the program FIFO and wait for the controller to consume this data.
 Similar to the read case, the controller will automatically stall when there is insufficient data in the FIFO.
-When all desired words have been programmed, the controller will post OP_DONE in the !!OP_STATUS register.
+When all desired words have been programmed, the controller will post OP_DONE in the {{< regref "OP_STATUS" >}} register.
 
-{{% section1 Register Table }}
+# Register Table
 
 The flash protocol controller maintains two separate access windows for the FIFO.
 It is implemented this way because the access window supports transaction back-pressure should the FIFO become full (in case of write) or empty (in case of read).
 
-{{% registers x }}
+{{< registers "hw/ip/flash_ctrl/data/flash_ctrl.hjson" >}}
diff --git a/hw/ip/gpio/doc/gpio.md b/hw/ip/gpio/doc/_index.md
similarity index 81%
rename from hw/ip/gpio/doc/gpio.md
rename to hw/ip/gpio/doc/_index.md
index 655966e..b4d04b1 100644
--- a/hw/ip/gpio/doc/gpio.md
+++ b/hw/ip/gpio/doc/_index.md
@@ -1,24 +1,23 @@
-{{% lowrisc-doc-hdr GPIO HWIP Technical Specification }}
-{{% regfile ../data/gpio.hjson }}
+---
+title: "GPIO HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies GPIO hardware IP functionality. This
 module conforms to the [Comportable guideline for peripheral device
-functionality](../../../../doc/rm/comportability_specification.md)
+functionality]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader top
 level system.
 
-{{% toc 3 }}
-
-{{% section2 Features }}
+## Features
 
 - 32 GPIO ports
 - Configurable interrupt per GPIO for detecting rising edge, falling edge,
   or active low/high input
 - Two ways to update GPIO output: direct-write and masked (thread-safe) update
 
-{{% section2 Description }}
+## Description
 
 The GPIO block allows software to communicate through general purpose I/O
 pins in a flexible manner. Each of 32 independent bits can be written
@@ -51,9 +50,9 @@
 See the Design Details section for more details on output, input, and
 interrupt control.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![GPIO Block Diagram](gpio_blockdiagram.svg)
 
@@ -62,31 +61,31 @@
 For reference, it also shows the assumed connections to pads in
 the top level netlist.
 
-{{% section2 Design Details }}
+## Design Details
 
 ### GPIO Output logic
 
 ![GPIO Output Diagram](gpio_output.svg)
 
 The GPIO module maintains one 32-bit output register `DATA_OUT` with two
-ways to write to it. Direct write access uses !!DIRECT_OUT, and masked
-access uses !!MASKED_OUT_UPPER and !!MASKED_OUT_LOWER. Direct access
+ways to write to it. Direct write access uses {{< regref "DIRECT_OUT" >}}, and masked
+access uses {{< regref "MASKED_OUT_UPPER" >}} and {{< regref "MASKED_OUT_LOWER" >}}. Direct access
 provides full write and read access for all 32 bits in one register.
 
 For masked access the bits to modify are given as a mask in the upper
-16 bits of the !!MASKED_OUT_UPPER and !!MASKED_OUT_LOWER register
+16 bits of the {{< regref "MASKED_OUT_UPPER" >}} and {{< regref "MASKED_OUT_LOWER" >}} register
 write, while the data to write is provided in the lower 16 bits of the
 register write.  The hardware updates `DATA_OUT` with the mask so that
 the modification is done without software requiring a Read-Modify-Write.
 
 Reads of masked registers return the lower/upper 16 bits of the `DATA_OUT`
 contents. Zeros are returned in the upper 16 bits (mask field). To read
-what is on the pins, software should read the !!DATA_IN register. (See
+what is on the pins, software should read the {{< regref "DATA_IN" >}} register. (See
 GPIO Input section below).
 
 The same concept is duplicated for the output enable register `DATA_OE`.
-Direct access uses !!DIRECT_OE, and masked access is available using
-!!MASKED_OE_UPPER and !!MASKED_OE_LOWER.
+Direct access uses {{< regref "DIRECT_OE" >}}, and masked access is available using
+{{< regref "MASKED_OE_UPPER" >}} and {{< regref "MASKED_OE_LOWER" >}}.
 
 The output enable is sent to the pad control block to determine if the
 pad should drive the `DATA_OUT` value to the associated pin or not.
@@ -101,23 +100,23 @@
 
 ### GPIO Input
 
-The !!DATA_IN register returns the contents as seen on the peripheral
+The {{< regref "DATA_IN" >}} register returns the contents as seen on the peripheral
 input, typically from the pads connected to those inputs.  In the
 presence of a pin-multiplexing unit, GPIO peripheral inputs that are
 not connected to a chip input will be tied to a constant zero input.
 
 The GPIO module provides optional independent noise filter control for
 each of the 32 input signals. Each input can be independently enabled with
-the !!CTRL_EN_INPUT_FILTER (one bit per input).  This 16-cycle filter
-is applied to both the !!DATA_IN register as well as to the interrupt
-detection logic. The timing for !!DATA_IN is still not instantaneous if
-!!CTRL_EN_INPUT_FILTER is false as there is top-level routing involved,
-but no flops are between the chip input and the !!DATA_IN register.
+the {{< regref "CTRL_EN_INPUT_FILTER" >}} (one bit per input).  This 16-cycle filter
+is applied to both the {{< regref "DATA_IN" >}} register as well as to the interrupt
+detection logic. The timing for {{< regref "DATA_IN" >}} is still not instantaneous if
+{{< regref "CTRL_EN_INPUT_FILTER" >}} is false as there is top-level routing involved,
+but no flops are between the chip input and the {{< regref "DATA_IN" >}} register.
 
-The contents of !!DATA_IN are always readable and reflect the value
+The contents of {{< regref "DATA_IN" >}} are always readable and reflect the value
 seen at the chip input pad regardless of the output enable setting from
 DATA_OE. If the output enable is true (and the GPIO is connected to a
-chip-level pad), the value read from !!DATA_IN includes the effect of
+chip-level pad), the value read from {{< regref "DATA_IN" >}} includes the effect of
 the peripheral's driven output (so will only differ from DATA_OUT if the
 output driver is unable to switch the pin or during the delay imposed
 if the noise filter is enabled).
@@ -127,18 +126,18 @@
 The GPIO module provides 32 interrupt signals to the main processor.
 Each interrupt can be independently enabled, tested, and configured.
 Following the standard interrupt guidelines in the [Comportability
-Specification](../../../../doc/rm/comportability_specification.md),
-the 32 bits of the !!INTR_ENABLE register determines whether or not the
+Specification]({{< relref "doc/rm/comportability_specification" >}}),
+the 32 bits of the {{< regref "INTR_ENABLE" >}} register determines whether or not the
 associated inputs are configured to detect interrupt events. If enabled
 via the various `INTR_CTRL_EN` registers, their current state can be
-read in the !!INTR_STATE register. Clearing is done by writing a `1`
-into the associated !!INTR_STATE bit field.
+read in the {{< regref "INTR_STATE" >}} register. Clearing is done by writing a `1`
+into the associated {{< regref "INTR_STATE" >}} bit field.
 
 For configuration, there are 4 types of interrupts available per bit,
-controlled with four control registers. !!INTR_CTRL_EN_RISING allows
+controlled with four control registers. {{< regref "INTR_CTRL_EN_RISING" >}} allows
 the associated input to be configured for rising-edge detection.
-Similarly, !!INTR_CTRL_EN_FALLING detects falling edge inputs.
-!!INTR_CTRL_EN_LVLHIGH and !!INTR_CTRL_EN_LVLLOW allow the input to be
+Similarly, {{< regref "INTR_CTRL_EN_FALLING" >}} detects falling edge inputs.
+{{< regref "INTR_CTRL_EN_LVLHIGH" >}} and {{< regref "INTR_CTRL_EN_LVLLOW" >}} allow the input to be
 level sensitive interrupts. In theory an input can be configured to detect
 both a rising and falling edge, but there is no hardware assistance to
 indicate which edge caused the output interrupt.
@@ -149,13 +148,13 @@
 guidelines. The GPIO module, if configured, converts an edge detection
 into a level interrupt to the processor core.
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg gpio }}
+{{< hwcfg "hw/ip/gpio/data/gpio.hjson" >}}
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
 Initialization of the GPIO module includes the setting up of the interrupt
 configuration for each GPIO input, as well as the configuration of
@@ -175,7 +174,7 @@
 *GPIO_CTRL_EN_INPUT_FILTER = 0b00001111;
 ```
 
-{{% section2 Common Examples }}
+## Common Examples
 
 This section below shows the interaction between the direct access
 and mask access for data output and data enable.
@@ -225,7 +224,7 @@
 printf("0x%x", *GPIO_DATA_IN);          // 0xf7f8f5f6
 ```
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 This section below gives an example of how interrupt clearing works,
 assuming some events have occurred as shown in comments.
@@ -273,6 +272,6 @@
 
 ```
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/gpio/data/gpio.hjson" >}}
diff --git a/hw/ip/gpio/doc/gpio_dv_plan.md b/hw/ip/gpio/doc/dv_plan/index.md
similarity index 80%
rename from hw/ip/gpio/doc/gpio_dv_plan.md
rename to hw/ip/gpio/doc/dv_plan/index.md
index 3fb2b6f..ea8e6d0 100644
--- a/hw/ip/gpio/doc/gpio_dv_plan.md
+++ b/hw/ip/gpio/doc/dv_plan/index.md
@@ -1,7 +1,6 @@
-{{% lowrisc-doc-hdr GPIO DV Plan }}
-{{% import_testplan ../data/gpio_testplan.hjson }}
-
-{{% toc 4 }}
+---
+title: "GPIO DV Plan"
+---
 
 ## Goals
 * **DV**
@@ -11,15 +10,15 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
-For detailed information on GPIO design features, please see the [GPIO design specification](gpio.md).
+For detailed information on GPIO design features, please see the [GPIO design specification]({{< relref "hw/ip/gpio/doc" >}}).
 
 ## Testbench architecture
-GPIO testbench has been constructed based on the [CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+GPIO testbench has been constructed based on the [CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -27,16 +26,16 @@
 ### Top level testbench
 Top level testbench is located at `hw/ip/gpio/dv/tb/tb.sv`. It instantiates the GPIO DUT module `hw/ip/gpio/rtl/gpio.sv`.
 In addition, it instantiates the following interfaces and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
-* GPIO IOs ([`pins_if`](../../../dv/sv/common_ifs/README.md))
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [TileLink host interfac]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
+* GPIO IOs ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [common_ifs](../../../dv/sv/common_ifs/README.md)
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [common_ifs]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Global types & methods
 All common types and methods defined at the package level can be found in `gpio_env_pkg`. Some of them in use are:
@@ -45,10 +44,10 @@
 parameter uint FILTER_CYCLES = 16;
 ```
 ### TL_agent
-GPIO testbench instantiates (handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md) which provides the ability to drive and independently monitor random traffic via TL host interface into GPIO device.
+GPIO testbench instantiates (handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}}) which provides the ability to drive and independently monitor random traffic via TL host interface into GPIO device.
 
 ### RAL
-The GPIO RAL model is constructed using the [regtool.py script](../../../../util/reggen/README.md) and is placed at `env/gpio_reg_block.sv`.
+The GPIO RAL model is constructed using the [regtool.py script]({{< relref "util/reggen/README.md" >}}) and is placed at `env/gpio_reg_block.sv`.
 
 ### Stimulus strategy
 #### Test sequences
@@ -56,12 +55,14 @@
 The `gpio_base_vseq` virtual sequence is extended from `cip_base_vseq` and serves as a starting point.
 All test sequences are extended from `gpio_base_vseq`. It provides commonly used handles, variables, functions and tasks that the test sequences can simple use / call.
 Some of the most commonly used tasks / functions are as follows:
+
 * `set_gpio_pulls`: This function overrides values of `pullup_en` and `pulldown_en` members of randomized `gpio_env_cfg`
 * `drive_gpio_in`: This task writes all bits of `direct_oe` register to 0's first and then drives specified value on dut GPIO inputs
 * `undrive_gpio_in`: This task drives all dut GPIO inputs to 'z' values, so that dut GPIO outputs may be driven
 
 #### Functional coverage
 To ensure high quality constrained random stimulus, it is necessary to develop a functional coverage model. The following covergroups have been developed to prove that the test intent has been adequately met:
+
 * `gpio_pin_values_cov_obj`: Covers values and transitions on all GPIO IOs
 * `intr_state_cov_obj`: Covers `intr_state` values and transitions for all GPIO interrupts
 * `intr_ctrl_en_cov_objs`: Covers values and transitions on all bits of following interrupt control enable registers:
@@ -95,14 +96,14 @@
 Any CSR read transaction would check actual read data against predicted value.  Additionally, CSR read on intr_state would also check if monitored value of interrupt pins match the predicted value.
 
 #### Assertions
-* TLUL assertions: The `tb/gpio_bind.sv` binds the `tlul_assert` [assertions](../../tlul/doc/TlulProtocolChecker.md) to the IP to ensure TileLink interface protocol compliance.
+* TLUL assertions: The `tb/gpio_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance.
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset
 * `intrGpioKnown`: Checks that GPIO interrupt pins do not have any unknowns
 * `CioGpioEnOKnown`: Checks that GPIO output does not have any unknowns
 * `CioGpioOKnown`: Checks that GPIO output enable does not have any unknowns
 
 ## Building and running tests
-We are using our in-house developed [regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
 Here's how to run a basic sanity test:
 ```console
@@ -111,4 +112,4 @@
 ```
 
 ## Testplan
-{{% insert_testplan x }}
+{{< testplan "hw/ip/gpio/data/gpio_testplan.hjson" >}}
diff --git a/hw/ip/gpio/doc/tb.svg b/hw/ip/gpio/doc/dv_plan/tb.svg
similarity index 100%
rename from hw/ip/gpio/doc/tb.svg
rename to hw/ip/gpio/doc/dv_plan/tb.svg
diff --git a/hw/ip/hmac/doc/hmac.md b/hw/ip/hmac/doc/_index.md
similarity index 85%
rename from hw/ip/hmac/doc/hmac.md
rename to hw/ip/hmac/doc/_index.md
index 9b17a3e..2165827 100644
--- a/hw/ip/hmac/doc/hmac.md
+++ b/hw/ip/hmac/doc/_index.md
@@ -1,22 +1,22 @@
-{{% lowrisc-doc-hdr HMAC HWIP Technical Specification }}
-{{% regfile ../data/hmac.hjson }}
+---
+title: "HMAC HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies HMAC hardware IP functionality. This module conforms to
-the [OpenTitan guideline for peripheral device functionality.](../../../../doc/rm/comportability_specification.md)
+the [OpenTitan guideline for peripheral device functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader OpenTitan top level system.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - HMAC with SHA256 hash algorithm
 - HMAC-SHA256, SHA256 dual mode
 - 256-bit secret key
 - 16 x 32-bit Message buffer
 
-{{% section2 Description }}
+## Description
 
 [sha256-spec]: https://csrc.nist.gov/publications/detail/fips/180/4/final
 
@@ -25,9 +25,9 @@
 with the same secret key. It generates a different authentication code with the
 same message if the secret key is different.
 
-The 256-bit secret key written in !!KEY0 to !!KEY7. The message to authenticate
-is written to !!MSG_FIFO and the HMAC generates a 256-bit digest value which can
-be read from !!DIGEST0 to !!DIGEST7. The `hash_done` interrupt is raised to
+The 256-bit secret key written in {{< regref "KEY0" >}} to {{< regref "KEY7" >}}. The message to authenticate
+is written to {{< regref "MSG_FIFO" >}} and the HMAC generates a 256-bit digest value which can
+be read from {{< regref "DIGEST0" >}} to {{< regref "DIGEST7" >}}. The `hash_done` interrupt is raised to
 report to software that the final digest is available.
 
 The HMAC IP can run in SHA-256-only mode, whose purpose is to check the
@@ -37,13 +37,13 @@
 
 The software doesn't need to write the message length to registers. The HMAC IP
 will calculate the length of the message received between **1** being written to
-!!CMD.hash_start and **1** being written to !!CMD.hash_process.
+{{< regref "CMD.hash_start" >}} and **1** being written to {{< regref "CMD.hash_process" >}}.
 
 This version doesn't have many defense mechanisms but does have the feature of
 wiping the internal variables such as the secret key, intermediate hash results
 H, and the message FIFO. It does not wipe the software accessible 16x32b FIFO.
 The software can wipe the variables by writing a 32-bit random value into
-!!WIPE_SECRET register. The internal variables will be reset to the written
+{{< regref "WIPE_SECRET" >}} register. The internal variables will be reset to the written
 value. This version of the HMAC doesn't have a internal pseudo-random number
 generator to derive the random number from the written seed number.
 
@@ -51,9 +51,9 @@
 the HMAC IP, such as a key manager, to update the secret key. It will also have
 the ability to send the digest directly to a shared internal bus.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![HMAC Block Diagram](hmac_block_diagram.svg)
 
@@ -81,19 +81,19 @@
 completed, the SHA-256 updates the digest registers with the addition of the
 hash result and the previous digest registers.
 
-{{% section2 Design Details }}
+## Design Details
 
 ### SHA-256 message feed and pad
 
 A message is fed via a memory-mapped message FIFO. Any write access to the
-memory-mapped window !!MSG_FIFO updates the message FIFO. If the FIFO is full,
+memory-mapped window {{< regref "MSG_FIFO" >}} updates the message FIFO. If the FIFO is full,
 the HMAC block will block any writes leading to back-pressure on the
 interconnect (as opposed to dropping those writes or overwriting existing FIFO
 contents). It is recommended this back-pressure is avoided by not writing to the
-memory-mapped message FIFO when it is full (As indicated by !!STATUS.fifo_full).
+memory-mapped message FIFO when it is full (As indicated by {{< regref "STATUS.fifo_full" >}}).
 The logic assumes the received message is big-endian. If it is little-endian,
-the software must set !!CFG.endian_swap to **1**.  The byte order of the digest
-registers, from !!DIGEST0 to !!DIGEST7 can be configured with !!CFG.digest_swap.
+the software must set {{< regref "CFG.endian_swap" >}} to **1**.  The byte order of the digest
+registers, from {{< regref "DIGEST0" >}} to {{< regref "DIGEST7" >}} can be configured with {{< regref "CFG.digest_swap" >}}.
 
 The message length is calculated by the packer logic. The packer converts
 non-word writes into full word writes and feeds into the message FIFO. While
@@ -194,17 +194,17 @@
 message. For instance, if an empty message is given, it takes 360 cycles(80 for
 msg itself and 240 for the extra) to get the HMAC authentication token.
 
-{{% section2 Hardware Interface }}
+## Hardware Interface
 
-{{% hwcfg hmac }}
+{{< hwcfg "hw/ip/hmac/data/hmac.hjson" >}}
 
-{{% section2 Defence Mechanism }}
+## Defence Mechanism
 
 ### Wipe secret
 
 ### Add feature or idea here
 
-{{% section1 Programmer's Guide }}
+# Programmer's Guide
 
 This chapter shows how to use the HMAC-SHA256 IP by showing some snippets such
 as initialization, initiating SHA-256 or HMAC process and processing the
@@ -212,12 +212,12 @@
 required. More detailed and complete code will eventually be found in the
 software under `sw/`.
 
-{{% section2 Initialization }}
+## Initialization
 
 This section of the code describes initializing the HMAC-SHA256, setting up the
-interrupts, endianess, and HMAC, SHA-256 mode. !!CFG.endian_swap reverses
+interrupts, endianess, and HMAC, SHA-256 mode. {{< regref "CFG.endian_swap" >}} reverses
 byte-oder of input message when the software writes message into the FIFO.
-!!CFG.digest_swap is to reverse the result of the HMAC or SHA hash. It doesn't
+{{< regref "CFG.digest_swap" >}} is to reverse the result of the HMAC or SHA hash. It doesn't
 reverse the byte-order of the internal logic but to the registers only.
 
 ```c
@@ -241,12 +241,12 @@
 }
 ```
 
-{{% section2 Trigger HMAC/SHA-256 engine }}
+## Trigger HMAC/SHA-256 engine }}
 
 The following code shows how to send a message to the HMAC, the proceedure is
 the same whether a full HMAC or SHA-256 calculation only is wanted (choose
-between them using !!CFG.hmac_en). In both cases the SHA-256 engine must be
-enabled using !!CFG.sha_en (once all other configuration has been properly set).
+between them using {{< regref "CFG.hmac_en" >}}). In both cases the SHA-256 engine must be
+enabled using {{< regref "CFG.sha_en" >}} (once all other configuration has been properly set).
 If the message is bigger than 512-bit, the software must wait until the FIFO
 isn't full before writing further bits.
 
@@ -277,16 +277,16 @@
 }
 ```
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling }}
 
 ### FIFO_FULL
 
 If the FIFO_FULL interrupt occurs, it is recommended the software does not write
-more data into !!MSG_FIFO untill the interrupt is cleared and the status
-!!STATUS.fifo_full is lowered. Whilst the FIFO is full the HMAC will block
+more data into {{< regref "MSG_FIFO" >}} untill the interrupt is cleared and the status
+{{< regref "STATUS.fifo_full" >}} is lowered. Whilst the FIFO is full the HMAC will block
 writes until the FIFO has space which will cause back-pressure on the
 interconnect.
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/hmac/data/hmac.hjson" >}}
diff --git a/hw/ip/hmac/doc/hmac_dv_plan.md b/hw/ip/hmac/doc/dv_plan/index.md
similarity index 80%
rename from hw/ip/hmac/doc/hmac_dv_plan.md
rename to hw/ip/hmac/doc/dv_plan/index.md
index 4dab3f4..e26a538 100644
--- a/hw/ip/hmac/doc/hmac_dv_plan.md
+++ b/hw/ip/hmac/doc/dv_plan/index.md
@@ -1,7 +1,6 @@
-{{% lowrisc-doc-hdr HMAC DV Plan }}
-{{% import_testplan ../data/hmac_testplan.hjson }}
-
-{{% toc 4 }}
+---
+title: "HMAC DV Plan"
+---
 
 ## Goals
 * **DV**
@@ -11,17 +10,17 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
 For detailed information on HMAC design features, please see the
-[HMAC design specification](hmac.md).
+[HMAC design specification]({{< relref "hw/ip/hmac/doc" >}}).
 
 ## Testbench architecture
 HMAC testbench has been constructed based on the
-[CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+[CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -30,15 +29,15 @@
 Top level testbench is located at `hw/ip/hmac/dv/tb/tb.sv`. It instantiates the
 HMAC DUT module `hw/ip/hmac/rtl/hmac.sv`. In addition, it instantiates the following
 interfaces and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
-* Devmode ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
+* Devmode ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Global types & methods
 All common types and methods defined at the package level can be found in `env/hmac_env_pkg`.
@@ -50,18 +49,18 @@
 ```
 
 ### TL_agent
-HMAC instantiates (handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md)
+HMAC instantiates (handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into HMAC device.
 
 ### RAL
 The HMAC RAL model is constructed using the
-[regtool.py script](../../../../util/reggen/README.md)
+[regtool.py script]({{< relref "util/reggen/README.md" >}})
 and is placed at `env/hmac_reg_block.sv`.
 
 ### Reference models
 To check the correctness of the output for SHA256 and HMAC, the testbench uses
-the [C reference model](cryptoc_dpi/README.md).
+the [C reference model]({{< relref "hw/ip/hmac/dv/cryptoc_dpi/README.md" >}}).
 Messages and keys generated by constrained random test sequences are passed on to the
 reference model. Then the hmac scoreboard will compare the reference model's expected
 digest data with the DUT output.
@@ -82,7 +81,7 @@
 
 ##### Standard test vectors
 Besides contrained random test sequences, hmac test sequences also includes [standard
-SHA256 and HMAC test vectors](../../../dv/sv/test_vectors/README.md) from
+SHA256 and HMAC test vectors]({{< relref "hw/dv/sv/test_vectors/README.md" >}}) from
 [NIST](https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Secure-Hashing#shavs)
 and [IETF](https://tools.ietf.org/html/rfc4868).
 The standard test vectors provide messages, keys (for HMAC only), and expected
@@ -130,12 +129,12 @@
 
 #### Assertions
 * TLUL assertions: The `tb/hmac_bind.sv` binds the `tlul_assert`
-  [assertions](../../tlul/doc/TlulProtocolChecker.md) to hmac to ensure TileLink interface protocol compliance.
+  [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to hmac to ensure TileLink interface protocol compliance.
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset.
 
 ## Building and running tests
 We are using our in-house developed
-[regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+[regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known
 issues.
 Here's how to run a basic sanity test:
@@ -145,4 +144,4 @@
 ```
 
 ## Testplan
-{{% insert_testplan x }}
+{{< testplan "hw/ip/hmac/data/hmac_testplan.hjson" >}}
diff --git a/hw/ip/hmac/doc/tb.svg b/hw/ip/hmac/doc/dv_plan/tb.svg
similarity index 100%
rename from hw/ip/hmac/doc/tb.svg
rename to hw/ip/hmac/doc/dv_plan/tb.svg
diff --git a/hw/ip/i2c/doc/_index.md b/hw/ip/i2c/doc/_index.md
new file mode 100644
index 0000000..c7509cc
--- /dev/null
+++ b/hw/ip/i2c/doc/_index.md
@@ -0,0 +1,3 @@
+---
+title: "I2C Technical Specification" 
+---
diff --git a/hw/ip/i2c/doc/i2c_dv_plan.md b/hw/ip/i2c/doc/dv_plan/index.md
similarity index 69%
rename from hw/ip/i2c/doc/i2c_dv_plan.md
rename to hw/ip/i2c/doc/dv_plan/index.md
index d9b769f..6279c6f 100644
--- a/hw/ip/i2c/doc/i2c_dv_plan.md
+++ b/hw/ip/i2c/doc/dv_plan/index.md
@@ -1,7 +1,6 @@
-{{% lowrisc-doc-hdr I2C DV Plan }}
-{{% import_testplan ../data/i2c_testplan.hjson }}
-
-{{% toc 4 }}
+---
+title: "I2C DV Plan"
+---
 
 ## Goals
 * **DV**
@@ -11,17 +10,17 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
 For detailed information on I2C design features, please see the
-[I2C design specification](../doc/i2c.md).
+[I2C design specification]({{< relref "hw/ip/i2c/doc" >}}).
 
 ## Testbench architecture
 I2C testbench has been constructed based on the
-[CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+[CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -29,16 +28,16 @@
 ### Top level testbench
 Top level testbench is located at `hw/ip/i2c/dv/tb/tb.sv`. It instantiates the I2C DUT module `hw/ip/i2c/rtl/i2c.sv`.
 In addition, it instantiates the following interfaces, connects them to the DUT and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 * I2C IOs
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [common_ifs](../../../dv/sv/common_ifs/README.md)
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [common_ifs]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Global types & methods
 All common types and methods defined at the package level can be found in
@@ -50,7 +49,7 @@
 ```
 
 ### TL_agent
-I2C instantiates (already handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md)
+I2C instantiates (already handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into I2C device.
 
@@ -58,7 +57,7 @@
 [describe or provide link to I2C agent documentation]
 
 ### RAL
-The I2C RAL model is constructed using the [regtool.py script](../../../../util/reggen/README.md) and is placed at `env/i2c_reg_block.sv`.
+The I2C RAL model is constructed using the [regtool.py script]({{< relref "util/reggen/README.md" >}}) and is placed at `env/i2c_reg_block.sv`.
 
 ### Stimulus strategy
 #### Test sequences
@@ -84,13 +83,13 @@
 * analysis port2:
 
 #### Assertions
-* TLUL assertions: The `tb/i2c_bind.sv` binds the `tlul_assert` [assertions](../../tlul/doc/TlulProtocolChecker.md) to the IP to ensure TileLink interface protocol compliance.
+* TLUL assertions: The `tb/i2c_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance.
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset.
 * assertion 1
 * assertion 2
 
 ## Building and running tests
-We are using our in-house developed [regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
 Here's how to run a basic sanity test:
 ```console
@@ -99,4 +98,4 @@
 ```
 
 ## Testplan
-{{% insert_testplan x }}
+{{< testplan "hw/ip/i2c/data/i2c_testplan.hjson" >}}
diff --git a/hw/ip/i2c/doc/tb.svg b/hw/ip/i2c/doc/dv_plan/tb.svg
similarity index 100%
rename from hw/ip/i2c/doc/tb.svg
rename to hw/ip/i2c/doc/dv_plan/tb.svg
diff --git a/hw/ip/padctrl/doc/padctrl.md b/hw/ip/padctrl/doc/index.md
similarity index 93%
rename from hw/ip/padctrl/doc/padctrl.md
rename to hw/ip/padctrl/doc/index.md
index 41be20c..5c07021 100644
--- a/hw/ip/padctrl/doc/padctrl.md
+++ b/hw/ip/padctrl/doc/index.md
@@ -1,19 +1,19 @@
-{{% lowrisc-doc-hdr Padctrl Technical Specification }}
-{{% regfile ../data/padctrl.hjson }}
+---
+title: "Padctrl Technical Specification"
+---
 
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the functionality of the pad control block.
 This module is a peripheral on the chip interconnect bus, and thus follows the
-[OpenTitan guideline for peripheral device functionality.](../../../../doc/rm/comportability_specification.md).
+[OpenTitan guideline for peripheral device functionality.]({{< relref "doc/rm/comportability_specification" >}}).
 See that document for integration overview within the broader OpenTitan top level system.
 
 
-{{% toc 4 }}
 
 
-{{% section2 Features }}
+## Features
 
 - Programmable control of chip pin input/output inversion
 
@@ -21,7 +21,7 @@
 
 - Programmable control of chip pin output drive strength, pull up, pull down, open drain
 
-{{% section2 Description }}
+## Description
 
 The `padctrl` module instantiates all chip pads and provides a software accessible register file to control pad attributes such as pull-up, pull-down, open-drain, drive-strength, keeper and input/output inversion.
 The `padctrl` module supports a comprehensive set of pin attributes, but it is permissible that some of them may not be supported by the underlying pad implementation.
@@ -30,10 +30,10 @@
 Note that static pin attributes for FPGAs are currently not covered in this specification.
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 Even though the pad control IP is referred to as one IP, it is logically split into two modules that are instantiated on the top-level and the chip-level respectively, as shown in the block diagram below.
 The top-level module `padctrl` contains the CSRs that are accessible via the TL-UL interface, while the chip-level module `padring` instantiates the bidirectional pads and connects the physical pin attributes.
@@ -42,7 +42,7 @@
 
 The chip level `padctrl` module provides two sets of parametric IO arrays prefixed with `mio*` and `dio*`.
 Both sets are functionally equivalent, but are meant to be used with either multiplexed or dedicated IOs as the naming suggests.
-I.e., the `mio*` pads can be connected to the `pinmux` module ([see spec](../../pinmux/doc/pinmux.md)) in order to provide as much IO flexibility as possible to the software running on the device.
+I.e., the `mio*` pads can be connected to the `pinmux` module ([see spec]({{< relref "hw/ip/pinmux/doc" >}})) in order to provide as much IO flexibility as possible to the software running on the device.
 The `dio*` pads on the other hand are to be connected to peripherals that require dedicated ownership of the pads.
 Examples that fall into the latter category are a high-speed SPI peripherals or a UART device that should always be connected for debugging purposes.
 
@@ -52,7 +52,7 @@
 Note that the chip-level `padctrl` module also contains the pads for clock and reset, but these have no associated runtime configurable pad attributes.
 
 
-{{% section2 Parameters }}
+## Parameters
 
 The following table lists the main parameters used throughout the `padctrl` design.
 Note that the `padctrl` modules are generated based on the system configuration, and hence these parameters are placed into a package as "localparams".
@@ -67,7 +67,7 @@
 The `padring` module uses this parameter to instantiate the correct pad implementation, and the `padctrl` module uses this parameter to determine which attribute bits are programmable (see programming guide below).
 The parameter values can be "generic" or "xilinx" as defined in the pad wrapper primitive.
 
-{{% section2 Signals }}
+## Signals
 
 The table below lists the `padctrl` signals.
 The number of IOs is parametric, and hence the signals are stacked in packed arrays.
@@ -102,7 +102,7 @@
 `dio_oe_i[NDioPads-1:0]`               | `input`          | packed `logic`      | Output data enable of dedicated IOs.
 
 
-{{% section2 Generic Pad Wrapper }}
+## Generic Pad Wrapper
 
 <center>
 <img src="generic_pad_wrapper.svg" width="50%">
@@ -139,14 +139,14 @@
 attr_i[AttrDw-1:6] | `input`    | logic | Additional (optional) attributes    | no
 
 
-{{% section2 Programmers Guide }}
+## Programmers Guide
 
 Software should determine and program the `padctrl` pin attributes at startup, or reprogram it when the functionality requirements change at runtime.
 
-This can be achieved by writing to the !!MIO_PADS and !!DIO_PADS registers.
+This can be achieved by writing to the {{< regref "MIO_PADS" >}} and {{< regref "DIO_PADS" >}} registers.
 Note that the IO attributes should be configured before enabling any driving modules such as the `pinmux` in order to avoid undesired electrical behavior and/or contention at the pads.
 
-The padctrl configuration can be locked down by writing a 0 to register !!REGEN.
+The padctrl configuration can be locked down by writing a 0 to register {{< regref "REGEN" >}}.
 The configuration can then not be altered anymore unless the system is reset.
 
 Note that the register description given in the next section is an example that has been generated with the default parameterization, and the layout may change once reparameterized.
@@ -166,7 +166,7 @@
 This behavior is also referred to as "writes-any-reads-legal" or "WARL" in the RISC-V world.
 
 
-{{% section1 Register Table }}
+# Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/padctrl/data/padctrl.hjson" >}}
 
diff --git a/hw/ip/pinmux/doc/pinmux.md b/hw/ip/pinmux/doc/index.md
similarity index 92%
rename from hw/ip/pinmux/doc/pinmux.md
rename to hw/ip/pinmux/doc/index.md
index 1016401..a4aceb9 100644
--- a/hw/ip/pinmux/doc/pinmux.md
+++ b/hw/ip/pinmux/doc/index.md
@@ -1,21 +1,21 @@
-{{% lowrisc-doc-hdr Pinmux Technical Specification }}
-{{% regfile ../data/pinmux.hjson }}
+---
+title: "Pinmux Technical Specification"
+---
 
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the functionality of the pin multiplexer (`pinmux`) peripheral.
-This module conforms to the [OpenTitan guideline for peripheral device functionality.](../../../../doc/rm/comportability_specification.md).
+This module conforms to the [OpenTitan guideline for peripheral device functionality.]({{< relref "doc/rm/comportability_specification" >}}).
 See that document for integration overview within the broader OpenTitan top level system.
 The module provides a mechanism to reconfigure the pin-to-pad mapping of GPIOs at runtime, which can greatly enhance the system flexibility.
 This IP is closely related to the `padctrl` instance which provides additional control of pin attributes (pull-up, pull-down, open drain, drive strength, keeper and inversion).
-See [that spec](../../padctrl/doc/padctrl.md) for more information.
+See [that spec]({{< relref "hw/ip/padctrl/doc" >}}) for more information.
 
 
-{{% toc 4 }}
 
 
-{{% section2 Features }}
+## Features
 
 - Configurable number of chip bidirectional pins
 
@@ -26,12 +26,12 @@
 - Programmable mapping from top-level inputs to peripheral inputs
 
 
-{{% section2 Description }}
+## Description
 
 The pinmux peripheral is a programmable module designed to wire arbitrary peripheral inputs and outputs to arbitrary multiplexable chip bidirectional pins.
 It gives much flexibility at the top level of the device, allowing most data pins to be flexibly wired and controlled by many peripherals.
 It is assumed that all available pins that the pinmux connects to are bidirectional, controlled by logic within this module.
-This document does not define how these are connected to pads at the toplevel, since that is governed by the `padctrl` IP [that spec](../../padctrl/doc/padctrl.md).
+This document does not define how these are connected to pads at the toplevel, since that is governed by the `padctrl` IP [that spec]({{< relref "hw/ip/padctrl/doc" >}}).
 However, some discussion is shared in a later section.
 
 The number of available peripheral IOs and muxed IOs is configurable, in other words modifiable at design time.
@@ -49,14 +49,14 @@
 One could imagine this to be a useful feature if a deep sleep mode were implemented in future; chip outputs could hold their output values and not be affected by internal power loss.
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 The pin multiplexor module intends to give maximum flexibility of peripheral and chip wiring to the software running on the device.
 The assumption is that the wiring is done once at the initialization of the application based upon usage of the device within the broader system.
 How this wiring is most effectively done is outside the scope of this document, but a section below briefly discusses use cases.
 
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 The diagram below shows connectivity between four arbitrary chip pins, named `MIO_00` .. `MIO_03`, and several peripheral inputs and outputs.
 This shows the connectivity available in all directions, as well as the control registers described later in this document.
@@ -72,7 +72,7 @@
 Additional details about the signal names and parameters is given in the tables below.
 
 
-{{% section2 Parameters }}
+## Parameters
 
 The following table lists the main parameters used throughout the `pinmux` design.
 Note that the pinmux is generated based on the system configuration, and hence these parameters are placed into a package as "localparams".
@@ -83,7 +83,7 @@
 NPeriphIn      | 16 (-)                | Number of peripheral input.
 NMioPads       | 8 (-)                 | Number of muxed bidirectional pads (depending on padctrl setup).
 
-{{% section2 Signals }}
+## Signals
 
 The table below lists the `pinmux` signals. The number of IOs is parametric, and hence the signals are stacked in packed arrays.
 
@@ -98,10 +98,10 @@
 `mio_oe_o[NMioPads-1:0]`             | `output`         | packed `logic` | Signals to `NMioPads` bidirectional pads as output enables.
 `mio_in_i[NMioPads-1:0]`             | `input`          | packed `logic` | Signals from `NMioPads` bidirectional pads as input data.
 
-{{% section2 Programmers Guide }}
+## Programmers Guide
 
 Software should determine and program the pinmux mapping at startup, or reprogram it when the functionality requirements change at runtime.
-This can be achieved by writing the following values to the !!PERIPH_INSEL and !!MIO_OUTSEL registers.
+This can be achieved by writing the following values to the {{< regref "PERIPH_INSEL" >}} and {{< regref "MIO_OUTSEL" >}} registers.
 Note that the pinmux configuration should be sequenced after any IO attribute-specific configuration in the `padctrl` module to avoid any unwanted electric behavior and/or contention.
 
 `periph_insel` Value  | Selected Input Signal
@@ -121,14 +121,13 @@
 
 The global default at reset is `2`, but the default of individual signals can be overridden at design time, if needed.
 
-The pinmux configuration can be locked down by writing 0 to register !!REGEN.
+The pinmux configuration can be locked down by writing 0 to register {{< regref "REGEN" >}}.
 The configuration can then not be altered anymore unless the system is reset.
 
 
-{{% section1 Register Table }}
+# Register Table
 
 The layout of the register table can change, based on the parameterization.
 The register table below is an example and has been generated using the default parameters.
 
-{{% registers x }}
-
+{{< registers "hw/ip/pinmux/data/pinmux.hjson" >}}
diff --git a/hw/ip/prim/doc/prim_lfsr.md b/hw/ip/prim/doc/prim_lfsr.md
index 6a863df..31b7ad4 100644
--- a/hw/ip/prim/doc/prim_lfsr.md
+++ b/hw/ip/prim/doc/prim_lfsr.md
@@ -1,6 +1,8 @@
-{{% lowrisc-doc-hdr Primitive Component: LFSR }}
+---
+title: "Primitive Component: LFSR"
+---
 
-{{% section1 Overview }}
+# Overview
 
 `prim_lfsr` is a parameterized linear feedback shift register (LFSR)
 implementation that supports Galois (XOR form) and Fibonacci (XNOR form)
@@ -14,7 +16,7 @@
 width availability in the lookup table (see below).
 
 
-{{% section2 Parameters }}
+## Parameters
 
 Name      | type   | Description
 ----------|--------|----------------------------------------------------------
@@ -26,7 +28,7 @@
 Custom    | logic  | Custom polynomial coefficients of length LfsrDw.
 MaxLenSVA | bit    | Enables maximum length assertions, use only in sim and FPV.
 
-{{% section2 Signal Interfaces }}
+## Signal Interfaces
 
 Name          | In/Out | Description
 --------------|--------|---------------------------------
@@ -34,7 +36,7 @@
 data_i[InDw]  | input  | Entropy input
 data_o[OutDw] | output | LFSR state output.
 
-{{% section1 Theory of Opeations }}
+# Theory of Opeations
 
 ```
            /-----------\
diff --git a/hw/ip/prim/doc/prim_packer.md b/hw/ip/prim/doc/prim_packer.md
index 970e7f8..3ae2f85 100644
--- a/hw/ip/prim/doc/prim_packer.md
+++ b/hw/ip/prim/doc/prim_packer.md
@@ -1,20 +1,22 @@
-{{% lowrisc-doc-hdr Primitive Component: Packer }}
+---
+title: "Primitive Component: Packer"
+---
 
-{{% section1 Overview }}
+# Overview
 
 `prim_packer` is a module that receives partial writes then packs and creates
 full configurable width writes. It is one of a set of shared primitive modules
 available for use within OpenTitan as referred to in the Comportability
 Specification section on shared primitives.
 
-{{% section2 Parameters }}
+## Parameters
 
 Name | type | Description
 -----|------|-------------
 InW  | int  | Input data width
 OutW | int  | Output data width
 
-{{% section2 Signal Interfaces }}
+## Signal Interfaces
 
 Name         | In/Out | Description
 -------------|--------|-------------
@@ -29,7 +31,7 @@
 flush_i      | input  | Send out stored data and clear state.
 flush_done_o | output | Indicates flush operation is completed.
 
-{{% section1 Theory of Opeations }}
+# Theory of Opeations
 
 ```code
            /----------\
@@ -57,7 +59,7 @@
 data and send outgoing data to the `data_o` port.
 
 
-```wavejson
+{{< wavejson >}}
 { signal: [
   { name: 'valid_i',      wave: '01.01.....0.'},
   { name: 'data_i[3:0]',  wave: 'x==x===.==x.', data:'0 1 2 3 4 5 6'},
@@ -76,7 +78,7 @@
     tick: ['0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18    ']
   }
 }
-```
+{{< /wavejson >}}
 
 The above waveform shows the case of InW := 4 and OutW := 6. After the first
 transaction, `prim_packer` has `0h` in the storage. When the second `valid_i`
diff --git a/hw/ip/rv_core_ibex/doc/rv_core_ibex.md b/hw/ip/rv_core_ibex/doc/index.md
similarity index 87%
rename from hw/ip/rv_core_ibex/doc/rv_core_ibex.md
rename to hw/ip/rv_core_ibex/doc/index.md
index 6b741a8..879b51c 100644
--- a/hw/ip/rv_core_ibex/doc/rv_core_ibex.md
+++ b/hw/ip/rv_core_ibex/doc/index.md
@@ -1,31 +1,32 @@
-{{% lowrisc-doc-hdr Ibex RISC-V Core Wrapper Technical Specification }}
+---
+title: "Ibex RISC-V Core Wrapper Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies Ibex CPU core wrapper functionality.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 * Instantiation of a [Ibex RV32 CPU Core](https://github.com/lowRISC/ibex).
 * TileLink Uncached Light (TL-UL) host interfaces for the instruction and data ports.
 
-{{% section2 Description }}
+## Description
 
 The Ibex RISC-V Core Wrapper instantiates an [Ibex RV32 CPU Core](https://github.com/lowRISC/ibex), and wraps its data and instruction memory interfaces to TileLink Uncached Light (TL-UL).
 All configuration parameters of Ibex are passed through.
 The pipelining of the bus adapters is configurable.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 Ibex is a compliant RV32 RISC-V CPU core, as [documented in the Ibex documentation](https://ibex-core.readthedocs.io/en/latest/introduction.html#standards-compliance).
 
 The TL-UL bus interfaces exposed by this wrapper block are compliant to the [TileLink Uncached Lite Specification version 1.7.1](https://sifive.cdn.prismic.io/sifive%2F57f93ecf-2c42-46f7-9818-bcdd7d39400a_tilelink-spec-1.7.1.pdf).
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
 All ports and parameters of Ibex are exposed through this wrapper module, except for the instruction and data memory interfaces (signals starting with `instr_` and `data_`).
 Refer to the [Ibex documentation](https://ibex-core.readthedocs.io/en/latest/integration.html) for a detailed description of these signals and parameters.
diff --git a/hw/ip/rv_dm/doc/rv_dm.md b/hw/ip/rv_dm/doc/index.md
similarity index 85%
rename from hw/ip/rv_dm/doc/rv_dm.md
rename to hw/ip/rv_dm/doc/index.md
index b38cc86..8604290 100644
--- a/hw/ip/rv_dm/doc/rv_dm.md
+++ b/hw/ip/rv_dm/doc/index.md
@@ -1,12 +1,11 @@
-{{% lowrisc-doc-hdr RISC-V Debug System Wrapper Technical Specification }}
-
-{{% section1 Overview }}
+---
+title: "RISC-V Debug System Wrapper Technical Specification"
+---
+# Overview
 
 This document specifies the RISC-V Debug System wrapper functionality.
 
-{{% toc 3 }}
-
-{{% section2 Features }}
+## Features
 
 The debug system follows the execution-based debug approach described in the [RISC-V Debug Specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf) and provides the following features.
 
@@ -16,24 +15,24 @@
 - Compatible with RISC-V Debug Specification 0.13-compliant debug software, including OpenOCD and GDB
 - TileLink Uncached Light (TL-UL) bus interfaces
 
-{{% section2 Description }}
+## Description
 
 This module provides a RISC-V Debug Specification-compliant debug system with TileLink Uncached Light bus interfaces.
 The main functionality is provided by the [PULP RISC-V Debug System](https://github.com/pulp-platform/riscv-dbg), which is instantiated by this module.
 All bus interfaces are converted into TL-UL.
 
-See the [PULP RISC-V Debug System Documentation](../../../vendor/pulp_riscv_dbg/doc/debug-system.md) for a full list of features and further design documentation.
+See the [PULP RISC-V Debug System Documentation](https://github.com/lowRISC/opentitan/blob/master/hw/vendor/pulp_riscv_dbg/doc/debug-system.md) for a full list of features and further design documentation.
 This document only describes the additional logic provided on top of the PULP RISC-V Debug System.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The debug system is compliant with the [RISC-V Debug Specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf).
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-All hardware interfaces of the debug system are documented in the [PULP RISC-V Debug System Documentation](../../../vendor/pulp_riscv_dbg/doc/debug-system.md), with the exception of the bus interfaces, which are converted to TL-UL by this wrapper.
+All hardware interfaces of the debug system are documented in the [PULP RISC-V Debug System Documentation](https://github.com/lowRISC/opentitan/blob/master/hw/vendor/pulp_riscv_dbg/doc/debug-system.md), with the exception of the bus interfaces, which are converted to TL-UL by this wrapper.
 
 ### JTAG
 
@@ -80,7 +79,7 @@
 The debug system implements execution-based debug according to the RISC-V Debug Specification.
 Most interactions between the core and the debug system are performed through the debug memory, a bus-exposed memory.
 The memory needs to be accessible from the core instruction *and* data interfaces.
-A full memory map is part of the [PULP RISC-V Debug System Documentation](../../../vendor/pulp_riscv_dbg/doc/debug-system.md).
+A full memory map is part of the [PULP RISC-V Debug System Documentation](https://github.com/lowRISC/opentitan/blob/master/hw/vendor/pulp_riscv_dbg/doc/debug-system.md).
 
 ```verilog
 input  tlul_pkg::tl_h2d_t tl_d_i,
diff --git a/hw/ip/rv_plic/doc/rv_plic.md b/hw/ip/rv_plic/doc/index.md
similarity index 79%
rename from hw/ip/rv_plic/doc/rv_plic.md
rename to hw/ip/rv_plic/doc/index.md
index 1158f0e..4e0e6fa 100644
--- a/hw/ip/rv_plic/doc/rv_plic.md
+++ b/hw/ip/rv_plic/doc/index.md
@@ -1,39 +1,39 @@
-{{% lowrisc-doc-hdr Interrupt Controller Technical Specification }}
-{{% regfile ../data/rv_plic.hjson }}
+---
+title: "Interrupt Controller Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the Interrupt Controller (RV_PLIC) functionality. This
 module conforms to the
-[Comportable guideline for peripheral functionality.](../../../../doc/rm/comportability_specification.md)
+[Comportable guideline for peripheral functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader top level system.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - RISC-V Platform-Level Interrupt Controller (PLIC) compliant interrupt controller
 - Support arbitrary number of interrupt vectors (up to 256) and targets
 - Support interrupt enable, interrupt status registers
 - Memory-mapped MSIP register per HART for software interrupt control.
 
-{{% section2 Description }}
+## Description
 
 The RV_PLIC module is designed to manage various interrupt sources from the
 peripherals. It receives interrupt events as either edge or level of the
 incoming interrupt signals (``intr_src_i``) and can notify multiple targets.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The RV_PLIC is compatible with any RISC-V core implementing the RISC-V privilege specification.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![RV_PLIC Block Diagram](block_diagram.svg)
 
-{{% section2 Details }}
+## Details
 
 ### Identifier
 
@@ -47,7 +47,7 @@
 
 Interrupt sources have configurable priority values. The maximum value of the
 priority is configurable through the localparam `MAX_PRIO` in the rv_plic
-top-level module. For each target there is a threshold value (!!THRESHOLD0 for
+top-level module. For each target there is a threshold value ({{< regref "THRESHOLD0" >}} for
 target 0). RV_PLIC notifies a target of an interrupt only if it's priority is
 strictly greater than the target's threshold. Note this means an interrupt with
 a priority is 0 is effectively prevented from causing an interrupt at any target
@@ -66,25 +66,25 @@
 level basis (where the signal remains at **1**).
 
 When the gateway detects an interrupt event it raises the interrupt pending bit
-(!!IP) for that interrupt source. When an interrupt is claimed by a target the
-relevant bit of !!IP is cleared. A bit in !!IP will be not reasserted until the
+({{< regref "IP" >}}) for that interrupt source. When an interrupt is claimed by a target the
+relevant bit of {{< regref "IP" >}} is cleared. A bit in {{< regref "IP" >}} will be not reasserted until the
 target signals completion of the interrupt. Any new interrupt event between a
-bit in !!IP asserting and completing that interrupt is ignored. In particular
+bit in {{< regref "IP" >}} asserting and completing that interrupt is ignored. In particular
 this means that for edge triggered interrupts if a new edge is seen after the
-source's !!IP bit is asserted and before completion that edge will be ignored
+source's {{< regref "IP" >}} bit is asserted and before completion that edge will be ignored
 (counting missed edges as discussed in the RISC-V PLIC specification is not
 supported).
 
 Note that there is no ability for a level triggered interrupt to be cancelled.
-If the interrupt drops after the gateway has set a bit in !!IP, the bit will
+If the interrupt drops after the gateway has set a bit in {{< regref "IP" >}}, the bit will
 remain set until the interrupt is completed. The SW handler should be conscious
 of this and check the interrupt still requires handling in the handler if this
 behaviour is possible.
 
 ### Interrupt Enables
 
-Each target has a set of Interrupt Enable (!!IE0 for target 0) registers. Each
-bit in the !!IE0 registers controls the corresponding interrupt source. If an
+Each target has a set of Interrupt Enable ({{< regref "IE0" >}} for target 0) registers. Each
+bit in the {{< regref "IE0" >}} registers controls the corresponding interrupt source. If an
 interrupt source is disabled for a target, then interrupt events from that
 source won't trigger an interrupt at the target. RV_PLIC doesn't have a global
 interrupt disable feature.
@@ -92,23 +92,23 @@
 ### Interrupt Claims
 
 "Claiming" an interrupt is done by a target reading the associated
-Claim/Completion register for the target (!!CC0 for target 0). The return value
-of the !!CC0 read represents the ID of the pending interrupt that has the
+Claim/Completion register for the target ({{< regref "CC0" >}} for target 0). The return value
+of the {{< regref "CC0" >}} read represents the ID of the pending interrupt that has the
 highest priority.  If two or more pending interrupts have the same priority,
 RV_PLIC chooses the one with lowest ID. Only interrupts that that are enabled
 for the target can be claimed. The target priority threshold doesn't matter
 (this only factors into whether an interrupt is signalled to the target) so
-lower priority interrupt IDs can be returned on a read from !!CC0. If no
+lower priority interrupt IDs can be returned on a read from {{< regref "CC0" >}}. If no
 interrupt is pending (or all pending interrupts are disabled for the target) a
-read of !!CC0 returns an ID of 0.
+read of {{< regref "CC0" >}} returns an ID of 0.
 
 ### Interrupt Completion
 
-After an interrupt is claimed, the relevant bit of interrupt pending (!!IP) is
+After an interrupt is claimed, the relevant bit of interrupt pending ({{< regref "IP" >}}) is
 cleared, regardless of the status of the `intr_src_i` input value.  Until a
 target "completes" the interrupt, it won't be re-asserted if a new event for the
 interrupt occurs. A target completes the interrupt by writing the ID of the
-interrupt to the Claim/Complete register (!!CC0 for target 0). The write event
+interrupt to the Claim/Complete register ({{< regref "CC0" >}} for target 0). The write event
 is forwarded to the Gateway logic, which resets the interrupt status to accept a
 new interrupt event. The assumption is that the processor has cleaned up the
 originating interrupt event during the time between claim and complete such that
@@ -140,23 +140,23 @@
 asserted. At g the interrupt is completed (by writing `i+1` to it's
 Claim/Complete register) so at h `irq_o` is asserted due to the new interrupt.
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg rv_plic }}
+{{< hwcfg "hw/ip/rv_plic/data/rv_plic.hjson" >}}
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
 After reset, RV_PLIC doesn't generate any interrupts to any targets even if
 interrupt sources are set, as all priorities and thresholds are 0 by default and
 all ``IE`` values are 0. Software should configure the above three registers and the
-interrupt source type !!LE .
+interrupt source type {{< regref "LE" >}} .
 
-!!LE and !!PRIO0 .. !!PRIO31 registers are unique. So, only one of the targets
+{{< regref "LE" >}} and {{< regref "PRIO0" >}} .. {{< regref "PRIO31" >}} registers are unique. So, only one of the targets
 shall configure them.
 
-~~~~c
+```c
 // Pseudo-code below
 void plic_init() {
   // Set to level-triggered for interrupt sources
@@ -179,19 +179,19 @@
 
   *(IE+offset) = *(IE+offset) | (1<<(iid%32));
 }
-~~~~
+```
 
-{{% section2 Handling Interrupt Request Events }}
+## Handling Interrupt Request Events
 
 If software receives an interrupt request, it is recommended to follow the steps
-shown below (assuming target 0 which uses !!CC0 for claim/complete).
+shown below (assuming target 0 which uses {{< regref "CC0" >}} for claim/complete).
 
 1. Claim the interrupts right after entering to the interrupt service routine
-   by reading the !!CC0 register.
+   by reading the {{< regref "CC0" >}} register.
 2. Determine which interrupt should be serviced based on the values read from
-   the !!CC0 register.
+   the {{< regref "CC0" >}} register.
 3. Execute ISR, clearing the originating peripheral interrupt.
-4. Write Interrupt ID to !!CC0
+4. Write Interrupt ID to {{< regref "CC0" >}}
 5. Repeat as necessary for other pending interrupts.
 
 It is possible to have multiple interrupt events claimed. If software claims one
@@ -222,7 +222,7 @@
 }
 ~~~~
 
-{{% section2 Registers }}
+## Registers
 
 The register description can be generated with `reg_rv_plic.py` script. The reason
 another script for register generation is that RV_PLIC is configurable to the
@@ -251,5 +251,5 @@
 -   CC: N_TARGET
     Claim by read, complete by write
 
-{{% registers x }}
+{{< registers "hw/ip/rv_plic/data/rv_plic.hjson" >}}
 
diff --git a/hw/ip/rv_timer/doc/rv_timer.md b/hw/ip/rv_timer/doc/_index.md
similarity index 89%
rename from hw/ip/rv_timer/doc/rv_timer.md
rename to hw/ip/rv_timer/doc/_index.md
index 92f0054..732d680 100644
--- a/hw/ip/rv_timer/doc/rv_timer.md
+++ b/hw/ip/rv_timer/doc/_index.md
@@ -1,23 +1,23 @@
-{{% lowrisc-doc-hdr Timer HWIP Technical Specification }}
-{{% regfile ../data/rv_timer.hjson }}
+---
+title: "Timer HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies RISC-V Timer hardware IP functionality. This module
 conforms to the
-[Comportable guideline for peripheral functionality.](../../../../doc/rm/comportability_specification.md)
+[Comportable guideline for peripheral functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader top level
 system.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - 64-bit timer with 12-bit prescaler and 8-bit step register
 - Compliant with RISC-V privileged specification v1.11
 - Configurable number of timers per hart and number of harts
 
-{{% section2 Description }}
+## Description
 
 The timer module provides a configurable number of 64-bit counters where each
 counter increments by a step value whenever the prescaler times out. Each timer
@@ -28,30 +28,30 @@
 In this version, the timer doesn't consider low-power modes and
 assumes the clock is neither turned off nor changed during runtime.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The timer IP provides memory-mapped registers `mtime` and `mtimecmp` which can
 be used as the machine-mode timer registers defined in the RISC-V privileged
 spec. Additional features such as prescaler, step, and a configurable number of
 timers and harts have been added.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![Timer Block Diagram](timer_block_diagram.svg)
 
 The timer module is composed of tick generators, counters, and comparators.
 A tick generator creates a tick every time its internal counter hits the
-!!CFG0.prescaler value. The tick is used to increment `mtime` by the !!CFG0.step
+{{< regref "CFG0.prescaler" >}} value. The tick is used to increment `mtime` by the {{< regref "CFG0.step" >}}
 value. The 64-bit `mtime` value is compared with the 64-bit `mtimecmp`. If
 `mtime` is greater than or equal to `mtimecmp`, the timer raises an interrupt.
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg timer }}
+{{< hwcfg "hw/ip/rv_timer/data/rv_timer.hjson" >}}
 
-{{% section2 Design Details }}
+## Design Details
 
 ### Tick Generator
 
@@ -59,13 +59,13 @@
 signal. This allows creation of a call-clock timer tick such as 1us or 10us
 regardless of the system clock period. It is useful if the system has more than
 one clock as a clock source. The firmware just needs to adjust the
-!!CFG0.prescaler value and the actual timer interrupt handling routine does not
+{{< regref "CFG0.prescaler" >}} value and the actual timer interrupt handling routine does not
 need a variable clock period to update `mtimecmp`.
 
 For instance, if a system switches between 48MHz and 200MHz clocks, a prescaler
 value of **47** for 48MHz and **199** for 200MHz will generate a 1us tick.
 In this version, the timer only supports a single fixed clock, so the firmware
-should change !!CFG0.prescaler appropriately.
+should change {{< regref "CFG0.prescaler" >}} appropriately.
 
 ### Configurable number of timers and harts
 
@@ -81,7 +81,7 @@
 It has separate interrupts per timer and a set of interrupt enable and state
 registers per Hart.
 
-```.hjson
+```hjson
 {
   // ...
   interrupt_list: [
@@ -161,16 +161,16 @@
 ```
 
 
-{{% section1 Programmer's Guide }}
+# Programmer's Guide
 
-{{% section2 Initialization }}
+## Initialization
 
 Software is expected to configure `prescaler` and `step` before activating the
 timer. These two fields need to be stable to correctly increment the timer
 value. If software wants to change these fields, it should de-activate the
 timer and then proceed.
 
-{{% section2 Register Access }}
+## Register Access
 
 The timer IP has 64-bit timer value registers and 64-bit compare registers. The
 register interface, however, is set to 32-bit data width. The CPU cannot access
@@ -209,7 +209,7 @@
 sw a0, mtimecmp   # New value.
 ```
 
-{{% section2 Timer behaviour close to 2^64 }}
+## Timer behaviour close to 2^64
 
 There are some peculiarities when `mtime` and `mtimecmp` get close to the end of
 the 64-bit integer range. In particular, because an unsigned comparison is done
@@ -232,10 +232,10 @@
    could take up to a timer clock tick) before scheduling the required interrupt
    using the originally computed `mtimecmp` value.
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 TBD
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/rv_timer/data/rv_timer.hjson" >}}
diff --git a/hw/ip/rv_timer/doc/rv_timer_dv_plan.md b/hw/ip/rv_timer/doc/dv_plan/index.md
similarity index 79%
rename from hw/ip/rv_timer/doc/rv_timer_dv_plan.md
rename to hw/ip/rv_timer/doc/dv_plan/index.md
index b60954c..cddabef 100644
--- a/hw/ip/rv_timer/doc/rv_timer_dv_plan.md
+++ b/hw/ip/rv_timer/doc/dv_plan/index.md
@@ -1,8 +1,6 @@
-{{% lowrisc-doc-hdr RV_TIMER DV Plan }}
-{{% import_testplan ../data/rv_timer_testplan.hjson }}
-
-
-{{% toc 4 }}
+---
+title: "RV_TIMER DV Plan"
+---
 
 ## Goals
 * **DV**
@@ -12,15 +10,15 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
-For detailed information on RV_TIMER design features, please see the [RV_TIMER design specification](rv_timer.md).
+For detailed information on RV_TIMER design features, please see the [RV_TIMER design specification]({{< relref "hw/ip/rv_timer/doc" >}}).
 
 ## Testbench architecture
-RV_TIMER testbench has been constructed based on the [CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+RV_TIMER testbench has been constructed based on the [CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -28,14 +26,14 @@
 ### Top level testbench
 Top level testbench is located at `hw/ip/rv_timer/dv/tb/tb.sv`. It instantiates the RV_TIMER DUT module `hw/ip/rv_timer/rtl/rv_timer.sv`.
 In addition, it instantiates the following interfaces, connects them to the DUT and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Global types & methods
 All common types and methods defined at the package level can be found in `env/rv_timer_env_pkg`.
@@ -46,12 +44,12 @@
 ```
 
 ### TL_agent
-RV_TIMER testbench instantiates (already handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md)
+RV_TIMER testbench instantiates (already handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into RV_TIMER device.
 
 ### RAL
-The RV_TIMER RAL model is constructed using the [regtool.py script](../../../../util/reggen/README.md) and is placed at `env/rv_timer_reg_block.sv`.
+The RV_TIMER RAL model is constructed using the [regtool.py script]({{< relref "util/reggen/README.md" >}}) and is placed at `env/rv_timer_reg_block.sv`.
 
 ### Stimulus strategy
 #### Test sequences
@@ -90,11 +88,11 @@
 Interrupt pins are checked against expected at every read/write data channel.
 
 #### Assertions
-* TLUL assertions: The `tb/rv_timer_bind.sv` binds the `tlul_assert` [assertions](../../tlul/doc/TlulProtocolChecker.md) to the IP to ensure TileLink interface protocol compliance
+* TLUL assertions: The `tb/rv_timer_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset
 
 ## Building and running tests
-We are using our in-house developed [regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
 Here's how to run a basic sanity test:
 ```console
@@ -103,4 +101,4 @@
 ```
 
 ## Testplan
-{{% insert_testplan x }}
+{{< testplan "hw/ip/rv_timer/data/rv_timer_testplan.hjson" >}}
diff --git a/hw/ip/rv_timer/doc/tb.svg b/hw/ip/rv_timer/doc/dv_plan/tb.svg
similarity index 100%
rename from hw/ip/rv_timer/doc/tb.svg
rename to hw/ip/rv_timer/doc/dv_plan/tb.svg
diff --git a/hw/ip/spi_device/doc/spi_device.md b/hw/ip/spi_device/doc/index.md
similarity index 91%
rename from hw/ip/spi_device/doc/spi_device.md
rename to hw/ip/spi_device/doc/index.md
index 0a3208e..116cd38 100644
--- a/hw/ip/spi_device/doc/spi_device.md
+++ b/hw/ip/spi_device/doc/index.md
@@ -1,11 +1,11 @@
-{{% lowrisc-doc-hdr SPI Device HWIP Technical Specification }}
-{{% regfile ../data/spi_device.hjson }}
+---
+title: "SPI Device HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - Single-bit wide SPI device interface implementing a raw data transfer protocol
   termed "Generic Mode"
@@ -19,7 +19,7 @@
 - Interrupts for RX/TX SRAM FIFO conditions (empty, full, designated level for
   RX, TX)
 
-{{% section2 Description }}
+## Description
 
 The SPI device module is a serial-to-parallel receive (RX) and
 parallel-to-serial transmit (TX) full duplex design (single line mode) to communicate
@@ -31,14 +31,14 @@
 interface logic as its primary clock, which has performance benefits, but incurs
 design complications described later.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The SPI device doesn't support emulating an EEPROM as of this initial version.
 This version is mostly compatible with the Haven SPI Slave Generic Mode design.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 General Data Transfer on Pins }}
+## General Data Transfer on Pins
 
 Data transfers with the SPI device module involve four peripheral SPI pins: SCK,
 CSB, MOSI, MISO. SCK is the SPI clock driven by an external SPI host. CSB (chip
@@ -52,7 +52,7 @@
 showing the beginning and end of the transfer). Configurability for active
 edges, polarities, and bit orders are described later.
 
-```wavejson
+{{< wavejson >}}
 { signal: [
   { name: 'CSB',  wave: '10.........|....1.'},
   { name: 'SCK',  wave: '0.p........|....l.'},
@@ -67,10 +67,10 @@
     tick: ['-2 -1 0 1 2 3 4 5 6 7 8 9 60 61 62 63     ']
   }
 }
-```
+{{< /wavejson >}}
 
 
-{{% section2 Defining "Generic Mode" }}
+## Defining "Generic Mode"
 
 Generic mode, as implemented by this SPI device, is used to bulk copy data in
 and out of the chip using the pins as shown above. In general, it is used to
@@ -145,7 +145,7 @@
 full, or when the transmitter encounters an empty TX circular buffer are
 error conditions discussed in the Design Details section that follows.
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![Block Diagram](block_diagram.svg)
 
@@ -153,7 +153,7 @@
 bit-serialized MOSI data into a valid byte, where the data bit is valid when the
 chip select signal (CSB) is 0 (active low) and SCK is at positive or negative
 edge (configurable, henceforth called the "active edge"). The bit order within
-the byte is determined by !!CFG.rx_order configuration register field. After a
+the byte is determined by {{< regref "CFG.rx_order" >}} configuration register field. After a
 byte is gathered, the interface module writes the byte data into a small FIFO
 ("RXFIFO") using SCK. It is read out of the FIFO and written into to the
 buffer SRAM ("DP_SRAM") using the system bus clock. If RXFIFO is full, this is
@@ -162,7 +162,7 @@
 The interface module also serializes data from the small transmit FIFO
 ("TXFIFO") and shifts it out on the MISO pin when CSB is 0 and SCK is at the
 active edge. The bit order within the byte can be configured with configuration
-register field !!CFG.tx_order. It is expected that software has prepared TX data
+register field {{< regref "CFG.tx_order" >}}. It is expected that software has prepared TX data
 as per the SPI Flash or general Firmware Mode described in the "Defining
 Generic Mode" section above. But because SCK is not under the control of
 software or the device (it is driven by the external SPI host), it is possible
@@ -171,7 +171,7 @@
 prepared TX data or software does not care about the contents of the TX data -
 then the hardware will send whatever lingering data is in the empty TXFIFO. If
 this is a security risk, then software should at least soft-reset the contents
-of the TXFIFO using the !!CONTROL.rst_txfifo register. The soft-reset signal
+of the TXFIFO using the {{< regref "CONTROL.rst_txfifo" >}} register. The soft-reset signal
 is not synchronized to the SCK clock, so software should drive the reset
 signal when the SPI interface is idle.
 
@@ -198,7 +198,7 @@
 RXFIFO (see details below) at appropriate size boundaries. This data is
 handshake-received on the core clock side, gathered into byte or word quantity,
 and written into the RX circular buffer of the dual-port SRAM. On each write,
-the RXF write pointer (!!RXF_PTR.wptr) is incremented by hardware, wrapping at
+the RXF write pointer ({{< regref "RXF_PTR.wptr" >}}) is incremented by hardware, wrapping at
 the size of the circular buffer. Software can watch (via polling or interrupts)
 the incrementing of this write pointer to determine how much valid data has been
 received, and determine when and what data to act upon. Once it has acted upon
@@ -211,7 +211,7 @@
 normally only write to the 32-bit wide SRAM when an entire word can be written.
 Since the end of the received data may not be aligned, there is a timer that
 forces sub-word writes if data has been staged for too long. The timer value
-(!!CFG.timer_v) represents the number of core clock cycles. For instance, if
+({{< regref "CFG.timer_v" >}}) represents the number of core clock cycles. For instance, if
 timer value is configured in 0xFF, the RXF control logic will write gathered
 sub-word data in 255 cycles if no further bit stream from SPI is received.
 
@@ -243,24 +243,24 @@
 direct reads and writes through the TLUL bus interface, managed by the
 auto-generated register file control logic.
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg spi_device }}
+{{< hwcfg "hw/ip/spi_device/data/spi_device.hjson" >}}
 
-{{% section1 Design Details }}
+# Design Details
 
-{{% section2 Clock and Phase }}
+## Clock and Phase
 
 The SPI device module has two programmable register bits to control the SPI
-clock, !!CFG.CPOL and !!CFG.CPHA. CPOL controls clock polarity and CPHA controls the clock
+clock, {{< regref "CFG.CPOL" >}} and {{< regref "CFG.CPHA" >}}. CPOL controls clock polarity and CPHA controls the clock
 phase. For further details, please refer to this diagram from Wikipedia:
 [File:SPI_timing_diagram2.svg](https://en.wikipedia.org/wiki/Serial_Peripheral_Interface#/media/File:SPI_timing_diagram2.svg)
 
-{{% section2 SPI Device Generic Mode }}
+## SPI Device Generic Mode
 
 As described in the Theory of Operations above, in generic mode, the SPI device
 writes incoming data directly into the SRAM (through RXFIFO) and updates the SPI
-device SRAM write pointer (!!RXF_PTR.wptr). It does not parse a command byte nor
+device SRAM write pointer ({{< regref "RXF_PTR.wptr" >}}). It does not parse a command byte nor
 address bytes, analyzing incoming data relies on firmware implementation of a
 higher level protocol. Data is sent from the TXF SRAM contents via TXFIFO.
 
@@ -270,7 +270,7 @@
 module registers bits [7:1] and combines them with the MOSI signal directly to
 form the input to RXFIFO. This is detailed in the waveform below.
 
-```wavejson
+{{< wavejson >}}
 { signal: [
   { name: 'CSB', wave: '10.||...|..1'},
   { name: 'SCK', wave: '0.p||...|..l', node:'......b' },
@@ -284,7 +284,7 @@
     tick: ['-2 -1 0 1 . 30 31 32 33 n-1 n n+1 n+2 '],
   },
 }
-```
+{{< /wavejson >}}
 
 As shown above, the RXFIFO write request signal (`RX_WEN`) is asserted when
 BitCount reaches 0h. Bitcount is reset by CSB asynchronously, returning to 7h
@@ -298,7 +298,7 @@
 change of MISO value at the negative edge of SCK. MISO_OE is controlled by the
 CSB signal. If CSB goes to high, MISO is returned to High-Z state.
 
-```wavejson
+{{< wavejson >}}
 { signal: [
   { name: 'CSB',      wave:'10.||...|..1'},
   { name: 'SCK',      wave:'0...p.|.|...|l' , node:'.............a', period:0.5},
@@ -314,9 +314,9 @@
     tick: ['-2 -1 0 1 . 30 31 32 33 n-1 n n+1 n+2 '],
   },
 }
-```
+{{< /wavejson >}}
 
-Note that in the SPI mode 3 configuration (!!CFG.CPOL=1, !!CFG.CPHA=1), the
+Note that in the SPI mode 3 configuration ({{< regref "CFG.CPOL" >}}=1, {{< regref "CFG.CPHA" >}}=1), the
 logic isn't able to pop the entry from the TX async FIFO after the last bit
 in the last byte of a transaction. In mode 3, no further SCK edge is given
 after sending the last bit before the CSB de-assertion. The design is chosen to
@@ -371,7 +371,7 @@
 
 ![State Machine](txf_ctrl_fsm_table.png)
 
-{{% section2 Data Storage Sizes }}
+## Data Storage Sizes
 
 SPI Device IP uses a 2kB internal Dual-Port SRAM. Firmware can resize RX / TX
 circular buffers within the SRAM size. For example, the firmware is able to set
@@ -381,22 +381,22 @@
 should be changed. It cannot exceed 13 (32kB) due to the read and write
 pointers' widths.
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
-By default, RX SRAM FIFO base and limit address (via !!RXF_ADDR register) are
+By default, RX SRAM FIFO base and limit address (via {{< regref "RXF_ADDR" >}} register) are
 set to 0x0 and 0x1FC, 512 bytes. And TX SRAM FIFO base and limit addresses (in
-the !!TXF_ADDR register)  are 0x200 and 0x3FC. If FW wants bigger spaces, it can
-change the values of the above registers !!RXF_ADDR and !!TXF_ADDR.
+the {{< regref "TXF_ADDR" >}} register)  are 0x200 and 0x3FC. If FW wants bigger spaces, it can
+change the values of the above registers {{< regref "RXF_ADDR" >}} and {{< regref "TXF_ADDR" >}}.
 
-Software can configure the timer value !!CFG.timer_v to change the delay between
+Software can configure the timer value {{< regref "CFG.timer_v" >}} to change the delay between
 partial DATA received from SPI interface being written into the SRAM. The value
 of the field is the number of the core clock cycles that the logic waits for.
 
-{{% section2 Pointers }}
+## Pointers
 
-RX / TX SRAM FIFO has read and write pointers, !!RXF_PTR and !!TXF_PTR . Those
+RX / TX SRAM FIFO has read and write pointers, {{< regref "RXF_PTR" >}} and {{< regref "TXF_PTR" >}} . Those
 pointers are used to manage circular FIFOs inside the SRAM. The pointer width in
 the register description is 16 bit but the number of valid bits in the pointers
 depends on the size of the SRAM.
@@ -412,6 +412,6 @@
 the read pointer outside the range 0x000 -  0x1FF (128*4 = 512Bytes ignoring
 the phase bit, bit 11).
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/spi_device/data/spi_device.hjson" >}}
diff --git a/hw/ip/tlul/doc/TlulProtocolChecker.md b/hw/ip/tlul/doc/TlulProtocolChecker.md
index 52d82e4..73fc72f 100644
--- a/hw/ip/tlul/doc/TlulProtocolChecker.md
+++ b/hw/ip/tlul/doc/TlulProtocolChecker.md
@@ -1,8 +1,9 @@
-{{% lowrisc-doc-hdr TL-UL Protocol Checker }}
+---
+title: "TL-UL Protocol Checker"
+---
 
 # TileLink-UL Protocol Checker
 
-{{% toc 3 }}
 
 ## **Overview**
 
diff --git a/hw/ip/tlul/doc/tlul.md b/hw/ip/tlul/doc/_index.md
similarity index 98%
rename from hw/ip/tlul/doc/tlul.md
rename to hw/ip/tlul/doc/_index.md
index 5225c36..d30b28f 100644
--- a/hw/ip/tlul/doc/tlul.md
+++ b/hw/ip/tlul/doc/_index.md
@@ -1,12 +1,14 @@
-{{% lowrisc-doc-hdr Bus Specification }}
+---
+title: "Bus Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the bus functionality within a Comportable top level
 system. This includes the bus protocol and all hardware IP that supports
 creating the network on chip within that framework.
 
-{{% section2 Features }}
+## Features
 
 - Support for multiple bus hosts and bus devices<sup>1</sup>
 - Support for multiple clock domains
@@ -22,7 +24,7 @@
 <sup>1</sup>lowRISC is avoiding the fraught terms master/slave and defaulting
 to host/device where applicable.
 
-{{% section2 Description }}
+## Description
 
 For chip-level interconnect, Comportable devices will be using
 [TileLink](https://static.dev.sifive.com/docs/tilelink/tilelink-spec-1.7-draft.pdf)
@@ -54,7 +56,7 @@
 usage of configuration files, but that will not change the operation of
 the fabric and is beyond the scope of this current specification.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 With the exception of the user extensions, the bus is
 compliant with TileLink-UL. The bus primitives, hosts and peripherals
@@ -64,9 +66,9 @@
 source will generate a project-specific default value. Alternatively,
 the blocks can be easily modified to make use of the user extensions.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Signals }}
+## Signals
 
 The table below lists all of the TL-UL signals. "Direction" is
 w.r.t. a bus host, signals marked as output will be in the verilog
@@ -123,7 +125,7 @@
 has a requirement on TL-UL hosts ("masters" in TileLink terminology) that "`valid` signals must be driven LOW for at least 100 cycles while reset is asserted."
 The TL-UL collateral within this library does **not** have this requirement on its TL-UL host drivers.
 TL-UL devices within the library can tolerate shorter reset windows.
-(See the reset section of the [Comportability Specification](../../../../doc/rm/comportability_specification.md)
+(See the reset section of the [Comportability Specification]({{< relref "doc/rm/comportability_specification" >}})
 for details on reset requirements.)
 
 ### Signal and Struct Definitions
@@ -327,13 +329,13 @@
 | `3'b001` | `AccessAckData` | Read command acknowledgement, data valid on `d_data` |
 | `3'b01x, 3'b1xx` | `undefined` | All other opcodes are undefined; responses with undefined opcodes should be ignored by hosts (with assertions). |
 
-{{% section2 Timing Diagrams }}
+## Timing Diagrams
 
 This section shows the timing relationship on the bus for writes with
 response, and reads with response. This shows a few transactions, see
 the TileLink specification for more examples.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',      wave: 'p...................' },
@@ -361,9 +363,9 @@
     text: 'six write transactions (four full, two partial) with various req/ready delays, error on I4 response',
     }
 }
-```
+{{< /wavejson >}}
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'clk_i',    wave: 'p...................' },
@@ -389,9 +391,9 @@
     text: 'six read transactions with various req/ready delays, error on I4 response',
     }
 }
-```
+{{< /wavejson >}}
 
-{{% section2 Bus Primitives }}
+## Bus Primitives
 
 The bus primitives are defined in the following table and described in
 detail below.
diff --git a/hw/ip/uart/doc/uart.md b/hw/ip/uart/doc/_index.md
similarity index 90%
rename from hw/ip/uart/doc/uart.md
rename to hw/ip/uart/doc/_index.md
index d1d7615..a9e9783 100644
--- a/hw/ip/uart/doc/uart.md
+++ b/hw/ip/uart/doc/_index.md
@@ -1,17 +1,17 @@
-{{% lowrisc-doc-hdr UART HWIP Technical Specification }}
-{{% regfile ../data/uart.hjson}}
+---
+title: "UART HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies UART hardware IP functionality. This module
 conforms to the
-[Comportable guideline for peripheral functionality.](../../../../doc/rm/comportability_specification.md)
+[Comportable guideline for peripheral functionality.](/doc/rm/comportability_specification)
 See that document for integration overview within the broader
 top level system.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - 2-pin full duplex external interface
 - 8-bit data word, optional even or odd parity bit per byte
@@ -22,7 +22,7 @@
 - Interrupt for overflow, frame error, parity error, break error, receive
   timeout
 
-{{% section2 Description }}
+## Description
 
 The UART module is a serial-to-parallel receive (RX) and parallel-to-serial
 (TX) full duplex design intended to communicate to an outside device, typically
@@ -31,7 +31,7 @@
 i.e. no synchronizing clock. The programmable baud rate guarantees to be met up
 to 1Mbps.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The UART is compatible with the feature set of H1 Secure Microcontroller UART as
 used in the [Chrome OS cr50][chrome-os-cr50] codebase. Additional features such
@@ -39,17 +39,17 @@
 
 [chrome-os-cr50]: https://chromium.googlesource.com/chromiumos/platform/ec/+/master/chip/g/
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![UART Block Diagram](block_diagram.svg)
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg uart}}
+{{< hwcfg "hw/ip/uart/data/uart.hjson" >}}
 
-{{% section2 Design Details }}
+## Design Details
 
 ### Serial interface (both directions)
 
@@ -58,7 +58,7 @@
 is turned on, at the end of the data bit, odd or even parity bit follows then
 STOP bit completes one byte data transfer.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'Baud Clock',     wave: 'p............'                                                        },
@@ -85,11 +85,11 @@
     tock: -2,
   }
 }
-```
+{{< /wavejson >}}
 
 ### Transmission
 
-A write to !!WDATA enqueues a data byte into the 32 depth write
+A write to {{< regref "WDATA" >}} enqueues a data byte into the 32 depth write
 FIFO, which triggers the transmit module to start UART TX serial data
 transfer. The TX module dequeues the byte from the FIFO and shifts it
 bit by bit out to the UART TX pin when baud tick is asserted.
@@ -108,7 +108,7 @@
 incoming serial bits into a charcter buffer. If the STOP bit is
 detected as high and the optional partity bit is correct the data byte
 is pushed into a 32 byte deep RX FIFO. The data can be read out by
-reading !!RDATA register. It is expected that the software reads all
+reading {{< regref "RDATA" >}} register. It is expected that the software reads all
 the pending data from RX FIFO if RX needs to be disabled.
 
 This behaviour of the receiver can be used to compute the approximate
@@ -128,7 +128,7 @@
 the stop bit will be a bit time later, so this becomes 8/160 or about
 +/- 5%.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'Sample', wave: '', node: '..P............', period: "2" },
@@ -144,7 +144,7 @@
     text: 'Receiver sampling window',
   },
 }
-```
+{{< /wavejson >}}
 
 In practice, the transmitter and receiver will both differ from the
 ideal baud rate. Since the worst case difference for reception is 5%,
@@ -153,11 +153,11 @@
 
 ### Setting the baud rate
 
-The baud rate is set by programming the !!CTRL.NCO register
+The baud rate is set by programming the {{< regref "CTRL.NCO" >}} register
 field. This should be set to `(2^20*baud)/freq`, where `freq` is the
 system clock frequency provided to the UART.
 
-$$ NCO = {{2^{20} * f_{baud}} \over {f_{pclk}}} $$
+$$ NCO = {{2^{20} * f\_{baud}} \over {f\_{pclk}}} $$
 
 Note that the NCO result from the above formula can be a fraction but
 the NCO register only accepts an integer value. This will create an
@@ -185,8 +185,8 @@
 can be supported, however if it is too far off an integer then the
 baud rate cannot be supported. This check is needed when
 
-$$ {{baud} < {{40 * f_{pclk}} \over {2^{20}}}} \qquad OR \qquad
-{{f_{pclk}} > {{{2^{20}} * {baud}} \over {40}}} $$
+$$ {{baud} < {{40 * f\_{pclk}} \over {2^{20}}}} \qquad OR \qquad
+{{f\_{pclk}} > {{{2^{20}} * {baud}} \over {40}}} $$
 
 Using rounded frequencies and common baud rates, this implies that
 care is needed for 9600 baud and below if the system clock is under
@@ -203,7 +203,7 @@
 If the TX or RX FIFO hits (meaning greater than or equal to) the designated
 depth of entries, interrupts `tx_watermark` or `rx_watermark` are raised to
 inform FW.  FW can configure the watermark value via registers
-!!FIFO_CTRL.RXILVL or !!FIFO_CTRL.TXILVL.
+{{< regref "FIFO_CTRL.RXILVL" >}} or {{< regref "FIFO_CTRL.TXILVL" >}}.
 
 If either FIFO receives an additional write request when its FIFO is full,
 the interrupt `tx_overflow` or `rx_overflow` is asserted and the character
@@ -212,7 +212,7 @@
 The `rx_break_err` interrupt is triggered if a break condition has
 been detected. A break condition is defined as the RX pin being
 continuously low for more than a programmable number of
-character-times (via !!CTRL.RXBLVL, either 2, 4, 8, or 16). A
+character-times (via {{< regref "CTRL.RXBLVL" >}}, either 2, 4, 8, or 16). A
 character time is 10 bit-times if parity is disabled (START + 8 data +
 STOP) or 11 bit-times if parity is enabled (START + 8 data + parity +
 STOP). If the UART is connected to an external connector this would
@@ -225,10 +225,10 @@
 and no break is generated.)  Note that only one interrupt is generated
 per break -- the line must return high for at least half a bit-time
 before an additional break interrupt is generated. The current break
-status can be read from the !!STATUS.BREAK bit. If STATUS.BREAK is set
-but !!INTR_STATE.BREAK is clear then the line break has already caused
+status can be read from the {{< regref "STATUS.BREAK" >}} bit. If STATUS.BREAK is set
+but {{< regref "INTR_STATE.BREAK" >}} is clear then the line break has already caused
 an interrupt that has been cleared but the line break is still going
-on. If !!STATUS.BREAK is clear but !!INTR_STATE.BREAK is set then
+on. If {{< regref "STATUS.BREAK" >}} is clear but {{< regref "INTR_STATE.BREAK" >}} is set then
 there has been a line break for which software has not cleared the
 interrupt but the line is now back to normal.
 
@@ -242,7 +242,7 @@
 zero and one frame error interrupt will be raised. If the line stays zero until
 the break error occurs, the frame error will be set at every char-times.
 
-```wavejson
+{{< wavejson >}}
 {
   signal: [
     { name: 'Baud Clock',        wave: 'p............'                                                 },
@@ -265,7 +265,7 @@
     tock: -2,
   }
 }
-```
+{{< /wavejson >}}
 
 The effects of the line being low for certain periods are summarized
 in the table:
@@ -280,7 +280,7 @@
 
 The `rx_timeout` interrupt is triggered when the RX FIFO has data sitting
 in it without software reading it for a programmable number of bit times
-(with baud rate clock as reference, programmable via !!TIMEOUT_CTRL). This
+(with baud rate clock as reference, programmable via {{< regref "TIMEOUT_CTRL" >}}). This
 is used to alert software that it has data still waiting in the FIFO that
 has not been handled yet. The timeout counter is reset whenever the FIFO depth
 is changed or `rx_timeout` event occurs. If FIFO is full and new character is
@@ -294,11 +294,11 @@
 
 The `rx_parity_err` interrupt is triggered if parity is enabled and
 the RX parity bit does not match the expected polarity as programmed
-in !!CTRL.PARITY_ODD.
+in {{< regref "CTRL.PARITY_ODD" >}}.
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
 The following code snippet shows initializing the UART to a programmable
 baud rate, clearing the RX and TX FIFO, setting up the FIFOs for interrupt
@@ -306,7 +306,7 @@
 rate, and should be set to `(2^20*baud)/freq`, where `freq` is the fixed
 clock frequency. The UART uses `clock_primary` as a clock source.
 
-$$ NCO = {{2^{20} * f_{baud}} \over {f_{pclk}}} $$
+$$ NCO = {{2^{20} * f\_{baud}} \over {f\_{pclk}}} $$
 
 Note that the NCO result from the above formula can be a fraction but
 the NCO register only accepts an integer value. See the the
@@ -350,7 +350,7 @@
 }
 ```
 
-{{% section2 Common Examples }}
+## Common Examples
 
 The following code shows the steps to transmit a string of characters.
 
@@ -385,7 +385,7 @@
 }
 ```
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 The code below shows one example of how to handle all UART interrupts
 in one service routine.
@@ -438,7 +438,7 @@
 }
 ```
 
-One use of the `rx_timeout` interrupt is when the !!FIFO_CTRL.RXILVL
+One use of the `rx_timeout` interrupt is when the {{< regref "FIFO_CTRL.RXILVL" >}}
 is set greater than one, so an interrupt is only fired when the fifo
 is full to a certain level. If the remote device sends fewer than the
 watermark number of characters before stopping sending (for example it
@@ -453,6 +453,6 @@
 case the host will eventually get a watermark interrupt, this will happen
 `((RXILVL - 1)*timeout)` after the first character was received.
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/uart/data/uart.hjson" >}}
diff --git a/hw/ip/uart/doc/uart_dv_plan.md b/hw/ip/uart/doc/dv_plan/index.md
similarity index 71%
rename from hw/ip/uart/doc/uart_dv_plan.md
rename to hw/ip/uart/doc/dv_plan/index.md
index ae7a69b..2cdd744 100644
--- a/hw/ip/uart/doc/uart_dv_plan.md
+++ b/hw/ip/uart/doc/dv_plan/index.md
@@ -1,7 +1,7 @@
-{{% lowrisc-doc-hdr UART DV Plan }}
-{{% import_testplan ../data/uart_testplan.hjson }}
+---
+title: "UART DV Plan"
+---
 
-{{% toc 4 }}
 
 ## Goals
 * **DV**
@@ -11,15 +11,16 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage](../../../../doc/project/hw_dashboard.md)
-  * [HW development stages](../../../../doc/ug/hw_stages.md)
+* [Design & verification stage]({{< relref "doc/project/hw_dashboard" >}})
+  * [HW development stages]({{< relref "doc/ug/hw_stages" >}})
 * DV regression results dashboard (link TBD)
 
 ## Design features
-For detailed information on UART design features, please see the [UART design specification](uart.md).
+For detailed information on UART design features, please see the [UART design specification]({{< relref "hw/ip/uart/doc" >}}).
 
 ## Testbench architecture
-UART testbench has been constructed based on the [CIP testbench architecture](../../../dv/sv/cip_lib/README.md).
+UART testbench has been constructed based on the
+[CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -27,16 +28,16 @@
 ### Top level testbench
 Top level testbench is located at `hw/ip/uart/dv/tb/tb.sv`. It instantiates the UART DUT module `hw/ip/uart/rtl/uart.sv`.
 In addition, it instantiates the following interfaces, connects them to the DUT and sets their handle into `uvm_config_db`:
-* [Clock and reset interface](../../../dv/sv/common_ifs/README.md)
-* [TileLink host interface](../../../dv/sv/tl_agent/README.md)
+* [Clock and reset interface]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 * UART IOs
-* Interrupts ([`pins_if`](../../../dv/sv/common_ifs/README.md))
+* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs/README.md" >}}))
 
 ### Common DV utility components
 The following utilities provide generic helper tasks and functions to perform activities that are common across the project:
-* [common_ifs](../../../dv/sv/common_ifs/README.md)
-* [dv_utils_pkg](../../../dv/sv/dv_utils/README.md)
-* [csr_utils_pkg](../../../dv/sv/csr_utils/README.md)
+* [common_ifs]({{< relref "hw/dv/sv/common_ifs/README.md" >}})
+* [dv_utils_pkg]({{< relref "hw/dv/sv/dv_utils/README.md" >}})
+* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/README.md" >}})
 
 ### Global types & methods
 All common types and methods defined at the package level can be found in
@@ -47,16 +48,16 @@
 ```
 
 ### TL_agent
-UART instantiates (already handled in CIP base env) [tl_agent](../../../dv/sv/tl_agent/README.md)
+UART instantiates (already handled in CIP base env) [tl_agent]({{< relref "hw/dv/sv/tl_agent/README.md" >}})
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into UART device.
 
 ### UART agent
-[UART agent](../../../dv/sv/uart_agent/README.md) is used to drive and monitor UART items, which also provides basic coverage on
+[UART agent]({{< relref "hw/dv/sv/uart_agent/README.md" >}}) is used to drive and monitor UART items, which also provides basic coverage on
 data, parity, baud rate etc.
 
 ### RAL
-The UART RAL model is constructed using the [regtool.py script](../../../../util/reggen/README.md) and is placed at `env/uart_reg_block.sv`.
+The UART RAL model is constructed using the [regtool.py script]({{< relref "util/reggen/README.md" >}}) and is placed at `env/uart_reg_block.sv`.
 
 ### Stimulus strategy
 #### Test sequences
@@ -85,11 +86,11 @@
 * uart_tx_fifo, uart_rx_fifo:     These 2 fifos provides UART TX and RX item when its transfer completes
 
 #### Assertions
-* TLUL assertions: The `tb/uart_bind.sv` binds the `tlul_assert` [assertions](../../tlul/doc/TlulProtocolChecker.md) to the IP to ensure TileLink interface protocol compliance.
+* TLUL assertions: The `tb/uart_bind.sv` binds the `tlul_assert` [assertions]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}) to the IP to ensure TileLink interface protocol compliance.
 * Unknown checks on DUT outputs: The RTL has assertions to ensure all outputs are initialized to known values after coming out of reset.
 
 ## Building and running tests
-We are using our in-house developed [regression tool](../../../dv/tools/README.md) for building and running our tests and regressions.
+We are using our in-house developed [regression tool]({{< relref "hw/dv/tools/README.md" >}}) for building and running our tests and regressions.
 Please take a look at the link for detailed information on the usage, capabilities, features and known issues.
 Here's how to run a basic sanity test:
 ```console
@@ -98,4 +99,4 @@
 ```
 
 ## Testplan
-{{% insert_testplan x }}
+{{< testplan "hw/ip/uart/data/uart_testplan.hjson" >}}
diff --git a/hw/ip/uart/doc/tb.svg b/hw/ip/uart/doc/dv_plan/tb.svg
similarity index 100%
rename from hw/ip/uart/doc/tb.svg
rename to hw/ip/uart/doc/dv_plan/tb.svg
diff --git a/hw/ip/usbdev/doc/usbdev.md b/hw/ip/usbdev/doc/index.md
similarity index 87%
rename from hw/ip/usbdev/doc/usbdev.md
rename to hw/ip/usbdev/doc/index.md
index 720c74c..bfe6e59 100644
--- a/hw/ip/usbdev/doc/usbdev.md
+++ b/hw/ip/usbdev/doc/index.md
@@ -1,12 +1,12 @@
-{{% lowrisc-doc-hdr Simple USB Full Speed Device IP Technical Specification }}
-{{% regfile ../data/usbdev.hjson }}
+---
+title: "Simple USB Full Speed Device IP Technical Specification"
+---
 
-{{% section1 Overview }}
-
-{{% toc 3 }}
+# Overview
 
 
-{{% section2 Features }}
+
+## Features
 
 - USB Full-Speed (12Mbps) Device interface
 
@@ -28,7 +28,7 @@
 - Interrupts for packet reception and transmission
 
 
-{{% section2 Description }}
+## Description
 
 The USB device module is a simple software-driven gneric USB device
 interface for Full-Speed USB operation. The IP includes the physical
@@ -37,13 +37,13 @@
 buffer interface to the software.
 
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The USB device programming interface is not based on any existing
 interface.
 
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 A useful quick reference for USB Full-Speed is
 [http://www.usbmadesimple.co.uk/ums_3.htm](http://www.usbmadesimple.co.uk/ums_3.htm)
@@ -54,7 +54,7 @@
 ![Block Diagram](usbdev_block.svg "image_tooltip")
 
 
-{{% section2 Clocking }}
+## Clocking
 
 The USB Full-Speed interface runs at a 12MHz datarate. The interface
 runs at four times this and must be clocked from an accurate 48MHz
@@ -67,7 +67,7 @@
 asynchronous buffer SRAM is used for data transfers between the bus
 clock and USB clock domains.
 
-{{% section2 USB Interface Pins }}
+## USB Interface Pins
 
 The interface pin table summarizes the external pins.
 
@@ -162,12 +162,12 @@
 (for USB-C where VBUS can be up to 20V) active level translation to an
 acceptable voltage for the input pin.
 
-{{% hwcfg usb_dev }}
+{{< hwcfg "hw/ip/usbdev/data/usbdev.hjson" >}}
 
-{{% section2 USB Link State }}
+## USB Link State
 
 The USB link has a number of states. These are detected and reported
-in !!usbstat.link_state and state changes are reported using
+in {{< regref "usbstat.link_state" >}} and state changes are reported using
 interrupts.
 
 |State| Description |
@@ -179,7 +179,7 @@
 |Host Lost| The host lost interrupt will be signalled if the link is active but a start of frame has not been received from the host in 4.096ms. The host is required to send a SOF every 1ms. This is not an expected condition.|
 
 
-{{% section2 USB Protocol Engine }}
+## USB Protocol Engine
 
 The USB 2.0 Full Speed Protocol Engine is provided by the common USB
 interface code and is not part of this module.
@@ -197,7 +197,7 @@
 may cancel a transaction because of a bad CRC or request a retry if an
 ACK was not received.
 
-{{% section2 Buffer Interface }}
+## Buffer Interface
 
 A 2k Byte SRAM is used to hold data between the system and the USB
 interface. This is divided up into 32 buffers each containing 64
@@ -216,7 +216,7 @@
 The software provides buffers for packet reception through a 4-entry
 available-buffer FIFO. (More study needed but 4 seems about right: one
 just returned to software, one being filled, one ready to be filled
-and one for luck.) The !!RxEnable register is used to indicate which
+and one for luck.) The {{< regref "RxEnable" >}} register is used to indicate which
 endpoints will accept data from the host using SETUP or OUT
 transactions. When a packet is transferred from the host to the device
 (using an OUT or SETUP transaction) and reception of that type of
@@ -241,20 +241,20 @@
 
 To send data to the host in response to an IN transaction the software
 writes the data into a free buffer and writes the buffer number, data
-length and a ready flag to the !!configin register for the endpoint
+length and a ready flag to the {{< regref "configin" >}} register for the endpoint
 that the data is from. When the host next does an IN transaction to
 that endpoint the data will be sent from the buffer. On receipt of the
-ACK from the host the ready flag in the !!configin register will be
+ACK from the host the ready flag in the {{< regref "configin" >}} register will be
 cleared and the bit corresponding to the endpoint number will be set
-in the !!in_sent register which will cause a pkt_sent
+in the {{< regref "in_sent" >}} register which will cause a pkt_sent
 interrupt. Software can return the buffer to the free pool and write a
-1 to clear the bit in the !!in_sent register. Note that streaming can be
+1 to clear the bit in the {{< regref "in_sent" >}} register. Note that streaming can be
 achieved if the next buffer has been prepared and is written to the
-!!configin register when the interrupt is received.
+{{< regref "configin" >}} register when the interrupt is received.
 
 A Control transfer may need an IN data transfer. Therefore when a
 SETUP transaction is received for an endpoint the ready bit is cleared
-in !!configin to cancel any buffer that was pending being sent to the
+in {{< regref "configin" >}} to cancel any buffer that was pending being sent to the
 host from the Endpoint. When this is done the pending bit will be
 set. The transfer must be queued again after the Control transfer is
 completed.
@@ -264,18 +264,18 @@
 
 In general the 32 buffers will be allocated with one being processed
 following reception, 4 in the available-buffer FIFO and 12 (worst
-case) waiting transmission in the !!configin registers. This leaves 15
+case) waiting transmission in the {{< regref "configin" >}} registers. This leaves 15
 for preparation of next transmission (which would need 12 in the worst
 case of one per endpoint) and the free pool.
 
 
-{{% section1 Design Details }}
+# Design Details
 
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
 
-{{% section2 Initialization }}
+## Initialization
 
 The basic hardware initialization is to (in any order) fill the
 Available buffer FIFO, enable reception of SETUP and OUT packets on
@@ -283,24 +283,24 @@
 configure the interface), enable reception of SETUP and OUT packets on
 any endpoints that accept them and enable any required
 interrupts. Finally the interface is enabled by setting the
-!!usbctrl.enable bit. Setting this bit will cause the USB device to
+{{< regref "usbctrl.enable" >}} bit. Setting this bit will cause the USB device to
 assert the Full-Speed pullup on the D+ line, which is used by the host
 to detect the device. In most cases there is no need to configure the
-device ID (!!usbctrl.device_address) at this point -- the line will be
+device ID ({{< regref "usbctrl.device_address" >}}) at this point -- the line will be
 in reset and the hardware will have forced the device ID to zero.
 
 The second stage of initialization is done under control of the host,
 which will use control transfers (always beginning with SETUP
 transactions) to Endpoint 0. Initially these will be sent to device
 ID 0. When a Set Address request is received the device ID received
-must be stored in the !!usbctrl.device_address register. Note that
+must be stored in the {{< regref "usbctrl.device_address" >}} register. Note that
 device 0 is used for the entire control transaction setting the new
 device ID, so writing the new ID to the register should not be done
 until the ACK for the Status stage (see USB specification) has been
 received.
 
 
-{{% section2 Buffers }}
+## Buffers
 
 The driver needs to manage the buffers in the interface SRAM. Each
 buffer can hold the maximum length packet (64 bytes). Other than for
@@ -325,13 +325,13 @@
 worse when a burst of packets are received.
 
 Flow control (using NAKs) may be done per-endpoint using the
-!!rxenable register. If this does not indicate OUT packet reception
+{{< regref "rxenable" >}} register. If this does not indicate OUT packet reception
 is enabled then any packet will receive a NAK to request a retry
 later. This should only be done for short durations or the host may
 timeout the transaction.
 
 
-{{% section2 Reception }}
+## Reception
 
 The host will send OUT or SETUP transactions when it wants to transfer
 data to the device. The data packets are directed to a particular
@@ -339,7 +339,7 @@
 Endpoint Descriptor (this must be the same or smaller than the maximum
 packet size supported by the device). A received interrupt is raised
 whenever there is one or more packets in the RX FIFO. The driver
-should pop the information from the FIFO by reading the !!rxfifo
+should pop the information from the FIFO by reading the {{< regref "rxfifo" >}}
 register, which gives (a) the buffer ID that the data was received in
 (b) the data length, in bytes, received (c) the endpoint to which the
 packet was sent (d) an indication if the packet was sent with an OUT
@@ -353,7 +353,7 @@
 hardware will request a retry.
 
 
-{{% section2 Transmission }}
+## Transmission
 
 Data is transferred to the host based on the host requesting a
 transfer with an IN transaction. The host will only generate IN
@@ -363,17 +363,17 @@
 includes a description of the frequency the endpoint should be polled.
 
 Data is queued for transmission by writing the corresponding
-!!configin register with the buffer ID containing the data, the
+{{< regref "configin" >}} register with the buffer ID containing the data, the
 length in bytes of data (0 to maximum packet length) and setting the
 Rdy bit. This data (with the packet CRC) will be sent as a response to
 the next IN transaction on the endpoint. When the host ACKs the data
 the Rdy bit is cleared, the bit corresponding to the endpoint is set
-in the !!in_sent register and a transmit interrupt is raised. If the
+in the {{< regref "in_sent" >}} register and a transmit interrupt is raised. If the
 host does not ACK the data then the packet will be retried. When the
 packet transmission has been noted by the driver the endpoint bit
-should be cleared by writing a 1 to it in the !!in_sent register.
+should be cleared by writing a 1 to it in the {{< regref "in_sent" >}} register.
 
-Note that the !!configin for an endpoint is a single register, so no
+Note that the {{< regref "configin" >}} for an endpoint is a single register, so no
 new data packet should be queued until the previous packet has been
 acknowledged. This causes a problem if a Control Transaction is
 received on an endpoint with a transmission pending because the
@@ -381,21 +381,21 @@
 will **clear the rdy bit** if an enabled SETUP transaction is received
 on any endpoint and **set the pending bit** if there was data
 pending. The driver must remember the pending transfer and, after the
-Control transaction is complete, write it back to the !!configin
+Control transaction is complete, write it back to the {{< regref "configin" >}}
 register with the rdy bit set.
 
 
-{{% section2 Stall }}
+## Stall
 
-The !!stall register is used to Stall an endpoint. This is used if it
+The {{< regref "stall" >}} register is used to Stall an endpoint. This is used if it
 is shutdown for some reason, or to signal certain error
-conditions. Unused endpoints can have their !!stall register bit left
-clear, so in many cases there is no need to use the !!stall
+conditions. Unused endpoints can have their {{< regref "stall" >}} register bit left
+clear, so in many cases there is no need to use the {{< regref "stall" >}}
 register. If the stall bit is set for an endpoint then the STALL
 response will be provided to any (IN, OUT or SETUP) request on that
 endpoint.
 
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/usbdev/data/usbdev.hjson" >}}
diff --git a/hw/ip/usbuart/doc/usbuart.md b/hw/ip/usbuart/doc/index.md
similarity index 86%
rename from hw/ip/usbuart/doc/usbuart.md
rename to hw/ip/usbuart/doc/index.md
index 40b9651..a4d5ca9 100644
--- a/hw/ip/usbuart/doc/usbuart.md
+++ b/hw/ip/usbuart/doc/index.md
@@ -1,17 +1,17 @@
-{{% lowrisc-doc-hdr USB UART HWIP Technical Specification }}
-{{% regfile ../data/usbuart.hjson}}
+---
+title: "USB UART HWIP Technical Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 This document specifies the USB UART hardware IP functionality. This module
 conforms to the
-[Comportable guideline for peripheral functionality.](../../../../doc/rm/comportability_specification.md)
+[Comportable guideline for peripheral functionality.]({{< relref "doc/rm/comportability_specification" >}})
 See that document for integration overview within the broader
 top level system.
 
-{{% toc 3 }}
 
-{{% section2 Features }}
+## Features
 
 - External full speed USB device interface with UART interface to CPU
 - Software developed for the Comportable UART should work without changes
@@ -24,7 +24,7 @@
 - Interrupt for overflow, break error, receive timeout. Frame and
   parity errors will not happen but may be configured.
 
-{{% section2 Description }}
+## Description
 
 The USBUART module is a USB Full Speed device interface that presents
 the standard Comportable UART interface to the system. The serial line
@@ -32,14 +32,14 @@
 characters flow directly over the Google USB Simple Serial Class
 interface.
 
-{{% section2 Compatibility }}
+## Compatibility
 
 The UART is compatible with the interface provided by the Comportable UART.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
 The register inteface to the USBUART matches the standard UART. There
-are two additional registers !!USBSTAT and !!USBPARAM that provide
+are two additional registers {{< regref "USBSTAT" >}} and {{< regref "USBPARAM" >}} that provide
 information about the USB interface that may be useful for software
 that knows this is more than the simple uart. In particular when the
 interface is used to bridge from USB to a real uart interface (for
@@ -48,15 +48,15 @@
 the requested Baud rate and parity may be read.
 
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![UART Block Diagram](block_diagram.svg)
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
-{{% hwcfg uart}}
+{{< hwcfg "hw/ip/usbuart/data/usbuart.hjson" >}}
 
-{{% section2 Design Details }}
+## Design Details
 
 ### USB interface
 
@@ -87,14 +87,14 @@
 from the host as though received by the UART and IN endpoint 1 that is
 used to transmit data from the UART transmit fifo to the
 host. Endpoint 1 will respond to SETUP packets to allow the host to
-read or write the baud rate and parity passed in the !!USBPARAM
+read or write the baud rate and parity passed in the {{< regref "USBPARAM" >}}
 register. (Note the current software implementation only implements
 the host writing these, so for portability it is best to avoid reads
 at this time.)
 
 ### Transmission
 
-Just as in the standard UART, a write to !!WDATA enqueues a data byte
+Just as in the standard UART, a write to {{< regref "WDATA" >}} enqueues a data byte
 into the 32 depth write FIFO. Characters will be moved from the fifo
 to be available to send when an IN request is received from the host
 on endpoint 1. The interface attempts to gather characters. An IN
@@ -108,7 +108,7 @@
 Characters received from the OUT endpoint are inserted in to the
 32-byte receive FIFO. Backpressure is applied to the host if the
 receive FIFO does not have room for a full USB packet.
-The received data can be read from the !!RDATA register.
+The received data can be read from the {{< regref "RDATA" >}} register.
 
 ### Interrupts
 
@@ -117,7 +117,7 @@
 
 If TX or RX FIFO hits designated depth of entries, `txint` or `rxint`
 signal is raised to inform FW.  FW can configure the watermark value
-!!FIFO_CTRL.RXILVL or !!FIFO_CTRL.TXILVL .
+{{< regref "FIFO_CTRL.RXILVL" >}} or {{< regref "FIFO_CTRL.TXILVL" >}} .
 
 In any case, if any FIFO receives write request when FIFO is full,
 `tx_overflow` or `rx_overflow` interrupts is asserted.
@@ -128,16 +128,16 @@
 host has not sent a Start-of-Frame for 2.048ms (an SOF should happen
 every 1ms).
 
-{{% section1 Programmers Guide }}
+# Programmers Guide
 
-{{% section2 Initialization }}
+## Initialization
 
 The UART does not need initialization (default is for usb_pullup to be
 applied).  However, since the settings will be ignored it is possible
 to run the initialization sequence for the standard UART.
 
 
-{{% section2 Common Examples }}
+## Common Examples
 
 Do the following to transmit a string of characters.
 
@@ -171,7 +171,7 @@
 }
 ```
 
-{{% section2 Interrupt Handling }}
+## Interrupt Handling
 
 ```cpp
 void uart_interrupt_routine() {
@@ -221,6 +221,6 @@
 }
 ```
 
-{{% section2 Register Table }}
+## Register Table
 
-{{% registers x }}
+{{< registers "hw/ip/usbuart/data/usbuart.hjson" >}}
diff --git a/hw/top_earlgrey/doc/top_earlgrey.md b/hw/top_earlgrey/doc/index.md
similarity index 96%
rename from hw/top_earlgrey/doc/top_earlgrey.md
rename to hw/top_earlgrey/doc/index.md
index 8b10123..67eafb2 100644
--- a/hw/top_earlgrey/doc/top_earlgrey.md
+++ b/hw/top_earlgrey/doc/index.md
@@ -1,6 +1,8 @@
-{{% lowrisc-doc-hdr Earl Grey Top Level Specification }}
+---
+title: "Earl Grey Top Level Specification"
+---
 
-{{% section1 Overview }}
+# Overview
 
 <img src="earlgrey.png" align="right" alt="Earl Grey logo" title="Earl Grey" hspace="25"/>
 This document specifies the top level functionality of the "Earl Grey"
@@ -9,7 +11,7 @@
 It is the expected subset of functionality needed to correspond with the public launch of the OpenTitan development endeavor.
 The new features and methodologies expected for 0.5 are detailed here, with discussions about the totality of its content w.r.t. our goals for the public launch.
 
-{{% section2 Features }}
+## Features
 
 - RISC-V microprocessor ("Ibex") and associated JTAG IO. Related features:
   - DM (debug module)
@@ -42,7 +44,7 @@
   - Rudimentary operating system
   - Example applications and validation tests
 
-{{% section2 Description }}
+## Description
 
 The netlist `top_earlgrey` is defined to contain just enough features to
 prove basic functionality of the Ibex RISC-V processor core on an FPGA
@@ -60,13 +62,13 @@
 This script (or set of scripts) generates the interconnecting crossbar (via `TLUL`) as well as the instantiations at the top level.
 It also feeds into the document generation to ensure that the chosen address locations are documented automatically using the data in the source files.
 
-{{% section1 Theory of Operations }}
+# Theory of Operations
 
-{{% section2 Block Diagram }}
+## Block Diagram
 
 ![Top Level Block Diagram](top_earlgrey_block_diagram.svg)
 
-{{% section2 Hardware Interfaces }}
+## Hardware Interfaces
 
 | Signal Name | Direction | Description |
 | --- | --- | --- |
@@ -87,7 +89,7 @@
 | `IO_SHDO`   | input  | SPI host output data |
 | `MIO_00` .. `MIO_23` | inout  | Multiplexible pins available for input or output connections with peripheral units' (UART, GPIO, etc.) IO |
 
-{{% section2 Design Details }}
+## Design Details
 
 This 0.5 version of the top level design contains features to prove some fundamental security functionality through some basic peripherals.
 This section provides some details for the processor and the peripherals.
@@ -142,7 +144,7 @@
 
 
 See also the
-[OpenTitan PLIC specification](https://bubble.servers.lowrisc.org/hw/ip/rv_plic/doc/rv_plic.html)
+[OpenTitan PLIC specification]({{< relref "hw/ip/rv_plic/doc" >}})
 for more details.
 
 #### Performance
@@ -206,7 +208,7 @@
 
 Earl Grey contains a suite of "peripherals", or subservient execution units connected to the Ibex processor by means of a bus interconnect.
 Each of these peripherals follows an interface scheme dictated in the
-[Comportability Specification.](../../../doc/rm/comportability_specification.md)
+[Comportability Specification.]({{< relref "doc/rm/comportability_specification" >}})
 This specification details how the processor communicates with the peripheral (via TLUL interconnect); how the peripheral communicates with the chip IO (via fixed or multiplexable IO); how the peripheral communicates with the processor (interrupts); and how the peripheral communicates security events (via alerts).
 See that specification for generic details on this scheme.
 
@@ -229,7 +231,7 @@
 These registers include a selection of every chip output as to which peripheral output drives it; a selection of every peripheral input as to what chip input drives it; and pad control for all pin IO.
 Details for this module will come at a later date.
 See the
-[pinmux specification](https://bubble.servers.lowrisc.org/hw/ip/pinmux/doc/pinmux.html)
+[pinmux specification]({{< relref "hw/ip/pinmux/doc" >}})
 for how to connect peripheral IO to chip IO.
 
 ##### UART
@@ -238,7 +240,7 @@
 There are two versions of the same UART included in the 0.5 release.
 Their outputs and inputs can be configured to any chip IO via the pinmux.
 See the
-[UART specification](https://bubble.servers.lowrisc.org/hw/ip/uart/doc/uart.html)
+[UART specification]({{< relref "hw/ip/uart/doc" >}})
 for more details on this peripheral.
 
 ##### GPIO
@@ -247,10 +249,10 @@
 Since currently only 24 pins of multiplexable IO (MIO) are specified for Earl Grey, not all of its capability can be realized at this time.
 But via pinmux any of the 32 pins of GPIO can be connected to any of the 24 chip pins, in any direction.
 See the
-[GPIO specification](https://bubble.servers.lowrisc.org/hw/ip/gpio/doc/gpio.html)
+[GPIO specification]({{< relref "hw/ip/gpio/doc" >}})
 for more details on this peripheral.
 See the
-[pinmux specification](https://bubble.servers.lowrisc.org/hw/ip/pinmux/doc/pinmux.html)
+[pinmux specification]({{< relref "hw/ip/pinmux/doc" >}})
 for how to connect peripheral IO to chip IO.
 
 ##### SPI device
@@ -261,7 +263,7 @@
 For the 0.5 release only single-mode functionality is required.
 
 See the
-[SPI device specification](https://bubble.servers.lowrisc.org/hw/ip/spi_device/doc/spi_device.html)
+[SPI device specification]({{< relref "hw/ip/spi_device/doc" >}})
 for more details on the Firmware Mode implementation.
 
 For SPI passthrough timing reasons, the SPI device pins will be hardwired to Earl Grey chip IO pins as shown in the block diagram above.
@@ -462,7 +464,7 @@
 In future, requirements for low power functionality (when the primary high precision high speed system clock is inactive) will likely be added.
 
 The specification for the timer can be found
-[here](https://bubble.servers.lowrisc.org/hw/ip/timer/doc/timer.html).
+[here]({{< relref "hw/ip/rv_timer/doc" >}}).
 
 ##### Flash Controller
 
@@ -496,10 +498,10 @@
 
 Interconnecting the processor and peripheral and memory units is a bus
 network built upon the TileLink-Uncached-Light protocol. See the
-[OpenTitan bus specification](https://bubble.servers.lowrisc.org/hw/ip/tlul/doc/tlul.html)
+[OpenTitan bus specification]({{< relref "hw/ip/tlul/doc" >}})
 for more details.
 
-{{% section2 Register Table }}
+## Register Table
 
 The base and bound addresses of the memory and peripherals are given in this
 table below. This is cut/paste at the moment, will be automated in the future.
diff --git a/site/docs/.gitignore b/site/docs/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/site/docs/.gitignore
diff --git a/site/docs/assets/scss/style.scss b/site/docs/assets/scss/style.scss
new file mode 100644
index 0000000..bc406f0
--- /dev/null
+++ b/site/docs/assets/scss/style.scss
@@ -0,0 +1 @@
+body { margin: 0; }
diff --git a/site/docs/config.toml b/site/docs/config.toml
new file mode 100644
index 0000000..de5ed51
--- /dev/null
+++ b/site/docs/config.toml
@@ -0,0 +1,46 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# CAUTION: Use util/build_docs.py to generate the documentation site.  There
+# are pre-processing steps that are not yet integrated into the hugo-based
+# build.
+
+assetDir = "site/docs/assets"
+baseURL = "https://docs.opentitan.org"
+disablePathToLower = true
+contentDir = "../.."
+resourceDir = "build/docs-resources"
+ignoreFiles = [
+  ".*\\.S$",
+  ".*\\.bin$",
+  ".*\\.c$",
+  ".*\\.cfg$",
+  ".*\\.core$",
+  ".*\\.dat$",
+  ".*\\.f$",
+  ".*\\.h$",
+  ".*\\.hjson$",
+  ".*\\.ld$",
+  ".*\\.mk$",
+  ".*\\.o$",
+  ".*\\.py$",
+  ".*\\.sh$",
+  ".*\\.sv$",
+  ".*\\.svh$",
+  ".*\\.tcl$",
+  ".*\\.tpl$",
+  ".*\\.txt$",
+  ".*\\.xdc$",
+  ".*\\.yml$",
+  "/build/*",
+  "/hw/vendor/*",
+  "/site/*",
+  "Makefile$",
+]
+pygmentsCodeFences = true
+pygmentsStyle = "github"
+disableKinds = ["taxonomy", "taxonomyTerm", "RSS", "sitemap"]
+
+[params]
+generatedRoot = "build/docs-generated"
diff --git a/site/docs/layouts/_default/list.html b/site/docs/layouts/_default/list.html
new file mode 100644
index 0000000..a70f59a
--- /dev/null
+++ b/site/docs/layouts/_default/list.html
@@ -0,0 +1,7 @@
+<html>
+  {{ partial "head.html" . }}
+  <body onload="WaveDrom.ProcessAll()">
+    {{ .Content }}
+    {{ partial "footer.html" . }}
+  </body>
+</html>
diff --git a/site/docs/layouts/_default/single.html b/site/docs/layouts/_default/single.html
new file mode 100644
index 0000000..a70f59a
--- /dev/null
+++ b/site/docs/layouts/_default/single.html
@@ -0,0 +1,7 @@
+<html>
+  {{ partial "head.html" . }}
+  <body onload="WaveDrom.ProcessAll()">
+    {{ .Content }}
+    {{ partial "footer.html" . }}
+  </body>
+</html>
diff --git a/site/docs/layouts/partials/footer.html b/site/docs/layouts/partials/footer.html
new file mode 100644
index 0000000..5d6adfc
--- /dev/null
+++ b/site/docs/layouts/partials/footer.html
@@ -0,0 +1,3 @@
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/2.1.2/skins/default.js"></script>
+<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/2.1.2/wavedrom.js"></script>
diff --git a/site/docs/layouts/partials/head.html b/site/docs/layouts/partials/head.html
new file mode 100644
index 0000000..a689a98
--- /dev/null
+++ b/site/docs/layouts/partials/head.html
@@ -0,0 +1,8 @@
+<head>
+  <meta charset="UTF-8">
+  <link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
+  <title>{{ if .Title }}{{ .Title }} | {{ end }}OpenTitan Documentation</title>
+  {{ $options := (dict "targetPath" "css/style.css" "outputStyle" "compressed" "enableSourceMap" true) }}
+  {{ $style := resources.Get "scss/style.scss" | resources.ToCSS $options }}
+  <link rel="stylesheet" href="{{ $style.Permalink }}">
+</head>
diff --git a/site/docs/layouts/shortcodes/dashboard.html b/site/docs/layouts/shortcodes/dashboard.html
new file mode 100644
index 0000000..af00d2a
--- /dev/null
+++ b/site/docs/layouts/shortcodes/dashboard.html
@@ -0,0 +1,15 @@
+<table class="hw-project-dashboard">
+	<thead>
+		<tr>
+			<th>Module</th>
+			<th>Version</th>
+			<th>Life Stage</th>
+			<th>Design Stage</th>
+			<th>Verification Stage</th>
+			<th>Notes</th>
+		</tr>
+	</thead>
+	<tbody>
+{{ readFile (path.Join .Site.Params.generatedRoot (.Get 0) "dashboard") | safeHTML }}
+	</tbody>
+</table>
diff --git a/site/docs/layouts/shortcodes/hwcfg.html b/site/docs/layouts/shortcodes/hwcfg.html
new file mode 100644
index 0000000..40da0e4
--- /dev/null
+++ b/site/docs/layouts/shortcodes/hwcfg.html
@@ -0,0 +1,6 @@
+{{ $baseName := .Get 0 }}
+{{ $path := path.Join .Site.Params.generatedRoot (printf "%s.hwcfg" $baseName) }}
+{{ if not (fileExists $path) }}
+  {{ errorf "hwcfg has not been generated for %s" $baseName }}
+{{ end }}
+{{ readFile $path | safeHTML }}
diff --git a/site/docs/layouts/shortcodes/registers.html b/site/docs/layouts/shortcodes/registers.html
new file mode 100644
index 0000000..6e0d678
--- /dev/null
+++ b/site/docs/layouts/shortcodes/registers.html
@@ -0,0 +1,6 @@
+{{ $baseName := .Get 0 }}
+{{ $path := path.Join .Site.Params.generatedRoot (printf "%s.registers" $baseName) }}
+{{ if not (fileExists $path) }}
+  {{ errorf "Registers have not been generated for %s" $baseName }}
+{{ end }}
+{{ readFile $path | safeHTML }}
diff --git a/site/docs/layouts/shortcodes/regref.html b/site/docs/layouts/shortcodes/regref.html
new file mode 100644
index 0000000..d6f4f67
--- /dev/null
+++ b/site/docs/layouts/shortcodes/regref.html
@@ -0,0 +1 @@
+{{ $name := .Get 0 }}{{ $ref := lower (index (split $name ".") 0) }}<a href="#Reg_{{ $ref }}">{{ $name }}</a>{{- "" -}}
diff --git a/site/docs/layouts/shortcodes/testplan.html b/site/docs/layouts/shortcodes/testplan.html
new file mode 100644
index 0000000..37c7a2f
--- /dev/null
+++ b/site/docs/layouts/shortcodes/testplan.html
@@ -0,0 +1,6 @@
+{{ $baseName := .Get 0 }}
+{{ $path := path.Join .Site.Params.generatedRoot (printf "%s.testplan" $baseName) }}
+{{ if not (fileExists $path) }}
+  {{ errorf "Testplan has not been generated for %s" $baseName }}
+{{ end }}
+{{ readFile $path | safeHTML }}
diff --git a/site/docs/layouts/shortcodes/wavejson.html b/site/docs/layouts/shortcodes/wavejson.html
new file mode 100644
index 0000000..ca1a522
--- /dev/null
+++ b/site/docs/layouts/shortcodes/wavejson.html
@@ -0,0 +1,3 @@
+<script type="WaveDrom">
+  {{ $.Inner }}
+</script>
diff --git a/site/docs/static/LICENSE b/site/docs/static/LICENSE
new file mode 120000
index 0000000..5853aae
--- /dev/null
+++ b/site/docs/static/LICENSE
@@ -0,0 +1 @@
+../../../LICENSE
\ No newline at end of file
diff --git a/sitemap.md b/sitemap.md
deleted file mode 100644
index ad6abe0..0000000
--- a/sitemap.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Sitemap
-
-{{% doctree ./doc ./hw ./sw ./util}}
diff --git a/sw/device/boot_rom/README.md b/sw/device/boot_rom/README.md
index bd5d8b4..3de1997 100644
--- a/sw/device/boot_rom/README.md
+++ b/sw/device/boot_rom/README.md
@@ -1,6 +1,7 @@
 ## Boot ROM Overview
 The boot ROM is always the first piece of code run in the system.
 At the moment, it serves 2 functions:
+
 * Bootstrap additional code if requested.
 * Jump to embedded flash and begin execution.
 
@@ -18,7 +19,7 @@
 The executable image is broken down into 1KB frames and sent one by one by the host over spi.
 Each frame is made up of a header and associated payload.
 The header contains information such as a SHA256 hash of the entire frame, the frame numer and the flash location to which it should be programmed.
-See [spiflash](../host/spiflash/README.md) for more details.
+See [spiflash]({{< relref "sw/host/spiflash/README.md" >}}) for more details.
 
 The boot ROM and host communicate through a request / ACK interface.
 Upon receiving each frame, boot ROM computes the hash of that frame and sends it back to the host.
diff --git a/sw/device/doc/sw_build_flow.md b/sw/device/doc/sw_build_flow.md
index d202973..ce6aa76 100644
--- a/sw/device/doc/sw_build_flow.md
+++ b/sw/device/doc/sw_build_flow.md
@@ -1,6 +1,6 @@
-{{% lowrisc-doc-hdr SW build flow }}
-
-{{% toc 3 }}
+---
+title: "SW build flow"
+---
 
 **The custom Makefile flow has been deprecated in favor of Meson; see sw/README.md for details.** 
 **Makefiles will eventually be deleted from SW, and should not be relied upon.**
diff --git a/sw/host/spiflash/README.md b/sw/host/spiflash/README.md
index 8f327f3..24e1d7b 100644
--- a/sw/host/spiflash/README.md
+++ b/sw/host/spiflash/README.md
@@ -25,7 +25,7 @@
 ```
 
 ## Setup instructions for Verilator and FPGA
-Please refer to [verilator](../../../doc/ug/getting_started_verilator.md) and [fpga](../../../doc/ug/getting_started_verilator.md) docs for more information.
+Please refer to [verilator]({{< relref "doc/ug/getting_started_verilator" >}}) and [fpga]({{< relref "doc/ug/getting_started_fpga" >}}) docs for more information.
 
 ## Build boot ROM and demo program
 
diff --git a/util/_index.md b/util/_index.md
new file mode 100644
index 0000000..4b5e386
--- /dev/null
+++ b/util/_index.md
@@ -0,0 +1,12 @@
+# Tools
+
+These are predominantly Readme's with details about the tooling scripts. Make sure to also check the corresponding [Reference Manuals]({{< relref "doc/rm" >}}).
+
+* [container]({{< relref "./container/README.md" >}})
+* [fpga]({{< relref "./fpga/README.md" >}})
+* [reggen]({{< relref "./reggen/README.md" >}})
+* [simplespi]({{< relref "./simplespi/README.md" >}})
+* [testplanner]({{< relref "./testplanner/README.md" >}})
+* [tlgen]({{< relref "./tlgen/README.md" >}})
+* [uvmdvgen]({{< relref "./uvmdvgen/README.md" >}})
+* [wavegen]({{< relref "./wavegen/README.md" >}})
diff --git a/util/build_docs.py b/util/build_docs.py
index 00389b0..cea7709 100755
--- a/util/build_docs.py
+++ b/util/build_docs.py
@@ -3,130 +3,154 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 #
-# pip3 install --user livereload
 # Usage:
 #   run './build_docs.py' to generate the documentation and keep it updated
-#   open 'http://localhost:5500/' to check live update (this opens the top
+#   open 'http://localhost:1313/' to check live update (this opens the top
 #   level index page). you can also directly access a specific document by
-#   accessing 'http://localhost:5500/path/to/doc.html',
-#       e.g. http://localhost:5500/hw/ip/uart/doc/uart.html
+#   accessing 'http://localhost:1313/path/to/doc',
+#       e.g. http://localhost:1313/hw/ip/uart/doc
 
 import argparse
+import io
 import logging
 import os
 import shutil
+import subprocess
 from pathlib import Path
 
-import livereload
+import hjson
 
-import docgen.generate
+import dashboard.gen_dashboard_entry as gen_dashboard_entry
+import reggen.gen_cfg_html as gen_cfg_html
+import reggen.gen_html as gen_html
+import reggen.validate as validate
+import testplanner.testplan_utils as testplan_utils
 
 USAGE = """
   build_docs [options]
 """
 
-MARKDOWN_EXTENSIONS = [
-    '.md',
-    '.mkd',
-]
-STATIC_ASSET_EXTENSIONS = [
-    '.svg',
-    '.png',
-    '.jpg',
-    '.css',
-]
-HJSON_EXTENSIONS = ['.hjson']
-
 # Configurations
 # TODO: Move to config.yaml
 SRCTREE_TOP = Path(__file__).parent.joinpath('..').resolve()
 config = {
     # Toplevel source directory
-    "topdir": SRCTREE_TOP,
+    "topdir":
+    SRCTREE_TOP,
 
-    # A list of directories containing documentation within topdir. To ensure
-    # the top-level sitemap doesn't have broken links, this should be kept
-    # in-sync with the doctree tag in sitemap.md.
-    "incdirs": ['./doc', './hw', './sw', './util'],
+    # Pre-generate register and hwcfg fragments from these files.
+    "hardware_definitions": [
+        "hw/ip/aes/data/aes.hjson",
+        "hw/ip/alert_handler/data/alert_handler.hjson",
+        "hw/ip/flash_ctrl/data/flash_ctrl.hjson",
+        "hw/ip/gpio/data/gpio.hjson",
+        "hw/ip/hmac/data/hmac.hjson",
+        "hw/ip/i2c/data/i2c.hjson",
+        "hw/ip/padctrl/data/padctrl.hjson",
+        "hw/ip/pinmux/data/pinmux.hjson",
+        "hw/ip/rv_plic/data/rv_plic.hjson",
+        "hw/ip/rv_timer/data/rv_timer.hjson",
+        "hw/ip/spi_device/data/spi_device.hjson",
+        "hw/ip/uart/data/uart.hjson",
+        "hw/ip/usbdev/data/usbdev.hjson",
+        "hw/ip/usbuart/data/usbuart.hjson",
+    ],
+
+    # Pre-generate dashboard fragments from these directories.
+    "dashboard_definitions": [
+        "hw/ip",
+    ],
+
+    # Pre-generate testplan fragments from these files.
+    "testplan_definitions": [
+        "hw/ip/gpio/data/gpio_testplan.hjson",
+        "hw/ip/hmac/data/hmac_testplan.hjson",
+        "hw/ip/i2c/data/i2c_testplan.hjson",
+        "hw/ip/rv_timer/data/rv_timer_testplan.hjson",
+        "hw/ip/uart/data/uart_testplan.hjson",
+        "util/testplanner/examples/foo_testplan.hjson",
+    ],
 
     # Output directory for documents
-    "outdir": SRCTREE_TOP.joinpath('opentitan-docs'),
-    "verbose": False,
+    "outdir":
+    SRCTREE_TOP.joinpath('build', 'docs'),
+    "outdir-generated":
+    SRCTREE_TOP.joinpath('build', 'docs-generated'),
+    "verbose":
+    False,
 }
 
 
-def get_doc_files(extensions=MARKDOWN_EXTENSIONS + STATIC_ASSET_EXTENSIONS):
-    """Get the absolute path of files containing documentation
-    """
-    file_list = []
-    # doc files on toplevel
-    for ext in extensions:
-        file_list += config["topdir"].glob('*' + ext)
-    # doc files in include dirs
-    for incdir in config['incdirs']:
-        for ext in extensions:
-            file_list += config["topdir"].joinpath(incdir).rglob('*' + ext)
-    return file_list
+def generate_dashboards():
+    for dashboard in config["dashboard_definitions"]:
+        hjson_paths = []
+        hjson_paths.extend(
+            sorted(SRCTREE_TOP.joinpath(dashboard).rglob('*.prj.hjson')))
+
+        dashboard_path = config["outdir-generated"].joinpath(
+            dashboard, 'dashboard')
+        dashboard_html = open(dashboard_path, mode='w')
+        for hjson_path in hjson_paths:
+            gen_dashboard_entry.gen_dashboard_html(hjson_path, dashboard_html)
+        dashboard_html.close()
 
 
-def ensure_dest_dir(dest_pathname):
-    os.makedirs(dest_pathname.parent, exist_ok=True)
+def generate_hardware_blocks():
+    for hardware in config["hardware_definitions"]:
+        hardware_file = open(SRCTREE_TOP.joinpath(hardware))
+        regs = hjson.load(hardware_file,
+                          use_decimal=True,
+                          object_pairs_hook=validate.checking_dict)
+        if validate.validate(regs) == 0:
+            logging.info("Parsed %s" % (hardware))
+        else:
+            logging.fatal("Failed to parse %s" % (hardware))
+
+        base_path = config["outdir-generated"].joinpath(hardware)
+        base_path.parent.mkdir(parents=True, exist_ok=True)
+
+        regs_html = open(base_path.parent.joinpath(base_path.name +
+                                                   '.registers'),
+                         mode='w')
+        gen_html.gen_html(regs, regs_html)
+        regs_html.close()
+
+        hwcfg_html = open(base_path.parent.joinpath(base_path.name + '.hwcfg'),
+                          mode='w')
+        gen_cfg_html.gen_cfg_html(regs, hwcfg_html)
+        hwcfg_html.close()
 
 
-def path_src_to_dest(src_pathname, dest_filename_suffix=None):
-    """Get the destination pathname from a source pathname
-    """
-    src_relpath = Path(src_pathname).relative_to(config["topdir"])
-    dest_pathname = Path(config["outdir"]).joinpath(src_relpath)
-    if dest_filename_suffix:
-        dest_pathname = dest_pathname.with_suffix(dest_filename_suffix)
-    return dest_pathname
+def generate_testplans():
+    for testplan in config["testplan_definitions"]:
+        plan = testplan_utils.parse_testplan(SRCTREE_TOP.joinpath(testplan))
+
+        plan_path = config["outdir-generated"].joinpath(testplan + '.testplan')
+        plan_path.parent.mkdir(parents=True, exist_ok=True)
+
+        testplan_html = open(plan_path, mode='w')
+        testplan_utils.gen_html_testplan_table(plan, testplan_html)
+        testplan_html.close()
 
 
-def process_file_markdown(src_pathname):
-    """Process a markdown file and copy it to the destination
-    """
-    dest_pathname = path_src_to_dest(src_pathname, '.html')
-
-    logging.info("Processing Markdown file: %s -> %s" %
-                 (str(src_pathname), str(dest_pathname)))
-
-    ensure_dest_dir(dest_pathname)
-
-    with open(dest_pathname, 'w', encoding='UTF-8') as f:
-        outstr = docgen.generate.generate_doc(str(src_pathname),
-                                              verbose=config['verbose'],
-                                              inlinecss=True,
-                                              inlinewave=True,
-                                              asdiv=False)
-        f.write(outstr)
-
-    return dest_pathname
-
-
-def process_file_copytodest(src_pathname):
-    """Copy a file to the destination directory with no further processing
-    """
-    dest_pathname = path_src_to_dest(src_pathname)
-
-    logging.info("Copying %s -> %s" % (str(src_pathname), str(dest_pathname)))
-
-    ensure_dest_dir(dest_pathname)
-    shutil.copy(src_pathname, dest_pathname)
-
-
-def process_all_files():
-    """Process all files
-
-    The specific processing action depends on the file type.
-    """
-    src_files = get_doc_files()
-
-    for src_pathname in src_files:
-        if src_pathname.suffix in MARKDOWN_EXTENSIONS:
-            process_file_markdown(src_pathname)
-        elif src_pathname.suffix in STATIC_ASSET_EXTENSIONS:
-            process_file_copytodest(src_pathname)
+def invoke_hugo(preview):
+    site_docs = SRCTREE_TOP.joinpath('site', 'docs')
+    config_file = str(site_docs.joinpath('config.toml'))
+    layout_dir = str(site_docs.joinpath('layouts'))
+    args = [
+        "hugo",
+        "--config",
+        config_file,
+        "--destination",
+        str(config["outdir"]),
+        "--contentDir",
+        str(SRCTREE_TOP),
+        "--layoutDir",
+        layout_dir,
+    ]
+    if preview:
+        args += ["server"]
+    subprocess.run(args, check=True, cwd=SRCTREE_TOP)
 
 
 def main():
@@ -144,21 +168,14 @@
         help="""starts a local server with live reload (updates triggered upon
              changes in the documentation files). this feature is intended
              to preview the documentation locally.""")
+    parser.add_argument('--hugo', help="""TODO""")
 
     args = parser.parse_args()
 
-    # Initial processing of all files
-    process_all_files()
-
-    if args.preview:
-        # Setup livereload watcher
-        server = livereload.Server()
-        exts_to_watch = MARKDOWN_EXTENSIONS +       \
-                        STATIC_ASSET_EXTENSIONS +   \
-                        HJSON_EXTENSIONS
-        for src_pathname in get_doc_files(exts_to_watch):
-            server.watch(str(src_pathname), process_all_files)
-        server.serve(root=config['topdir'].joinpath(config['outdir']))
+    generate_hardware_blocks()
+    generate_dashboards()
+    generate_testplans()
+    invoke_hugo(args.preview)
 
 
 if __name__ == "__main__":
diff --git a/util/docgen.py b/util/docgen.py
deleted file mode 100755
index 7e62889..0000000
--- a/util/docgen.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-r"""Command-line tool to merge markdown and register spec to html
-
-"""
-import argparse
-import logging as log
-import os.path
-import shutil
-import sys
-
-from pkg_resources import resource_filename
-
-from docgen import generate, html_data, lowrisc_renderer
-from reggen import version
-
-USAGE = """
-  docgen [options]
-  docgen [options] <file>
-  docgen (-h | --help)
-  docgen --version
-"""
-
-
-def main():
-    parser = argparse.ArgumentParser(
-        prog="docgen",
-        formatter_class=argparse.RawDescriptionHelpFormatter,
-        usage=USAGE,
-        description=__doc__,
-        epilog='defaults or the filename - can be used for stdin/stdout')
-    parser.add_argument(
-        '--version', action='store_true', help='Show version and exit')
-    parser.add_argument(
-        '-v',
-        '--verbose',
-        action='store_true',
-        help='Verbose output during processing')
-    parser.add_argument(
-        '-c',
-        '--inline-css',
-        action='store_true',
-        help='Put CSS inline in output file')
-    parser.add_argument(
-        '-d',
-        '--asdiv',
-        action='store_true',
-        help='Output as a <div> without html header/trailer')
-    parser.add_argument(
-        '-j',
-        '--wavesvg-usejs',
-        action='store_true',
-        help='Waveforms should use javascript wavedrom '
-        'rather than generating inline svg')
-    parser.add_argument(
-        '-o',
-        '--output',
-        type=argparse.FileType('w'),
-        default=sys.stdout,
-        metavar='file',
-        help='Output file (default stdout)')
-    parser.add_argument(
-        'srcfile',
-        nargs='?',
-        metavar='file',
-        default='-',
-        help='source markdown file (default stdin)')
-    args = parser.parse_args()
-
-    if args.version:
-        version.show_and_exit(__file__, ["Hjson", "Mistletoe"])
-
-    if (args.verbose):
-        log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
-    else:
-        log.basicConfig(format="%(levelname)s: %(message)s")
-
-    outfile = args.output
-
-    with outfile:
-        outfile.write(
-            generate.generate_doc(args.srcfile, args.verbose, args.inline_css,
-                                  not args.wavesvg_usejs, args.asdiv))
-
-
-if __name__ == '__main__':
-    main()
diff --git a/util/docgen/LICENSE.mistletoe b/util/docgen/LICENSE.mistletoe
deleted file mode 100644
index 9ad0f5a..0000000
--- a/util/docgen/LICENSE.mistletoe
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License
-
-Copyright 2017 Mi Yu
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/util/docgen/README.md b/util/docgen/README.md
deleted file mode 100644
index faf93c4..0000000
--- a/util/docgen/README.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Docgen -- lowRISC Document generator
-
-Docgen is a python3 tool to read markdown documentation and generate html.
-
-It works in conjunction with reggen to generate documentation with the
-register information inserted. Examples are described in the README.md
-in the examples subdirectory.
-
-The lowRISC markdown is based on CommonMark (A strongly defined, highly
-compatible specification of Markdown defined at
-https://commonmark.org/ ) parsed by mistletoe (a fast, extensible and
-spec-compliant Markdown parser in pure Python).
-
-Mistletoe already adds tables using the Github markdown syntax.
-
-The following extensions have been made for the lowRISC version:
-* `{{% lowrisc-doc-hdr Title Of Doc }}` Insert a standard title header
-  and give
-  the document a title. Expected to extend this to have lowrisc-doc-hdr=type
-  (type could be component, core, guide,...) to allow the tool to
-  validate required sections are in the document.
-
-* `{{% regfile filename.hjson }}` Pointer to the register definition
-  json/hjson. This is expected to go early in the document. After this line
-  the registers are available as markup items.
-
-* `{{% registers x }}` Insert the register tables at this point in the
-  document. Must be after the regfile extension! TODO fix the need for `x`
-
-* `{{% import_testplan testplan.hjson }}` Pointer to the testplan
-  hjson. This is expected to go early in the document. After this line
-  the testplan entries are available as markup items.
-
-* `{{% insert_testplan x }}` Insert the testplan table at this point in the
-  document. Must be after the `import_testplan` extension! TODO fix the need for `x`
-
-* `{{% include file }}` Insert the file into the markdown
-  document. Any other text on the same line as the include directive
-  will be inserted, then a newline and then the included file. The
-  file is included before any other processing so the result is a
-  single file processed by the markdown processor (thus all
-  definitions like anchor links are global and not confined to the
-  file they are in). Includes may be nested. The filename is relative
-  to the directory that the markdown file currently being processed is
-  in (so relative links work from inside included files). If the
-  include file is not found then an error is reported and a line
-  indicating the error will be inserted in the markdown.
-
-* `{{% include !command -options }}` Use the shell to cd to the
-  directory that the markdown file is in and run the command with
-  given options (everything from the `!` to the closing `}}` is used
-  as the shell command). Insert the output (stdout) from the command
-  into the markdown document. Any other text on the same line as the
-  include directive will be inserted, then a newline and then the
-  command output. (As a result, if the triple back-tick to start a
-  code block immediately follows the `}}` then the output from the
-  command will be inserted inside that code block.) Error returns from
-  the command will be ignored, and any output on stderr will be
-  reported in the docgen stderr output.
-
-
-* `!!Reg` or `!!Reg.Field` Insert Component.Reg or Component.Reg.Field
-  in the output file as a link to the register table for Reg and
-  tagged for special CSS decoration (currently makes them blue,
-  monospace and a little smaller). If Reg is not in the list of
-  registers read from the regfile directive then a warning is printed
-  and the output is not transformed. (Note the use of period rather
-  than underline as the separator was to avoid syntax highlighter
-  issuses because of markdown's use of underline for italics.
-
-* ` ```lang ` Code blocks are highlighted by pygments (a generic
-  syntax highlighter in python). Background colour can be set using the
-  {.good} and {.bad} tags after the lang.
-
-* ` ```wavejson ` Code blocks describing waveforms are converted into
-  an svg picture in the output file. The `-j` or `--wavesvg-usejs` flag
-  will instead generate the <script> output needed by the WaveDrom
-  javascript parser and include invocation of wavedrom in the output
-  html.
-
-
-* Anticipate adding support for ToC generation and insertion (there is
-  partial support for this already in mistletoe)
diff --git a/util/docgen/__init__.py b/util/docgen/__init__.py
deleted file mode 100644
index 8b13789..0000000
--- a/util/docgen/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/util/docgen/examples/README.md b/util/docgen/examples/README.md
deleted file mode 100644
index 5646693..0000000
--- a/util/docgen/examples/README.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# Examples using docgen
-
-This directory has some examples of using docgen. The example commands
-assume $REPO_TOP is set to the toplevel directory of the repo.
-
-### Setup
-
-If packages have not previously been installed you will need to set a
-few things up. First use `pip3` to install some required packages:
-```
-$ pip3 install --user hjson
-$ pip3 install --user mistletoe
-$ pip3 install --user pygments
-```
-
-The examples all use the -c flag to ensure CSS styles are included
-inline in the html file generated. If building a site this flag will
-likely not be used. If this flag is not given then `md_html.css` from
-docgen and `reg_html.css` from reggen should be put in the same
-directory as the html file.
-
-### Build example1
-
-Example 1 is the Simple Uart. There are a couple of changes from the
-actuall implemmentation: the registers are declared as 64-bit and the
-STATECLR register bits have been moved up by 16 bits.
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -c uart.md > /tmp/uartout.html
-```
-
-If you want it to tell more about progress add the verbose flag. Note
-that this causes a second validation pass that checks the output of
-the first pass, there should be fewer things to do on the second pass.
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -c -v uart.md > /tmp/uartout.html
-```
-
-You can open the output using file:///tmp/uartout.html
-
-### Build example2
-
-Example 2 is a 16550 style Uart. This shows 8-bit registers and
-registers at the same address.
-
-Note this example has a deliberate error in the second waveform description.
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -c uart16550.md > /tmp/uart16550out.html
-```
-
-If you want it to tell more about progress add the verbose flag. Note
-that this causes a second validation pass that checks the output of
-the first pass, there should be fewer things to do on the second pass.
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -c -v uart16550.md > /tmp/uart16550out.html
-```
-
-The waveforms can also be generated using the browser-based wavedom
-javascript.
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -cj uart16550.md > /tmp/uart16550out.html
-```
-
-
-You can open the output using file:///tmp/uart16550out.html
-
-### Build example 3
-
-Example 3 is an example of using the multireg key to generate registers.
-
-
-```
-$ cd $REPO_TOP/util/docgen/examples
-$ ../../docgen.py -c gp.md > /tmp/gpout.html
-```
diff --git a/util/docgen/examples/badwen.hjson b/util/docgen/examples/badwen.hjson
deleted file mode 100644
index 07eedad..0000000
--- a/util/docgen/examples/badwen.hjson
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "BADWEN",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-
-  regwidth: "32",
-  registers: [
-    {name: "RDATA", desc: "UART read data",
-      swaccess: "ro", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {name: "WDATA", desc: "UART write data", swaccess: "wo", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {name: "NCO", desc: "Baud clock rate control",
-      swaccess: "rw", regwen: "GOODWEN", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "NCO1", desc: "Baud clock rate control",
-      swaccess: "rw", regwen: "BADWEN1", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "NCO2", desc: "Baud clock rate control",
-      swaccess: "rw", regwen: "BADWEN2", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "NCO3", desc: "Baud clock rate control",
-      swaccess: "rw", regwen: "BADWEN3", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "NCO4", desc: "Baud clock rate control",
-      swaccess: "rw", regwen: "NONE", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "GOODWEN", desc: "Write enable control",
-      swaccess: "rw1c", fields: [
-      {name: "wen", desc: "wr enable", bits: "0", resval: "1"}
-    ]},
-    {name: "BADWEN1", desc: "Write enable control too many bits",
-      swaccess: "rw1c", fields: [
-      {name: "wen", desc: "wr enable", bits: "0", resval: "1"}
-      {name: "foo", desc: "wr enable", bits: "1", resval: "1"}
-    ]},
-    {name: "BADWEN2", desc: "Write enable control not rw1c",
-      swaccess: "rw", fields: [
-      {name: "wen", desc: "wr enable", bits: "0", resval: "1"}
-    ]},
-    {name: "BADWEN3", desc: "Write enable control not default to 1",
-      swaccess: "rw1c", fields: [
-      {name: "wen", desc: "wr enable", bits: "0", resval: "0"}
-    ]},
-    {name: "DVREG", desc: "DV-accessible test register", swaccess: "rw",
-     fields: [
-      {bits: "7:0", name: "", desc: "-" }
-    ]}
-  ]
-}
diff --git a/util/docgen/examples/badwen.md b/util/docgen/examples/badwen.md
deleted file mode 100644
index c8cc971..0000000
--- a/util/docgen/examples/badwen.md
+++ /dev/null
@@ -1,31 +0,0 @@
-{{% lowrisc-doc-hdr Test for register write enable errors }}
-{{% regfile badwen.hjson }}
-
-Blah
-
-{{% toc 3 }}
-
-## Compatibility
-
-## Theory of operation
-
-Check for cross reference to !!RDATA
-
-## Programmer Guide
-
-
-### Initialization
-
-
-### Interrupts
-
-
-
-### Debug Features
-
-
-## Implementation Guide
-
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/errors.hjson b/util/docgen/examples/errors.hjson
deleted file mode 100644
index 0e8e5a1..0000000
--- a/util/docgen/examples/errors.hjson
+++ /dev/null
@@ -1,208 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "ERR",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-
-  regwidth: "32",
-  registers: [
-    { name: "OE",
-      desc: '''GPIO Output Enable
-
-            bit[i]=1'b0: Input mode for GPIO[i]
-            bit[i]=1'b1: Output mode for GPIO[i]
-            ''',
-      swaccess: "rw",
-      fields: [
-        { bits: "31:0" }
-      ],
-    },
-    { reserved: "4" }
-    { reserved: "4" name: "error" }
-    { name: "DATA_IN",
-      desc: "GPIO Input data read bitfield",
-      swaccess: "ro",
-      fields: [
-        { bits: "31:0",
-          resval: "x"
-        }
-      ],
-    },
-    { name: "DATA_IN",
-      desc: "Duplicate name",
-      swaccess: "ro",
-      fields: [
-        { bits: "0", name: "abit", desc: "a bit"},
-        { bits: "1", name: "abit", desc: "a bit duplicate name"},
-	{ bits: "32", name: "bbit", desc: "out of bounds bit"},
-      ],
-    },
-    { name: "DATA_BB",
-      desc: "should be ok",
-      swaccess: "ro",
-      fields: [
-        { bits: "0", name: "abit", desc: "a bit"},
-	{ bits: "1", name: "bbit", desc: "ok"},
-      ],
-    },
-    { name: "data_bb",
-      desc: "Duplicate name although the case differs",
-      swaccess: "ro",
-      fields: [
-        { bits: "0", name: "abit", desc: "a bit"},
-	{ bits: "1", name: "bbit", desc: "ok"},
-      ],
-    },
-    { name: "DATA_YY",
-      desc: "Name ok should show field errors",
-      swaccess: "ro",
-      fields: [
-        { bits: "0", name: "abit", desc: "a bit"},
-        { bits: "1", name: "abit", desc: "a bit duplicate name"},
-	{ bits: "32", name: "bbit", desc: "out of bounds bit"},
-      ],
-    },
-    { name: "DATA_ZZ",
-      desc: "More errors two swaccess",
-      swaccess: "ro",
-      swaccess: "rw",
-      fields: [
-        { bits: "0", name: "abit", desc: "a bit"},
-        { bits: 1, name: "intbit", desc: "bit is an integer"},
-	{ bits: "32:20", name: "bbit", desc: "out of bounds bit range"},
-      ],
-    },
-    { name: "DATA_QQ",
-      desc: "No fields",
-      swaccess: "rw",
-      fields: [
-      ],
-    },
-    # multireg for single bit instance?
-    { multireg: {
-        name: "DATA_OUT",
-        desc: "GPIO output data",
-        count: "32",
-        cname: "GPIO",
-        swaccess: "rw",
-        fields: [
-          { bits: "0", name: "D" desc: "Output data" }
-        ],
-      }
-    },
-    {sameaddr: [
-	{name: "IIR", desc: "Interrupt identification register",
-	 resval: "0xa0", swaccess: "ro", fields: [
-	     {bits: "0", name: "INT", resval: "1",
-	      desc: "This bit is clear if the UART is interrupting."
-	     }
-	     {bits: "3:1", name: "TYPE",
-	      desc: '''
-              If the INT bit is clear, these bits indicate the highest
-              priority pending interrupt.
-              '''
-	      enum: [
-      		 { value: "0", name: "mdm", desc: "Modem status (lowest)" },
-       		 { value: "1", name: "txe", desc: "TX holding register empty" },
-       		 { value: "2", name: "rxd", desc: "RX data available" },
-       		 { value: "3", name: "rxl", desc: "RX line status (highest)" }
-	      ]
-	     }
-	     {bits: "3", name: "TO",
-	      desc: "This bit overlaps."
-	      resval: "NaN"
-	     }
-	     {bits: "5", name: "F64", resval: "1",
-	      desc: "Will always be clear because the FIFO is not 64 bytes."
-	     }
-	     {bits: "7:6", resval: "6", name: "FEN", desc: '''
-	      These bits will both be clear if the FIFO is disabled
-	      and both be set if it is enabled.
-              '''
-	      enum: [
-      		 { value: "0", name: "mdm", desc: "Modem status (lowest)" },
-       		 { value: "1", name: "txe", desc: "TX holding register empty" },
-       		 { value: "2", name: "rxd", desc: "RX data available" },
-       		 { value: "3", name: "rxl" }
-	      ]
-	     }
-	 ]}
-	{name: "FCR", desc: "FIFO Control Register",
-	 swaccess: "ab", fields: [
-	     {bits: "0", name: "FEN",
-	      desc: "This bit must be set to enable the FIFOs."
-	     }
-	     {bits: "1", name: "CRX", desc: '''
-               Writing this bit with a 1 will reset the RX fifo. The
-               bit will clear after the FIFO is reset.
-              '''
-	     }
-	     {bits: "2", name: "CTX", desc: '''
-               Writing this bit with a 1 will reset the TX fifo. The
-               bit will clear after the FIFO is reset.
-              '''
-	     }
-	     {bits: "3", name: "DMAS",
-	      desc: "DMA Mode Select. This bit is not used."
-	     }
-	     {bits: "5", name: "E64",
-	      desc: "This bit is reserved because the FIFO is not 64 bytes."
-	     }
-	     {bits: "6:7", name: "NOTPPC",
-	      desc: '''
-                These two bits set the interrupt trigger level for the
-                receive FIFO. The received data available interrupt
-                will be set when there are at least this number of
-                bytes in the receive FIFO.
-              '''
-	      enum: [
-      		 { value: "0", name: "rxlvl1", desc: "1 Byte" },
-       		 { value: "1", name: "rxlvl4", desc: "4 Bytes" },
-       		 { value: "2", name: "rxlvl8", desc: "8 bytes" },
-       		 { value: "3", name: "rxlvl14", desc: "14 bytes" }
-	      ]
-	     }
-	 ]}
-    ]}
-    // skipto bad offset
-    { skipto: "0x41" }
-    // Backwards skip is an error
-    { skipto: "16" }
-    {window: {
-    	     name: "FCR"
-	     items: "16"
-	     validbits: "48"
-	     byte-write: "True"
-	     swaccess: "rw"
-	     desc: '''
-	     	   Duplicate name, too wide.
-		   '''
-	}
-    },
-    {window: {
-    	     name: "win1"
-	     items: "16"
-	     validbits: "48"
-	     byte-write: "True"
-	     swaccess: "rw"
-	     desc: '''
-	     	   Too wide.
-		   '''
-	}
-    },
-    {window: {
-    	     name: "win2"
-	     items: "big"
-	     validbits: "x"
-	     byte-write: "True"
-	     swaccess: "read"
-	     desc: '''
-	     	   size, width not a number, bad swaccess
-		   '''
-	}
-    },
-
-  ]
-}
diff --git a/util/docgen/examples/errors.md b/util/docgen/examples/errors.md
deleted file mode 100644
index db9ee1d..0000000
--- a/util/docgen/examples/errors.md
+++ /dev/null
@@ -1,55 +0,0 @@
-{{% lowrisc-doc-hdr Example with lots of errors }}
-{{% regfile errors.hjson }}
-
-Blah
-
-Error in lowRISC tag
-{{% bob 3 }}
-
-## Compatibility
-
-
-## Theory of operation
-
-Check for cross reference to !!DATA_OUT and !!INT_CTRL and !!INT_CTRL2 blah
-
-For testing, this version should report an error:
-```wavejson
-{signal: [
-  {name:'Baud Clock',  wave: 'p...........' },
-  {name:'Data 8 bit',        wave: '10========1=',
-   data: [ "lsb", "", "", "", "", "", "", "msb", "next" ] )
-  {name:'Data 7 bit',        wave: '10=======1=.',
-   data: [ "lsb", "", "", "", "", "", "msb", "next" ] },
-  {name:'Data 6 bit',        wave: '10======1=..',
-   data: [ "lsb", "", "", "", "", "msb", "next" ] },
-  {name:'Data 5 bit',        wave: '10=====1=...',
-   data: [ "lsb", "", "", "", "msb", "next" ] },
-  {name:'8 with Parity', wave: '10=========1',
-   data: [ "lsb", "", "", "", "", "", "", "msb", "par" ] },
- ],
- head:{
-   text:'Serial Line format (one stop bit)',
-   tock:-1,
- }
-}
-```
-
-## Programmer Guide
-
-
-### Initialization
-
-
-### Interrupts
-
-
-
-### Debug Features
-
-
-## Implementation Guide
-
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/gp.hjson b/util/docgen/examples/gp.hjson
deleted file mode 100644
index 1329d55..0000000
--- a/util/docgen/examples/gp.hjson
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "GP",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-
-  regwidth: "32",
-  registers: [
-    { name: "OE",
-      desc: '''GPIO Output Enable
-
-            bit[i]=1'b0: Input mode for GPIO[i]
-            bit[i]=1'b1: Output mode for GPIO[i]
-            ''',
-      swaccess: "rw",
-      fields: [
-        { bits: "31:0" }
-      ],
-    },
-    { reserved: "4" }
-    { name: "DATA_IN",
-      desc: "GPIO Input data read bitfield",
-      swaccess: "ro",
-      fields: [
-        { bits: "31:0",
-          resval: "x"
-        }
-      ],
-    },
-    # multireg for single bit instance?
-    { multireg: {
-        name: "DATA_OUT",
-        desc: "GPIO output data",
-        count: "32",
-        cname: "GPIO",
-        swaccess: "rw",
-        fields: [
-          { bits: "0", name: "D" desc: "Output data" }
-        ],
-      }
-    },
-
-    { multireg: {
-          name: "INT_CTRL",
-	  desc: "GPIO Interrupt control",
-	  count: "32",
-	  cname: "GPIO",
-	  swaccess: "rw",
-	  fields: [
-	      { bits: "0", name: "POS", resval: "0",
-	        desc: "Set to interrupt on rising edge"
-	      }
-	      { bits: "1", name: "NEG", resval: "0",
-	        desc: "Set to interrupt on falling edge"
-	      }
-	      { bits: "4:2", name: "TYPE", resval: "0",
-	        desc: "Type of interrupt to raise"
-		enum: [
-		  {value: "0", name: "none", desc: "log but no interrupt" },
-		  {value: "1", name: "low", desc: "low priotiry interrupt" },
-		  {value: "2", name: "high", desc: "high priotiry interrupt" },
-		  {value: "3", name: "nmi", desc: "non maskable interrupt" }
-		]
-	      }
-	  ]
-      }
-    },
-    { multireg: {
-          name: "WDATA",
-	  desc: "Write with mask to GPIO out register",
-	  count: "32",
-	  cname: "GPIO",
-	  swaccess: "rw",
-	  fields: [
-	      { bits: "0", name: "DATA", resval: "0",
-	        desc: "Data to write if mask bit is 1"
-	      }
-	      { bits: "16", name: "MASK", resval: "0",
-	        desc: "Set to allow data write"
-	      }
-	  ]
-      }
-    }
-  ]
-}
diff --git a/util/docgen/examples/gp.md b/util/docgen/examples/gp.md
deleted file mode 100644
index 510a48c..0000000
--- a/util/docgen/examples/gp.md
+++ /dev/null
@@ -1,32 +0,0 @@
-{{% lowrisc-doc-hdr Example like first gpio }}
-{{% regfile gp.hjson }}
-
-Blah
-
-{{% toc 3 }}
-
-## Compatibility
-
-
-## Theory of operation
-
-Check for cross reference to !!DATA_OUT and !!INT_CTRL and !!INT_CTRL2 blah
-
-## Programmer Guide
-
-
-### Initialization
-
-
-### Interrupts
-
-
-
-### Debug Features
-
-
-## Implementation Guide
-
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/include/inc2.md b/util/docgen/examples/include/inc2.md
deleted file mode 100644
index 0a9e37f..0000000
--- a/util/docgen/examples/include/inc2.md
+++ /dev/null
@@ -1,8 +0,0 @@
-### depth 2 include
-
-[Anchor 3]: https://google.com
-
-mumble
-* Try out [Anchor 1][] defined in outer
-
-### end depth 2 include
diff --git a/util/docgen/examples/include/included.md b/util/docgen/examples/include/included.md
deleted file mode 100644
index 89c8096..0000000
--- a/util/docgen/examples/include/included.md
+++ /dev/null
@@ -1,14 +0,0 @@
-
-### This is the **included** md file!
-
-Check from included for cross reference to !!DATA_OUT and !!INT_CTRL and !!INT_CTRL2 blah
-
-[Anchor 2]: https://github.com/mdhayter
-
-mumble
-* Try out [Anchor 1][] defined later
-* Try out [Anchor 2][] defined in include
-
-{{% include inc2.md }}
-
-### end first include
diff --git a/util/docgen/examples/ls b/util/docgen/examples/ls
deleted file mode 100755
index 66127dc..0000000
--- a/util/docgen/examples/ls
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-ls $*
diff --git a/util/docgen/examples/test_inc.md b/util/docgen/examples/test_inc.md
deleted file mode 100644
index 3873b21..0000000
--- a/util/docgen/examples/test_inc.md
+++ /dev/null
@@ -1,58 +0,0 @@
-{{% lowrisc-doc-hdr Example like first gpio }}
-{{% regfile gp.hjson }}
-
-Blah
-
-{{% toc 3 }}
-
-## Compatibility
-
-* Try out [Anchor 1][] defined in outer
-* Try out [Anchor 2][] defined in include depth 1
-* Try out [Anchor 3][] defined in include depth 2
-
-
-{{% include include/included.md }}
-
-[Anchor 1]: https://github.com/lowRisc
-
-## this include should fail
-
-{{% include no_such_file.md }}
-
-## exec include
-
-This should work since there is an exec ls in the directory
-{{% include !ls -1 errors.md errorsregs.hjson }}```
-```
-
-This should fail because command does not exist {{% include !ps au }}
-
-This should fail for trying to escape the repo {{% include !../../../../foo }}
-
-# big include
-
-{{% include !../../regtool.py --doc }}
-
-## Theory of operation
-
-Check for cross reference to !!DATA_OUT and !!INT_CTRL and !!INT_CTRL2 blah
-
-## Programmer Guide
-
-
-### Initialization
-
-
-### Interrupts
-
-
-
-### Debug Features
-
-
-## Implementation Guide
-
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/test_win.md b/util/docgen/examples/test_win.md
deleted file mode 100644
index 3c9c92e..0000000
--- a/util/docgen/examples/test_win.md
+++ /dev/null
@@ -1,31 +0,0 @@
-{{% lowrisc-doc-hdr Test for register space windows }}
-{{% regfile win.hjson }}
-
-Blah
-
-{{% toc 3 }}
-
-## Compatibility
-
-## Theory of operation
-
-Check for cross reference to !!win1 and !!RDATA and !!win2 blah
-
-## Programmer Guide
-
-
-### Initialization
-
-
-### Interrupts
-
-
-
-### Debug Features
-
-
-## Implementation Guide
-
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/uart.hjson b/util/docgen/examples/uart.hjson
deleted file mode 100644
index bf8e36c..0000000
--- a/util/docgen/examples/uart.hjson
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "UART",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-  regwidth: "64",
-  registers: [
-    {name: "RDATA", desc: "UART read data",
-      swaccess: "ro", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {name: "WDATA", desc: "UART write data", swaccess: "wo", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {name: "NCO", desc: "Baud clock rate control", swaccess: "rw", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {name: "CTRL", desc: "UART control register", swaccess: "rw", fields: [
-      {bits: "0", name: "TX", desc: '''
-        TX enable has a really long description that will go on over
-	several lines and really want to wrap to be seen well in the
-	source format.
-	'''
-	}
-      {bits: "1", name: "RX", desc: "RX enable"}
-      {bits: "2", name: "CTS", desc: "CTS hardware flow-control enable"}
-      {bits: "3", name: "RTS", desc: "RTS hardware flow-control enable"}
-      {bits: "4", name: "SLPBK", desc: "System loopback enable"}
-      {bits: "5", name: "LLPBK", desc: "Line loopback enable"}
-      {bits: "6", name: "RCOS", desc: "Oversample enable for RX and CTS"}
-      {bits: "7", name: "NF", desc: "RX noise filter enable"}
-      {bits: "8", name: "PARITY_EN", desc: "Parity enable"}
-      {bits: "9", name: "PARITY_ODD", desc: "1 for odd parity, 0 for even."}
-    ]}
-    {name: "ICTRL", desc: "UART Interrupt control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX interrupt enable" }
-      {bits: "1", name: "RX", desc: "RX interrupt enable"}
-      {bits: "2", name: "TXO", desc: "TX overflow interrupt enable"}
-      {bits: "3", name: "RXO", desc: "RX overflow interrupt enable"}
-      {bits: "4", name: "RXF", desc: "RX frame error interrupt enable"}
-      {bits: "5", name: "RXB", desc: "RX break error interrupt enable"}
-      {bits: "7:6", name: "RXBLVL", desc: '''
-       Trigger level for rx break detection. Sets the number of character
-       times the line must be low to detect a break
-       ''',
-       enum: [
-       	       { value: "0", name: "break2", desc: "2 characters" },
-       	       { value: "1", name: "break4", desc: "4 characters" },
-       	       { value: "2", name: "break8", desc: "8 characters" },
-       	       { value: "3", name: "break16", desc: "16 characters" }
-	     ]
-      }
-      {bits: "8", name: "RXTO", desc: "RX timeout interrupt enable"}
-      {bits: "9", name: "RXPE", desc: "RX parity error interrupt enable"}
-    ]}
-    {name: "STATE", desc: "UART state register", swaccess: "ro",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX buffer full" }
-      {bits: "1", name: "RX", desc: "RX buffer full"}
-      {bits: "2", name: "TXO", desc: "TX buffer overflow"}
-      {bits: "3", name: "RXO", desc: "RX buffer overflow"}
-      {bits: "4", name: "TXEMPTY", desc: "TX buffer empty"}
-      {bits: "5", name: "TXIDLE", desc: "TX idle"}
-      {bits: "6", name: "RXIDLE", desc: "RX idle"}
-      {bits: "7", name: "RXEMPTY", desc: "RX fifo empty"}
-    ]}
-    // I suspect STATECLR should be r0w1c or something
-    {name: "STATECLR", desc: "UART state register", swaccess: "rw",
-     fields: [
-      {bits: "19", name: "TXO", desc: "Clear TX buffer overflow"}
-      {bits: "20", name: "RXO", desc: "Clear RX buffer overflow"}
-    ]}
-    {name: "ISTATE", desc: "UART Interrupt state register", swaccess: "ro",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX interrupt state" }
-      {bits: "1", name: "RX", desc: "RX interrupt state"}
-      {bits: "2", name: "TXO", desc: "TX overflow interrupt state"}
-      {bits: "3", name: "RXO", desc: "RX overflow interrupt state"}
-      {bits: "4", name: "RXF", desc: "RX frame error interrupt state"}
-      {bits: "5", name: "RXB", desc: "RX break error interrupt state"}
-      {bits: "6", name: "RXTO", desc: "RX timeout interrupt state"}
-      {bits: "7", name: "RXPE", desc: "RX parity error interrupt state"}
-    ]}
-    {name: "ISTATECLR", desc: "UART Interrupt clear register",
-     swaccess: "r0w1c",
-     fields: [
-      {bits: "0", name: "TX", desc: "Clear TX interrupt" }
-      {bits: "1", name: "RX", desc: "Clear RX interrupt"}
-      {bits: "2", name: "TXO", desc: "Clear TX overflow interrupt"}
-      {bits: "3", name: "RXO", desc: "Clear RX overflow interrupt"}
-      {bits: "4", name: "RXF", desc: "Clear RX frame error interrupt"}
-      {bits: "5", name: "RXB", desc: "Clear RX break error interrupt"}
-      {bits: "6", name: "RXTO", desc: "Clear RX timeout interrupt"}
-      {bits: "7", name: "RXPE", desc: "Clear RX parity error interrupt"}
-    ]}
-    {name: "FIFO", desc: "UART FIFO control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "RXRST", swaccess: "r0w1c", desc: "RX fifo reset" }
-      {bits: "1", name: "TXRST", swaccess: "r0w1c", desc: "TX fifo reset" }
-      {bits: "4:2", name: "RXILVL",
-       desc: "Trigger level for RX interrupts"
-       enum: [
-       	       { value: "0", name: "rxlvl1", desc: "1 character" },
-       	       { value: "1", name: "rxlvl4", desc: "4 characters" },
-       	       { value: "2", name: "rxlvl8", desc: "8 characters" },
-       	       { value: "3", name: "rxlvl16", desc: "16 characters" }
-       	       { value: "4", name: "rxlvl30", desc: "30 characters" }
-	       // TODO expect generator to make others reserved
-	     ]
-      }
-      {bits: "6:5", name: "TXILVL",
-       desc: "Trigger level for TX interrupts"
-       enum: [
-       	       { value: "0", name: "txlvl1", desc: "1 character" },
-       	       { value: "1", name: "txlvl4", desc: "4 characters" },
-       	       { value: "2", name: "txlvl8", desc: "8 characters" },
-       	       { value: "3", name: "txlvl16", desc: "16 characters" }
-	     ]
-      }
-    ]}
-    {name: "RFIFO", desc: "UART FIFO status register", swaccess: "ro",
-     fields: [
-      {bits: "5:0", name: "TXLVL", desc: "Current fill level of TX fifo" }
-      {bits: "11:6", name: "RXLVL", desc: "Current fill level of RX fifo" }
-    ]}
-    {name: "OVRD", desc: "UART override control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TXEN", desc: "Override the TX signal" }
-      {bits: "1", name: "TXVAL", desc: "Value for TX Override" }
-      {bits: "2", name: "RTSEN", desc: "Override the RTS signal" }
-      {bits: "3", name: "RTSVAL", desc: "Value for RTS Override" }
-    ]}    
-    {name: "VAL", desc: "UART oversampled values", swaccess: "ro",
-     fields: [
-      {bits: "15:0", name: "RX", desc: '''
-       Last 16 oversampled values of RX. Most recent bit is bit 0, oldest 15.
-      ''' }
-      {bits: "31:16", name: "CTS", desc: '''
-       Last 16 oversampled values of CTS. Most recent bit is bit 16, oldest 31.
-      ''' }
-    ]}
-    {name: "RXTO", desc: "UART RX timeout control", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "EN", desc: "Enable RX timeout feature" }
-      {bits: "24:1", name: "VAL", desc: "RX timeout value in UART bit times" }
-    ]}    
-    { skipto: "0x0f00" }
-    {name: "ITCR", desc: "UART Integration test control", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "", desc: "-" }
-    ]}    
-    {name: "ITOP", desc: "UART Integration test overrides", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TX", desc: "Drive txint when UART_ITCR asserted" }
-      {bits: "1", name: "RX", desc: "Drive rxint when UART_ITCR asserted" }
-      {bits: "2", name: "TXO", desc: "Drive txoint when UART_ITCR asserted" }
-      {bits: "3", name: "RXO", desc: "Drive rxoint when UART_ITCR asserted" }
-      {bits: "4", name: "RXF", desc: "Drive rxfint when UART_ITCR asserted" }
-      {bits: "5", name: "RXB", desc: "Drive rxbint when UART_ITCR asserted" }
-      {bits: "6", name: "RXTO", desc: "Drive rxtoint when UART_ITCR asserted" }
-      {bits: "7", name: "RXPE", desc: "Drive rxpeint when UART_ITCR asserted" }
-    ]}
-    {name: "DVREG", desc: "DV-accessible test register", swaccess: "rw",
-     fields: [
-      {bits: "56:0", name: "", desc: "-" }
-    ]}    
-  ]
-}
diff --git a/util/docgen/examples/uart.md b/util/docgen/examples/uart.md
deleted file mode 100644
index e0be560..0000000
--- a/util/docgen/examples/uart.md
+++ /dev/null
@@ -1,264 +0,0 @@
-{{% lowrisc-doc-hdr Simple UART }}
-{{% regfile uart.hjson }}
-
-The simple UART provides an asynchronous serial interface that can
-operate at programmable BAUD rates. The main features are:
-
-- 32 byte transmit FIFO
-- 32 byte receive FIFO
-- Programmable fractional baud rate generator
-- Hardware flow control (when enabled)
-- 8 data bits
-- optional parity bit (even or odd)
-- 1 stop bit
-
-{{% toc 3 }}
-
-## Compatibility
-
-The simple UART is compatible with the H1 Secure Microcontroller UART
-used in the Chrome OS cr50 codebase
-(https://chromium.googlesource.com/chromiumos/platform/ec/+/master/chip/g/).
-The parity option has been added.
-
-## Theory of operation
-
-*TODO block diagram of UART*
-
-The UART can connect to four external pins:
-* TX: transmit data output.
-* RX: receive data input.
-* RTS: request to send flow control output. This pin is active low.
-* CTS: clear to send flow control input. This pin is active low.
-
-### Serial data format
-
-The serial line is high when idle. Characters are sent using a start
-bit (low) followed by 8 data bits sent least significant
-first. Optionally there may be a parity bit which is computed to give
-either even or odd parity. Finally there is a stop bit (high). The
-start bit for the next character may immediately follow the stop bit,
-or the line may be in the idle (high) state for some time.
-
-#### Serial waveform
-
-```wavejson
-{signal: [
-  {name:'Baud Clock',  wave: 'p...........' },
-  {name:'Data',        wave: '10========1.',
-   data: [ "lsb", "", "", "", "", "", "", "msb" ] },
-  {name:'With Parity', wave: '10=========1',
-   data: [ "lsb", "", "", "", "", "", "", "msb", "par" ] },
- ],
- head:{
-   text:'Serial Line format',
-   tick:0,
- }
-}
-```
-
-### Transmission
-
-The UART will normally format characters (add start, parity and stop
-bits) and transmit them whenever the line is idle and there is a
-character available in the transmit FIFO.
-
-If !!CTRL.CTS is set then the CTS input is checked before
-starting a transmission. If CTS is active (low) then the character is
-transmitted as usual. If CTS is inactive (high) then tranmission is
-delayed until CTS is asserted again. Note that once transmission of a
-character has started the state of the CTS line will have no effect
-until the stop bit has been transmitted.
-
-### Reception
-
-The internal clock runs at 16x the baud rate. This is used to sample
-the receive data. While the line is idle the data is sampled
-high. After the line goes low the data and parity bits are sampled in
-the middle of their bit time and the character received. The stop bit
-is checked in the middle of its bit time and must be high or a framing
-error will be reported.
-
-If there is space in the receive FIFO then the received character is
-written to the FIFO otherwise it is discarded and the RX overrun
-interrupt set. *TODO what happens if it has a parity or framing
-error, is the character added to the FIFO or not?*.
-
-For a character to be correctly recieved the local clock and the
-peer's transmit clock must drift by no more than half a bit time
-between the start of the start bit and the mid-point of the stop
-bit. Thus without parity the clocks can differ by no more than 5.2%
-(0.5 bit-times / 9.5 bit-times), and with parity they can differ by no
-more than 4.7% (0.5 bit-times / 10.5 bit-times).
-
-The stop bit in the serial line format ensures that the line will
-never be low for more than 9 (10 with parity) bit-times. If the line
-is detected low for multiple character times (configured in the
-!!ICTRL.RXBLVL field) then the receiver will detect a break
-condition and signal an interrupt. *TODO will any zero characters be
-received? Depends on answer to framing error question?*
-
-If !!CTRL.CTS is set, the receiver provides flow control by
-driving the RTS output. When the number of characters in the receive
-FIFO is below the level set in !!FIFO.RXILVL the UART will drive
-this pin low (active) to indicate it is ready to accept data. Once the
-FIFO has reached the programmed level the UART will drive the pin high
-(inactive) to stop the remote device sending more data.
-
-If !!CTRL.CTS is clear, active flow control is disabled. The UART
-will drive the RTS pin low (active) whenever its receiver is enabled
-(!!CTRL.RX set) and high (inactive) whenever the receiver is
-disabled.
-
-*TODO say something about the RX noise filter enable bit*
-
-## Programmer Guide
-
-
-### Initialization
-
-The internal clock must be configured to run at 16x the required BAUD
-rate. This is done by programming the Numerically Controlled
-Oscillator (!!NCO register). The register should be set to
-(2<sup>20</sup>*f<sub>baud</sub>)/f<sub>pclk</sub>, where
-f<sub>baud</sub> is the required baud rate and f<sub>pclk</sub> is the
-peripheral clock provided to the UART.
-
-Care should be taken not to overflow the registers during the baud
-rate setting, 64-bit arithmetic may need to be forced. For example:
-
-```c
-	long long setting = (16 * (1 << UART_NCO_WIDTH) *
-			     (long long)CONFIG_UART_BAUD_RATE / PCLK_FREQ);
-	/* set frequency */
-	GR_UART_NCO(uart) = setting;
-```
-
-During initialization the !!FIFO register should be written to
-clear the FIFOs and set the trigger levels for interrupt and flow
-control. This should be done before enabling the UART and flow control
-by setting the !!CTRL register.
-
-### Character FIFOs
-
-The transmit and recieve FIFOs are always used and each are always 32
-characters deep.
-
-Prior to adding a character to the transmit FIFO the !!STATE.TX
-bit can be checked to ensure there is space in the FIFO. If a
-character is written to a full FIFO then the character is discarded,
-the !!STATE.TXO bit is set and a TX overrun interrupt raised.
-
-If the receive FIFO is full when a new character is received thenthe
-!!STATE.RXO bit is set and a RX overrun interrupt raised.
-
-The overrun status is latched, so will persist to indicate characters
-have been lost, even if characters are removed from the corresponding
-FIFO. The state must be cleared by writing 1 to !!STATECLR.TXO or
-!!STATECLR.RXO.
-
-The number of characters in the FIFO selects the interrupt
-behaviour. The TX interrupt will be raised when there are fewer
-characters in the FIFO than configured in the !!FIFO.TXILVL
-field. The RX interrupt will be raised when there are the same or more
-characters in the FIFO than configured in the !!FIFO.RXILVL
-field. *TODO check I understand these levels*
-
-The number of characters currently in each FIFO can always be read
-from the !!RFIFO register.
-
-### Receive timeout
-
-The receiver timeout mechanism can raise an interrupt if the receiver
-detects the line to be idle for a long period. This is enabled and the
-timeout configured in the !!RXTO register.
-
-### Interrupts
-
-The UART has eight interrupts:
-- TX: raised if the transmit FIFO is past the trigger level
-- RX: raised if the receive FIFO is past the trigger level
-- TXOV: raised if the transmit FIFO has overflowed
-- RXOV: raised if the receive FIFO has overflowed
-- RXF: raised if a framing error has been detected on receive
-- RXB: raised if a break condition is detected on receive
-- RXTO: raised if the receiver has not received any characters in a time
-- RXPE: raised if the receiver has detected a parity error
-
-The current state of the interrupts can be read from the !!ISTATE
-register. Each interrupt has a corresponding bit in the
-!!ISTATECLR register that must be written with a 1 to clear the
-interrupt.
-
-Interrupts are enabled in the !!ICTRL register (note the bit
-assignment does not match the other registers bacuse this register
-also configurs the break condition). This registe just configures if
-the interrupt will be signalled to the system interrupt controller, it
-will not change or mask the value in the !!ISTATE register.
-
-### Debug Features
-
-There are two loopback modes that may be useful during debugging.
-
-System loopback is enabled by setting !!CTRL.SLPBK. Any
-characters written to the transmit buffer will be copied to the
-receive buffer. The state of the RX and CTS pins are ignored. Hardware
-flow control should be disabled when this mode is used.
-
-Line loopback is enabled by setting !!CTRL.LLPBK. Any data
-received on the RX pin is transmitted on the TX pin. Data is retimed
-by the peripheral clock, so this is only reliable if f<sub>pclk</sub>
-is more than twice the baud rate being received. Hardware flow
-control, the TX and RX fifos and interrupts should be disabled when
-this mode is used.
-
-Direct control of the TX pin can be done by setting the value to drive
-in the !!OVRD.TXVAL bit with the !!OVRD.TXEN bit set.  Direct control
-of the RTS pin can be done by setting the value to drive in the
-!!OVRD.RTSVAL bit with the !!OVRD.RTSEN bit set.
-
-The most recent samples from the receive and CTS pins gathered at 16x
-the baud clock can be read from the !!VAL register if
-!!CTRL.RCOS is set.
-
-Interrupts can be tested by configuring the interrupts to be raised in
-the !!ITOP register and setting the !!ITCR bit. This will
-raise the corresponding interrupts to the system. It is recommended
-the regular sources are disabled in the !!ICTRL register when
-this feature is used. *TODO is this implementation?*
-
-
-
-## Implementation Guide
-
-The toplevel of the UART has the following signals that connect to
-external pins:
-- TX data output connects to external pin
-- RX: receive data input connects to external pin
-- RTS: request to send flow control output. This pin is active
-  low. Connects to external pin.
-- CTS: clear to send flow control input. This pin is active
-  low. Connects to external pin.
-
-The following signals connect to the interrupt controller:
-- txint
-- rxint
-- txoint
-- rxoint
-- rxfint
-- rxbint
-- rxtoint
-- rxpeint
-
-A clock with some spec must be provided:
-- pckl
-
-The main register interface is an APB slave using:
-- APB signals
-
-An additional 32-bit scratch register !!DVREG is provided.
-
-The UART code has no build-time options. (Unless it does.)
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/uart16550.hjson b/util/docgen/examples/uart16550.hjson
deleted file mode 100644
index c88f294..0000000
--- a/util/docgen/examples/uart16550.hjson
+++ /dev/null
@@ -1,334 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "UART16550",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-
-  regwidth: 8,
-  registers: [
-    {name: "DATA", desc: "UART data",
-      swaccess: "rw", fields: [
-	  { bits: "7:0", resval: "x", desc: '''
-             A read returns the **bold character** in the receive buffer if
-             !!LCR.DLAB is clear, or the *italic low byte* of the ** bold
-	     divisor split over line** if
-	     !!LCR.DLAB is set.  Writes send characters to the
-	     transmitter holding
-             buffer if !!LCR.DLAB is clear, or set the low byte of the
-             divisor if DLAB in !!LCR is set.
-            '''
-	  }
-      ]
-    },
-    {name: "IER", desc: "Interrupt enable register", swaccess: "rw", fields: [
-      {bits: "0", name: "RDAE", desc: '''
-        Enable Received Data Available interrupt. If this bit is set
-        an interrupt will be raised whenever the receive FIFO contains
-        data. This reference is to a !!BOGUS register
-	'''
-      }
-      {bits: "1", name: "TXEE", desc: '''
-        Enable Transmit holding register empty interrupt. If this bit
-        is set then an interrupt will be raised whenever the
-        transmitter buffer is empty.
-	'''
-      }
-      {bits: "2", name: "RLE", desc: '''
-        Enable Receiver Line Status. If this bit is set then an
-        interrupt will be raised whenever the receive side line status
-        changes.
-	'''
-      }
-      {bits: "3", name: "MSE", desc: '''
-        Enable Modem Status interrupt. If this bit is set then an
-        interrupt will be raised whenever the modem status changes.
-	'''
-      }
-      {bits: "4", name: "SLP", desc: '''
-        If this bit is set the UART will enter sleep mode. Operation
-        will be stopped until a transition is detected on DIN, CTS_L,
-        DSR_L, DCD_L or RI_L.
-	'''
-      }
-      {bits: "5", name: "LPE", desc: '''
-        If this bit is set the UART will enter low power mode, the
-        same as sleep.
-	'''
-      }
-    ]},
-    {sameaddr: [
-	{name: "IIR", desc: "Interrupt identification register",
-	 swaccess: "ro", fields: [
-	     {bits: "0", name: "INT", resval: "1",
-	      desc: "This bit is clear if the UART is interrupting."
-	     }
-	     {bits: "2:1", name: "TYPE",
-	      desc: '''
-              If the INT bit is clear, these bits indicate the highest
-              priority pending interrupt.
-              '''
-	      enum: [
-      		 { value: "0", name: "mdm", desc: "Modem status (lowest)" },
-       		 { value: "1", name: "txe", desc: "TX holding register empty" },
-       		 { value: "2", name: "rxd", desc: "RX data available" },
-       		 { value: "3", name: "rxl", desc: "RX line status (highest)" }
-	      ]
-	     }
-	     {bits: "3", name: "TO",
-	      desc: "This bit is set if there is a timeout interrupt pending."
-	     }
-	     {bits: "5", name: "F64",
-	      desc: "Will always be clear because the FIFO is not 64 bytes."
-	     }
-	     {bits: "7:6", name: "FEN", desc: '''
-	      These bits will both be clear if the FIFO is disabled
-	      and both be set if it is enabled.
-              '''
-	     }
-	 ]}
-	{name: "FCR", desc: "FIFO Control Register",
-	 swaccess: "wo", fields: [
-	     {bits: "0", name: "FEN",
-	      desc: "This bit must be set to enable the FIFOs."
-	     }
-	     {bits: "1", name: "CRX", desc: '''
-               Writing this bit with a 1 will reset the RX fifo. The
-               bit will clear after the FIFO is reset.
-              '''
-	     }
-	     {bits: "2", name: "CTX", desc: '''
-               Writing this bit with a 1 will reset the TX fifo. The
-               bit will clear after the FIFO is reset.
-              '''
-	     }
-	     {bits: "3", name: "DMAS",
-	      desc: "DMA Mode Select. This bit is not used."
-	     }
-	     {bits: "5", name: "E64",
-	      desc: "This bit is reserved because the FIFO is not 64 bytes."
-	     }
-	     {bits: "7:6", name: "TL",
-	      desc: '''
-                These two bits set the interrupt trigger level for the
-                receive FIFO. The received data available interrupt
-                will be set when there are at least this number of
-                bytes in the receive FIFO.
-              '''
-	      enum: [
-      		 { value: "0", name: "rxlvl1", desc: "1 Byte" },
-       		 { value: "1", name: "rxlvl4", desc: "4 Bytes" },
-       		 { value: "2", name: "rxlvl8", desc: "8 bytes" },
-       		 { value: "3", name: "rxlvl14", desc: "14 bytes" }
-	      ]
-	     }
-	 ]}
-    ]}
-      {name: "LCR", desc: "Line control register", swaccess: "rw", fields: [
-       {bits: "1:0", name: "WSIZE", desc: '''
-        These two bits set the word size for both transmission and reception.
-	'''
-       enum: [
-      	   { value: "0", name: "bits5", desc: "5 bits" },
-       	   { value: "1", name: "bits6", desc: "6 bits" },
-       	   { value: "2", name: "bits7", desc: "7 bits" },
-       	   { value: "3", name: "bits8", desc: "8 bits" }
-       ]
-      }
-      {bits: "2", name: "STOP", desc: '''
-        If this bit is clear one stop bit is used. If this bit is set
-        then two stop bits are used for 6,7, and 8 bit transmission
-        and 1.5 stop bits for 5 bit transmission.
-	'''
-      }
-      {bits: "3", name: "PAR", desc: '''
-        If this bit is clear parity is not used. If this bit is set
-        then parity is added according to the PTYPE field.
-	'''
-      }
-      {bits: "5:4", name: "PTYPE", desc: '''
-        These two bits set the parity for both transmission and reception.
-	'''
-       enum: [
-      	   { value: "0", name: "parodd", desc: "Odd parity" },
-       	   { value: "1", name: "pareven", desc: "Even parity" },
-       	   { value: "2", name: "parhigh", desc: "Parity bit always 1" },
-       	   { value: "3", name: "parlow", desc: "Parity bit always 0" }
-       ]
-      }
-      {bits: "6", name: "BRKEN", desc: '''
-        If this bit is clear the line runs as normal. If set then TX
-        is forced low, sending a break condition.
-	'''
-      }
-      {bits: "7", name: "DLAB", desc: '''
-        If this bit is clear the normal registers are accessed at
-        offset 0 and 1. If set then the divisor latch can be accessed.
-	'''
-      }
-    ]},
-    {name: "MCR", desc: "Modem control register", swaccess: "rw", fields: [
-      {bits: "0", name: "FDTR", desc: '''
-        The state of this bit sets the state of the DTR pin. When
-        loopback mode is selected this drives the DSR_L input.
-	'''
-      }
-      {bits: "1", name: "FRTS", desc: '''
-        The state of this bit sets the state of the RTS_L pin. When
-        loopback mode is selected this drives the CTS_L input.
-	'''
-      }
-      {bits: "2", name: "OUT1", desc: '''
-        The state of this bit sets the state of the OUT1 signal. This
-        is only used in loopback mode when it drives the RI input.
-	'''
-      }
-      {bits: "3", name: "OUT2", desc: '''
-        The state of this bit sets the state of the OUT2 signal. This
-        is only used in loopback mode when it drives the DCD_L input.
-	'''
-      }
-      {bits: "4", name: "LOOP", desc: '''
-        This bit should be clear for normal operation. If this bit is
-        set the TX pin will go high, the handshake outputs will go
-        inactive and the receiver inputs are ignorred. Internally the
-        transmitter is looped back to the receiver, allowing testing
-        of the UART.
-	'''
-      }
-      {bits: "5", name: "AFC", desc: '''
-        If this bit is set and the FRTS bit is set the UART will
-        generate RTS based on the trigger level set for the receive
-        FIFO. (If FRTS is clear then RTS will be deasserted whatever
-        the state of this bit.) RTS will be asserted while the FIFO
-        contains fewer bytes than set in the FCR TL field. If a start
-        bit is seen while the FIFO is at or above the trigger level
-        then RTS will be deasserted. If the FIFO is not being emptied
-        one byte above the trigger level will be received, and there
-        could be one byte more to cover the source being slow to
-        respond to the RTS deassertion. In addition when this bit is
-        set the UART will only start trasmission of a character when
-        CTS is asserted.
-	'''
-      }
-
-    ]},
-    {name: "LSR", desc: "Line status register", swaccess: "ro", fields: [
-	{bits: "0", name: "DRDY", desc: '''
-           If this bit is set there is data ready to be read from the
-           receive buffer.
-	   '''
-	}
-	{bits: "1", name: "OVR", swaccess:"rc", desc: '''
-           If this bit is set then the receive FIFO has overrun and
-           data has been lost. The overflow status is raised when a
-           character is received and the FIFO is full, and cleared
-           whenever the LSR is read. Thus the OVR bit does not
-           indicate where in the data the lost character(s) happened.
-	   '''
-	}
-	{bits: "2", name: "PE", desc: '''
-           If this bit is set then the character at the head of the
-           receive FIFO (i.e. the next character to be read from the
-           receive buffer) has a parity error.
-	'''
-	}
-	{bits: "3", name: "FE", desc: '''
-           If this bit is set then the character at the head of the
-           receive FIFO (i.e. the next character to be read from the
-           receive buffer) has a framing error. The STOP bit was not
-           seen as set.
-	'''
-	}
-	{bits: "4", name: "BRK", desc: '''
-           If this bit is set then the character at the head of the
-           receive FIFO (i.e. the next character to be read from the
-           receive buffer) is zero and was the start of a line break condition.
-	   '''
-	}
-	{bits: "5", name: "THRE", resval: "1", desc: '''
-           If this bit is set the transmitter FIFO is empty (but there may
-           be a character being transmitted).
-	   '''
-      }
-	{bits: "6", name: "TEMT", resval: "1", desc: '''
-         If this bit is set the transmitter is empty. There are no
-         characters in the holding register, FIFO or currently
-         being transmitted.
-	 '''
-      }
-	{bits: "7", name: "RFE", swaccess:"rc", desc: '''
-        If this bit is set there is at least one character in the
-        receive FIFO with a parity error or framing error or break
-        condition. This bit is cleared by reading the LSR if there
-        are no subsequent errors in the FIFO.
-	'''
-      }
-    ]},
-    {name: "MSR", desc: "Modem status register", swaccess: "ro", fields: [
-	{bits: "0", name: "DCTS", swaccess:"rc", desc: '''
-          If this bit is set the CTS input has changed since this
-          register was last read. (Note that this bit is set by a
-          transition on the line. If multiple transitions have happened
-          then the velue in the CTS bit may be the same as in the
-          last read even though this bit is set.) In loopback mode a write
-          to the register with a 1 in this bit will cause the bit to be
-          set (and potentially an interrupt raised), writes of 0 are ignorred.
-	'''
-	}
-	{bits: "1", name: "DDSR", swaccess:"rc", desc: '''
-          If this bit is set the DSR input has changed since this
-          register was last read. (See note for DCTS) In loopback mode a write
-          to the register with a 1 in this bit will cause the bit to be
-          set (and potentially an interrupt raised), writes of 0 are ignorred.
-	'''
-	}
-	{bits: "2", name: "TRI", swaccess:"rc", desc: '''
-          If this bit is set a low to high transition was seen on the RI input
-          since this
-          register was last read. (See note for DCTS) In loopback mode a write
-          to the register with a 1 in this bit will cause the bit to be
-          set (and potentially an interrupt raised), writes of 0 are ignorred.
-	  '''
-	}
-	{bits: "3", name: "DDCD", swaccess:"rc", desc: '''
-          If this bit is set the DCD input has changed since this
-          register was last read. (See note for DCTS) In loopback mode a write
-          to the register with a 1 in this bit will cause the bit to be
-          set (and potentially an interrupt raised), writes of 0 are ignorred.
-	'''
-	}
-	{bits: "4", name: "CTS", desc: '''
-           This bit reflects the state of the CTS_L input. Note that since
-           CTS_L is active low, the value of this bit is the inverse of the pin.
-	   '''
-	}
-	{bits: "5", name: "DSR", desc: '''
-        This bit reflects the state of the DSR_L input. Note that since
-        DSR_L is active low, the value of this bit is the inverse of the pin.
-	'''
-      }
-      {bits: "6", name: "RI", desc: '''
-        This bit reflects the state of the RI_L input. Note that since
-        RI_L is active low, the value of this bit is the inverse of the pin.
-	In loopback mode this bit reflects the value of OUT1 in the MCR.
-        '''
-      }
-      {bits: "7", name: "DCD", desc: '''
-        This bit reflects the state of the DCD_L input. Note that since
-        DCD_L is active low, the value of this bit is the inverse of the pin.
-	In loopback mode this bit reflects the value of OUT2 in the MCR.
-	'''
-      }
-    ]},
-    {name: "SCR", desc: "Scratch register", swaccess: "rw", fields: [
-	{bits: "7:0", name: "scratch", resval: "x", desc: '''
-        This register is not used by the hardware. Software may use it
-        as a scratch register.
-	'''
-      }
-    ]},
-
-  ]
-}
diff --git a/util/docgen/examples/uart16550.md b/util/docgen/examples/uart16550.md
deleted file mode 100644
index 8fb3395..0000000
--- a/util/docgen/examples/uart16550.md
+++ /dev/null
@@ -1,280 +0,0 @@
-{{% lowrisc-doc-hdr UART with 16550 style interface }}
-{{% regfile uart16550.hjson }}
-
-The UART16550 provides an asynchronous serial interface that can
-operate at programmable BAUD rates. The main features are:
-
-- 16 byte transmit FIFO
-- 16 byte receive FIFO
-- Programmable baud rate generator
-- Hardware flow control (when enabled)
-- 5, 6, 7, or 8 data bits
-- optional parity bit (even, odd, mark or space)
-- 1 or 2 stop bits when used with 6, 7 or 8 data bits
-- 1 or 1.5 stop bits when used with 5 data bits
-
-## Compatibility
-
-The UART16550 is compatible with the de-facto standard 16550 driver
-with registers at byte offsets.
-
-
-## Theory of operation
-
-*TODO block diagram of UART*
-
-The UART can connect to eight external pins:
-* TX: transmit data output.
-* RX: receive data input.
-* RTS_L: request to send flow control output. This pin is active low.
-* CTS_L: clear to send flow control input. This pin is active low.
-* DTR_L: data terminal ready output. This pin is active low.
-* DSR_L: data set ready input. This pin is active low.
-* DCD_L: data carrier detect input.  This pin is active low.
-* RI_L: ring indicate. This pin is active low.
-
-### Baud Rate
-
-The serial line timing is based on a 16x baud rate clock. The
-programmable baud rate generator is driven by a 133.33MHz clock and
-has a 16 bit divider to generate the 16xbaud rate reference. This
-allows generation of the standard baud rates from 300 to 921600 baud
-with less than 1% error. The divisor is accessed by setting the DLAB
-bit in the line control register which makes the low and high parts of
-the divisor value available for read and write through the byte
-registers at offset 0 (low byte of divisor)and 1 (high byte). Writing
-either of the divisor registers causes the divider counter to be
-reloaded.
-
-Required Baud | Divisor | Actual Baud | Error
---------------|---------|-------------|------
-B |D = INT(0.5 + 133.33MHz/(16*B)) | A = (133.33MHz/D)/16 | (A-B)/B
-300    | 27778 | 300       | 0%
-600    | 13889 | 600.04    | 0.01%
-1200   | 6944  | 1200.08   | 0.01%
-1800   | 4630  | 1799.86   | -0.01%
-2400   | 3472  | 2400.15   | 0.01%
-4800   | 1736  | 4800.31   | 0.01%
-9600   | 868   | 9600.61   | 0.01%
-19200  | 434   | 19201.23  | 0.01%
-38400  | 217   | 38402.46  | 0.01%
-57600  | 145   | 57471.26  | 0.47%
-115200 | 72    | 115740.74 | 0.47%
-230400 | 36    | 231481.48 | 0.47%
-460800 | 18    | 462962.96 | 0.47%
-921600 | 9     | 925925.92 | 0.47%
-
-If the baud rate divisor is set to zero the baud rate clock is stopped
-and the UART will be disabled, this is the default. The baud rate
-clock is automatically stopped to save power when the UART is idle.
-
-### Serial data format
-
-The serial line is high when idle. Characters are sent using a start
-bit (low) followed by 5, 6, 7 or 8 data bits sent least significant
-first. Optionally there may be a parity bit which is computed to give
-either even or odd parity or may be always high or always low. Finally
-there is a stop sequence during which the line is high or one or two
-(1.5 for 5 bit characters) bit times. The start bit for the next
-character may immediately follow the stop sequence, or the line may be
-in the idle (high) state for some time. The data format and (for
-reference) the baud clock are illustrated for the different number of
-data bits with no parity and a single stop bit, and for 8 data bits
-with parity. The line could go idle (high) or the next character start
-after the stop bit where "next" is indicated. All formatting
-parameters are controlled in the !!LCR.
-
-```wavejson
-{signal: [
-  {name:'Baud Clock',  wave: 'p...........' },
-  {name:'Data 8 bit',        wave: '10========1=',
-   data: [ "lsb", "", "", "", "", "", "", "msb", "next" ] },
-  {name:'Data 7 bit',        wave: '10=======1=.',
-   data: [ "lsb", "", "", "", "", "", "msb", "next" ] },
-  {name:'Data 6 bit',        wave: '10======1=..',
-   data: [ "lsb", "", "", "", "", "msb", "next" ] },
-  {name:'Data 5 bit',        wave: '10=====1=...',
-   data: [ "lsb", "", "", "", "msb", "next" ] },
-  {name:'8 with Parity', wave: '10=========1',
-   data: [ "lsb", "", "", "", "", "", "", "msb", "par" ] },
- ],
- head:{
-   text:'Serial Line format (one stop bit)',
-   tock:-1,
- }
-}
-```
-
-The data formatting ensures that in normal operation the line cannot
-be low for more than the number of data bits plus two (low start bit
-plus all zero character plus even or low parity bit) before the stop
-sequence forces the line high. If the line remains low for longer than
-this time the condition is known as a Break. The uart can be set to
-generate a (continuous) break on its output line by setting BrkEn in
-the !!LCR. Detection of a break is signalled by reception of a
-character containing all zeros that is accompanied by the Break Detect
-Flag.
-
-### Serial Data Reception
-
-The UART detect the RX line transitioning from high to low as the
-start of a potential reception. The line is checked after half a bit
-time (8 cycles of the 16xbaud rate clock) and if still low then a
-start bit is detected. Every bit-time (16 cycles of the 16x baud rate
-clock) the data is sampled. One additional bit is sampled following
-the data and parity bits. This should be the stop bit and should
-therefore be set. If the line is detected low when the stop bit is
-expected then the data is still received but is marked with a framing
-error (note that only one bit is checked even if the stop sequence is
-set to two bits). If parity is enabled and the bit does not match the
-expected value then the received character is marked with a parity
-error.
-
-### Serial Data Transmission
-
-The UART will normally format characters (add start, parity and stop
-bits) and transmit them whenever characters are available to be sent
-and the line is idle. However, setting the AFC bit in the !!MCR
-enables automatic flow control. With this setting the transmitter will
-only start to send a character if the CTS_L line is asserted
-(i.e. low) indicating that the peer device is able to receive data.
-
-### Interface FIFOs
-
-The interface has a FIFO to hold characters waiting to be transmitted
-and a FIFO to hold characters that have been received but not yet read
-by software. These FIFOs are 16 characters deep. By default the FIFOs
-are disabled (effectively one character deep) and should be enabled by
-setting the FEN bit in the FIFO Control Register. Note that when the
-FEN bit is set any character that is currently in the holding register
-will be transmitted before the FIFO is enabled (this was not the case
-prior to revision 16 of the UART where it was advised to check the
-TEMT bit in the LSR to ensure there are no characters in-flight when
-the FIFO is enabled).
-
-Writes to the Data Register when the Transmit Holding Register Empty
-(THRE) status bit is set will add characters to the transmit
-FIFO. This status bit will be clear when the FIFO is full, and any
-writes to the Data Register will be discarded.
-
-Reads from the Data Register will return the next received character
-and will remove it from the receive FIFO. Prior to reading the Data
-Register a read should be done of the Line Status Register which will
-indicate if there is data available and give the error flags that
-accompany the character at the head of the FIFO. (The error flags flow
-through the FIFO with their corresponding character.)
-
-Once the FIFOs are enabled the TL field in the FCR can be used to
-configure the number of characters that must bein the receive FIFO to
-trigger two events:
-
-1. The receive data available interrupt is raised (if the FIFO is
-   disabled this is done when a single character is received).
-
-2. If automatic flow control is enabled the RTS_L output is deasserted
-   (i.e. set high) on reception of a start bit.
-
-### Modem/Handshake Signals
-
-The UART has two output lines (RTS\_L and DTR\_L) and four input lines
-(CTS\_L, DSR\_L, DCD\_L and RI\_L) that can be used for modem
-control. However, only the RTS\_L output and CTS\_L input are given
-dedicated pins. The other lines are shared with GPIO signals and the
-GPIO configuration register must be set correctly to enable their
-use. (See section on the GPIO pins.)
-
-The state of the input signals can be read in the Modem Status
-Register which also reports if any of the lines have changed since the
-previous read of the register. Detection of a change in state can
-generate an interrupt. The state of the output lines can be set in the
-Modem Control Register.
-
-If automatic flow control is enabled then the hardware will control
-the RTS\_L output and use the state of the CTS\_L input. RTS\_L will be
-asserted whenever the receive FIFO is full to the threshold level set
-in the TL field of the FIFO Control Register and a start bit is
-detected. RTS\_L will be deasserted whenever the receive FIFO is below
-the threshold. The transmitter will check the CTS\_L signal prior to
-sending a character and will wait for CTS\_L to be asserted before
-starting the character (once a character has been started it will be
-completed before the CTS_L is checked again).
-
-
-### Interrupts and powerdown
-
-The UART can generate an interrupt to the CPU. The Interrupt Enable
-Register configures which UART events cause the interrupt to be raised
-and the Interrupt Identification Register allows detection of the
-cause of the interrupt.  In addition to the normal UART register
-controls, the interrupt may be disabled by setting the intd control
-bit in the PCI header Command register and the state of the interrupt
-may be detected in the is bit of the PCI header Status register. The
-interrupt source number that the UART will use can be read as the
-default value in the iline field of the PCI header.
-
-The UART may be forced into a low power mode by setting either or both
-of the SLP and LPE bits in the Interrupt Enable Register.
-
-### Scratch Register
-
-The UART contains an 8 bit read/write register that is not used by the
-hardware. Software may use this register as it sees fit. The value in
-the scratch register is unpredictable following a reset.
-
-Testing cross reference to !!DATA (or with punctuation !!DATA). The
-strange case will be !!LCR. Which is a different period than in
-!!LCR.DLAB or could be used twice in !!LCR.DLAB. How about
-!!LCR-!!DATA? Phew!
-
-## Programmer Guide
-
-
-### Initialization
-
-The baud rate should be set as previously outlined to enable the UART.
-
-### Interrupts
-
-The UART raises a single interrupt to the system based on the four
-sources that can be enabled in !!IER:
-
-- TXEE: raised if the transmit buffer is empty
-- RDAE: raised if received data is available (if the FIFO is enabled the
-  TL field in the FCR sets the number of characters in the FIFO before
-  this is raised)
-- RLE: The receiver line status has changed
-- MSE: The modem status has changed
-
-
-### Debug Features
-
-A loopback mode can be enabled. In this the output serial data is
-internally looped back to the receiver and the output control lines
-(and two addition signals) are looped back to the four handshake
-inputs. This allows software testing. In this mode the output pins
-will be in their inactive state (i.e. high).
-
-
-## Implementation Guide
-
-The toplevel of the UART has the following signals that connect to
-external pins:
-- TX data output connects to external pin
-- RX: receive data input connects to external pin
-- RTS_L: request to send flow control output. This pin is active
-  low. Connects to external pin.
-- CTS_L: clear to send flow control input. This pin is active
-  low. Connects to external pin.
-- DTR_L: data terminal ready output. This pin is active low.
-- DSR_L: data set ready input. This pin is active low.
-- DCD_L: data carrier detect input.  This pin is active low.
-- RI_L: ring indicate. This pin is active low.
-
-The int signal connects to the interrupt controller.
-
-The 133.33MHz peripheral clock is connected to pclk.
-
-The main register interface is connected on the I/O ring.
-
-## Registers
-{{% registers x }}
diff --git a/util/docgen/examples/uartcfg.hjson b/util/docgen/examples/uartcfg.hjson
deleted file mode 100644
index 8f227d6..0000000
--- a/util/docgen/examples/uartcfg.hjson
+++ /dev/null
@@ -1,286 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "uart",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-  bus_host: "none",
-  available_input_list: [
-    { name: "rx", desc: "Serial receive bit" }
-  ],
-  available_output_list: [
-    { name: "tx", desc: "Serial transmit bit" }
-  ],
-  interrupt_list: [
-    { name: "tx_watermark",  desc: "raised if the transmit FIFO is past the programmed highwater mark."}
-    { name: "rx_watermark",  desc: "raised if the receive FIFO is past the programmed highwater mark."}
-    { name: "tx_overflow",   desc: "raised if the transmit FIFO has overflowed."}
-    { name: "rx_overflow",   desc: "raised if the receive FIFO has overflowed."}
-    { name: "rx_frame_err",  desc: "raised if a framing error has been detected on receive."}
-    { name: "rx_break_err",  desc: "raised if break condition has been detected on receive."}
-    { name: "rx_timeout",    desc: "raised if RX FIFO has characters remaining inFIFO without being retrieved for the programmed time period."}
-    { name: "rx_parity_err", desc: "raised if the receiver has detected a parity error."}
-  ],
-
-  regwidth: "32",
-  registers: [
-    { name: "CTRL",
-      desc: "UART control register",
-      swaccess: "rw",
-      hwaccess: "hro",
-      fields: [
-        { bits: "0",
-          name: "TX",
-          desc: "TX enable"
-        }
-        { bits: "1",
-          name: "RX",
-          desc: "RX enable"
-        }
-        { bits: "2",
-          name: "NF",
-          desc: "RX noise filter enable"
-        }
-        { bits: "4",
-          name: "SLPBK",
-          desc: '''System loopback enable.
-
-                If this bit is turned on, any outgoing bits to TX are received through RX.
-                See Block Diagram.
-                '''
-        }
-        { bits: "5",
-          name: "LLPBK",
-          desc: '''Line loopback enable.
-
-                If this bit is turned on, incoming bits are forwarded to TX for testing purpose.
-                See Block Diagram.
-                '''
-        }
-        { bits: "6",
-          name: "PARITY_EN",
-          desc: "If true, parity is enabled in both RX and TX directions."
-        }
-        { bits: "7",
-          name: "PARITY_ODD",
-          desc: "If PARITY_EN is true, this determines the type, 1 for odd parity, 0 for even."
-        }
-        { bits: "9:8",
-          name: "RXBLVL",
-          desc: '''
-                Trigger level for RX break detection. Sets the number of character
-                times the line must be low to detect a break.
-                ''',
-          enum: [
-            { value: "0",
-              name: "break2",
-              desc: "2 characters"
-            },
-            { value: "1",
-              name: "break4",
-              desc: "4 characters"
-            },
-            { value: "2",
-              name: "break8",
-              desc: "8 characters"
-            },
-            { value: "3",
-              name: "break16",
-              desc: "16 characters"
-            }
-          ]
-        }
-        { bits: "31:16",
-          name: "NCO",
-          desc: "BAUD clock rate control."
-        }
-      ]
-    },
-    { name:     "STATUS"
-      desc:     "UART live status register"
-      swaccess: "ro"
-      hwaccess: "hrw"
-      hwext:    "true"
-      hwre:     "true"
-      fields: [
-        { bits: "0"
-          name: "TXFULL"
-          desc: "TX buffer is full"
-        }
-        { bits: "1"
-          name: "RXFULL"
-          desc: "RX buffer is full"
-        }
-        { bits: "2"
-          name: "TXOVERFLOW"
-          desc: "TX buffer overflow"
-        }
-        { bits: "3"
-          name: "RXOVERFLOW"
-          desc: "RX buffer overflow"
-        }
-        { bits: "4"
-          name: "TXEMPTY"
-          desc: "TX FIFO is empty"
-        }
-        { bits: "5"
-          name: "TXIDLE"
-          desc: "TX is idle"
-        }
-        { bits: "6"
-          name: "RXIDLE"
-          desc: "RX is idle"
-        }
-        { bits: "7"
-          name: "RXEMPTY"
-          desc: "RX FIFO is empty"
-        }
-      ]
-    }
-    { name: "RDATA",
-      desc: "UART read data",
-      swaccess: "ro",
-      hwaccess: "hrw",
-      hwext: "true",
-      hwre: "true",
-      fields: [
-        { bits: "7:0" }
-      ]
-    }
-    { name: "WDATA",
-      desc: "UART write data",
-      swaccess: "wo",
-      hwaccess: "hro",
-      hwqe: "true",
-      fields: [
-        { bits: "7:0" }
-      ]
-    }
-    { name: "FIFO_CTRL",
-      desc: "UART FIFO control register",
-      swaccess: "rw",
-      hwaccess: "hrw",
-      hwqe:     "true",
-      fields: [
-        { bits: "0",
-          name: "RXRST",
-          desc: "RX fifo reset"
-        }
-        { bits: "1",
-          name: "TXRST",
-          desc: "TX fifo reset"
-        }
-        { bits: "4:2",
-          name: "RXILVL",
-          desc: "Trigger level for RX interrupts",
-          enum: [
-            { value: "0",
-              name: "rxlvl1",
-              desc: "1 character"
-            },
-            { value: "1",
-              name: "rxlvl4",
-              desc: "4 characters"
-            },
-            { value: "2",
-              name: "rxlvl8",
-              desc: "8 characters"
-            },
-            { value: "3",
-              name: "rxlvl16",
-              desc: "16 characters"
-            },
-            { value: "4",
-              name: "rxlvl30",
-              desc: "30 characters"
-            },
-            // TODO expect generator to make others reserved
-          ]
-        }
-        { bits: "6:5",
-          name: "TXILVL",
-          desc: "Trigger level for TX interrupts",
-          enum: [
-            { value: "0",
-              name: "txlvl1",
-              desc: "1 character"
-            },
-            { value: "1",
-              name: "txlvl4",
-              desc: "4 characters"
-            },
-            { value: "2",
-              name: "txlvl8",
-              desc: "8 characters"
-            },
-            { value: "3",
-              name: "txlvl16",
-              desc: "16 characters"
-            }
-          ]
-        }
-      ]
-    }
-    { name: "FIFO_STATUS",
-      desc: "UART FIFO status register",
-      swaccess: "ro",
-      hwaccess: "hwo",
-      hwext: "true",
-      fields: [
-        { bits: "4:0",
-          name: "TXLVL",
-          desc: "Current fill level of TX fifo"
-        }
-        { bits: "10:6",
-          name: "RXLVL",
-          desc: "Current fill level of RX fifo"
-        }
-      ]
-    }
-    { name: "OVRD",
-      desc: "UART override control register",
-      swaccess: "rw",
-      hwaccess: "hro",
-      fields: [
-        { bits: "0",
-          name: "TXEN",
-          desc: "Override the TX signal"
-        }
-        { bits: "1",
-          name: "TXVAL",
-          desc: "Value for TX Override"
-        }
-      ]
-    }
-    { name: "VAL",
-      desc: "UART oversampled values",
-      swaccess: "ro",
-      hwaccess: "hwo",
-      hwext:    "true",
-      fields: [
-        { bits: "15:0",
-          name: "RX",
-          desc: '''
-                Last 16 oversampled values of RX. Most recent bit is bit 0, oldest 15.
-                '''
-        }
-      ]
-    }
-    { name: "TIMEOUT_CTRL",
-      desc: "UART RX timeout control",
-      swaccess: "rw",
-      hwaccess: "hro",
-      fields: [
-        { bits: "23:0",
-          name: "VAL",
-          desc: "RX timeout value in UART bit times"
-        }
-        { bits: "31",
-          name: "EN",
-          desc: "Enable RX timeout feature"
-        }
-      ]
-    }
-  ]
-}
diff --git a/util/docgen/examples/uartcfg.md b/util/docgen/examples/uartcfg.md
deleted file mode 100644
index 2ac94e9..0000000
--- a/util/docgen/examples/uartcfg.md
+++ /dev/null
@@ -1,309 +0,0 @@
-{{% lowrisc-doc-hdr UART HWIP Technical Specification }}
-{{% regfile uartcfg.hjson}}
-
-{{% section1 Overview }}
-
-This document specifies UART hardware IP functionality. This module
-conforms to the
-[Comportable guideline for peripheral device functionality.](../../../doc/rm/comportability_specification.md)
-See that document for integration overview within the broader
-top level system.
-
-{{% toc 3 }}
-
-{{% section2 Features }}
-
-- 2-pin full duplex external interface
-- 8-bit data word, optional even or odd parity bit per byte
-- 1 stop bit
-- 32 x 8b RX buffer
-- 32 x 8b TX buffer
-- Programmable baud rate
-- Interrupt for overflow, frame error, parity error, break error, receive
-  timeout
-
-{{% section2 Description }}
-
-The UART module is a serial-to-parallel receive (RX) and parallel-to-serial
-(TX) full duplex design intended to communicate to an outside device, typically
-for basic terminal-style communication. It is programmed to run at a particular
-BAUD rate and contains only a transmit and receive signal to the outside world,
-i.e. no synchronizing clock. The programmable BAUD rate guarantees to be met up
-to 1Mbps.
-
-{{% section2 Compatibility }}
-
-The UART is compatible with the feature set of H1 Secure Microcontroller UART as
-used in the [Chrome OS cr50][chrome-os-cr50] codebase. Additional features such
-as parity have been added.
-
-[chrome-os-cr50]: https://chromium.googlesource.com/chromiumos/platform/ec/+/master/chip/g/
-
-{{% section1 Theory of Operations }}
-
-{{% section2 Block Diagram }}
-
-![UART Block Diagram](block_diagram.svg)
-
-{{% section2 Hardware Interfaces }}
-
-{{% hwcfg uart}}
-
-{{% section2 Design Details }}
-
-### Serial interface (both directions)
-
-TX/RX serial lines are high when idle. Data starts with START bit (1-->0)
-followed by 8 data bits. Least significant bit is sent first. If parity feature
-is turned on, at the end of the data bit, odd or even parity bit follows then
-STOP bit completes one byte data transfer.
-
-```wavejson
-{
-  signal: [
-    { name: 'Baud Clock',     wave: 'p............'                                                        },
-    { name: 'tx',             wave: '10333333331..', data: [ "lsb", "", "", "", "", "", "", "msb" ]        },
-    { name: 'Baud Clock',     wave: 'p............'                                                        },
-    { name: 'tx (w/ parity)', wave: '103333333341.', data: [ "lsb", "", "", "", "", "", "", "msb", "par" ] },
-  ],
-  head: {
-    text: 'Serial Transmission Frame',
-  },
-  foot: {
-    text: 'start bit ("0") at cycle -1, stop bit ("1") at cycle 8, or after parity bit',
-    tock: -2
-  },
-  foot: {
-    text: [
-      'tspan',
-        ['tspan', 'start bit '],
-        ['tspan', {class:'info h4'}, '0'],
-        ['tspan', ' at cycle -1, stop bit '],
-        ['tspan', {class:'info h4'}, '1'],
-        ['tspan', ' at cycle 8, or at cycle 9 after parity bit'],
-      ],
-    tock: -2,
-  }
-}
-```
-
-### Transmission
-
-A write to !!WDATA enqueues a data byte into the 32 depth write
-FIFO, which triggers the transmit module to start UART TX serial data
-transfer. The TX module dequeues the byte from the FIFO and shifts it
-bit by bit out to the UART TX pin when BAUD tick is asserted.
-
-### Reception
-
-The RX module samples the RX input pin with 16x oversampled BAUD
-clock. After it detects START bit, RX module gathers incoming serial
-bits into one byte data and pushes to 32 depth RX FIFO if it receives
-optional parity bit and correct STOP bit.  These pushed data can be read
-out by reading !!RDATA register.
-
-### Interrupts
-
-UART module has a few interrupts including general data flow interrupts
-and unexpected event interrupts.
-
-If the TX or RX FIFO hits the designated depth of entries, interrupts
-`tx_watermark` or `rx_watermark` are raised to inform FW.  FW can
-configure the watermark value via registers !!FIFO_CTRL.RXILVL or
-!!FIFO_CTRL.TXILVL .
-
-If either FIFO receives an additional write request when its FIFO is full,
-the interrupt `tx_overflow` or `rx_overflow` is asserted and the character
-is dropped.
-
-The `rx_frame_err` interrupt is triggered if RX module receives the
-`START` bit and series of data bits but did not detect `STOP` bit (`1`).
-
-```wavejson
-{
-  signal: [
-    { name: 'Baud Clock',        wave: 'p............'                                                 },
-    { name: 'rx',                wave: '10333333330..', data: [ "lsb", "", "", "", "", "", "", "msb" ] },
-    {},
-    { name: 'intr_rx_frame_err', wave: '0..........1.'},
-  ],
-  head: {
-    text: 'Serial Receive with Framing Error',
-  },
-  foot: {
-    text: [
-      'tspan',
-        ['tspan', 'start bit '],
-        ['tspan', {class:'info h4'}, '0'],
-        ['tspan', ' at cycle -1, stop bit '],
-        ['tspan', {class:'error h4'}, '1'],
-        ['tspan', ' missing at cycle 8'],
-      ],
-    tock: -2,
-  }
-}
-```
-
-The `rx_break_err` interrupt is triggered if a break condition has
-been detected. A break condition is defined as a programmable number
-of characters (via !!CTRL.RXBLVL, either 2, 4, 8, or 16) all equal to
-`0` during a frame error. This typically indicates that the UART is not
-being driven at this time.
-
-The `rx_timeout` interrupt is triggered when the RX FIFO has data sitting
-in it without software reading it for a programmable number of bit times
-(with baud rate clock as reference, programmable via !!TIMEOUT_CTRL). This
-is used to alert software that it has data still waiting in the FIFO that
-has not been handled yet. The timeout counter is reset whenever software
-reads a character from the FIFO, or if a new character is received from
-the line.
-
-The `rx_parity_err` interrupt is triggered if parity is enabled and
-the RX parity bit does not match the expected polarity as programmed
-in !!CTRL.PARITY_ODD.
-
-{{% section1 Programmers Guide }}
-
-{{% section2 Initialization }}
-
-The following code snippet shows initializing the UART to a programmable
-baud rate, clearing the RX and TX FIFO, setting up the FIFOs for interrupt
-levels, and enabling some interrupts. The NCO register controls the baud
-rate, and should be set to `(2^20*baud)/freq`, where `freq` is the fixed
-clock frequency. The UART uses `clock_primary` as a clock source.
-
-$$ NCO = {{2^{20} * f_{baud}} \over {f_{pclk}}} $$
-
-```cpp
-#define CLK_FIXED_FREQ_MHZ 48
-
-void uart_init(int baud) {
-  // set baud rate. NCO = baud * 2^20 / clock_freq =~ baud / freq_mhz
-  int setting = baud / CLK_FIXED_FREQ_MHZ;
-  *UART_CTRL_NCO_REG = setting;
-
-  // clear FIFOs and set up to interrupt on any RX, half-full TX
-  *UART_FIFO_CTRL_REG =
-      UART_FIFO_CTRL_RXRST                 | // clear both FIFOs
-      UART_FIFO_CTRL_TXRST                 |
-      (UART_FIFO_CTRL_RXILVL_RXFULL_1 <<3) | // intr on RX 1 character
-      (UART_FIFO_CTRL_TXILVL_TXFULL_16<<5) ; // intr on TX 16 character
-
-  // enable only RX, overflow, and error interrupts
-  *UART_INTR_ENABLE_REG =
-      UART_INTR_ENABLE_RX_WATERMARK_MASK  |
-      UART_INTR_ENABLE_TX_OVERFLOW_MASK   |
-      UART_INTR_ENABLE_RX_OVERFLOW_MASK   |
-      UART_INTR_ENABLE_RX_FRAME_ERR_MASK  |
-      UART_INTR_ENABLE_RX_PARITY_ERR_MASK;
-
-  // at the processor level, the UART interrupts should also be enabled
-}
-```
-
-{{% section2 Common Examples }}
-
-The following code shows the steps to transmit a string of characters.
-
-```cpp
-int uart_tx_rdy() {
-  return ((*UART_FIFO_STATUS_REG & UART_FIFO_STATUS_TXLVL_MASK) == 32) ? 0 : 1;
-}
-
-void uart_send_char(char val) {
-  while(!uart_tx_rdy()) {}
-  *UART_WDATA_REG = val;
-}
-
-void uart_send_str(char *str) {
-  while(*str != \0) {
-    uart_send_char(*str++);
-}
-```
-
-Do the following to receive a character, with -1 returned if RX is empty.
-
-```cpp
-int uart_rx_empty() {
-  return ((*UART_FIFO_STATUS_REG & UART_FIFO_STATUS_RXLVL_MASK) ==
-          (0 << UART_FIFO_STATUS_RXLVL_LSB)) ? 1 : 0;
-}
-
-char uart_rcv_char() {
-  if(uart_rx_empty())
-    return 0xff;
-  return *UART_RDATA_REG;
-}
-```
-
-{{% section2 Interrupt Handling }}
-
-The code below shows one example of how to handle all UART interrupts
-in one service routine.
-
-```cpp
-void uart_interrupt_routine() {
-  volatile uint32 intr_state = *UART_INTR_STATE_REG;
-  uint32 intr_state_mask = 0;
-  char uart_ch;
-  uint32 intr_enable_reg;
-
-  // Turn off Interrupt Enable
-  intr_enable_reg = *UART_INTR_ENABLE_REG;
-  *UART_INTR_ENABLE_REG = intr_enable_reg & 0xFFFFFF00; // Clr bits 7:0
-
-  if (intr_state & UART_INTR_STATE_RX_PARITY_ERR_MASK) {
-    // Do something ...
-
-    // Store Int mask
-    intr_state_mask |= UART_INTR_STATE_RX_PARITY_ERR_MASK;
-  }
-
-  if (intr_state & UART_INTR_STATE_RX_BREAK_ERR_MASK) {
-    // Do something ...
-
-    // Store Int mask
-    intr_state_mask |= UART_INTR_STATE_RX_BREAK_ERR_MASK;
-  }
-
-  // .. Frame Error
-
-  // TX/RX Overflow Error
-
-  // RX Int
-  if (intr_state & UART_INTR_STATE_RX_WATERMARK_MASK) {
-    while(1) {
-      uart_ch = uart_rcv_char();
-      if (uart_ch == 0xff) break;
-      uart_buf.append(uart_ch);
-    }
-    // Store Int mask
-    intr_state_mask |= UART_INTR_STATE_RX_WATERMARK_MASK;
-  }
-
-  // Clear Interrupt State
-  *UART_INTR_STATE_REG = intr_state_mask;
-
-  // Restore Interrupt Enable
-  *UART_INTR_ENABLE_REG = intr_enable_reg;
-}
-```
-
-One use of the `rx_timeout` interrupt is when the !!FIFO_CTRL.RXILVL
-is set greater than one, so an interrupt is only fired when the fifo
-is full to a certain level. If the remote device sends fewer than the
-watermark number of characters before stopping sending (for example it
-is waiting an acknowledgement) then the usual `rx_watermark` interrupt
-would not be raised. In this case an `rx_timeout` would generate an
-interrupt that allows the host to read these additional characters. The
-`rx_timeout` can be selected based on the worst latency experienced by a
-character. The worst case latency experienced by a character will happen
-if characters happen to arrive just slower than the timeout: the second
-character arrives just before the timeout for the first (resetting the
-timer), the third just before the timeout from the second etc. In this
-case the host will eventually get a watermark interrupt, this will happen
-`((RXILVL - 1)*timeout)` after the first character was received.
-
-{{% section2 Register Table }}
-
-{{% registers x }}
diff --git a/util/docgen/examples/win.hjson b/util/docgen/examples/win.hjson
deleted file mode 100644
index 9d228d1..0000000
--- a/util/docgen/examples/win.hjson
+++ /dev/null
@@ -1,234 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "WIND",
-  clock_primary: "clk_fixed",
-  bus_device: "tlul",
-
-  regwidth: "32",
-  registers: [
-    {name: "RDATA", desc: "UART read data",
-      swaccess: "ro", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {name: "WDATA", desc: "UART write data", swaccess: "wo", fields: [
-      {bits: "7:0", resval: "0x0"}
-    ]},
-    {window: {
-    	     name: "win1"
-	     items: "64"
-	     swaccess: "rw"
-	     desc: '''
-	     	   A simple 256 byte window that should get aligned.
-		   It references !!RDATA and ** *bold italcs* **
-		   For testing (it also references !!WDATA) and !!NCO1.
-		   '''
-	}
-    },
-    {name: "NCO", desc: "Baud clock rate control", swaccess: "rw", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {window: {
-    	     name: "win2"
-	     items: "15"
-	     validbits: "16"
-	     byte-write: "True"
-	     noalign: "True"
-	     swaccess: "rw1c"
-	     desc: '''
-	     	   A 60 byte window that does not get aligned.
-		   Should generate warnings
-		   '''
-	}
-    },
-    {name: "NCO1", desc: "Baud clock rate control", swaccess: "rw", fields: [
-      {bits: "15:0", resval: "0b0"}
-    ]},
-    {window: {
-    	     name: "win3"
-	     items: "15"
-	     validbits: "16"
-	     byte-write: "True"
-	     unusual: "True"
-	     swaccess: "rw1c"
-	     desc: '''
-	     	   A 60 byte window that does get aligned.
-		   Marked unusual so no warnings
-		   '''
-	}
-    },
-    {name: "CTRL", desc: "UART control register", swaccess: "rw", fields: [
-      {bits: "0", name: "TX", desc: '''
-        TX enable has a really long description that will go on over
-	several lines and really want to wrap to be seen well in the
-	source format.
-	'''
-	}
-      {bits: "1", name: "RX", desc: "RX enable"}
-      {bits: "2", name: "CTS", desc: "CTS hardware flow-control enable"}
-      {bits: "3", name: "RTS", desc: "RTS hardware flow-control enable"}
-      {bits: "4", name: "SLPBK", desc: "System loopback enable"}
-      {bits: "5", name: "LLPBK", desc: "Line loopback enable"}
-      {bits: "6", name: "RCOS", desc: "Oversample enable for RX and CTS"}
-      {bits: "7", name: "NF", desc: "RX noise filter enable"}
-      {bits: "8", name: "PARITY_EN", desc: "Parity enable"}
-      {bits: "9", name: "PARITY_ODD", desc: "1 for odd parity, 0 for even."}
-    ]}
-    {window: {
-    	     name: "win4"
-	     items: "16"
-	     validbits: "16"
-	     byte-write: "True"
-	     swaccess: "rw"
-	     desc: '''
-	     	   A simple 64 byte window that should get aligned.
-		   '''
-	}
-    },
-    {window: {
-    	     name: "win5"
-	     items: "16"
-	     validbits: "16"
-	     byte-write: "True"
-	     swaccess: "rw"
-	     desc: '''
-	     	   A simple 64 byte window that should immediately follow.
-		   '''
-	}
-    },
-    {name: "ICTRL", desc: "UART Interrupt control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX interrupt enable" }
-      {bits: "1", name: "RX", desc: "RX interrupt enable"}
-      {bits: "2", name: "TXO", desc: "TX overflow interrupt enable"}
-      {bits: "3", name: "RXO", desc: "RX overflow interrupt enable"}
-      {bits: "4", name: "RXF", desc: "RX frame error interrupt enable"}
-      {bits: "5", name: "RXB", desc: "RX break error interrupt enable"}
-      {bits: "7:6", name: "RXBLVL", desc: '''
-       Trigger level for rx break detection. Sets the number of character
-       times the line must be low to detect a break
-       ''',
-       enum: [
-       	       { value: "0", name: "break2", desc: "2 characters" },
-       	       { value: "1", name: "break4", desc: "4 characters" },
-       	       { value: "2", name: "break8", desc: "8 characters" },
-       	       { value: "3", name: "break16", desc: "16 characters" }
-	     ]
-      }
-      {bits: "8", name: "RXTO", desc: "RX timeout interrupt enable"}
-      {bits: "9", name: "RXPE", desc: "RX parity error interrupt enable"}
-    ]}
-    {name: "STATE", desc: "UART state register", swaccess: "ro",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX buffer full" }
-      {bits: "1", name: "RX", desc: "RX buffer full"}
-      {bits: "2", name: "TXO", desc: "TX buffer overflow"}
-      {bits: "3", name: "RXO", desc: "RX buffer overflow"}
-      {bits: "4", name: "TXEMPTY", desc: "TX buffer empty"}
-      {bits: "5", name: "TXIDLE", desc: "TX idle"}
-      {bits: "6", name: "RXIDLE", desc: "RX idle"}
-      {bits: "7", name: "RXEMPTY", desc: "RX fifo empty"}
-    ]}
-    // I suspect STATECLR should be r0w1c or something
-    {name: "STATECLR", desc: "UART state register", swaccess: "rw",
-     fields: [
-      {bits: "19", name: "TXO", desc: "Clear TX buffer overflow"}
-      {bits: "20", name: "RXO", desc: "Clear RX buffer overflow"}
-    ]}
-    {name: "ISTATE", desc: "UART Interrupt state register", swaccess: "ro",
-     fields: [
-      {bits: "0", name: "TX", desc: "TX interrupt state" }
-      {bits: "1", name: "RX", desc: "RX interrupt state"}
-      {bits: "2", name: "TXO", desc: "TX overflow interrupt state"}
-      {bits: "3", name: "RXO", desc: "RX overflow interrupt state"}
-      {bits: "4", name: "RXF", desc: "RX frame error interrupt state"}
-      {bits: "5", name: "RXB", desc: "RX break error interrupt state"}
-      {bits: "6", name: "RXTO", desc: "RX timeout interrupt state"}
-      {bits: "7", name: "RXPE", desc: "RX parity error interrupt state"}
-    ]}
-    {name: "ISTATECLR", desc: "UART Interrupt clear register",
-     swaccess: "r0w1c",
-     fields: [
-      {bits: "0", name: "TX", desc: "Clear TX interrupt" }
-      {bits: "1", name: "RX", desc: "Clear RX interrupt"}
-      {bits: "2", name: "TXO", desc: "Clear TX overflow interrupt"}
-      {bits: "3", name: "RXO", desc: "Clear RX overflow interrupt"}
-      {bits: "4", name: "RXF", desc: "Clear RX frame error interrupt"}
-      {bits: "5", name: "RXB", desc: "Clear RX break error interrupt"}
-      {bits: "6", name: "RXTO", desc: "Clear RX timeout interrupt"}
-      {bits: "7", name: "RXPE", desc: "Clear RX parity error interrupt"}
-    ]}
-    {name: "FIFO", desc: "UART FIFO control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "RXRST", swaccess: "r0w1c", desc: "RX fifo reset" }
-      {bits: "1", name: "TXRST", swaccess: "r0w1c", desc: "TX fifo reset" }
-      {bits: "4:2", name: "RXILVL",
-       desc: "Trigger level for RX interrupts"
-       enum: [
-       	       { value: "0", name: "rxlvl1", desc: "1 character" },
-       	       { value: "1", name: "rxlvl4", desc: "4 characters" },
-       	       { value: "2", name: "rxlvl8", desc: "8 characters" },
-       	       { value: "3", name: "rxlvl16", desc: "16 characters" }
-       	       { value: "4", name: "rxlvl30", desc: "30 characters" }
-	       // TODO expect generator to make others reserved
-	     ]
-      }
-      {bits: "6:5", name: "TXILVL",
-       desc: "Trigger level for TX interrupts"
-       enum: [
-       	       { value: "0", name: "txlvl1", desc: "1 character" },
-       	       { value: "1", name: "txlvl4", desc: "4 characters" },
-       	       { value: "2", name: "txlvl8", desc: "8 characters" },
-       	       { value: "3", name: "txlvl16", desc: "16 characters" }
-	     ]
-      }
-    ]}
-    {name: "RFIFO", desc: "UART FIFO status register", swaccess: "ro",
-     fields: [
-      {bits: "5:0", name: "TXLVL", desc: "Current fill level of TX fifo" }
-      {bits: "11:6", name: "RXLVL", desc: "Current fill level of RX fifo" }
-    ]}
-    {name: "OVRD", desc: "UART override control register", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TXEN", desc: "Override the TX signal" }
-      {bits: "1", name: "TXVAL", desc: "Value for TX Override" }
-      {bits: "2", name: "RTSEN", desc: "Override the RTS signal" }
-      {bits: "3", name: "RTSVAL", desc: "Value for RTS Override" }
-    ]}
-    {name: "VAL", desc: "UART oversampled values", swaccess: "ro",
-     fields: [
-      {bits: "15:0", name: "RX", desc: '''
-       Last 16 oversampled values of RX. Most recent bit is bit 0, oldest 15.
-      ''' }
-      {bits: "31:16", name: "CTS", desc: '''
-       Last 16 oversampled values of CTS. Most recent bit is bit 16, oldest 31.
-      ''' }
-    ]}
-    {name: "RXTO", desc: "UART RX timeout control", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "EN", desc: "Enable RX timeout feature" }
-      {bits: "24:1", name: "VAL", desc: "RX timeout value in UART bit times" }
-    ]}
-    { skipto: "0x0f00" }
-    {name: "ITCR", desc: "UART Integration test control", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "", desc: "-" }
-    ]}
-    {name: "ITOP", desc: "UART Integration test overrides", swaccess: "rw",
-     fields: [
-      {bits: "0", name: "TX", desc: "Drive txint when UART_ITCR asserted" }
-      {bits: "1", name: "RX", desc: "Drive rxint when UART_ITCR asserted" }
-      {bits: "2", name: "TXO", desc: "Drive txoint when UART_ITCR asserted" }
-      {bits: "3", name: "RXO", desc: "Drive rxoint when UART_ITCR asserted" }
-      {bits: "4", name: "RXF", desc: "Drive rxfint when UART_ITCR asserted" }
-      {bits: "5", name: "RXB", desc: "Drive rxbint when UART_ITCR asserted" }
-      {bits: "6", name: "RXTO", desc: "Drive rxtoint when UART_ITCR asserted" }
-      {bits: "7", name: "RXPE", desc: "Drive rxpeint when UART_ITCR asserted" }
-    ]}
-    {name: "DVREG", desc: "DV-accessible test register", swaccess: "rw",
-     fields: [
-      {bits: "7:0", name: "", desc: "-" }
-    ]}
-  ]
-}
diff --git a/util/docgen/generate.py b/util/docgen/generate.py
deleted file mode 100644
index 05f2de7..0000000
--- a/util/docgen/generate.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-
-import logging
-import os
-import shutil
-import sys
-
-import mistletoe
-from pkg_resources import resource_filename
-
-from . import html_data, lowrisc_renderer
-
-
-def generate_doc(src_path, verbose, inlinecss, inlinewave, asdiv):
-    """Generate Document for other library to use
-    """
-
-    if src_path == '-':
-        infile = sys.stdin
-    else:
-        if os.path.isfile(src_path):
-            infile = open(src_path, 'r', encoding='UTF-8')
-        else:
-            logging.error("Source is not a file: %s", src_path)
-            return ""
-
-    if (asdiv):
-        outstr = html_data.header_asdiv
-        # no body to add the onload to, so must inline waveforms
-        inlinewave = True
-    elif (inlinewave):
-        outstr = html_data.header_waveinline
-    else:
-        outstr = html_data.header_wavejs
-
-    if (asdiv):
-        logging.info("asdiv: no CSS included")
-    elif (inlinecss):
-        outstr += "<style type='text/css'>"
-        with open(
-                resource_filename('docgen', 'md_html.css'), 'r',
-                encoding='UTF-8') as fin:
-            outstr += fin.read()
-        with open(
-                resource_filename('reggen', 'reg_html.css'), 'r',
-                encoding='UTF-8') as fin:
-            outstr += fin.read()
-        outstr += "</style>"
-    else:
-        outstr += '<link rel="stylesheet" type="text/css" href="md_html.css">'
-        outstr += '<link rel="stylesheet" type="text/css" href="reg_html.css">'
-
-    outstr += html_data.markdown_header
-
-    # lowrisc_renderer.Document rather than mistletoe.Document to get includes
-    with infile:
-        with lowrisc_renderer.LowriscRenderer(
-                srcfile=src_path, wavejs=not inlinewave) as renderer:
-            document = lowrisc_renderer.Document(infile, src_path)
-            rendered = renderer.render(document)
-            tocpos = rendered.find(html_data.toc_mark_head)
-            toc = renderer.toc
-            if tocpos < 0 or len(toc) == 0:
-                outstr += rendered
-            else:
-                tocp = tocpos + len(html_data.toc_mark_head)
-                toci = tocp
-                while rendered[tocp] != '-':
-                    tocp += 1
-                maxlvl = int(rendered[toci:tocp])
-                outstr += rendered[:tocpos]
-                outstr += html_data.toc_title
-                outstr += '<ul>\n'
-                lvl = 2
-                for x in toc:
-                    # don't expect H1, collapse to H2 if it is there
-                    wantlvl = x[0] if x[0] > 1 else 2
-                    if (wantlvl > maxlvl):
-                        continue
-                    while lvl < wantlvl:
-                        outstr += '<ul>\n'
-                        lvl += 1
-                    while lvl > wantlvl:
-                        outstr += '</ul>\n'
-                        lvl -= 1
-                    outstr += '<li><a href=#' + x[2] + '>' + x[1] + '</a>\n'
-                while lvl > 1:
-                    outstr += '</ul>\n'
-                    lvl -= 1
-                outstr += rendered[tocpos:]
-
-    outstr += html_data.markdown_trailer
-    outstr += html_data.trailer_asdiv if asdiv else html_data.trailer
-
-    return outstr
diff --git a/util/docgen/hjson_lexer.py b/util/docgen/hjson_lexer.py
deleted file mode 100644
index 23e64c1..0000000
--- a/util/docgen/hjson_lexer.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-     Hjson Lexer for pygments
-     ~~~~~~~~~~~~~~~~~~~~~~~~
-
-     Derived from JsonLexer in pygments.lexers.data
-     which is
-     :copyright: Copyright 2006-2017 by the Pygments team, see AUTHORS.
-     :license: BSD, see pygments LICENSE for details.
-
-     Modifications copyright lowRisc contributors
-"""
-
-import re
-
-from pygments.lexer import RegexLexer, include
-from pygments.token import (Comment, Error, Keyword, Literal, Name, Number,
-                            Punctuation, String, Text)
-
-
-class HjsonLexer(RegexLexer):
-    """
-     For HJSON data structures.
-
-     .. versionadded:: 1.5
-     """
-
-    name = 'HJSON'
-    aliases = ['hjson']
-    filenames = ['*.hjson']
-    mimetypes = ['application/hjson']
-
-    flags = re.DOTALL
-
-    # integer part of a number
-    int_part = r'-?(0|[1-9]\d*)'
-
-    # fractional part of a number
-    frac_part = r'\.\d+'
-
-    # exponential part of a number
-    exp_part = r'[eE](\+|-)?\d+'
-
-    tokens = {
-        'whitespace': [
-            (r'\s+', Text),
-        ],
-
-        # represents a simple terminal value
-        'simplevalue': [
-            (r'(true|false|null)\b', Keyword.Constant),
-            (('%(int_part)s(%(frac_part)s%(exp_part)s|'
-              '%(exp_part)s|%(frac_part)s)') % vars(), Number.Float),
-            (int_part, Number.Integer),
-            (r'"(\\\\|\\"|[^"])*"', String.Double),
-        ],
-
-        # the right hand side of an object, after the attribute name
-        'objectattribute': [
-            include('value'),
-            (r':', Punctuation),
-            # triple quote is a multiline string
-            # accept any non-quote, single quote plus non-quote
-            # two quotes plus non-quote to cover all cases
-            (r"'''([^']|'[^']|''[^'])*'''", Text),
-            # comma terminates the attribute but expects more
-            (r',', Punctuation, '#pop'),
-            # a closing bracket terminates the entire object, so pop twice
-            (r'\}', Punctuation, '#pop:2'),
-            # comma is optional in hjson so terminate on anything else
-            # but use re syntax so this match does not consume it
-            # This is should really only be done if a value or string matched
-            (r'(?=.)', Text, '#pop'),
-        ],
-
-        # a json object - { attr, attr, ... }
-        'objectvalue': [
-            include('whitespace'),
-            # a comment
-            (r'#[^\n]*', Comment.Single),
-            (r'//[^\n]*', Comment.Single),
-            (r'"(\\\\|\\"|[^"])*"|(\\\\|[^:])*', Name.Tag, 'objectattribute'),
-            (r'\}', Punctuation, '#pop'),
-        ],
-
-        # json array - [ value, value, ... }
-        'arrayvalue': [
-            include('whitespace'),
-            (r'#[^\n]*', Comment.Single),
-            (r'//[^\n]*', Comment.Single),
-            include('value'),
-            (r',', Punctuation),
-            (r'\]', Punctuation, '#pop'),
-        ],
-
-        # a json value - either a simple value or a complex value
-        # (object or array)
-        'value': [
-            include('whitespace'),
-            (r'#[^\n]*', Comment.Single),
-            (r'//[^\n]*', Comment.Single),
-            include('simplevalue'),
-            (r'\{', Punctuation, 'objectvalue'),
-            (r'\[', Punctuation, 'arrayvalue'),
-        ],
-
-        # the root of a json document whould be a value
-        'root': [
-            (r'#[^\n]*', Comment.Single),
-            (r'//[^\n]*', Comment.Single),
-            include('value'),
-            # hjson does not require the outer {}
-            # this is also helpful for styleguide examples!
-            include('objectvalue'),
-        ],
-    }
diff --git a/util/docgen/html_data.py b/util/docgen/html_data.py
deleted file mode 100644
index 87bcd0c..0000000
--- a/util/docgen/html_data.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-
-header_wavejs = """
-<html>
-<head>
-<meta charset="UTF-8">
-<script src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/2.1.2/skins/default.js"
-        type="text/javascript"></script>
-<script src="https://cdnjs.cloudflare.com/ajax/libs/wavedrom/2.1.2/wavedrom.min.js"
-       type="text/javascript"></script>
-</head>
-<body onload="WaveDrom.ProcessAll()">
-"""
-
-header_waveinline = """
-<html>
-<head>
-<meta charset="UTF-8">
-</head>
-"""
-
-header_asdiv = """
-<div>
-"""
-
-markdown_header = """
-<div class="mdown">
-"""
-
-markdown_trailer = """
-</div>
-"""
-
-register_header = """
-<div>
-"""
-
-register_trailer = """
-</div>
-"""
-
-hwcfg_header = """
-<div class="mdown">
-"""
-
-hwcfg_trailer = """
-</div>
-"""
-
-trailer = """
-</body>
-</html>
-"""
-
-trailer_asdiv = """
-</div>
-"""
-
-lowrisc_title_head = """
-<table class="section_heading">
-<tr><td>lowRISC Comportable IP Document</td></tr>
-<tr><td>
-"""
-
-lowrisc_title_tail = """
-</td></tr>
-<tr><td>&copy; lowrisc.org Contributors</td></tr></table>
-"""
-
-section_template = """
-<table class="{cls}" id="{id}">
-<tr><td>{inner}</td></tr></table>
-"""
-
-doctree_head = "<ul>"
-doctree_template = """
-<li> <a href="{link}">{text}</a> </li>
-"""
-doctree_tail = "</ul>"
-
-toc_title = """
-<h2>Table of Contents</h2>
-"""
-
-toc_mark_head = "<!--TOC "
-toc_mark_tail = "-->\n"
-
-dashboard_header = """
-  <table class="hw-project-dashboard">
-    <thead>
-      <tr>
-        <th>Module</th>
-        <th>Version</th>
-        <th>Life Stage</th>
-        <th>Design Stage</th>
-        <th>Verification Stage</th>
-        <th>Notes</th>
-      </tr>
-    </thead>
-    <tbody>
-"""
-dashboard_trailer = """
-    </tbody>
-  </table>
-"""
-
-specboard_header = """
-  <table class="hw-project-dashboard">
-    <thead>
-      <tr>
-        <th>Module</th>
-        <th>Design Spec</th>
-        <th>DV Plan</th>
-      </tr>
-    </thead>
-    <tbody>
-"""
-specboard_trailer = """
-    </tbody>
-  </table>
-"""
diff --git a/util/docgen/lowrisc_renderer.py b/util/docgen/lowrisc_renderer.py
deleted file mode 100644
index 0375a80..0000000
--- a/util/docgen/lowrisc_renderer.py
+++ /dev/null
@@ -1,450 +0,0 @@
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-"""
-Provides lowRISC extension support for rendering Markdown to html.
-{{% }} directives
-!!Reg !!Reg.Field to generate cross reference to registers
-Syntax highlighting with pygments
-Conversion of WaveJSON timing diagrams
-Adapted from examples in mistletoe.contrib
-<https://github.com/miyuchina/mistletoe/blob/master/contrib/>
-"""
-
-import io
-import logging as log
-import os.path as path
-import re
-import subprocess
-import sys
-from itertools import chain
-from os import walk
-from pathlib import Path
-from urllib.parse import urlparse, urlunparse
-
-import hjson
-import mistletoe.block_token
-import mistletoe.span_token
-from mistletoe import HTMLRenderer
-from mistletoe.block_token import BlockToken, CodeFence, add_token, tokenize
-from mistletoe.span_token import EscapeSequence, RawText, SpanToken
-from pkg_resources import resource_filename
-from pygments import highlight
-from pygments.formatters.html import HtmlFormatter
-from pygments.lexers import get_lexer_by_name as get_lexer
-from pygments.lexers import guess_lexer
-from pygments.styles import get_style_by_name as get_style
-
-import dashboard.gen_dashboard_entry as gen_dashboard_entry
-import reggen.gen_cfg_html as gen_cfg_html
-import reggen.gen_html as gen_html
-import reggen.validate as validate
-from docgen import html_data, mathjax
-from docgen.hjson_lexer import HjsonLexer
-from testplanner import class_defs, testplan_utils
-from wavegen import wavesvg
-
-
-# mirrors Document but adds includes
-# have to pull all the sub-files in to the main text so cross-links work
-# By default anchor links only resolve within a single file
-# arguably this is correct isolation but we want to be able to include anchors
-class Document(BlockToken):
-    """
-    Document token with includes.
-    """
-
-    # Called when the include directive starts with a !
-    # to indicate execute the first word as a command with rest as opts
-    # To help avoid mistakes (and mimimally help avoid attacks in the case
-    # of a trusted docgen given untrusted input files) the command must
-    # live inside the repo (the example uses a local ls script to
-    # run a command from outside, but the script was reviewed and checkedin)
-    def exec_include(self, include_text, basedir):
-        expand = include_text.split(maxsplit=1)
-        cmd = expand[0]
-        opts = '' if len(expand) < 2 else expand[1]
-        abscmd = path.abspath(path.join(basedir, cmd))
-        if not abscmd.startswith(self.treetop):
-            log.error("Blocked include: " + cmd + ' (' + abscmd +
-                      ") is outside the repo.")
-            raise NameError('Command file must be in the repo')
-        # do the cd in the subprocess to avoid save/restore of cwd
-        res = subprocess.run(
-            'cd ' + basedir + '; ' + abscmd + ' ' + opts,
-            shell=True,
-            universal_newlines=True,
-            stdout=subprocess.PIPE).stdout
-        return res.splitlines(keepends=True)
-
-    def add_include(self, l, pat, basedir):
-        lines = []
-        for line in l:
-            match = pat.search(line)
-            # because this is pre-processed a sepcial case is needed to
-            # allow documentation with include command inside back-ticks
-            if (match and not (match.start() > 0 and
-                               line[match.start() - 1] == '`')):
-                lines.append(line[:match.start()] + line[match.end():])
-                if match.group(1)[0] == "!":
-                    try:
-                        res = self.exec_include(match.group(1)[1:], basedir)
-                        lines.extend(self.add_include(res, pat, basedir))
-                    except NameError:
-                        lines.append("Blocked execution of " + match.group(1))
-                else:
-                    incfname = path.join(basedir, match.group(1))
-                    try:
-                        incfile = open(incfname, 'r', encoding='UTF-8')
-                        with incfile:
-                            newdir = path.dirname(incfname)
-                            lines.extend(
-                                self.add_include(incfile, pat, newdir))
-                    except OSError as err:
-                        log.error("Could not open include file: " + str(err))
-                        lines.append("Failed to include " + incfname + "\n\n")
-            else:
-                lines.append(line)
-        return lines
-
-    def __init__(self, lines, srcfile):
-        docdir = path.dirname(resource_filename('docgen', 'md_html.css'))
-        self.treetop = path.abspath(path.join(docdir, "../.."))
-        pat = re.compile(r"\{\{\% *include +(.+?) *\}\}")
-        basedir = ""
-        if len(srcfile) > 0:
-            basedir = path.dirname(srcfile)
-        if basedir == '':
-            basedir = '.'
-        if isinstance(lines, str):
-            lines = lines.splitlines(keepends=True)
-
-        lines = self.add_include(lines, pat, basedir)
-        self.footnotes = {}
-        mistletoe.block_token._root_node = self
-        mistletoe.span_token._root_node = self
-        self.children = tokenize(lines)
-        mistletoe.span_token._root_node = None
-        mistletoe.block_token._root_node = None
-
-
-# mirrors the CodeFence in mistletoe but with additional parameter
-# note this maintains the bug with `~` matching the RE
-class CodeFenceDirective(CodeFence):
-    """
-    Code fence with language and directive
-
-    Supports code blocks starting
-    ```language {directive}
-    Up to 3 spaces indentation, minimum of 3 fence characters,
-    optional spaces, language text, optional spaces, open {,
-    optional spaces, directive text, optional spaces, close }
-    at the moment there cannot be spaces inside language or directive
-    """
-    # future may want something like \{ *([^\}]*\} for multiple directives
-    pattern = re.compile(r'( {0,3})((?:`|~){3,}) *(\S+) *\{ *(\S*) *\}')
-    _open_info = None
-
-    def __init__(self, match):
-        lines, open_info = match
-        self.language = EscapeSequence.strip(open_info[2])
-        self.directive = EscapeSequence.strip(open_info[3])
-        self.children = (RawText(''.join(lines)), )
-
-    @classmethod
-    def start(cls, line):
-        match_obj = cls.pattern.match(line)
-        if not match_obj:
-            return False
-        prepend, leader, lang, direct = match_obj.groups()
-        if (leader[0] in lang or leader[0] in direct or
-                leader[0] in line[match_obj.end():]):
-            return False
-        cls._open_info = len(prepend), leader, lang, direct
-        return True
-
-
-class LowriscEscape(SpanToken):
-    pattern = re.compile(r"\{\{\% *(.+?) +(.+?) *\}\}")
-
-    def __init__(self, match):
-        self.type = match.group(1)
-        self.text = match.group(2)
-
-
-class RegRef(SpanToken):
-    pattern = re.compile(r"!!([A-Za-z0-9_.]+)")
-
-    def __init__(self, match):
-        self.rname = match.group(1)
-
-
-class LowriscRenderer(mathjax.MathJaxRenderer):
-    formatter = HtmlFormatter()
-    formatter.noclasses = True
-
-    def __init__(self, *extras, style='default', srcfile='', wavejs=False):
-        # yapf requests different formatting for this code block depending on
-        # the Python3 version. Work around that by disabling yapf for this code
-        # block.
-        # Bug: https://github.com/google/yapf/issues/696
-        # yapf: disable
-        super().__init__(*chain((LowriscEscape, RegRef,
-                                 CodeFenceDirective), extras))
-        # yapf: enable
-        self.formatter.style = get_style(style)
-        self.regs = None
-        self.wavejs = wavejs
-        self.num_svg = 0
-        # compute base of srcfile to allow relative imports
-        basedir = ""
-        if len(srcfile) > 0:
-            basedir = path.dirname(srcfile)
-        self.basedir = basedir
-        self.toc = []
-
-    # Convert the inner text of header or section into id for html href
-    # inner is a flat string but may have html tags
-    # html id rules are:
-    #    Must contain at least one character
-    #    Must not contain any space characters
-    # Want to match github, can't find its exact rules
-    # The id is derived from the heading text by stripping html tags,
-    # changing whitespace to - and lower-casing.
-    # e.g. 'Theory of operation' becomes 'theory-of-operation
-    # TODO worry about & eg 'Foo & Bar' becomes 'foo-&-bar'
-    def id_from_inner(self, inner):
-        return re.sub(r'\s+', '-', re.sub(r'<.+?>', '', inner)).lower()
-
-    def render_lowrisc_code(self, token, directive):
-        code = token.children[0].content
-        # parser seems to get confused (eg by `~`) and makes empty calls
-        if len(code) == 0:
-            log.warn('Unexpected empty code block. Check for `~`')
-            return ""
-        # waveforms look like embedded code in the markdown
-        # but the WaveDrom javascript wants it in a script tag
-        if token.language == "wavejson":
-            if self.wavejs:
-                return '<script type="WaveDrom">' + code + '</script>'
-            else:
-                try:
-                    wvobj = hjson.loads(code, use_decimal=True)
-                except ValueError as err:
-                    log.warn('wavejson parse failed at line ' +
-                             str(err.lineno) + ': ' + err.msg)
-                    return '<pre>Error line '  + str(err.lineno) + \
-                        ': ' + err.msg + " in:\n" + code[:err.pos] + \
-                        '</pre><pre style="color:red">' + \
-                        code[err.pos:] + '</pre>'
-                self.num_svg += 1
-                return wavesvg.convert(wvobj, self.num_svg - 1)
-        else:
-            # pygments.util.ClassNotFound subclass of ValueError
-            lexer = None
-            if (token.language):
-                if token.language == 'hjson':
-                    lexer = HjsonLexer()
-                else:
-                    try:
-                        lexer = get_lexer(token.language)
-                    except ValueError:
-                        log.info('Failed to get lexer for language=' +
-                                 token.language)
-                        lexer = None
-            if lexer == None:
-                try:
-                    lexer = guess_lexer(code)
-                    log.info('Guess lexer as ' + lexer.name)
-                except ValueError:
-                    log.info('Failed to guess lexer for code=' + code)
-                    lexer = None
-            if lexer:
-                if directive == '.good':
-                    self.formatter.cssstyles='background:#e0ffe0; ' \
-                        'border-left-color: #108040;'
-                elif directive == '.bad':
-                    self.formatter.cssstyles='background:#ffe0e0; ' \
-                        'border-left-color: #c04030'
-                else:
-                    self.formatter.cssstyles = ''
-
-                return highlight(code, lexer, self.formatter)
-            else:
-                return super().render_block_code(token)
-
-    def render_code_fence_directive(self, token):
-        return self.render_lowrisc_code(token, token.directive)
-
-    def render_block_code(self, token):
-        return self.render_lowrisc_code(token, '')
-
-    def render_lowrisc_escape(self, token):
-        # plan eventually to allow lowrisc-doc-hdr=doctype
-        if token.type[:15] == "lowrisc-doc-hdr":
-            return html_data.lowrisc_title_head + token.text + \
-                   html_data.lowrisc_title_tail
-        if token.type == "toc":
-            return html_data.toc_mark_head + token.text + \
-                   html_data.toc_mark_tail
-        if token.type == "regfile":
-            regfile = open(
-                path.join(self.basedir, token.text), 'r', encoding='UTF-8')
-            with regfile:
-                try:
-                    obj = hjson.load(
-                        regfile,
-                        use_decimal=True,
-                        object_pairs_hook=validate.checking_dict)
-                except ValueError:
-                    raise SystemExit(sys.exc_info()[1])
-            if validate.validate(obj) == 0:
-                log.info("Generated register object\n")
-                self.regs = obj
-            else:
-                log.warn("Register import failed\n")
-                self.regs = None
-            return ""
-        if token.type == "registers":
-            if self.regs == None:
-                return "<B>Errors parsing registers prevents insertion.</B>"
-            outbuf = io.StringIO()
-            # note for CSS need to escape the mdown class on the div
-            outbuf.write("</div>" + html_data.register_header)
-            gen_html.gen_html(self.regs, outbuf, toclist=self.toc, toclevel=3)
-            outbuf.write(html_data.register_trailer + '<div class="mdown">')
-            generated = outbuf.getvalue()
-            outbuf.close()
-            return generated
-        if token.type == "cfgfile":
-            log.error("Deprecated lowRISC token cfgfile ignored. Config is now"\
-                      " in a single file with the registers!")
-            return ""
-        if token.type == "hwcfg":
-            if self.regs == None:
-                return "<B>Errors parsing configuration prevents insertion.</B>"
-            outbuf = io.StringIO()
-            # note for CSS need to escape the mdown class on the div
-            outbuf.write("</div>" + html_data.hwcfg_header)
-            gen_cfg_html.gen_cfg_html(self.regs, outbuf)
-            outbuf.write(html_data.hwcfg_trailer + '<div class="mdown">')
-            generated = outbuf.getvalue()
-            outbuf.close()
-            return generated
-        if token.type == "section1":
-            # TODO should token.text get parsed to allow markdown in it?
-            id = self.id_from_inner(token.text)
-            self.toc.append((2, token.text, id))
-            return html_data.section_template.format(
-                cls="section_heading", id=id, inner=token.text)
-        if token.type == "section2":
-            # TODO should token.text get parsed to allow markdown in it?
-            id = self.id_from_inner(token.text)
-            self.toc.append((3, token.text, id))
-            return html_data.section_template.format(
-                cls="subsection_heading", id=id, inner=token.text)
-        if token.type == "doctree":
-            md_paths = []
-            return_string = ''
-            subdirs = [path.join(self.basedir, s) for s in token.text.split()]
-            for subdir in sorted(subdirs):
-                md_paths.extend(sorted(Path(subdir).rglob('*.md')))
-            for md_path in md_paths:
-                rel_md_path = md_path.relative_to(self.basedir)
-                return_string += html_data.doctree_template.format(
-                    link=rel_md_path.with_suffix('.html'),
-                    text=rel_md_path.with_suffix(''))
-            return html_data.doctree_head + return_string + html_data.doctree_tail
-        if token.type == "import_testplan":
-            self.testplan = testplan_utils.parse_testplan(
-                path.join(self.basedir, token.text))
-            return ""
-        if token.type == "insert_testplan":
-            if self.testplan == None:
-                return "<B>Errors parsing testplan prevents insertion.</B>"
-            outbuf = io.StringIO()
-            testplan_utils.gen_html_testplan_table(self.testplan, outbuf)
-            generated = outbuf.getvalue()
-            outbuf.close()
-            return generated
-        if token.type == "dashboard":
-            hjson_paths = []
-            # find all of the .prj.hjson files in the given path
-            hjson_paths.extend(
-                sorted(
-                    Path(path.join(self.basedir,
-                                   token.text)).rglob('*.prj.hjson')))
-            outbuf = io.StringIO()
-            outbuf.write(html_data.dashboard_header)
-            for hjson_path in hjson_paths:
-                gen_dashboard_entry.gen_dashboard_html(hjson_path, outbuf)
-            outbuf.write(html_data.dashboard_trailer)
-            generated = outbuf.getvalue()
-            outbuf.close()
-            return generated
-        if token.type == "specboard":
-            hjson_paths = []
-            # find all of the .prj.hjson files in the given path
-            hjson_paths.extend(
-                sorted(
-                    Path(path.join(self.basedir,
-                                   token.text)).rglob('*.prj.hjson')))
-            outbuf = io.StringIO()
-            outbuf.write(html_data.specboard_header)
-            for hjson_path in hjson_paths:
-                gen_dashboard_entry.gen_specboard_html(hjson_path,
-                    hjson_path.relative_to(self.basedir), outbuf)
-            outbuf.write(html_data.specboard_trailer)
-            generated = outbuf.getvalue()
-            outbuf.close()
-            return generated
-
-        bad_tag = '{{% ' + token.type + ' ' + token.text + ' }}'
-        log.warn("Unknown lowRISC tag " + bad_tag)
-        return bad_tag
-
-    def render_reg_ref(self, token):
-        if self.regs == None:
-            log.warn("!!" + token.rname + ": no register import was done.")
-            return '!!' + token.rname
-        cname = self.regs['name']
-        base = token.rname.partition('.')[0].lower()
-        if not base in self.regs['genrnames']:
-            log.warn("!!" + token.rname + " not found in register list.")
-            return '!!' + token.rname
-
-        if token.rname[-1] == ".":
-            return '<a href="#Reg_' + base + '"><code class=\"reg\">' + \
-                cname + "." + token.rname[:-1] + '</code></a>.'
-        else:
-            return '<a href="#Reg_' + base + '"><code class=\"reg\">' + \
-                cname + "." + token.rname + '</code></a>'
-
-    # copied from mistletoe/html_renderer.py and id added
-    # override heading to insert reference for anchor
-    def render_heading(self, token):
-        template = '<h{level} id="{id}">{inner}</h{level}>'
-        inner = self.render_inner(token)
-        id = self.id_from_inner(inner)
-        self.toc.append((token.level, inner, id))
-        return template.format(level=token.level, inner=inner, id=id)
-
-    # decorator for link rendering functions in class HTMLRenderer
-    # converts relative .md link targets to .html link targets
-    def _convert_local_links(func):
-        def _wrapper_convert_local_links(*args, **kwargs):
-            target_url = urlparse(args[1].target)
-            target_path = Path(target_url.path)
-            # check link is not absolute
-            if not target_url.netloc and target_path.suffix in ['.md', '.mkd']:
-                target_url = target_url._replace(
-                    path=str(target_path.with_suffix('.html')))
-                args[1].target = urlunparse(target_url)
-
-            return func(*args, **kwargs)
-
-        return _wrapper_convert_local_links
-
-    # apply to the link rendering functions inherited from HTMLRenderer
-    render_link = _convert_local_links(HTMLRenderer.render_link)
-    render_auto_link = _convert_local_links(HTMLRenderer.render_auto_link)
diff --git a/util/docgen/mathjax.py b/util/docgen/mathjax.py
deleted file mode 100644
index ca3eb8e..0000000
--- a/util/docgen/mathjax.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copied from mistletoe.contrib
-# <https://github.com/miyuchina/mistletoe/blob/master/contrib/mathjax.py>
-#
-# mistletoe is licenced under the MIT license see LICENSE.mistletoe
-#
-"""
-Provides MathJax support for rendering Markdown with LaTeX to html.
-Taken from mistletoe.contrib <https://github.com/miyuchina/mistletoe/blob/master/contrib/mathjax.py>
-"""
-
-from mistletoe.html_renderer import HTMLRenderer
-from mistletoe.latex_renderer import LaTeXRenderer
-
-
-class MathJaxRenderer(HTMLRenderer, LaTeXRenderer):
-    """
-    MRO will first look for render functions under HTMLRenderer,
-    then LaTeXRenderer.
-    """
-    mathjax_src = '<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML"></script>\n'
-
-    def render_math(self, token):
-        """
-        Ensure Math tokens are all enclosed in two dollar signs.
-        """
-        if token.content.startswith('$$'):
-            return self.render_raw_text(token)
-        return '${}$'.format(self.render_raw_text(token))
-
-    def render_document(self, token):
-        """
-        Append CDN link for MathJax to the end of <body>.
-        """
-        return super().render_document(token) + self.mathjax_src
diff --git a/util/docgen/md_html.css b/util/docgen/md_html.css
deleted file mode 100644
index 429e442..0000000
--- a/util/docgen/md_html.css
+++ /dev/null
@@ -1,144 +0,0 @@
-/* Stylesheet for mistletoe output in class=mdown div */
-/* Copyright lowRISC contributors. */
-/* Licensed under the Apache License, Version 2.0, see LICENSE for details.*/
-/* SPDX-License-Identifier: Apache-2.0 */
-
-.mdown {
-  -ms-text-size-adjust: 100%;
-  -webkit-text-size-adjust: 100%;
-  color: #24292e;
-  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
-  font-size: 16px;
-  word-wrap: break-word;
-  width: 80%;
-  margin-left:auto;
-  margin-right:auto;
-}
-
-.mdown code,
-.mdown pre {
-  font-family: monospace, monospace;
-  font-size: 1em;
-  display: inline;
-  max-width: auto;
-  padding: 0;
-  margin: 0;
-  overflow: visible;
-  line-height: inherit;
-  word-wrap: normal;
-  background-color: transparent;
-  border: 0;
-}
-
-.mdown table {
-  border-spacing: 0;
-  border-collapse: collapse;
-  display: block;
-  width: 100%;
-  overflow: auto;
-}
-
-.mdown table th {
-  font-weight: 600;
-}
-
-.mdown table th,
-.mdown table td {
-  padding: 6px 13px;
-  border: 1px solid black;
-}
-
-.mdown table tr {
-  background-color: #fff;
-  border-top: 1px solid #c6cbd1;
-}
-
-.mdown table tr:nth-child(even) {
-  background-color: lightgray;
-}
-
-p.titleright {
-  font-size: 1em;
-  text-align: right;
-  font-weight: 600;
-}
-
-h1 {
-    text-align: center;
-}
-p.copy {
-    font-weight: 100;
-    font-size: 1em;
-    text-align: left;
-}
-
-code.reg {
-    font-family: monospace, monospace;
-    font-size: 0.8em;
-    color: blue;
-}
-
-.highlight {
-    border-left: 2px solid;
-    font-size: 80%;
-    padding: 12px 8px;
-}
-
-table.section_heading {
-    border: 2px solid black;
-    width: 100%;
-    font-size: 140%;
-    background-color:#ffe8e8;
-    text-align:center;
-    vertical-align:middle;
-    font-family: serif;
-    display: table;
-}
-
-table.section_heading tr,
-table.section_heading td {
-    border: 1px solid black;
-    width: 100%;
-    background-color:#ffe8e8;
-    border: 0px;
-    float: center;
-}
-
-table.subsection_heading {
-    border: 2px solid black;
-    width: 100%;
-    font-size: 110%;
-    background-color:white;
-    text-align:center;
-    vertical-align:middle;
-    font-family: serif;
-    display: table;
-}
-
-table.subsection_heading tr,
-table.subsection_heading td {
-    border: 1px solid black;
-    width: 100%;
-    background-color:white;
-    border: 0px;
-    float: center;
-}
-
-table.hw-project-dashboard table,
-table.hw-project-dashboard td,
-table.hw-project-dashboard th {
-    border: 1px solid #000080;
-}
-table.hw-project-dashboard table {
-    font-family: sans-serif;
-}
-table.hw-project-dashboard .fixleft {
-    font-family: monospace;
-    text-align: left;
-}
-table.hw-project-dashboard th {
-    background-color: #e0e0ff;
-}
-table.hw-project-dashboard td, tr, th {
-    padding: 4px;
-}
diff --git a/util/index.md b/util/index.md
deleted file mode 100644
index 96eb99b..0000000
--- a/util/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Tools
-
-These are predominantly Readme's with details about the tooling scripts. Make sure to also check the corresponding [Reference Manuals](../doc/rm/index.md).
-
-{{% doctree ./ }}
diff --git a/util/reggen/gen_cfg_html.py b/util/reggen/gen_cfg_html.py
index 3cf0308..7d07102 100644
--- a/util/reggen/gen_cfg_html.py
+++ b/util/reggen/gen_cfg_html.py
@@ -25,7 +25,7 @@
     genout(outfile, "<p>Referring to the \n")
     genout(
         outfile,
-        "<a href=\"https://github.com/lowRISC/opentitan/blob/master/doc/rm/comportability_specification.md\">\n"
+        "<a href=\"https://docs.opentitan.org/doc/rm/comportability_specification\">\n"
     )
     genout(outfile, "Comportable guideline for peripheral device functionality</a>,\n")
     genout(outfile,
diff --git a/util/testplanner/README.md b/util/testplanner/README.md
index 6376675..e4b664f 100644
--- a/util/testplanner/README.md
+++ b/util/testplanner/README.md
@@ -1,4 +1,5 @@
-{{% toc 4 }}
+---
+---
 
 # Testplanner tool
 
@@ -38,13 +39,13 @@
   that the reader gets the full picture of what and how the said feature is being
   tested.
 
-  Full [markdown](../doc/rm/markdown_usage_style.md) syntax is supported when writing
+  Full [markdown]({{< relref "doc/rm/markdown_usage_style" >}}) syntax is supported when writing
   the description.
 
 * **tests: list of actual written tests that maps to this planned test**
 
   Testplan is written very early in the V0 stage of the HW development
-  [life-cycle](../../doc/ug/hw_stages.md). When the DV engineer gets to actually
+  [life-cycle]({{< relref "doc/ug/hw_stages" >}}). When the DV engineer gets to actually
   developing the test, it may not map 1:1 to the planned test - it may be possible
   that an already written test that mapped to another planned test also satisfies
   the current one; OR it may also be possible that the planned test needs to be
@@ -160,10 +161,10 @@
 - **common_testplan.hjson**: shared testplan imported within the DUT tesplan
 - **foo_dv_plan.md**: DUT testplan imported within the DV plan doc in markdown
 
-In addition, see [UART DV Plan](../../hw/ip/uart/doc/uart_dv_plan.md) for a
+In addition, see [UART DV Plan]({{< relref "hw/ip/uart/doc/dv_plan" >}}) for a
 real 'production' example of inline expansion of an imported testplan as a table
-within the DV Plan document done using [docgen](../docgen/README.md).
-The [UART tesplan](../../hw/ip/uart/data/uart_testplan.hjson) imports the shared
+within the DV Plan document.
+The [UART tesplan](https://github.com/lowRISC/opentitan/blob/master/hw/ip/uart/data/uart_testplan.hjson) imports the shared
 testplans located at `hw/dv/tools/testplans` area.
 
 ### Limitations
@@ -185,11 +186,6 @@
 $ util/testplanner.py testplanner/examples/foo_testplan.hjson -o /tmp/foo_testplan_table.html
 ```
 
-Generate the testplan table in HTML styled with [docgen](../docgen/README.md):
-```console
-$ util/testplanner.py testplanner/examples/foo_testplan.hjson | ./docgen.py -c -o /tmp/foo_testplan_table.html
-```
-
 Generate regression results table in HTML to stdout:
 ```console
 $ util/testplanner.py testplanner/examples/foo_testplan.hjson -r testplanner/examples/foo_regr_results.hjson
@@ -201,14 +197,8 @@
     -r testplanner/examples/foo_regr_results.hjson -o /tmp/foo_regr_results.html
 ```
 
-Generate regression results table in HTML styled with [docgen](../docgen/README.md):
-```console
-$ util/testplanner.py testplanner/examples/foo_testplan.hjson \
-    -r testplanner/examples/foo_regr_results.hjson | ./docgen.py -c -o /tmp/foo_regr_results.html
-```
-
 ### APIs for external tools
-The [docgen](../docgen/README.md) invokes the testplanner utility functions
+The `util/build_docs.py` invokes the testplanner utility functions
 directly to parse the Hjson testplan and insert a HTML table within the DV
 plan document. This is done by invoking:
 ```console
diff --git a/util/testplanner/examples/foo_dv_plan.md b/util/testplanner/examples/foo_dv_plan.md
deleted file mode 100644
index 2524a0f..0000000
--- a/util/testplanner/examples/foo_dv_plan.md
+++ /dev/null
@@ -1,5 +0,0 @@
-{{% lowrisc-doc-hdr FOO DV plan }}
-{{% import_testplan foo_testplan.hjson }}
-
-## Testplan
-{{% insert_testplan x }}
diff --git a/util/uvmdvgen/README.md b/util/uvmdvgen/README.md
index 1df9efa..12c9c5e 100644
--- a/util/uvmdvgen/README.md
+++ b/util/uvmdvgen/README.md
@@ -72,7 +72,7 @@
 
 The boilerplate code for a UVM agent for an interface can be generated using the
 `-a` switch. This results in the generation of complete agent with classes that
-extend from the [DV library](../../hw/dv/sv/dv_lib/README.md). Please see
+extend from the [DV library]({{< relref "hw/dv/sv/dv_lib/README.md" >}}). Please see
 description for more details.
 
 The tool generates an interface, item, cfg, cov, monitor, driver and sequence
@@ -154,8 +154,8 @@
 
 The boilerplate code for a UVM environment and the testbench for a DUT can be
 generated using the `-e` switch. This results in the generation of classes that
-extend from [DV library](../../hw/dv/sv/dv_lib/README.md). If the `-c` switch is
-passed, it extends from [cip library](../../hw/dv/sv/cip_lib/README.md). With
+extend from [DV library]({{< relref "hw/dv/sv/dv_lib/README.md" >}}). If the `-c` switch is
+passed, it extends from [cip library]({{< relref "hw/dv/sv/cip_lib/doc" >}}). With
 `-ea` switch, user can provide a list of downstream agents to create within the
 environment. Please see description for more details.
 
@@ -190,7 +190,7 @@
 
   This is the UVM reg based RAL model. This is created for completeness. The
   actual RAL model needs to be generated prior to running simulations using the
-  [regtool](../reggen/README.md).
+  [regtool]({{< relref "util/reggen/README.md" >}}).
 
 * **env/i2c_host_scoreboard**
 
@@ -286,14 +286,14 @@
 * **Makefile**
 
   This is the simulation Makefile that is used as the starting point for
-  building and running tests using the [make flow](../../hw/dv/tools/README.md).
+  building and running tests using the [make flow]({{< relref "hw/dv/tools/README.md" >}}).
   It already includes the sanity and CSR suite of tests to allow users to start
   running tests right away.
 
 * **plan.md**
 
   This is the empty DV plan document that will describe the entire testbench. A
-  template for this is available [here](../../hw/dv/doc/dv_plan_template.md).
+  template for this is available [here](https://github.com/lowRISC/opentitan/blob/master/hw/dv/doc/dv_plan_template.md).
 
 #### Examples