Merge at opentitan f243e680

Change-Id: I5c447581944309d63f2d58ec95647815f462bae0
diff --git a/.bazelignore b/.bazelignore
index cbe86f6..58c43f7 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -1,3 +1,5 @@
+# Hugo and fusesoc generate a build/ directory
+build
 hw/ip/prim/util/vendor/google_verible_verilog_syntax_py
 sw/vendor/google_googletest
 util/lowrisc_misc-linters
diff --git a/.bazelrc b/.bazelrc
index 8c70e48..ebf1b60 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 
 # https://docs.opentitan.org/doc/rm/c_cpp_coding_style/#cxx-version specifies
+build --action_env=BAZEL_CXXOPTS="-std=gnu++14"
 build --cxxopt='-std=gnu++14'
 build --conlyopt='-std=gnu11'
 
@@ -21,13 +22,85 @@
 # accessed like we do in util/BUILD
 build --workspace_status_command=util/get_workspace_status.sh
 
-# Generate coverage in lcov format, which can be post-processed by lcov
-# into html-formatted reports.
-coverage --combined_report=lcov --instrument_test_targets --experimental_cc_coverage
+# This enables convenient building for opentitan targets with the argument
+# --config=riscv32
+build:riscv32 --platforms=@crt//platforms/riscv32:opentitan
 
-# Verilator is built for 4 cores and can time out if bazel is running more
-# than 1 test per four cores.
-# Until we get the built-in tag "cpu:4" to work for verilator tests, we can run
-# 1 test per 4 cores to avoid verilator performance falloff and timeouts we'd
-# otherwise see on machines with fewer than 40 cores.
-test --local_test_jobs=HOST_CPUS*0.25
+# Shared configuration for clang's source-based coverage instrumentation.
+# Bazel seems to support this only partially, thus we have to perform some
+# additional processing. See
+# https://github.com/bazelbuild/bazel/commit/21b5eb627d78c09d47c4de957e9e6b56a9ae6fad
+# and `util/coverage/coverage_off_target.py`.
+build:ot_coverage --repo_env='CC=clang'
+build:ot_coverage --repo_env='BAZEL_USE_LLVM_NATIVE_COVERAGE=1'
+build:ot_coverage --java_runtime_version='remotejdk_11'
+# Docs state that bazel will fail to create coverage information if tests have
+# been cached previously. See
+# https://bazel.build/configure/coverage?hl=en#remote-execution
+coverage:ot_coverage --nocache_test_results
+
+# Configuration for measuring off-target coverage. Enable with
+# `--config=ot_coverage_off_target`.
+build:ot_coverage_off_target --config='ot_coverage'
+build:ot_coverage_off_target --collect_code_coverage
+coverage:ot_coverage_off_target --repo_env='GCOV=/usr/bin/llvm-profdata'
+coverage:ot_coverage_off_target --repo_env='BAZEL_LLVM_COV=/usr/bin/llvm-cov'
+
+# Configuration for measuring on-target coverage. Enable with
+# `--config=ot_coverage_on_target`.
+build:ot_coverage_on_target --config='ot_coverage'
+build:ot_coverage_on_target --platforms="@crt//platforms/riscv32:opentitan"
+build:ot_coverage_on_target --define='measure_coverage_on_target=true'
+# Instrument selectively to limit size overhead when measuring on-target coverage.
+# Note: We have to disable optimizations until the corresponding item in #16761 is
+# resolved.
+build:ot_coverage_on_target --per_file_copt='//sw/device/silicon_creator[/:].*,//sw/device/lib/base:.*@-fprofile-instr-generate,-fcoverage-mapping,-O0'
+# Needed to be able to build host binaries while collecting coverage.
+coverage:ot_coverage_on_target --platforms=""
+
+# Configuration to override resource constrained test
+# scheduling. Enable with `--config=local_test_jobs_per_cpus`
+test:local_test_jobs_per_cpus --local_test_jobs=HOST_CPUS*0.22
+# Verilator is built for 4 cores, but requires a harness and additional overhead.
+# Empirically 72 cores can support 16 simultaneous tests, but not 17. Setting
+# this will ignore tags like "cpu:5"
+
+# We have verilator tests that take more than an hour to complete
+test --test_timeout=60,300,1500,7200
+
+# AddressSanitizer (ASan) catches runtime out-of-bounds accesses to globals, the
+# stack, and (less importantly for OpenTitan) the heap. ASan instruments
+# programs at compile time and also requires a runtime library.
+#
+# ASan documentation: https://clang.llvm.org/docs/AddressSanitizer.html
+#
+# Enable ASan with --config=asan.
+build:asan --copt -fsanitize=address
+build:asan --copt -g
+build:asan --strip=never
+build:asan --copt -fno-omit-frame-pointer
+build:asan --linkopt -fsanitize=address
+
+# UndefinedBehaviorSanitizer (UBSan) catches C/C++ undefined behavior at
+# runtime, e.g. signed integer overflow. UBSan instruments programs at compile
+# time and also requires a runtime library.
+#
+# UBSan documentation:
+# https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
+#
+# Enable UBSan with --config=ubsan.
+build:ubsan --copt -fsanitize=undefined
+build:ubsan --copt -g
+build:ubsan --strip=never
+build:ubsan --copt -fno-omit-frame-pointer
+build:ubsan --linkopt -fsanitize=undefined
+
+# Enable the rust nightly toolchain
+build --@rules_rust//rust/toolchain/channel=nightly
+
+# Enable the rust 'clippy' linter.
+#build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect
+#build --output_groups=+clippy_checks
+
+# Import site-specific configuration.
+try-import .bazelrc-site
diff --git a/.gitattributes b/.gitattributes
index 334a8b6..786d919 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -11,3 +11,18 @@
 hw/rdc/tools/*/run-rdc.tcl   cdc
 hw/rdc/tools/dvsim/rdc.mk    cdc dvsim
 hw/rdc/tools/dvsim/*.hjson   cdc dvsim
+
+# doc
+/doc/   doc
+**/*.md doc
+
+hw/**/doc/*.md         doc
+hw/**/doc/*.svg        doc
+hw/**/doc/*.png        doc
+hw/**/doc/*.jpg        doc
+hw/**/data/*.prj.hjson doc
+
+sw/**/*.md  doc
+sw/**/*.svg doc
+sw/**/*.jpg doc
+sw/**/*.png doc
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6deb4e3..f42e35a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -24,8 +24,6 @@
 
 # Bazel build rules
 *.bzl               @cfrantz @drewmacrae
-BUILD               @cfrantz @drewmacrae
-BUILD.bazel         @cfrantz @drewmacrae
 /WORKSPACE          @cfrantz @drewmacrae
 /.bazelrc           @cfrantz @drewmacrae
 
@@ -44,7 +42,6 @@
 /hw/ip/alert_handler/   @msfschaffner
 /hw/ip/flash_ctrl/      @tjaychen
 /hw/ip/kmac/rtl/        @eunchan
-/hw/ip/padctrl/         @msfschaffner @tjaychen
 /hw/ip/pinmux/          @msfschaffner @tjaychen
 /hw/ip/prim*            @tjaychen
 /hw/ip/rv_core_ibex/    @tjaychen
@@ -72,6 +69,6 @@
 /util/licence-checker.hjson  @mundaym
 
 # CI and testing
-/ci/                @milesdai @rswarbrick
+/ci/                @drewmacrae @milesdai @rswarbrick
 # /test/            # TBD
-azure-pipelines.yml @milesdai @rswarbrick
+azure-pipelines.yml @drewmacrae @milesdai @rswarbrick
diff --git a/.github/ISSUE_TEMPLATE/chip-level-test.yml b/.github/ISSUE_TEMPLATE/chip-level-test.yml
new file mode 100644
index 0000000..e697d13
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/chip-level-test.yml
@@ -0,0 +1,55 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+name: Chip-Level Test
+description: Issue to track the development of a chip-level test.
+title: "[chip-test] "
+labels: ["Component:ChipLevelTest"]
+body:
+  - type: input
+    id: name
+    attributes:
+      label: Test point name
+      description: Test point name with link to entry in test plan.
+      placeholder: >
+        Example: [chip_sw_uart_tx_rx](https://github.com/lowRISC/opentitan/blob/65e2c995beb96806d13bf04dcd8ef25ca64ac6c5/hw/top_earlgrey/data/chip_testplan.hjson#L24)
+    validations:
+      required: true
+  - type: dropdown
+    id: host-side-component
+    attributes:
+      label: Host side component
+      description: Does this test require development of a host side component? (In other words, does the test require an external stimulus, like receiving UART or SPI transactions?)
+      options:
+        - None Required
+        - Unknown
+        - SystemVerilog
+        - Rust
+        - SystemVerilog+Rust
+  - type: dropdown
+    id: opentitantool-infra-done
+    attributes:
+      label: OpenTitanTool infrastructure implemented
+      description: Does the required opentitantool infrastructure exist so that the Rust component can be developed? If not, this must be developed in SV. 'None' means that there is no Rust component for this test.
+      options:
+        - Unknown
+        - "Yes"
+        - "No"
+  - type: input
+    id: contact
+    attributes:
+      label: Contact person
+      description: GitHub username for a hardware engineer who can answer questions.
+  - type: textarea
+    id: checklist
+    attributes:
+      label: Checklist
+      value: |
+        Please fill out this checklist as items are completed. Link to PRs and issues as appropriate.
+        - [ ] Check if existing test covers most or all of this testpoint (if so, either extend said test to cover all points, or skip the next 3 checkboxes)
+        - [ ] Device-side (C) component developed
+        - [ ] Bazel build rules developed
+        - [ ] Host-side component developed
+        - [ ] HJSON test plan updated with test name (so it shows up in the dashboard)
+        - [ ] Test added to dvsim nightly regression (and passing at time of checking)
diff --git a/.github/ISSUE_TEMPLATE/darjeeling-issue.yml b/.github/ISSUE_TEMPLATE/darjeeling-issue.yml
new file mode 100644
index 0000000..6cbe885
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/darjeeling-issue.yml
@@ -0,0 +1,15 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+name: Darjeeling
+description: Issues specific to top_darjeeling.
+title: "[top_darjeeling] "
+labels: ["Component:Darjeeling"]
+body:
+  - type: textarea
+    id: desc
+    attributes:
+      label: desc
+      value: |
+        Please describe issue or task
diff --git a/.github/ISSUE_TEMPLATE/general-issue.yml b/.github/ISSUE_TEMPLATE/general-issue.yml
new file mode 100644
index 0000000..e9a776c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/general-issue.yml
@@ -0,0 +1,15 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+name: General Issue
+description: A blank issue for general purpose use.
+title: "[component] Title"
+body:
+- type: textarea
+  attributes:
+    label: Description
+    value: |
+      Give a description of the problem, listing similar issues and if able:
+      - Assign the issue to someone familiar with it.
+      - Add appropriate labels.
diff --git a/.github/ISSUE_TEMPLATE/test-triage.yml b/.github/ISSUE_TEMPLATE/test-triage.yml
new file mode 100644
index 0000000..2c70c31
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/test-triage.yml
@@ -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
+
+name: Test Triage
+description: Issue to track regression failure triaging.
+title: "[test-triage] "
+labels: ["Component:TestTriage"]
+body:
+  - type: dropdown
+    id: hierarchy
+    attributes:
+      label: Hierarchy of regression failure
+      description: What hierarchy level is the regression failure?
+      options:
+        - Block level
+        - Chip Level
+    validations:
+      required: true
+  - type: textarea
+    id: description
+    attributes:
+      label: Failure Description
+      description: A description of the test failure
+      placeholder: >
+        Example:  UVM_FATAL @   1.270237 us: (mem_bkdr_util.sv:480) [mem_bkdr_util[Rom]] file test_rom_sim_dv.scr.39.vmem could not be opened for r mode
+    validations:
+      required: true
+  - type: textarea
+    id: reproduction
+    attributes:
+      label: Steps to Reproduce
+      value: |
+        - GitHub Revision: HASH_VALUE
+        - dvsim invocation command to reproduce the failure, inclusive of build and run seeds:
+          ./util/dvsim/dvsim.py hw/top_earlgrey/dv/chip_sim_cfg.hjson -i TEST_NAME --build-seed BUILD_SEED --fixed-seed FAILING_SEED --waves fsdb
+        - Kokoro build number if applicable
+    validations:
+      required: true
+  - type: textarea
+    id: related-tests
+    attributes:
+      label: Tests with similar or related failures
+      value: |
+        - [ ] Test_name_1
+        - [ ] Test_name_2
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5a51190
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+version: 2
+updates:
+  - package-ecosystem: "pip"
+    directory: "/"
+    schedule:
+      # Updates are scheduled at the beginning of the work week.
+      interval: "weekly"
+      day: "monday"
+      time: "02:00"
+      # US Pacific Time
+      timezone: "America/Los_Angeles"
+    commit-message:
+      prefix: "[dependabot]"
+      reviewers:
+        - "lowRISC/ot-python-reviewers"
+    # Ignore packages fetched from GitHub URLs.
+    ignore:
+      - dependency-name: "*fusesoc*"
+      - dependency-name: "*edalize*"
+      - dependency-name: "*chipwhisperer*"
+    reviewers:
+      - "timothytrippel"
diff --git a/.gitignore b/.gitignore
index b755c1d..110b2e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,15 +70,26 @@
 hw/top_englishbreakfast/ip/xbar_peri/xbar_peri.core
 
 # Rust Cargo build system files.
-Cargo.lock
 sw/host/**/target
 rust-project.json
 
 # Bazel-related cache and output directories
 bazel-*
 
+# Site-specific bazel configuration
+.bazelrc-site
+
 # Compilation database generated from build system
 compile_commands.json
 
 # Local binaries that should not be committed to the codebase
 .bin/
+
+# Clangd index directory.
+.cache/clangd/
+
+# mdbook builds into `./book` directories, let's ignore them
+book
+
+# Hugo Lock
+.hugo_build.lock
diff --git a/BUILD.bazel b/BUILD.bazel
index d81bc26..d3449e6 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -2,72 +2,12 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
-load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier")
-load("//rules:quality.bzl", "clang_format_check", "html_coverage_report")
-load("//rules:cargo.bzl", "cargo_raze")
-load("@lowrisc_lint//rules:rules.bzl", "licence_check")
-load("@rules_rust//rust:defs.bzl", "rust_analyzer")
-
 package(default_visibility = ["//visibility:public"])
 
-exports_files(["python-requirements.txt"])
-
-unbuildify = [
-    "./WORKSPACE",  # Prevent Buildifier from inserting unnecessary newlines.
-    "./**/vendor/**",
-    "./util/lowrisc_misc-linters/**",
-    "./build/**",
-]
-
-buildifier(
-    name = "buildifier_fix",
-    exclude_patterns = unbuildify,
-)
-
-buildifier(
-    name = "buildifier_check",
-    diff_command = "diff -u",
-    exclude_patterns = unbuildify,
-    mode = "diff",
-)
-
-licence_check(
-    name = "license_check",
-    exclude_patterns = [".style.yapf"],
-    licence = """
-    Copyright lowRISC contributors.
-    Licensed under the Apache License, Version 2.0, see LICENSE for details.
-    SPDX-License-Identifier: Apache-2.0
-    """,
-)
-
-clang_format_exclude = [
-    # Vendored source code dirs
-    "./**/vendor/**",
-    # Rust cargo build dirs
-    "./**/target/**",
-    # Directories used exclusively to store build artifacts are still copied into.
-    "./build-out/**",
-    "./build-bin/**",
-    # fusesoc build dir
-    "./build/**",
-]
-
-clang_format_check(
-    name = "clang_format_check",
-    exclude_patterns = clang_format_exclude,
-    mode = "diff",
-)
-
-clang_format_check(
-    name = "clang_format_fix",
-    exclude_patterns = clang_format_exclude,
-    mode = "fix",
-)
-
-html_coverage_report(
-    name = "html_coverage_report",
-)
+exports_files([
+    "WORKSPACE",
+    "python-requirements.txt",
+])
 
 filegroup(
     name = "cores",
@@ -82,20 +22,3 @@
     name = "gen_rust_project",
     actual = "@rules_rust//tools/rust_analyzer:gen_rust_project",
 )
-
-# Do not use directly, run `:gen_rust_project` instead.
-rust_analyzer(
-    name = "rust_analyzer",
-    targets = [
-        "//sw/host/opentitanlib:opentitanlib",
-        "//sw/host/opentitansession:opentitansession",
-        "//sw/host/opentitantool:opentitantool",
-    ],
-)
-
-cargo_raze(
-    name = "cargo_raze",
-    cargo = [
-        "third_party/rust/crates/Cargo.toml",
-    ],
-)
diff --git a/COMMITTERS b/COMMITTERS
index a6cf9ab..874ef5a 100644
--- a/COMMITTERS
+++ b/COMMITTERS
@@ -13,10 +13,14 @@
 * Jon Flatley (jon-flatley)
 * Chris Frantz (cfrantz)
 * Srikrishna Iyer (sriyerg)
-* Eunchan Kim (eunchan)
+* Eli Kim (eunchan)
+* Jaedon Kim (jdonjdon)
+* Andreas Kurth (andreaskurth)
 * Martin Lueker-Boden (martin-lueker)
 * Rasmus Madsen (rasmus-madsen)
 * Guillermo Maturana (matutem)
+* Drew Macrae (drewmacrae)
+* Dan McArdle (dmcardle)
 * Felix Miller (felixmiller)
 * Michael Munday (mundaym)
 * Miguel Osorio (moidx)
@@ -25,7 +29,6 @@
 * Dominic Rizzo (domrizz0)
 * Michael Schaffner (msfschaffner)
 * Rupert Swarbrick (rswarbrick)
-* Canberk Topal (ctopal)
 * Timothy Trippel (timothytrippel)
 * Alphan Ulusoy (alphan)
 * Pirmin Vogel (vogelpi)
diff --git a/SUMMARY.md b/SUMMARY.md
new file mode 100644
index 0000000..33f6b73
--- /dev/null
+++ b/SUMMARY.md
@@ -0,0 +1,433 @@
+# Summary
+
+[OpenTitan](./doc/introduction.md)
+
+# Hardware
+
+- [Introduction](./hw/README.md)
+
+- [Top Earlgrey](./hw/top_earlgrey/README.md)
+  - [Datasheet](./hw/top_earlgrey/doc/specification.md)
+  - [Design](./hw/top_earlgrey/doc/design/README.md)
+  - [Design Verification](./hw/top_earlgrey/dv/README.md)
+  - [Analog Sensor Top](./hw/top_earlgrey/ip/ast/README.md)
+  - [Alert Handler](./hw/top_earlgrey/ip_autogen/alert_handler/README.md)
+    - [Design Verification](./hw/top_earlgrey/ip_autogen/alert_handler/dv/README.md)
+      - [Testplan](./hw/top_earlgrey/ip_autogen/alert_handler/data/alert_handler_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip_autogen/alert_handler/data/alert_handler.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_alert_handler.h)
+    - [Checklist](./hw/top_earlgrey/ip_autogen/alert_handler/doc/checklist.md)
+  - [Interrupt Controller](./hw/top_earlgrey/ip_autogen/rv_plic/README.md)
+    - [Design Verification](./hw/top_earlgrey/ip_autogen/rv_plic/doc/dv/README.md)
+      - [Testplan](./hw/top_earlgrey/ip_autogen/rv_plic/data/rv_plic_fpv_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip_autogen/rv_plic/data/rv_plic.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_rv_plic.h)
+    - [Checklist](./hw/top_earlgrey/ip_autogen/rv_plic/doc/checklist.md)
+  - [Sensor Control](./hw/top_earlgrey/ip/sensor_ctrl/README.md)
+    - [Interface and Registers](./hw/top_earlgrey/ip/sensor_ctrl/data/sensor_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_sensor_ctrl.h)
+    - [Checklist](./hw/top_earlgrey/ip/sensor_ctrl/doc/checklist.md)
+  - [TL-UL Checklist](./hw/top_earlgrey/ip/xbar/doc/checklist.md)
+  - [Pinmux Targets](./hw/top_earlgrey/ip/pinmux/doc/autogen/targets.md)
+    - [ASIC Target Pinout and Pinmux Connectivity](./hw/top_earlgrey/ip/pinmux/doc/autogen/pinout_asic.md)
+    - [CW310 Target Pinout and Pinmux Connectivity](./hw/top_earlgrey/ip/pinmux/doc/autogen/pinout_cw310.md)
+    - [NEXYSVIDEO Target Pinout and Pinmux Connectivity](./hw/top_earlgrey/ip/pinmux/doc/autogen/pinout_nexysvideo.md)
+
+- [Cores](./hw/doc/cores.md)
+  - [Ibex RISC-V Core Wrapper](./hw/ip/rv_core_ibex/README.md)
+    - [Design Verification](./hw/ip/rv_core_ibex/dv/README.md)
+    - [Interface and Registers](./hw/ip/rv_core_ibex/data/rv_core_ibex.hjson)
+    - [Checklist](./hw/ip/rv_core_ibex/doc/checklist.md)
+  - [OTBN](./hw/ip/otbn/README.md)
+    - [Developer Guide](./hw/ip/otbn/doc/developer-guide.md)
+    - [ISA Guide](./hw/ip/otbn/doc/isa.md)
+    - [Design Verification](./hw/ip/otbn/dv/README.md)
+      - [Random Instruction Generator](./hw/ip/otbn/dv/rig/README.md)
+        - [Internals](./hw/ip/otbn/dv/rig/rig/README.md)
+        - [Configuration](./hw/ip/otbn/dv/rig/rig/configs/README.md)
+      - [memutil wrapper](./hw/ip/otbn/dv/memutil/README.md)
+      - [OTBN Simulation Software](./hw/ip/otbn/dv/otbnsim/README.md)
+      - [Tracer](./hw/ip/otbn/dv/tracer/README.md)
+      - [Formal Masking Verification Using Alma](./hw/ip/otbn/pre_sca/alma/README.md)
+    - [Functional Coverage](./hw/ip/otbn/dv/doc/fcov.md)
+    - [Interface and Registers](./hw/ip/otbn/data/otbn.hjson)
+    - [Checklist](./hw/ip/otbn/doc/checklist.md)
+
+- [Hardware IP Blocks](./hw/ip/README.md)
+  - [Analog to Digital Converter Control](./hw/ip/adc_ctrl/README.md)
+    - [Design Verification](./hw/ip/adc_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/adc_ctrl/data/adc_ctrl_testplan.hjson)
+    - [Checklist](./hw/ip/adc_ctrl/doc/checklist.md)
+    - [Interface and Registers](./hw/ip/adc_ctrl/data/adc_ctrl.hjson)
+  - [AES](./hw/ip/aes/README.md)
+    - [Design Verification](./hw/ip/aes/dv/README.md)
+      - [Testplan](./hw/ip/aes/data/aes_testplan.hjson)
+    - [Interface and Registers](./hw/ip/aes/data/aes.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_aes.h)
+    - [Checklist](./hw/ip/aes/doc/checklist.md)
+  - [AON Timer](./hw/ip/aon_timer/README.md)
+    - [Design Verification](./hw/ip/aon_timer/dv/README.md)
+      - [Testplan](./hw/ip/aon_timer/data/aon_timer_testplan.hjson)
+    - [Interface and Registers](./hw/ip/aon_timer/data/aon_timer.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_aon_timer.h)
+    - [Checklist](./hw/ip/aon_timer/doc/checklist.md)
+  - [Clock Manager](./hw/ip/clkmgr/README.md)
+    - [Design Verification](./hw/ip/clkmgr/dv/README.md)
+      - [Testplan](./hw/ip/clkmgr/data/clkmgr_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_clkmgr.h)
+    - [Checklist](./hw/ip/clkmgr/doc/checklist.md)
+  - [CSRNG](./hw/ip/csrng/README.md)
+    - [Design Verification](./hw/ip/csrng/dv/README.md)
+      - [Testplan](./hw/ip/csrng/data/csrng_testplan.hjson)
+    - [Interface and Registers](./hw/ip/csrng/data/csrng.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_csrng.h)
+    - [Checklist](./hw/ip/csrng/doc/checklist.md)
+  - [EDN](./hw/ip/edn/README.md)
+    - [Design Verification](./hw/ip/edn/dv/README.md)
+      - [Testplan](./hw/ip/edn/data/edn_testplan.hjson)
+    - [Interface and Registers](./hw/ip/edn/data/edn.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_edn.h)
+    - [Checklist](./hw/ip/edn/doc/checklist.md)
+  - [Entropy Source](./hw/ip/entropy_src/README.md)
+    - [Design Verification](./hw/ip/entropy_src/dv/README.md)
+      - [Testplan](./hw/ip/entropy_src/data/entropy_src_testplan.hjson)
+    - [Interface and Registers](./hw/ip/entropy_src/data/entropy_src.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_entropy_src.h)
+    - [Checklist](./hw/ip/entropy_src/doc/checklist.md)
+  - [Flash Controller](./hw/ip/flash_ctrl/README.md)
+    - [Design Verification](./hw/ip/flash_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/flash_ctrl/data/flash_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/flash_ctrl/data/flash_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_flash_ctrl.h)
+    - [Checklist](./hw/ip/flash_ctrl/doc/checklist.md)
+  - [GPIO](./hw/ip/gpio/README.md)
+    - [Design Verification](./hw/ip/gpio/dv/README.md)
+      - [Testplan](./hw/ip/gpio/data/gpio_testplan.hjson)
+    - [Interface and Registers](./hw/ip/gpio/data/gpio.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_gpio.h)
+    - [Checklist](./hw/ip/gpio/doc/checklist.md)
+  - [HMAC](./hw/ip/hmac/README.md)
+    - [Design Verification](./hw/ip/hmac/dv/README.md)
+      - [Testplan](./hw/ip/hmac/data/hmac_testplan.hjson)
+    - [Interface and Registers](./hw/ip/hmac/data/hmac.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_hmac.h)
+    - [Checklist](./hw/ip/hmac/doc/checklist.md)
+  - [I2C](./hw/ip/i2c/README.md)
+    - [Design Verification](./hw/ip/i2c/dv/README.md)
+      - [Testplan](./hw/ip/i2c/data/i2c_testplan.hjson)
+    - [Interface and Registers](./hw/ip/i2c/data/i2c.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_i2c.h)
+    - [Checklist](./hw/ip/i2c/doc/checklist.md)
+  - [Key Manager](./hw/ip/keymgr/README.md)
+    - [Design Verification](./hw/ip/keymgr/dv/README.md)
+      - [Testplan](./hw/ip/keymgr/data/keymgr_testplan.hjson)
+    - [Interface and Registers](./hw/ip/keymgr/data/keymgr.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_keymgr.h)
+    - [Checklist](./hw/ip/keymgr/doc/checklist.md)
+  - [KMAC](./hw/ip/kmac/README.md)
+    - [Design Verification](./hw/ip/kmac/dv/README.md)
+      - [Testplan](./hw/ip/kmac/data/kmac_testplan.hjson)
+    - [Interface and Registers](./hw/ip/kmac/data/kmac.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_kmac.h)
+    - [Checklist](./hw/ip/kmac/doc/checklist.md)
+  - [Life Cycle Controller](./hw/ip/lc_ctrl/README.md)
+    - [Design Verification](./hw/ip/lc_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/lc_ctrl/data/lc_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/lc_ctrl/data/lc_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_lc_ctrl.h)
+    - [Checklist](./hw/ip/lc_ctrl/doc/checklist.md)
+  - [OTP Controller](./hw/ip/otp_ctrl/README.md)
+    - [Design Verification](./hw/ip/otp_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/otp_ctrl/data/otp_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/otp_ctrl/data/otp_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_otp_ctrl.h)
+    - [Checklist](./hw/ip/otp_ctrl/doc/checklist.md)
+  - [Pattern Generator](./hw/ip/pattgen/README.md)
+    - [Design Verification](./hw/ip/pattgen/dv/README.md)
+      - [Testplan](./hw/ip/pattgen/data/pattgen_testplan.hjson)
+    - [Interface and Registers](./hw/ip/pattgen/data/pattgen.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_pattgen.h)
+    - [Checklist](./hw/ip/pattgen/doc/checklist.md)
+  - [Pinmux](./hw/ip/pinmux/README.md)
+    - [Design Verification](./hw/ip/pinmux/doc/dv/README.md)
+      - [Testplan](./hw/ip/pinmux/data/pinmux_fpv_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip/pinmux/data/autogen/pinmux.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_pinmux.h)
+    - [Checklist](./hw/ip/pinmux/doc/checklist.md)
+  - [Pulse Width Modulator](./hw/ip/pwm/README.md)
+    - [Design Verification](./hw/ip/pwm/dv/README.md)
+      - [Testplan](./hw/ip/pwm/data/pwm_testplan.hjson)
+    - [Interface and Registers](./hw/ip/pwm/data/pwm.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_pwm.h)
+    - [Checklist](./hw/ip/pwm/doc/checklist.md)
+  - [Power Management](./hw/ip/pwrmgr/README.md)
+    - [Design Verification](./hw/ip/pwrmgr/dv/README.md)
+      - [Testplan](./hw/ip/pwrmgr/data/pwrmgr_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip/pwrmgr/data/autogen/pwrmgr.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_pwrmgr.h)
+    - [Checklist](./hw/ip/pwrmgr/doc/checklist.md)
+  - [ROM Control](./hw/ip/rom_ctrl/README.md)
+    - [Design Verification](./hw/ip/rom_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/rom_ctrl/data/rom_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/rom_ctrl/data/rom_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_rom_ctrl.h)
+    - [Checklist](./hw/ip/rom_ctrl/doc/checklist.md)
+  - [Reset Manager](./hw/ip/rstmgr/README.md)
+    - [Design Verification](./hw/ip/rstmgr/dv/README.md)
+      - [Testplan](./hw/ip/rstmgr/data/rstmgr_testplan.hjson)
+    - [Interface and Registers](./hw/top_earlgrey/ip/rstmgr/data/autogen/rstmgr.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_rstmgr.h)
+    - [Checklist](./hw/ip/rstmgr/doc/checklist.md)
+  - [RISC-V Debug Manager](./hw/ip/rv_dm/README.md)
+    - [Design Verification](./hw/ip/rv_dm/dv/README.md)
+      - [Testplan](./hw/ip/rv_dm/data/rv_dm_testplan.hjson)
+    - [Interface and Registers](./hw/ip/rv_dm/data/rv_dm.hjson)
+    - [Checklist](./hw/ip/rv_dm/doc/checklist.md)
+  - [SPI Device](./hw/ip/spi_device/README.md)
+    - [Design Verification](./hw/ip/spi_device/dv/README.md)
+      - [Testplan](./hw/ip/spi_device/data/spi_device_testplan.hjson)
+    - [Interface and Registers](./hw/ip/spi_device/data/spi_device.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_spi_device.h)
+    - [Checklist](./hw/ip/spi_device/doc/checklist.md)
+  - [SPI Host](./hw/ip/spi_host/README.md)
+    - [Design Verification](./hw/ip/spi_host/dv/README.md)
+      - [Testplan](./hw/ip/spi_host/data/spi_host_testplan.hjson)
+    - [Interface and Registers](./hw/ip/spi_host/data/spi_host.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_spi_host.h)
+    - [Checklist](./hw/ip/spi_host/doc/checklist.md)
+  - [SRAM Controller](./hw/ip/sram_ctrl/README.md)
+    - [Design Verification](./hw/ip/sram_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/sram_ctrl/data/sram_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/sram_ctrl/data/sram_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_sram_ctrl.h)
+    - [Checklist](./hw/ip/sram_ctrl/doc/checklist.md)
+  - [System Reset Controller](./hw/ip/sysrst_ctrl/README.md)
+    - [Design Verification](./hw/ip/sysrst_ctrl/dv/README.md)
+      - [Testplan](./hw/ip/sysrst_ctrl/data/sysrst_ctrl_testplan.hjson)
+    - [Interface and Registers](./hw/ip/sysrst_ctrl/data/sysrst_ctrl.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_sysrst_ctrl.h)
+    - [Checklist](./hw/ip/sysrst_ctrl/doc/checklist.md)
+  - [Timer](./hw/ip/rv_timer/README.md)
+    - [Design Verification](./hw/ip/rv_timer/dv/README.md)
+      - [Testplan](./hw/ip/rv_timer/data/rv_timer_testplan.hjson)
+    - [Interface and Registers](./hw/ip/rv_timer/data/rv_timer.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_rv_timer.h)
+    - [Checklist](./hw/ip/rv_timer/doc/checklist.md)
+  - [TL-UL Bus](./hw/ip/tlul/README.md)
+    - [Design Verification](./hw/ip/tlul/doc/dv/README.md)
+      - [Testplan](./hw/ip/tlul/data/tlul_testplan.hjson)
+      - [Protocol Checker](./hw/ip/tlul/doc/TlulProtocolChecker.md)
+  - [UART](./hw/ip/uart/README.md)
+    - [Design Verification](./hw/ip/uart/dv/README.md)
+      - [Testplan](./hw/ip/uart/data/uart_testplan.hjson)
+    - [Interface and Registers](./hw/ip/uart/data/uart.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_uart.h)
+    - [Checklist](./hw/ip/uart/doc/checklist.md)
+  - [USB 2.0](./hw/ip/usbdev/README.md)
+    - [Design Verification](./hw/ip/usbdev/dv/README.md)
+      - [Testplan](./hw/ip/usbdev/data/usbdev_testplan.hjson)
+    - [Suspending and Resuming](./hw/ip/usbdev/doc/wake_resume.md)
+    - [Interface and Registers](./hw/ip/usbdev/data/usbdev.hjson)
+    - [Device Interface Functions](./sw/device/lib/dif/dif_usbdev.h)
+    - [Checklist](./hw/ip/usbdev/doc/checklist.md)
+  - [lowRISC Hardware Primitives](./hw/ip/prim/README.md)
+    - [Two Input Clock](./hw/ip/prim/doc/prim_clock_gp_mux2.md)
+    - [Flash Wrapper](./hw/ip/prim/doc/prim_flash.md)
+    - [Keccak Permutation](./hw/ip/prim/doc/prim_keccak.md)
+    - [Linear Feedback Shift Register](./hw/ip/prim/doc/prim_lfsr.md)
+    - [Packer](./hw/ip/prim/doc/prim_packer.md)
+    - [Packer FIFO](./hw/ip/prim/doc/prim_packer_fifo.md)
+    - [Present Scrambler](./hw/ip/prim/doc/prim_present.md)
+    - [Prince Scrambler](./hw/ip/prim/doc/prim_prince.md)
+    - [SRAM Scrambler](./hw/ip/prim/doc/prim_ram_1p_scr.md)
+    - [Pseudo Random Number Generator](./hw/ip/prim/doc/prim_xoshiro256pp.md)
+
+- [Common SystemVerilog and UVM Components](./hw/dv/sv/README.md)
+  - [ALERT_ESC Agent](./hw/dv/sv/alert_esc_agent/README.md)
+  - [Bus Params Package](./hw/dv/sv/bus_params_pkg/README.md)
+  - [Comportable IP Testbench Architecture](./hw/dv/sv/cip_lib/README.md)
+  - [Common Interfaces](./hw/dv/sv/common_ifs/README.md)
+  - [CSR Utils](./hw/dv/sv/csr_utils/README.md)
+  - [CSRNG Agent](./hw/dv/sv/csrng_agent/README.md)
+  - [DV Library Classes](./hw/dv/sv/dv_lib/README.md)
+  - [DV Utils](./hw/dv/sv/dv_utils/README.md)
+  - [FLASH_PHY_PRIM Agent](./hw/dv/sv/flash_phy_prim_agent/README.md)
+  - [I2C Agent](./hw/dv/sv/i2c_agent/README.md)
+  - [JTAG Agent](./hw/dv/sv/jtag_agent/README.md)
+  - [JTAG DMI Agent](./hw/dv/sv/jtag_dmi_agent/README.md)
+  - [JTAG RISCV Agent](./hw/dv/sv/jtag_riscv_agent/README.md)
+  - [KEY_SIDELOAD Agent](./hw/dv/sv/key_sideload_agent/README.md)
+  - [KMAC_APP Agent](./hw/dv/sv/kmac_app_agent/README.md)
+  - [Memory Backdoor Scoreboard](./hw/dv/sv/mem_bkdr_scb/README.md)
+  - [Memory Backdoor Utility](./hw/dv/sv/mem_bkdr_util/README.md)
+  - [Memory Model](./hw/dv/sv/mem_model/README.md)
+  - [PATTGEN Agent](./hw/dv/sv/pattgen_agent/README.md)
+  - [PUSH_PULL Agent](./hw/dv/sv/push_pull_agent/README.md)
+  - [PWM Monitor](./hw/dv/sv/pwm_monitor/README.md)
+  - [RNG Agent](./hw/dv/sv/rng_agent/README.md)
+  - [Scoreboard](./hw/dv/sv/scoreboard/README.md)
+  - [Simulation SRAM](./hw/dv/sv/sim_sram/README.md)
+  - [SPI Agent](./hw/dv/sv/spi_agent/README.md)
+  - [String Utils](./hw/dv/sv/str_utils/README.md)
+  - [Test Vectors](./hw/dv/sv/test_vectors/README.md)
+  - [Tile Link Agent](./hw/dv/sv/tl_agent/README.md)
+  - [UART Agent](./hw/dv/sv/uart_agent/README.md)
+  - [USB20 Agent](./hw/dv/sv/usb20_agent/README.md)
+
+# Software
+- [Introduction](./sw/README.md)
+- [Build Software](./sw/doc/build_software.md)
+
+- [Device Software](./sw/device/README.md)
+  - [Device Libraries](./sw/device/lib/README.md)
+    - [DIF Library](./sw/device/lib/dif/README.md)
+      - [ADC Checklist](sw/device/lib/dif/dif_adc_ctrl.md)
+      - [AES Checklist](sw/device/lib/dif/dif_aes.md)
+      - [Alert Handler Checklist](sw/device/lib/dif/dif_alert_handler.md)
+      - [Always-On Timer Checklist](sw/device/lib/dif/dif_aon_timer.md)
+      - [Clock Manager Checklist](sw/device/lib/dif/dif_clkmgr.md)
+      - [CSRNG Checklist](sw/device/lib/dif/dif_csrng.md)
+      - [EDN Checklist](sw/device/lib/dif/dif_edn.md)
+      - [Entropy Source Checklist](sw/device/lib/dif/dif_entropy_src.md)
+      - [Flash Controller Checklist](sw/device/lib/dif/dif_flash_ctrl.md)
+      - [GPIO Checklist](sw/device/lib/dif/dif_gpio.md)
+      - [HMAC Checklist](sw/device/lib/dif/dif_hmac.md)
+      - [I2C Checklist](sw/device/lib/dif/dif_i2c.md)
+      - [Key Manager Checklist](sw/device/lib/dif/dif_keymgr.md)
+      - [KMAC Checklist](sw/device/lib/dif/dif_kmac.md)
+      - [Lifecycle Checklist](sw/device/lib/dif/dif_lc_ctrl.md)
+      - [OTBN Checklist](sw/device/lib/dif/dif_otbn.md)
+      - [OTP Controller Checklist](sw/device/lib/dif/dif_otp_ctrl.md)
+      - [Pattern Generator Checklist](sw/device/lib/dif/dif_pattgen.md)
+      - [Pin Multiplexer Checklist](sw/device/lib/dif/dif_pinmux.md)
+      - [PWM Checklist](sw/device/lib/dif/dif_pwm.md)
+      - [Power Manager Checklist](sw/device/lib/dif/dif_pwrmgr.md)
+      - [ROM Checklist](sw/device/lib/dif/dif_rom_ctrl.md)
+      - [Reset Manager Checklist](sw/device/lib/dif/dif_rstmgr.md)
+      - [RV Core Ibex Checklist](sw/device/lib/dif/dif_rv_core_ibex.md)
+      - [PLIC Checklist](sw/device/lib/dif/dif_rv_plic.md)
+      - [RV Timer Checklist](sw/device/lib/dif/dif_rv_timer.md)
+      - [Sensor Controller Checklist](sw/device/lib/dif/dif_sensor_ctrl.md)
+      - [SPI Device Checklist](sw/device/lib/dif/dif_spi_device.md)
+      - [SPI Host Checklist](sw/device/lib/dif/dif_spi_host.md)
+      - [SRAM Controller Checklist](sw/device/lib/dif/dif_sram_ctrl.md)
+      - [System Reset Controller Checklist](sw/device/lib/dif/dif_sysrst_ctrl.md)
+      - [UART Checklist](sw/device/lib/dif/dif_uart.md)
+      - [USB Checklist](sw/device/lib/dif/dif_usbdev.md)
+    - [Top-Level Test Libraries](./sw/device/lib/testing/README.md)
+      - [On-Device Test Framework](./sw/device/lib/testing/test_framework/README.md)
+    - [OpenTitan Standard Library](./sw/device/lib/base/README.md)
+        - [Freestanding C Headers](./sw/device/lib/base/freestanding/README.md)
+  - [Silicon Creator Software](./sw/device/silicon_creator/README.md)
+    - [Manufacturing Firmware](./sw/device/silicon_creator/manuf/README.md)
+    - [ROM](./sw/device/silicon_creator/rom/README.md)
+      - [Memory Protection](./sw/device/silicon_creator/rom/doc/memory_protection.md)
+      - [Signing Keys](./sw/device/silicon_creator/rom/keys/README.md)
+    - [Manifest Format](./sw/device/silicon_creator/rom_ext/doc/manifest.md)
+  - [Top-Level Tests](./sw/device/tests/README.md)
+    - [Manufacturer Test Hooks](./sw/device/tests/closed_source/README.md)
+    - [Crypto Library Tests](./sw/device/tests/crypto/README.md)
+
+- [Host Software](./sw/host/README.md)
+
+
+# Tooling
+
+- [Tools Overview](./util/README.md)
+- [Design-Related Tooling](./util/design/README.md)
+
+- [dvsim: Testplanner](./util/dvsim/README.md)
+- [fpvgen: Initial FPV Testbench Generation](./util/fpvgen/README.md)
+- [reggen & regtool: Register Generator](./util/reggen/README.md)
+  - [Setup and use of regtool](./util/reggen/doc/setup_and_use.md)
+
+- [ralgen: FuseSoC UVM RAL Generator](./hw/dv/tools/ralgen/README.md)
+- [uvmdvgen: Initial Testbench Auto-generation](./util/uvmdvgen/README.md)
+- [tlgen: Crossbar Generation](./util/tlgen/README.md)
+- [ipgen: Generate IP Blocks from IP Templates](./util/ipgen/README.md)
+- [topgen: Top Generator](./util/topgen/README.md)
+- [vendor: Vendoring In Tool](./util/doc/vendor.md)
+- [i2csvg: Generate SVGs of I2C Commands](./util/i2csvg/README.md)
+
+
+# Contributing
+
+- [Contributing](./doc/contributing/README.md)
+  - [Detailed Contribution Guide](./doc/contributing/detailed_contribution_guide/README.md)
+  - [Directory Structure](./doc/contributing/directory_structure.md)
+  - [Continueous Intergration](./doc/contributing/ci/README.md)
+  - [Top-Level Design and Targets](./doc/contributing/system_list.md)
+  - [GitHub Notes](./doc/contributing/github_notes.md)
+  - [Bazel Notes](./doc/contributing/bazel_notes.md)
+  - [Using the Container](./util/container/README.md)
+
+- [Contributing to Hardware]()
+  - [Comportability](./doc/contributing/hw/comportability/README.md)
+  - [Hardware Design](./doc/contributing/hw/design.md)
+  - [Design Methodology](./doc/contributing/hw/methodology.md)
+  - [Vendoring in Hardware](./doc/contributing/hw/vendor.md)
+  - [Linting](./hw/lint/README.md)
+  - [Synthesis Flow](./hw/syn/README.md)
+
+- [Contributing to Verification]()
+  - [Verification Methodology](./doc/contributing/dv/methodology/README.md)
+  - [Security Countermeasure Verification Framework](./doc/contributing/dv/sec_cm_dv_framework/README.md)
+  - [Assertions](./hw/formal/README.md)
+
+- [Contributing to Software]()
+  - [Device Interface Functions](./doc/contributing/sw/device_interface_functions.md)
+  - [Writing and Building Software for OTBN](./doc/contributing/sw/otbn_sw.md)
+
+- [Style Guides](./doc/contributing/style_guides/README.md)
+  - [HJSON](./doc/contributing/style_guides/hjson_usage_style.md)
+  - [Python](./doc/contributing/style_guides/python_coding_style.md)
+  - [C & C++](./doc/contributing/style_guides/c_cpp_coding_style.md)
+  - [Markdown](./doc/contributing/style_guides/markdown_usage_style.md)
+  - [RISC-V Assembly](./doc/contributing/style_guides/asm_coding_style.md)
+  - [OTBN Assembly](./doc/contributing/style_guides/otbn_style_guide.md)
+  - [Guidance for Volatile](./doc/contributing/style_guides/guidance_for_volatile.md)
+
+- [Developing on an FPGA]()
+  - [Get a Board](./doc/contributing/fpga/get_a_board.md)
+  - [FPGA Reference Manual](./doc/contributing/fpga/ref_manual_fpga.md)
+
+# Project Governance
+
+- [Introduction](./doc/project_governance/README.md)
+- [Committers](./doc/project_governance/committers.md)
+- [RFC Process](./doc/project_governance/rfc_process.md)
+- [Generalized Priority Definitions](./doc/project_governance/priority_definitions.md)
+- [OpenTitan Technical Committee](./doc/project_governance/technical_committee.md)
+- [Hardware Development Stages](./doc/project_governance/development_stages.md)
+- [Signoff Checklist](./doc/project_governance/checklist/README.md)
+
+# Security
+
+- [Security](./doc/security/README.md)
+- [Implementation Guidelines](./doc/security/implementation_guidelines/README.md)
+  - [Secure Hardware Design Guidelines](./doc/security/implementation_guidelines/hardware/README.md)
+
+- [Logical Security Model](./doc/security/logical_security_model/README.md)
+- [Security Model Specification](./doc/security/specs/README.md)
+  - [Device Attestation](./doc/security/specs/attestation/README.md)
+  - [Device Life Cycle](./doc/security/specs/device_life_cycle/README.md)
+  - [Device Provisioning](./doc/security/specs/device_provisioning/README.md)
+  - [Firmware Update](./doc/security/specs/firmware_update/README.md)
+  - [Identities and Root Keys](./doc/security/specs/identities_and_root_keys/README.md)
+  - [Ownership Transfer](./doc/security/specs/ownership_transfer/README.md)
+  - [Secure Boot](./doc/security/specs/secure_boot/README.md)
+
+- [Lightweight Threat Model](./doc/security/threat_model/README.md)
+
+# Use Cases
+
+- [Use Cases](./doc/use_cases/README.md)
+- [Platform Integrity Module](./doc/use_cases/platform_integrity_module/README.md)
+- [Trusted Platform Module](./doc/use_cases/tpm/README.md)
+- [Universal 2nd-Factor Security Key](./doc/use_cases/u2f/README.md)
+
+
+# Rust for C Developers
+
+- [Rust for Embedded C Programmers](./doc/rust_for_c_devs.md)
diff --git a/WORKSPACE b/WORKSPACE
index 89fe2ae..9eb984c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -7,28 +7,33 @@
 
 workspace(name = "lowrisc_opentitan")
 
-# Bazel Embedded; Bazel platform configuration for embedded environments.
-load("//third_party/bazel_embedded:repos.bzl", "bazel_embedded_repos")
-bazel_embedded_repos()
-load("//third_party/bazel_embedded:deps.bzl", "bazel_embedded_deps")
-bazel_embedded_deps()
+# CRT is the Compiler Repository Toolkit.  It contains the configuration for
+# the windows compiler.
+load("//third_party/crt:repos.bzl", "crt_repos")
+crt_repos()
+load("@crt//:repos.bzl", "crt_repos")
+crt_repos()
+load("@crt//:deps.bzl", "crt_deps")
+crt_deps()
+load("@crt//config:registration.bzl", "crt_register_toolchains")
+crt_register_toolchains(riscv32 = True)
 
-# The lowRISC LLVM Toolchain
-load("//third_party/lowrisc_toolchain:repos.bzl", "lowrisc_toolchain_repos")
-lowrisc_toolchain_repos()
-load("//third_party/lowrisc_toolchain:deps.bzl", "lowrisc_toolchain_deps")
-lowrisc_toolchain_deps()
+# Tools for release automation
+load("//third_party/github:repos.bzl", "github_tools_repos")
+github_tools_repos()
 
-# C/C++ Library Dependencies
-load("//third_party/cc:repos.bzl", "cc_repos")
-cc_repos()
-
-# Go Toolchain
+# Go Toolchain (needed by the Buildifier linter)
 load("//third_party/go:repos.bzl", "go_repos")
 go_repos()
 load("//third_party/go:deps.bzl", "go_deps")
 go_deps()
 
+# Various linters
+load("//third_party/lint:repos.bzl", "lint_repos")
+lint_repos()
+load("//third_party/lint:deps.bzl", "lint_deps")
+lint_deps()
+
 # Python Toolchain + PIP Dependencies
 load("//third_party/python:repos.bzl", "python_repos")
 python_repos()
@@ -37,25 +42,28 @@
 load("//third_party/python:pip.bzl", "pip_deps")
 pip_deps()
 
+# Google/Bazel dependencies.  This needs to be after Python initialization
+# so that our preferred python configuration takes precedence.
+load("//third_party/google:repos.bzl", "google_repos")
+google_repos()
+load("//third_party/google:deps.bzl", "google_deps")
+google_deps()
+
 # Rust Toolchain + crates.io Dependencies
 load("//third_party/rust:repos.bzl", "rust_repos")
 rust_repos()
 load("//third_party/rust:deps.bzl", "rust_deps")
 rust_deps()
 
-# Cargo Raze dependencies
-load("//third_party/cargo_raze:repos.bzl", "raze_repos")
-raze_repos()
-load("//third_party/cargo_raze:deps.bzl", "raze_deps")
-raze_deps()
-# The raze instructions would have us call `cargo_raze_transitive_deps`, but that
-# wants to re-instantiate rules_rust and mess up our rust configuration.
-# Instead, we perform the single other action that transitive_deps would perform:
-# load and instantiate `rules_foreign_cc_dependencies`.
-#load("@cargo_raze//:transitive_deps.bzl", "cargo_raze_transitive_deps")
-#cargo_raze_transitive_deps()
-load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies")
-rules_foreign_cc_dependencies()
+load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies")
+crate_universe_dependencies(bootstrap = True)
+
+load("//third_party/rust/crates:crates.bzl", "crate_repositories")
+crate_repositories()
+
+# OpenOCD
+load("//third_party/openocd:repos.bzl", "openocd_repos")
+openocd_repos()
 
 # Protobuf Toolchain
 load("//third_party/protobuf:repos.bzl", "protobuf_repos")
@@ -63,20 +71,26 @@
 load("//third_party/protobuf:deps.bzl", "protobuf_deps")
 protobuf_deps()
 
-# Various linters
-load("//third_party/lint:repos.bzl", "lint_repos")
-lint_repos()
-load("//third_party/lint:deps.bzl", "lint_deps")
-lint_deps()
-
 # FreeRTOS; used by the OTTF
 load("//third_party/freertos:repos.bzl", "freertos_repos")
 freertos_repos()
 
+# LLVM Compiler Runtime for Profiling
+load("//third_party/llvm_compiler_rt:repos.bzl", "llvm_compiler_rt_repos")
+llvm_compiler_rt_repos()
+
 # RISC-V Compliance Tests
 load("//third_party/riscv-compliance:repos.bzl", "riscv_compliance_repos")
 riscv_compliance_repos()
 
+# CoreMark benchmark
+load("//third_party/coremark:repos.bzl", "coremark_repos")
+coremark_repos()
+
+# The standard Keccak algorithms
+load("//third_party/xkcp:repos.bzl", "xkcp_repos")
+xkcp_repos()
+
 # Bitstreams from https://storage.googleapis.com/opentitan-bitstreams/
 load("//rules:bitstreams.bzl", "bitstreams_repo")
 bitstreams_repo(name = "bitstreams")
@@ -95,3 +109,11 @@
 # The nonhermetic_repo imports environment variables needed to run vivado.
 load("//rules:nonhermetic.bzl", "nonhermetic_repo")
 nonhermetic_repo(name = "nonhermetic")
+
+# Binary firmware image for HyperDebug
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")
+http_file(
+    name = "hyperdebug_firmware",
+    urls = ["https://storage.googleapis.com/aoa-recovery-test-images/hyperdebug_v2.0.20491-956ccf530.bin"],
+    sha256 = "e9c93d2935b9b6a571b547f20fe6177c48a909535d87533b7a0c64fb049bd643",
+)
diff --git a/_index.md b/_index.md
deleted file mode 100644
index 5d7fb10..0000000
--- a/_index.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: "Introduction to OpenTitan"
----
-
-[OpenTitan](https://opentitan.org) is an open source silicon Root of Trust (RoT) project.
-OpenTitan will make the silicon RoT design and implementation more transparent, trustworthy, and secure for enterprises, platform providers, and chip manufacturers.
-OpenTitan is administered by lowRISC CIC as a collaborative [project]({{< relref "doc/project" >}}) to produce high quality, open IP for instantiation as a full-featured product.
-This repository exists to enable collaboration across partners participating in the OpenTitan project.
-
-## Getting Started
-
-To get started with OpenTitan, see the [Getting Started]({{< relref "doc/getting_started" >}}) page.
-For additional resources when working with OpenTitan, see the [list of user guides]({{< relref "doc/ug" >}}).
-For details on coding styles or how to use our project-specific tooling, see the [reference manuals]({{< relref "doc/rm" >}}).
-Lastly, the [Hardware Dashboard page]({{< relref "hw" >}}) contains technical documentation on the SoC, the Ibex processor core, and the individual IP blocks.
-For questions about how the project is organized, see the [project]({{< relref "doc/project" >}}) landing spot for more information.
-
-## Repository Structure
-
-The underlying
-[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.
-
-Unless otherwise noted, everything in the repository is covered by the Apache License, Version 2.0. See the [LICENSE](https://github.com/lowRISC/opentitan/blob/master/LICENSE) file and [repository README](https://github.com/lowRISC/opentitan/blob/master/README.md) for more information on licensing and see the user guides below for more information on development methodology.
-
-## Documentation Sections
-
-{{% sectionContent type="section" depth="1" %}}
diff --git a/apt-requirements.txt b/apt-requirements.txt
index f1f5841..e28bf69 100644
--- a/apt-requirements.txt
+++ b/apt-requirements.txt
@@ -30,10 +30,11 @@
 libftdi1-2
 libftdi1-dev
 # A requirement of the prebuilt clang toolchain.
-libncurses5
+libncursesw5
 libssl-dev
 libudev-dev
 libusb-1.0-0
+lld
 lsb-release
 make
 ninja-build
@@ -44,6 +45,7 @@
 python3-setuptools
 python3-urllib3
 python3-wheel
+shellcheck
 srecord
 tree
 xsltproc
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index fda1df7..e180b48 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -7,11 +7,10 @@
 
 variables:
   #
-  # If updating VERILATOR_VERSION, OPENOCD_VERSION, TOOLCHAIN_VERSION or RUST_VERSION
-  # update the definitions in util/container/Dockerfile as well.
+  # If updating VERILATOR_VERSION, TOOLCHAIN_VERSION or RUST_VERSION, update the
+  # definitions in util/container/Dockerfile as well.
   #
   VERILATOR_VERSION: 4.210
-  OPENOCD_VERSION: 0.11.0
   TOOLCHAIN_PATH: /opt/buildcache/riscv
   VERIBLE_VERSION: v0.0-2135-gb534c1fe
   RUST_VERSION: 1.60.0
@@ -35,12 +34,27 @@
     - "*"
 
 jobs:
+- job: checkout
+  displayName: Checkout repository
+  pool:
+    vmImage: ubuntu-20.04
+  steps:
+  - checkout: self
+    path: opentitan-repo
+  - bash: |
+      tar -C $(Pipeline.Workspace)/opentitan-repo -czf $(Pipeline.Workspace)/opentitan-repo.tar.gz .
+    displayName: Pack up repository
+  - publish: $(Pipeline.Workspace)/opentitan-repo.tar.gz
+    artifact: opentitan-repo
+    displayName: Upload repository
 - job: lint
   displayName: Quality (quick lint)
   # Run code quality checks (quick lint)
+  dependsOn: checkout
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   steps:
+  - template: ci/checkout-template.yml
   - bash: |
       sudo apt-get remove -y clang-6.0 libclang-common-6.0-dev libclang1-6.0 libllvm6.0
     displayName: Uninstall Clang
@@ -72,14 +86,10 @@
     condition: eq(variables['Build.Reason'], 'PullRequest')
     displayName: flake8 (Python lint)
     # Run Python lint (flake8)
-    continueOnError: true
   - bash: ci/scripts/mypy.sh
     condition: eq(variables['Build.Reason'], 'PullRequest')
     displayName: mypy (Python lint)
     # Run Python lint (mypy)
-  - bash: ci/scripts/check-generated.sh
-    displayName: Check Generated
-    # Ensure all generated files are clean and up-to-date
   - bash: ci/scripts/clang-format.sh $SYSTEM_PULLREQUEST_TARGETBRANCH
     condition: eq(variables['Build.Reason'], 'PullRequest')
     displayName: clang-format (C/C++ lint)
@@ -87,6 +97,28 @@
   - bash: ci/scripts/rust-format.sh $SYSTEM_PULLREQUEST_TARGETBRANCH
     condition: eq(variables['Build.Reason'], 'PullRequest')
     displayName: rustfmt
+  - bash: |
+      set -e
+      # Azure-specific installation:
+      # The latest version of shellcheck available to ubuntu-18.04 is 0.4.6-1.
+      # We need a newer version that provides the --warning flag.
+      SC_VERSION="v0.8.0"
+      SHELLCHECK_FILE="shellcheck-$SC_VERSION.linux.x86_64.tar.xz"
+      SHELLCHECK_URL="https://github.com/koalaman/shellcheck/releases/download/$SC_VERSION/$SHELLCHECK_FILE"
+      SC_SHA256=ab6ee1b178f014d1b86d1e24da20d1139656c8b0ed34d2867fbb834dad02bf0a
+      echo "Installing Shellcheck $SC_VERSION"
+      curl -L -o "$SHELLCHECK_FILE" "$SHELLCHECK_URL" --silent --show-error
+      echo "${SC_SHA256}  ${SHELLCHECK_FILE}" | sha256sum -c
+      tar -xJf $SHELLCHECK_FILE
+      sudo cp "shellcheck-$SC_VERSION/shellcheck" /usr/bin
+      # Run shellcheck
+      echo "Checking shell scripts..."
+      ci/scripts/run-shellcheck.sh || {
+        echo -n "##vso[task.logissue type=error]"
+        echo "Shellcheck failed. Run ci/scripts/run-shellcheck.sh to see errors."
+        exit 1
+      }
+    displayName: shellcheck
   - bash: ci/scripts/include-guard.sh $SYSTEM_PULLREQUEST_TARGETBRANCH
     condition: eq(variables['Build.Reason'], 'PullRequest')
     displayName: Header guards
@@ -96,42 +128,52 @@
     displayName: Check trailing whitespace
   - bash: ci/scripts/build-docs.sh
     displayName: Render documentation
-  - bash: ci/scripts/build-site.sh
-    displayName: Render landing site
   - bash: ci/scripts/get-build-type.sh "$SYSTEM_PULLREQUEST_TARGETBRANCH" "$(Build.Reason)"
     displayName: Type of change
     # Check what kinds of changes the PR contains
     name: DetermineBuildType
+  - bash: ci/scripts/check-no-bazelrc-site.sh
+    condition: eq(variables['Build.Reason'], 'PullRequest')
+    displayName: Confirm no .bazelrc-site files
 
-- job: download_bazel_dependencies
-  displayName: Prefetch Bazel deps
-  # Download Bazel fetched dependencies
+- job: airgapped_bazel_build
+  displayName: Test an airgapped Bazel build
+  timeoutInMinutes: 90
+  dependsOn: checkout
+  condition: eq(variables['Build.Reason'], 'PullRequest')
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   steps:
-  - bash: |
-      set -x -e
-      util/prep-bazel-airgapped-build.sh || {
-        echo -n "##vso[task.logissue type=warning]Failed to prefetch Bazel dependencies";
-        exit 1;
-      }
-      rm -rf bazel-airgapped/bitstreams-cache
-      tar -cf bazel-airgapped.tar.gz bazel-airgapped
-    displayName: Pre-fetch and package Bazel dependencies
-    continueOnError: True # This step is flaky on the ubuntu-18.04 image
-  - publish: $(System.DefaultWorkingDirectory)/bazel-airgapped.tar.gz
-    artifact: bazel-dependencies
-    condition: eq(variables['Agent.JobStatus'], 'Succeeded')
+  - template: ci/checkout-template.yml
+  - template: ci/install-package-dependencies.yml
+  - bash: ci/scripts/test-airgapped-build.sh
 
 - job: slow_lints
   displayName: Quality (in-depth lint)
   # Run code quality checks (in-depth lint)
   dependsOn: lint
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
-  - bash: ci/bazelisk.sh run buildifier_check
+  # Bazel test suites are a common cause of problematic tags. Check test suites
+  # before checking for other tag issues.
+  - bash:  ci/scripts/check_bazel_test_suites.py
+    displayName: Check Bazel test suites (Experimental)
+    continueOnError: True
+  - bash: ci/scripts/check-bazel-tags.sh
+    displayName: Check Bazel Tags (Experimental)
+    continueOnError: True
+  - bash: ci/scripts/check-bazel-banned-rules.sh
+    displayName: Check for banned rules
+  - bash:  ci/scripts/check_bazel_target_names.py
+    displayName: Check Bazel target names (Experimental)
+    continueOnError: True
+  - bash: ci/scripts/check-generated.sh
+    displayName: Check Generated
+    # Ensure all generated files are clean and up-to-date
+  - bash: ci/bazelisk.sh test //quality:buildifier_check --test_output=streamed
     displayName: Buildifier (Bazel lint)
   - bash: ci/scripts/check-vendoring.sh
     displayName: Vendored directories
@@ -148,15 +190,15 @@
 - job: sw_build
   displayName: Earl Grey SW Build & Test
   # Build and test Software for Earl Grey toplevel design
-  timeoutInMinutes: 120
+  timeoutInMinutes: 180
   dependsOn: lint
   condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
-  pool:
-    vmImage: ubuntu-18.04
+  pool: ci-public-general
   variables:
     - name: bazelCacheGcpKeyPath
       value: ''
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - task: DownloadSecureFile@1
     condition: eq(variables['Build.SourceBranchName'], 'master')
@@ -169,69 +211,62 @@
     # Set the remote cache GCP key path
   - bash: |
       set -x -e
-      # This command builds all software and runs all unit tests that run on
-      # the host.
+      # This command builds all software and runs all unit tests that run on the
+      # host, with a few exceptions:
+      # * It excludes //quality because that's the purview of `slow_lints`.
+      # * It excludes the tests from //third_party/riscv-compliance because
+      #   they're already covered by `execute_fpga_tests_cw310`.
+      # * It excludes //hw:all to avoid building Verilator, which is pulled in
+      #   because //... effectively asks to build //hw:verilator_real and other
+      #   targets in //hw:all that depend on it. Note that this is only a
+      #   shallow exclusion; tests deeper under //hw will still be found.
+      # * It excludes targets that depend on bitstream_splice rules, since the
+      #   environment does not have access to Vivado.
       export GCP_BAZEL_CACHE_KEY=$(bazelCacheGcpKeyPath)
+      TARGET_PATTERN_FILE=$(mktemp)
+      echo //... > "${TARGET_PATTERN_FILE}"
+      echo -//quality/... >> "${TARGET_PATTERN_FILE}"
+      echo -//third_party/riscv-compliance/... >> "${TARGET_PATTERN_FILE}"
+      echo -//hw:all >> "${TARGET_PATTERN_FILE}"
+      ./bazelisk.sh cquery \
+        --noinclude_aspects \
+        --output=starlark \
+        --starlark:expr='"-{}".format(target.label)' \
+        --define DISABLE_VERILATOR_BUILD=true \
+        -- "rdeps(//..., kind(bitstream_splice, //...))" \
+        >> "${TARGET_PATTERN_FILE}"
       ci/bazelisk.sh test \
-      --build_tests_only=false \
-      --define DISABLE_VERILATOR_BUILD=true \
-      --test_tag_filters=-broken,-cw310,-verilator,-dv \
-      //sw/...
+        --build_tests_only=false \
+        --test_output=errors \
+        --define DISABLE_VERILATOR_BUILD=true \
+        --test_tag_filters=-broken,-cw310,-verilator,-dv \
+        --target_pattern_file="${TARGET_PATTERN_FILE}"
     displayName: Build & test SW
   - bash: |
       set -x -e
       . util/build_consts.sh
-      # copy the mask rom to a specific location
-      MASK_ROM_TARGET=${BIN_DIR}/sw/device/silicon_creator/mask_rom
-      mkdir -p ${MASK_ROM_TARGET}
-      MASK_ROM=$(ci/scripts/target-location.sh //sw/device/silicon_creator/mask_rom:mask_rom_fpga_cw310_scr_vmem)
-      cp $MASK_ROM $MASK_ROM_TARGET/mask_rom_fpga_cw310.scr.39.vmem
-      MASK_ROM_DIR=$(dirname $MASK_ROM)
-      cp $MASK_ROM_DIR/mask_rom_fpga_cw310.elf $MASK_ROM_TARGET/mask_rom_fpga_cw310.elf
-      cp $MASK_ROM_DIR/mask_rom_fpga_cw310.bin $MASK_ROM_TARGET/mask_rom_fpga_cw310.bin
-  - template: ci/upload-artifacts-template.yml
-    parameters:
-      includePatterns:
-        - "/sw/***"
-
-- job: cw310_sw_build
-  displayName: CW310 Bitstream Software
-  # Build Earl Grey Software required for CW310 FPGA synthesis
-  dependsOn: lint
-  condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
-  pool:
-    vmImage: ubuntu-18.04
-  steps:
-  - template: ci/install-package-dependencies.yml
-  - bash: |
-      . util/build_consts.sh
-      ci/bazelisk.sh build \
-        //hw/ip/otp_ctrl/data:img_rma \
-        //sw/device/lib/testing/test_rom:test_rom_fpga_cw310_scr_vmem \
-        //sw/device/lib/testing/test_rom:test_rom_fpga_cw310_bin
-      mkdir -p $BIN_DIR/sw/device/otp_img
-      mkdir -p $BIN_DIR/sw/device/lib/testing/test_rom
-      cp $(ci/scripts/target-location.sh //hw/ip/otp_ctrl/data:img_rma) $BIN_DIR/sw/device/otp_img/otp_img_fpga_cw310.vmem
-      TEST_ROM=$(ci/scripts/target-location.sh //sw/device/lib/testing/test_rom:test_rom_fpga_cw310_scr_vmem)
-      TEST_ROM_DIR=$(dirname $TEST_ROM)
-      TEST_ROM_TARGET=$BIN_DIR/sw/device/lib/testing/test_rom
-      cp $TEST_ROM $TEST_ROM_TARGET/test_rom_fpga_cw310.scr.39.vmem
-      cp $TEST_ROM_DIR/*fpga_cw310.elf $TEST_ROM_TARGET/test_rom_fpga_cw310.elf
-      cp $TEST_ROM_DIR/*fpga_cw310.bin $TEST_ROM_TARGET/test_rom_fpga_cw310.bin
-    displayName: Build embedded targets
+      # copy the rom to a specific location
+      ROM_TARGET=${BIN_DIR}/sw/device/silicon_creator/rom
+      mkdir -p ${ROM_TARGET}
+      ROM=$(ci/scripts/target-location.sh //sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310_scr_vmem)
+      cp $ROM $ROM_TARGET/rom_with_fake_keys_fpga_cw310.scr.39.vmem
+      ROM_DIR=$(dirname $ROM)
+      cp $ROM_DIR/rom_with_fake_keys_fpga_cw310.elf $ROM_TARGET/rom_with_fake_keys_fpga_cw310.elf
+      cp $ROM_DIR/rom_with_fake_keys_fpga_cw310.bin $ROM_TARGET/rom_with_fake_keys_fpga_cw310.bin
   - template: ci/upload-artifacts-template.yml
     parameters:
       includePatterns:
         - "/sw/***"
 
 - job: chip_englishbreakfast_verilator
-  displayName: Verilated English Breakfast
+  displayName: Verilated English Breakfast (Build)
   # Build Verilator simulation of the English Breakfast toplevel design
   dependsOn: lint
   condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - bash: |
       python3 --version
@@ -250,9 +285,10 @@
   displayName: Fast Verilated Earl Grey tests
   # Build and run fast tests on sim_verilator
   pool: ci-public
-  timeoutInMinutes: 120
+  timeoutInMinutes: 180
   dependsOn: lint
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - task: DownloadSecureFile@1
     condition: eq(variables['Build.SourceBranchName'], 'master')
@@ -272,7 +308,7 @@
   - bash: |
       . util/build_consts.sh
       mkdir -p "$BIN_DIR/hw/top_earlgrey/"
-      cp $(ci/scripts/target-location.sh //hw:verilator)/sim-verilator/Vchip_sim_tb \
+      cp $(ci/scripts/target-location.sh //hw:verilator) \
         "$BIN_DIR/hw/top_earlgrey/Vchip_earlgrey_verilator"
     displayName: Copy //hw:verilator to $BIN_DIR
   - template: ci/upload-artifacts-template.yml
@@ -287,12 +323,13 @@
 # building more than one top-level design with different parametrizations.
 # Work towards this goal is tracked in issue #4669.
 - job: build_and_execute_verilated_tests_englishbreakfast
-  displayName: Verilated English Breakfast
+  displayName: Verilated English Breakfast (Execute)
   # Build and execute tests on the Verilated English Breakfast toplevel design with Bazel
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   dependsOn: chip_englishbreakfast_verilator
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - template: ci/download-artifacts-template.yml
     parameters:
@@ -305,22 +342,23 @@
   - bash: |
       . util/build_consts.sh
       mkdir -p "$BIN_DIR/sw/device/lib/testing/test_rom"
-      cp $(ci/scripts/target-location.sh //sw/device/lib/testing/test_rom:test_rom_fpga_nexysvideo_vmem) \
+      cp $(ci/scripts/target-location.sh //sw/device/lib/testing/test_rom:test_rom_fpga_cw305_vmem) \
         "$BIN_DIR/sw/device/lib/testing/test_rom"
-    displayName: Copy test_rom_fpga_nexysvideo_vmem to $BIN_DIR
+    displayName: Copy test_rom_fpga_cw305_vmem to $BIN_DIR
   - template: ci/upload-artifacts-template.yml
     parameters:
       includePatterns:
-        - "/sw/device/lib/testing/test_rom/test_rom_fpga_nexysvideo.32.vmem"
+        - "/sw/device/lib/testing/test_rom/test_rom_fpga_cw305.32.vmem"
 
 - job: otbn_standalone_tests
   displayName: Run OTBN Smoke Test
   dependsOn: lint
   condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   timeoutInMinutes: 10
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - bash: |
       set -x
@@ -350,30 +388,49 @@
   # Build CW310 variant of the Earl Grey toplevel design using Vivado
   dependsOn:
     - lint
-    # The bootrom is built into the FPGA image at synthesis time.
-    - cw310_sw_build
   condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyDvChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
-  pool: ci-public
-  timeoutInMinutes: 180
+  pool: ci-public-eda
+  timeoutInMinutes: 240
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
-  - template: ci/download-artifacts-template.yml
-    parameters:
-      downloadPartialBuildBinFrom:
-        - cw310_sw_build
   - bash: |
-      set -e
+      ci/scripts/get-bitstream-strategy.sh "@bitstreams//:chip_earlgrey_cw310_bitstream" ':!/sw/' ':!/*testplan.hjson' ':!/site/' ':!/doc/' ':!/COMMITTERS' ':!/CLA' ':!/*.md' ':!/hw/**/dv/*'
+    displayName: Configure bitstream strategy
+  - bash: |
+      set -ex
       module load "xilinx/vivado/$(VIVADO_VERSION)"
-      ci/scripts/build-bitstream-vivado.sh top_earlgrey cw310
-    displayName: Build bitstream with Vivado
+      ci/scripts/prepare-cached-bitstream.sh
+    condition: eq(variables.bitstreamStrategy, 'cached')
+    displayName: Splice cached bitstream
+  - bash: |
+      set -ex
+      trap 'get_logs' EXIT
+      get_logs() {
+        SUB_PATH="hw/top_earlgrey"
+        mkdir -p "$OBJ_DIR/$SUB_PATH" "$BIN_DIR/$SUB_PATH"
+        cp -rLvt "$OBJ_DIR/$SUB_PATH/" \
+          $($REPO_TOP/bazelisk.sh outquery-all //hw/bitstream/vivado:fpga_cw310)
+        cp -rLvt "$BIN_DIR/$SUB_PATH/" \
+          $($REPO_TOP/bazelisk.sh outquery-all //hw/bitstream/vivado:standard)
+        # Rename bitstreams to be compatible with subsequent steps
+        # TODO(#13807): replace this after choosing a naming scheme
+        mv "$BIN_DIR/$SUB_PATH/lowrisc_systems_chip_earlgrey_cw310_0.1.bit" "$BIN_DIR/$SUB_PATH/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig"
+      }
+
+      . util/build_consts.sh
+      module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/bazelisk.sh build //hw/bitstream/vivado:standard
+    condition: ne(variables.bitstreamStrategy, 'cached')
+    displayName: Build and splice bitstream with Vivado
   - bash: |
       . util/build_consts.sh
-      echo Synthesis log
-      cat $OBJ_DIR/hw/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.runs/synth_1/runme.log || true
+      echo "Synthesis log"
+      cat $OBJ_DIR/hw/top_earlgrey/build.fpga_cw310/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.runs/synth_1/runme.log || true
 
-      echo Implementation log
-      cat $OBJ_DIR/hw/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.runs/impl_1/runme.log || true
-    condition: always()
+      echo "Implementation log"
+      cat $OBJ_DIR/hw/top_earlgrey/build.fpga_cw310/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.runs/impl_1/runme.log || true
+    condition: ne(variables.bitstreamStrategy, 'cached')
     displayName: Display synthesis & implementation logs
   - template: ci/upload-artifacts-template.yml
     parameters:
@@ -384,47 +441,48 @@
     displayName: Upload artifacts for CW310
     condition: failed()
 
-- job: chip_earlgrey_cw310_splice_mask_rom
-  displayName: Splice Mask ROM into CW310 bitstream
+- job: chip_earlgrey_cw310_hyperdebug
+  displayName: CW310's Earl Grey Bitstream for Hyperdebug
+  # Build CW310-hyperdebug variant of the Earl Grey toplevel design using Vivado
   dependsOn:
-    - chip_earlgrey_cw310
-    - sw_build
-  condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyDvChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
-  pool: ci-public
-  timeoutInMinutes: 10
+    - lint
+  condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyDvChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'), eq(variables['Build.SourceBranchName'], 'master'))
+  pool: ci-public-eda
+  timeoutInMinutes: 240
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
-  - template: ci/download-artifacts-template.yml
-    parameters:
-      downloadPartialBuildBinFrom:
-        - chip_earlgrey_cw310
-        - sw_build
   - bash: |
-      set -e
+      set -ex
+      trap 'get_logs' EXIT
+      get_logs() {
+        mkdir -p $OBJ_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/
+        mkdir -p $BIN_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/
+        cp -rLvt $OBJ_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/ \
+          $($REPO_TOP/bazelisk.sh outquery-all //hw/bitstream/vivado:fpga_cw310_hyperdebug)
+        cp -rLvt $BIN_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/ \
+          $($REPO_TOP/bazelisk.sh outquery-all //hw/bitstream/vivado:hyperdebug)
+      }
+
       . util/build_consts.sh
-
       module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/bazelisk.sh build //hw/bitstream/vivado:fpga_cw310_hyperdebug
+    displayName: Build bitstream with Vivado
+  - bash: |
+      . util/build_consts.sh
+      echo "Synthesis log"
+      cat $OBJ_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/build.fpga_cw310_hyperdebug/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_hyperdebug_0.1.runs/synth_1/runme.log || true
 
-      util/fpga/splice_rom.sh -t cw310 -T earlgrey -b PROD
-
-    displayName: Splicing bitstream with Vivado
+      echo "Implementation log"
+      cat $OBJ_DIR/hw/top_earlgrey/chip_earlgrey_cw310_hyperdebug/build.fpga_cw310_hyperdebug/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_hyperdebug_0.1.runs/impl_1/runme.log || true
+    displayName: Display synthesis & implementation logs
+    condition: succeededOrFailed()
   - template: ci/upload-artifacts-template.yml
     parameters:
       includePatterns:
         - "/hw/***"
-  - ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
-    - template: ci/gcp-upload-bitstream-template.yml
-      parameters:
-        parentDir: "$BIN_DIR/hw/top_earlgrey"
-        includeFiles:
-          - "lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig"
-          - "lowrisc_systems_chip_earlgrey_cw310_0.1.bit.splice"
-          - "rom.mmi"
-          - "otp.mmi"
-        gcpKeyFile: "gcpkey.json"
-        bucketURI: "gs://opentitan-bitstreams/master"
   - publish: "$(Build.ArtifactStagingDirectory)"
-    artifact: chip_earlgrey_cw310-splice-mask-rom-build-out
+    artifact: chip_earlgrey_cw310_hyperdebug-build-out
     displayName: Upload artifacts for CW310
     condition: failed()
 
@@ -433,9 +491,10 @@
   # Build CW305 variant of the English Breakfast toplevel design using Vivado
   dependsOn: build_and_execute_verilated_tests_englishbreakfast
   condition: and(succeeded(), eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyDvChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
-  pool: ci-public
+  pool: ci-public-eda
   timeoutInMinutes: 120 # 2 hours
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - template: ci/download-artifacts-template.yml
     parameters:
@@ -451,31 +510,111 @@
       includePatterns:
         - "/hw/top_englishbreakfast/lowrisc_systems_chip_englishbreakfast_cw305_0.1.bit"
 
-- job: execute_fpga_tests_cw310
-  displayName: CW310 Tests
-  # Execute tests on ChipWhisperer CW310 FPGA board
-  pool: FPGA
-  timeoutInMinutes: 30
+- job: cache_bitstreams
+  displayName: Cache bitstreams to GCP
+  pool:
+    vmImage: ubuntu-20.04
   dependsOn:
     - chip_earlgrey_cw310
-    - chip_earlgrey_cw310_splice_mask_rom
-    - sw_build
+    - chip_earlgrey_cw310_hyperdebug
+  condition: eq(variables['Build.SourceBranchName'], 'master')
   steps:
+    - template: ci/download-artifacts-template.yml
+      parameters:
+        downloadPartialBuildBinFrom:
+          - chip_earlgrey_cw310
+          - chip_earlgrey_cw310_hyperdebug
+    - bash: |
+        set -x
+        . util/build_consts.sh
+    - template: ci/gcp-upload-bitstream-template.yml
+      parameters:
+        parentDir: "$BIN_DIR/hw/top_earlgrey"
+        includeFiles:
+          - "lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig"
+          - "rom.mmi"
+          - "otp.mmi"
+          - "chip_earlgrey_cw310_hyperdebug/lowrisc_systems_chip_earlgrey_cw310_hyperdebug_0.1.bit"
+          - "chip_earlgrey_cw310_hyperdebug/rom.mmi"
+          - "chip_earlgrey_cw310_hyperdebug/otp.mmi"
+        gcpKeyFile: "gcpkey.json"
+        bucketURI: "gs://opentitan-bitstreams/master"
+
+- job: execute_test_rom_fpga_tests_cw310
+  displayName: CW310 Test ROM Tests
+  pool: FPGA
+  timeoutInMinutes: 45
+  dependsOn:
+    - chip_earlgrey_cw310
+    - sw_build
+  condition: succeeded( 'chip_earlgrey_cw310', 'sw_build' )
+  steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - template: ci/download-artifacts-template.yml
     parameters:
       downloadPartialBuildBinFrom:
-        - chip_earlgrey_cw310_splice_mask_rom
+        - chip_earlgrey_cw310
         - sw_build
   - bash: |
       set -e
-      ci/scripts/run-fpga-cw310-tests.sh
+      . util/build_consts.sh
+      module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/scripts/run-fpga-cw310-tests.sh cw310_test_rom,-jtag || { res=$?; echo "To reproduce failures locally, follow the instructions at https://docs.opentitan.org/doc/getting_started/setup_fpga/#reproducing-fpga-ci-failures-locally"; exit "${res}"; }
+    displayName: Execute tests
+
+- job: execute_rom_fpga_jtag_tests_cw310
+  displayName: Experimental CW310 ROM JTAG Tests
+  pool: FPGA
+  timeoutInMinutes: 45
+  dependsOn:
+    - chip_earlgrey_cw310
+    - sw_build
+  condition: succeeded( 'chip_earlgrey_cw310', 'sw_build' )
+  steps:
+  - template: ci/checkout-template.yml
+  - template: ci/install-package-dependencies.yml
+  - template: ci/download-artifacts-template.yml
+    parameters:
+      downloadPartialBuildBinFrom:
+        - chip_earlgrey_cw310
+        - sw_build
+  - bash: |
+      set -e
+      . util/build_consts.sh
+      module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/scripts/run-fpga-cw310-tests.sh jtag || { res=$?; echo "To reproduce failures locally, follow the instructions at https://docs.opentitan.org/doc/getting_started/setup_fpga/#reproducing-fpga-ci-failures-locally"; exit "${res}"; }
+    displayName: Execute tests
+    # TODO(lowRISC/opentitan#16067) Fail CI when JTAG tests fail.
+    continueOnError: True
+
+- job: execute_rom_fpga_tests_cw310
+  displayName: CW310 ROM Tests
+  pool: FPGA
+  timeoutInMinutes: 45
+  dependsOn:
+    - chip_earlgrey_cw310
+    - sw_build
+  condition: succeeded( 'chip_earlgrey_cw310', 'sw_build' )
+  steps:
+  - template: ci/checkout-template.yml
+  - template: ci/install-package-dependencies.yml
+  - template: ci/download-artifacts-template.yml
+    parameters:
+      downloadPartialBuildBinFrom:
+        - chip_earlgrey_cw310
+        - sw_build
+  - bash: |
+      set -e
+      . util/build_consts.sh
+      module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/scripts/run-fpga-cw310-tests.sh cw310_rom,-jtag || { res=$?; echo "To reproduce failures locally, follow the instructions at https://docs.opentitan.org/doc/getting_started/setup_fpga/#reproducing-fpga-ci-failures-locally"; exit "${res}"; }
     displayName: Execute tests
 
 - job: deploy_release_artifacts
   displayName: Package & deploy release
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   dependsOn:
     - lint
     - sw_build
@@ -483,6 +622,7 @@
     - chip_earlgrey_cw310
   condition: and(eq(dependencies.lint.outputs['DetermineBuildType.onlyDocChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyDvChanges'], '0'), eq(dependencies.lint.outputs['DetermineBuildType.onlyCdcChanges'], '0'))
   steps:
+  - template: ci/checkout-template.yml
   - template: ci/install-package-dependencies.yml
   - template: ci/download-artifacts-template.yml
     parameters:
@@ -493,9 +633,7 @@
         - chip_englishbreakfast_verilator
   - bash: |
       . util/build_consts.sh
-
       ci/scripts/make_distribution.sh
-
       tar --list -f $BIN_DIR/opentitan-*.tar.xz
       # Put the resulting tar file into a directory the |publish| step below can reference.
       mkdir "$BUILD_ROOT/dist-final"
@@ -514,16 +652,17 @@
       assets: |
           $(Build.ArtifactStagingDirectory)/dist-final/*
 
-
 - job: build_docker_containers
   displayName: "Build Docker Containers"
   pool:
-    vmImage: ubuntu-18.04
+    vmImage: ubuntu-20.04
   dependsOn:
     - lint
   steps:
+  - template: ci/checkout-template.yml
   - task: Docker@2
     displayName: Build Developer Utility Container
+    continueOnError: True
     inputs:
       command: build
       Dockerfile: ./util/container/Dockerfile
@@ -533,11 +672,11 @@
     inputs:
       command: build
       tags: gcr.io/active-premise-257318/builder
-      Dockerfile: ./site/docs/builder.Dockerfile
+      Dockerfile: ./util/site/site-builder/builder.Dockerfile
       buildContext: .
   - task: Docker@2
     displayName: Build Documentation Redirector Container
     inputs:
       command: build
-      Dockerfile: ./site/redirector/Dockerfile
-      buildContext: ./site/redirector
+      Dockerfile: ./site/redirector/landing/Dockerfile
+      buildContext: ./site/redirector/landing
diff --git a/bazelisk.sh b/bazelisk.sh
index 4537019..befaf05 100755
--- a/bazelisk.sh
+++ b/bazelisk.sh
@@ -18,6 +18,7 @@
 : "${CURL_FLAGS:=--silent}"
 : "${REPO_TOP:=$(git rev-parse --show-toplevel)}"
 : "${BINDIR:=.bin}"
+: "${BAZEL_BIN:=$(which bazel)}"
 
 readonly release="v1.11.0"
 declare -A hashes=(
@@ -31,20 +32,21 @@
 )
 
 function os_arch() {
-    local arch="$(uname -m -o)"
+    local arch
+    arch="$(uname -m -o)"
     echo "${architectures[$arch]:-${arch}}"
 }
 
 function check_hash() {
-    local file="$1"
-    local target="$(os_arch)"
-    local value="$(sha256sum "${file}" | cut -f1 -d' ')"
-    local expect="${hashes[$target]}"
-    return $(test "$value" == "$expect")
+    local file target
+    file="$1"
+    target="$(os_arch)"
+    echo "${hashes[$target]}  $file" | sha256sum --check --quiet
 }
 
 function prepare() {
-    local target="$(os_arch)"
+    local target
+    target="$(os_arch)"
     local bindir="${REPO_TOP}/${BINDIR}"
     local file="${bindir}/bazelisk"
     local url="https://github.com/bazelbuild/bazelisk/releases/download/${release}/bazelisk-${target}"
@@ -63,23 +65,97 @@
     return 0
 }
 
+function outquery_starlark_expr() {
+    local query="$1"
+    shift
+    if [[ ${query} == "outquery" ]]; then
+        q="-one"
+    else
+        q=${query#outquery}
+    fi
+
+    case "$q" in
+        -one)
+            echo "target.files.to_list()[0].path"
+            ;;
+        -all)
+            echo "\"\\n\".join([f.path for f in target.files.to_list()])"
+            ;;
+        -*)
+            echo "\"\\n\".join([f.path for f in target.files.to_list() if \"$q\"[1:] in f.path])"
+            ;;
+        .*)
+            echo "\"\\n\".join([f.path for f in target.files.to_list() if f.path.endswith(\"$q\")])"
+            ;;
+    esac
+}
+
+function do_outquery() {
+    local qexpr="$1"
+    shift
+    "$file" cquery "$@" \
+        --output=starlark --starlark:expr="$qexpr" \
+        --ui_event_filters=-info --noshow_progress
+}
+
 function main() {
     local bindir="${REPO_TOP}/${BINDIR}"
-    local file="${bindir}/bazelisk"
+    local file="${BAZEL_BIN:-${bindir}/bazelisk}"
     local lockfile="${bindir}/bazelisk.lock"
 
-    if ! up_to_date "$file"; then
-        # Grab the lock, blocking until success. Upon success, check again
-        # whether we're up to date (because some other process might have
-        # downloaded bazelisk in the meantime). If not, download it ourselves.
-        mkdir -p "$bindir"
-        (flock -x 9; up_to_date "$file" || prepare) 9>>"$lockfile"
+    # Are we using bazel from the user's PATH or using bazelisk?
+    if expr match "${file}" ".*bazelisk$" >/dev/null; then
+        if ! up_to_date "$file"; then
+            # Grab the lock, blocking until success. Upon success, check again
+            # whether we're up to date (because some other process might have
+            # downloaded bazelisk in the meantime). If not, download it ourselves.
+            mkdir -p "$bindir"
+            (flock -x 9; up_to_date "$file" || prepare) 9>>"$lockfile"
+        fi
+        if ! check_hash "$file"; then
+            echo "sha256sum doesn't match expected value"
+            exit 1
+        fi
     fi
-    if ! check_hash "$file"; then
-        echo "sha256sum doesn't match expected value"
-        exit 1
-    fi
-    exec "$file" "$@"
+
+    case "$1" in
+        outquery*)
+            # The custom 'outquery' command can be used to query bazel for the
+            # outputs associated with labels.
+            # The outquery command can take several forms:
+            #   outquery: return one output file associated with the label.
+            #   outquery-all: return all output files associated with the label.
+            #   outquery-x: return output files containing the substring "x".
+            #   outquery.x: return output files ending with the substring ".x".
+            local qexpr
+            qexpr="$(outquery_starlark_expr "$1")"
+            shift
+            do_outquery "$qexpr" "$@"
+            ;;
+        build-then)
+            # The 'build-then' command builds the requested targets and then
+            # evaluates the given command template, replacing "%s" with the path
+            # to an output file.
+            #
+            # For example, the command below would build "//:foo" and run "less"
+            # on one of the output files.
+            #
+            #     ./bazelisk.sh build-then "less %s" //:foo
+            shift
+            local command_template="$1"
+            shift
+            local qexpr outfile
+            qexpr="$(outquery_starlark_expr outquery)"
+            outfile=$(do_outquery "$qexpr" "$@")
+            "$file" build "$@"
+            # shellcheck disable=SC2059
+            # We are intentionally using $command_template as a format string.
+            eval "$(printf "$command_template" "$outfile")"
+            ;;
+        *)
+            exec "$file" "$@"
+            ;;
+    esac
 }
 
 main "$@"
diff --git a/book.toml b/book.toml
new file mode 100644
index 0000000..67a2a04
--- /dev/null
+++ b/book.toml
@@ -0,0 +1,55 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+[book]
+authors = ["lowRISC"]
+language = "en"
+multilingual = false
+src = "."
+title = "OpenTitan Documentation"
+
+[output.html]
+site-url = "book/"
+fold = { enable = true}
+git-repository-url = "https://github.com/lowrisc/opentitan"
+edit-url-template = "https://github.com/lowrisc/opentitan/edit/master/{path}"
+curly-quotes = true
+
+additional-js = [
+    "./util/mdbook/wavejson/default.js",
+    "./util/mdbook/wavejson/wavedrom.min.js",
+    "./util/mdbook/wavejson/wavejson.js",
+]
+
+additional-css = [
+    "./util/mdbook/wavejson/wavejson.css",
+]
+
+[preprocessor.readme2index]
+command = "./util/mdbook_readme2index.py"
+
+[preprocessor.wavejson]
+command = "./util/mdbook_wavejson.py"
+
+[preprocessor.reggen]
+command = "./util/mdbook_reggen.py"
+# Python RegEx identifying ip block config paths.
+ip-cfg-py-regex = '(ip|ip_autogen)/.+/data/(?!.+_testplan).+\.hjson'
+
+[preprocessor.testplan]
+command = "./util/mdbook_testplan.py"
+# Python RegEx identifying testplan paths.
+testplan-py-regex = '.+_testplan\.hjson'
+
+[preprocessor.otbn]
+command = "./util/mdbook_otbn.py"
+
+[preprocessor.dashboard]
+command = "./util/mdbook_dashboard.py"
+
+[preprocessor.doxygen]
+command = "./util/mdbook_doxygen.py"
+out-dir = "build-site/gen"
+html-out-dir = "gen/doxy"
+dif-src-py-regex = 'dif_\S*\.h'
diff --git a/ci/.bazelrc b/ci/.bazelrc
index 379ebd7..b77c07b 100644
--- a/ci/.bazelrc
+++ b/ci/.bazelrc
@@ -4,12 +4,13 @@
 
 # These options disable the Bazel progress UI, resulting in output closer
 # to ninja and similar tools.
-common --color=no --curses=yes
+common --curses=no
+common --color=yes
 
 # These options ensure that Bazel prints *everything* it is doing as aggressively
 # as possible, but only updating once every second.
 build --show_timestamps
-build --show_progress_rate_limit=1
+build --show_progress_rate_limit=4
 
 # If something goes wrong, show the whole command line for it.
 build --verbose_failures
@@ -20,4 +21,4 @@
 
 # Use the remote Bazel cache for CI.
 build --remote_cache=https://storage.googleapis.com/opentitan-bazel-cache
-build --remote_timeout=60
+build --remote_timeout=5
diff --git a/ci/azure-pipelines-nightly.yml b/ci/azure-pipelines-nightly.yml
new file mode 100644
index 0000000..3982786
--- /dev/null
+++ b/ci/azure-pipelines-nightly.yml
@@ -0,0 +1,70 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Azure Release Pipeline configuration
+# Documentation at https://aka.ms/yaml
+
+schedules:
+# Run this pipeline every day at 07:21 UTC.
+# Use an arbitrary minute value to avoid congestion on the hour.
+- cron: "21 7 * * *"
+  displayName: "OpenTitan Nightly Tests"
+  branches:
+    include:
+    - "master"
+  always: True
+
+trigger: none
+pr: none
+
+variables:
+  # If updating VERILATOR_VERSION, TOOLCHAIN_VERSION or RUST_VERSION, update
+  # the definitions in util/container/Dockerfile as well.
+  VERILATOR_VERSION: 4.210
+  TOOLCHAIN_PATH: /opt/buildcache/riscv
+  VERIBLE_VERSION: v0.0-2135-gb534c1fe
+  RUST_VERSION: 1.60.0
+    # Release tag from https://github.com/lowRISC/lowrisc-toolchains/releases
+  TOOLCHAIN_VERSION: 20220210-1
+    # This controls where builds happen, and gets picked up by build_consts.sh.
+  BUILD_ROOT: $(Build.ArtifactStagingDirectory)
+  VIVADO_VERSION: "2020.2"
+
+jobs:
+- job: checkout
+  displayName: "Checkout repository"
+  pool:
+    vmImage: ubuntu-20.04
+  steps:
+  - checkout: self
+    # Set fetchDepth to 0 to pull the entire git tree for the bitstream cache to work
+    # https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps-checkout#shallow-fetch
+    fetchDepth: 0
+    path: opentitan-repo
+  - bash: |
+      tar -C $(Pipeline.Workspace)/opentitan-repo -czf $(Pipeline.Workspace)/opentitan-repo.tar.gz .
+    displayName: "Pack up repository"
+  - publish: $(Pipeline.Workspace)/opentitan-repo.tar.gz
+    artifact: opentitan-repo
+    displayName: "Upload repository"
+
+- job: rom_e2e
+  displayName: "ROM E2E Tests"
+  timeoutInMinutes: 180
+  dependsOn: checkout
+  pool: FPGA
+  steps:
+  - template: ../ci/checkout-template.yml
+  - template: ../ci/install-package-dependencies.yml
+  - bash: |
+      set -x
+      module load "xilinx/vivado/$(VIVADO_VERSION)"
+      ci/bazelisk.sh test \
+        --define DISABLE_VERILATOR_BUILD=true \
+        --define bitstream=gcp_splice \
+        --test_tag_filters=-verilator,-dv,-broken \
+        --build_tests_only \
+        --test_output=errors \
+        //sw/device/silicon_creator/rom/e2e/...
+    displayName: "Run all ROM E2E tests"
diff --git a/ci/bazelisk.sh b/ci/bazelisk.sh
index 867d073..89722b1 100755
--- a/ci/bazelisk.sh
+++ b/ci/bazelisk.sh
@@ -7,8 +7,8 @@
 # but adds various flags to produce CI-friendly output. It does so by prociding a
 # command-line specified .bazelrc (that is applied alongside //.bazelrc).
 
-if [[ -n "$PWD_OVERRIDE" ]]; then
-    cd "$PWD_OVERRIDE"
+if [[ -n "${PWD_OVERRIDE}" ]]; then
+    cd "${PWD_OVERRIDE}" || exit
 fi
 
 echo "Running bazelisk in $(pwd)."
@@ -16,17 +16,22 @@
 # An additional bazelrc must be synthesized to specify precisely how to use the
 # GCP bazel cache.
 GCP_BAZELRC="$(mktemp /tmp/XXXXXX.bazelrc)"
-trap "rm $GCP_BAZELRC" EXIT
+trap 'rm ${GCP_BAZELRC}' EXIT
 
-if [[ -n "$GCP_BAZEL_CACHE_KEY" && -f "$GCP_BAZEL_CACHE_KEY" ]]; then
+if [[ -n "${GCP_BAZEL_CACHE_KEY}" && -f "${GCP_BAZEL_CACHE_KEY}" ]]; then
     echo "Applying GCP cache key; will upload to the cache."
-    echo "build --google_credentials=$GCP_BAZEL_CACHE_KEY" > "$GCP_BAZELRC"
+    echo "build --google_credentials=${GCP_BAZEL_CACHE_KEY}" >> "${GCP_BAZELRC}"
 else
     echo "No key/invalid path to key. Download from cache only."
-    echo "build --remote_upload_local_results=false" > "$GCP_BAZELRC"
+    echo "build --remote_upload_local_results=false" >> "${GCP_BAZELRC}"
 fi
 
+# Inject the OS version into a parameter used in the action key computation to
+# avoid collisions between different operating systems in the caches.
+# See #14695 for more information.
+echo "build --remote_default_exec_properties=OSVersion=\"$(lsb_release -ds)\"" >> "${GCP_BAZELRC}"
+
 "$(dirname $0)"/../bazelisk.sh \
-  --bazelrc="$GCP_BAZELRC" \
+  --bazelrc="${GCP_BAZELRC}" \
   --bazelrc="$(dirname $0)"/.bazelrc \
-  $@
+  "$@"
diff --git a/ci/checkout-template.yml b/ci/checkout-template.yml
new file mode 100644
index 0000000..6a5fd9e
--- /dev/null
+++ b/ci/checkout-template.yml
@@ -0,0 +1,27 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# This template downloads the version of the repository checked out in the first
+# pipeline job.
+# This ensures that every pipeline job runs on the same version of the
+# repository instead of having Azure perform the checkout each time. More
+# information can be found in #13993.
+
+# The first job in the pipeline must use the default Azure checkout job to
+# fetch the repository. It creates opentitan-repo.tar.gz and uploads it as an
+# artifact. This template downloads the repo into
+# $(Pipeline.Workspace)/opentitan-repo. The files are locaed in
+# $(Pipeline.Workspace)/opentitan-repo/opentitan-repo.tar.gz. This is untarred
+# into the working directory.
+
+steps:
+  - checkout: none # Suppress the default checkout action
+  - download: current
+    artifact: opentitan-repo
+    displayName: Download repository
+  - bash: |
+      tar -xf $(Pipeline.Workspace)/opentitan-repo/opentitan-repo.tar.gz --no-same-owner
+      # --no-same-owner is required since Azure runs this command as root which
+      # preserves ownership. Git will complain about this.
+    displayName: Unpack repository
diff --git a/ci/install-package-dependencies.sh b/ci/install-package-dependencies.sh
index 975a197..0a2050d 100755
--- a/ci/install-package-dependencies.sh
+++ b/ci/install-package-dependencies.sh
@@ -8,7 +8,6 @@
 usage()
 {
     echo "Usage: install-package-dependencies.sh --verilator-version V"
-    echo "                                       --openocd-version V"
     echo "                                       --verible-version V"
     echo "                                       --rust-version V"
     exit 1
@@ -20,11 +19,10 @@
     exit 1
 }
 
-long="verilator-version:,openocd-version:,verible-version:,rust-version:"
+long="verilator-version:,verible-version:,rust-version:"
 ARGS="$(getopt -o "" -l "$long" -- "$@")" || usage
 
 VERILATOR_VERSION=
-OPENOCD_VERSION=
 VERIBLE_VERSION=
 
 eval set -- "$ARGS"
@@ -32,7 +30,6 @@
 do
     case "$1" in
         --verilator-version) VERILATOR_VERSION="$2"; shift 2 ;;
-        --openocd-version)   OPENOCD_VERSION="$2";   shift 2 ;;
         --verible-version)   VERIBLE_VERSION="$2";   shift 2 ;;
         --rust-version)      RUST_VERSION="$2";      shift 2 ;;
         --) shift; break ;;
@@ -42,14 +39,13 @@
 
 # Check that we've seen all the expected versions
 test -n "$VERILATOR_VERSION" || error "Missing --verilator-version"
-test -n "$OPENOCD_VERSION"   || error "Missing --openocd-version"
 test -n "$VERIBLE_VERSION"   || error "Missing --verible-version"
 test -n "$RUST_VERSION"      || error "Missing --rust-version"
 
 # Check that there aren't any positional arguments
 test $# = 0 || error "Unexpected positional arguments"
 
-CI_DIR="$(dirname "$(readlink -e "$BASH_SOURCE")")"
+CI_DIR="$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")"
 REPO_TOP="$(readlink -e "$CI_DIR/..")"
 
 cd "$REPO_TOP"
@@ -66,26 +62,6 @@
 }
 trap 'rm -rf "$TMPDIR"' EXIT
 
-
-# Install verilator from experimental OBS repository
-# apt-requirements.txt doesn't cover this dependency as we don't support
-# using the repository below for anything but our CI (yet).
-lsb_sr="$(lsb_release -sr)"
-OBS_URL="https://download.opensuse.org/repositories"
-OBS_PATH="/home:/phiwag:/edatools/xUbuntu_${lsb_sr}"
-REPO_URL="${OBS_URL}${OBS_PATH}"
-
-EDATOOLS_REPO_KEY="${REPO_URL}/Release.key"
-EDATOOLS_REPO="deb ${REPO_URL}/ /"
-
-curl -f -sL -o "$TMPDIR/obs.asc" "$EDATOOLS_REPO_KEY" || {
-    error "Failed to download repository key from ${REPO_URL}"
-}
-echo "$EDATOOLS_REPO" > "$TMPDIR/obs.list"
-
-sudo mv "$TMPDIR/obs.asc"  /etc/apt/trusted.gpg.d/obs.asc
-sudo mv "$TMPDIR/obs.list" /etc/apt/sources.list.d/edatools.list
-
 # Install gcc-9 and set it as the default.
 sudo add-apt-repository ppa:ubuntu-toolchain-r/test \
   && sudo $APT_CMD update \
@@ -102,8 +78,6 @@
 
 ci_reqs="$TMPDIR/apt-requirements-ci.txt"
 cp apt-requirements.txt "$ci_reqs"
-echo "verilator-${VERILATOR_VERSION}" >> "$ci_reqs"
-echo "openocd-${OPENOCD_VERSION}" >> "$ci_reqs"
 echo rsync >> "$ci_reqs"
 
 # NOTE: We use sed to remove all comments from apt-requirements-ci.txt,
@@ -124,11 +98,12 @@
 # specify that an older version of a package must be used for a certain
 # Python version. If that information is not read, pip installs the latest
 # version, which then fails to run.
-python3 -m pip install --user -U pip setuptools
+python3 -m pip install --user -U pip "setuptools<66.0.0"
 
 pip3 install --user -r python-requirements.txt
 
 # Install Verible
+lsb_sr="$(lsb_release -sr)"
 lsb_sc="$(lsb_release -sc)"
 VERIBLE_BASE_URL="https://github.com/google/verible/releases/download"
 VERIBLE_TARBALL="verible-${VERIBLE_VERSION}-Ubuntu-${lsb_sr}-${lsb_sc}-x86_64.tar.gz"
@@ -145,10 +120,43 @@
 tar -C /tools/verible -xf "$verible_tar" --strip-components=1
 export PATH=/tools/verible/bin:$PATH
 
+# Install verilator
+if [ $lsb_sr = "18.04" ]; then
+  UBUNTU_SUFFIX="-u18"
+fi
+
+VERILATOR_TARBALL=verilator"$UBUNTU_SUFFIX-v$VERILATOR_VERSION".tar.gz
+VERILATOR_URL=https://storage.googleapis.com/verilator-builds/$VERILATOR_TARBALL
+echo "Fetching verilator tarball" $VERILATOR_URL
+curl -f -Ls -o "$VERILATOR_TARBALL" "$VERILATOR_URL" || {
+    error "Failed to download verilator from ${VERILATOR_URL}"
+}
+
+sudo mkdir -p /tools/verilator
+sudo chmod 777 /tools/verilator
+tar -C /tools/verilator -xvzf $VERILATOR_TARBALL
+export PATH=/tools/verilator/v$VERILATOR_VERSION/bin:$PATH
+
 # Install Rust
 sw/vendor/rustup/rustup-init.sh -y \
     --default-toolchain "${RUST_VERSION}"
 export PATH=$HOME/.cargo/bin:$PATH
 
+# Install mdbook
+MDBOOK_VERSION="v0.4.27"
+MDBOOK_BASE_URL="https://github.com/rust-lang/mdBook/releases/download"
+MDBOOK_TARBALL="mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
+MDBOOK_URL="${MDBOOK_BASE_URL}/${MDBOOK_VERSION}/${MDBOOK_TARBALL}"
+MDBOOK_DOWNLOAD="$TMPDIR/mdbook.tar.gz"
+
+curl -f -Ls -o "$MDBOOK_DOWNLOAD" "${MDBOOK_URL}" || {
+    error "Failed to download verible from ${MDBOOK_URL}"
+}
+
+sudo mkdir -p /tools/mdbook
+sudo chmod 777 /tools/mdbook
+tar -C /tools/mdbook -xf "$MDBOOK_DOWNLOAD"
+export PATH=/tools/mdbook:$PATH
+
 # Propagate PATH changes to all subsequent steps of the job
 echo "##vso[task.setvariable variable=PATH]$PATH"
diff --git a/ci/install-package-dependencies.yml b/ci/install-package-dependencies.yml
index 271febc..04c22e6 100644
--- a/ci/install-package-dependencies.yml
+++ b/ci/install-package-dependencies.yml
@@ -26,7 +26,6 @@
       cd ${{ parameters.REPO_TOP }}
       ci/install-package-dependencies.sh \
         --verilator-version $(VERILATOR_VERSION) \
-        --openocd-version $(OPENOCD_VERSION) \
         --verible-version $(VERIBLE_VERSION) \
         --rust-version $(RUST_VERSION)
     retryCountOnTaskFailure: 3
diff --git a/ci/jobs/quick-lint.sh b/ci/jobs/quick-lint.sh
index d48ba11..09ac1d9 100755
--- a/ci/jobs/quick-lint.sh
+++ b/ci/jobs/quick-lint.sh
@@ -49,9 +49,6 @@
 echo -e "\n### Run Python lint (mypy)"
 ci/scripts/mypy.sh $tgt_branch
 
-echo -e "\n### Ensure all generated files are clean and up-to-date"
-ci/scripts/check-generated.sh
-
 echo -e "\n### Use clang-format to check C/C++ coding style"
 ci/scripts/clang-format.sh $tgt_branch
 
@@ -61,6 +58,9 @@
 echo -e "\n### Use rustfmt to check Rust coding style"
 ci/scripts/rust-format.sh $tgt_branch
 
+echo -e "\n### Run shellcheck on all shell scripts"
+ci/scripts/run-shellcheck.sh
+
 echo -e "\n### Render documentation"
 ci/scripts/build-docs.sh
 
diff --git a/ci/jobs/slow-lint.sh b/ci/jobs/slow-lint.sh
index c5ff179..48f7047 100755
--- a/ci/jobs/slow-lint.sh
+++ b/ci/jobs/slow-lint.sh
@@ -11,8 +11,17 @@
 
 set -e
 
+echo -e "\n### Check tags on Bazel artifacts"
+ci/scripts/check-bazel-tags.sh
+
+echo -e "\n### Ensure bazel doesn't use 'git_repository's"
+ci/scripts/check-bazel-banned-rules.sh
+
+echo -e "\n### Ensure all generated files are clean and up-to-date"
+ci/scripts/check-generated.sh
+
 echo -e "\n### Use buiildifier to check Bazel coding style"
-bazel run buildifier_check
+bazel test //quality:buildifier_check --test_output=streamed
 
 echo "### Check vendored directories are up-to-date"
 ci/scripts/check-vendoring.sh
diff --git a/ci/scripts/build-bitstream-vivado.sh b/ci/scripts/build-bitstream-vivado.sh
index d43a444..5f296d4 100755
--- a/ci/scripts/build-bitstream-vivado.sh
+++ b/ci/scripts/build-bitstream-vivado.sh
@@ -46,7 +46,7 @@
         ROM_TARGET=cw310
         ;;
     xcw305)
-        ROM_TARGET=nexysvideo
+        ROM_TARGET=cw305
         ;;
     *)
         usage "Unknown target: $TARGET"
diff --git a/ci/scripts/build-chip-verilator.sh b/ci/scripts/build-chip-verilator.sh
index 654d183..1d6d374 100755
--- a/ci/scripts/build-chip-verilator.sh
+++ b/ci/scripts/build-chip-verilator.sh
@@ -22,6 +22,7 @@
         fusesoc_core=lowrisc:dv:chip_verilator_sim
         vname=Vchip_sim_tb
         verilator_options="--threads 4"
+        make_options="-j 4"
         ;;
     englishbreakfast)
         fileset=fileset_topgen
@@ -29,7 +30,7 @@
         vname=Vchip_englishbreakfast_verilator
         # Englishbreakfast on CI runs on a 2-core CPU
         verilator_options="--threads 2"
-
+        make_options="-j 2"
         util/topgen-fusesoc.py --files-root=. --topname=top_englishbreakfast
         ;;
     *)
@@ -38,7 +39,7 @@
 esac
 
 # Move to project root
-cd $(git rev-parse --show-toplevel)
+cd "$(git rev-parse --show-toplevel)"
 
 . util/build_consts.sh
 
@@ -51,7 +52,8 @@
   run --flag=$fileset --target=sim --setup --build \
   --build-root="$OBJ_DIR/hw" \
   $fusesoc_core \
-  --verilator_options="${verilator_options}"
+  --verilator_options="${verilator_options}" \
+  --make_options="${make_options}"
 
 cp "$OBJ_DIR/hw/sim-verilator/${vname}" \
    "$BIN_DIR/hw/top_${tl}/Vchip_${tl}_verilator"
diff --git a/ci/scripts/build-docs.sh b/ci/scripts/build-docs.sh
index eabbb85..e194ded 100755
--- a/ci/scripts/build-docs.sh
+++ b/ci/scripts/build-docs.sh
@@ -6,17 +6,17 @@
 # Build docs and tell the Azure runner to upload any doxygen warnings
 
 set -e
-util/build_docs.py || {
+util/site/build-docs.sh || {
   echo -n "##vso[task.logissue type=error]"
   echo "Documentation build failed."
   exit 1
 }
 
 # Upload Doxygen Warnings if Present
-if [ -f "build/docs-generated/sw/doxygen_warnings.log" ]; then
+if [ -f "build-site/gen/doxygen_warnings.log" ]; then
   echo -n "##vso[task.uploadfile]"
-  echo "${PWD}/build/docs-generated/sw/doxygen_warnings.log"
+  echo "${PWD}/build-site/gen/doxygen_warnings.log"
   # Doxygen currently generates lots of warnings.
   # echo -n "##vso[task.issue type=warning]"
-  # echo "Doxygen generated warnings. Use 'util/build_docs.py' to generate warning logfile."
+  # echo "Doxygen generated warnings. Use 'util/site/build-docs.sh' to generate warning logfile."
 fi
diff --git a/ci/scripts/build-site.sh b/ci/scripts/build-site.sh
deleted file mode 100755
index 2353f4f..0000000
--- a/ci/scripts/build-site.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-
-# Build the landing site
-
-set -e
-cd site/landing
-../../build/docs-hugo/hugo
diff --git a/ci/scripts/check-ascii.sh b/ci/scripts/check-ascii.sh
index 3a9b007..588c410 100755
--- a/ci/scripts/check-ascii.sh
+++ b/ci/scripts/check-ascii.sh
@@ -23,6 +23,18 @@
     md
     html
 
+    # The ECMA Script standard allows unicode:
+    # https://262.ecma-international.org/6.0/#sec-source-text
+    #
+    # There is no OpenTitan JavaScript style guide so all `js`
+    # have not been white listed.
+    #
+    # However, minified Javascript is similar to executables
+    # in that it is not edited directly
+    # but produced by a compiler/minifier for a JavaScript run-time,
+    # and so it makes sense for it to be excluded from this ascii check.
+    min.js
+
     # We don't mandate 7-bit ASCII for Python or C/C++ code. The
     # Google style guide, which we inherit suggests avoiding
     # unnecessary non-ASCII text, but doesn't forbid it entirely.
diff --git a/ci/scripts/check-bazel-banned-rules.sh b/ci/scripts/check-bazel-banned-rules.sh
new file mode 100755
index 0000000..ef605c7
--- /dev/null
+++ b/ci/scripts/check-bazel-banned-rules.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+GIT_REPOS=$(./bazelisk.sh query "kind('(new_)?git_repository', //external:*)")
+if [[ ${GIT_REPOS} ]]; then
+  echo "Bazel's 'git_repository' rule is insecure and incompatible with OpenTitan's airgapping strategy."
+  echo "Please replace $GIT_REPOS with our 'http_archive_or_local' rule and set a sha256 so it can be canonically reproducible."
+  exit 1
+fi
diff --git a/ci/scripts/check-bazel-tags.sh b/ci/scripts/check-bazel-tags.sh
new file mode 100755
index 0000000..35d8cb8
--- /dev/null
+++ b/ci/scripts/check-bazel-tags.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+# The list of bazel tags is represented as a string and checked with a regex
+# https://bazel.build/query/language#attr
+# This function takes a tag(or regex component) and wraps it so attr can query
+# for exact matches.
+exact_regex () {
+  echo "[\\[ ]${1}[,\\]]"
+}
+
+check_empty () {
+    if [[ ${2} ]]; then
+        echo "$1"
+        echo "$2"|sed 's/^/    /';
+        echo "$3"
+        return 1
+    fi
+}
+
+# This check ensures OpenTitan software can be built with a wildcard without
+# waiting for Verilator using --build_tag_filters=-verilator
+untagged=$(./bazelisk.sh query \
+  "rdeps(
+      //...,
+      //hw:verilator
+  )
+  except
+  attr(
+      tags,
+      '$(exact_regex "(verilator|manual)")',
+      //...
+  )" \
+  --output=label_kind)
+check_empty "Error:" "${untagged}" \
+"Target(s) above depend(s) on //hw:verilator; please tag it with verilator or
+(to prevent matching any wildcards) manual.
+NOTE: test_suites that contain bazel tests with different tags should almost
+universally use the manual tag."
+
+# This check ensures OpenTitan software can be built with wildcards in
+# environments that don't have vivado or vivado tools installed by using
+# --build_tag_filters=-vivado.
+untagged=$(./bazelisk.sh query \
+  "rdeps(
+      //...,
+      kind(
+          'bitstream_splice',
+          //...
+      )
+      except`# Other than those used to build cached bitstreams`
+      (
+          deps(//hw/bitstream:rom)
+          union
+          deps(//hw/bitstream:test_rom)
+      )
+  )
+  except
+  attr(
+      tags,
+      '$(exact_regex "(vivado|manual)")',
+      //...
+  )" \
+  --output=label_kind)
+check_empty "Error:" "${untagged}" \
+"Target(s) above depend(s) on a bitstream_splice that isn't cached.
+Please tag it with vivado or (to prevent matching any wildcards) manual.
+NOTE: test_suites that contain tests with different sets of tags should almost
+universally use the manual tag."
diff --git a/ci/scripts/check-generated.sh b/ci/scripts/check-generated.sh
index 7183414..aa3006d 100755
--- a/ci/scripts/check-generated.sh
+++ b/ci/scripts/check-generated.sh
@@ -26,7 +26,7 @@
     shift
     "$@" || {
         echo -n "##vso[task.logissue type=error]"
-        echo "Failed to auto-generate $thing. (command: '$@')"
+        echo "Failed to auto-generate $thing. (command: '$*')"
         echo
         cleanup
         return 1
@@ -34,7 +34,7 @@
 
     is_clean || {
         echo -n "##vso[task.logissue type=error]"
-        echo "Auto-generated $thing not up-to-date. Regenerate with '$@'."
+        echo "Auto-generated $thing not up-to-date. Regenerate with '$*'."
         echo
         cleanup
         return 1
diff --git a/ci/scripts/check-licence-headers.sh b/ci/scripts/check-licence-headers.sh
index e87676e..220f683 100755
--- a/ci/scripts/check-licence-headers.sh
+++ b/ci/scripts/check-licence-headers.sh
@@ -3,7 +3,7 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
-# A wrapper around licence-checker.py, used for CI.
+# A wrapper around //quality:license_check, used for CI.
 #
 # Expects a single argument, which is the pull request's target branch
 # (usually "master").
@@ -22,16 +22,15 @@
 }
 echo "Checking licence headers on files changed since $merge_base"
 
-lc_script=util/lowrisc_misc-linters/licence-checker/licence-checker.py
-lc_cmd="$lc_script --config util/licence-checker.hjson"
-
 # Ask git for a list of null-separated names of changed files and pipe
 # those through to the licence checker using xargs. Setting pipefail
 # ensures that we'll see an error if the git diff command fails for
 # some reason.
 set -o pipefail
-git diff -z --name-only --diff-filter=ACMRTUXB "$merge_base" | \
-    xargs -r -0 $lc_cmd || {
+(for F in $(git diff --name-only --diff-filter=ACMRTUXB "$merge_base"); do
+    echo "--test_arg=\"$F\""
+done)| \
+    xargs -r ./bazelisk.sh test //quality:license_check --test_output=streamed || {
 
     echo >&2 -n "##vso[task.logissue type=error]"
     echo >&2 "Licence header check failed."
diff --git a/ci/scripts/check-no-bazelrc-site.sh b/ci/scripts/check-no-bazelrc-site.sh
new file mode 100755
index 0000000..1d93630
--- /dev/null
+++ b/ci/scripts/check-no-bazelrc-site.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# Checks that .bazelrc-site file has not been accidentally committed to git
+# where it could potentially impact the local configuration of other users.
+#
+# To be run in a CI environment (with a repo), and thus we simply check for
+# the non-existence of the file, to confirm that it is not in the repo.
+
+set -e
+
+if [ -f .bazelrc-site ]; then
+    echo -n "##vso[task.logissue type=error]"
+    echo "The .bazelrc-site file should not appear in a clean checkout"
+    exit 1
+fi
diff --git a/ci/scripts/check_bazel_target_names.py b/ci/scripts/check_bazel_target_names.py
new file mode 100755
index 0000000..3412f01
--- /dev/null
+++ b/ci/scripts/check_bazel_target_names.py
@@ -0,0 +1,27 @@
+#!/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
+"""Checks for Bazel targets that contain banned characters.
+"""
+
+import sys
+
+from lib.bazel_query import BazelQueryRunner
+
+AZURE_PIPELINES_ERROR = "##vso[task.logissue type=warning]"
+
+if __name__ == '__main__':
+    bazel = BazelQueryRunner()
+
+    results = list(bazel.find_targets_with_banned_chars())
+    if results:
+        print(AZURE_PIPELINES_ERROR +
+              "Some target names have banned characters.")
+    for target, bad_chars in results:
+        print("Target name contains banned characters {}: {}".format(
+            bad_chars, target))
+
+    if results:
+        sys.exit(1)
diff --git a/ci/scripts/check_bazel_test_suites.py b/ci/scripts/check_bazel_test_suites.py
new file mode 100755
index 0000000..49cf984
--- /dev/null
+++ b/ci/scripts/check_bazel_test_suites.py
@@ -0,0 +1,50 @@
+#!/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
+"""Checks for test_suite targets that are either empty or not tagged "manual".
+"""
+
+import sys
+
+from lib.bazel_query import BazelQueryRunner
+
+NON_MANUAL_TEST_SUITE_DESCRIPTION = """
+Test suites below aren't tagged with manual, but probably should be.
+
+Without the "manual" tag, these test suites will match wildcards like //... and
+may pull in tests that were otherwise excluded with --build_tag_filters. (This
+will not happen if the test suites have the same tags as their contents.)
+
+There aren't many scenarios in which you need a test_suite to match wildcards
+(because its tests are also in the workspace) so you should probably tag it
+with manual.
+"""
+
+EMPTY_TEST_SUITE_DESCRIPTION = """
+Test suites below contain zero tests. This is probably an accident.
+"""
+
+AZURE_PIPELINES_WARNING = "##vso[task.logissue type=error]"
+AZURE_PIPELINES_ERROR = "##vso[task.logissue type=warning]"
+
+if __name__ == '__main__':
+    bazel = BazelQueryRunner()
+    non_manual_test_suites = list(bazel.find_non_manual_test_suites())
+    empty_test_suites = list(bazel.find_empty_test_suites())
+
+    if non_manual_test_suites:
+        print("-" * 80)
+        print(AZURE_PIPELINES_WARNING + NON_MANUAL_TEST_SUITE_DESCRIPTION)
+        for target in non_manual_test_suites:
+            print("  - " + target)
+
+    if empty_test_suites:
+        print("-" * 80)
+        print(AZURE_PIPELINES_ERROR + EMPTY_TEST_SUITE_DESCRIPTION)
+        for target in empty_test_suites:
+            print("  - " + target)
+
+    if empty_test_suites:
+        sys.exit(1)
diff --git a/ci/scripts/clang-format.sh b/ci/scripts/clang-format.sh
index edb5edc..288c3f8 100755
--- a/ci/scripts/clang-format.sh
+++ b/ci/scripts/clang-format.sh
@@ -22,8 +22,10 @@
 }
 echo "Running C/C++ lint checks on files changed since $merge_base"
 
-trap 'echo "code failed clang_format_check fix with ./bazelisk.sh run //:clang_format_fix"' ERR
+trap 'echo "code failed clang_format_check fix with ./bazelisk.sh run //quality:clang_format_fix"' ERR
 
 set -o pipefail
-git diff --name-only "$merge_base" -- "*.cpp" "*.cc" "*.c" "*.h" ':!*/vendor/*' | \
-    xargs -r ./bazelisk.sh run //:clang_format_check --
+(for F in $(git diff --name-only "$merge_base" -- "*.cpp" "*.cc" "*.c" "*.h" ':!*/vendor/*'); do
+    echo "--test_arg=\"$F\""
+done) | \
+    xargs -r ./bazelisk.sh test //quality:clang_format_check --test_output=streamed
diff --git a/ci/scripts/get-bitstream-strategy.sh b/ci/scripts/get-bitstream-strategy.sh
new file mode 100755
index 0000000..1ee93e5
--- /dev/null
+++ b/ci/scripts/get-bitstream-strategy.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+usage_string="
+Usage: get-bitstream-strategy.sh <cached-bitstream-target> [pathspec]...
+
+    cached-bitsream-target The bazel target for the cached bitstream. This script
+                           will retrieve the most recent image from a commit in this
+                           this branch's history.
+    pathspec A git pathspec (multiple instances permitted) indicating which files
+                   trigger building the bitstream. Please use only exclude pathspecs
+                   to cause new, unconsidered paths to trigger the build.
+"
+
+if [ $# -lt 1 ]; then
+  echo >&2 "ERROR: Unexpected number of arguments"
+  echo >&2 "${usage_string}"
+  exit 1
+fi
+
+. util/build_consts.sh
+
+bitstream_target=${*:1:1}
+
+if [ $# -gt 1 ]; then
+  excluded_files=( "${@:2}" )
+else
+  excluded_files=( "." )
+fi
+
+# Retrieve the most recent bitstream at or before HEAD.
+BITSTREAM="--refresh HEAD" ./bazelisk.sh build ${bitstream_target}
+
+# The directory containing the bitstream is named after the git hash.
+bitstream_file=$(ci/scripts/target-location.sh ${bitstream_target})
+bitstream_commit=$(basename "$(dirname ${bitstream_file})")
+
+echo "Checking for changes against pre-built bitstream from ${bitstream_commit}"
+echo "Files changed:"
+git diff --stat --name-only ${bitstream_commit}
+echo
+echo "Changed files after exclusions applied:"
+# Use the cached bitstream if no changed files remain.
+if git diff --exit-code --stat --name-only ${bitstream_commit} -- "${excluded_files[@]}"; then
+  bitstream_strategy=cached
+else
+  bitstream_strategy=build
+fi
+
+echo
+echo "Bitstream strategy is ${bitstream_strategy}"
+echo "##vso[task.setvariable variable=bitstreamStrategy]${bitstream_strategy}"
diff --git a/ci/scripts/get-build-type.sh b/ci/scripts/get-build-type.sh
index 24decd8..368c8bc 100755
--- a/ci/scripts/get-build-type.sh
+++ b/ci/scripts/get-build-type.sh
@@ -62,8 +62,8 @@
     # Check if the commit has only CDC related changes (run-cdc.tcl,
     # cdc_waivers*.tcl).
     echo "Checking for changes in this PR other than to CDC waivers"
-    git diff --quiet "$merge_base" -- ':(attr:!cdc)' && only_cdc_change=1 || true
-    if [[ $only_cdc_change -eq 1 ]]; then
+    git diff --quiet "$merge_base" -- ':(attr:!cdc)' && only_cdc_changes=1 || true
+    if [[ $only_cdc_changes -eq 1 ]]; then
         echo "PR is only CDC waiver changes"
     else
         echo "PR contains non CDC-related changes"
diff --git a/ci/scripts/lib/BUILD b/ci/scripts/lib/BUILD
new file mode 100644
index 0000000..4086ee5
--- /dev/null
+++ b/ci/scripts/lib/BUILD
@@ -0,0 +1,15 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+load("@rules_python//python:defs.bzl", "py_test")
+
+package(default_visibility = ["//visibility:public"])
+
+py_test(
+    name = "bazel_query_test",
+    srcs = [
+        "bazel_query.py",
+        "bazel_query_test.py",
+    ],
+)
diff --git a/ci/scripts/lib/bazel_query.py b/ci/scripts/lib/bazel_query.py
new file mode 100644
index 0000000..e6fbbf2
--- /dev/null
+++ b/ci/scripts/lib/bazel_query.py
@@ -0,0 +1,117 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import string
+import subprocess
+from typing import Callable, Iterable, List, Set, Tuple
+
+
+class BazelQuery:
+    """A collection of functions useful for constructing Bazel queries."""
+
+    @staticmethod
+    def rule_exact(rule: str, input: str) -> str:
+        """Match targets in `input` defined by `rule`."""
+        return f'kind("^{rule} rule$", {input})'
+
+    @staticmethod
+    def tag_exact(tag: str, input: str) -> str:
+        """Match targets in `input` that are tagged with `tag`."""
+        regex = BazelQuery.regex_for_tag(tag)
+        return f'attr("tags", "{regex}", {input})'
+
+    @staticmethod
+    def regex_for_tag(tag: str) -> str:
+        """Build a regex to find the given tag in a list.
+
+        The query `attr("tags", pattern, input)` would match the given pattern
+        against a serialized list of tags, e.g. `[foo, bar]`. Subtly, if the
+        pattern is "foo", it will also match targets that are tagged "foobar".
+
+        This function constructs a regex that matches only when the list of tags
+        actually contains `tag`, not just a superstring of `tag`, as recommended
+        by the Bazel query docs: https://bazel.build/query/language#attr
+        """
+        return f"[\\[ ]{tag}[,\\]]"
+
+
+class BazelQueryRunner:
+
+    def __init__(self, backend: Callable[[str], List[str]] = None):
+        self._backend = backend
+
+    def find_targets_with_banned_chars(self) -> Iterable[Tuple[str, Set[str]]]:
+        """Find targets in //... with names containing disallowed characters.
+
+        Bazel allows a liberal set of characters in target names [0], which
+        enables type confusion bugs to go unnoticed. For example, a tuple's
+        string representation, complete with parentheses and commas, can
+        accidentally be inserted into a target name via `string.format()`.
+
+        [0]: https://bazel.build/concepts/labels#target-names
+
+        Yields:
+          A tuple for each target that has banned characters in its name. The
+          first element is the Bazel target's name. The second element is the
+          set of characters that must be removed from the target's name.
+
+        """
+        allowed_chars = set(string.ascii_letters + string.digits + '/:_-.')
+        for target in self.query("//..."):
+            if bad_chars := set(target) - allowed_chars:
+                yield (target, bad_chars)
+
+    def find_empty_test_suites(self) -> Iterable[str]:
+        """Find test_suite targets in //... that contain zero tests.
+
+        Finding empty test suites is not as simple as querying for test_suite
+        targets where `tests = []`. In fact, that is a special case of
+        test_suite that causes it to select all tests in the package.
+
+        There are a few ways to wind up with an empty test suite. One way is to
+        provide a nonempty `tests` argument containing only nonexistent labels.
+        Another way is to provide a non-empty list of tests that exist, but to
+        specify `tags` that exclude all the tests.
+
+        Yields:
+          Names of Bazel targets.
+        """
+
+        def print_progress(i, n):
+            if n // 5 == 0 or i % (n // 5) == 0:
+                print(self.find_empty_test_suites.__name__, end="")
+                print(": Running query {}/{}...".format(i + 1, n))
+
+        query_test_suites = BazelQuery.rule_exact("test_suite", "//...")
+        all_test_suites = self.query(query_test_suites)
+
+        for i, test_suite in enumerate(all_test_suites):
+            print_progress(i, len(all_test_suites))
+            tests = self.query("tests({})".format(test_suite))
+            if len(tests) == 0:
+                yield test_suite
+
+    def find_non_manual_test_suites(self) -> Iterable[str]:
+        """Find test_suite targets in //... that are not tagged with 'manual'."""
+        query_pieces = [
+            BazelQuery.rule_exact("test_suite", "//..."),
+            "except",
+            BazelQuery.tag_exact("manual", "//..."),
+        ]
+        query_str = " ".join(query_pieces)
+        return self.query(query_str)
+
+    def query(self, query: str) -> List[str]:
+        """Perform a Bazel query and return the resulting targets."""
+        if self._backend:
+            return self._backend(query)
+
+        bazel = subprocess.run(
+            ["./bazelisk.sh", "query", "--output=label", query],
+            stdout=subprocess.PIPE,
+            stderr=subprocess.DEVNULL,
+            encoding='utf-8',
+            check=True)
+        stdout_lines = bazel.stdout.split('\n')
+        return [s for s in stdout_lines if s != ""]
diff --git a/ci/scripts/lib/bazel_query_test.py b/ci/scripts/lib/bazel_query_test.py
new file mode 100644
index 0000000..af98637
--- /dev/null
+++ b/ci/scripts/lib/bazel_query_test.py
@@ -0,0 +1,147 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import re
+import unittest
+from unittest.mock import Mock
+
+from bazel_query import BazelQuery, BazelQueryRunner
+
+
+class TestBazelQuery(unittest.TestCase):
+
+    def test_rule_exact(self):
+        query = BazelQuery.rule_exact("foo", "bar")
+        self.assertEqual(query, 'kind("^foo rule$", bar)')
+
+    def test_tag_exact(self):
+        query = BazelQuery.tag_exact("manual", "foo")
+        self.assertEqual(query, 'attr("tags", "[\\[ ]manual[,\\]]", foo)')
+
+    def test_regex_for_tag(self):
+        regex = BazelQuery.regex_for_tag("foo")
+        self.assertEqual(regex, '[\\[ ]foo[,\\]]')
+
+        # Regex doesn't match lists without "foo".
+        self.assertFalse(re.search(regex, "[]"))
+        self.assertFalse(re.search(regex, "[bar]"))
+        self.assertFalse(re.search(regex, "[bar, baz]"))
+
+        # Regex does not match superstrings of "foo".
+        self.assertFalse(re.search(regex, "[foobar]"))
+        self.assertFalse(re.search(regex, "[barfoo]"))
+        self.assertFalse(re.search(regex, "[foofoo]"))
+
+        # Regex matches "foo" in any position.
+        self.assertTrue(re.search(regex, "[bar, foo, baz]"))
+        self.assertTrue(re.search(regex, "[bar, foo]"))
+        self.assertTrue(re.search(regex, "[foo, baz]"))
+        self.assertTrue(re.search(regex, "[foo]"))
+
+
+class TestFindTargetsWithBannedChars(unittest.TestCase):
+
+    def test_no_test_suites(self):
+        backend = Mock()
+        backend.return_value = []
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_targets_with_banned_chars()
+        self.assertEqual(list(targets), [])
+
+    def test_one_target_empty_string(self):
+        """Bazel does not allow targets to have empty names.
+
+        This test simply documents how we respond to the impossible scenario.
+        """
+        backend = Mock()
+        backend.return_value = [""]
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_targets_with_banned_chars()
+        self.assertEqual(list(targets), [])
+
+    def test_only_good_chars(self):
+        backend = Mock()
+        backend.return_value = ["//foo:bar", "//bar_baz:foo"]
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_targets_with_banned_chars()
+        self.assertEqual(list(targets), [])
+
+    def test_only_bad_chars(self):
+        backend = Mock()
+        backend.return_value = ["!@#$", "^&*()", "\x01"]
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_targets_with_banned_chars()
+        self.assertCountEqual(list(targets), [
+            ("!@#$", set("!@#$")),
+            ("^&*()", set("^&*()")),
+            ("\x01", set("\x01")),
+        ])
+
+    def test_mixed(self):
+        backend = Mock()
+        backend.return_value = [
+            '!@#$', '\x01', '//foo:bar', '^&*()', '//bar_baz:foo'
+        ]
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_targets_with_banned_chars()
+        self.assertCountEqual(list(targets), [
+            ("!@#$", set("!@#$")),
+            ("\x01", set("\x01")),
+            ("^&*()", set("^&*()")),
+        ])
+
+
+class TestFindEmptyTestSuites(unittest.TestCase):
+
+    def test_empty(self):
+        backend = Mock()
+        backend.return_value = []
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_empty_test_suites()
+        self.assertEqual(list(targets), [])
+
+    def test_one_empty_suite(self):
+        query_result_sequence = [
+            ["//foo:some_test_suite"],  # List of test suites.
+            [],  # List of tests in the first test suite.
+        ]
+
+        def backend(_query):
+            return query_result_sequence.pop(0)
+
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_empty_test_suites()
+        self.assertEqual(list(targets), ["//foo:some_test_suite"])
+        self.assertEqual(query_result_sequence, [])
+
+    def test_second_suite_empty(self):
+        query_result_sequence = [[
+            "//foo:some_test_suite", "//foo:another_test_suite"
+        ], ["//foo:test"], []]
+
+        def backend(_query):
+            return query_result_sequence.pop(0)
+
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_empty_test_suites()
+        self.assertEqual(list(targets), ["//foo:another_test_suite"])
+        self.assertEqual(query_result_sequence, [])
+
+
+class TestFindNonManualTestSuites(unittest.TestCase):
+
+    def test_simple(self):
+        backend = Mock()
+        backend.return_value = ["//foo:bar"]
+
+        bazel = BazelQueryRunner(backend=backend)
+        targets = bazel.find_non_manual_test_suites()
+        self.assertEqual(list(targets), ["//foo:bar"])
+        backend.assert_called_once_with(
+            'kind("^test_suite rule$", //...) except attr("tags", "[\\[ ]manual[,\\]]", //...)'
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/ci/scripts/make_distribution.sh b/ci/scripts/make_distribution.sh
index 47a3b5e..c15f490 100755
--- a/ci/scripts/make_distribution.sh
+++ b/ci/scripts/make_distribution.sh
@@ -25,7 +25,8 @@
   'sw/device/*.bin'
   'sw/device/*.vmem'
   'hw/top_earlgrey/Vchip_earlgrey_verilator'
-  'hw/top_earlgrey/lowrisc_systems_chip_earlgrey_*.bit'
+  'hw/top_earlgrey/lowrisc_systems_chip_earlgrey_*.bit.*'
+  'hw/top_earlgrey/*.mmi'
 )
 
 DIST_DIR="$OBJ_DIR/$OT_VERSION"
@@ -37,6 +38,11 @@
 for pat in "${DIST_ARTIFACTS[@]}"; do
   echo "Searching for $pat." >&2
   found_file=false
+  # Looping over find output is a problem if our file paths have glob
+  # characters or spaces.  However, we do not have filenames like that in CI,
+  # and the alternative formulations of this for loop are less intuitive.
+
+  # shellcheck disable=SC2044
   for file in $(find "$BIN_DIR" -type f -path "$BIN_DIR/$pat"); do
     found_file=true
     relative_file="${file#"$BIN_DIR/"}"
diff --git a/ci/scripts/prepare-cached-bitstream.sh b/ci/scripts/prepare-cached-bitstream.sh
new file mode 100755
index 0000000..111ea53
--- /dev/null
+++ b/ci/scripts/prepare-cached-bitstream.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+# This script currently assumes it is used for chip_earlgrey_cw310.
+
+set -ex
+
+. util/build_consts.sh
+
+readonly TOPLEVEL=top_earlgrey
+readonly TOPLEVEL_BIN_DIR="${BIN_DIR}/hw/${TOPLEVEL}"
+readonly TARGETS=(
+  @bitstreams//:chip_earlgrey_cw310_bitstream
+  @bitstreams//:chip_earlgrey_cw310_rom_mmi
+  @bitstreams//:chip_earlgrey_cw310_otp_mmi
+)
+readonly BAZEL_OPTS=(
+  "--define"
+  "bitstream=gcp_splice"
+)
+
+BITSTREAM=HEAD ci/bazelisk.sh build "${BAZEL_OPTS[@]}" "${TARGETS[@]}"
+mkdir -p "${TOPLEVEL_BIN_DIR}"
+for target in "${TARGETS[@]}"; do
+  src="$(ci/scripts/target-location.sh "${target}" "${BAZEL_OPTS[@]}")"
+  dst="${TOPLEVEL_BIN_DIR}/$(basename "$(ci/scripts/target-location.sh "${target}")")"
+  cp -vL "${src}" "${dst}"
+done
diff --git a/ci/scripts/run-english-breakfast-verilator-tests.sh b/ci/scripts/run-english-breakfast-verilator-tests.sh
index 8e3a1ac..f5b1f7e 100755
--- a/ci/scripts/run-english-breakfast-verilator-tests.sh
+++ b/ci/scripts/run-english-breakfast-verilator-tests.sh
@@ -18,7 +18,9 @@
 ./hw/top_englishbreakfast/util/prepare_sw.py -b
 
 # Build some other dependencies.
-ci/bazelisk.sh build //sw/host/opentitantool //hw/ip/otp_ctrl/data:img_rma
+ci/bazelisk.sh build  \
+    --copt=-DOT_IS_ENGLISH_BREAKFAST_REDUCED_SUPPORT_FOR_INTERNAL_USE_ONLY_ \
+    //sw/host/opentitantool //hw/ip/otp_ctrl/data:img_rma
 
 # Run the one test.
 # This needs to be run outside the bazel sandbox, so we do not use `bazel run`
@@ -26,10 +28,9 @@
     --rcfile="" \
     --logging=info \
     --interface=verilator \
-    --conf=sw/host/opentitantool/config/opentitan_verilator.json \
     --verilator-bin=$BIN_DIR/hw/top_englishbreakfast/Vchip_englishbreakfast_verilator \
-    --verilator-rom=$(find bazel-out/* -name 'test_rom_sim_verilator.32.vmem') \
-    --verilator-flash=$(find bazel-out/* -name 'aes_smoketest_prog_sim_verilator.64.scr.vmem') \
+    --verilator-rom="$(find bazel-out/* -name 'test_rom_sim_verilator.32.vmem')" \
+    --verilator-flash="$(find bazel-out/* -name 'aes_smoketest_prog_sim_verilator.64.scr.vmem')" \
     console \
     --exit-failure="(FAIL|FAULT).*\n" \
     --exit-success="PASS.*\n" \
diff --git a/ci/scripts/run-fpga-cw310-tests.sh b/ci/scripts/run-fpga-cw310-tests.sh
index b11e970..50a8d69 100755
--- a/ci/scripts/run-fpga-cw310-tests.sh
+++ b/ci/scripts/run-fpga-cw310-tests.sh
@@ -7,29 +7,55 @@
 set -e
 
 . util/build_consts.sh
-readonly SHA=$(git rev-parse HEAD)
-readonly BIT_CACHE_DIR="${HOME}/.cache/opentitan-bitstreams/cache/${SHA}"
-readonly BIT_SRC_PREFIX="${BIN_DIR}/hw/top_earlgrey/lowrisc_systems_chip_earlgrey_cw310_0.1.bit"
-readonly BIT_DST_PREFIX="${BIT_CACHE_DIR}/lowrisc_systems_chip_earlgrey_cw310_0.1.bit"
 
-mkdir -p ${BIT_CACHE_DIR}
-for suffix in orig splice; do
-  cp "${BIT_SRC_PREFIX}.${suffix}" "${BIT_DST_PREFIX}.${suffix}"
-done
-echo -n ${SHA} > ${BIT_CACHE_DIR}/../../latest.txt
+SHA=$(git rev-parse HEAD)
+readonly SHA
+
+if [ $# == 0 ]; then
+    echo >&2 "Usage: run-fpga-cw310-tests.sh <cw310_tags>"
+    echo >&2 "E.g. ./run-fpga-cw310-tests.sh cw310_rom"
+    echo >&2 "E.g. ./run-fpga-cw310-tests.sh cw310_rom cw310_test_rom"
+    exit 1
+fi
+cw310_tags=("$@")
+
+# Copy bitstreams and related files into the cache directory so Bazel will have
+# the corresponding targets in the @bitstreams workspace.
+#
+# TODO(#13807) Update this when we change the naming scheme.
+readonly BIT_CACHE_DIR="${HOME}/.cache/opentitan-bitstreams/cache/${SHA}"
+readonly BIT_SRC_DIR="${BIN_DIR}/hw/top_earlgrey"
+readonly BIT_NAME_PREFIX="lowrisc_systems_chip_earlgrey_cw310_0.1.bit"
+mkdir -p "${BIT_CACHE_DIR}"
+cp "${BIT_SRC_DIR}/${BIT_NAME_PREFIX}.orig" \
+    "${BIT_SRC_DIR}/otp.mmi"  \
+    "${BIT_SRC_DIR}/rom.mmi" \
+    "${BIT_CACHE_DIR}"
+
+echo -n "$SHA" > "${BIT_CACHE_DIR}/../../latest.txt"
 export BITSTREAM="--offline --list ${SHA}"
 
 # We will lose serial access when we reboot, but if tests fail we should reboot
 # in case we've crashed the UART handler on the CW310's SAM3U
-trap 'python3 ./util/fpga/cw310_reboot.py' EXIT
+trap 'ci/bazelisk.sh run //sw/host/opentitantool -- --rcfile= --interface=cw310 fpga reset-sam3x' EXIT
 
-ci/bazelisk.sh test \
-    --define DISABLE_VERILATOR_BUILD=true \
-    --nokeep_going \
-    --test_tag_filters=cw310,-broken \
-    --test_output=all \
-    --build_tests_only \
-    --define cw310=lowrisc \
-    --flaky_test_attempts=2 \
-    $(./bazelisk.sh query 'rdeps(//...,@bitstreams//:bitstream_test_rom)') \
-    $(./bazelisk.sh query 'rdeps(//...,@bitstreams//:bitstream_mask_rom)')
+# In case tests update OTP or otherwise leave state on the FPGA we should start
+# by clearing the bitstream.
+# FIXME: #16543 The following step sometimes has trouble reading the I2C we'll
+# log it better and continue even if it fails (the pll is mostly correctly set
+# anyway).
+ci/bazelisk.sh run //sw/host/opentitantool -- --rcfile= --interface=cw310 --logging debug fpga set-pll || true
+ci/bazelisk.sh run //sw/host/opentitantool -- --rcfile= --interface=cw310 fpga clear-bitstream
+
+for tag in "${cw310_tags[@]}"; do
+    ci/bazelisk.sh test //... @manufacturer_test_hooks//...\
+        --define DISABLE_VERILATOR_BUILD=true \
+        --nokeep_going \
+        --test_tag_filters="${tag}",-broken,-skip_in_ci \
+        --test_timeout_filters=short,moderate \
+        --test_output=all \
+        --build_tests_only \
+        --define cw310=lowrisc \
+        --flaky_test_attempts=2
+
+done
diff --git a/ci/scripts/run-shellcheck.sh b/ci/scripts/run-shellcheck.sh
new file mode 100755
index 0000000..c9018ba
--- /dev/null
+++ b/ci/scripts/run-shellcheck.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -e
+
+# cd into $REPO_TOP
+cd "$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")"/../..
+
+EXCLUDED_DIRS="-name third_party -o -name vendor -o -name lowrisc_misc-linters"
+# Get an array of all shell scripts to check using input redirection and
+# process substitution. For details on this syntax, see ShellCheck SC2046.
+readarray -t shell_scripts < \
+    <(find . \( $EXCLUDED_DIRS \) -prune -o -name '*.sh' -print)
+shellcheck --severity=warning "${shell_scripts[@]}"
diff --git a/ci/scripts/run-verilator-tests.sh b/ci/scripts/run-verilator-tests.sh
index 96e11ab..3f9effa 100755
--- a/ci/scripts/run-verilator-tests.sh
+++ b/ci/scripts/run-verilator-tests.sh
@@ -7,8 +7,7 @@
 
 # Increase the test_timeout due to slow performance on CI
 
-./bazelisk.sh query "filter('sim_verilator\_.*\_smoketest', tests(//sw/device/...))" | \
-xargs ci/bazelisk.sh test \
+ci/bazelisk.sh test \
     --build_tests_only=true \
     --test_timeout=2400,2400,3600,-1 \
     --local_test_jobs=4 \
@@ -16,6 +15,9 @@
     --test_tag_filters=verilator,-broken \
     --test_output=errors \
     --//hw:verilator_options=--threads,1 \
+    --//hw:make_options=-j,4 \
+    //sw/device/tests:aes_smoketest_sim_verilator \
+    //sw/device/tests:uart_smoketest_sim_verilator \
     //sw/device/tests:crt_test_sim_verilator \
     //sw/device/tests:otbn_randomness_test_sim_verilator \
     //sw/device/tests:otbn_irq_test_sim_verilator \
@@ -29,4 +31,4 @@
     //sw/device/silicon_creator/lib/drivers:alert_functest_sim_verilator \
     //sw/device/silicon_creator/lib/drivers:watchdog_functest_sim_verilator \
     //sw/device/silicon_creator/lib:irq_asm_functest_sim_verilator \
-    //sw/device/silicon_creator/mask_rom:mask_rom_epmp_test_sim_verilator
+    //sw/device/silicon_creator/rom:rom_epmp_test_sim_verilator
diff --git a/ci/scripts/target-location.sh b/ci/scripts/target-location.sh
index 2a306fc..562ba28 100755
--- a/ci/scripts/target-location.sh
+++ b/ci/scripts/target-location.sh
@@ -6,11 +6,12 @@
 # Use Bazel to query for the location of targets instead of searching
 
 set -e
-readonly REPO_TOP=$(git rev-parse --show-toplevel)
+REPO_TOP=$(git rev-parse --show-toplevel)
+readonly REPO_TOP
 
 verbose='false'
 print_usage() {
-  printf "Usage: $0 [-v] <bazel target label>"
+  printf "Usage: $0 [-v] <bazel target label> [bazel options...]"
 }
 
 while getopts 'v' flag; do
@@ -29,5 +30,8 @@
   REDIR='/dev/null'
 fi
 
-readonly REL_PATH=$(${REPO_TOP}/bazelisk.sh cquery $@ --output starlark --starlark:file=${REPO_TOP}/rules/output.cquery 2>$REDIR)
-echo "${REPO_TOP}/${REL_PATH}"
+REL_PATH=$(${REPO_TOP}/bazelisk.sh outquery "$@" 2>$REDIR)
+readonly REL_PATH
+REPO_EXECROOT=$(${REPO_TOP}/bazelisk.sh info --show_make_env execution_root)
+readonly REPO_EXECROOT
+echo "${REPO_EXECROOT}/${REL_PATH}"
diff --git a/ci/scripts/test-airgapped-build.sh b/ci/scripts/test-airgapped-build.sh
new file mode 100755
index 0000000..4a5c3cb
--- /dev/null
+++ b/ci/scripts/test-airgapped-build.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -ex
+
+# Prefetch bazel airgapped dependencies.
+util/prep-bazel-airgapped-build.sh -f
+
+# Remove the airgapped network namespace.
+remove_airgapped_netns() {
+  sudo ip netns delete airgapped
+}
+trap remove_airgapped_netns EXIT
+
+# Set up a network namespace named "airgapped" with access to loopback.
+sudo ip netns add airgapped
+sudo ip netns exec airgapped ip addr add 127.0.0.1/8 dev lo
+sudo ip netns exec airgapped ip link set dev lo up
+
+# Enter the network namespace and perform several builds.
+sudo ip netns exec airgapped sudo -u "$USER" bash -c \
+  "export BAZEL_BITSTREAMS_CACHE=$(pwd)/bazel-airgapped/bitstreams-cache;
+  export BITSTREAM=\"--offline latest\";
+  export BAZEL_PYTHON_WHEELS_REPO=$(pwd)/bazel-airgapped/ot_python_wheels;
+  TARGET_PATTERN_FILE=\$(mktemp)
+  echo //sw/device/silicon_creator/... > \"\${TARGET_PATTERN_FILE}\"
+  bazel-airgapped/bazel cquery \
+    --distdir=$(pwd)/bazel-airgapped/bazel-distdir \
+    --repository_cache=$(pwd)/bazel-airgapped/bazel-cache \
+    --noinclude_aspects \
+    --output=starlark \
+    --starlark:expr='\"-{}\".format(target.label)' \
+    --define DISABLE_VERILATOR_BUILD=true \
+    -- \"rdeps(//..., kind(bitstream_splice, //...))\" \
+    >> \"\${TARGET_PATTERN_FILE}\"
+  echo Building target pattern:
+  cat \"\${TARGET_PATTERN_FILE}\"
+  bazel-airgapped/bazel build \
+    --distdir=$(pwd)/bazel-airgapped/bazel-distdir \
+    --repository_cache=$(pwd)/bazel-airgapped/bazel-cache \
+    --define DISABLE_VERILATOR_BUILD=true \
+    --target_pattern_file=\"\${TARGET_PATTERN_FILE}\""
+exit 0
diff --git a/ci/scripts/whitespace.sh b/ci/scripts/whitespace.sh
index 9de57b7..5995314 100755
--- a/ci/scripts/whitespace.sh
+++ b/ci/scripts/whitespace.sh
@@ -23,7 +23,9 @@
 echo "Checking whitespace on files changed since $merge_base"
 
 set -o pipefail
-git diff --name-only --diff-filter=ACMRTUXB "$merge_base" -- ':!*/vendor/*' | \
+git diff --name-only --diff-filter=ACMRTUXB "$merge_base" -- \
+        ':!third_party/rust/crates/*' \
+        ':!*/vendor/*' | \
     xargs -r util/fix_trailing_whitespace.py --dry-run || {
     echo -n "##vso[task.logissue type=error]"
     echo "Whitespace check failed. Please run util/fix_trailing_whitespace.py on the above files."
diff --git a/ci/upload-artifacts-template.yml b/ci/upload-artifacts-template.yml
index 986675b..573d985 100644
--- a/ci/upload-artifacts-template.yml
+++ b/ci/upload-artifacts-template.yml
@@ -12,10 +12,18 @@
 
 
 parameters:
-  # Rsync-style file patterns to include in the partial BIN_DIR output.
+  # Rsync-style file patterns to include in the partial BIN_DIR output. If a
+  # file is captured by these patterns, but it was previously downloaded via
+  # ci/download-artifacts-template.yml, it will not be uploaded again.
   - name: includePatterns
     type: object
     default: []
+  # Rsync-style file patterns to unconditionally include in the partial BIN_DIR
+  # output. If a file is captured by these patterns, it will be uploaded even if
+  # it came from ci/download-artifacts-template.yml.
+  - name: unconditionalIncludePatterns
+    type: object
+    default: []
 
 steps:
   - bash: |
@@ -23,15 +31,24 @@
       test -n "$BUILD_ROOT"
       . util/build_consts.sh
 
-      # Write all include patterns to a file used by rsync.
-      echo "${{ join('\n', parameters.includePatterns) }}" > "$BUILD_ROOT/include_patterns.txt"
+      # Write all sets of include patterns to files used by rsync.
+      echo -e "${{ join('\n', parameters.includePatterns) }}" > "$BUILD_ROOT/include_patterns.txt"
+      echo -e "${{ join('\n', parameters.unconditionalIncludePatterns) }}" > "$BUILD_ROOT/unconditional_include_patterns.txt"
 
       echo
-      echo Files matching these patterns will be included in the binary build artifact for this job:
+      echo Files matching these patterns will be included in the binary build
+      echo artifact for this job unless they were downloaded from an upstream job:
       echo vvvvvvvvvvvvvvvvvv
       cat "$BUILD_ROOT/include_patterns.txt"
       echo ^^^^^^^^^^^^^^^^^^
 
+      echo
+      echo Files matching these patterns will always be included in the binary
+      echo build artifact for this job:
+      echo vvvvvvvvvvvvvvvvvv
+      cat "$BUILD_ROOT/unconditional_include_patterns.txt"
+      echo ^^^^^^^^^^^^^^^^^^
+
       # The file upstream_bin_dir_contents.txt lists all files which were part
       # of an "upstream" BIN_DIR which got downloaded at the beginning of this
       # job. Ensure that this file exists, even if no upstream BIN_DIR was
@@ -49,6 +66,7 @@
         --verbose \
         --remove-source-files \
         --prune-empty-dirs \
+        --include-from="$BUILD_ROOT/unconditional_include_patterns.txt" \
         --exclude-from="$BUILD_ROOT/upstream_bin_dir_contents.txt" \
         --include="*/" \
         --include-from="$BUILD_ROOT/include_patterns.txt" \
diff --git a/doc/_index.md b/doc/_index.md
deleted file mode 100644
index 277ea46..0000000
--- a/doc/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Documentation Index"
----
-
-{{% sectionContent %}}
diff --git a/doc/contributing/README.md b/doc/contributing/README.md
new file mode 100644
index 0000000..ac8f089
--- /dev/null
+++ b/doc/contributing/README.md
@@ -0,0 +1,60 @@
+# Contributing to OpenTitan
+
+Thank you for your interest in contributing to OpenTitan.
+This document provides some guidelines to making those contributions.
+Important points before getting started:
+* We consider honest feedback crucial to quality.
+  We work hard to thoroughly review changes and provide actionable feedback.
+  We do this to ensure a high quality open source design.
+* Always assume good intent.
+  Our feedback may be demanding and may even feel disheartening.
+  Again, this is to support a high quality silicon design, and we definitely appreciate all OpenTitan contributions.
+* Please be friendly and patient in your communications.
+* All OpenTitan interactions are covered by [lowRISC's code of conduct](https://www.lowrisc.org/code-of-conduct/).
+* When communicating, remember OpenTitan is a security-focused project.
+  Because of this, certain issues may need to be discussed in a small group first.
+  See the [Security Issues Process](#security-issues) described below for more details.
+* OpenTitan involves both hardware and software.
+  We follow a hybrid approach involving both silicon and software design practices.
+* OpenTitan is a work in progress.
+  We are always looking for ways to improve and welcome feedback on any project matter, technical or not.
+
+**Important**: Please read the next three, short sections on reporting bugs, reporting security issues, and contributing code in preparation for making your first contribution to OpenTitan.
+If you would like more details, see the [Detailed Contribution Guide](./detailed_contribution_guide/README.md).
+
+## Bug reports
+
+**To report a security issue, please follow the [Security Issues Process](#security-issues)**.
+
+Ideally, all designs are bug free.
+Realistically, each piece of collateral in our repository is in a different state of maturity with some still under active testing and development.
+See the [Hardware Development Stages](../project_governance/development_stages.md) for an example of how hardware progress is tracked.
+
+We are happy to receive bug reports and eager to fix them.
+Please make reports by opening a new issue in our [GitHub issue tracker](https://github.com/lowRISC/opentitan/issues).
+
+## Security issues
+
+Security is of major importance to the OpenTitan project.
+When dealing with security matters, and in keeping with standard industry practice, there are reasons why it makes sense to be cautious and have a non-public discussion within a small group of experts before full disclosure.
+For example,
+* to ensure responsible disclosure of vulnerabilities,
+* or to discuss the security impact of new features or proposed changes to an existing feature.
+
+If you believe you have found a security issue or intend to work on potentially security sensitive matters, please first reach out to our experienced security team at security@opentitan.org before starting a public discussion.
+That will enable us to engage successfully without creating undue risk to the project or its consumers.
+
+## Contributing code
+
+The information below aims at helping you get involved in the OpenTitan project by guiding you through our process of preparing your contribution and getting it integrated.
+
+For straight-forward and non-invasive contributions, a high level of coordination is unlikely to be necessary.
+In these cases, please open a pull request.
+
+For larger proposed changes we ask contributors to:
+* Discuss the matter with the team, either through the [opentitan-dev@opentitan.org](https://groups.google.com/a/opentitan.org/forum/#!forum/opentitan-dev) mailing list or through discussions in issues on GitHub.
+  Agree on a course of action and document this in a GitHub issue.
+* Implement the contribution, i.e., the solution previously agreed on, and reference the discussion when submitting the contribution.
+* Have the implementation reviewed by the team, address any feedback, and finally have it integrated into the project.
+
+Note that contributions must be accompanied by sign-off text which indicates acceptance of the project's Contributor License Agreement - see [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md) for details.
diff --git a/doc/contributing/bazel_notes.md b/doc/contributing/bazel_notes.md
new file mode 100644
index 0000000..92cc105
--- /dev/null
+++ b/doc/contributing/bazel_notes.md
@@ -0,0 +1,378 @@
+# Bazel Notes
+
+Both OpenTitan hardware and software is built with Bazel.
+While our [Getting Started](https://docs.opentitan.org/doc/guides/getting_started/) guides detail some of the Bazel commands that can be used to build both types of artifacts, below are detailed notes on:
+* how Bazel is configured for our project, and
+* brief examples of Bazel commands that are useful for:
+    * querying,
+    * building, and
+    * running tests with Bazel.
+
+# OpenTitan Bazel Workspace
+
+The rules for Bazel are described in a language called Starlark, which looks a lot like Python.
+
+The `$REPO_TOP` directory is defined as a Bazel workspace by the `//WORKSPACE` file.
+`BUILD` files provide the information Bazel needs to build the targets in a directory.
+`BUILD` files also manage any subdirectories that don't have their own `BUILD` files.
+
+OpenTitan uses .bzl files to specify custom rules to build artifacts that require specific attention like on-device test rules and project specific binaries.
+
+## WORKSPACE file
+
+The `WORKSPACE` file controls external dependencies such that builds can be made reproducible and hermetic.
+Bazel loads specific external dependencies, such as various language toolchains.
+It uses them to build OpenTitan targets (like it does with bazel\_embedded) or to satisfy dependencies (as it does with abseil).
+To produce increasingly stable releases the external dependencies loaded in `WORKSPACE` file attempts to fix a all external `http_archive`s to a specific SHA.
+As we add more dependencies to the workspace, builds and tests will become less sensitive to external updates, and we will vastly simplify the [Getting Started](https://docs.opentitan.org/doc/guides/getting_started/) instructions.
+
+## BUILD files
+
+Throughout the OpenTitan repository, `BUILD` files describe targets and dependencies in the same directory (and subdirectories that lack their own `BUILD` files).
+`BUILD` files are mostly hand-written.
+To maintain the invariant that hand-written files not be included in autogen directories, there are `BUILD` files that describe how to build and depend on auto-generated files in autogen subdirectories.
+
+# General Commands
+- Build everything (software and Verilator hardware):
+  ```console
+  bazel build //...
+  ```
+- Build and run all tests (on-host tests, and Verilator/FPGA on-device tests):
+  ```console
+  bazel test --test_tag_filters=-broken,-dv //sw/...
+  ```
+  Note: this will take several hours.
+- Clean all build outputs and reclaim all disk and memory traces of a Bazel instance:
+  ```console
+  bazel clean --expunge
+  ```
+  Note: you should rarely need to run this, see [below](#troubleshooting-builds) for when this may be necessary.
+
+# Locating Build Artifacts
+
+When Bazel builds a target, or the dependencies of a target, it places them in a Bazel-managed output directory.
+This can make them difficult to find manually, however, Bazel also provides a mechanism to query for built artifacts that is demonstrated below.
+Note, the `outquery-*` command shown below is a special command that is parsed via our `bazelisk.sh` script.
+Therefore, it cannot be run with a standalone `bazel ...` invocation.
+
+## `opentitan_{rom,flash}_binary` Artifacts
+
+- Query the locations of all Bazel-built artifacts for all OpenTitan devices for an `opentitan_{rom,flash}_binary` macro:
+  ```console
+  ./bazelisk.sh outquery-all <target>
+  ```
+- Query the locations of all Bazel-built artifacts for a specific OpenTitan device for an `opentitan_{rom,flash}_binary` macro:
+  ```console
+  ./bazelisk.sh outquery-all <target>_<device>
+  ```
+  Note: `<device>` will be in {`sim_dv`, `sim_verilator`, `fpga_cw310`}.
+
+See [Building (and Testing) Software](../guides/getting_started/src/build_sw.md#device-artifacts), device software can be built for multiple OpenTitan devices and memories, using OpenTitan-specific Bazel macros.
+
+## `opentitan_functest` Artifacts
+
+As described [Building (and Testing) Software](../guides/getting_started/src/build_sw.md#device-artifacts), device software can be built for multiple OpenTitan devices and memories, using OpenTitan-specific Bazel macros.
+Since running tests on multiple OpenTitan devices (whether DV or Verilator simulation, or an FPGA) involves building several software images for multiple memories, we provide a Bazel macro for this.
+This macro is called `opentitan_functest`.
+
+- List all `sh_test` targets instantiated by a `opentitan_functest`, e.g. the UART smoketest:
+  ```console
+  bazel query 'labels(tests, //sw/device/tests:uart_smoketest)'
+  ```
+- Query the HW and SW dependencies of a specific `opentitan_functest` for the `fpga_cw310` device, e.g. the UART smoketest:
+  ```console
+  bazel query 'labels(data, //sw/device/tests:uart_smoketest_fpga_cw310)'
+  ```
+  or for any `opentitan_functest` target and `<device>` in {`sim_dv`, `sim_verilator`, `fpga_cw310`}
+  ```console
+  bazel query 'labels(data, <target>_<device>)'
+  ```
+- Query the software artifacts built for the `opentitan_flash_binary` that is a dependency of an `opentitan_functest` for the `fpga_cw310` device, e.g. the UART smoketest:
+  ```console
+  bazel query 'labels(srcs, //sw/device/tests:uart_smoketest_prog_fpga_cw310)'
+  ```
+  or for any `opentitan_functest` `<target>` and `<device>` in {`sim_dv`, `sim_verilator`, `fpga_cw310`}
+  ```console
+  bazel query 'labels(srcs, <target>_prog_<device>)'
+  ```
+  Note: if an `opentitan_functest` target has the name `foo`, then the `opentitan_flash_binary` target that is instantiated by the `opentitan_functest` will be named `foo_prog_<device>`.
+
+# Building Software
+
+* To build OpenTitan software see [here](../guides/getting_started/src/build_sw.md#building-software-with-bazel), or run
+  ```console
+  bazel build <target>
+  ```
+
+# Testing Software
+
+## On-Host Tests
+
+* To query-for/run *on-host* software tests see [here](../guides/getting_started/src/build_sw.md#running-tests-with-bazel).
+
+## On-Device Tests
+
+All device software, regardless of the device, is built with Bazel.
+However, only Verilator simulation and FPGA device software tests can be run with Bazel.
+
+### ROM Tests
+* Query for all ROM functional and E2E tests for FPGA:
+  ```console
+  bazel query 'filter(".*_fpga_cw310", kind(".*test rule", //sw/device/silicon_creator/...))'
+  ```
+  and for Verilator:
+  ```console
+  bazel query 'filter(".*_sim_verilator", kind(".*test rule", //sw/device/silicon_creator/...))'
+  ```
+* Run all ROM functional and E2E tests on FPGA:
+  ```console
+  bazel test --test_tag_filters=cw310 //sw/device/silicon_creator/...
+  ```
+  and for Verilator:
+  ```console
+  bazel test --test_tag_filters=verilator //sw/device/silicon_creator/...
+  ```
+* Run a single ROM functional or E2E test on FPGA and see the output in real time:
+  ```console
+  bazel test \
+    --define DISABLE_VERILATOR_BUILD=true \
+    --test_tag_filters=cw310 \
+    --test_output=streamed \
+    //sw/device/silicon_creator/lib:boot_data_functest
+  ```
+  or, remove the define/filtering flags and just append the `<device>` name like:
+  ```console
+  bazel test --test_output=streamed //sw/device/silicon_creator/lib:boot_data_functest_fpga_cw310
+  ```
+  and similarly for Verilator:
+  ```console
+  bazel test --test_output=streamed //sw/device/silicon_creator/lib:boot_data_functest_sim_verilator
+  ```
+
+### Chip-Level Tests
+* Query for all chip-level tests for FPGA:
+  ```console
+  bazel query 'filter(".*_fpga_cw310", kind(".*test rule", //sw/device/tests/...))'
+  ```
+  and for Verilator:
+  ```console
+  bazel query 'filter(".*_sim_verilator", kind(".*test rule", //sw/device/tests/...))'
+  ```
+* Run all chip-level tests on FPGA:
+  ```console
+  bazel test --define DISABLE_VERILATOR_BUILD=true --test_tag_filters=cw310 //sw/device/tests/...
+  ```
+  and for Verilator:
+  ```console
+  bazel test --test_tag_filters=verilator //sw/device/tests/...
+  ```
+* Run a single chip-level test on FPGA and see the output in real time:
+  ```console
+  bazel test \
+    --define DISABLE_VERILATOR_BUILD=true
+    --test_tag_filters=cw310 \
+    --test_output=streamed \
+    //sw/device/tests:uart_smoketest
+  ```
+  or, remove the define/filtering flags and just append the `<device>` name like:
+  ```console
+  bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
+  ```
+  and similarly for Verilator:
+  ```console
+  bazel test --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator
+  ```
+
+# Linting Software
+
+There are several Bazel rules that enable running quality checks and fixers on code.
+The subsections below describe how to use them.
+All of the tools described below are run in CI on every pull request, so it is best to run them before committing code.
+
+## Linting C/C++ Code
+The OpenTitan supported linter for C/C++ files is `clang-format`.
+It can be run with Bazel as shown below.
+
+Run the following to check if all C/C++ code as been formatted correctly:
+```console
+bazel test //quality:clang_format_check --test_output=streamed
+```
+and run the following to fix it, if it is not formatted correctly.
+```console
+bazel run //quality:clang_format_fix
+```
+
+## Linting Starlark
+
+The OpenTitan supported linter for Bazel files is `buildifier`.
+It can be run with Bazel as shown below.
+
+Run the following to check if all `WORKSPACE`, `BUILD`, and `.bzl` files have been formatted correctly:
+```console
+bazel test //quality:buildifier_check --test_output=streamed
+```
+and run the following to fix them, if they are not formatted correctly.
+```console
+bazel run //quality:buildifier_fix
+```
+
+## Checking License Headers
+
+Lastly, the OpenTitan supported linter for checking that every source code file contains a license header may be run with:
+```console
+bazel run //quality:license_check --test_output=streamed
+```
+
+# Building Hardware
+
+Note, to run (software) tests on these OpenTitan hardware platforms **does not** require these Bazel commands be invoked before the test commands above.
+Bazel is aware of all dependency relationships, and knows what prerequisites to build to run a test.
+
+- Build FPGA bitstream with (test) ROM, see [here](../guides/getting_started/src/setup_fpga.md#build-an-fpga-bitstream).
+- Build FPGA bitstream with (production) ROM, see [here](../guides/getting_started/src/setup_fpga.md#build-an-fpga-bitstream).
+- Build Verilator simulation binary:
+  ```console
+  bazel build //hw:verilator
+  ```
+
+# Miscellaneous
+
+## Troubleshooting Builds
+
+If you encounter an unexplained error building or running any `bazel` commands, you can issue a subsequent `bazel clean` command to erase any existing building directories to yield a clean build.
+Specifically, according to the Bazel [documentation](https://docs.bazel.build/versions/main/user-manual.html#clean), issuing a
+```console
+bazel clean
+```
+deletes all the output build directories, while running a
+```console
+bazel clean --expunge
+```
+will wipe all disk and memory traces (i.e., any cached intermediate files) produced by Bazel.
+The latter sledgehammer is only intended to be used as a last resort when the existing configuration is seriously broken.
+
+## Create a `.bazelrc` File
+
+Create a `.bazelrc` file in your home directory to simplify executing bazel commands.
+For example, you can use a `.bazelrc` to:
+* set up a [disk cache](#disk-cache), or
+* skip running tests on the CW310 FPGA if you don not have one.
+
+A `.bazelrc` file that would accomplish this would look like:
+```
+# Make Bazel use a local directory as a remote cache.
+build --disk_cache=~/bazel_cache
+
+# Skip CW310 FPGA tests, since I do not have said FPGA.
+test --test_tag_filters=-cw310
+```
+
+See the [`.bazelrc`](https://github.com/lowRISC/opentitan/blob/master/.bazelrc) file in the OpenTitan repository for more examples.
+Additionally, for more information see the Bazel [documentation](https://bazel.build/run/bazelrc).
+
+### Site-specific `.bazelrc-site` options
+
+We use the term "compute site" to refer to a particular development host, or more broadly a _collection_ of compute nodes, that is operated by an organization with its own unique compute requirements or IT policies.
+The experience of working in such an environment may be different than using an off-the-shelf Linux distribution, and so team members working on some compute sites may require the use of slightly different Bazel options.
+
+In addition to each user's `$HOME/.bazelrc` file and the project-level configurations in [`$REPO_TOP/.bazelrc`](https://github.com/lowRISC/opentitan/blob/master/.bazelrc), there is the option to add site-specific configuration options, by adding them to the file `$REPO_TOP/.bazelrc-site`.
+
+The `.bazelrc-site` file can be useful for enforcing site-specific policies such as:
+- Default locations for build artifacts (e.g., new defaults for `--output_user_root` or perhaps `--output_base` options, outside the `$HOME` filesystem).
+- Site-specific, non-standard paths for build-tools, libraries, or package configuration data.
+
+Putting Bazel options in this file adds another layer of configuration to help groups of individuals working on a common compute site share the same site-specific default options.
+
+At a more fine-grained level, individual users can still place options in `$HOME/.bazelrc`, potentially overriding the options used in `.bazelrc-site` and `$REPO_TOP/.bazelrc`.
+However the "$HOME/.bazelrc" file is not specific to OpenTitan, which could create confusion for users working on a number of projects.
+
+The policies and paths for each contributing site could vary greatly.
+So in order to avoid accidental conflicts between sites, the `.bazelrc-site` file is explicitly _not_ included in the git repository and OpenTitan CI scripts will reject any commit that includes a `.bazelrc-site` file.
+
+## Disk Cache
+
+Bazel can use a directory on the file system as a remote cache.
+This is useful for sharing build artifacts across multiple [`git` worktrees](https://git-scm.com/docs/git-worktree) or multiple workspaces of the same project, such as multiple checkouts.
+
+Use the `--disk_cache=<filename>` to specify a cache directory.
+For example, running
+```console
+bazel build //... --disk_cache=~/.cache/bazel-disk-cache
+```
+will cache all built artifacts.
+Alternatively add the following to `$HOME/.bazelrc` to avoid having automatically use the disk cache on every Bazel invocation, as shown [above](#create-a-bazelrc-file).
+
+Note that Bazel does not perform any garbage collection on the disk cache.
+To clean out the disk cache, you can set a cron job to periodically delete all files that have not been accessed for a certain amount of time.
+For example add the following line with the path to your disk cache to your crontab (using `crontab -e`) to delete all files that were last accessed over 60 days ago.
+
+```console
+0 0 * * 0 /usr/bin/find /path/to/disk/cache -type f -atime +60 -delete
+```
+
+For more documentation on Bazel disk caches see the [official documentation](https://docs.bazel.build/versions/main/remote-caching.html#disk-cache).
+
+## Excluding Verilator Simulation Binary Builds
+
+Many device software targets depend on the Verilator simulation binary,
+The Verilator simulation binary is slow to build.
+To avoid building it, use the
+`--define DISABLE_VERILATOR_BUILD=true` build option.
+For example, to build the UART smoke test artifacts but not the Verilator simulation binary run
+```console
+bazel build --define DISABLE_VERILATOR_BUILD=true //sw/device/tests:uart_smoketest
+```
+
+## Displaying Test Outputs in Real Time
+
+By default, Bazel does not write test outputs to STDOUT.
+To see a test's output in real time, use the `--test_output=streamed` flag, like:
+```console
+bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
+```
+
+## Filtering Broken Tests
+
+Some tests are marked known to be broken (and are in the process of being triaged).
+To prevent running these tests when running a block of tests, use the `test_tag_filters=-broken` flag.
+For example, to run all chip-level tests except the broken ones on FPGA:
+```console
+bazel test --test_tag_filters=cw310,-broken //sw/device/tests/...
+```
+
+## Using Bazel with Git Worktrees
+
+Bazel was not optimized for the `git` worktree workflow, but using worktrees can help with branch management and provides the advantage of being able to run multiple Bazel jobs simultaneously.
+Here are some tips that can improve the developer experience when using worktrees.
+
+1. Follow the [instructions above](#disk-cache) to enable the disk cache.
+  Bazel uses the workspace's path when caching actions.
+  Because each worktree is a separate workspace at a different path, different worktrees cannot share an action cache.
+  They can, however, share a disk cache, which helps avoid rebuilding the same artifacts across different worktrees.
+  Note that the repository cache is shared by default across all workspaces, so no additional configuration is needed there.
+1. Before deleting a worktree, be sure to run `bazel clean --expunge` to remove Bazel's generated files.
+  Otherwise, files from old worktrees can accumulate in your output user root (located at `~/.cache/bazel/_bazel_${USER}/` by default).
+
+## Commonly Encountered Issues
+
+### Cannot find ld
+
+One issue encountered while using bazel is the following error message when attempting to build:
+```console
+  = note: collect2: fatal error: cannot find 'ld'
+          compilation terminated.
+```
+
+The reason this occurs is related to these issues:
+* [opentitan issue](https://github.com/lowRISC/opentitan/issues/12448)
+* [rust issue](https://github.com/bazelbuild/rules_rust/issues/1114)
+
+Specifically, when the rustc compiler is invoked, it uses the LLVM linker that is not managed by the bazel flow.
+This means bazel cannot ensure it is installed at a specific location, and instead just uses whatever is available on the host machine.
+The issue above points out that rustc expects the linker to be located in the same directory as `gcc`, so if on the host machine this statement is untrue, there can be build issues.
+
+To resolve this problem:
+1. Install the LLVM linker with, e.g., `sudo apt install lld`.
+2. Symlink `lld` to where `gcc` is installed.
+
+This workaround will be needed until the rust issue is resolved.
diff --git a/doc/contributing/ci/README.md b/doc/contributing/ci/README.md
new file mode 100644
index 0000000..7e56b3e
--- /dev/null
+++ b/doc/contributing/ci/README.md
@@ -0,0 +1,83 @@
+# OpenTitan Continuous Integration
+
+All changes to the OpenTitan source code are tested thoroughly in a continuous integration system.
+Tests run automatically when changes are proposed for inclusion by submitting a pull request, and on the `master` branch after changes are merged.
+This ensures that the OpenTitan source code meets certain quality criteria at all points in time, as defined by the tests which are executed.
+
+Read on to learn more about the types of tests, and the infrastructure that runs these tests.
+
+## How to report CI problems
+
+If you detect CI failures which look like they might not be related to the tested code, but the test infrastructure, please file an [issue on GitHub](https://github.com/lowRISC/opentitan/issues).
+In urgent cases also reach out on Slack and send an email to lowRISC IT at [internal-tech@lowrisc.org](mailto:internal-tech@lowrisc.org).
+Note that lowRISC is based in the UK and most active during European business hours.
+
+## Overview
+
+<!--
+Source: https://docs.google.com/drawings/d/1-Zjm3k2S0TNmne3F9z3rpTFJfLJJvvmrBAsfx_HG5lk/edit
+
+Download the SVG from Google Draw, open it in Inkscape once and save it without changes to add width/height information to the image.
+-->
+![CI Overview](continuous_integration_overview.svg)
+
+OpenTitan uses [Azure Pipelines](https://azure.microsoft.com/en-gb/services/devops/pipelines/) as continuous integration provider: test jobs are described in an Azure Pipelines-specific way, and then executed on compute resources, some of which are provided by Azure Pipelines, and others of which are provided by lowRISC.
+
+Two things are special in the way OpenTitan does continuous integration: private CI, and testing on FPGA boards.
+
+"Private CI" is a term we use for a subset of test jobs which require tighter access control.
+The primary use case for private CI are tests using proprietary EDA tools, where the license agreement prevents us from testing arbitrary code with it, from showing the configuration or the output in public, etc.
+We run such test jobs in a separate environment where only OpenTitan project members have access.
+The test result (pass/fail) is still shared publicly to enable outside contributors to at least get some feedback if their pull request passed our tests.
+
+To test OpenTitan (both the hardware and the software) on FPGAs we have various FPGA boards connected to a machine at lowRISC.
+Azure Pipelines is configured to schedule test jobs on this machine when FPGA testing is required.
+The results and logs of these test runs are shown publicly.
+
+## Azure Pipelines projects
+
+OpenTitan CI uses two Azure DevOps projects (which Azure Pipelines is a part of):
+
+* https://dev.azure.com/lowrisc/opentitan/ for public CI.
+* https://dev.azure.com/lowrisc/opentitan-private/ for private CI.
+
+## Test descriptions
+
+All tests are described in a Azure Pipelines-specific YAML syntax.
+`$REPO_TOP/azure-pipelines.yml` is the main configuration file for all public CI jobs.
+The private CI jobs are described in a separate private repository, [lowrisc/opentitan-private-ci](https://github.com/lowRISC/opentitan-private-ci), to keep the job descriptions internal for legal reasons.
+
+The [YAML schema](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema) is part of the Azure Pipelines documentation.
+
+## Compute resources: agent pools and agents
+
+Each test in the YAML file also specifies which type of compute resource it wants to run on.
+Identical compute resources are grouped into *agent pools*, and an individual compute resource is called an *agent*.
+
+For OpenTitan, we have the following agent pools available, which can also be seen in the [Azure Pipelines UI](https://dev.azure.com/lowrisc/opentitan/_settings/agentqueues):
+* The *Azure Pipelines* pool is a Microsoft-provided pool of VMs which are free of charge for us.
+  They are described in more detail in the [Azure Pipelines documentation](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted).
+* The *ci-public* pool has a lowRISC-specific setup with tools such as Xilinx Vivado installed, but has no access to tools with special license restrictions.
+* The *ci-eda* pool has proprietary EDA tools installed and access to the respective licenses.
+* The *FPGA* pool currently consists of a single machine with our FPGA boards connected to it.
+
+All pools except for the *Azure Pipelines* pool are managed by lowRISC IT.
+
+All agents provide ephemeral test environments: the test environment is initialized at the start of a test job and completely destroyed at the end.
+This is achieved by running tests in Docker containers which are recreated after each run.
+The base image used for all lowRISC-hosted agent pools is available [as lowrisc/container-ci-eda on DockerHub](https://hub.docker.com/r/lowrisc/container-ci-eda).
+(The build rules/Dockerfile for this image are lowRISC-internal.)
+
+lowRISC-provided agents run in a Kubernetes cluster on Google Cloud (GCP), where we also define the resources allocated for the individual agents.
+The agent pools are static in size, i.e. the number of agents doesn't increase and decrease depending on the number of scheduled jobs.
+
+## Job scheduling, build triggers and status reporting
+
+Builds are triggered by GitHub, which sends notifications to Azure Pipelines on a range of events, e.g. the creation of new pull requests, the merge of code into a branch, etc.
+
+The Azure Pipelines scheduler consumes these events and compares them with the configured pipeline triggers in the `azure-pipelines.yml` file.
+It then processes the pipeline description and adds test jobs to the respective agent pool queues, taking test dependencies into account.
+
+After the agent has completed a test job it reports back the result to the Azure Pipelines scheduler, which makes this information (build artifacts and logs) available to users through the web UI.
+
+Azure Pipelines also reports the test status back to GitHub, where it is displayed below a pull request, as marks next to commits, and in various other places.
diff --git a/doc/rm/continuous_integration/continuous_integration_overview.svg b/doc/contributing/ci/continuous_integration_overview.svg
similarity index 100%
rename from doc/rm/continuous_integration/continuous_integration_overview.svg
rename to doc/contributing/ci/continuous_integration_overview.svg
diff --git a/doc/contributing/detailed_contribution_guide/README.md b/doc/contributing/detailed_contribution_guide/README.md
new file mode 100644
index 0000000..bcf9c81
--- /dev/null
+++ b/doc/contributing/detailed_contribution_guide/README.md
@@ -0,0 +1,339 @@
+# In-depth guide to contributing to OpenTitan
+
+The way we work on OpenTitan is very similar to what is done in other collaborative open-source projects.
+For a brief overview see [Contributing to OpenTitan](../README.md).
+This document provides a detailed reference on how we collaborate within the OpenTitan project and is organized as follows:
+* [Communication](#communication)
+* [Working with Issues](#working-with-issues)
+* [Overview on Contributing Code](#overview-on-contributing-code)
+* [Writing Code](#writing-code)
+* [Working with pull requests (PRs)](#working-with-pull-requests)
+
+# Communication
+
+All OpenTitan interactions, on all platforms and face to face, are covered by lowRISC's [code of conduct](https://www.lowrisc.org/code-of-conduct/).
+We believe in creating a welcoming and respectful community, so we have a few ground rules we expect contributors to follow:
+
+* be friendly and patient,
+* be welcoming,
+* be considerate,
+* be respectful,
+* be careful in the words that you choose and be kind to others, and
+* when we disagree, try to understand why.
+
+We list these principles as general guidance to make it easier to communicate and participate in the OpenTitan (and lowRISC) community.
+
+## When to file an issue vs. sending an email vs. creating a document?
+
+GitHub issues and shared Google Docs are generally preferable to email, as these can be more easily tracked, cross-referenced, archived and shared, e.g., with people joining later.
+
+Emails (e.g. to the [opentitan-dev@opentitan.org](https://groups.google.com/a/opentitan.org/forum/#!forum/opentitan-dev) mailing list) are suitable to raise awareness of discussions and to call for participation.
+Emails between members of a smaller group are also useful for preliminary evaluations before starting a public issue or document.
+
+When it comes to technical discussions, either shared documents on Google Docs or GitHub issues may be used.
+The former are more suitable for initial, broader discussions, for comparing different options and for soliciting comments from a wider audience on a proposal over a longer period of time, whereas the latter are more suitable for cross-referencing in pull requests, and for presenting the final proposal.
+The outcome of such discussions should always be summarized in a GitHub issue for later reference.
+
+## Where do we discuss implementation details/proposals before creating PRs?
+
+A shared Google Doc is suitable for initial, broader discussions, for comparing different design options and for a wider audience and agile commenting, but not for revisioning, referencing and storage in the repository.
+Therefore, such a shared document should always be linked from a GitHub issue and the outcome of this discussion should be summarized in that issue.
+For short proposals, the entire discussion can be had in a GitHub issue, without a linked document.
+
+## How to/why use Google Docs?
+
+Collaborative documents are more useful than GitHub issues for initial, broader discussions, for comparing different design options and for a wider audience commenting on a proposal over a longer period of time, or when interactive editing is required.
+We often make use of a Google Doc to start the discussion of an idea or proposal, before later converting it to Markdown and moving to GitHub (e.g. as a PR adding new documentation).
+
+## When to assign issues or request specific reviewers?
+
+We typically rely on contributors self-assigning to an issue when they start working on it, so it is best to leave issues unassigned when creating them.
+If you think a particular issue might be relevant for someone, you can bring it to their attention by mentioning the username in a comment.
+Team members are regularly scanning through open issues and will also help ensure issues are brought to the attention of the right people.
+
+For PRs, you should always request reviews unless the PR is work-in-progress or just in draft state (in these cases, please explicitly label the PR as a [draft](https://github.blog/2019-02-14-introducing-draft-pull-requests/)).
+In many cases, there will be a default review assignment depending on the files modified in the PR.
+You are also strongly encouraged to ask for reviews from anyone you know to be working in that area.
+
+# Working with Issues
+
+## Labeling and assigning issues
+
+Issue and PR labels can be useful as they help categorizing, prioritizing and assigning open issues/PRs.
+When filing a new issue, you are welcome to assign labels if you feel comfortable about selecting suitable labels.
+If no labels are assigned, committers and other team members might do that when browsing through the open issues/PR from time to time.
+If possible, you should also add a meaningful tag to the subject line of your issue as outlined in [subject tags](#subject-tags).
+
+In this project, we use the assignee field to indicate people who are actively working on an issue, so it's best to let people assign themselves when they start working on it.
+If you'd like to bring an issue to the attention of a particular contributor or ask if they might be able to help solve it, simply mention them in a comment.
+
+## Who should respond to filed issues?
+
+Anyone who feels qualified to help, though if you're working outside your usual area of expertise and making a best-effort guess it's polite to say so.
+If you see an issue that might be of relevance for a particular team member, you are welcome to ping this person by commenting on the issue mentioning that person (*e.g.* "@*githubname* what do you think?").
+
+## How to reference issues in commit messages and PRs?
+
+Commits and PRs related to existing issues or PRs should reference those.
+References should always be accompanied by a brief summary of the outcome of the referenced discussion and how the current PR/commit/issue relates to that.
+References are not only relevant for automatically closing issues but also and especially for documentation.
+
+Both long references such as "lowRISC/opentitan#1" and short references "#1" can be used in commit messages.
+However, short references should not be used in the subject line as they might accidentally create links to unrelated issues if commits containing such references end up in other repositories, e.g., when vendoring in external sources.
+
+See also [References and URLs on GitHub](https://help.github.com/en/github/writing-on-github/autolinked-references-and-urls).
+
+# Overview on Contributing Code
+
+Depending on the size and impact of your contribution, it may need to go through the OpenTitan RFC process.
+If your contribution is large, cross cutting, or potentially contentious, we will ask you to follow the procedure documented in the [Request for Comments (RFC) Process](../../project_governance/rfc_process.md).
+
+If you believe an RFC is not required, a lighter weight process will suffice.
+Note that if a core committer suggests that a contributor follow the RFC process, you will likely need to do so.
+The lightweight process is:
+
+![Code contribution process flowchart](contributing_code_flow_chart.svg)
+
+1. Check if an issue or PR addressing the same matter already exists.
+   If so, consider contributing to the existing issue/PR instead of opening a new one.
+2. Assess whether the matter may be security sensitive.
+   If so, follow the [[Security Issues Process](../README.md#security-issues).
+3. [Create a GitHub issue](#working-with-issues) to raise awareness, start the discussion, and build consensus that the issue needs to be addressed.
+   For more information, refer to [Communication](#communication).
+4. Start discussing possible solutions in a smaller group, possibly outside of GitHub, but in a shared document (we typically use Google Docs for convenience) that is linked to the original GitHub issue.
+   Find consensus inside the interest group and come up with a proposal.
+   For short proposals, the entire discussion can be had in a GitHub issue, without a linked document.
+   For more information, refer to [Communication](#communication).
+5. Summarize the outcome of the discussion or the proposed solution in the original GitHub issue.
+   For bug fixes, proceed to Step 8.
+6. Share the proposal with the wider team and collect feedback.
+   For more information, refer to [Communication](#communication).
+7. If the feedback is negative, go back to Step 4.
+   If agreement with the wider team cannot be found even after a second or third iteration, consider starting the [Request for Comments (RFC) Process](../../project_governance/rfc_process.md).
+8. Start implementation and prepare a first PR.
+   Create the PR from a fork rather than making new branches in `github.com/lowRISC/opentitan`.
+   Reference the GitHub issue in your PR description and commit messages (see also [Working with Issues](#working-with-issues), [Writing Code](#writing-code)).
+   To simplify and speed up code review, we expect larger contributions like new features and major changes to be broken into multiple, smaller PRs wherever possible.
+   For more information refer to [Working with PRs](#working-with-pull-requests).
+
+Further information can be found in [Getting Started with a Design](../hw/design.md) and in [Request for Comments (RFC) Process](../../project_governance/rfc_process.md).
+
+# Writing Code
+
+## Licensing
+
+The main license used by OpenTitan is the Apache License, Version 2.0, marked by the following license header in all source files:
+
+    // Copyright lowRISC contributors.
+    // Licensed under the Apache License, Version 2.0, see LICENSE for details.
+    // SPDX-License-Identifier: Apache-2.0
+
+Do not attempt to commit code with a non-Apache license without discussing first.
+
+## Coding style guides, formatting tools
+
+All source code contributions must adhere to project style guides.
+We use separate style guides for different languages:
+* [SystemVerilog](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
+* [C/C++](../style_guides/c_cpp_coding_style.md)
+* [Python](../style_guides/python_coding_style.md)
+* [Markdown](../style_guides/markdown_usage_style.md)
+* [Hjson](../style_guides/hjson_usage_style.md)
+* [RISC-V Assembly](../style_guides/asm_coding_style.md)
+
+If unsure about the style, be consistent with the existing code and do your best to match its style.
+
+For C/C++ code, we recommend to run "git-clang-format" on your changes.
+
+## What should commit messages look like?
+
+There are many [useful guides](https://chris.beams.io/posts/git-commit/) available on how to write commit messages and why.
+
+The following list contains a set of simple guidelines that we consider most useful.
+* A commit message consists of a subject line followed by an optional body.
+* Keep the subject line short. Ideally under 50 characters.
+* Capitalize the subject line.
+* Use the imperative mood in the subject line.
+* Use the present tense ("Add feature" not "Added feature").
+* When changes are restricted to a specific area, you should add a tag to the start of the subject line in square brackets, e.g. "\[uart/rtl\] Rework Tx FSM". See also [subject tags](#subject-tags).
+* Do not end the subject line with a period.
+* Separate subject from body with a blank line.
+* Wrap the body at 72 characters.
+* Use the body to explain what and why (rather than how).
+
+To indicate acceptance of our Contributor License Agreement (CLA), commits should also include a Signed-off-by line (which can be generated by `git commit -s`).
+See [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md) for more information.
+
+## Subject tags
+
+If code changes, issues or pull requests (PRs) are restricted to a specific area, we recommend adding a tag in square brackets, *e.g.*, "\[uart/rtl\]", to the subject line of your commit message, issue or PR.
+Such tags help to identify areas affected by changes.
+Tags are particularly helpful when dealing with pull requests (PRs) as they can guide team members for code review and simplify later retrieval of specific PRs through the GitHub web interface.
+
+You can add multiple tags separated by a comma, e.g., "\[top, dv\]" to indicate that multiple separated areas are affected.
+In addition, you can add further levels of hierarchy to make the indication more precise such as "\[uart/rtl\]" instead of just "\[uart\]", "\[sw/device\]" instead of "\[sw\]".
+
+## What about TODO comments?
+
+We track non-trivial pieces of work via GitHub issues rather than TODO comments in the code.
+We do not use TODO(name) comments as used in some code bases - if it is important enough to assign someone, it is worth tracking via an issue.
+
+If you as a code reviewer come across TODOs, you should encourage the author to create a GitHub issue to track the work item, which can be referenced using a comment such as `// TODO(lowrisc/<project_name>#<issue_number>): <brief description>`.
+Code authors are encouraged to use TODO rather than FIXME.
+
+## How are auto-generated files treated?
+
+We leverage a set of utilities to automatically generate parts of the source code based on configuration files and templates.
+Examples for auto-generated RTL code include the register interface of the peripheral IP cores (using the register tool reggen), the top level (using topgen) and the main TL-UL crossbar (using tlgen).
+Auto-generated source files are typically checked into the OpenTitan repository.
+This allows people to build the system without having to invoke the utilities.
+
+To prevent people from editing auto-generated top-level files, these files both contain a header marking them as such and they live in subfolders called `autogen/`.
+When implementing changes that affect auto-generated files, the configuration or template file must be edited, the auto-generated files need to be regenerated and everything needs to be committed to the repository.
+
+## Vendored Code
+
+Not all source code included in the OpenTitan repository has been
+developed as part of the project.
+OpenTitan also leverages code from other repositories (e.g. [Ibex](https://github.com/lowrisc/ibex) or code from third parties released under compatible open-source licenses.
+The process of incorporating such code into the OpenTitan repository, which besides creating a copy may also involve the application of patches, is managed through the "vendor" script.
+
+The "vendored" code is usually found in a vendor subdirectory such as `hw/vendor`.
+It should never be modified directly in the OpenTitan repository.
+Instead, modifications should be discussed and implemented upstream and then be vendored into OpenTitan.
+However, when dealing with third-party code, please first raise awareness and discuss inside OpenTitan before creating issues and pull requests upstream.
+
+Further information regarding vendor code can be found in the [Vendor Tool User Guide](../hw/vendor.md)
+
+
+# Working with Pull Requests
+
+## What should a PR look like (single/multiple commits)
+
+A pull request (PR) should contain all the code changes required to address an issue, or all the code changes leading to the implementation of a new feature or a major change in the design.
+At the same time, a PR should be as small as possible to simplify and speed up the review process.
+It may thus make sense to break down larger contributions into multiple, self-contained PRs.
+This does not hold for bug fixes: A single PR is sufficient to fix a bug.
+
+Independent of how a contribution is structured, each individual commit should be atomic.
+The code base should remain usable even with only the first commit of a PR, or the first PR of a series of PRs, merged.
+
+Code changes not strictly related to the target feature, major change or issue should go into a separate PR.
+
+In some cases, preparatory code changes may be required before a change is implemented, or a new feature may require changes in a somewhat unrelated part of the code.
+For example, it may be the case that the register interface of an IP must be updated using the latest version of the register tool, before a new register required for the targeted feature can be added.
+Such changes, if small, can be in the same PR, but they should be in a separate commit with a reasonable commit message explaining why this change is necessary.
+
+PRs with more, smaller commits instead of a few large commits simplifies debugging in the case of regressions.
+
+For more detailed information on how to submit a pull request, see our [GitHub notes](../github_notes.md).
+
+## What are committers?
+
+Committers are the only people in the project that can definitively approve contributions for inclusion.
+
+See the [Committers](../../project_governance/committers.md) definition and role description for a fuller explanation.
+
+## Labeling and assigning PRs
+
+Labels can be useful as they help categorizing, prioritizing and assigning open issues/PRs.
+When creating a PR, you are welcome to assign labels if you feel comfortable about selecting suitable labels.
+If no labels are assigned, committers and other team members might do that when browsing through the open PR from time to time.
+If possible, you should also add a meaningful tag to the subject line of your PR as outlined in [subject tags](#subject-tags).
+
+For PRs, it does not make sense to assign people.
+But when creating a PR, you should request a review from committers and team members.
+Some reviewers may be assigned by default depending on the paths of changed files.
+To balance the review load, you are welcome to also request a review from other people familiar in the corresponding area.
+However, you should not request a review from more than 2 to 5 team members, as this increases the review load to unsustainable levels.
+
+## Who should review PRs?
+
+Contributors should request reviews from:
+* people regularly working on the affected parts of the code, and
+* people involved in discussing the solution of the problem addressed by the PR.
+
+However, review requests are not obligations to perform a review.
+It is understood that reviewers may not be able to review every single PR where a review has been requested by them.
+
+## How to give good code reviews?
+
+Code review is not design review and doesn't remove the need for discussing implementation options.
+If you would like to make a large-scale change or discuss multiple implementation options, follow the procedure outlined under [How to Contribute Code](#how-to-contributed-code).
+
+The main purpose of code review is to make sure the code changes appropriately solve the problem the PR intends to address.
+It is thus vital to provide meaningful commit messages and PR descriptions with references to issues where problems and the proposed solutions are outlined.
+
+Code review is a core part of OpenTitan's focus on high quality implementation.
+It helps ensure that:
+* The design follows good practices.
+* The code matches the style guides.
+* The code is appropriately commented.
+* Code changes are reflected in the documentation.
+* The changes are reasonably structured.
+* Meaningful commit messages are provided.
+
+When doing code review, you should check if these guidelines are followed.
+If some of the points are not met, or if you see potential improvements in the modified areas, bring this up in a comment.
+The author of the PR will then address or comment on all feedback.
+It is generally the responsibility of the reviewer who started the discussion to decide whether the feedback has been addressed appropriately.
+If the author and the reviewer cannot find agreement, another committer or team member should be brought into the discussion.
+
+When reviewing code and giving/discussing feedback, keep in mind that:
+* Code review is not design review.
+  If a PR comes without clear motivation or does not follow a course of action previously agreed on, this should be discussed first and separately from the actual code review.
+* This is a collaborative, distributed open-source project bringing lots of people together with different motivations and backgrounds.
+  Some people work full-time on this project and might see the same mistakes repeatedly while others can only contribute in their spare time.
+  This means that many people will see the same issue from different viewpoints.
+  Always be friendly and patient and remember to adhere to our [code of conduct](https://www.lowrisc.org/code-of-conduct/).
+
+See also: [reviewing pull requests on GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/reviewing-proposed-changes-in-a-pull-request) and the [OpenTitan Commit Escalation Guidelines](../../project_governance/committers.md#commit-escalation-guidelines" >}}).
+
+## How to receive a code review?
+
+Code review may be an intimidating process, especially if you're new to the open source community.
+Getting lots of comments on even minor style violations can be disheartening.
+Know that your contributions are always appreciated - the reality is that in many cases initial code review is the most scrutiny a given piece of code will get.
+Therefore, it is worth pushing to ensure it meets all coding standards rather than hoping it will be fixed in the future.
+
+## Do we rebase and merge or squash multiple commits into a single one?
+
+Feedback addressed during the review process should go into the corresponding commit in the PR instead of having a single commit containing all changes addressing the feedback.
+Try to keep a clean, linear history.
+This means no merge commits and no long series of "fixup" patches.
+Instead, use rebase to restructure your work as a series of logically ordered, atomic patches (`git rebase -i` helps).
+PRs should not be squashed when merging to keep the individual commits including commit messages making up the PR.
+
+Also see our [GitHub notes](../github_notes.md).
+
+
+## When to merge a PR after it has been approved?
+
+To reduce the risk of accidentally introducing bugs or breaking existing functionality, we leverage continuous integration (CI) checks on every PR in addition to nightly regression tests.
+Depending on the target area of the PR, these automated CI checks can include checking code format and lint rules, checking commit metadata, building and running simulation environments, generating an FPGA bitstream, compilation and execution of smoke tests on an FPGA emulator.
+The results of these CI checks can be viewed through the GitHub web interface (check the conversation pane of the PR of interest).
+
+In case the CI checks do not succeed, it is not okay to self-approve the PR or merge it.
+PRs addressing CI failures themselves may be exempted from this rule.
+However, such PRs are rare and they must always involve senior project committers.
+
+If the CI checks for a PR succeed and if the PR affects larger parts of the code base, it is best to wait 24 hours before merging.
+This allows project contributors spread across their many time zones an opportunity to provide feedback.
+
+## Who should merge the PR once it is approved?
+
+If the author of the PR has commit access, they should merge it once it has been reviewed and approved.
+Otherwise, a committer will need to merge it.
+Normally a committer involved in the review will do this.
+Note that irrespective of who merges the PR, the original authorship of the commit is preserved.
+
+## What to do if a PR gets merged and breaks things?
+
+The `master` branch should always be stable.
+If a PR is merged that causes breakage that is likely to prevent others from making progress (e.g. breaking simulation or CI infrastructure), this failing PR should be [reverted](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/reverting-a-pull-request) unless the required fix is obvious and trivial.
+We see two primary advantages to a quick-revert policy:
+* It brings the master branch back into a stable state.
+  This means it immediately unblocks other people affected by the problems introduced with the failing PR and allows them to continue their work.
+* It removes time pressure for finding the right fix.
+  This allows us to track down and fix the root cause of the failure, including getting proper review.
+  It also prevents the creation of dirty hotfixes, which are possibly incomplete and lead to other failures requiring follow-up patches themselves.
diff --git a/doc/project/detailed_contribution_guide/contributing_code_flow_chart.svg b/doc/contributing/detailed_contribution_guide/contributing_code_flow_chart.svg
similarity index 100%
rename from doc/project/detailed_contribution_guide/contributing_code_flow_chart.svg
rename to doc/contributing/detailed_contribution_guide/contributing_code_flow_chart.svg
diff --git a/doc/contributing/directory_structure.md b/doc/contributing/directory_structure.md
new file mode 100644
index 0000000..227785e
--- /dev/null
+++ b/doc/contributing/directory_structure.md
@@ -0,0 +1,140 @@
+# Directory Structure
+
+This document provides an overview of the opentitan repository directory structure.
+The hierarchy underneath the root is fairly self explanatory, containing the following:
+* `doc`: High level documentation, user guides and reference manuals
+* `util`: Helper scripts and generator tools
+* `hw`: Design and DV sources
+* `sw`: All software sources used in the project
+
+We will focus on the directory structure underneath `hw` and `sw` below.
+
+## Directory structure underneath `hw`
+```
+hw
+├──dv             => Common / shared resources for SV/UVM as well as
+│                    Verilator based testbenches
+│
+├──formal         => Scripts to build and run formal property verification
+│                    (FPV) for all applicable IPs to ensure protocol
+│                    compliance
+│
+├──ip             => Standalone or generic / parameterized implementations
+│                    of comportable IPs designed towards building SoCs
+│
+├──lint           => Scripts to run the `lint` tool on all RTL sources
+│
+├──top_earlgrey   => An implementation of OpenTitan SoC built using above
+│                    IPs as well as third-party 'vendored' IPs
+│
+├──vendor         => Vendored-in open source IPs from external repositories
+```
+
+### `hw/ip`
+```
+   ip
+   ├──uart        => UART IP root dir
+   │  │
+   │  ├──data     => Configuration data sources for design, DV and project
+   │  │              status tracking
+   │  │
+   │  ├──doc      => All documentation sources including design specification
+   │  │              and DV document
+   │  │
+   │  ├──dv       => SV/UVM testbench sources
+   │  │
+   │  ├──fpv      => Testbench sources used in FPV (if applicable)
+   │  │
+   │  ├──model    => Reference 'model' implementation in C (if applicable)
+   │  │
+   │  ├──pre_dv   => This is a work area for experimental DV work that is worthy of
+   │  │              maintaining, but not intended to apply towards code coverage.
+   │  │
+   │  ├──rtl      => RTL design sources
+   │  │
+   │  ├──util     => IP-specfic automation scripts (if applicable)
+   │  │
+   │  ├──...      => Additional sub-directories could exist for specific IPs
+   │                 based on need
+   │
+   ├──...         => More such Comportable IPs...
+```
+
+### `hw/top_earlgrey`
+```
+   top_earlgrey         => Chip root dir
+   │
+   ├──data              => Configuration data sources for design, DV and
+   │                       project status tracking
+   │
+   ├──doc               => All documentation sources including chip
+   │                       specification and DV document
+   │
+   ├──dv                => Chip level SV/UVM testbench sources
+   │  └──autogen        => auto-generated chip DV sources
+   │
+   ├──ip                => IPs tailor-made for top_earlgrey
+   │  │
+   │  ├──xbar           => XBAR implementation for top_earlgrey
+   │  │  ├──dv          => DV sources
+   │  │  │  └──autogen  => auto-generated XBAR DV sources
+   │  │  └──rtl         => RTL sources
+   │  │     └──autogen  => Auto-generated XBAR RTL sources
+   │  │
+   │  ├──...            => More such IPs tailored for top_earlgrey...
+   │
+   ├──rtl               => Chip level RTL sources
+   │  └──autogen        => auto-generated chip RTL sources
+   │
+   ├──sw                => Auto-generated chip-specific headers for SW
+   │
+   └──util              => Chip-specfic automation scripts
+```
+
+### Auto-generated sources: checked-in
+In cases where we rely on automatic generation of RTL, DV, or software sources we currently check those files in to the repository.
+This is primarily motivated by a desire to make it easy for engineers to rapidly test spot-fixes.
+This is a decision that might be revisited in the future.
+
+#### Mitigating issues
+Auto-generated sources can get out-of-sync if the underlying tools or templates are updated.
+Also, users may accidentally make modifications to those files by hand, which will cease to exist the next time the tools are run.
+We employ the following methods to mitigate these risks:
+* Add a CI check when a pull request is made to merge new changes to ensure that the checked-in file and the generator output are equivalent and not out-of-sync
+* Put auto-generated sources under a dedicated `autogen/` directory
+* Add a warning message banner as a comment clearly indicating that the file has been auto-generated with the complete command
+
+## Directory structure underneath `sw`
+```
+sw
+├──device                 => Sources compiled for the OpenTitan chip,
+│  │                         including tests run on FPGA and simulations
+│  │
+│  ├──benchmarks          => Standard benchmarks and instructions for running
+│  │   └──coremark           them
+│  │
+│  ├──examples            => Example programs to demonstrate basic
+│  │   ├──hello_world        functionality
+│  │   ├──...
+│  │
+│  ├──exts                => Sources that are specific to the intended target
+│  │   │                     (FPGA, Verilator, DV, firmware)
+│  │   └──common          => Common sources for all SW tests including the CRT
+│  │                         source and the linker script
+│  │
+│  ├──lib                 => SW libraries, including device interface functions
+│  │                         (DIFs), testutils, test boot ROM (test_rom), the
+│  │                         OTTF, and other base APIs for controlling the
+│  │                         hardware
+│  │
+│  └──tests               => SW tests implemented on FPGA/Verilator/DV targets
+│     ├──flash_ctrl
+│     ├──...
+│
+├──host                   => Sources compiled for the host communicating with
+│                            our OpenTitan chip
+│
+└──vendor                 => Vendored-in open source software sources from
+    │                        external repositories
+    └── cryptoc
+```
diff --git a/doc/contributing/dv/methodology/README.md b/doc/contributing/dv/methodology/README.md
new file mode 100644
index 0000000..ea0853a
--- /dev/null
+++ b/doc/contributing/dv/methodology/README.md
@@ -0,0 +1,638 @@
+# Design Verification Methodology within OpenTitan
+
+Verification within OpenTitan combines the challenges of industry-strength verification methodologies with open source ambitions.
+When in conflict, quality must win, and thus we aim to create a verification product that is equal to the quality required from a full production silicon chip tapeout.
+
+For the purpose of this document, each design (IPs or the full chip) verified within the OpenTitan project will be referred to as the 'design under test' or 'DUT' ('RTL' or 'design' may be used interchangeably as well), and the design verification as 'DV'.
+
+## Language and Tool Selection
+
+The following are the key techniques used to perform design verification within OpenTitan:
+
+*  Dynamic simulations of the design with functional tests
+*  Formal Property Verification (FPV)
+
+For running dynamic simulations, the strategy is to use the [UVM1.2 methodology](https://www.accellera.org/downloads/standards/uvm) on top of a foundation of SystemVerilog based verification to develop constrained-random functional tests.
+Each DUT will include within the repository, a UVM testbench, a [testplan](./README.md#testplan), overall [DV document](./README.md#dv-document), a suite of tests, and a method to build, run tests and report the current status.
+For FPV, some DUTs may also include an SV testbench along with design properties captured in the SystemVerilog Assertions (SVA) language.
+As the project is still in development, the current status will not be completed for all IP, but that is the ultimate goal.
+See discussion below on tracking progress.
+
+For professional tooling, the team has chosen [Synopsys' VCS](https://www.synopsys.com/verification/simulation/vcs.html) as the simulator of choice with respect to the tracking of verification completeness and [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.html) for FPV.
+Wherever possible we attempt to remain tool-agnostic, but we must choose a simulator as our ground truth for our own confidence of signoff-level assurances.
+Likewise, for FPV, [Synopsys VC Formal](https://www.synopsys.com/verification/static-and-formal-verification/vc-formal.html) is also supported within the same flow, and can be used by those with access to VC Formal licenses.
+At this time there is also some support for Cadence's Xcelium, for those organizations which have few Synopsys VCS licenses.
+However support is not as mature as for VCS, which remains the tool for signoff.
+Furthermore, as a project we promote other open source verification methodologies - Verilator, Yosys, cocoTB, etc - and work towards a future where these are signoff-grade.
+The discussions on how those are used within the program are carried out in a different user guide.
+
+## Defining Verification Complete: Stages and Checklists
+
+Verification within the OpenTitan project comes in a variety of completion status levels.
+Some designs are "tapeout ready" while others are still a work in progress.
+Understanding the status of verification is important to gauge the confidence in the design's advertised feature set.
+To that end, we've designated a spectrum of design and verification stages in the  [OpenTitan Hardware Development Stages](../../../project_governance/development_stages.md) document.
+It defines the verification stages and references where one can find the current verification status of each of the designs in the repository.
+Splitting the effort in such a way enables the team to pace the development effort and allows the progress to be in lock-step with the design stages.
+The list of tasks that are required to be completed to enable the effort to transition from one stage to the next is defined in the [checklists](../../../project_governance/checklist/README.md) document.
+Verification is said to be complete when the checklist items for all stages are marked as done.
+We will explain some of the key items in those checklists in the remainder of this document.
+
+## Documentation
+
+DV effort needs to be well documented to not only provide a detailed description of what tests are being planned and functionality covered, but also how the overall effort is strategized and implemented.
+The first is provided by the **testplan** document and the second, by the **DV document**.
+The [**project status**](../../../project_governance/development_stages.md#indicating-stages-and-making-transitions) document tracks to progression of the effort through the stages.
+
+In addition to these documents, a nightly **regression dashboard** tabulating the test and coverage results will provide ability to track progress towards completion of the verification stages.
+
+To effectively document all these pieces, there are some key tooling components which are also discussed briefly below.
+
+### Testplan
+
+The testplan consist of two parts:
+- A testplan that captures at a high level, a list of tests that are being planned to verify all design features listed in the design specification.
+- A functional coverage plan that captures at high level a list of functional coverage points and coverage crosses needed to verify that the features listed in the design specification is tested by the list of tests.
+
+The testplan is written in Hjson format and is made available in the corresponding `data` directory of each DUT.
+
+The Hjson schema enables this information to be human-writable and machine-parseable, which facilitates an automated and documentation-driven DV effort.
+The testplan is parsed into a data structure that serves the following purposes:
+
+*  Provide the ability to insert the testplan and coverage plan as tables into the DV document itself, so that all of the required information is in one place
+*  Annotate the nightly regression results to allow us to track our progress towards executing the testplan and coverage collection
+
+The [testplanner](../../../../util/dvsim/README.md) tool provides some additional information on the Hjson testplan anatomy and some of the features and constructs supported.
+The [build_docs](../../../guides/getting_started/src/build_docs.md) tool works in conjunction with the `testplanner` tool to enable its insertion into the DV document as a table.
+
+### DV document
+
+The DV document expands the testplan inline, in addition to capturing the overall strategy, intent, the testbench block diagram, a list of interfaces / agents, VIPs, reference models, the functional coverage model, assertions and checkers. It also covers FPV goals, if applicable.
+This is written in [Markdown](../../style_guides/markdown_usage_style.md) and is made available in the corresponding `doc` directory of each DUT.
+
+A [template](https://github.com/lowRISC/opentitan/blob/master/hw/dv/doc/dv_doc_template.md) for the DV documentation as well as the testbench block diagram in the OpenTitan team drive  (under the 'design verification' directory) are available to help get started.
+
+### Regression Dashboard
+
+The DV document provides a link to the latest [nightly](#nightly) regression and coverage results dashboard uploaded to the web server.
+This dashboard contains information in a tabulated format mapping the written tests to planned tests (in the testplan) to provide ability to track progress towards executing the testplan.
+
+## Automation
+
+We rely on automation wherever possible, to avoid doing repetitive tasks manually.
+With this in mind, there are three key areas where the whole testbench, or part of it is auto-generated.
+These are described below.
+
+### Initial UVM Testbench Generation
+
+As is the case with design, we strive for conformity in our verification efforts as well.
+The motivation for this is not just aesthetics, but also to reap the advantages of [code reuse](#code-reuse), which we rely heavily on.
+To help achieve this, we provide a verification starter tool-kit called [uvmdvgen](../../../../util/uvmdvgen/README.md).
+It can be used to completely auto-generate the complete initial DV environment for a new DUT, including the [documentation](#documentation) pieces (testplan as well as DV document), the complete UVM environment including the testbench, to the collaterals for building and running tests along with some common tests.
+This significantly helps reduce the development time.
+It can also be used to auto-generate the initial skeleton source code for building a new reusable verification component for an interface (a complete UVM agent).
+
+### UVM Register Abstraction Layer (RAL) Model
+
+The UVM RAL model for DUTs containing CSRs is auto-generated using the [reggen](../../../../util/reggen/doc/setup_and_use.md) tool.
+The specification for capturing the CSRs in the Hjson format can be found in the [Register Tool](../../../../util/reggen/README.md) documentation.
+We currently check-in the auto-generated UVM RAL model along with our UVM testbench code and rely on CI checks for consistency.
+In the future we may move to a flow where it is not checked into the repository, but auto-generated on-the-fly as a part of the simulation.
+
+### Testbench Automation
+
+For a parameterized DUT that may possibly have multiple flavors instantiated in the chip, it would be prohibitively difficult to manually maintain the DV testbenches for all those flavors.
+To cater to this, we develop a generic UVM testbench and rely on custom tooling to auto-generate the specific parameter sets that are required to undergo the full verification till signoff.
+<!-- TODO: have this point to TLUL DV document -->
+An effort of this sort is planned for verifying the [TileLink XBAR](../../../../hw/ip/tlul/README.md).
+
+## Code Reuse
+
+SystemVerilog / UVM is structured to make code highly reusable across different benches.
+To that end, several commonly used verification infrastructure pieces are provided to aid the testbench development, which are discussed below.
+
+### DV Base Library
+
+We provide an elementary scaffolding / base layer for constructing UVM testbenches via a [DV base library](../../../../hw/dv/sv/dv_lib/README.md) of classes to help us get off the ground quickly.
+Most, if not all, testbenches in OpenTitan (whether developed for a comportable IP or not) extend from this library, which provides a common set of features.
+A UVM testbench feature (stimulus / sequence, checking logic or functional coverage element) that is generic enough to be applicable for use in all testbenches is a valid candidate to be added to the DV base library.
+By doing so, we improve synergies across our testbenches and reduce the overall development effort & time to market.
+The features are discussed in more detail in the document referenced above.
+The actual UVM testbenches for some of the IPs extend from this library as the final layer.
+
+### Comportable IP DV Library
+
+Defining a common ground to develop all OpenTitan IPs as described in the [Comportable IP specification](../../hw/comportability/README.md) provides us an excellent path to maximize code reuse and shorten the testbench development time even further.
+In view of that, we provide the [Comportable IP DV library](../../../../hw/dv/sv/cip_lib/README.md) of classes, which themselves extend from DV base library to form the second layer.
+These provide a common set of DV features that are specific to Comportable IPs.
+The actual UVM testbenches for the Comportable IPs extend from this library as the third and the final layer.
+
+### Common Verification Components
+
+In addition to the above library of classes, there are several common plug-and-play verification components (a.k.a. universal verification components or UVCs) provided for use in testbenches at `hw/dv/sv` location.
+A few examples of these are as follows:
+
+*  [Common interfaces](../../../../hw/dv/sv/common_ifs/README.md)
+*  [DV utilities](../../../../hw/dv/sv/dv_utils/README.md)
+*  [CSR utilities](../../../../hw/dv/sv/csr_utils/README.md)
+*  [Device memory model](../../../../hw/dv/sv/mem_model/README.md)
+*  Interface agents
+  *  [TileLink agent](../../../../hw/dv/sv/tl_agent/README.md)
+  *  [UART agent](../../../../hw/dv/sv/usb20_agent/README.md)
+
+This is not an exhaustive list since we are still actively developing and adding more such components as we speak.
+Please navigate to the above code location to find more sure UVCs.
+
+## DV Efforts in OpenTitan
+
+The overall OpenTitan DV effort is spread across 3 tiers:
+
+*  IP level DV
+*  Core (Ibex) level DV
+*  Chip level DV
+
+### IP Level DV
+
+IP level DV testbenches are small and provide fine grained control of stimulus and corner case generation.
+Tests at this level run relatively quickly and development cycles are shorter.
+Coverage closure is more intensive since there are typically no pre-verified sub-modules.
+To achieve our coverage goals, we take a constrained random approach to generate the stimulus.
+The DV environment models the behavior of the DUT more closely to perform checks (typically within the scoreboard or via SVAs) independently of the stimulus.
+In some IPs, specifically the ones that provide cryptographic functions, we also use open source third party C libraries as reference models to check the behavior of the DUT through DPI-C calls.
+
+Each of the IP level DV environments are described in further detail within their own [DV document](#dv-document).
+To find all of them, please navigate to this [landing page](../../../../hw/README.md).
+The [UART DV document](../../../../hw/ip/uart/dv/README.md) documentation can be used as a reference.
+
+### Core Ibex Level DV
+
+The RISC-V CPU core Ibex used in OpenTitan has its own DV testbench and it is verified to full coverage closure.
+Please see the [Ibex DV documentation](https://ibex-core.readthedocs.io/en/latest/03_reference/verification.html) for more details.
+
+### Chip Level DV
+
+The chip level DV effort is aimed at ensuring that all of the IPs are integrated correctly into the chip.
+For IPs that are pre-verified sub-modules, we perform [integration testing](#integration-testing).
+These are simple functional tests written in C which are cross-compiled and run natively on the Ibex core.
+The software compilation flow to enable this is explained in further detail in the [Building Software](../../../guides/getting_started/src/build_sw.md) document.
+Further, there is a mechanism for the C test running on the CPU to signal the SystemVerilog testbench the test pass or fail indication based on the observed DUT behavior.
+We also provide an environment knob to 'stub' the CPU and use a TL agent to drive the traffic via the CPU's data channel instead, in cases where more intensive testing is needed.
+<!-- TODO: add link to chip DV document -->
+The chip DV document, which is currently under active development will explain these methodologies and flows in further detail.
+
+## Key Test Focus Areas
+
+When progressing through the verification stages, there are key focus areas or testing activities that are perhaps common across all DUTs.
+These are described under [Hardware Verification Stages](../../../project_governance/development_stages.md#hardware-verification-stages-v).
+The basic steps are:
+
+### [Progressing towards V1](../../../project_governance/checklist/README.md#v1)
+
+These set of tests (not exhaustive) provide the confidence that the design is ready for vertical integration.
+
+#### Basic Smoke Test
+
+At this stage, just a skeleton testbench environment is available and most components lack functionality.
+A basic sanity test drives the clock, brings the DUT out of reset, checks if all outputs are legal values (not unknown) and exercise a major datapath with simple set of checks.
+This paves the way for more complex testing.
+During the testplan and the DV document review, the key stake holders at the higher level who consume the DUT as an IP (for example, design and DV engineers working at the chip level into which the IP is integrated) may drive the requirements for the level of testing to be done.
+This test (or set of tests) is also included as a part of the sanity regression to maintain the code health.
+
+#### CSR Suite of Tests
+
+The very first set of real tests validate the SW interface laid out using the regtool.
+These prove that the SW interface is solid and all assumptions in CSRs in terms of field descriptions and their accessibility are correctly captured and there are no address decode bugs.
+
+### [Progressing towards V2](../../../project_governance/checklist/README.md#v2)
+
+Bulk of testing in this stage focus on functionally testing the DUT.
+There however are certain categories of tests that may need additional attention.
+These categories are fairly generic and apply to most DUTs.
+
+#### Power Tests
+
+It is vital to be able to predict the power consumption of our SoC in early development stages and refactor the design as needed to optimize the RTL.
+Typically, DV tests that mimic idle and high power consumption scenarios are written and FSDB generated from those tests are used for analysis.
+
+This is perhaps applicable when an actual ASIC will be built out of our SoC.
+At that time, we could have lower power requirements in terms of being able to put parts of the SoC in different power 'islands' in retention voltage or be power collapsed.
+If and when such requirements are fully specified for the product, we need to ensure that power-aware tests have been added to verify this.
+
+#### Performance Tests
+
+ASICs vary widely in terms of their target applications.
+For some, performance testing may take the center stage, and for some, it may not be as important.
+No matter what the target application is, there is almost always some kind of requirement in terms of sustained bandwidth on a particular interface captured in the DUT specification.
+These set of tests ensure that those requirements are indeed met.
+
+#### Security & Error Tests
+
+Error tests focus on generating stimulus that may not be considered legal and ensure that the DUT can detect, react and behave accordingly, instead of locking up or worse, exposing sensitive information.
+These types of tests are particularly important for a security chip such as ours.
+These are typically handled via directed tests since it can be prohibitively time consuming to develop complex scoreboards that can handle the error-checking when running completely unconstrained random sequences.
+A classic example of this is the illegal / error access tests via the TileLink interface, which are run for all DUTs.
+Here, we constrain a random sequence to generate TL accesses that are considered illegal and ensure that the DUT responds with an error when appropriate.
+Another example is testing RAMs that support ECC / error correction.
+
+While some of the examples listed above pertain to concrete features in the design, we are actively also exploring alternative ways of finding and covering security holes that may not be uncovered via traditional DV efforts.
+
+#### Debug Tests
+
+This mainly applies to DUTs that contain a processing element (CPU).
+These focus on verifying the debug features supported by the CPU at the chip level based on the [RISCV debug specification](https://riscv.org/specifications/debug-specification).
+
+#### Stress Tests
+
+Stress tests lean heavily on constrained random techniques, and exercise multiple interfaces and / or design features simultaneously.
+This is done by forking off multiple individual test sequences in parallel (or sequentially if the pieces of hardware exercised by the tests sequences overlap).
+Stress tests are hard to debug due to lot of things happening in parallel and the scoreboard may not be written as robustly initially to handle those scenarios.
+To mitigate that, they are constructed with knobs to control the level of constraints applied to the randomization (of individual sequences), so that the scoreboard can be made more robust incrementally to handle the corner cases.
+The level of constraints are then slowly eased to allow deeper state space exploration, until all areas of the DUT are satisfactorily stressed.
+Stress tests are ideal for bug hunting and closing coverage.
+
+### [Progressing towards V3](../../../project_governance/checklist/README.md#v3)
+
+The main focus of testing at this stage is to meet our [regression](#nightly) and [coverage](#coverage-collection) goals.
+Apart from that, there are cleanup activities to resolve all pending TODO items in the DV code base and fix all compile and run time warnings (if any) thrown by the simulator tools.
+
+## Assertions
+
+In DV, we follow the same assertion methodology as indicated in the [design methodology](../../hw/design.md#assertion-methodology).
+Wherever possible, the assertion monitors developed for FPV are reused in UVM testbenches when running dynamic simulations.
+An example of this is the [TLUL Protocol Checker](../../../../hw/ip/tlul/doc/TlulProtocolChecker.md).
+
+Unlike design assertions, in DV assertions are typically created within SV interfaces bound to the DUT.
+This way assertions and any collateral code don't affect the design, and can reach any internal design signal if needed.
+For an example of this see the [clkmgr assertions](https://github.com/lowRISC/opentitan/tree/master/hw/ip/clkmgr/dv/sva).
+
+## Regressions
+
+There are 2 main types of regressions suites - 'sanity' and 'nightly'.
+
+### Sanity
+
+Due to heavy code reuse, breakages happen quite often.
+It is necessary for each DUT testbench to provide a set of simple sanity test that accesses a major datapath in the DUT.
+Categorizing such tests into a sanity suite provides a quick path for users who touch common / shared piece of code to run those tests for all DUTs and ensure no breakages occur.
+If the DUT testbench has more than one compile-time configuration, there needs to be at least 1 sanity test per configuration.
+
+Ideally, the sanity regression is run as a part of the CI check whenever a PR is submitted. Due to use of proprietary tools for running DV simulations, this cannot be accomplished. Instead, we run a daily cron job locally on the up-to-date `master` branch to identify such breakages and deploy fixes quickly.
+
+### Nightly
+
+While development mostly happens during the work day, nighttime and weekends are better utilized to run all of our simulations (a.k.a "regression").
+Achieving 100% pass rate in our nightly regressions consistently is a key to asserting 'verification complete'.
+The main goals (for all DUTs) are:
+
+*  Run each constrained random test with a sufficiently large number of seeds (arbitrarily chosen to be 100)
+   * Pass completely random seed values to the simulator when running the tests
+*  Spawn jobs via LSF to leverage compute resources at one's disposal
+*  Run resource intensive simulations without impacting daytime development
+*  Collect and merge coverage
+*  Publish the testplan-annotated regression and coverage results in the regression dashboard
+
+One of the key requirements of nightly regressions is to complete overnight, so that the results are available for analysis and triage the next morning.
+If test runtimes are longer, we could define a weekly regression based on need.
+In general, it is a good practice to periodically profile the simulation to identify bottlenecks in terms of simulation performance (which often is a result of specific coding style choices).
+
+## Coverage Collection
+
+Collecting, analyzing, and reporting coverage with waivers is a requirement to assert 'verification complete'.
+Any gaps in our measured coverage need to be understood and either waived (no need to cover) or closed by additional testing.
+The end goal is to achieve 100% coverage across all applicable coverage metrics.
+This process is known as "coverage closure", and is done in close collaboration with the designer(s).
+Coverage collected from all tests run as a part of the regression is merged into a database for analysis.
+Our primary tool of choice for our coverage closure needs is Synopsys VCS & Verdi.
+However, the use of other simulators is welcome.
+
+**Why do we need coverage?**
+
+The common answer is to flush out bugs in the design.
+This is not accurate enough.
+Making sure there are no bugs in a design is important, but not sufficient.
+One must also make sure the design works as intended.
+That is, it must provide all the functionality specified in the design specification.
+So a more precise answer for why we need coverage is to flush out flaws in the design.
+These flaws can be either design bugs or deficiencies in the design with respect to the specification.
+
+Another reason why we need coverage is to answer the seemingly simple but important question:
+**When are we done testing?**
+Do we need 1, 10, or 100 tests and should they run 10, 100, or 1000 regressions?
+Only coverage can answer this question for you.
+
+There are two key types of coverage metrics: code coverage and functional coverage.
+Both are important and are covered in more detail below.
+For this topic, we define 'pre-verified sub-modules' as IPs within the DUT that have already been (or are planned to be) verified to complete sign-off within individual test benches.
+
+### Code Coverage
+
+Commercial simulators typically provide a way to extract coverage statistics from our regressions.
+Tools automatically analyze the design to extract key structures such as lines, branches, FSMs, conditions, and IO toggle, and provide them as different coverage metrics as follows:
+
+* **Line Coverage** measures which lines of SystemVerilog RTL code were executed during the simulation.
+  This is probably the most intuitive metric to use.
+  Note that `assign` statements are always listed as covered using this metric.
+* **Toggle Coverage** measures every logic bit to see if it transitions from 1 &rarr; 0 and 0 &rarr; 1.
+  It is very difficult, and not particularly useful to achieve 100% toggle coverage across a design.
+  Instead, we focus on closing toggle coverage only on the IO ports of the DUT and IO ports of pre-verified IPs within the DUT.
+* **FSM state Coverage** measures which finite state machine states were executed during a simulation.
+* **FSM transition Coverage** measures which arcs were traversed for each finite state machine in the design.
+* **Conditional Coverage** tracks all combinations of conditional expressions simulated.
+* **Branch Coverage** is similar to line coverage, but not quite the same.
+  It tracks the flow of simulation (e.g. if/else blocks) as well as conditional expressions.
+  Note that FSM states and transitions not covered almost always shows up as branches not covered as well.
+
+Code coverage is sometimes referred to as implicit coverage as it is generated based on the code and takes no additional effort to implement.
+
+### Functional Coverage
+
+Unlike code coverage, functional coverage requires the designer and/or DV engineer to write additional cover points and covergroups.
+For this reason functional coverage is sometimes referred to as explicit coverage.
+Cover points and covergroups are more complex constructs that capture whether signals (that reflect the current state of the design) have met an interesting set or a sequence of values (often called corner cases).
+These constructs also allow us to capture whether multiple scenarios have occurred simultaneously through crosses.
+This is also often referred to as cross coverage.
+These constructs are typically encapsulated in a class and are sometimes referred to as the 'functional coverage model'.
+They are sampled in 'reactive' components of the testbench, such as monitors and/or the scoreboards.
+They can also be embedded within the RTL to sample interesting scenarios through DV stimulus.
+
+Here are the metrics used with a brief explanation:
+
+* **Covergroup Coverage**: Observes values on buses, registers and so on.
+  This can verify that a specific value or range of values was seen on a bus and that no illegal values were seen.
+  Simulators can be set to throw an error if an illegal value is seen.
+  One use of a covergroup is to define something called cross coverage.
+  This asserts that several coverage points are hit at once. For example, we might want to see a FIFO's full and write enable signals be asserted at the same time.
+  This is called a coverage cross.
+* **Cover Property coverage**:
+   * **Assertion coverage using SVA**
+   * **procedural code**
+
+Most often property coverage is implemented using SystemVerilog Assertions (SVA).
+This observes events or series of events.
+As an example think of the TL UL register bus used in OpenTitan.
+A cover property cover point could be the handshaking between valid and ready.
+SVA also allows the design engineer to add cover for procedures and variables not visible on output pins.
+Note, an assertion precondition counts as a cover point.
+
+#### Do we need both types of coverage
+
+Reaching a 100% code coverage does not tell you the whole story.
+Think of the simple example of an AND gate with inputs A, B, and output O.
+To get to 100% code coverage only two different input combinations are needed: 00 and 11, these two will produce all possible outcomes of O.
+
+![single AND gate](dv_method_single_gate.svg)
+
+
+The coverage will indicate that all code was exercised.
+But we do not know that our design works as intended.
+All we know is that A, B, and O have been observed to take on both logic 0 and 1.
+We could not say for certain that the design was in fact an AND gate: it could just as easily be an OR gate.
+So we need functional coverage to tell us this.
+The first thing functional coverage will tell us is if we observed all possible values on the inputs.
+And by adding a cross between the inputs and the output it will tell us which gate we are looking at.
+
+Reaching 100% functional coverage is not enough either.
+Remember functional coverage requires the designer to manually add coverage point into the design.
+Going back to our AND gate, let us say we take two of these and OR the outputs of the two.
+
+![multi gate](dv_method_multi_gate.svg)
+
+
+If we only add the cover points from our original example, these will still exercise the new output of our system and therefore result in reaching 100% functional coverage, but half of the design was not exercised.
+This is called a coverage hole and code coverage would have indicated that a part of the code was never exercised.
+
+While functional coverage will tell you if your design is working correctly, code coverage will highlight if your testplan is incomplete, or if there are any uncovered/unreachable features in the design.
+Coverage holes can be addressed accordingly through either updating the design specification and augmenting the testplan / written tests, or by optimizing the design to remove unreachable logic if possible.
+There may be features that cannot be tested and cannot be removed from the design either.
+These would have to be analyzed and excluded from the coverage collection as a waiver.
+This is an exercise the DV and the designer typically perform together, and is discussed in more detail below.
+
+### Exclusions
+
+Post-simulation coverage analysis typically yields items that may need to be waived off for various reasons.
+This is documented via exclusions, which are generated by the simulator tools.
+The following are some of the best practices when adding exclusions:
+
+*  Designers are required to sign-off on exclusions in a PR review.
+*  Provide annotations for ALL exclusions to explain the reasoning for the waiver.
+*  Annotate exclusions with a standardized prefix (this makes writing exclusions and reviewing them easier).
+   Exclusions almost always fall under a set of categories that can be standardized.
+   Annotation can be prefixed with a category tag reflecting one of those categories, like this:
+
+   `[CATEGORY-TAG] <additional explanation if required>`
+
+   These categories are as follows:
+
+   *  **UNR**: Unreachable code due to design constraints, or module inputs being tied off in a certain way will result in specific coverage items being unreachable.
+      Additional explanation is optional.
+   *  **NON_RTL**: Simulation constructs in RTL that can be safely excluded in structural coverage collection.
+      These include tasks and functions, initial / final blocks that are specifically used for simulation such as backdoor write and read functions for memory elements.
+      Additional explanation is optional.
+   *  **UNSUPPORTED**: Item being excluded is a part of design feature that is not supported.
+      Additional explanation is optional.
+      *  IP designed by some other team / third party is incorporated, but only a subset of the features are in use. Remaining ones are not supported.
+      *  Features that are added into the design but are not made a part of the design specification for the current generation / chip being taped out.
+      *  UVC / agent with detailed coverage items where certain crosses are not supported by the design (ex: TL agent with fcov on full spectrum of burst with all sizes and lengths, but only a subset of it actually being supported).
+      Additional explanation is **mandatory**.
+   *  **EXTERNAL**: Items that are already covered in another bench.
+      Additional explanation is **mandatory**.
+   *  **LOW_RISK**: Items that are prohibitively hard to hit, given the resource constraints and are deemed to be of low risk and low value.
+      Features that are added to the design AND are described adequately in the design spec AND a collective upfront decision has been made in agreement with SW/architecture/design/DV to not verify it due to resource constraints.
+      Additional explanation is **mandatory**.
+
+### Integration Testing
+
+For a DUT containing pre-verified sub-modules, the DV effort can be slightly eased.
+From the code coverage collection perspective, such sub-modules can be 'black boxed' where we turn off all other metrics within their hierarchies and only collect the toggle coverage on their IOs.
+This eases our effort by allowing us to develop less complex tests and verification logic (pertaining to those pre-verified modules) since our criteria for closure reduces to only functionally exercising the IOs and their interactions with the rest of the DUT to prove that sub-modules are properly connected.
+
+Of course, the rest of the sub-modules and glue-logic within the DUT that are not pre-verified do need to undergo the full spectrum of coverage closure.
+We achieve this by patterning the compile-time code coverage model in a particular way; this is a simulator tool-specific capability: for VCS, this uses the coverage hierarchy file that is written and passed to the simulator with the `-cm_hier` option.
+
+### Coverage Collection Guidelines
+
+Coverage closure is perhaps the most time-consuming part of the whole DV effort, often with low return.
+Conservatively collecting coverage on everything might result in poor ROI of DV user's time.
+Also, excessive coverage collection slows down simulation.
+This section aims to capture some of the best practices related to coverage closure.
+
+It is recommended to follow these guidelines when collecting coverage:
+
+*  Shape the coverage collection at compile time if possible by only enabling coverage collection on DUT/sub-modules and nodes, and/or metrics of interest.
+*  Collect toggle coverage only on IOs of DUT and all of its sub-modules.
+  *  If this is not worthwhile, then collect toggle coverage on top-level DUT IOs and IOs of pre-verified sub-modules.
+*  Collect all coverage metrics (except toggle based on above bullet) on the DUT and all of its non-pre-verified sub-modules.
+*  Treat pre-verified sub-modules as ['black-box'](#integration-testing) in terms of coverage collection.
+
+### Unreachable Coverage Analysis
+
+Instead of manually reviewing coverage reports to find unreachable code, we use VCS UNR to generate a UNR exclusion file which lists all the unreachable codes.
+VCS UNR (Unreachability) is a formal solution that determines the unreachable coverage objects automatically from simulation.
+The same coverage hierarchy file and the exclusion files used for the simulation can be supplied to VCS UNR.
+
+Follow these steps to run and submit the exclusion file.
+1. Generate the VCS coverage database for the block by running full regression with `--cov` switch.
+2. Launch the VCS UNR flow:
+```
+util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson --cov-unr
+```
+3. If no exclusion file is generated, there is no unreachable code in RTL.
+   If there is an exclusion file generated, the output should be reviewed by both designer and verification engineer.
+   When the unreachable code is sensible and we decide to exclude it in coverage report, create a PR to add to ['common_cov_excl.cfg'](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/vcs/common_cov_excl.cfg) or block specific exclusion file, such as ['uart_cov_excl.el'](https://github.com/lowRISC/opentitan/blob/master/hw/ip/uart/dv/cov/uart_cov_excl.el).
+
+Here are some guidelines for using UNR and checking in generating exclusion.
+1. It's encouraged that designers run UNR to check the design in the early design stage (D1/D2), but adding exclusions for unreachable coverage should be done between the D2 and V3 stage when the design is frozen (no feature update is allowed, except bug fix).
+Getting to 90% coverage via functional tests is easy.
+Over 90% is the hard part as there may be a big chunk of unreachable codes.
+It is cumbersome to go through a coverage report to manually add exclusions, but the VCS UNR flow provides a path to weed out all of the unreachable ones.
+However, it is not the right thing to add a coverage exclusion file to reach the 80% needed for V2 since the design isn't stable at that period.
+2. If any RTL changes happen to the design after the coverage exclusion file has been created, it needs to be redone and re-reviewed.
+3. All coverage exclusion files and coverage configuration file (if it's not using default [cover.cfg](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/vcs/cover.cfg)) should be checked during sign-off.
+4. Keep the VCS generated `CHECKSUM` along with exclusions, as it checks the exclusion isn't outdated by changes on the corresponding code.
+We should not use `--exclude-bypass-checks` to disable the check, otherwise, it's needed to have additional review to make sure exclusions match to the design.
+5. For IP verified in IP-level test bench, UNR should be run in IP-level to generate exclusion.
+For IP verified in top-level, UNR should be run in top-level.
+There is no reuse of exclusions from IP to top, since they are independently closed for coverage.
+
+Note: VCS UNR doesn't support assertion or functional coverage.
+
+## X-Propagation (Xprop)
+
+Standard RTL simulations (RTL-sim) ignore the uncertainty of X-valued control signals and assign predictable output values.
+As a result, classic RTL-sim often fail to detect design problems related to the lack of propagation of unknown values, which actually can be detected in gate-level simulations (gate-sim).
+With Xprop in RTL-sim, we can detect these problems without having to run gate-sim.
+
+Synopsys VCS and Cadence Xcelium both provide the following 2 modes for Xprop.
+  * **Optimistic Mode**: Closer to actual hardware behavior and is the more commonly used mode.
+  * **Pessimistic Mode**: More pessimistic than a standard gate-sim.
+
+Example:
+```systemverilog
+always @(posedge clk) begin
+  if (cond) out <= a;
+  else      out <= b;
+end
+```
+
+In the above example, results of 'out' are shown as following.
+
+a | b | cond | Classic RTL-sim | Gate-sim | Actual Hardware | Xprop Optimistic | Xprop Pessimistic |
+--|---|------|-----------------|----------|-----------------|------------------|-------------------|
+0 | 0 |  X   |        0        |     0    |       0         |         0        |         X         |
+0 | 1 |  X   |        1        |     X    |       0/1       |         X        |         X         |
+1 | 0 |  X   |        0        |     X    |       0/1       |         X        |         X         |
+1 | 1 |  X   |        1        |     X    |       1         |         1        |         X         |
+
+We choose **Pessimistic Mode** as we want to avoid using X value in the condition.
+Xprop is enabled by default when running simulations for all of our DUTs due to the acceptable level of overhead it adds in terms of wall-clock time (less than 10%).
+
+It's mandatory to enable Xprop when running regression for coverage closure.
+Please refer to the simulator documentation to identify and apply the necessary build-time and / or run-time options to enable Xprop.
+In OpenTitan, Xprop is enabled by default for VCS and Xcelium simulators.
+To test Xprop more effectively, the address / data / control signals are required to be driven to Xs when invalid (valid bit is not set).
+For example, when a_valid is 0 in the TLUL interface, we drive data, address and control signals to unknown values.
+```systemverilog
+  function void invalidate_a_channel();
+    vif.host_cb.h2d.a_opcode  <= tlul_pkg::tl_a_op_e'('x);
+    vif.host_cb.h2d.a_param   <= '{default:'x};
+    vif.host_cb.h2d.a_size    <= '{default:'x};
+    vif.host_cb.h2d.a_source  <= '{default:'x};
+    vif.host_cb.h2d.a_address <= '{default:'x};
+    vif.host_cb.h2d.a_mask    <= '{default:'x};
+    vif.host_cb.h2d.a_data    <= '{default:'x};
+    vif.host_cb.h2d.a_user    <= '{default:'x};
+    vif.host_cb.h2d.a_valid   <= 1'b0;
+  endfunction : invalidate_a_channel
+```
+
+The simulator may report that some portions of RTL / DV could not be instrumented for X-propagation due to the way they were written.
+This is typically captured in a log file during the simulator's build step.
+It is mandatory to analyze these pieces of logic to either refactor them to be amenable to X-prop instrumentation, or waive them if it is not possible to do so.
+This is captured as the V3 checklist item `X_PROP_ANALYSIS_COMPLETED`.
+
+### Common XProp Issues
+
+Xprop instrumentation is simulator specific; this focuses on VCS, and lists some simple and common constructs that block instrumentation.
+
+    *  Some behavioral statements, like `break`, `continue`, `return`, and `disable`.
+    *  Functions that have multiple `return` statements.
+    *  Calling tasks or functions with side-effects, notably using the `uvm_info` macro.
+
+If the problematic code is in a SV process, Xprop can be disabled adding the `xprop_off` attribute to that process as follows:
+
+```systemverilog
+  always (* xprop_off *) @(posedge clk) begin
+    <some problematic constructs>
+  end
+```
+
+There are cases where the problematic constructs are not in processes, and the code cannot be rewritten to make it Xprop-friendly.
+In these cases we need to disable Xprop for the full module or interface using a custom xprop configuration file.
+This uses the `vcs_xprop_cfg_file` field in the corresponding sim_cfg hjson file.
+See the [top_earlgrey vcs xprop configuration file](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/dv/vcs_xprop.cfg) and `vcs_xprop_cfg_file` override in the [top_earlgrey sim_cfg hjson file](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/dv/chip_sim_cfg.hjson) for an example.
+
+## FPV
+
+Refer to the [formal verification documentation](../../../../hw/formal/README.md).
+
+## Security Verification
+
+Security verification is one of the critical challenges in OpenTitan.
+Each design IP contains certain security countermeasures.
+There are several common countermeasures that are widely used in the blocks.
+Therefore, a common verification framework is built up in the DV base libraries.
+The following common countermeasures can be either automatically or semi-automatically verified by this framework.
+
+1. Countermeasures using common primitives can be verified by the [Security Countermeasure Verification Framework](../sec_cm_dv_framework/README.md).
+2. The following common countermeasures can be verified by cip_lib.
+The steps to enabling them is described in cip_lib [document](../../../../hw/dv/sv/cip_lib/README.md#security-verification-in-cip_lib).
+  - Bus integrity
+  - Shadowed CSRs
+  - REGWEN CSRs
+  - MUBI type CSRs/ports
+
+For custom countermeasures, they have to be handled in a case-by-case manner.
+
+## Reviews
+
+One of the best ways to convince ourselves that we have done our job right is by seeking from, as well as providing feedback to, our contributors.
+We have the following types of reviews for DV.
+
+### Code Reviews
+
+Whenever a pull-request is made with DV updates, at least one approval is required by a peer and / or the original code developer to enable the changes to be submitted.
+DV updates are scrutinized in sufficient detail to enforce coding style, identify areas of optimizations / improvements and promote code-reuse.
+
+### Sign-off Reviews
+
+In the initial work stage of verification, the DV document and the completed testplan documents are reviewed face-to-face with the following individuals:
+
+*  Designer(s)
+*  DV peers
+*  Leads
+*  Chip level (or higher level) designers and DV engineers within which the DUT is integrated
+*  Software team
+*  Product architect
+
+The goal of this review is to achieve utmost clarity in the planning of the DV effort and resolve any queries or assumptions.
+The feedback in this review flows both ways - the language in the design specification could be made more precise, and missing items in both the design specification and the testplan can be identified and added.
+This enables the development stages to progress smoothly.
+
+Subsequently, the intermediate transitions within the verification stages are reviewed within the GitHub pull-request made for updating the checklist and the [project status](../../../project_governance/development_stages.md#indicating-stages-and-making-transitions).
+
+Finally, after the verification effort is complete, there is a final sign-off review to ensure all checklist items are completed satisfactorily without any major exceptions or open issues.
+
+## Filing Issues
+
+We use the [OpenTitan GitHub Issue tracker](https://github.com/lowRISC/opentitan/issues) for filing possible bugs not just in the design, but also in the DV code base or in any associated tools or processes that may hinder progress.
+
+## Getting Started with DV
+
+The process for getting started with DV involves many steps, including getting clarity on its purpose, setting up the testbench, documentation, etc.
+These are discussed in the [Getting Started with DV](https://docs.opentitan.org/doc/guides/getting_started/src/setup_dv.md) document.
+
+## Pending Work Items
+
+These capabilities are currently under active development and will be available sometime in the near future:
+
+*  Provide ability to run regressions.
diff --git a/doc/ug/dv_methodology/dv_method_multi_gate.svg b/doc/contributing/dv/methodology/dv_method_multi_gate.svg
similarity index 100%
rename from doc/ug/dv_methodology/dv_method_multi_gate.svg
rename to doc/contributing/dv/methodology/dv_method_multi_gate.svg
diff --git a/doc/ug/dv_methodology/dv_method_single_gate.svg b/doc/contributing/dv/methodology/dv_method_single_gate.svg
similarity index 100%
rename from doc/ug/dv_methodology/dv_method_single_gate.svg
rename to doc/contributing/dv/methodology/dv_method_single_gate.svg
diff --git a/doc/contributing/dv/sec_cm_dv_framework/README.md b/doc/contributing/dv/sec_cm_dv_framework/README.md
new file mode 100644
index 0000000..3608517
--- /dev/null
+++ b/doc/contributing/dv/sec_cm_dv_framework/README.md
@@ -0,0 +1,156 @@
+# Security Countermeasure Verification Framework
+
+## Purpose
+In a security chip, there are many hardening security countermeasures implemented in security blocks.
+Hardened security countermeasures are used to detect attacks by embedding integrity checks.
+Usually it can’t be triggered by software or toggling the pins on the chip.
+So, this may need to be tested one by one via forcing internal design signals on the security countermeasure related logic, which mimics attackers to flip the circuit.
+This kind of verification is needed but the effort could be tremendous.
+The work can be significantly reduced by using a unified verification framework.
+
+## Standardized Design Countermeasure Primitive
+Each countermeasure is implemented as a primitive - an RTL design building block written in SystemVerilog.
+The standardization of how these building blocks are designed paves the way for automated verification.
+
+There are 2 requirements that are needed in this primitive.
+1. Create a standard primitive for each kind of countermeasure, which can be reused across all blocks in the chip.
+Each primitive has an error indicator as an IO (or an internal signal if other blocks don’t need to connect with this error indicator).
+Once the design detects an attack, the error will be set.
+
+![diagram_of_sec_cm_prim](diagram_of_sec_cm_prim.svg)
+
+2. Once the error is set, the block should report a fatal alert or trigger unmasked interrupt to the system.
+As shown in the diagram below, there could be some additional logics which are triggered when error occurs but not unified across the blocks.
+For example, the block may contain custom logics which cancelling the operation when error occurs.
+![diagram_of_block_that_contains_sec_cm_primitive](diagram_of_block_that_contains_sec_cm_primitive.svg)
+
+## Embedded Common Checks In The Design
+At the top of each IP, an assertion check should be added to ensure that the fatal alert will be triggered once error is set.
+In the countermeasure primitive, there is another check to ensure that each instance of the countermeasure primitive must contain this assertion check.
+These 2 things can be accomplished by using the SystemVerilog macro.
+
+Firstly, define a macro in the countermeasure primitive and declare a logic “assert_connected” which will be assigned when the macro is invoked correctly.
+If the macro isn’t used for any instance of countermeasure primitive, the assertion will fail.
+
+```systemverilog
+`define ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(NAME_, PRIM_HIER_, ALERT_, MAX_CYCLES_ = 5) \
+  NAME_: assert property (@(posedge clk)) disable iff(!rst_n) \
+                ($rose(PRIM_HIER_.err_o) |-> ##[1:MAX_CYCLES_] $rose(ALERT_.alert_p)) \
+  `ifdef SIM_OR_FPV \
+  assign PRIM_HIER_.unused_assert_connected = 1'b1; \
+  `endif
+
+module prim_count();
+  ...
+  // This logic that will be assign to one, when user adds macro
+  // ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT to check the error with alert, in case that prim_count
+  // is used in design without adding this assertion check.
+  `ifdef SIM_OR_FPV \
+  logic assert_connected;
+
+  initial #0 assert(assert_connected === 1'b1);
+  `endif
+endmodule
+```
+
+Secondly, invoke the assertion macro for each instance of the countermeasure primitive in the IP top.
+
+```systemverilog
+module a_ip_top();
+  ...
+  // Invoke the assertion macro for each instance of the countermeasure primitive
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CtrlCntAlertCheck_A, u_ctrl.u_cnt, alert_tx_o[0])
+
+  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FsmCntAlertCheck_A, u_fsm.u_cnt, alert_tx_o[0])
+
+endmodule
+
+```
+
+### Special Handling of Sparse FSM Primitive
+
+Sparse FSMs in OpenTitan security IPs are implemented with the `prim_sparse_fsm_flop` countermeasure primitive to ensure that the state encoding cannot be altered by synthesis tools.
+This primitive also implements the embedded common checks mentioned above.
+
+However, simulation tools like Xcelium and VCS are at this time not able to correctly infer FSMs and report FSM coverage when the state registers reside in a different hierarchy (such as `prim_sparse_fsm_flop`) than the next-state logic of the FSMs.
+
+In order to work around this issue, the wrapper macro `PRIM_FLOP_SPARSE_FSM` should be used instead of directly instantiating the `prim_sparse_fsm_flop` primitive.
+The `PRIM_FLOP_SPARSE_FSM` macro instantiates a behavioral state register in addition to the `prim_sparse_fsm_flop` primitive when the design is built with `SIMULATION` defined.
+This enables simulation tools to correctly infer FSMs and report coverage accordingly.
+For other build targets that do not define `SIMULATION` this macro only instantiates the `prim_sparse_fsm_flop` primitive.
+
+An example of how the macro should be used is shown below:
+
+```systemverilog
+// u_state_flops: instance name of the prim_sparse_fsm_flop primitive
+// state_d: FSM next state
+// state_q: FSM current state
+// state_e: FSM state enum type
+// ResetSt: FSM reset state
+// clk_i: Clock of the design (defaults to clk_i)
+// rst_ni: Reset of the design (defaults to rst_ni)
+// SvaEn: A value of 1 enables the embedded assertion (defaults to 1)
+`PRIM_FLOP_SPARSE_FSM(u_state_flops, state_d, state__q, state_e, ResetSt, clk_i, rst_ni, SvaEn)
+```
+
+In order to generate a complete template for sparsely encoded FSMs, please refer to the [the sparse-fsm-encode.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py).
+
+## Verification Framework For The Standardized Design Countermeasures
+
+This verification framework involves three different steps, which are all needed in order to achieve the completed verification for countermeasures.
+
+### Primitive-Level Verification
+Use FPV or a simple simulation based testbench to verify the correctness of the error indicator as well as other functionality in the primitive.
+This unit-level testing eases the verification work at the higher level.
+Coverage closure for this primitive at higher-level can be eliminated.
+
+### FPV Proves The Embedded Assertion
+Use FPV to prove that the embedded assertion is true (fatal alert is triggered once error is set).
+This mathematically proves that an error unconditionally leads to the fatal alert.
+
+### Block-Level Verification With A Unified Semi-Automated Framework.
+Triggering the fatal alert is usually not the only outcome of detecting an attack.
+Some other behavior may occur as well, such as wiping all the internal secrets or invaliding new commands, after detecting a fault.
+However, there are usually many security primitives that are embedded in different places of the security chip.
+It could take tremendous effort to manually test them one by one.
+The following steps are the key implementation of this framework, which uses some automation to reduce the effort.
+
+1. Create a SystemVerilog interface with a proxy class (refer to this [article](https://blog.verificationgentleman.com/2015/08/31/sv-if-polymorphism-extendability.html) for implementation) for each countermeasure primitive and bind the interface to the primitive module.
+All the proxy classes extend from the same base class.
+
+2. In the interface, implement an “initial” block that stores the handle of the proxy class to a queue in a package.
+This queue is declared as the base type of the proxy class. All the handles of various proxy classes are automatically stored at time 0 in the simulation.
+
+```systemverilog
+interface prim_count_if;
+
+  // this class has the access to any signals in the interface and interface can connect to the signals in the primitive
+  class prim_count_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy; // allow extendability
+    virtual task inject_fault();
+       // use uvm_hdl_force/deposit to do fault injection
+    endtask
+  endclass
+
+  prim_count_if_proxy if_proxy;
+  initial begin
+    if_proxy = new("if_proxy");
+    if_proxy.sec_cm_type = sec_cm_pkg::SecCmPrimCount;
+    if_proxy.prim_path = $sformatf("%m");
+    sec_cm_pkg::sec_cm_if_proxy_q.push_back(if_proxy);
+  end
+```
+
+3. In the base sequence, create a loop to test each instance of the countermeasure primitive. In each loop, “inject_fault” task from the proxy class will be invoked, which is followed by a “check_resp” task. The block owner can extend this sequence and add additional checks for non-standardized behavior based on the proxy_class info such as the hierarchy of the primitive instance.
+
+```systemverilog
+foreach (sec_cm_pkg::sec_cm_if_proxy_q[i]) begin
+    sec_cm_pkg::sec_cm_base_if_proxy if_proxy = sec_cm_pkg::sec_cm_if_proxy_q[i];
+    inject_fault(if_proxy));  // call if_proxy.inject_fault
+    check_resp(if_proxy);
+end
+```
+This automation essentially relies on standardizing design countermeasure primitives.
+It uses the primitive types to anchor all the instances of countermeasure primitives and store all the handles that associate to the interface of the countermeasure primitive in a global location.
+The framework loops over all the handles and provides a common sequence to check common behavior as well as callback functions that allow users to test any non-standard behavior.
+
+Refer to cip_lib [document](../../../../hw/dv/sv/cip_lib/README.md#security-verification-in-cip_lib) at the section - "Security Verification for common countermeasure primitives" for the steps to enable this test in block-level testbench.
diff --git a/doc/ug/sec_cm_dv_framework/diagram_of_block_that_contains_sec_cm_primitive.svg b/doc/contributing/dv/sec_cm_dv_framework/diagram_of_block_that_contains_sec_cm_primitive.svg
similarity index 100%
rename from doc/ug/sec_cm_dv_framework/diagram_of_block_that_contains_sec_cm_primitive.svg
rename to doc/contributing/dv/sec_cm_dv_framework/diagram_of_block_that_contains_sec_cm_primitive.svg
diff --git a/doc/ug/sec_cm_dv_framework/diagram_of_sec_cm_prim.svg b/doc/contributing/dv/sec_cm_dv_framework/diagram_of_sec_cm_prim.svg
similarity index 100%
rename from doc/ug/sec_cm_dv_framework/diagram_of_sec_cm_prim.svg
rename to doc/contributing/dv/sec_cm_dv_framework/diagram_of_sec_cm_prim.svg
diff --git a/doc/contributing/fpga/get_a_board.md b/doc/contributing/fpga/get_a_board.md
new file mode 100644
index 0000000..91aab9c
--- /dev/null
+++ b/doc/contributing/fpga/get_a_board.md
@@ -0,0 +1,25 @@
+# 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.
+The following sections give details of how to obtain our supported FPGA boards.
+
+## ChipWhisperer CW310
+
+The ChipWhisperer CW310 board is produced by [NewAE Technology](https://www.newae.com/).
+It is available with a Xilinx Kintex 7 XC7K160T or XC7K410T FPGA.
+At the moment, the version with the smaller Xilinx Kintex 7 XC7K160T FPGA is not supported by OpenTitan.
+The ChipWhisperer CW310 board with the bigger Xilinx Kintex 7 XC7K410T FPGA is the main development FPGA board for OpenTitan.
+
+### Ordering
+
+You can get the ChipWhisperer CW310 board from the [NewAE Technology web store](https://store.newae.com/cw310-bergen-board-large-fpga-k410t-for-full-emulation/).
+
+In the future, the board might also be listed on [Mouser](https://eu.mouser.com/manufacturer/newae-technology/).
+
+### Notes
+
+* By default the board ships with a US and European power plug.
+  Optionally, a UK power plug can be selected in the NewAE Technology web store.
+* The board is available in two options.
+  The normal option is suitable for e.g. regular hardware and software development.
+  If you also plan to do Side Channel Analysis (SCA), you should make sure to order the SCA option which misses some decoupling capacitors.
diff --git a/doc/contributing/fpga/ref_manual_fpga.md b/doc/contributing/fpga/ref_manual_fpga.md
new file mode 100644
index 0000000..8806199
--- /dev/null
+++ b/doc/contributing/fpga/ref_manual_fpga.md
@@ -0,0 +1,163 @@
+# FPGA Reference Manual
+
+This manual provides additional usage details about the FPGA.
+Specifically, it provides instructions on SW development flows and testing procedures.
+Refer to the [FPGA Setup](../../guides/getting_started/src/setup_fpga.md) guide for more information on initial setup.
+
+## FPGA SW Development Flow
+
+The FPGA is meant for both boot ROM and general software development.
+The flow for each is different, as the boot ROM is meant to be fairly static while general software can change very frequently.
+However, for both flows, Vivado must be installed, with instructions described [here](../../guides/getting_started/src/install_vivado/README.md).
+
+### Boot ROM development
+
+The FPGA bitstream is built after compiling whatever code is sitting in `sw/device/lib/testing/test_rom`.
+This binary is used to initialize internal FPGA memory and is part of the bitstream directly.
+
+To update this content without rebuilding the FPGA, a flow is required to splice a new boot ROM binary into the bitstream.
+First, you must splice the new content into an existing bitstream.
+Then, you can flash the new bitstream onto the FPGA with `opentitantool`.
+
+There are two ways to splice content into a bitstream.
+
+1. Define a Bazel target (or use an existing one).
+For example, see the `//hw/bitstream:rom` target defined in [hw/bitstream/BUILD](https://github.com/lowRISC/opentitan/blob/master/hw/bitstream/BUILD).
+
+2. Use the [`splice_rom.sh`](https://github.com/lowRISC/opentitan/blob/master/util/fpga/splice_rom.sh) script.
+
+    There are two prerequisites in order for this flow to work:
+
+    * The boot ROM during the build process must be correctly inferred by the tool.
+      * See [prim_rom](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim_generic/rtl/prim_generic_rom.sv) and [vivado_hook_opt_design_post.tcl](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/util/vivado_hook_opt_design_post.tcl).
+    * The MMI file outlining the physical boot ROM placement and mapping to FPGA block RAM primitives needs to be generated by the tool.
+      * See [vivado_hook_write_bitstream_pre.tcl](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/util/vivado_hook_write_bitstream_pre.tcl).
+
+    With these steps in place, a script can be invoked to take a new binary and push its contents into an existing bitfile.
+    For details, please see the [`splice_rom.sh`](https://github.com/lowRISC/opentitan/blob/master/util/fpga/splice_rom.sh) script.
+
+    See example below:
+
+    ```console
+    $ cd $REPO_TOP
+    $ ./util/fpga/splice_rom.sh
+    $ bazel run //sw/host/opentitantool fpga load-bitstream build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit
+    ```
+
+    The script assumes that there is an existing bitfile `build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit` (this is created after following the steps in [FPGA Setup](../../guides/getting_started/src/setup_fpga.md)).
+
+    The script assumes that there is an existing boot ROM image under `build-bin/sw/device/lib/testing/test_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_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig`.
+
+    `opentitantool` can then be used to directly flash the updated bitstream to the FPGA.
+
+### General Software Development
+
+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, `opentitantool` is required.
+
+Also the binary you wish to load needs to be built first.
+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.
+The example below builds the `hello_world` image and loads it onto the FPGA.
+
+```console
+$ cd ${REPO_TOP}
+$ bazel run //sw/host/opentitantool fpga set-pll # This needs to be done only once.
+$ bazel build //sw/device/examples/hello_world:hello_world_fpga_cw310_bin
+$ bazel run //sw/host/opentitantool bootstrap $(ci/scripts/target-location.sh //sw/device/examples/hello_world:hello_world_fpga_cw310_bin)
+```
+
+Uart output:
+```
+I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
+I00001 test_rom.c:87] TestROM:6b2ca9a1
+I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
+I00001 test_rom.c:87] TestROM:6b2ca9a1
+I00002 test_rom.c:118] Test ROM complete, jumping to flash!
+I00000 hello_world.c:66] Hello World!
+I00001 hello_world.c:67] Built at: Jun 13 2022, 14:16:59
+I00002 demos.c:18] Watch the LEDs!
+I00003 hello_world.c:74] Try out the switches on the board
+I00004 hello_world.c:75] or type anything into the console window.
+I00005 hello_world.c:76] The LEDs show the ASCII code of the last character.
+```
+
+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](https://github.com/lowRISC/opentitan/tree/master/sw/device/lib/testing/test_rom).
+
+### Accelerating `git bisect` with the bitstream cache
+
+To set the stage, let's say you've discovered a test regression.
+The test used to pass on `GOOD_COMMIT`, but now it fails at `BAD_COMMIT`.
+Your goal is to find the *first bad commit*.
+
+In general, a linear search from `GOOD_COMMIT` to `BAD_COMMIT` is one of the slowest ways to find the first bad commit.
+We can save time by testing fewer commits with `git bisect`, which effectively applies binary search to the range of commits.
+We can save even more time by leveraging the bitstream cache with **[`//util/fpga:bitstream_bisect`](https://github.com/lowRISC/opentitan/tree/master/util/fpga/bitstream_bisect.py)**.
+
+The `:bitstream_bisect` tool is faster than regular `git bisect` because it restricts itself to cached bitstreams until it can make no more progress.
+Building a bitstream is many times slower than running a test (hours compared to minutes), and `git bisect` has no idea that some commits will be faster to classify than others due to the bitstream cache.
+
+For example, suppose that `//sw/device/tests:uart_smoketest` has regressed sometime in the last 30 commits.
+The following command could easily save hours compared to a naive `git bisect`:
+
+```sh
+# This will use the fast command to classify commits with cached bitstreams. If
+# the results are ambiguous, it will narrow them down with the slow command.
+./bazelisk.sh run //util/fpga:bitstream_bisect -- \
+    --good HEAD~30 \
+    --bad HEAD \
+    --fast-command "./bazelisk.sh test //sw/device/tests:uart_smoketest_fpga_cw310_rom" \
+    --slow-command "./bazelisk.sh test --define bitstream=vivado //sw/device/tests:uart_smoketest_fpga_cw310_rom"
+```
+
+One caveat is that neither `git bisect` nor `:bitstream_bisect` will help if the FPGA somehow retains state between tests.
+That is, if the test command bricks the FPGA causing future tests to fail, bisection will return entirely bogus results.
+We plan to add a "canary" feature to `:bitstream_bisect` that will abort the bisect when FPGA flakiness is detected (issue [#16788](https://github.com/lowRISC/opentitan/issues/16788)).
+For now, if you suspect this kind of FPGA flakiness, the best strategy may be a linear progression from `GOOD_COMMIT` to `BAD_COMMIT`.
+
+Note that the slow command doesn't necessarily have to build a bitstream.
+If you don't have a Vivado license and the test regression is reproducible in Verilator, it could make sense to fall back to the Verilated test.
+Building on the example above, you could replace the slow command with `"./bazelisk.sh test //sw/device/tests:uart_smoketest_sim_verilator"` and the `:bitstream_bisect` tool would never build any bitstreams.
+
+For more information, consult the `:bitstream_bisect` tool directly!
+
+```sh
+./bazelisk.sh run //util/fpga:bitstream_bisect -- --help
+```
+
+## Implementation of Bitstream Caching and Splicing
+
+This section gives an overview of where bitstreams are generated, how they are uploaded to the GCP cache, and how Bazel reaches into the cache.
+
+OpenTitan runs CI tasks on Azure Pipelines that build FPGA bitstreams.
+A full bitstream build can take hours, so we cache the output artifacts in a GCS bucket.
+These cached bitstreams can be downloaded and used as-is, or we can splice in freshly-compiled components, including the ROM and the OTP image.
+
+### Building bitstreams on CI and uploading artifacts to GCS
+
+The `chip_earlgrey_cw310` CI job builds the `//hw/bitstream/vivado:standard` target which will build a bitstream with the test ROM and RMA OTP image.
+This target will also produce bitstreams with the ROM spliced in and the DEV OTP image spliced in.
+The following files are produced as a result:
+
+* `fpga_cw310_rom.bit` (ROM, RMA OTP image)
+* `fpga_cw310_rom_otp_dev.bit` (ROM, DEV OTP image)
+* `lowrisc_systems_chip_earlgrey_cw310_0.1.bit` (test ROM, RMA OTP image)
+* `otp.mmi`
+* `rom.mmi`
+
+If CI is working on the `master` branch, it puts selected build artifacts into a tarball, which it then uploads to the GCS bucket. The latest tarball is available here: https://storage.googleapis.com/opentitan-bitstreams/master/bitstream-latest.tar.gz
+
+### Exposing GCS-cached artifacts to Bazel
+
+The `@bitstreams//` workspace contains autogenerated Bazel targets for the GCS-cached artifacts.
+This magic happens in `rules/scripts/bitstreams_workspace.py`.
+Under the hood, it fetches the latest tarball from the GCS bucket and constructs a BUILD file that defines one target per artifact.
+
+One meta-level up, we have targets in `//hw/bitstream` that decide whether to use cached artifacts or to build them from scratch.
+By default, these targets use cached artifacts by pulling in their corresponding `@bitstreams//` targets.
+
+### Related work
+
+* TODO Define the new naming scheme.
+  https://github.com/lowRISC/opentitan/issues/13807
diff --git a/doc/contributing/github_notes.md b/doc/contributing/github_notes.md
new file mode 100644
index 0000000..d24aae2
--- /dev/null
+++ b/doc/contributing/github_notes.md
@@ -0,0 +1,507 @@
+# GitHub Notes
+
+The OpenTitan source tree is maintained on GitHub in a [monolithic repository](https://github.com/lowRISC/opentitan) called opentitan.
+
+These notes are for people who intend to contribute back to OpenTitan (based on
+notes taken by a relatively inexperienced git user). If you don't intend to do
+this you can simply clone the main repository, instructions are in [install
+instructions](../guides/getting_started/src/README.md) and this document can be
+ignored. There is much more to using git; a possible next step is to refer to
+[Resources to learn Git](https://try.github.io/).
+
+## Getting a working repository
+
+To develop in the repository you will need to get a copy on your local
+machine. To allow contributions to be made back to the main repo
+(through a process called a Pull Request) you need to first make your
+own copy of the repository on GitHub then transfer that to your local
+machine.
+
+You will need to log in to GitHub, go to the [opentitan repository](https://github.com/lowRISC/opentitan) and click on
+"Fork". This will generate a copy in your GitHub area that you can
+use.
+
+Then setup git on your local machine and set some standard parameters
+to ensure your name and email are correctly inserted. These commands
+set them globally for all your use of git on the local machine so you
+may have done this step already, there is no need to repeat it. (See
+below if you want to use a different email address for this repo.)
+
+Check the parameters:
+```console
+$ git config -l
+```
+
+And if they do not exist then set them:
+
+```console
+$ git config --global user.name "My Name"
+$ git config --global user.email "my_name@email.com"
+```
+
+`git` will take care of prompting for your GitHub user name and
+password when it is required, but it can be useful to allow it to
+cache these credentials (set here to an hour using the timeout in
+seconds) so you don't have to enter every time:
+
+```console
+$ git config --global credential.helper 'cache --timeout=3600'
+```
+
+Now make a local copy of your GitHub copy of the repository and let git know
+that it is derived from the **upstream** lowRISC repo:
+
+```console
+$ cd <where the repository should go>
+$ git clone https://github.com/$GITHUB_USER/opentitan.git
+$ cd opentitan
+$ git remote add upstream https://github.com/lowRISC/opentitan.git
+$ git fetch upstream
+$ git remote -v
+```
+
+The `git remote -v` should give your GitHub copy as **origin** and the
+lowRISC one as **upstream**. Making this link will allow you to keep your
+local and GitHub repos up to date with the lowRISC one.
+
+If you want a different email address (or name) for the lowRISC repository then
+you can set it locally in the repository (similar to above but without the
+--global flag). This command must be executed from a directory inside
+the local copy of the repo. (There is no need for the first `cd` if
+you are following the previous step.)
+
+
+```console
+$ cd opentitan
+$ git config user.email "my_name@lowrisc.org"
+```
+
+## Working in your local repo
+
+The repository that you have created locally will initially be on the
+`master` branch. In general you should not make changes on this
+branch, just use it to track your GitHub repository and synchronize with the
+main lowRISC repository.
+
+The typical workflow is to make your own branch which it is
+conventional to name based on the change you are making:
+
+```console
+$ git checkout -b forchange
+$ git status
+```
+
+The status will initially indicate there are no changes, but as you
+add, delete or edit files it will let you know the state of things.
+
+Once you are happy with your changes, commit them to the local repository by adding the files to the changes (if things are clean you can add using `git commit -a -s` instead of a number of `add` commands):
+
+```console
+$ git add...
+$ git commit -s
+```
+
+The commit will make you add a message. The first line of this is a
+short summary of the change. It should be prefixed with a word in
+square brackets indicating the area being changed, typically the IP or
+Tool name. For example:
+
+```console
+[doc/um] Add notes on using GitHub and the repo
+```
+
+After this there should be a blank line and the main description of
+the change. If you are fixing an issue then add a line at the end of
+the message `Fixes #nn` where `nn` is the issue number. This will link
+the fix and close out the issue when it is added to the lowRISC repo.
+If the change is an attempted fix that has not yet had confirmation from
+verification engineers, it should not close the related issue. In this case,
+write `Related to #nn` in the commit message rather than `Fixes #nn`.
+
+The commit message will end with a "Signed-off-by" line inserted by `git` due to using the `-s` option to `git commit`.
+Adding this line certifies you agree to the statement in [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md), indicating your contribution is made under our Contributor License Agreement.
+
+When you have finished everything locally (it is good practice to do a
+status check to ensure things are clean) you can push your branch (eg
+forchange) to **your** GitHub repository (the **origin**):
+
+```console
+$ git status
+$ git push origin forchange
+```
+
+Then you need to go to your repository in GitHub and either select branch
+from the pulldown or often there is a status message that you can
+click on, review the changes and make a Pull Request. You can add
+reviewers and get your change reviewed.
+
+If you need to make changes to satisfy the reviews then you do that in
+your local repository on the same branch. You will need to `add` files and
+commit again. It is normally best to squash your changes into a single
+commit by doing it with `--amend` which will give you a chance to edit
+the message. If you do this you need to force `-f` the push back to
+your repo.
+
+```console
+$ git add...
+$ git commit --amend
+$ git status
+$ git push -f origin forchange
+```
+
+Once the reviewers are happy you can "Rebase and Merge" the Pull
+Request on GitHub, delete the branch there (it offers to do this when
+you do the merge). You can delete the branch in your local repository with:
+
+```console
+$ git checkout master
+$ git branch -D forchange
+```
+
+When a Pull Request contain multiple commits, those commits should be logically
+independent. Interactive rebase can be used to manipulate commits in a pull
+request to achieve that goal. Commonly, it might be used to squash commits that
+fix up issues reported during review back into the relevant commit.
+
+```console
+$ git rebase -i `git merge-base {current_branch} master`
+```
+
+Then, an editor will open. Follow the instructions given there, to reorder and
+combine commits, or to change the commit message. Then update the PR branch in
+the GitHub remote repository.
+
+```console
+$ git push -f origin HEAD
+```
+
+## Automatically adding a Signed-off-by line for your commits
+
+One option to avoid having to remember to add `-s` to `git commit` is to configure `git` to append it to your commit messages for you.
+This can be done using an executable hook.
+An appropriate hook can be installed by executing this from the root of your repository checkout:
+
+```console
+$ cat <<"EOF" > .git/hooks/prepare-commit-msg
+#!/bin/sh
+
+# Add a Signed-off-by line to the commit message if not already present.
+git interpret-trailers --if-exists doNothing --trailer \
+  "Signed-off-by: $(git config user.name) <$(git config user.email)>" \
+  --in-place "$1"
+EOF
+$ chmod +x .git/hooks/prepare-commit-msg
+```
+
+## Update your repository with changes in the lowRISC repo
+
+There is a little work to do to keep everything in sync. Normally you
+want to first get your local repository `master` branch up to date with the
+lowRISC repository (**upstream**) and then you use that to update your GitHub
+copy (**origin**).
+
+```console
+$ git checkout master
+$ git pull --ff-only upstream master
+$ git push origin
+```
+
+If you do this while you have changes on some other branch then before
+a Pull Request will work you need to be sure your branch merges
+cleanly into the new lowRISC repo. Assuming you got the local `master`
+branch up to date with the procedure above you can now **rebase** your
+changes on the new `master`. Assuming you have your changes on the local
+branch `forchange`:
+
+```console
+$ git checkout forchange
+$ git rebase master
+```
+
+If you are lucky this will just work, it unwinds your changes, gets
+the updated `master` and replays your changes. If there are conflicts
+then you need a big pot of coffee and patience (see next section).
+
+Once everything has rebased properly you can do
+
+
+```console
+$ git log
+```
+
+And see that the changes you committed on the branch are at the top of
+the log followed by the latest changes on the `master` branch.
+
+Note: be mindful about which branch you are working on.
+If you have added new commits to your local `master` branch, aligning it with `upstream` using `git pull --ff-only` may fail.
+An alternative is to use `git pull --rebase` in that case, which directly rebases your local `master` branch onto `upstream/master`.
+Do not use plain `git pull` or `git merge` commands, as they do not preserve a linear commit history.
+See also [this tutorial](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) to learn more about `git rebase` vs `git merge`.
+
+## Dealing with conflicts after a rebase
+
+If a rebase fails because of conflicts between your changes and the
+code you are rebasing to then git will leave your working directories
+in a bit of a mess and expect you to fix it. Often the conflict is
+simple (eg you and someone else added a new routine at the same place
+in the file) and resolution is simple (have both in the new
+output). Sometimes there is more to untangle if different changes were
+done to the same routine. In either case git has marked that you are
+in a conflict state and work is needed before you can go back to using
+your local git tree as usual.
+
+The git output actually describes what to do (once you are used to how
+to read it). For example:
+
+```console
+$ git rebase master
+First, rewinding head to replay your work on top of it...
+Applying: [util][pystyle] Clean Python style in single file tools
+Using index info to reconstruct a base tree...
+M       util/diff_generated_util_output.py
+M       util/build_docs.py
+Falling back to patching base and 3-way merge...
+Auto-merging util/build_docs.py
+CONFLICT (content): Merge conflict in util/build_docs.py
+Auto-merging util/diff_generated_util_output.py
+error: Failed to merge in the changes.
+Patch failed at 0001 [util][pystyle] Clean Python style in single file tools
+Use 'git am --show-current-patch' to see the failed patch
+
+Resolve all conflicts manually, mark them as resolved with
+"git add/rm <conflicted_files>", then run "git rebase --continue".
+You can instead skip this commit: run "git rebase --skip".
+To abort and get back to the state before "git rebase", run "git rebase --abort".
+```
+
+The last line of this gives the ultimate out. You can abort the rebase
+and figure some other way to proceed. As it says, this is done with:
+
+```console
+$ git rebase --abort
+```
+
+After executing this command you are back to a clean tree with your
+changes intact, but they are still based on whatever the earlier state
+of the repository was. Normally you will have to resolve the conflict
+sometime, but the escape hatch can be useful if you don't have time
+immediately!
+
+In the normal case, read the output to find the file with the
+problem. In this case `Merge conflict in util/build_docs.py` (The merge
+of `util/diff_generated_util_output.py` was successful even though it
+is mentioned in the middle of what looks like error output.)
+
+If the file is opened with an editor the points at which there are
+conflicts will have diff-style change information embedded in to them. For example:
+
+```console
+<<<<<<< HEAD
+import livereload
+
+import docgen.generate
+=======
+import docgen
+import livereload
+>>>>>>> [util][pystyle] Clean Python style in single file tools
+
+```
+
+In this case, the upstream repository's copy of `util/build_docs.py`
+was modified to import `docgen.generate` rather than just `docgen`.
+Because git couldn't automatically merge that change with the one
+we made, it gave up. The code between `<<<<<<< HEAD` and `=======`
+represents the change in the upstream repository and the code between
+`=======` and `>>>>>>>` represents the change in our copy.
+
+These lines have to be edited to get the correct merged
+result and the diff markers removed. There may be multiple points in
+the file where fixes are needed. Once all conflicts have been
+addressed the file can be `git add`ed and once all files addressed the
+rebase continued.
+
+
+After the fix a status report will remind you where you are.
+
+```console
+$ git status
+rebase in progress; onto cb85dc4
+You are currently rebasing branch 'sastyle' on 'cb85dc4'.
+  (all conflicts fixed: run "git rebase --continue")
+
+Changes to be committed:
+  (use "git reset HEAD <file>..." to unstage)
+
+        modified:   diff_generated_util_output.py
+        modified:   build_docs.py
+
+Changes not staged for commit:
+  (use "git add <file>..." to update what will be committed)
+  (use "git checkout -- <file>..." to discard changes in working directory)
+
+        modified:   build_docs.py
+
+```
+
+This gives the same instructions as the original merge failure message
+and gives the comfort that all conflicts were fixed. To finish up you
+need to follow the instructions.
+
+```console
+$ git add build_docs.py
+$ git rebase --continue
+Applying: [util][pystyle] Clean Python style in single file tools
+```
+
+If there were more than one patch outstanding (which isn't usual if
+you use the `commit --amend` flow) then you may get subsequent
+conflicts following the `rebase --continue` as other patches are
+replayed.
+
+You can check the rebase worked as expected by looking at the log to
+see your branch is one commit (or more if there were more) ahead of
+the `master` branch.
+
+```console
+$ git log
+
+commit dd8721d2b1529c575c4aef988219fbf2ecd3fd1b (HEAD -> sastyle)
+Author: Mark Hayter <mark.hayter@gmail.com>
+Date:   Thu Jan 10 09:41:20 2019 +0000
+
+    [util][pystyle] Clean Python style in single file tools
+
+    Result of lintpy.py --fix on the diff and build_docs tools
+
+    Tested with  ./diff_generated_util_output.py master
+
+commit cb85dc42199e925ad09c45d33f6483a14764b93e (upstream/master, origin/master, origin/HEAD, master)
+
+```
+
+This shows the new commit (`HEAD` of the branch `sastyle`) and the
+preceding commit is at the `master` branch (and at the same point as
+`master` on both `origin` and `upstream` so everything is in sync at
+`master`).
+
+At this point the conflicts have been cleared and the local repository can
+be used as expected.
+
+You may find it useful to change the default for the way git reports conflicts in a file. See [Take the pain out of git conflict resolution: use diff3](https://blog.nilbus.com/take-the-pain-out-of-git-conflict-resolution-use-diff3/)
+
+## Downloading a pull request to our local repo
+
+With the commands below, you can checkout a pull request from the upstream repository to
+your local repo.
+
+```console
+$ git fetch upstream pull/{ID}/head:{BRANCH_NAME}
+$ # e.g. git fetch upstream pull/5/head:docgen_review
+$ git checkout {BRANCH_NAME}
+```
+
+### Applying the pull request to the local commit
+
+In some cases, you might need to apply a pull request on top of your local commits.
+For instance, if one user prepares a verification test case and finds a bug.
+Another user fixed it and created a pull request. The first user needs
+to test if the fix works on the new verification environment.
+
+In this case, it is recommended to create a new branch on top of the local
+commit and `rebase`, `cherry-pick`, or `merge` the pull request. Assume you have a branch
+name 'br_localfix' which has an update for the verification environment. If the
+other user created a pull request with ID #345, which has a fix for the
+design bug, then you can apply the pull request into new branch
+'br_localandremote' with the following three methods:
+
+* The `rebase` method:
+
+    ```console
+    $ git checkout -b br_localandremote br_localfix
+    $ git fetch upstream pull/345/head:pr_345
+    $ git rebase pr_345
+    ```
+
+* The `cherry-pick` method:
+
+    ```console
+    $ git checkout -b br_localandremote br_localfix
+    $ git fetch upstream pull/345/head:pr_345
+    $ # find the commit ID from pr_345 that you want to merge: b345232342ff
+    $ git cherry-pick b345232342ff
+    ```
+* The `merge` method:
+
+    ```console
+    $ git fetch upstream pull/345/head:pr_345
+    $ git checkout -b br_localandremote br_localfix
+    $ git merge pr_345
+    ```
+
+The `rebase` method is more convenient than `cherry-pick` if you have more
+than one commit ID to merge. The `merge` method can only be used for local
+testing as the upstream lowRISC repository does not allow merge commits.
+Sometimes, applying other contributor's pull request can result in conflicts
+if both commits have the same change. In that case, you should resolve the conflicts
+and continue the merge. Section [Dealing with conflicts after a rebase](#dealing-with-conflicts-after-a-rebase)
+has detailed guidance on how to solve conflicts.
+
+## Creating updates on top of a pending pull request
+
+In some cases, you might want to directly change other contributor's pull
+request. The process is quite complicated so please follow the instruction below
+step-by-step with caution:
+
+* Step 1: Checkout the other pull request branch
+
+    ```console
+    $ git fetch upstream pull/{ID}/head:{BRANCH_NAME}
+    $ git checkout {BRANCH_NAME}
+    ```
+
+* Step 2: Make necessary changes
+
+    ```console
+    $ git add...
+    $ git commit -m "Add CFG examples to UART specification"
+    ```
+
+* Step 3: Create your GitHub branch for the pull request
+
+    ```console
+    $ git push -u origin {BRANCH_NAME}:<remote_branch_name>
+    ```
+
+    You can use any branch name for the pull request.
+    If you want to the created branch name same as local branch name, this can
+    simply be:
+
+    ```console
+    $ git push -u origin HEAD
+    ```
+
+* Step 4: Create a pull request into the other contributor's forked repository
+
+    To create a pull request in other's forked repository, you can follow the same method
+    as creating a pull request as section [Working in your local
+    repo](#working-in-your-local-repo) explained in details.
+    Once the other contributor's pull request is merged into the upstream
+    repository, you will need to create a pull request in that upstream repository
+    instead.
+
+These are all the steps needed. Once your pull request is reviewed and merged, you will
+be able to see that the commit is also visible in the original pull request.
+
+## Dealing with email notifications
+In your profile settings, under notifications, you can set up whether you get email notifications and what your default email address is.
+Under the "Custom routing" you can choose to get emails from lowRISC repositories to a different email address in case you're active in multiple organizations.
+You can also filter GitHub emails based on the To and CC fields.
+For example, notifications related to the OpenTitan repository will be sent to `opentitan@noreply.github.com`.
+The CC field can include any of the following depending on the type of notification:
+- `assign@noreply.github.com`
+- `author@noreply.github.com`
+- `ci-activity@noreply.github.com`
+- `comment@noreply.github.com`
+- `mention@noreply.github.com`
+- `subscribed@noreply.github.com`
+These can help you set up automatically sorting emails into specific folders.
diff --git a/doc/contributing/hw/comportability/README.md b/doc/contributing/hw/comportability/README.md
new file mode 100644
index 0000000..f89ae1c
--- /dev/null
+++ b/doc/contributing/hw/comportability/README.md
@@ -0,0 +1,695 @@
+# Comportability Definition and Specification
+
+## Document Goals
+
+This document is aimed at laying out the definition of a *Comportable IP design*, i.e. one that is ported to conform to the framework of lowRISC ecosystem IP, suitable for inclusion in compliant designs.
+This is primarily a technical discussion and specification of interface compliance within the framework.
+Separate documents contain or will contain critical elements like coding style, verification, and documentation, but are not the purview of this specification.
+
+A good definition of Comportable can be found in
+[Johnson's Dictionary of the English Language](https://en.wikipedia.org/wiki/A_Dictionary_of_the_English_Language).
+The 1808 miniature edition gives
+["Comportable, a. consistent, suitable, fit"](https://books.google.co.uk/books?id=JwC-GInMrW4C&dq=%22comportable%22&pg=PA45&ci=31%2C225%2C415%2C42&source=bookclip)
+
+![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)
+
+
+
+## Definitions
+
+The table below lists some keywords used in this specification.
+
+| Keyword | Definition |
+| --- | --- |
+| alerts      | Interrupt-type outputs of IP designs that are classified as security critical. These have special handling in the outer chip framework. |
+| comportable | A definition of compliance on the part of IP that is able to plug and play with other IP to form the full chip framework. |
+| CSRs        | Control and Status Registers; loosely the collection of registers within a peripheral which are addressable by the (local) host processor via a chip-wide address map.  Special care is dedicated to the definition and handling of CSRs to maximize software uniformity and re-use, as well as documentation consistency. |
+| framework   | this project concerns itself not only with compliant IP, but also provides a full chip framework suitable for FPGA implementation, and prepared to be the foundation for a full silicon implementation. This could roughly be translated as Top Level Netlist. |
+| interrupts  | Non-security critical signals from peripheral devices to the local host processor within the framework SOC. |
+| MIO         | Multiplexable IO; a pad at the top chip level which can be connected to one of the peripherals' MIO-ready inputs or outputs. |
+| peripheral  | Any comportable IP that is part of the library, outside of the local host processor. |
+
+## Non-Technical Comportability Requirements
+
+All comportable IP must adhere to a few requirements, briefly discussed here.
+
+### License and copyright
+
+All files should include a comment with a copyright message.
+This is normally "lowRISC contributors".
+The style is to not include a year in the notice.
+Files adapted from other sources should retain any copyright messages and include details of the upstream location.
+
+The Apache License, Version 2.0 is the default for all files in the repository.
+Use of other licenses must be noted (and care is needed to ensure compatibility with the rest of the code).
+All files should include a comment line with the SPDX-License-Identifier: tag and the Identifier from the [License List](https://spdx.org/licenses/).
+An additional "Licensed under" line may be used to give a more human readable version.
+If the file is not covered by a SPDX license then the "Licensed under" line is required (note that such files are unlikely to be permitted in the main open source repository).
+
+All files that use the default copyright and license should therefore include the following header (change the comment character as appropriate):
+
+```
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+```
+
+The project has adopted [Hjson](https://hjson.org) for JSON files, extending JSON to allow comments.
+Thus the Hjson files can include the header above.
+If pure JSON must be used for some reason, the "SPDX-License-Identifier:" can be added as the first key after the opening "{".
+Tools developed by the project should accept and ignore this key.
+
+### Coding Style
+
+All IP must follow the [lowRISC Verilog Coding Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
+This style guide sets the definition of agreed-upon SystemVerilog style, requirements and preferences.
+See that document for details.
+It is the goal of lowRISC to create technical collateral to inform when an IP does not conform, as well as assist in the formatting of Verilog to this style.
+The methods and details for this collateral have not been agreed upon yet.
+
+### Documentation
+
+All lowRISC IP must conform to a common specification and documentation format.
+lowRISC will release a template for IP specifications in a separate document for reference.
+It is notable that register tooling auto-creates documentation material for register definitions, address maps, hardware interfaces, etc.
+The hardware interfaces of this process is discussed later in this document.
+
+## Comportable Peripheral Definition
+
+All comportable IP peripherals must adhere to a minimum set of functionality in order to be compliant with the framework that is going to be set around it.
+(An example framework is the [earlgrey top level design](../../../../hw/top_earlgrey/README.md).)
+This includes several mandatory features as well as several optional ones.
+It is notable that the framework contains designs that are neither the local host processor nor peripherals \- for example the power management unit, clock generators.
+These are handled as special case designs with their own specifications.
+Similarly the memory domain is handled separately and in its own specification.
+
+Examples of peripherals that are expected to be in this category include ones with primary inputs and outputs (SPI, I2C, etc);
+offload and specialty units (crypto, TRNG, key manager); timers; analog designs (temperature sensor); as well as bus hosts<sup>1</sup> (DMA).
+
+<sup>1</sup>lowRISC is avoiding the fraught terms master/slave and defaulting to host/device where applicable.
+
+### Feature List
+
+All comportable designs must specify and conform to a list of mandatory features, and can optionally specify and conform to a list of optional features.
+These are briefly summarized in the table below, and are covered individually in the sections that follow.
+For most of these, the definition of the feature is in the form of a configuration file.
+This file is specified later within this document.
+
+| Feature | Mand/Opt | Description |
+| ---     | ---      | --- |
+| Clocking     | mandatory | Each peripheral must specify what its primary functional clock is, and any other clocks needed.  The primary clock is the one driving the bus the peripheral is receiving.  The clocking section lists the available clocks. Other clocks can be designated as needed. |
+| Bus Interfaces | mandatory | A list of the bus interfaces that the peripheral supports. This must contain at least one device entry. More details in the Bus Interfaces section. |
+| Available IO | optional  | Peripherals can optionally make connections to dedicated or multiplexed IO pins; the chip peripheral needs to indicate its module inputs and outputs that are available for this purpose. Details are available in the peripheral IO section below. |
+| Registers    | mandatory | Each peripheral must define its collection of registers in the specified register format.  The registers are automatically generated in the form of hardware, software, and documentation collateral. Details are available in the register section. |
+| Interrupts   | optional  | Peripherals have the option of generating signals that can be used to interrupt the primary processor.  These are designated as a list of signals, and each results in a single wire or bundled output that is sent to the processor to be gathered as part of its interrupt vector input.  Details can be found in the interrupt and alert section. |
+| Alerts       | optional  | Peripherals have the option of generating signals that indicate a potential security threat. These are designated as a list of signals, and each results in a complementary signal pair that is sent to the alert handling module.  Details are found in the interrupt and alert section. |
+| Inter Signal | optional  | Peripherals have the option of struct signals that connect from/to other peripherals, which are called as Inter-Module Signals in OpenTitan. Details are found in the inter module section.
+| (more)       |           | More will come later, including special handling for testability, power management, device entropy, etc. |
+
+![Typical Peripheral Block Diagram](comportability_diagram_peripheral.svg)
+
+**Figure 1**: Typical peripheral communication channels within full chip framework.
+
+In this diagram the auto-generated register submodule is shown within the peripheral IP, communicating with the rest of the chip framework using the TL-UL (see below) bus protocol.
+This register block communicates with the rest of the peripheral logic to manage configuration and status communication with software.
+Also shown is the mandatory clock, and the optional bus (TL-UL) host, interrupts, alerts, and chip IO.
+
+Additionally an optional input `devmode` is shown which represents an indication to the peripheral what mode the SOC is operating in.
+For now this includes only two modes: development (`devmode = 1`) and production (`devmode = 0`).
+This is the beginning of a security feature that will convey the full life cycle mode status to the peripheral.
+In its current form, only the distinction of development mode vs. production mode is required in order to determine how to handle software errors (see the [Register Tooling](../../../../util/reggen/README.md) documentation for details).
+The full definition of life cycle modes will come upon further refinement of the security properties of the SOC.
+
+## Peripheral Feature Details
+
+### Configuration File
+
+Each peripheral contains a configuration file that describes the peripheral features that are mandatory and optional in the above comportability feature list.
+The configuration file format is given below.
+
+### Clocking
+
+Each peripheral specifies how it is clocked and reset.
+This is done with the `clocking` element, whose value is a nonempty list of dictionaries.
+Each dictionary (called a *clocking item*) defines a clock and reset signal.
+The clock signal is the field called `clock`.
+The reset signal is the field called `reset`.
+
+One of the clocking items is called the *primary clock*.
+If there is just one item, that is the primary clock.
+If there are several, exactly one of the items' dictionaries should also have a boolean field called `primary` set to true.
+This primary clock used to clock any device interfaces, indicating to the top level if asynchronous handling of the bus interface is needed.
+
+Resets within the design are **asynchronous active low** (see below).
+Special care will be required for security sensitive storage elements.
+Further instructions on the handling of these storage elements will come at a later date.
+
+For most blocks, each clocking item has one clock and one reset signal.
+However, there are a few blocks where this might not be true (blocks that generate clocks or resets).
+To allow these blocks to be described, the `clock` and `reset` fields of an item are optional.
+However, the primary clock must have both.
+
+#### Details and rationale for asynchronous active low reset strategy
+
+Resets within the design are asynchronous active low, where the assertion of the reset is asynchronous to any clock, but deassertion is synchronized to the clock of the associated storage element.
+The selection of asynchronous active low (as opposed to say synchronous active high) was made based upon a survey of existing design IP, comfort level of project team members, and through security analysis.
+The conclusion roughly was the following:
+
+1. Security storage elements might "leak" sensitive state content, and should be handled with care regardless of reset methodology.
+By "care" an example would be to reset their value synchronously at a time after chip-wide reset, to a value that is randomized so that the Hamming distance between the register value and all zeros cannot produce information available to an attacker.
+2. For control path and other storage elements, the selection of asynchronous active low vs. synchronous active high is often a "religious" topic, with both presenting pros and cons.
+3. Asynchronous active low incurs slightly more area and requires more hand-holding, but is more common.
+4. Synchronous active high is slightly more efficient, but requires the existence of a clock edge to take effect.
+
+Based upon this and the fact that much of the team history was with asynchronous active low reset, we chose that methodology with added requirements that special care be applied for security state, the details of which will come at a later date.
+
+### Bus Interfaces {#bus-interfaces}
+
+Peripherals can connect to the chip bus.
+All peripherals are assumed to have registers, and are thus required to expose at least one device interface on the chip bus.
+Peripherals can act as a bus host on some occasions, though for full chip simplicity the preferred model is for the processor to be primary host.
+An example of a peripheral that acts as a host is a DMA unit.
+
+The arrangement of peripherals' device interfaces into a system-wide address map is controlled by a higher level full-chip configuration file.
+Addresses within the block of addresses assigned to a device interface are defined by the peripheral's configuration file.
+
+The `bus_interfaces` attribute in the configuration file should contain a list of dictionaries, describing the interfaces that the peripheral exposes.
+The full syntax for an entry in the list looks like this:
+```
+{ protocol: "tlul", direction: "device", name: "my_interface" }
+```
+
+For each entry, the `protocol` field gives the protocol that the peripheral uses to connect to the bus.
+All peripherals use TileLink-UL (TileLink-Uncached-Lite, aka TL-UL) as their interface to the framework.
+To signify this, the peripheral must have a protocol of `tlul`.
+The TileLink-UL protocol and its usage within lowRISC devices is given in the
+[TileLink-UL Bus Specification](../../../../hw/ip/tlul/README.md).
+As of this writing, there are no other options, but this field leaves an option for extension in the future.
+
+Each entry must also contain a `direction`.
+This must either be `host` or `device`.
+All bus hosts must use the same clock as the defined primary host clock.
+Each bus host is provided a 4-bit host ID to distinguish hosts within the system.
+This is done by the framework in order to ensure uniqueness.
+The use of the ID within the bus fabric is discussed in the bus specification.
+
+An entry may also include a name.
+This name is added as a prefix to the module's top-level port names.
+This is optional unless there is another entry in the list with the same direction (in which case, the port names would collide).
+
+### Available IO
+
+Each peripheral has the option of designating signals (inputs, outputs, or inouts) available to be used for chip IO.
+The framework determines for each signal if it goes directly to a dedicated chip pin or is multiplexed with signal(s) from other peripherals before reaching a pin.
+
+Designation of available IO is given with the configuration file entries of `available_input_list`, `available_output_list`, and `available_inout_list`.
+These can be skipped, or contain an empty list `[]`, or a comma-separated list of signal names.
+Items on the input list of the form `name` incur a module input of the form `cio_name_i`.
+Items on the output list of the form `name` incur a module output of the form `cio_name_o` as well as an output enable `cio_name_en_o`.
+Items on the inout list of the form `name` incur all three.
+
+#### Multiplexing Feature and Pad Control
+
+In the top level chip framework there is a [pin multiplexing unit (`pinmux`)](../../../../hw/ip/pinmux/README.md) which provides flexible assignment to/from peripheral IO and chip pin IO.
+Comportable peripherals do not designate whether their available IO are hardwired to chip IO, or available for multiplexing.
+That is done at the top level with an Hjson configuration file.
+See the top level specification for information about that configuration file.
+
+In addition, full pad control is not done by the peripheral logic, but is done, by the `pinmux` as well.
+The `pinmux` module provides software configuration control over pad drive strength, pin mapping, pad type (push/pull, open drain, etc).
+
+### Interrupts
+
+Each peripheral has the option of designating output signals as interrupts destined for the local host processor.
+These are non-security-critical signals sent to the processor for it to handle with its interrupt service routines.
+The peripheral lists its collection of interrupts with the `interrupt_list` attribute in the configuration file.
+Each item of the form `name` in the interrupt list expects a module output named `intr_name_o`.
+
+See the section on [Interrupt Handling](#interrupt-handling) below, which defines details on register, hardware, and software uniformity for interrupts within the project.
+
+### Alerts
+
+Each peripheral has the option of designating output signals as security critical alerts destined for the hardware [alert handler module](../../../../hw/top_earlgrey/ip_autogen/alert_handler/README.md).
+These are differential signals (to avoid single point of failure) sent to the alert handler for it to send to the processor for first-line defense handling, or hardware security response if the processor does not act.
+The peripheral lists its collection of alerts with the `alert_list` attribute in the configuration file.
+For each alert in the full system, a corresponding set of signals will be generated in the alert handler to carry out this communication between alert sender and handler.
+
+See the section on [Alert Handling](#alert-handling) below, which defines details on register, hardware, and software uniformity for alerts within the project.
+
+### Inter-module signal
+
+The peripherals in OpenTitan have optional signals connecting between the peripherals other than the interrupts and alerts.
+The peripheral lists its collection of inter-module signals with the `inter_signal_list` attribute in the configuration file.
+The peripheral defines the type of inter-module signals.
+The connection between the modules are defined in the top-level configuration file.
+
+See the section on [Inter Signal Handling](#inter-signal-handling) below for detailed data structure in the configuration file.
+
+### Security countermeasures {#countermeasures}
+
+If this IP block is considered security-critical, it will probably have design features that try to mitigate against attacks like fault injection or side channel analysis.
+These features can be loosely categorised and named with identifiers of the following form:
+
+```
+[UNIQUIFIER.]ASSET.CM_TYPE
+```
+
+Here, `ASSET` is the asset that is being protected.
+This might be secret information like a key, or it might be internal state like a processor's control flow.
+The countermeasure that is providing the protection is named with `CM_TYPE`.
+The `UNIQUIFIER` label is optional and allows to add a custom prefix to make the identifier unique or more concise.
+
+Below are a few examples for generic and more concise labels following this format:
+
+```
+LFSR.REDUN or MASKING_PRNG.LFSR.REDUN
+FSM.SPARSE or HASHING_INTERFACE.FSM.SPARSE
+INTERSIG.MUBI or ALERT.INTERSIG.MUBI
+```
+
+The following standardised assets are defined:
+
+| Asset name | Intended meaning |
+| --- | --- |
+| KEY         | A key (secret data) |
+| ADDR        | An address |
+| DATA_REG    | A configuration data register that doesn't come from software (such as Keccak state) |
+| DATA_REG_SW | A data holding register that is manipulated by software |
+| CTRL_FLOW   | The control flow of software or a module |
+| CTRL        | Logic used to steer hardware behavior |
+| CONFIG      | Software-supplied configuration, programmed through the comportable register interface |
+| LFSR        | A linear feedback shift register |
+| RNG         | A random number generator |
+| CTR         | A counter |
+| FSM         | A finite state machine |
+| MEM         | A generic data memory; volatile or non-volatile |
+| CLK         | A clock |
+| RST         | A reset signal |
+| BUS         | Data transferred on a bus |
+| INTERSIG    | A non-bus signal between two IP blocks |
+| MUX         | A multiplexer that controls propagation of sensitive data |
+| CONSTANTS   | A netlist constant |
+| STATE       | An internal state signal (other than FSM state, which is covered by the FSM label) |
+| TOKEN       | A cryptographic token |
+| LOGIC       | Any logic. This is a very broad category: avoid if possible and give an instance or net name if not. |
+
+The following standardised countermeasures are defined:
+
+| Countermeasure name | Intended meaning | Commonly associated assets |
+| --- | --- | --- |
+| MUBI           | A signal is multi-bit encoded | CTRL, CONFIG, CONSTANTS, INTERSIG |
+| SPARSE         | A signal is sparsely encoded  | FSM |
+| DIFF           | A signal is differentially encoded | CTRL, CTR |
+| REDUN          | There are redundant versions of the asset | ADDR, CTRL, CONFIG, CTR
+| REGWEN         | A register write enable is used to protect the asset from write access | CONFIG, MEM
+| SHADOW         | The asset has a shadow replica to cross-check against | CONFIG
+| REGREN         | A register write enable is used to protect the asset from read access | CONFIG, MEM
+| SCRAMBLE       | The asset is scrambled | CONFIG, MEM
+| INTEGRITY      | The asset has integrity protection from a computed value such as a checksum | CONFIG, REG, MEM
+| CONSISTENCY    | This asset is checked for consistency other than by associating integrity bits | CTRL, RST
+| DIGEST         | Similar to integrity but more computationally intensive, implying a full hash function | CONFIG, REG, MEM
+| LC_GATED       | Access to the asset is qualified by life-cycle state | REG, MEM, CONSTANTS, CONFIG
+| BKGN_CHK       | The asset is protected with a continuous background health check |
+| GLITCH_DETECT  | The asset is protected by an analog glitch detector | CTRL, FSM, CLK, RST
+| SW_UNREADABLE  | The asset is not readable by software | MEM, KEY
+| SW_UNWRITABLE  | The asset is not writable by software | MEM, KEY
+| SW_NOACCESS    | The asset is not writable nor readable by software (SW_UNWRITABLE and SW_UNREADABLE at the same time) | MEM, KEY
+| SIDELOAD       | The asset can be loaded without exposing it to software | KEY
+| SEC_WIPE       | The asset is initialized or cleared using pseudo-random data | KEY, DATA_REG, MEM
+| SCA            | A countermeasure that provides side-channel attack resistance |
+| MASKING        | A more specific version of SCA where an asset is split into shares |
+| LOCAL_ESC      | A local escalation event is triggered when an attack is detected |
+| GLOBAL_ESC     | A global escalation event is triggered when an attack is detected |
+| UNPREDICTABLE  | Behaviour is unpredictable to frustrate repeatable FI attacks |
+| TERMINAL       | The asset goes into a terminal state that no longer responds to any stimulus |
+| COUNT          | The number of operations or items processed is counted which can be checked by software to ensure the correct number have occurred |
+| CM             | Catch-all for countermeasures that cannot be further specified. This is a very broad category: avoid if possible and give an instance or net name if not. |
+
+## Register Handling
+
+The definition and handling of registers is a topic all on its own, and is specified in its [own document](../../../../util/reggen/README.md).
+All lowRISC peripheral designs must conform to this register specification.
+
+## Configuration description Hjson
+
+The description of the IP block and its registers is done in an Hjson file that is specified in the
+[Register Tool document](../../../../util/reggen/README.md).
+All lowRISC peripheral designs must conform to this configuration and register specification.
+
+A description of Hjson (a variant of json) and the recommended style is in the [Hjson Usage and Style Guide](../../style_guides/hjson_usage_style.md).
+
+### Configuration information in the file
+
+The configuration part of the file has the following elements, with a comment as to if required or optional.
+In this example, the IP name is `uart`, though the other configuration fields are contrived and not in-line with the expected functionality of a UART but are shown for edification.
+
+```hjson
+  {
+    name: "uart",
+    clocking: [
+      {clock: "clk_fixed", reset: "rst_fixed_n", primary: true},
+      {clock: "clk", reset: "rst_n"},
+      {clock: "clk_lowpower", reset: "rst_lowpower_n"}
+    ],
+    bus_interfaces: [
+      { protocol: "tlul", direction: "device", name: "regs" }
+    ],
+    available_input_list: [          // optional; default []
+      { name: "rx", desc: "Receive bit" }
+    ],
+    available_output_list: [         // optional; default []
+      { name: "tx", desc: "Transmit bit" }
+    ],
+    available_inout_list: [],        // optional; default []
+    interrupt_list: [                // optional; default []
+      { name: "tx_watermark",  desc: "raised if the transmit FIFO..."}
+      { name: "rx_watermark",  desc: "raised if the receive FIFO..."}
+      { name: "tx_overflow",   desc: "raised if the transmit FIFO..."}
+      { name: "rx_overflow",   desc: "raised if the receive FIFO..."}
+      { name: "rx_frame_err",  desc: "raised if a framing error..."}
+      { name: "rx_break_err",  desc: "raised if break condition..."}
+      { name: "rx_timeout",    desc: "raised if the receiver..."}
+      { name: "rx_parity_err", desc: "raised if the receiver..."}
+    ],
+    alert_list: [                    // optional; default []
+      { name: "fatal_uart_breach", desc: "Someone has attacked the ..."}
+      { name: "recov_uart_frozen", desc: "The UART lines are frozen..." }
+    ],
+    inter_signal_list: [
+      { name: "msg_fifo",
+        struct: "fifo",
+        package: "msg_fifo_pkg",
+        type: "req_rsp",
+        act: "req",
+        width: 1
+      }
+      { name: "suspend",
+        struct: "logic",
+        package: "",
+        type: "uni",
+        act: "rcv",
+        width: 1
+      }
+    ]
+    regwidth: "32", // standard register width
+    register: [
+      // Register information...
+    ]
+  }
+```
+
+### Documentation Output
+
+The following shows the expected documentation format for this example.
+
+*Primary Clock:* `clk_fixed`
+
+*Other clocks:* `clk, clk_lowpower`
+
+*Bus Device Interfaces (TL-UL):* `regs_tl`
+
+*Bus Host Interfaces (TL-UL): none*
+
+*Peripheral Pins available for chip-level IO:*
+
+| Pin name | direction | Description |
+| --- | --- | --- |
+| tx | output | Transmit bit |
+| rx | input | Receive bit |
+
+*Interrupts:*
+
+| Intr Name | Description |
+| --- | --- |
+| `tx_watermark`  | Raised if the transmit FIFO is past the high water mark |
+| `rx_watermark`  | Raised if the receive FIFO is past the high water mark |
+| `tx_overflow`   | Raised if the transmit FIFO has overflowed |
+| `rx_overflow`   | Raised if the receive FIFO has overflowed |
+| `rx_frame_err`  | Raised if a framing error has been detected on receive |
+| `rx_break_err`  | Raised if a break condition is detected on receive |
+| `rx_timeout`    | Raised if the receiver has not received any characters programmable time period |
+| `rx_parity_err` | Raised if the receiver has detected a parity error |
+
+*Security alerts:*
+
+| Alert name | Description |
+| --- | --- |
+| `fatal_uart_breach` | Someone has attacked the UART module |
+| `recov_uart_frozen` | The UART lines are frozen and might be under attack |
+
+### Specifying countermeasures
+
+Countermeasure information can be specified in the register Hjson format.
+This is done with a list with key `countermeasures`.
+Each item is a dictionary with keys `name` and `desc`.
+The `desc` field is a human-readable description of the countermeasure.
+The `name` field should be either of the form `ASSET.CM_TYPE` or `INSTANCE.ASSET.CM_TYPE`.
+Here, `ASSET` and `CM_TYPE` should be one of the values given in the tables in the [Security countermeasures](#countermeasures) section.
+If specified, `INSTANCE` should name a submodule of the IP block holding the asset.
+It can be used to disambiguate in situations such as where there are two different keys that are protected with different countermeasures.
+
+Here is an example specification:
+```hjson
+  countermeasures: [
+    {
+      name: "BUS.INTEGRITY",
+      desc: "End-to-end bus integrity scheme."
+    }
+    {
+      name: "STATE.SPARSE",
+      desc: "Sparse manufacturing state encoding."
+    }
+    {
+      name: "MAIN.FSM.SPARSE",
+      desc: "The main state FSM is sparsely encoded."
+    }
+  ]
+```
+
+## Interrupt Handling
+
+Interrupts are critical and common enough to attempt to standardize across the project.
+Where possible (exceptions for inherited IP that is too tricky to convert) all interrupts shall have common naming, hardware interface, and software interface.
+These are described in this section.
+
+### Event Type Interrupt
+
+Event type interrupts are latched indications of defined peripheral events that have occurred and not yet been addressed by the local processor.
+All interrupts are sent to the processor as active-high level (as opposed to edge) interrupts.
+Events themselves can be edge or level, active high or low, as defined by the associated peripheral.
+For instance, the GPIO module might detect the rising or falling edge of one its input bits as an interrupt event.
+
+The latching of the event is done by the auto-generated register file as described below.
+The clearing of the event is done by a processor write when the handling of the event is completed.
+The waveform below shows the timing of the event occurrence, its latched value, and the clearing by the processor.
+More details follow.
+
+```wavejson
+{
+  signal: [
+    { name: 'Clock',             wave: 'p.............' },
+    { name: 'event',             wave: '0..10.........' },
+    { name: 'INTR_ENABLE',       wave: '1.............' },
+    { name: 'INTR_STATE',        wave: '0...1....0....' },
+    { name: 'intr_o',            wave: '0...1....0....' },
+    { name: 'SW write to clear', wave: '0.......10....' },
+  ],
+  head: {
+    text: 'Interrupt Latching and Clearing',
+  },
+  foot: {
+    text: 'event signaled at cycle 3, state bit cleared in cycle 8',
+    tock: 0
+  },
+}
+```
+
+### Status Type Interrupt
+
+Status type interrupts forwards the internal status signals to the local processor.
+They may or may not be latched inside the hw module.
+The difference between the event type and status type interrupt is the persistence.
+The status type interrupts do not drop the interrupt lines until the cause signals are dropped by the processor or hardware logic
+SW is responsible to mask the interrupts if they want to process the interrupt in a deferred way.
+
+```wavejson
+{
+  signal: [
+    { name: 'Clock',                  wave: 'p.............' },
+    { name: 'event',                  wave: '0..1......0...' },
+    { name: 'INTR_ENABLE',            wave: '1.............' },
+    { name: 'INTR_STATE',             wave: '0...1......0..' },
+    { name: 'intr_o',                 wave: '0...1......0..' },
+    { name: 'SW addresses the cause', wave: '0.........10..' },
+  ],
+  head: {
+    text: 'Interrupt Latching and Clearing',
+  },
+  foot: {
+    text: 'status lasts until processor addresses the cause',
+    tock: 0
+  },
+}
+```
+
+### Interrupts per module
+
+A peripheral generates a separate interrupt for each event and sends them all as bundle to the local processor's interrupt module.
+"Disambiguation", or the determining of which interrupt has woken the processor, is done at the processor in its handler (to be specified eventually in the core processor specification).
+This is as distinct from a model in which each peripheral would send only one interrupt, and the processor would disambiguate by querying the peripheral to figure out which interrupt was triggered.
+
+### Defining Interrupts
+
+The configuration file defined above specifies all that needs to be known about the interrupts in the standard case.
+The following sections specify what comes out of various tools based upon the simple list defined in the above example.
+
+### Register Creation
+
+For every peripheral, by default, three registers are **automatically** created to manage each of the interrupts for that peripheral (as defined in the `interrupt_list` portion of the Hjson file).
+Every interrupt has one field bit for each of three registers.
+(It is an error condition if there are more than 32 interrupts per peripheral.)
+The three registers are the `INTR_STATE` register, the `INTR_ENABLE` register, and the `INTR_TEST` register.
+They are placed at the top of the peripheral's address map in that order automatically by the `reggen` tool.
+
+The `INTR_ENABLE` register is readable and writeable by the CPU (`rw`), with one bit per interrupt which, when true, enables the interrupt of the module to be reported to the output to the processor.
+The `INTR_STATE` register for the event type interrupt is readable by the CPU and each bit may be written with `1` to clear it (`rw1c`), so that a read of the register indicates the current state of all latched interrupts, and a write of `1` to any field clears the state of the corresponding interrupt.
+`INTR_TEST` for the event type interrupt is a write-only (`wo`) register that allows software to test the reporting of the interrupt, simulating a trigger of the original event, the setting of the `INTR_STATE` register, and the raised level of the interrupt output to the processor (modulo the effect of `INTR_ENABLE`).
+No modifications to other portions of the hardware (eg. clearing of FIFO pointers) occurs.
+`INTR_STATE` and `INTR_TEST` for the status type interrupt differ from them in the event type interrupts. The details will be described later (TBD).
+See the next section for the hardware implementation.
+
+The contents of the `INTR_STATE` register do **not** take into consideration the enable value, but rather show the raw state of all latched hardware interrupt events.
+The output interrupt to the processor ANDs the interrupt state with the interrupt enable register before sending to the processor for consideration.
+
+### Interrupt Hardware Implementation
+
+All interrupts as sent to the processor are active-high level interrupts of equal severity<sup>2</sup>.
+Taking an interrupt `foo` as an example, the block diagram below shows the hardware implementation.
+The assumption is that there is an internal signal (call it `event_foo`) that indicates the detection of the event that is to trigger the interrupt.
+The block diagram shows the interaction between that event, the three defining software-facing registers, and the output interrupt `intr_foo_o`.
+
+<sup>2</sup> Higher priority interrupts in the form of a Non-Maskable Interrupt (NMI) are expected to be overlaid in the future.
+
+![Example Interrupt HW](comportability_diagram_intr_hw.svg)
+
+**Figure 2**: Example interrupt `foo` with its three registers and associated HW
+
+In this figure the event is shown coming in from another part of the peripheral hardware.
+The assumption is this event `foo` is one of multiple interrupt events in the design.
+Within the register file, the event triggers the setting of the associated bit in the `INTR_STATE` register to `1`.
+Additionally, a write of `1` of the associated `foo` bit of the `INTR_TEST` register can set the corresponding `INTR_STATE` bit.
+The output of the `INTR_STATE` register becomes the outgoing interrupt to the processor after masking (ANDing) with the value of `INTR_ENABLE`.
+
+Note that the handling of the `ro/rw1c` functionality of the `INTR_STATE` register allows software to control the clearing of the `INTR_STATE` content.
+A write of `1` to the corresponding bit of `INTR_STATE` clears the latched value, but if the event itself is still active, the `INTR_STATE` register will return to true.
+The hardware does not have the ability to clear the latched interrupt state, only software does.
+
+Interrupts sent to the processor are handled by its interrupt controller.
+Within that logic there may be another level of control for enabling, prioritizing, and enumeration.
+Specification of this control is defined in the rv_plic documentation of the corresponding toplevel design.
+
+## Alert Handling
+
+Alerts are another critical and common implementation to standardize for all peripherals.
+Unlike interrupts, there is no software component to alerts at the peripheral, though there is at the hardware alert handler.
+See that [specification](../../../../hw/top_earlgrey/ip_autogen/alert_handler/README.md) for full details.
+A general description of the handling of alerts at the hardware level is given here.
+
+### Alerts per Module
+
+Alerts are sent as a bundled output from a peripheral to the hardware alert handler.
+Each peripheral can send zero or more alerts, where each is a distinguishable security threat.
+Each alert originates in some internal event, and must be specially handled within the peripheral, and then within the alert handler module.
+
+Alerts of comportable IPs in the system must be in either of the following two categories:
+
+1. *Recoverable*, one-time triggered alerts.
+This category is for regular alerts that are due to recoverable error conditions.
+The alert sender transmits one single alert event when the corresponding error condition is asserted.
+
+2. *Fatal* alerts that are continuously triggered until reset.
+This category is for highly critical alerts that are due to terminal error conditions.
+The alert sender continuously transmits alert events until the system is reset.
+
+It is recommended that fatal alerts also trigger local security countermeasures, if they exist.
+For example, a redundantly encoded FSM that is glitched into an invalid state is typically considered to be a fatal error condition.
+In this case, a local countermeasure could be to move the FSM into a terminal error state in order to render the FSM inoperable until the next reset.
+
+The table below lists a few common error conditions and the recommended alert type for each of those errors.
+
+Error Event                                                             | Regular IRQ | Recoverable Alert | Fatal Alert
+------------------------------------------------------------------------|-------------|-------------------|-------------
+ECC correctable in NVM (OTP, Flash)                                     | (x)         | x                 |
+ECC uncorrectable in Flash                                              | (x)         | x                 |
+ECC uncorrectable in OTP                                                | (x)         |                   | x
+Any ECC / Parity error in SRAMs or register files                       | (x)         |                   | x
+Glitch detectors (e.g., invalid FSM encoding)                           | (x)         |                   | x
+Incorrect usage of security IP (e.g., shadowed control register in AES) | (x)         | x                 |
+Incorrect usage of regular IP                                           | x           |                   |
+
+(x): optional
+
+The column "Regular IRQ" indicates whether the corresponding error condition should also send out a regular IRQ.
+A peripheral may optionally send out an IRQ for any alert event, depending on whether this is needed by the programming model to make forward progress.
+Note that while alerts may eventually lead to a system wide reset, this is not guaranteed since the alert response depends on the alert handler configuration.
+
+### Defining Alerts
+
+The Hjson configuration file defined above specifies all that needs to be known about the alerts in the standard case.
+The following sections specify what comes out of various tools based upon the simple list defined in the above example.
+
+In terms of naming convention, alerts shall be given a meaningful name that is indicative of its cause.
+Recoverable alerts must be prefixed with `recov_*`, whereas fatal alerts must be prefixed with `fatal_*`.
+For instance, an uncorrectable parity error in SRAM could be named `fatal_parity_error`.
+
+In cases where many diverse alert sources are bundled into one alert event (see [Alert Hardware Implementation](#alert-hardware-implementation)), it may sometimes be difficult to assign the alert event a meaningful and descriptive name.
+In such cases, it is permissible to default the alert names to just `recov` and/or `fatal`.
+Note that this implies that the peripheral does not expose more than one alert for that type.
+
+### Test Alert Register Creation
+
+For every peripheral, by default, one register named `ALERT_TEST` is **automatically** created.
+
+`ALERT_TEST` is a write-only (`wo`) register that allows software to test the reporting of alerts in the alert handler.
+Every alert of a peripheral has one field bit inside the `ALERT_TEST` register, and each field bit is meant to be connected to the test input of the corresponding `prim_alert_sender` (see next subsection).
+
+### Alert Hardware Implementation
+
+Internal events are sent active-high to a piece of IP within the peripheral called the `prim_alert_sender`.
+One `prim_alert_sender` must be instantiated per distinct alert event, and the `IsFatal` parameter of the alert sender must be set to 1 for fatal alerts (this causes the alert sender to latch the alert until the next system reset).
+
+It is up to the peripheral maintainer to determine what are distinct alert events;
+multiple ones can be bundled depending upon the distinction required within the module (i.e.  high priority threat vs. low level threat).
+As a general guideline, it is recommended that each peripheral bundles alert sources into one or two distinct alerts, for example one fatal and one recoverable alert.
+This helps to keep the total number of alerts (and their physical impact) low at the system level.
+
+It is recommended that comportable IPs with multiple bundled alerts expose a cause register for disambiguation, which is useful for debugging and crash dumps.
+Cause registers for recoverable alerts must either be clearable by SW, or the HW must provide an automatic mechanism to clear them (e.g., upon starting a new transaction initiated by SW).
+Cause registers for fatal alerts must not be clearable in any way and must hence be read-only.
+
+The `prim_alert_sender` converts the event into a differentially encoded signal pair to be routed to the hardware alert handler, as dictated by the details in the
+[alert handler specification](../../../../hw/top_earlgrey/ip_autogen/alert_handler/README.md).
+The alert handler module is automatically generated to have enough alert ports to represent each alert declared in the different included peripheral IP configuration files.
+
+## Inter Signal Handling
+
+Inter-module signal is a term that describes bundled signals connecting instances in the top.
+A few peripherals can be stand-alone such as GPIO and UART peripherals.
+They don't need to talk with other modules other than reporting the interrupts to the main processor.
+By contrast, many peripherals and the main processing unit in OpenTitan communicate between the modules.
+For example, `flash_ctrl` sends requests to the flash macro for read/ program/ erase operations.
+
+Inter-module signal aims to handle the connection by the tool [topgen](../../../../util/topgen/README.md)
+
+### Defining the inter-module signal
+
+The example configuration file above specifies two cases of inter-module signals, `msg_fifo` and `suspend`.
+
+| Attribute | Mand/Opt  | Description |
+| --------- | --------- | ----------- |
+| name      | mandatory | `name` attribute specifies the port name of the inter-module signal. If the type is `req_rsp`, it indicates the peripheral has `name`_req , `name`_rsp ports (with `_i` and `_o` suffix) |
+| struct    | mandatory | The `struct` field defines the signal's data structure. The inter-module signal is generally bundled into `struct packed` type. This `struct` is used with `package` for topgen tool to define the signal. If the inter-module signal is `logic` type, `package` field can be omitted. |
+| package   | optional  |             |
+| type      | mandatory | There are two types of inter-module signal. `req_rsp` is a connection that a module sends requests and the other module returns with responses. `uni` is one-way signal, which can be used as a broadcasting signal or signals that don't need the response. |
+| act       | mandatory | `act` attribute pairs with the `type`. It specifies the input/output of the signal in the peripheral. `req_rsp` type can have `req`(requester) or `rsp`(responder) in `act` field. `uni` type can have `req` or `rcv`(receiver) in `act`. |
+| width     | optional  | If `width` is not 1 or undefined, the port is defined as a vector of struct. It, then, can be connected to multiple peripherals. Currently, `logic` doesn't support the connection to multiple modules if `width` is not 1. |
diff --git a/doc/rm/comportability_specification/comportability_diagram_intr_hw.svg b/doc/contributing/hw/comportability/comportability_diagram_intr_hw.svg
similarity index 100%
rename from doc/rm/comportability_specification/comportability_diagram_intr_hw.svg
rename to doc/contributing/hw/comportability/comportability_diagram_intr_hw.svg
diff --git a/doc/rm/comportability_specification/comportability_diagram_peripheral.svg b/doc/contributing/hw/comportability/comportability_diagram_peripheral.svg
similarity index 100%
rename from doc/rm/comportability_specification/comportability_diagram_peripheral.svg
rename to doc/contributing/hw/comportability/comportability_diagram_peripheral.svg
diff --git a/doc/contributing/hw/design.md b/doc/contributing/hw/design.md
new file mode 100644
index 0000000..14dd0d4
--- /dev/null
+++ b/doc/contributing/hw/design.md
@@ -0,0 +1,135 @@
+# Designing Hardware
+
+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](./comportability/README.md) 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.
+There are times when a more regimented approach is warranted, and those will be highlighted in this document.
+Feedback and improvements are welcome.
+
+
+## Stages of a Design
+
+The life stages of a design within the OpenTitan are described in the [Hardware Development Stages](../../project_governance/development_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.
+
+
+## Concept and RFC
+
+The concept of a design might come from a variety of inspirations: a known required module already slated for future product need; a new design needed for a proposed product feature; an inspiration worthy of being discussed more broadly; perhaps even a new approach to an existing design (higher performance; more secure; etc).
+Regardless of the inspiration, the concept should be codified into a brief proposal with basic features.
+This is as opposed to minor modification proposals to an existing design, which can be handled as a GitHub pull request or issue filing associated with the existing design.
+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_governance/rfc_process.md) process, which handles all such proposals.
+If the RFC potentially contains information that could be certification-sensitive (guidance to be shared), send a note to security@opentitan.org first for feedback.
+The OpenTitan Technical Committee may be able to suggest other collaborators to help with early stage review.
+
+An example of a canonical RFC will be provided *here* (TODO).
+
+
+## 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](./methodology.md) and [documentation methodology](../style_guides/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.
+
+An example of a canonical detailed specification is the pinmux specification which can be found in the TeamDrive under TechnicalSpecifications --> deprecated, for those that have access to that resource.
+(Google Docs that have been converted into Markdown on GitHub are archived here).
+
+Note that when developing OpenTitan security IP, designers should follow the [Secure Hardware Design Guidelines](../../security/implementation_guidelines/hardware/README.md).
+
+## Specification Publication
+
+Once the specification is published to the wider team, the author(s) need to address input, comments, questions, etc., continuing to hone the proposal.
+Other team members should also feel empowered to help address these questions and feedback.
+It is acceptable to keep an unanswered-questions section of the document and move forward where there is agreement.
+
+
+## Initial Skeleton Design
+
+In parallel with publication and review of the specification, the initial skeleton design can commence.
+There are many ways to get past this "big bang" of necessary implementation and get it through eventual code review.
+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](../../../util/reggen/README.md)), 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](./comportability/README.md) equivalent, etc., as indicated by the [design methodology user guide](./methodology.md).
+
+A good example of an initial skeleton design can be seen in [Pull Request #166](https://github.com/lowRISC/opentitan/pull/166) for the AES module.
+
+As part of the GitHub filing process, the Google Doc specification must be converted into a Markdown specification.
+(Tip: there are Google Doc add-ons that can convert the specification into Markdown format).
+Once this is completed, any specifications on the Team Drive should be moved to the deprecated section of the drive, with a link at the top indicating that the document is for historical purposes only.
+This applies only for those specifications that originated on the Drive.
+
+
+## Full Design
+
+As the design develops within the OpenTitan repository, it transitions into "D0", "D1", etc., [design stages](../../project_governance/development_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
+
+Once an IP block is ready, it can be included in the top-level design.
+To do so, follow the steps below.
+
+* Get an agreement on if and how the IP block should be integrated.
+* Ensure that the IP block is of acceptable quality.
+* Ensure that the top level simulation is not adversely affected.
+* Open a Pull Request with the necessary code changes.
+
+If it is not clear on how to proceed, feel free to file an issue requesting assistance.
+
+## Change of Interrupts
+
+Changing the interrupts of an IP block needs some extra work in addition to the RTL change.
+1. If a DV environment exists, its testbench (`tb.sv`) will need the ports connected.
+1. If the IP block has been instantiated in a top-level, that top-level will need re-generating to include the new ports.
+1. The auto-generated DIFs will need re-generating.
+1. The PLIC test should also be updated.
+
+Item 1 is reasonably self-explanatory.
+The sections below show how to do items 2-4.
+
+### Update TOP
+
+```console
+$ make -C hw top
+```
+
+After you revise the IP `.hjson`, IP top module, IP register interface, and DV testbench `tb.sv`, you can re-generate top-levels with the command above.
+This reads the `.hjson` file and updates the interrupt signal and re-generates the PLIC module if needed.
+
+### New DIF
+
+Unfortunately, re-generating TOP does not resolve everything.
+If the interrupt names are changed or new interrupts are introduced, the DIF for the IP block should be re-generated.
+
+```console
+$ ./util/make_new_dif.py --mode=regen --only=autogen
+```
+
+The command above updates the DIF (auto-generated part) under `sw/device/lib/dif/`.
+
+If DIF for the IP block already uses the interrupt enums inside, you need to manually revise the reference.
+In most cases, the interrupt name is `kDif`, followed by the IP name in PascalCase (e.g `SpiDevice`), then `Irq`, then the interrupt name in PascalCase (e.g. `RxFull`).
+For example, `kDifSpiDeviceIrqRxFull`.
+To refer to an interrupt in the top-level PLIC enum, the format is `kTopEarlgreyPlicIrqId`, followed by the IP name in PascalCase, then the interrupt name in PascalCase.
+For example, `kTopEarlgreyPlicIrqIdSpiDeviceRxFull`.
+
+### PLIC Unittest
+
+If the number of interrupts has changed, you need to revise the SW unit test manually.
+
+Open `sw/device/lib/dif/dif_rv_plic_unittest.cc` and change the `RV_PLIC_PARAM_NUM_SRC` macro to the current interrupt number.
+Then, find `RV_PLIC_IE0_*_REG_OFFSET`, `RV_PLIC_IP0_*_REG_OFFSET` and revise the Bit value or add lines below if new `IE`, `IP` registers are added.
diff --git a/doc/contributing/hw/methodology.md b/doc/contributing/hw/methodology.md
new file mode 100644
index 0000000..35fc9bd
--- /dev/null
+++ b/doc/contributing/hw/methodology.md
@@ -0,0 +1,253 @@
+# Design Methodology within OpenTitan
+
+The design methodology within OpenTitan combines the challenges of industry-strength design methodologies with open source ambitions.
+When in conflict, quality must win, and thus we aim to create a final design product that is equal to the quality required from a full production silicon chip tapeout.
+
+## Language and Tool Selection
+
+Starting with the language, the strategy is to use the SystemVerilog language, restricted to a feature set described by the lowRISC
+[Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
+All IP should be developed and delivered under the feature set described by this style guide.
+Inconsistencies or lack of clarity within the style guide should be solved by filing and helping close an issue on the style guide in the
+[lowrisc/style-guides GitHub repo](https://github.com/lowRISC/style-guides).
+Note that when developing OpenTitan security IP, designers should additionally follow the [Secure Hardware Design Guidelines](../../security/implementation_guidelines/hardware/README.md).
+
+For professional tooling, the team has chosen several industry-grade tools for its design signoff process.
+Wherever possible we attempt to remain tool-agnostic, but we must choose a selection of tools as our ground truth for our own confidence of signoff-level assurances.
+As a project we promote other open source methodologies and work towards a future where these are signoff-grade as well.
+The discussions on how the design tools are used and which ones are chosen are given below in separate sections.
+
+## 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](./comportability/README.md).
+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.
+
+TODO: briefly discuss key architectural decisions, and how we came to the conclusion, with pointers to more thorough documentation. Some candidates:
+*   Processor/RISC-V strategy
+*   Bus strategy
+*   Reset strategy
+
+## Defining Design Complete: stages and tracking
+
+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](../../project_governance/development_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
+
+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 [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.
+To automate the process a script [build-docs.sh](https://opentitan.org/guides/getting_started/build_docs.html) is provided for generating the documentation.
+
+The second is the [reggen](../../../util/reggen/README.md) register tool that helps define the methodology and description language for specifying hardware registers.
+These descriptions are used by `build-docs.sh` to 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](../style_guides/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](../../../util/reggen/README.md) 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](./comportability/README.md).
+
+## Linting Methodology
+
+Linting is a productivity tool for designers to quickly find typos and bugs at the time when the RTL is written.
+Capturing fast and efficient feedback on syntactic and semantic (as well as style) content early in the process proves to be useful for high quality as well as consistent usage of the language.
+Running lint is especially useful with SystemVerilog, a weakly-typed language, unlike more modern hardware description languages.
+Running lint is faster than running a simulation.
+
+### Semantic Linting using Verilator (Open Source)
+
+The Verilator tool is open source, thus enabling all project contributors to conveniently download, install and run the tool locally as described [in the installation instructions](../../guides/getting_started/src/setup_verilator.md), without the need for buying a lint tool license.
+
+For developers of design IP, the recommendation is thus to set up the Verilator lint flow for their IP as described in the [Lint Flow README](../../../hw/lint/README.md).
+Developers should run their code through the Verilator lint tool before creating a design pull request.
+Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
+These waivers have to be reviewed as part of the pull request review process.
+
+Note that a pull request cannot be merged if it is not lint-clean, since the continuous integration infrastructure will run Verilator lint on each pull request.
+
+### Style Linting using Verible (Open Source)
+
+To complement the Verilator lint explained above, we also leverage the Verible style linter, which captures different aspects of the code and detects style elements that are in violation of our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
+
+The tool is open source and freely available on the [Verible GitHub page](https://github.com/google/verible/).
+Hence, we recommend IP designers install the tool as described [here](../../guides/getting_started/src/README.md#step-6a-install-verible-optional) and in the [Lint Flow README](../../../hw/lint/README.md), and use the flow locally to close the errors and warnings.
+
+Developers should run their code through the Verible style lint tool before creating a design pull request.
+Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
+These waivers have to be reviewed as part of the pull request review process.
+
+Note that a pull request cannot be merged if it is not lint-clean, since the continuous integration infrastructure will run Verible lint on each pull request.
+
+### Semantic Linting using AscentLint (Sign-Off)
+
+The rule set and capabilities of commercial tools currently still go beyond what open-source tools can provide.
+Hence, we have standardized on the [AscentLint](https://www.realintent.com/rtl-linting-ascent-lint/) tool from RealIntent for sign-off.
+This tool exhibits fast run-times and a comprehensive set of rules that provide concise error and warning messages.
+
+The sign-off lint flow leverages a new lint rule policy named _"lowRISC Lint Rules"_ that has been tailored towards our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
+The lint flow run scripts and waiver files are available in the GitHub repository of this project but, due to the proprietary nature of the lint rules and their configuration, the _"lowRISC Lint Rules"_ lint policy file can not be publicly provided.
+However, the _"lowRISC Lint Rules"_ are available as part of the default policies in AscentLint release 2019.A.p3 or newer (as `LRLR-v1.0.policy`).
+This allows designers with access to this tool to run the lint flow locally on their premises.
+
+If developers of design IP have access to AscentLint, we recommend to set up the AscentLint flow for their IP as described in the [Lint Flow README](../../../hw/lint/README.md), and use the flow locally to close the errors and warnings.
+Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
+These waivers have to be reviewed as part of the pull request review process.
+
+Note that our continuous integration infrastructure does not currently run AscentLint on each pull request as it does with Verilator lint.
+However, all designs with enabled AscentLint targets on the master branch will be run through the tool in eight-hour intervals and the results are published as part of the tool dashboards on the [hardware IP overview page](https://docs.opentitan.org/hw), enabling designers to close the lint errors and warnings even if they cannot run the sign-off tool locally.
+
+Goals for sign-off linting closure per design development stage are given in the [OpenTitan Development Stages](../../project_governance/development_stages.md) document.
+
+Note that cases may occur where the open-source and the sign-off lint tools both output a warning/error that is difficult to fix in RTL in a way that satisfies both tools at the same time.
+In those cases, priority shall be given to the RTL fix that satisfies the sign-off lint tool, and the open-source tool message shall be waived.
+
+## Assertion Methodology
+
+The creation and maintenance of assertions within RTL design code is an essential way to get feedback if a design is being used improperly.
+Common examples include asserting that a full FIFO should never be written to, a state machine doesn't receive an input while in a particular state, or two signals should remain mutually exclusive.
+Often these will eventually result in a downstream error (incorrect data, bus collisions, etc.), but early feedback at the first point of inconsistency gives designers and verifiers alike fast access to easier debug.
+
+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).
+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.
+
+## CDC Methodology
+
+Logic designs that have signals that cross from one clock domain to another unrelated clock domain are notorious for introducing hard to debug problems.
+The reason is that design verification, with its constant and idealized timing relationships on signals, does not represent the variability and uncertainty of real world systems.
+For this reason, maintaining a robust Clock Domain Crossing verification strategy ("CDC methodology") is critical to the success of any multi-clock design.
+
+Our general strategy is threefold:
+maintain a list of proven domain crossing submodules;
+enforce the usage of these submodules;
+use a production-worthy tool to check all signals within the design conform to correct crossing rules.
+The *CDC Methodology document* (TODO:Coming Soon) gives details on the submodules and explains more rationale for the designs chosen.
+
+The tool chosen for this program is not finalized.
+We will choose a sign-off-grade CDC checking tool that provides the features needed for CDC assurance.
+It is understandable that not all partner members will have access to the tool.
+Once chosen, the project will use it as its sign-off tool, and results will be shared in some form (TODO: final decision).
+CDC checking errors can be closed by fixing the code in question (preferred), or waiving the error.
+CDC waivers should be reviewed as part of the pull request review process.
+Details on how to run the tool will be provided once the decision has been finalized.
+
+The team will standardize on a suite of clock-crossing modules that can be used for most multi-clock designs.
+Many of those will be documented in the `hw/ip/prim/doc` directory.
+
+Similar to the linting tool, due to the proprietary nature of the CDC tool, it is possible that not all content towards running the tool will be checked in in the open source repository.
+For those items, we will work with the tool provider to allow other partners to also use the tool.
+When this methodology is finalized the details will be given here. (TODO)
+
+## DFT
+
+Design For Testability is another critical part of any design methodology.
+It is the preparation of a design for a successful manufacturing test regime.
+This includes, but is not limited to, the ability to use scan chains for testing digital logic;
+the optimization of design logic to allow maximum access of test logic for fault coverage;
+the ability to observe and control memory cells and other storage macros;
+the control of analog designs and other items that are often outside the reach of test logic;
+built in self test (BIST) insertion for logic and memories.
+In this context, our primary concern at this stage is what impact does this have on the RTL that makes up the IP in our library.
+
+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 later in this 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.
+
+At this time the DFT methodology for OpenTitan is not finalized.
+The expectation is that the RTL collateral will undergo a DFT introduction -
+likely with the propagation of such signals as `testmode`, `scanmode`, `bistmode`, etc -
+at a stage before final project completion.
+At this point there are a few references to such signals but they are not yet built into a coherent whole.
+At that future time the DFT considerations will be fully documented and carried out throughout all IP.
+
+## Generated Code
+
+The OpenTitan project contains a lot of generated code through a variety of methods.
+Most modern SystemVerilog-based projects work around the weaknesses in the language in such a way.
+But our first goal is to take full advantage of the language as much as possible, and only resort to generated code where necessary.
+
+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 an over-arching 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](../../../util/reggen/README.md))
+are checked in.
+There is an over-arching 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.
+
+## Automatic SV Code Formatting using Verible (Open Source)
+
+The open source Verible tool used for [style linting](#style-linting-using-verible-open-source) also supports an automatic code formatting mode for SystemVerilog.
+The formatter follows our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md) and helps reducing manual code alignment steps.
+
+Note that this formatter is still under development and not entirely production ready yet due to some remaining formatting bugs and discrepancies - hence automatic code formatting is not enforced in CI at this point.
+However, the tool is mature enough for manual use on individual files (i.e., certain edits may have to be manually amended after using it).
+
+The tool is open source and freely available on the [Verible GitHub page](https://github.com/google/verible/).
+Hence, we encourage IP designers to install the tool as described [here](../../guides/getting_started/src/README.md#step-6a-install-verible-optional), and run their code through the formatter tool before creating a design pull request.
+
+The tool can be invoked on specific SystemVerilog files with the following command:
+```shell
+util/verible-format.py --inplace --files <path to SV files>
+
+```
+This is an in-place operation, hence it is recommended to commit all changes before invoking the formatter.
+
+Note that the formatter only edits whitespace.
+The tool performs an equivalency check before emitting the reformatted code to ensure that no errors are introduced.
+
+## Getting Started Designing Hardware
+
+The process for getting started with a design involves many steps, including getting clarity on its purpose, its feature set, authorship, documentation, etc.
+These are discussed in the [Getting Started Designing Hardware](./design.md) document.
+
+## FPGA vs Silicon
+
+One output of the OpenTitan project will be silicon instantiations of hardware functionality described in this open source repository.
+The RTL repository defines design functionality at a level satisfactory to prove the hardware and software functionality in an FPGA (see [user guides](https://docs.opentitan.org/doc/guides/getting_started/)).
+That level is so-called "tapeout ready".
+Once the project reaches that milestone, the team will work with a vendor or vendors to ensure a trustworthy, industry-quality, fully functional OpenTitan chip is manufactured.
+
+It is important that any IP that *can* be open *is* open to ensure maximal trustworthiness and transparency of the final devices.
+
+To that end, OpenTitan will define compliance collateral that ensures correctness - that the FPGA and the eventual silicon work the same.
+Due to fundamental economic and technical limitations, there may, and likely will, be differences between these incarnations.
+Some examples include the following:
+
+* Silicon versions by definition use different technologies for fundamental vendor collateral, including memories, analog designs, pads, and standard cells.
+* Some of the silicon collateral is beyond the so-called "foundry boundry" and not available for open sourcing.
+
+Some IP blocks will undergo hardening of designs to protect them against physical attack to meet security and certification requirements.
+Some of this hardening, for instance in fuses, may be of necessity proprietary.
+These changes will not impact the functionality of the design, but are described in processes unique to an ASIC flow vs. the emulated flow of an FPGA.
+
+Even with these differences, the overriding objective is compliance equivalence between the FPGA and silicon versions.
+This may require instantiation-specific differences in the software implementation of the compliance suite.
+
+Consider the embedded flash macro.
+This design is highly dependent upon the silicon technology node.
+In the open source repository, the embedded flash macro is emulated by a model that approximates the timing one would typically find in silicon.
+It lacks the myriad timing knobs and configuration points required to control the final flash block.
+This necessitates that the compliance suite will have initialization sections for flash that differ between FPGA and silicon.
+
+We consider this demonstration of "security equivalence" to be an open, unsolved problem and are committed to clearly delimiting any differences in the compliance suite implementation.
diff --git a/doc/contributing/hw/vendor.md b/doc/contributing/hw/vendor.md
new file mode 100644
index 0000000..ba29437
--- /dev/null
+++ b/doc/contributing/hw/vendor.md
@@ -0,0 +1,403 @@
+# Work with hardware code in external repositories
+
+OpenTitan is not a closed ecosystem: we incorporate code from third parties, and we split out pieces of our code to reach a wider audience.
+In both cases, we need to import and use code from external repositories in our OpenTitan code base.
+Read on for step-by-step instructions for common tasks, and for background information on the topic.
+
+## Summary
+
+Code in subdirectories of `hw/vendor` is imported (copied in) from external repositories (which may be provided by lowRISC or other sources).
+The external repository is called "upstream".
+Any development on imported in `hw/vendor` code should happen upstream when possible.
+Files ending with `.vendor.hjson` indicate where the upstream repository is located.
+
+In particular, this means:
+
+- If you find a bug in imported code or want to enhance it, report it upstream.
+- Follow the rules and style guides of the upstream project.
+   They might differ from our own rules.
+- Use the upstream mechanisms to do code changes. In many cases, upstream uses GitHub just like we do with Pull Requests.
+- Work with upstream reviewers to get your changes merged into their code base.
+- Once the change is part of the upstream repository, the `util/vendor` tool can be used to copy the upstream code back into our OpenTitan repository.
+
+Read on for the longer version of these guidelines.
+
+Pushing changes upstream first isn't always possible or desirable: upstream might not accept changes, or be slow to respond.
+In some cases, code changes are needed which are irrelevant for upstream and need to be maintained by us.
+Our vendoring infrastructure is able to handle such cases, read on for more information on how to do it.
+
+## Background
+
+OpenTitan is developed in a "monorepo", a single repository containing all its source code.
+This approach is beneficial for many reasons, ranging from an easier workflow to better reproducibility of the results, and that's why large companies like [Google](https://ai.google/research/pubs/pub45424) and Facebook are using monorepos.
+Monorepos are even more compelling for hardware development, which cannot make use of a standardized language-specific package manager like npm or pip.
+
+At the same time, open source is all about sharing and a free flow of code between projects.
+We want to take in code from others, but also to give back and grow a wider ecosystem around our output.
+To be able to do that, code repositories should be sufficiently modular and self-contained.
+For example, if a CPU core is buried deep in a repository containing a full SoC design, people will have a hard time using this CPU core for their designs and contributing to it.
+
+Our approach to this challenge: develop reusable parts of our code base in an external repository, and copy the source code back into our monorepo in an automated way.
+The process of copying in external code is commonly called "vendoring".
+
+Vendoring code is a good thing.
+We continue to maintain a single code base which is easy to fork, tag and generally work with, as all the normal Git tooling works.
+By explicitly importing code we also ensure that no unreviewed code sneaks into our code base, and a "always buildable" configuration is maintained.
+
+But what happens if the imported code needs to be modified?
+Ideally, all code changes are submitted upstream, integrated into the upstream code base, and then re-imported into our code base.
+This development methodology is called "upstream first".
+History has shown repeatedly that an upstream first policy can help significantly with the long-term maintenance of code.
+
+However, strictly following an upstream first policy isn't great either.
+Some changes might not be useful for the upstream community, others might be not acceptable upstream or only applied after a long delay.
+In these situations it must be possible to modify the code downstream, i.e. in our repository, as well.
+Our setup includes multiple options to achieve this goal.
+In many cases, applying patches on top of the imported code is the most sustainable option.
+
+To ease the pain points of vendoring code we have developed tooling and continue to do so.
+Please open an issue ticket if you see areas where the tooling could be improved.
+
+## Basic concepts
+
+This section gives a quick overview how we include code from other repositories into our repository.
+
+All imported ("vendored") hardware code is by convention put into the `hw/vendor` directory.
+(We have more conventions for file and directory names which are discussed below when the import of new code is described.)
+To interact with code in this directory a tool called `util/vendor.py` is used.
+A "vendor description file" controls the vendoring process and serves as input to the `util/vendor` tool.
+
+In the simple, yet typical, case, the vendor description file is only a couple of lines of human-readable JSON:
+
+```command
+$ cat hw/vendor/lowrisc_ibex.vendor.hjson
+{
+  name: "lowrisc_ibex",
+  target_dir: "lowrisc_ibex",
+
+  upstream: {
+    url: "https://github.com/lowRISC/ibex.git",
+    rev: "master",
+  },
+}
+```
+
+This description file essentially says:
+We vendor a component called "lowrisc_ibex" and place the code into the "lowrisc_ibex" directory (relative to the description file).
+The code comes from the `master` branch of the Git repository found at https://github.com/lowRISC/ibex.git.
+
+With this description file written, the `util/vendor` tool can do its job.
+
+```command
+$ cd $REPO_TOP
+$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson --verbose --update
+INFO: Cloning upstream repository https://github.com/lowRISC/ibex.git @ master
+INFO: Cloned at revision 7728b7b6f2318fb4078945570a55af31ee77537a
+INFO: Copying upstream sources to /home/philipp/src/opentitan/hw/vendor/lowrisc_ibex
+INFO: Changes since the last import:
+* Typo fix in muldiv: Reminder->Remainder (Stefan Wallentowitz)
+INFO: Wrote lock file /home/philipp/src/opentitan/hw/vendor/lowrisc_ibex.lock.hjson
+INFO: Import finished
+```
+
+Looking at the output, you might wonder: how did the `util/vendor` tool know what changed since the last import?
+It knows because it records the commit hash of the last import in a file called the "lock file".
+This file can be found along the `.vendor.hjson` file, it's named `.lock.hjson`.
+
+In the example above, it looks roughly like this:
+
+```command
+$ cat hw/vendor/lowrisc_ibex.lock.hjson
+{
+  upstream:
+  {
+    url: https://github.com/lowRISC/ibex.git
+    rev: 7728b7b6f2318fb4078945570a55af31ee77537a
+  }
+}
+```
+
+The lock file should be committed together with the code itself to make the import step reproducible at any time.
+This import step can be reproduced by running the `util/vendor` tool without the `--update` flag.
+
+After running `util/vendor`, the code in your local working copy is updated to the latest upstream version.
+Next is testing: run simulations, syntheses, or other tests to ensure that the new code works as expected.
+Once you're confident that the new code is good to be committed, do so using the normal Git commands.
+
+```command
+$ cd $REPO_TOP
+
+$ # Stage all files in the vendored directory
+$ git add -A hw/vendor/lowrisc_ibex
+
+$ # Stage the lock file as well
+$ git add hw/vendor/lowrisc_ibex.lock.hjson
+
+$ # Now commit everything. Don't forget to write a useful commit message!
+$ git commit
+```
+
+Instead of running `util/vendor` first, and then manually creating a Git commit, you can also use the `--commit` flag.
+
+```command
+$ cd $REPO_TOP
+$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson \
+    --verbose --update --commit
+```
+
+This command updates the "lowrisc_ibex" code, and creates a Git commit from it.
+
+Read on for a complete example how to efficiently update a vendored dependency, and how to make changes to such code.
+
+## Update vendored code in our repository
+
+A complete example to update a vendored dependency, commit its changes, and create a pull request from it, is given below.
+
+```command
+$ cd $REPO_TOP
+$ # Ensure a clean working directory
+$ git stash
+$ # Create a new branch for the pull request
+$ git checkout -b update-ibex-code upstream/master
+$ # Update lowrisc_ibex and create a commit
+$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson \
+    --verbose --update --commit
+$ # Push the new branch to your fork
+$ git push origin update-ibex-code
+$ # Restore changes in working directory (if anything was stashed before)
+$ git stash pop
+```
+
+Now go to the GitHub web interface to open a Pull Request for the `update-ibex-code` branch.
+
+## How to modify vendored code (fix a bug, improve it)
+
+### Step 1: Get the vendored repository
+
+1. Open the vendor description file (`.vendor.hjson`) of the dependency you want to update and take note of the `url` and the `branch` in the `upstream` section.
+
+2. Clone the upstream repository and switch to the used branch:
+
+   ```command
+   $ # Go to your source directory (can be anywhere)
+   $ cd ~/src
+   $ # Clone the repository and switch the branch. Below is an example for ibex.
+   $ git clone https://github.com/lowRISC/ibex.git
+   $ cd ibex
+   $ git checkout master
+   ```
+
+After this step you're ready to make your modifications.
+You can do so *either* directly in the upstream repository, *or* start in the OpenTitan repository.
+
+### Step 2a: Make modifications in the upstream repository
+
+The easiest option is to modify the upstream repository directly as usual.
+
+### Step 2b: Make modifications in the OpenTitan repository
+
+Most changes to external code are motivated by our own needs.
+Modifying the external code directly in the `hw/vendor` directory is therefore a sensible starting point.
+
+1. Make your changes in the OpenTitan repository. Do not commit them.
+
+2. Create a patch with your changes. The example below uses `lowrisc_ibex`.
+
+   ```command
+   $ cd hw/vendor/lowrisc_ibex
+   $ git diff --relative . > changes.patch
+   ```
+
+3. Take note of the revision of the imported repository from the lock file.
+   ```command
+   $ cat hw/vendor/lowrisc_ibex.lock.hjson | grep rev
+    rev: 7728b7b6f2318fb4078945570a55af31ee77537a
+   ```
+
+4. Switch to the checked out upstream repository and bring it into the same state as the imported repository.
+   Again, the example below uses ibex, adjust as needed.
+
+   ```command
+   # Change to the upstream repository
+   $ cd ~/src/ibex
+
+   $ # Create a new branch for your patch
+   $ # Use the revision you determined in the previous step!
+   $ git checkout -b modify-ibex-somehow 7728b7b6f2318fb4078945570a55af31ee77537a
+   $ git apply -p1 < $REPO_BASE/hw/vendor/lowrisc_ibex/changes.patch
+
+   $ # Add and commit your changes as usual
+   $ # You can create multiple commits with git add -p and committing
+   $ # multiple times.
+   $ git add -u
+   $ git commit
+   ```
+
+### Step 3: Get your changes accepted upstream
+
+You have now created a commit in the upstream repository.
+Before submitting your changes upstream, rebase them on top of the upstream development branch, typically `master`, and ensure that all tests pass.
+Now you need to follow the upstream guidelines on how to get the change accepted.
+In many cases their workflow is similar to ours: push your changes to a repository fork on your namespace, create a pull request, work through review comments, and update it until the change is accepted and merged.
+
+### Step 4: Update the vendored copy of the external dependency
+
+After your change is accepted upstream, you can update our copy of the code using the `util/vendor` tool as described before.
+
+## How to vendor new code
+
+Vendoring external code is done by creating a vendor description file, and then running the `util/vendor` tool.
+
+1. Create a vendor description file for the new dependency.
+   1. Make note of the Git repository and the branch you want to vendor in.
+   2. Choose a name for the external dependency.
+      It is recommended to use the format `<vendor>_<name>`.
+      Typically `<vendor>` is the lower-cased user or organization name on GitHub, and `<name>` is the lower-cased project name.
+   3. Choose a target directory.
+      It is recommended use the dependency name as directory name.
+   4. Create the vendor description file in `hw/vendor/<vendor>_<name>.vendor.hjson` with the following contents (adjust as needed):
+
+      ```
+      // Copyright lowRISC contributors.
+      // Licensed under the Apache License, Version 2.0, see LICENSE for details.
+      // SPDX-License-Identifier: Apache-2.0
+      {
+        name: "lowrisc_ibex",
+        target_dir: "lowrisc_ibex",
+
+        upstream: {
+          url: "https://github.com/lowRISC/ibex.git",
+          rev: "master",
+        },
+      }
+      ```
+
+2. Create a new branch for a subsequent pull request
+
+   ```command
+   $ git checkout -b vendor-something upstream/master
+   ```
+
+3. Commit the vendor description file
+
+   ```command
+   $ git add hw/vendor/<vendor>_<name>.vendor.hjson
+   $ git commit
+   ```
+
+4. Run the `util/vendor` tool for the newly vendored code.
+
+   ```command
+   $ cd $REPO_TOP
+   $ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson --verbose --commit
+   ```
+
+5. Push the branch to your fork for review (assuming `origin` is the remote name of your fork).
+
+   ```command
+   $ git push -u origin vendor-something
+   ```
+
+   Now go the GitHub web interface to create a Pull Request for the newly created branch.
+
+## How to exclude some files from the upstream repository
+
+You can exclude files from the upstream code base by listing them in the vendor description file under `exclude_from_upstream`.
+Glob-style wildcards are supported (`*`, `?`, etc.), as known from shells.
+
+Example:
+
+```
+// section of a .vendor.hjson file
+exclude_from_upstream: [
+  // exclude all *.h files in the src directory
+  "src/*.h",
+  // exclude the src_files.yml file
+  "src_files.yml",
+  // exclude some_directory and all files below it
+  "some_directory",
+]
+```
+
+If you want to add more files to `exclude_from_upstream`, just update this section of the `.vendor.hjson` file and re-run the vendor tool without `--update`.
+The repository will be re-cloned without pulling in upstream updates, and the file exclusions and patches specified in the vendor file will be applied.
+
+## How to add patches on top of the imported code
+
+In some cases the upstream code must be modified before it can be used.
+For this purpose, the `util/vendor` tool can apply patches on top of imported code.
+The patches are kept as separate files in our repository, making it easy to understand the differences to the upstream code, and to switch the upstream code to a newer version.
+
+To apply patches on top of vendored code, do the following:
+
+1. Extend the `.vendor.hjson` file of the dependency and add a `patch_dir` line pointing to a directory of patch files.
+   It is recommended to place patches into the `patches/<vendor>_<name>` directory.
+
+    ```
+    patch_dir: "patches/lowrisc_ibex",
+    ```
+
+2. Place patch files with a `.patch` suffix in the `patch_dir`.
+
+3. When running `util/vendor`, patches are applied on top of the imported code according to the following rules.
+
+   - Patches are applied alphabetical order according to the filename.
+     Name patches like `0001-do-someting.patch` to apply them in a deterministic order.
+   - Patches are applied relative to the base directory of the imported code.
+   - The first directory component of the filename in a patch is stripped, i.e. they are applied with the `-p1` argument of `patch`.
+   - Patches are applied with `git apply`, making all extended features of Git patches available (e.g. renames).
+
+If you want to add more patches and re-apply them without updating the upstream repository, add them to the patches directory and re-run the vendor tool without `--update`.
+
+## How to manage patches in a Git repository
+
+Managing patch series on top of code can be challenging.
+As the underlying code changes, the patches need to be refreshed to continue to apply.
+Adding new patches is a very manual process.
+And so on.
+
+Fortunately, Git can be used to simplify this task.
+The idea:
+
+- Create a forked Git repository of the upstream code
+- Create a new branch in this fork.
+- Commit all your changes on top of the upstream code into this branch.
+- Convert all commits into patch files and store them where the `util/vendor` tool can find and apply them.
+
+The last step is automated by the `util/vendor` tool through its `--refresh-patches` argument.
+
+1. Modify the vendor description file to add a `patch_repo` section.
+   - The `url` parameter specifies the URL to the fork of the upstream repository containing all modifications.
+   - The `rev_base` is the base revision, typically the `master` branch.
+   - The `rev_patched` is the patched revision, typically the name of the branch with your changes.
+
+    ```
+    patch_repo: {
+      url: "https://github.com/lowRISC/riscv-dbg.git",
+      rev_base: "master",
+      rev_patched: "changes",
+    },
+    ```
+
+2. Create commit and push to the forked repository.
+   Make sure to push both branches to the fork: `rev_base` **and** `rev_patched`.
+   In the example above, this would be (with `REMOTE_NAME_FORK` being the remote name of the fork):
+
+   ```command
+   git push REMOTE_NAME_FORK master changes
+   ```
+
+3. Run the `util/vendor` tool with the `--refresh-patches` argument.
+   It will first check out the patch repository and convert all commits which are in the `rev_patched` branch and not in the `rev_base` branch into patch files.
+   These patch files are then stored in the patch directory.
+   After that, the vendoring process continues as usual: changes from the upstream repository are downloaded if `--update` passed, all patches are applied, and if instructed by the `--commit` flag, a commit is created.
+   This commit now also includes the updated patch files.
+
+To update the patches you can use all the usual Git tools in the forked repository.
+
+- Use `git rebase` to refresh them on top of changes in the upstream repository.
+- Add new patches with commits to the `rev_patched` fork.
+- Remove patches or reorder them with Git interactive rebase (`git rebase -i`).
+
+It is important to update and push *both* branches in the forked repository: the `rev_base` branch and the `rev_patched` branch.
+Use `git log rev_base..rev_patched` (replace `rev_base` and `rev_patched` as needed) to show all commits which will be turned into patches.
diff --git a/doc/contributing/style_guides/README.md b/doc/contributing/style_guides/README.md
new file mode 100644
index 0000000..adae309
--- /dev/null
+++ b/doc/contributing/style_guides/README.md
@@ -0,0 +1,13 @@
+# Style Guides
+
+OpenTitan's CI enforces what it can of the following style guides to avoid conflict, toil and confusion.
+However, code reviewers are also responsible for enforcing coding style guidelines and best practices.
+
+- [RISC-V Assembly Style Guide](./asm_coding_style.md)
+- [C and C++ Coding Style Guide](./c_cpp_coding_style.md)
+- [Hjson Usage and Style Guide](./hjson_usage_style.md)
+- [Markdown Usage and Style Guide](./markdown_usage_style.md)
+- [OTBN Assembly Style Guide](./otbn_style_guide.md)
+- [Python Coding Style Guide](./python_coding_style.md)
+- [Verilog Coding Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
+- [SystemVerilog Coding Style Guide for Design Verification (DV)](https://github.com/lowRISC/style-guides/blob/master/DVCodingStyle.md)
diff --git a/doc/contributing/style_guides/asm_coding_style.md b/doc/contributing/style_guides/asm_coding_style.md
new file mode 100644
index 0000000..2d768fc
--- /dev/null
+++ b/doc/contributing/style_guides/asm_coding_style.md
@@ -0,0 +1,362 @@
+# RISC-V Assembly Style Guide
+
+## Basics
+
+### Summary
+
+OpenTitan needs to implement substantial functionality directly in RISC-V assembly.
+This document describes best practices for both assembly `.S` files and inline assembly statements in C and C++.
+It also codifies otherwise unwritten style guidelines in one central location.
+
+This document is not an introduction to RISC-V assembly; for that purpose, see the [RISC-V Assembly Programmer's Manual](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md).
+
+Assembly is typically very specialized; the following rules do not presume to describe every use-case, so use your best judgment.
+
+This style guide is specialized for R32IMC, the ISA implemented by Ibex.
+As such, no advice is provided for other RISC-V extensions, though this style guide is written such that advice for other extensions could be added without conflicts.
+
+## General Advice
+
+### Register Names
+
+***When referring to a RISC-V register, they must be referred to by their ABI names.***
+
+See the [psABI Reference](https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#register-convention) for a reference to these names.
+
+Example:
+```S
+  // Correct:
+  li a0, 42
+  // Wrong:
+  li x10, 42
+```
+
+This rule can be ignored when the ABI meaning of a register is unimportant, e.g., such as when clobbering all 31 general-purpose registers.
+
+### Pseudoinstructions
+
+***When performing an operation for which a pseudoinstruction exists, that pseudoinstruction must be used.***
+
+Pseudoinstructions make RISC-V's otherwise verbose RISC style more readable; for consistency, these must be used where possible.
+
+Example:
+```S
+  // Correct:
+  sw t0, _my_global, t1
+  // Wrong:
+  la t1, _my_global
+  sw t0, 0(t1)
+
+  // Correct:
+  ret
+  // Wrong:
+  jr ra
+```
+
+### Operation-with-immediate mnemonics
+
+***Do not use aliases for operation-with-immediate instructions, like `add rd, rs, imm`.***
+
+Assemblers usually recognize instructions like `add t0, t1, 5` as an alias for `addi`. These should be avoided, since they are confusing and a potential source of errors.
+
+Example:
+```S
+  // Correct:
+  addi t0, t1, 0xf
+  ori  a0, a0, 0x4
+  // Wrong:
+  add  t0, t1, 0xf
+  or   a0, a0, 0x4
+```
+
+### Loading Addresses
+
+***Always use `la` to load the address of a symbol; always use `li` to load an address stored in a `#define`.***
+
+Some assemblers allow `la` with an immediate expression instead of a symbol, allowing a form of symbol+offset.
+However, support for this behavior is patchy, and the semantics of PIC `la` with immediate are unclear (in PIC mode, `la` should perform a GOT lookup, not a `pc`-relative load).
+
+### Jumping into C
+
+***Jumping into a C function must be done either with a `call` instruction, or, if that function is marked `noreturn`, a `tail` instruction.***
+
+The RISC-V jump instructions take a "link register", which holds the return address (this should always be `zero` or `ra`), and a small `pc`-relative immediate.
+For jumping to a symbol, there are two user-controlled settings: "near" or "far", and "returnable" (i.e., a link register of `zero` or `ra`).
+The mnemonics for these are:
+- `j sym`, for a near non-returnable jump.
+- `jal sym`, for a near returnable jump.
+- `tail sym`, for a far non-returnable jump (i.e., a non-unwinding tail-call).
+- `call sym`, for a far returnable jump (i.e., function calls).
+
+Far jumps are implemented in the assembler by emitting `auipc` instructions as necessary (since the jump-and-link instruction takes only a small immediate).
+Jumps into C should always be treated as far jumps, and as such use the `call` instruction, unless the C function is marked `noreturn`, in which case `tail` can be used.
+
+Example:
+```S
+  call _syscall_start
+
+  tail _crt0
+```
+
+### Control and Status Register (CSR) Names
+
+***CSRs defined in the RISC-V spec must be referred to by those names (like `mstatus`), while custom non-standard ones must be encapsulated in a `#define`.***
+
+Naturally, if a pseudoinstruction exists to read that CSR (like `rdtime`) that one should be used, instead.
+
+`#define`s for CSRs should be prefixed with `CSR_<design>_`, where `<design>` is the name of the design the CSR corresponds to.
+
+Recognized CSR prefixes:
+- `CSR_IBEX_` - A CSR specific to the Ibex core.
+- `CSR_OT_` - A CSR specific to the OpenTitan chip, beyond the Ibex core.
+
+Example:
+```S
+  csrr t0, mstatus
+
+  #define CSR_OT_HMAC_ENABLED ...
+  csrw CSR_OT_HMAC_ENABLED, 0x1
+```
+
+### Load and Store From Pointer in Register
+
+***When loading and storing from a pointer in a register, prefer to use `n(reg)` shorthand.***
+
+In the case that a pointer is being read without an offset, prefer `0(reg)` over `(reg)`.
+
+```S
+  // Correct:
+  lw t3, 8(sp)
+  sb t3, 0(a0)
+  // Wrong:
+  lw t3, sp, 8
+  sb t3, a0
+```
+
+### Compressed Instruction Mnemonics
+
+***Do not use compressed instruction mnemonics.***
+
+While Ibex implements the RISC-V C extension, it is expected that the toolchain will automatically compress instructions where possible.
+
+Of course, this advice should be ignored when it is necessary to prove that a certain block of instructions does not exceed a particular width.
+
+### "Current Point" Label
+
+***Do not use the current point (`.`) label.***
+
+The current point label does not look like a label, and can be easily missed during review.
+
+### Label Names
+
+***Local labels (for control flow) should start with `.L_`.***
+
+This is the convention for private symbols in ELF files.
+After the prefix, labels should be `snake_case` like other symbols.
+
+```S
+.L_my_label:
+  beqz a0, .L_my_label
+```
+
+## `.S` Files
+
+This advice applies specifically to `.S` files, as well as globally-scoped assembly in `.c` and `.cc` files.
+
+While this is is already implicit, we only use the `.S` extension for assembly files; not `.s` or `.asm`. Note that `.s` actually means something else; `.S`
+files have the preprocessor run on them; `.s` files do not.
+
+### Indentation
+
+Assembly files must be formatted with all directives indented two spaces, except for labels.
+Comments should be indented as usual.
+
+There is no mandated requirement on aligning instruction operands.
+
+Example:
+```S
+_trap_start:
+  csrr a0, mcause
+  sw x1, 0(sp)
+  sw x2, 4(sp)
+  // ...
+```
+
+### Comments
+
+***Comments must use either the `//` or `/* */` syntaxes.***
+
+Every function-like label which is meant to be called like a function (*especially* `.global`s) should be given a Doxygen-style comment.
+While Doxygen is not suited for assembly, that style should be used for consistency.
+See the [C/C++ style guide](./c_cpp_coding_style.md) for more information.
+
+Comments should be indented to match the line immediately after. For example:
+```S
+  // This comment is correctly indented.
+  call foo
+
+// This one is not.
+  call foo
+```
+
+All other advice for writing comments, as in the C/C++ style guide, also applies.
+
+
+### Declaring a Symbol
+
+***All "top-level" symbols must have the correct preamble and footer of directives.***
+
+To aid the disassembler, every function must follow the following template:
+
+```S
+  /**
+   * Comment describing what my function does
+   */
+  .section .some_section  // Optional if the previous symbol is in this setion.
+  .balign 4
+  .global my_function  // Only for exported symbols.
+  .type my_function, @function
+my_function:
+  // Instructions and stuff.
+  .size my_function, .-my_function
+```
+
+Note that `.global` is not spelled with the legacy `.globl` spelling.
+If the symbol represents a global variable that does not consist of encoded RISC-V instructions, `@function` should be replaced with `@object`, so that the disassembler does not disassemble it as code.
+Thus, interrupt vectors, although not actually functions, are marked with  `@function`.
+
+The first instruction in the function should immediately follow the opening label.
+
+### Register usage
+
+***Register usage in a "function" that diverges from the RISC-V function call ABI must be documented.***
+
+This includes non-standard calling conventions, non-standard clobbers, and other behavior not expected of a well-behaved RISC-V function.
+Non-standard input and output registers should use Doxygen's `param[in] reg` and
+`param[out] reg` annotations, respectively.
+
+Within a function, whether or not it conforms to RISC-V's calling convention, comments should be present to describe the assignment of logical values to registers.
+
+Example:
+```S
+  /**
+   * Compute some stuff, outputing a 96-bit integer.
+   *
+   * @param[out] a0 bits [31:0] of the result.
+   * @param[out] a1 bits [63:32] of the result.
+   * @param[out] a2 bits [95:64] of the result.
+   */
+  .balign 4
+  .global compute_stuff
+  .type compute_stuff, @function
+compute_stuff:
+  // a0 is to be used as an accumulator, which will be returned as-is.
+  li a0, 0xdeadbeef
+  // t0 is a loop variable.
+  li t0, 0x0
+1:
+  // ...
+  bnez t0, 1b
+
+  li   a1, 0xbeefcafe
+  li   a2, 0xcafedead
+  ret
+  .size compute_stuff, .-compute_stuff
+```
+
+### Ending an Instruction Sequence
+
+***Every code path within an assembly file must end in a non-linking jump.***
+
+Assembly should be written such that the program counter can't wander off past the written instructions.
+As such, all assembly should be ended with `ret` (or any of the protection ring returns like `mret`), an infinite `wfi` loop, or an instruction that is guaranteed to trap and not return, like an `exit`-like syscall or `unimp`.
+
+Example:
+```S
+loop_forever:
+  wfi
+  j loop_forever
+```
+
+Functions may end without a terminator instruction if they are intended to fall
+through to the next one, so long as this is explicitly noted in a comment.
+
+### Alignment Directives
+
+***Do not use `.align`; use `.p2align` and `.balign` as the situation requires.***
+
+The exact meaning of `.align` depends on architecture; rather than asking readers to second-guess themselves, use alignment directives with strongly-typed arguments.
+
+Example:
+```S
+  // Correct:
+  .balign 8 // 8-byte aligned.
+  tail _magic_symbol
+
+  // Wrong:
+  .align 8 // Is this 8-byte aligned, or 256-byte aligned?
+  tail _magic_symbol
+```
+
+### Inline Binary Directives
+
+***Always use `.byte`/`.2byte`/`.4byte`/`.8byte` for inline binary data.***
+
+`.word`, `.long`, and friends are confusing, for the same reason `.align` is.
+
+If a sequence of zeroes is required, use `.zero count`, instead.
+
+### The `.extern` Directive
+
+***All symbols are implicitly external unless defined in the current file; there is no need to use the `.extern` directive.***
+
+`.extern` was previously allowed to "bring" symbols into scope, but GNU-flavored assemblers ignore it.
+Because it is not checked, it can bit-rot, and thus provides diminishing value.
+
+## Inline Assembly
+
+This advice applies to function-scope inline assembly in `.c` and `.cc` files.
+For an introduction on this syntax, check out [GCC's documentation](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html).
+
+### When to Use
+
+***Avoid inline assembly as much as possible, as long as correctness and readability are not impacted.***
+
+Inline assembly is best reserved for when a high-level language cannot express what we need to do, such as expressing complex control flow or talking to the hardware.
+If a compiler intrinsic can achieve the same effect, such as `__builtin_clz()`, then that should be used instead.
+
+> The compiler is *always* smarter than you; only in the rare case where it is not, assembly should be used instead.
+
+### Formatting
+
+Inline assembly statements must conform to the following formatting requirements, which are chosen to closely resemble how Google's clang-format rules format function calls.
+- Neither the `asm` or `__asm__` keyword is specified in C; the former must be used, and should be `#define`d into existence if not supported by the compiler.
+  C++ specifies `asm` to be part of the grammar, and should be used exclusively.
+- There should not be a space after the `asm` qualfiers and the opening parentheses:
+  ```c
+  asm(...);
+  asm volatile(...);
+  ```
+- Single-instruction `asm` statements should be written on one line, if possible:
+  ```c
+  asm volatile("wfi");
+  ```
+- Multiple-instruction `asm` statements should be written with one instruction per line, formatted as follows:
+  ```c
+  asm volatile(
+    "my_label:"
+    "  la   sp, _stack_start;"
+    "  tail _crt0;"
+    ::: "memory");
+  ```
+- The colons separating register constraints should be surrounded with spaces, unless there are no constraints between them, in which case they should be adjacent.
+  ```c
+  asm("..." : "=a0"(foo) :: "memory");
+  ```
+
+### Non-returning `asm`
+
+***Functions with non-returning `asm` must be marked as `noreturn`.***
+
+C and C++ compilers are, in general, not supposed to introspect `asm` blocks, and as such cannot determine that they never return.
+Functions marked as never returning should end in `__builtin_unreachable()`, which the compiler will usually turn into an `unimp`.
diff --git a/doc/contributing/style_guides/c_cpp_coding_style.md b/doc/contributing/style_guides/c_cpp_coding_style.md
new file mode 100644
index 0000000..aa9c110
--- /dev/null
+++ b/doc/contributing/style_guides/c_cpp_coding_style.md
@@ -0,0 +1,556 @@
+# C and C++ Coding Style Guide
+
+## Basics
+
+### Summary
+
+C and C++ are widely used languages for (embedded) software.
+
+Our C and C++ style guide follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), with some exceptions and clarifications.
+
+As with all style guides the intention is to:
+*   promote consistency across projects
+*   promote best practices
+*   increase code sharing and re-use
+
+
+### Terminology Conventions
+
+Unless otherwise noted, the following terminology conventions apply to this
+style guide:
+
+*   The word ***must*** indicates a mandatory requirement.
+    Similarly, ***do not*** indicates a prohibition.
+    Imperative and declarative statements correspond to ***must***.
+*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
+    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
+    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
+*   The word ***may*** indicates a course of action is permitted and optional.
+*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
+
+## Shared C and C++ Style Guide
+
+We use the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) for both C and C++ code.
+The following exceptions and additions to this style guide apply to both C and C++ code.
+
+### Pointers
+
+***When declaring pointer types, the asterisk (`*`) should be placed next to the variable name, not the type.***
+
+Example:
+
+```c
+int *ptr;
+```
+
+### Formatting of loops and conditionals
+
+***Single-statement blocks are not allowed. All conditions and loops must use braces.***
+
+Example:
+```c
+if (foo) {
+  do_something();
+}
+```
+### Infinite loops
+
+Prefer `while(true){}` infinite loops rather than `for(;;)`
+
+### Comments
+
+Non-doc comments should use the `// C99-style` for consistency with C++.
+
+<!-- To render a backtick in inline code in markdown, you need to double the surrounding backticks.
+https://daringfireball.net/projects/markdown/syntax#code -->
+Variables mentioned in comments should be delimited with backtick (`` ` ``) characters.
+Example:
+
+```c
+// `ptr` can never be NULL for reasons.
+```
+
+Documentation comments should use Doxygen-style `/** ... */` blocks. Examples:
+
+```c
+/**
+ * This function sorts an int array very quickly.
+ */
+void sort(int array[], size_t size){
+  // Loop through the array moving higher numbers to the end.
+  for (size_t i = 0; i < size; ++i>){
+    ...
+  }
+}
+```
+
+This also applies to `struct` and `enum` members:
+
+```c
+/**
+ * Boot data stored in the flash info partition.
+ */
+typedef struct boot_data {
+  /**
+   * SHA-256 digest of boot data.
+   *
+   * The region covered by this digest starts immediately after this field and
+   * ends at the end of the entry.
+   */
+  hmac_digest_t digest;
+  ...
+  /**
+   * Boot data identifier.
+   */
+  uint32_t identifier;
+  ...
+} boot_data_t;
+```
+
+Note also [Public function (API) documentation](#public-function-api-documentation) below.
+
+### TODO Comments
+***TODO comments should be in the format `TODO: message`.***
+
+***TODO comments which require more explanation should reference an issue.***
+
+It is recommended to use fully-qualified issue numbers or URLs when referencing issues or pull requests.
+
+TODO comments should not indicate an assignee of the work.
+
+Example:
+
+```c
+// TODO: This algorithm should be rewritten to be more efficient.
+// (Bug lowrisc/reponame#27)
+```
+
+### Included files
+
+***`#include` directives must, with exceptions, be rooted at `$REPO_TOP`.***
+
+Every `#include` directive must be rooted at the repository base, including files in the same directory.
+This helps the reader quickly find headers in the repository, without having to worry about local include-search rules.
+
+Example: `my/device/library.c` would start with a directive like the following:
+
+```c
+#include "my/device/library.h"
+```
+
+This rule does not apply to generated headers, since they do not yet have a designated location in the source tree, and are instead included from ad-hoc locations.
+Until these questions are resolved, these includes must be marked as follows:
+```c
+#include "my_generated_file.h"  // Generated.
+```
+This convention helps readers distinguish which files they should not expect to find in-tree.
+
+The above rules also do not apply to system includes, which should be included by the names dictated by the ISO standard, e.g. `#include <stddef.h>`.
+
+### Linker Script- and Assembly-Provided Symbols
+
+Some C/C++ programs may need to use symbols that are defined by a linker script or in an external assembly file.
+Referring to linker script- and assembly-provided symbols can be complex and error-prone, as they don't quite work like C's global variables.
+We have chosen the following approach based on the examples in [the binutils ld manual](https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html).
+
+If you need to refer to the symbol `_my_linker_symbol`, the correct way to do so is with an incomplete extern char array declaration, as shown below.
+It is good practice to provide a comment that directs others to where the symbol is actually defined, and whether the symbol should be treated as a memory address or another kind of value.
+```c
+/**
+ * `_my_linker_symbol` is defined in the linker script `sw/device/my_feature.ld`.
+ *
+ * `_my_linker_symbol` is a memory address in RAM.
+ */
+extern char _my_linker_symbol[];
+```
+
+A symbol's value is exposed using its address, and declaring it this way allows you to use the symbol where you need a pointer.
+```c
+char my_buffer[4];
+memcpy(my_buffer, _my_linker_symbol, sizeof(my_buffer));
+```
+
+If the symbol has been defined to a non-address value (usually using `ABSOLUTE()` in a linker script, or `.set` in assembly), you must cast the symbol to obtain its value using `(intptr_t)_my_linker_symbol`.
+You must not dereference a symbol that has non-address value.
+
+### Public function (API) documentation
+
+***It is recommended to document public functions, classes, Methods, and data structures in the header file with a Doxygen-style comment.***
+
+The first line of the comment is the summary, followed by a new line, and an optional longer description.
+Input arguments and return arguments should be documented with `@param` and `@return` if they are not self-explanatory from the name. Output arguments should be documented with `@param[out]`.
+
+The documentation tool will also render markdown within descriptions, so backticks should be used to get monospaced text.
+It can also generate references to other named declarations using `#other_function` (for C-style declarations), or `ns::foo` (for C++ declarations).
+
+Example:
+
+```c
+/**
+ * Do something amazing
+ *
+ * Create a rainbow and place a unicorn at the bottom of it. `pots_of_gold`
+ * pots of gold will be positioned on the east end of the rainbow.
+ *
+ * Can be recycled with #recycle_rainbow.
+ *
+ * @param pots_of_gold Number of gold pots to place next to the rainbow
+ * @param unicorns Number of unicorns to position on the rainbow
+ * @param[out] expiration_time Pointer to receive the time the rainbow will last in seconds.
+ * @return 0 if the function was successful, -1 otherwise
+ */
+int create_rainbow(int pots_of_gold, int unicorns, int *expiration_time);
+```
+
+### Polyglot headers
+
+***Headers intended to be included from both languages must contain `extern` guards; `#include`s should not be wrapped in `extern "C" {}`.***
+
+A *polyglot header* is a header file that can be safely included in either a `.c` or `.cc` file.
+In particular, this means that the file must not depend on any of the places where C and C++ semantics disagree.
+For example:
+- `sizeof(struct {})` and `sizeof(true)` are different in C and C++.
+- Function-scope `static` variables generate lock calls in C++.
+- Some libc macros, like `static_assert`, may not be present in C++.
+- Character literals type as `char` in C++ but `int` in C.
+
+Such files must be explicitly marked with `extern` guards like the following, starting after the file's `#include`s.
+```
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Declarations...
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+```
+Moreover, all non-system `#includes` in a polyglot header must also be of other polyglot headers.
+(In other words, all C system headers may be assumed to be polyglot, even if they lack guards.)
+
+Additionally, it is forbidden to wrap `#include` statements in `extern "C"` in a C++ file.
+While this does correctly set the ABI for a header implemented in C, that header may contain code that subtly depends on the peculiarities of C.
+
+This last rule is waived for third-party headers, which may be polyglot but not declared in our style.
+
+### X Macros
+
+In order to avoid repetitive definitions or statements, we allow the use of [X macros](https://en.wikipedia.org/wiki/X_Macro) in our C and C++ code.
+
+Uses of X Macros should follow the following example, which uses this pattern in a switch definition:
+```c
+#define MANY_FIELDS(X) \
+  X(1, 2, 3)           \
+  X(4, 5, 6)
+
+int get_field2(int identifier) {
+#define ITEM_(id_field, data_field1, data_field2) \
+  case id_field:                               \
+    return data_field2;
+
+  switch (identifier) {
+    MANY_FIELDS(ITEM_)
+    default:
+      return 0;
+  }
+}
+```
+This example expands to a case statement for each item, which returns the `data_field2` value where the passed in identifier matches `id_field`.
+
+X macros that are not part of a header's API should be `#undef`ed after they are not needed.
+Similarly, the arguments to an X macro, if they are defined in a header, should be `#undef`ed too.
+This is not necessary in a `.c` or `.cc` file, where this cannot cause downstream namespace pollution.
+
+## C++ Style Guide {#cxx-style-guide}
+
+### C++ Version {#cxx-version}
+
+C++ code should target C++14.
+
+### Aggregate Initializers
+
+***C++20-style designated initializers are permitted in C++ code, when used with POD types.***
+
+While we target C++14, both GCC and Clang allow C++20 designated initializers in C++14-mode, as an extension:
+```
+struct Foo { int a, b; };
+
+Foo f = { .a = 1, .b = 42, };
+```
+
+This feature is fairly mature in both compilers, though it varies from the C11 variant in two ways important ways:
+  - It can only be used with structs and unions, not arrays.
+  - Members must be initialized in declaration order.
+
+As it is especially useful with types declared for C, we allow designated initializers whenever the type is a plain-old-data type, and:
+  - All members are public.
+  - It has no non-trivial constructors.
+  - It has no `virtual` members.
+
+Furthermore, designated initializers do not play well with type deduction and overload resolution.
+As such, they are forbidden in the following contexts:
+- Do not call overloaded functions with a designated initializer: `overloaded({ .foo = 0 })`.
+  Instead, disambiguate with syntax like `T var = { .foo = 0 }; overloaded(var);`.
+- Do not use designated initializers in any place where they would be used for type deduction.
+  This includes `auto`, such as `auto var = { .foo = 0 };`, and a templated argument in a template function.
+
+It is recommended to only use designated initializers with types which use C-style declarations.
+
+### Naming
+**Structs, Classes and Methods**
+As stated by the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Type_Names), the names of Structs, Classes and Methods must be in `CamelCase` format.
+
+**Variable and Class Members Naming**
+As stated by the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Variable_Names), the names of variables (including function parameters) and data members must be in `lower_snake_case` format. Data members of Classes (but not Structs) additionally have trailing underscores, unless the variable represents a constant that should follow the [rule](#function-enum-struct-and-typedef-naming).
+
+For example:
+```cpp
+// in animal.cpp
+public Class AnimalInfo{
+  ...
+private:
+  uint32_t number_of_paws_;
+  std::string name_;
+public:
+  static constexpr uint32_t kMaxNumOfPaws=100;
+
+  void SetAnimalName(std::string new_name);
+};
+```
+
+### Features
+
+Avoid using `C-style` features in C++ code, here are some examples:
+- Use `C++-style` casting (`static_cast`, `reinterpret_cast`, etc) rather than `C-style` casting.
+- Use the `constexpr` keyword to define constants rather than `const`.
+- Use the `nullptr` keyword rather than `NULL`.
+- Use `std::endl` rather than `'\n'`.
+- Use `new` and `delete` rather than `malloc()` and `free()`.
+- Use smart pointers rather than pointers. Refer to [Ownership and Smart Pointers](https://google.github.io/styleguide/cppguide.html#Ownership_and_Smart_Pointers) for more details.
+
+## C Style Guide
+
+The [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) targets C++, but it can also be used for C code with minor adjustments.
+Consequently, C++-specific rules don't apply.
+In addition to the shared C and C++ style guide rules outlined before, the following C-specific rules apply.
+
+### C Version
+
+***C code should target C11.***
+
+The following nonstandard extensions may be used:
+*   Inline assembly
+*   Nonstandard attributes
+*   Compiler builtins
+
+It is recommended that no other nonstandard extensions are used.
+
+Any nonstandard features that are used must be compatible with both GCC and Clang.
+
+### Function, enum, struct and typedef naming
+
+***Names of functions, `enum`s, `struct`s, and `typedef`s must be `lower_snake_case`.***
+
+This rule deviates from the Google C++ style guide to align closer with a typical way of writing C code.
+
+***All symbols in a particular header must share the same unique prefix.***
+
+"Prefix" in this case refers to the identifying string of words, and not the specific type/struct/enum/constant/macro-based capitalisation.
+This rule also deviates from the Google C++ style guide, because C does not have namespaces, so we have to use long names to avoid name clashes.
+Symbols that have specific, global meaning imparted by an external script or specification may break this rule.
+For example:
+```c
+// in my_unit.h
+extern const int kMyUnitMaskValue = 0xFF;
+
+typedef enum { kMyUnitReturnOk } my_unit_return_t;
+
+my_unit_return_t my_unit_init(void);
+```
+
+***The names of enumeration constants must be prefixed with the name of their respective enumeration type.***
+
+Again, this is because C does not have namespaces.
+The exact capitalisation does not need to match, as enumeration type names have a different capitalisation rule to enumeration constants.
+For example:
+```c
+typedef enum my_wonderful_option {
+  kMyWonderfulOptionEnabled,
+  kMyWonderfulOptionDisabled,
+  kMyWonderfulOptionOnlySometimes
+} my_wonderful_option_t;
+```
+
+### C-specific Keywords
+
+C11 introduces a number of underscore-prefixed keywords, such as `_Static_assert`, `_Bool`, and `_Noreturn`, which do not have a C++ counterpart.
+These should be avoided in preference for macros that wrap them, such as `static_assert`, `bool`, and `noreturn`.
+
+### Constants and Preprocessor Macros
+**Constants**
+  Prefer using a `enum` to define named constants rather than Preprocessor Macros or even `const int` in `C` because:
+  - It appears in the symbol table which improves debugging in contrast to Macros.
+  - It can be used as `case` labels in a `switch` statement in contrast to `const int`.
+  - It can be used as the dimension of global arrays in contrast to `const int`.
+  - It doesn't use any memory as well as Macros.
+
+  Note that, if the constant will be used in assembly code, then Preprocessor macros would be the best choice.
+
+**Macros**
+Macros are often necessary and reasonable coding practice C (as opposed to C++) projects.
+In contrast to the recommendation in the Google C++ style guide, exporting macros as part of the public API is allowed in C code.
+A typical use case is a header with register definitions.
+
+**Function-like Macros**
+Function-like macros should be avoided whenever possible since they are error-prone. Where they are necessary, they should be hygienic. Here are some useful tips:
+- Expand the macro arguments between brackets `()` as the caller could use an expression as an argument.
+- Variables local to the macro must be named with a trailing underscore `_`.
+- Wrap up multiline macros inside a block `do { ... } while (false)` to make them expand to a single statement.
+    For example:
+    ```c
+    #define CHECK(condition, ...)                      \
+    do {                                               \
+      if (!(condition)) {                              \
+        ...                                            \
+      }                                                \
+    } while (false)
+    ```
+- Don't finish macros with a semicolon `;` to force the caller to include it.
+
+### Aggregate Initialization
+
+C99 introduces designated initializers: when initializing a type of struct, array, or union type, it is possible to *designate* an initializer as being for a particular field or array index.
+For example:
+```c
+my_struct_t s = { .my_field = 42 };
+int arr[5] = { [3] = 0xff, [4] = 0x1b };
+```
+With judicious use, designated initializers can make code more readable and robust; struct field reordering will not affect downstream users, and weak typing will not lead to surprising union initialization.
+
+When initializing a struct or union, initializers within *must* be designated; array-style initialization (or mixing designated and non-designated initializers) is forbidden.
+
+Furthermore, the nested forms of designated initialization are forbidden (e.g., `.x.y = foo` and `.x[0] = bar`), to discourage initialization of deeply nested structures with flat syntax.
+This may change if we find cases where this initialization improves readability.
+
+When initializing an array, initializers *may* be designated when that makes the array more readable (e.g., lookup tables that are mostly zeroed).
+Mixing designated and non-designated initializers, or using nested initializers, is still forbidden.
+
+### Function Declarations
+
+***All function declarations in C must include a list of the function's parameters, with their types.***
+
+C functions declared as `return_t my_function()` are called "K&R declarations", and are type compatible with any list of arguments, with any types.
+Declarations of this type allow type confusion, especially if the function definition is not available.
+
+The correct way to denote that a function takes no arguments is using the parameter type `void`.
+For example `return_t my_function(void)` is the correct way to declare that `my_function` takes no arguments.
+
+The parameter names in every declaration should match the parameter names in the function definition.
+
+### Inline Functions
+
+Functions that we strongly wish to be inlined, and which are part of a public interface, should be declared as an inline function.
+This annotation serves as an indication to the programmer that the function has a low calling overhead, despite being part of a public interface.
+Presence---or lack---of an `inline` annotation does not imply a function will---or will not---be inlined by the compiler.
+
+[C11](#c-version) standardised inline functions, learning from the mistakes in C++ and various nonstandard extensions.
+This means there are many legacy ways to define an inline function in C.
+We have chosen to follow how C11 designed the `inline` keyword.
+
+The function definition is written in a header file, with the keyword `inline`:
+```c
+// in my_inline.h
+inline int my_inline_function(long param1) {
+  // implementation
+}
+```
+
+There should be exactly one compilation unit with a compatible `extern` declaration of the same function:
+```c
+// in my_inline.c
+#include <my_inline.h>
+extern int my_inline_function(long param1);
+```
+
+Any compilation unit that includes `my_inline.h` must be linked to the compilation unit with the extern declarations.
+This ensures that if the compiler chooses not to inline `my_inline_function`, there is a function definition that can be called.
+This also ensures that the function can be used via a function pointer.
+
+### Static Declarations
+
+Declarations marked `static` must not appear in header files.
+Header files are declarations of public interfaces, and `static` definitions are copied, not shared, between compilation units.
+
+This is especially important in the case of a polyglot header, since function-local static declarations have different, incompatible semantics in C and C++.
+
+Functions marked `static` must not be marked `inline`.
+The compiler is capable of inlining static functions without the `inline` annotation.
+
+### `volatile` Type Qualifier
+
+Do not use `volatile` in production, i.e. non-test, silicon creator code unless you are implementing a library explicitly for this purpose like `sec_mmio`, `abs_mmio`, or `hardened`.
+See [guidance for volatile](./guidance_for_volatile.md) for more details.
+When in doubt, please do not hesitate to reach out by creating a GitHub issue (preferably with the "Type:Question" label).
+
+### Nonstandard Attributes
+
+The following nonstandard attributes may be used:
+*   `section(<name>)` to put a definition into a given object section.
+*   `weak` to make a symbol definition have weak linkage.
+*   `interrupt` to ensure a function has the right prolog and epilog for interrupts (this involves saving and restoring more registers than normal).
+*   `packed` to ensure a struct contains no padding.
+*   `warn_unused_result`, to mark functions that return error values that should be checked.
+
+It is recommended that other nonstandard attributes are not used, especially where C11 provides a standard means to accomplish the same thing.
+
+All nonstandard attributes must be supported by both GCC and Clang.
+
+Nonstandard attributes must be written before the declaration, like the following example, so they work with both declarations and definitions.
+
+```c
+__attribute__((section(".crt"))) void _crt(void);
+```
+
+### Nonstandard Compiler Builtins
+
+In order to avoid a total reliance on a single compiler, any nonstandard compiler builtins (also known as intrinsics) should be used via a single canonical definition.
+This ensures changes that add compatibility for other compilers are less invasive, as we already have a function that includes a full implementation.
+
+All nonstandard builtins should be supported by both GCC and Clang.
+Compiler builtin usage is complex, and it is recommended that a compiler engineer reviews any code that adds new builtins.
+
+In the following, `__builtin_foo` is the *builtin name* and `foo` is the corresponding *general name*.
+
+***The use of nonstandard compiler builtins must be hidden using a canonical, compatible definition.***
+
+There are two ways of providing this canonical definition, depending on what the builtin does.
+
+For builtins that correspond to a C library function, the general name must be available to the linker, as the compiler may still insert a call to this function.
+Unfortunately, older versions of GCC do not support the `__has_builtin()` preprocessor function, so compiler detection of support for these builtins is next to impossible.
+In this case, a standards-compliant implementation of the general name must be provided, and the compilation unit should be compiled with `-fno-builtins`.
+
+For builtins that correspond to low-level byte and integer manipulations, an [inline function](#inline-functions) should be provided with a general name, which contains a call to the builtin name itself, or an equivalent implementation.
+Only the general name may be called by users: for instance, `uint32_t __builtin_bswap32(uint32_t)` must not be called, instead users should use `inline uint32_t bswap32(uint32_t x)`.
+Where the general name is already taken by an incompatible host or device library symbol, the general name can be prefixed with the current C namespace prefix, for instance `inline uint32_t bitfield_bswap32(uint32_t x)` for a function in `bitfield.h`.
+Where the general name is a short acronym, the name may be expanded for clarity, for instance `__builtin_ffs` may have a canonical definition named `bitfield_find_first_set`.
+Where there are compatible typedefs that convey additional meaning (e.g. `uint32_t` vs `unsigned int`), these may be written instead of the official builtin types.
+
+For builtins that cannot be used via a compatible function definition (e.g. if an argument is a type or identifier), there should be a single canonical preprocessor definition with the general name, which expands to the builtin.
+
+## Code Lint
+
+The clang-format tool can check for adherence to this style guide.
+The repository contains a `.clang-format` file which configures clang-format according to the rules outlined in this style guide.
+
+You can run clang-format on you changes by calling `git clang-format`.
+
+```sh
+cd $REPO_TOP
+# make changes to the code ...
+git add your_modified_file.c
+# format the staged changes
+git clang-format
+```
+
+To reformat the whole tree the command `./bazelisk.sh run //quality:clang_format_fix` can be used.
diff --git a/doc/contributing/style_guides/guidance_for_volatile.md b/doc/contributing/style_guides/guidance_for_volatile.md
new file mode 100644
index 0000000..a2e5264
--- /dev/null
+++ b/doc/contributing/style_guides/guidance_for_volatile.md
@@ -0,0 +1,151 @@
+# Guidance for `volatile` in OpenTitan Silicon Creator Code
+
+## TL;DR
+
+Do not use `volatile` in production, i.e. non-test, silicon creator code unless you are implementing a library explicitly for this purpose like `sec_mmio`, `abs_mmio`, or `hardened`.
+Specifically, do not use `volatile` for
+
+*   Accessing memory-mapped registers,
+    *   _Instead, use `sec_mmio` or `abs_mmio`_.
+*   Hardening purposes.
+    *   _Instead, use the primitives in `hardened.h`._
+*   Variables initialized outside the lifetime of the program, e.g. a pointer to some data in flash or retention SRAM, or
+    *   _Instead, declare as non-`const `and non-`volatile`. Use a getter to enforce `const`-ness if needed._
+*   Pointers created out of thin air.
+    *   _Compiler cannot optimize the first access (assuming that the pointer doesn't point to something already known by the compiler)._
+
+Do use `volatile` if a variable is shared between an interrupt/exception handler and the rest of the on-target test program to ensure correctness.
+*   _Since silicon creator code does not use handlers in the conventional sense, this essentially limits `volatile` usage to on-target tests._
+
+When in doubt, please do not hesitate to reach out by creating a GitHub issue (preferably with the "Type:Question" label).
+
+## Introduction
+
+The goal of this document is to provide guidance for using `volatile` in OpenTitan Silicon Creator code, i.e. `rom`, `rom_ext`, related tests, and examples.
+There are several reasons for this guidance:
+
+*   `volatile` is contagious and, similar to `const`-correctness, `volatile`-correctness can be hard to achieve and maintain. Once a variable is declared as `volatile`, the `volatile` keyword must appear throughout the call stack in all other variables that will reference the same object.
+*   `volatile` pointers cannot be passed as arguments to standard functions such as `memcpy`, `memset`, etc. Casting `volatile`-ness away in such cases results in undefined behavior. This is an easy-to-make but hard-to-catch mistake.
+*   Semantic meaning of `volatile` is not very clear and it can have significant effects on the size and efficiency of the generated code. Often what we want is `volatile` access or forced loads/stores as opposed to the type of the variable being `volatile`. `abs_mmio` and `sec_mmio` libraries, for example, implement `volatile` access for memory mapped registers.
+
+Review the links provided in the References section for more information and some criticism on `volatile`.
+
+## An Intuitive Definition of `volatile`
+
+Consider the following toy example:
+```
+static bool my_var = false;
+
+void my_function(void) {
+    my_var = true;
+    // Code that doesn't touch `my_var`
+    ...
+    if (my_var) {  // <- `my_var` must be `true`.
+        ...
+    } else {
+        ...
+    }
+}
+```
+Since the value of `my_var` is guaranteed to be `true`, an optimizing compiler can optimize away the comparison and the `else` branch.
+Now, assume we add an ISR that resets `my_var`:
+```
+static bool my_var = false;
+
+// `my_isr` can modify `my_var` at any time but the compiler doesn't know that.
+void my_isr(void) {
+    my_var = false;
+}
+
+void my_function(void) {
+    my_var = true;
+    // Code that doesn't touch `my_var` and doesn't call `my_isr()`.
+    ...
+    if (my_var) {  // <- The compiler would think that `my_var` must be `true`.
+        ...
+    } else {
+        ...
+    }
+}
+```
+Using the same kind of analysis, an optimizing compiler would reach the same conclusion as in the previous example, i.e. optimize away the comparison along the else branch, and break our program.
+This is where `volatile` comes into play:
+```
+// `volatile`: `my_var` may be modified in ways unknown to the implementation.
+static volatile bool my_var = false;
+
+// Indeed, `my_isr` can modify `my_var` at any time.
+void my_isr(void) {
+    my_var = false;
+}
+
+void my_function(void) {
+    my_var = true;
+    // Code that doesn't touch `my_var` and doesn't call `my_isr()`.
+    ...
+    if (my_var) {  // <- Cannot remove this check since `my_var` may be modified
+        ...        //    in ways unknown to the implementation.
+    } else {
+        ...
+    }
+}
+```
+In this case, an optimizing compiler cannot optimize away the comparison since the value of `my_var` may have changed since it has been set to `true`.
+
+## Rule 1: No `volatile` in Production Silicon Creator C Code[^1]
+
+Cases like the example above, however, do not typically appear in _production, i.e. non-test,_ silicon creator C code since we don't enable interrupts and our handlers shut down the chip instead of returning.
+Thus, `volatile` should not be used in _production_ silicon creator C code.
+The following subsections cover the four most relevant cases where `volatile` might be considered and the rationale behind this guidance.
+
+### Memory-Mapped Registers
+
+`abs_mmio` and `sec_mmio` libraries already encapsulate `volatile` access semantics.
+When accessing memory mapped registers, any new code must use `abs_mmio` or `sec_mmio` libraries instead of declaring `volatile` pointers or performing `volatile` accesses by casting at the point of dereferencing.
+
+### Hardening
+
+Do not use `volatile` for hardening purposes.
+OpenTitan has invested considerably to define simple and predictable building blocks and to leverage them to harden various patterns in `rom` and `rom_ext`.
+See `hardened.h` for the hardening primitives we have.
+
+### `const` and Non-`const` Variables Initialized Outside the Lifetime of a Program
+
+There are several cases in OpenTitan silicon creator code where a variable with static storage duration is initialized either partially or completely in a way that isn’t visible to the C source code overriding the static initializer of the C object.
+A `const` example to this case is the definition of `kManifest` in `sw/device/silicon_creator/lib/manifest_def.c`.
+`kManifest` is an aggregate object of type `manifest_t` that resides in flash memory whose actual value at runtime is different from the initializer in source code because the binary is modified by the build system before it is loaded into flash memory.
+Non-`const` examples include the `struct`s in the `static_critical` section of the main SRAM.
+
+Such variables must be declared as
+*   non-`const`, and
+    *   _Rationale: Even if the object resides in read-only memory, it must not be declared as a `const`-qualified type. If `const` is used, the compiler is not required to allocate space for the object and may replace reads with the value specified in the initializer. If needed, `const` related compiler diagnostics must be enabled by providing a getter that returns a `const` pointer to the actual object instead of exposing the non-`const` declaration._
+*   non-`volatile`.
+    *   _Rationale: The `volatile` qualifier prevents various optimizations and the use of `memcpy`, `memset`, and many other standard functions. The specification states that "All objects with static storage duration shall be initialized (set to their initial values) before program startup. The manner and timing of such initialization are otherwise unspecified." (C11, section 5.1.2, paragraph 1). By not using `volatile` we’re no longer informing the compiler that the value of the object "may be modified in ways unknown to the implementation" (C11, section 6.7.3, paragraph 7). In principle, this could be problematic in situations where the implementation is allowed to assume that the object contains its initial value, e.g. when the object is `const` or when performing LTO and the compiler assumes that it has visibility into the whole program (using the `-fwhole-program` flag in gcc)_[^2]_. Since neither of these are true in this case, it seems safe to conclude that the compiler has to be conservative and cannot assume that the object has not been modified since the program startup._
+
+### Pointers Created Out of Thin Air
+
+OpenTitan secure boot consists of at least three stages: `rom`, `rom_ext`, and the first owner boot stage.
+All boot stages except `rom` are stored in flash and all in-flash boot stages are required to start with a "manifest" so that their integrity and authenticity can be verified.
+Since OpenTitan uses a fixed flash layout, `rom` and `rom_ext` exactly know the next stage's manifest's location in flash.
+Since this address is not part of their images, `rom` and `rom_ext` create pointers essentially out of thin air using functions like `_const manifest_t *boot_policy_manifest_a_get(void)_`.
+If the pointers in question do not point to something already known by the compiler, an optimizing compiler cannot optimize away the first access.
+Thus, there is no need to declare such pointers as `volatile`.
+
+## Rule 2: Use `volatile` in Tests For Correctness
+
+`rom` and `rom_ext` do not enable interrupts and their handlers shut down the chip instead of returning.
+Tests, however, can define their custom interrupt/exception handlers for their own purposes.
+In cases where a variable is shared between a handler and the rest of the program, it must be declared as `volatile` to ensure correctness.
+
+## References
+
+*   [Final draft of C11](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)
+*   [Deprecating volatile - JF Bastien - CppCon 2019](https://www.youtube.com/watch?v=KJW_DLaVXIY): A longer discussion on `volatile` and c++ with a good set of demotivating examples in the first 20+ minutes
+*   [LLVM Language Reference Manual — LLVM 16.0.0git documentation](https://llvm.org/docs/LangRef.html#volatile-memory-accesses)
+*   PRs that implement the guidance in this doc: [#15253](https://github.com/lowRISC/opentitan/pull/15253), [#15286](https://github.com/lowRISC/opentitan/pull/15286), [#15287](https://github.com/lowRISC/opentitan/pull/15287), [#15302](https://github.com/lowRISC/opentitan/pull/15302), [#15303](https://github.com/lowRISC/opentitan/pull/15303).
+
+## Notes
+
+[^1]: This rule does not apply to libraries written specifically to leverage or encapsulate `volatile` semantics such as `abs_mmio`, `sec_mmio`, or `hardened`.
+
+[^2]: We can prevent such optimizations by adding `asm volatile("" : : : "memory")` at the entry point when using LTO but that seems unnecessary in practice. `-fwhole-program` is not supported by Clang and the more fine-grained flags that Clang has for similar purposes don't seem to interact with this issue.
diff --git a/doc/contributing/style_guides/hjson_usage_style.md b/doc/contributing/style_guides/hjson_usage_style.md
new file mode 100644
index 0000000..9212bfe
--- /dev/null
+++ b/doc/contributing/style_guides/hjson_usage_style.md
@@ -0,0 +1,222 @@
+# Hjson Usage and Style Guide
+
+## Basics
+
+### Summary
+
+Json files are used to provide input data to many of the tools.
+The preference is to use [Hjson](https://hjson.org/), which is a variation of regular JSON that is easier to write.
+In particular, it allows the quote marks to be left off the key names, it allows a single string to be quoted with triple quote marks and flow over multiple lines (which is often needed in text descriptions) and it allows comments using the # or // style.
+
+This guide covers the enhancements provided by Hjson 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
+
+
+### Terminology Conventions
+
+Unless otherwise noted, the following terminology conventions apply to this style guide:
+
+*   The word ***must*** indicates a mandatory requirement.
+    Similarly, ***do not*** indicates a prohibition.
+    Imperative and declarative statements correspond to ***must***.
+*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
+    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
+    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
+*   The word ***may*** indicates a course of action is permitted and optional.
+*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
+
+### Style Guide Exceptions
+
+***Justify exceptions with a comment.***
+
+No style guide is perfect.
+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.
+
+
+## Hjson file format
+
+Hjson is a variation of regular JSON that is easier to write.
+There are parsers in a number of languages and the tools make extensive use of the `hjson` package provided for Python 3.
+A full description can be found on the [Hjson website](https://hjson.org/), but the main features that make it convenient are that it keeps files cleaner by allowing the quote marks to be left off the key names, it enables long descriptive text by allowing a single string to flow over multiple lines and it allows comments using the # or // style.
+
+For example:
+
+```hjson
+  key1: "value1",
+  // Now a key with a long value
+  key2: '''
+        A long descriptive value2 that can
+        span over multiple lines
+        '''
+```
+
+### Text Format
+
+Where possible, please restrict Hjson text to the ASCII character set to avoid downstream tool issues.
+Unicode may be used when referring to proper names.
+
+### File delimiters and header
+
+***Use `{}` to delimit the file***
+
+***Include a header comment with copyright and license information***
+
+The file must start with a `{` and end with a `}` to be well-formed json.
+In both cases, these should be on a single line and have no indentation.
+Anything enclosed should have two space indentation.
+(Hjson allows these to be omitted but this is not recommended style.)
+
+In most cases, before the opening `{` the file should start with a comment containing the copyright and license details and the SPDX-License-Identifier.
+
+```hjson {.good}
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+{
+  // details...
+}
+```
+
+In cases where the file may need to be parsed by a standard JSON parser, the comments must be omitted, but the SPDX license information should be provided as a top-level key/value using pure JSON syntax and ignored by any tool.
+
+```json {.good}
+
+{
+  "SPDX-License-Identifier": "Apache-2.0",
+  ...
+}
+```
+
+### Simple key-value entries
+
+***Use unquoted alphanumeric strings for keys***
+
+***Include all values in quote marks***
+
+Single entries are of the form `key: "value"` Keys must be alphanumeric strings and should not be in quotes.
+(Hjson allows this.
+The quotes may be included as an exception to the style guide if there is an expectation that the file needs to be parsed with a more traditional JSON parser.)
+The valid keys for each tool are described in the tool documentation.
+The style is for a simple value to be in quotes (even if it is a number) and the tool should manage any type conversions.
+
+In some cases Hjson allows the quotes to be omitted from values, but this is not recommended because the value string will not be terminated by a comma so there is potential for confusion when multiple key-value pairs are put on the same line.
+For example:
+
+```hjson {.good}
+      // This is recommended usage and will work as expected
+      { name: "fred", tag: "2", desc: "fred has tag 2" }
+```
+
+But:
+
+```hjson {.bad}
+      // This will cause confusion. The value for tag
+      // is the rest of the line after the colon
+      // so desc is not defined and the close } is lost
+      { name: "fred", tag: 2, desc: "fred has tag 2" }
+```
+
+### Groups of entries
+
+***Use two character indentation for items in groups***
+
+Groups of entries are made by enclosing a comma separated list of key/value pairs in {}.
+For example
+
+```hjson {.good}
+    { name:"odd", value:"1", desc:"odd parity"}
+```
+
+In most cases, a group will be too long for a single line, particularly where it has a good descriptive string.
+In that case, the entry should contain one key-value pair per line with a 2 character indent from the opening bracket.
+The closing bracket should not have the extra indentation.
+
+```hjson {.good}
+    {
+      name:"odd",
+      value:"1",
+      desc:"odd parity"
+    }
+```
+
+The first entry in a group may be on the same line as the opening bracket, with a single space.
+
+```hjson
+    { name:"odd",
+      value:"1",
+      desc:"odd parity"
+    }
+```
+
+Unless the group fits on a single line, the closing bracket should not be on the same line as the final item in the group.
+
+```hjson {.bad}
+    { name:"odd",
+      value:"1",
+      desc:"odd parity"}
+```
+
+
+### Lists
+
+***Use two character indentation for items in lists***
+
+A list value is a comma-separated list of entries or groups enclosed in [].
+For example, a list of groups could be presented:
+```hjson
+  registers: [{name:"reg1", desc:"description 1"},
+              {name:"reg2", desc:"description 2"}]
+```
+
+Longer lists and anywhere elements split over multiple lines should use a 2 character indent from the opening bracket.
+The closing bracket should not have the extra indentation.
+
+
+```hjson {.good}
+  registers: [
+    {
+      name:"reg1",
+      desc:"description 1"
+    },
+    {
+      name:"reg2",
+      desc:"description 2"
+    }
+  ]
+```
+
+Hjson allows commas to be omitted when an item that would be terminated with a comma is terminated by the end of a line, and will tolerate extra "trailing" commas.
+There is currently no style-guide rule on this.
+In general, use the comma if it is likely items may be combined on a single line and omit it if it keeps the file cleaner.
+
+
+### Long strings
+
+Long strings are encouraged for descriptive text that will be presented to users.
+They are delimited by three single-quote marks.
+
+The string should be indented to line up under the opening quote marks and the closing quote marks should be on their own line with the same indentation.
+
+```hjson {.good}
+       key: '''
+            A long descriptive value that can
+            span over multiple lines
+            '''
+```
+
+The first line of the string may be on the same line as the opening quotes.
+
+```hjson
+       key: '''A long descriptive value that can
+            span over multiple lines
+            '''
+```
+### Comments
+
+Comments should be used where needed.
+The use of `//` as the comment delimiter is recommended, but `#` may also be used.
diff --git a/doc/contributing/style_guides/markdown_usage_style.md b/doc/contributing/style_guides/markdown_usage_style.md
new file mode 100644
index 0000000..792d942
--- /dev/null
+++ b/doc/contributing/style_guides/markdown_usage_style.md
@@ -0,0 +1,313 @@
+# Markdown Usage and Style Guide
+
+## Basics
+
+### Summary
+
+Markdown files are used to write most documentation.
+The main Markdown tool is [mdbook](https://rust-lang.github.io/mdBook/).
+
+There exists a script, `./util/site/build-docs.sh`, to build all `mdbook` books as well as run documentation generators such as `doxygen`.
+
+As with all style guides the intention is to:
+
+*   promote consistency across projects
+*   promote best practices
+*   increase code sharing and re-use
+
+
+### Terminology Conventions
+
+Unless otherwise noted, the following terminology conventions apply to this style guide:
+
+*   The word ***must*** indicates a mandatory requirement.
+    Similarly, ***do not*** indicates a prohibition.
+    Imperative and declarative statements correspond to ***must***.
+*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
+    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
+    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
+*   The word ***may*** indicates a course of action is permitted and optional.
+*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
+
+### Style Guide Exceptions
+
+***Justify exceptions with a comment.***
+
+No style guide is perfect.
+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.
+
+## General Markdown Style
+
+### Line length
+
+In OpenTitan, most--but not all--Markdown documents will be rendered to HTML before they are presented to the reader.
+However, README files are an important exception, and so the recommended line-wrapping style differs for these two types of files.
+
+1. ***Rendered Files***:
+Files which are intended to be rendered before viewing should have exactly one sentence per line, with no line breaks in the middle of a sentence.
+This way change reviews will highlight only those sentences which are modified.
+Though the long line lengths make the files slightly less convenient to read from the command-line, this greatly simplifies the review process.
+When reviewing Markdown changes, every altered sentence will be included in its entirety in the file diff.
+
+2. ***README Files***:
+README files should wrap lines at under 80 characters.
+This ensures that the source is readable without any Markdown processing.
+Please note, however, that re-wrapping a paragraph after an insertion or deletion tends to cause longer diffs when the change is reviewed.
+When making changes to a document using this style, please consider allowing short lines rather than a full re-wrap after minor edits.
+Then occasionally separate commits can be used that only do re-wrapping of the paragraphs.
+
+### Headings and sections
+
+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.
+
+### Images
+
+Pictures can be included using the standard Markdown syntax (`![Alt Text](url)`).
+The preferred format is Scalable Vector Graphics (`.svg`), alternatively Portable Network Graphics (`.png`).
+
+### Waveforms
+
+Waveforms can be included by adding [wavejson](https://github.com/wavedrom/schema/blob/master/WaveJSON.md) code surrounded by `{{</* wavejson */>}}` shortcode tags.
+
+### Text Format
+
+Where possible, please restrict Markdown text to the ASCII character set to avoid downstream tool issues.
+Unicode may be used when referring to proper names.
+
+### Comments
+
+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` file extension.
+
+## Markdown file format for IP module descriptions
+
+Typically the Markdown file for an IP block follows the same outline.
+
+The header instantiates the standard document header and reads the Hjson description of the module.
+
+```
+---
+title: "Example IP Block"
+---
+```
+
+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.]({{</* relref "doc/rm/comportability_specification" */>}})
+See that document for integration overview within the broader top level system.
+```
+
+The next section summarizes the feature set of the IP block.
+
+```
+## Features
+
+* Bulleted list
+* Of main features
+```
+
+There then follows a general description of the IP
+
+```
+## Description
+
+Description of the IP.
+```
+The Compatibility information will allow device driver writers to identify existing code that can be used directly or with minor changes.
+
+_This section is primarily of interest to software engineers._
+
+
+```
+## Compatability
+
+Notes on if the IP register interface is compatible with any existing register interface.
+Also note any differences.
+For example: Matches 16550 UART interface but registers are at 32-bit word offsets.
+```
+
+The next major section is a more detailed operational description of the module.
+
+```
+# Theory of Operations
+
+```
+
+Conventionally one of the first sections includes a block diagram and a description.
+
+_Should be useful to hardware designers, verification engineers and software engineers._
+
+
+```
+
+## Block Diagram
+
+![Name Block Diagram](block_diagram.svg)
+
+```
+
+There should be a section containing the automatically generated description of the IP including the signals, interrupts and alerts that it uses.
+
+_Primary user is the SoC integrator, but useful for everyone._
+
+Note that the interrupt descriptions are also automatically placed in the interrupt status register bit descriptions, which is the most likely place for software engineers to reference them.
+
+
+```
+
+## Hardware Interfaces
+
+
+```
+
+The organization of the design details section is done to suit the module.
+
+```
+
+## Design Details
+
+Details of the design.
+
+### Many third level headings
+```
+There are probably waveforms embedded here:
+
+```
+
+{{</* 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.
+
+_This section is primarily for software engineers, but it is expected that the code fragments are used by verification engineers._
+
+```
+
+# 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!
+
+```
+## Initialization
+```
+```c
+
+ if (...) {
+   a = ...
+ }
+```
+
+Other sections cover different use cases and example code fragments.
+
+```
+
+## Use case A (eg Transmission)
+
+## Use case B (eg Reception)
+
+```
+
+It is important to include a discussion of error conditions.
+
+```
+## Error conditions
+
+```
+
+Also comment on anything special about interrupts, potentially including the priority order for servicing interrupts.
+
+
+```
+
+## Interrupt Handling
+
+```
+
+The document should end with the automatically generated register tables.
+
+```
+## Register Table
+
+{{</* registers "hw/ip/component/data/component.hjson" */>}}
+
+```
+
+To allow cut/paste of the default structure, here is an uncommented version:
+
+```
+---
+title: Name HWIP Technical Specification
+---
+
+# Overview
+
+This document specifies Name hardware IP functionality.
+This module conforms to the [Comportable guideline for peripheral functionality.](../hw/comportability/README.md)
+See that document for integration overview within the broader top level system.
+
+## Features
+
+* Bulleted list
+
+## Description
+
+
+## Compatibility
+
+
+# Theory of Operations
+
+
+## Block Diagram
+
+![Name Block Diagram](block_diagram.svg)
+
+## Hardware Interfaces
+
+{{</* incGenFromIpDesc "../data/component.hjson" "hwcfg" */>}}
+
+## Design Details
+
+### Many third level headings
+
+# Programmers Guide
+
+## Initialization
+
+## Use case A (eg Transmission)
+
+## Use case B (eg Reception)
+
+## Error conditions
+
+## Interrupt Handling
+
+## Register Table
+
+{{</* incGenFromIpDesc "../data/component.hjson" "registers" */>}}
+
+```
diff --git a/doc/contributing/style_guides/otbn_style_guide.md b/doc/contributing/style_guides/otbn_style_guide.md
new file mode 100644
index 0000000..8a6d12c
--- /dev/null
+++ b/doc/contributing/style_guides/otbn_style_guide.md
@@ -0,0 +1,244 @@
+# OTBN Assembly Style Guide
+
+Where possible, OTBN assembly should follow the same principles as the [RISC-V assembly style guide](./asm_coding_style.md).
+This guide describes additional OTBN-specific guidelines, and places where OTBN assembly can or should diverge from the RISC-V style guidelines.
+
+## General Advice
+
+### Register Names
+
+There's no ABI, so OTBN registers cannot be referred to by ABI names.
+
+### Capitalization
+
+Lower case for mnemonics and registers.
+
+Example:
+```S
+  /* Correct */
+  bn.addi   w3, w3, 2
+
+  /* Wrong */
+  BN.ADDI   W3, W3, 2
+```
+
+Upper case for flags, flag groups, and half word identifiers in multiplication instructions.
+Example:
+```S
+  /* Correct */
+  bn.mulqacc.so  w27.L, w30.0, w25.1, 64
+  bn.sel         w3, w4, w2, FG1.C
+
+  /* Wrong */
+  bn.mulqacc.so  w27.l, w30.0, w25.1, 64
+  bn.sel         w3, w4, w2, fg1.c
+```
+
+### Pseudoinstructions
+
+As in RISC-V, prefer pseudoinstructions (e.g. `ret`, `li`) in OTBN code where they exist.
+However, `loop` and `loopi` instructions require counting the number of instructions in the loop body, which can be tricky for pseudoinstructions that can expand to multiple instructions.
+(For example, `li` can expand to either 1 or 2 instructions depending on the immediate value.)
+Therefore, it is permitted to avoid pseudoinstructions that can expand to multiple instructions within loop bodies.
+
+### Wrappers and ecall
+
+Generally, OTBN routines should be written so that they finish in `ret`.
+Use of `ecall` should be restricted to thin wrapper files which serve as an interface for the more substantial assembly files, usually just reading one or two arguments and then calling subroutines from the other files.
+
+### Comments
+
+The `//` syntax is not currently available for OTBN because it is not supported by `riscv32-unknown-as`.
+Use the `/* */` syntax instead.
+
+### Labels
+
+It is not required to use an `L_` prefix for internal labels.
+Labels should not be indented.
+
+### Operand Alignment
+
+***Operands should be aligned within blocks of OTBN code.***
+
+The exact spacing between mnemonic and operand is not important, as long as it is consistent within the block.
+
+Example:
+```S
+  /* Correct */
+  bn.add  w4, w7, w10
+  bn.addc w5, w8, w11
+  bn.sel  w2, w2, w3, C
+
+  /* Correct */
+  bn.add        w4, w7, w10
+  bn.addc       w5, w8, w11
+  bn.sel        w2, w2, w3, C
+
+  /* Wrong */
+  bn.add w4, w7, w10
+  bn.addc w5, w8, w11
+  bn.sel w2, w2, w3, C
+```
+
+### Register and flag group clobbering
+
+***Always document which registers and flag groups are clobbered by an OTBN "function", and which flags have meaning.***
+
+OTBN subroutines should always document the registers and flag groups whose values they overwrite, including those used for output.
+In addition to Doxygen-style `@param[in]` and `@param[out]` notations, OTBN subroutines should also state whether the flags have meaning at the end of the subroutine.
+If a subroutine jumps to another subroutine that clobbers additional registers or flag groups, these additional names should be added to the caller's list.
+
+Example:
+```S
+/**
+ * Compute the sum of two 2048-bit numbers.
+ *
+ *   Returns C = A + B.
+ *
+ * Flags: When leaving this subroutine, the C flag of FG0 depends on the
+ *        addition.
+ *
+ * @param[in]  [w10:w3]: A, first 2048-bit operand
+ * @param[in]  [w18:w11]: B, second 2048-bit operand
+ * @param[in]  w31: all-zero
+ * @param[out] [w26:w19]: C, result
+ *
+ * clobbered registers: x9 to x13, w19 to w28
+ * clobbered flag groups: FG0
+ */
+wide_sum:
+  /* Prepare temporary registers. */
+  li       x9, 3
+  li       x10, 11
+  li       x11, 19
+  li       x12, 27
+  li       x13, 28
+
+  /* Clear flags. */
+  bn.add   w31, w31, 0
+
+  /* Addition loop. */
+  loopi    8, 4
+    /* w27 <= A[i] */
+    bn.movr  x12, x9++
+    /* w28 <= B[i] */
+    bn.movr  x13, x10++
+    /* w27 <= w27 + w28 */
+    bn.addc  w27, w27, w28
+    /* C[i] <= w27 */
+    bn.movr  x11++, x12
+
+  ret
+```
+
+### Alignment Directives
+
+Big-number load/store instructions (`bn.lid`, `bn.sid`) require DMEM addresses
+to be 256-bit aligned; use `.balign 32` for any big-number inline binary data
+to ensure this. For non-big-number data, `.balign 4` (32-bit alignment) is
+recommended.
+
+### Inline Binary Directives
+
+Prefer `.word` over alternatives such as `.quad`.
+
+## Secure Coding for Cryptography
+
+The following guidelines address cryptography-specific concerns for OTBN assembly.
+
+### Copying register values
+
+Prefer `bn.mov <wrd>, <wrs>` to `bn.addi <wrd>, <wrs>, 0` when copying data between registers.
+Because `bn.addi` passes data through the ALU, it will strip off integrity protection, while `bn.mov` will copy the integrity protection bits.
+
+### Constant time code
+
+***In cryptographic code, subroutines should always document whether they run in constant or variable time.***
+
+In situations where constant- and variable- time code is mixed, it is recommended to name variable-time subroutines something that makes it clear they are variable-time, such as a name that ends in `_var`.
+If a piece of code is constant-time with respect to some inputs but not others (e.g. with respect to the `MOD` register but not to the operands), this should be documented.
+
+Example:
+```S
+/**
+ * Determine if two 2048-bit operands are equal.
+ *
+ *   Returns 1 if A = B, otherwise 0.
+ *
+ * This routine runs in constant time.
+ *
+ * Flags: Flags have no meaning beyond the scope of this subroutine.
+ *
+ * @param[in]  [w10:w3]: A, first 2048-bit operand
+ * @param[in]  [w18:w11]: B, second 2048-bit operand
+ * @param[in]  w31: all-zero
+ * @param[out] x3: result, 1 or 0
+ *
+ * clobbered registers: x2, x3, x6, x9 to x12
+ * clobbered flag groups: FG0
+ */
+eq_2048:
+
+  /* Prepare temporary registers. */
+  li       x9, 3
+  li       x10, 11
+  li       x11, 27
+  li       x12, 28
+
+  /* Check if all limbs are equal. */
+  li       x3, 1
+  loopi    8, 4
+    /* w27 <= A[i] */
+    bn.movr  x11, x9++
+    /* w28 <= B[i] */
+    bn.movr  x12, x10++
+    /* x2 <= (w27 == w28) */
+    jal      x1, eq_256
+    /* x3 <= x3 & x2 */
+    and      x3, x2, x2
+
+  ret
+
+/**
+ * Determine if two 2048-bit operands are equal.
+ *
+ *   Returns 1 if A = B, otherwise 0.
+ *
+ * This routine runs in variable time.
+ *
+ * Flags: Flags have no meaning beyond the scope of this subroutine.
+ *
+ * @param[in]  [w10:w3]: A, first 2048-bit operand
+ * @param[in]  [w18:w11]: B, second 2048-bit operand
+ * @param[in]  w31: all-zero
+ * @param[out] x2: result, 1 or 0
+ *
+ * clobbered registers: x2, x6, x9 to x12
+ * clobbered flag groups: FG0
+ */
+eq_2048_var:
+
+  /* Prepare temporary registers. */
+  li       x9, 3
+  li       x10, 11
+  li       x11, 27
+  li       x12, 28
+
+  /* Check if all limbs are equal. */
+  li       x2, 1
+  loopi    8, 5
+    /* If x2 is 0, skip to the end of the loop. (It's still necessary to
+       complete the same number of loop iterations to avoid polluting the loop
+       stack, but we can skip all instructions except the last.) */
+    beq      x2, x0, loop_end
+    /* w27 <= A[i] */
+    bn.movr  x11, x9++
+    /* w28 <= B[i] */
+    bn.movr  x12, x10++
+    /* x2 <= (w27 == w28) */
+    jal      x1, eq_256
+loop_end:
+    nop
+
+  ret
+```
diff --git a/doc/contributing/style_guides/python_coding_style.md b/doc/contributing/style_guides/python_coding_style.md
new file mode 100644
index 0000000..15dda01
--- /dev/null
+++ b/doc/contributing/style_guides/python_coding_style.md
@@ -0,0 +1,198 @@
+# Python Coding Style Guide
+
+## Basics
+
+### Summary
+
+Python3 is the main language used for simple tools.
+As tools grow in complexity, or the requirements on them develop, they serve as valuable prototypes to re-implement tools or their behaviors more maintainably.
+
+Python can be written in vastly different styles, which can lead to code conflicts and code review latency.
+This style guide aims to promote Python readability across groups.
+To quote the C++ style guide: "Creating common, required idioms and patterns makes code much easier to understand."
+
+This guide defines the lowRISC style for Python version 3.
+The goals are to:
+
+*   promote consistency across hardware development projects
+*   promote best practices
+*   increase code sharing and re-use
+
+
+### Terminology Conventions
+
+Unless otherwise noted, the following terminology conventions apply to this style guide:
+
+*   The word ***must*** indicates a mandatory requirement.
+    Similarly, ***do not*** indicates a prohibition.
+    Imperative and declarative statements correspond to ***must***.
+*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
+    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
+    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
+*   The word ***may*** indicates a course of action is permitted and optional.
+*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
+
+### Style Guide Exceptions
+
+***Justify exceptions with a comment.***
+
+No style guide is perfect.
+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, as well as a lint waiver pragma where appropriate.
+
+A common case where you may wish to disable tool-enforced reformatting is for large manually formatted data literals.
+In this case, no explanatory comment is required and yapf can be disabled for that literal [with a single pragma](https://github.com/google/yapf#why-does-yapf-destroy-my-awesome-formatting).
+
+## Python Conventions
+
+### Summary
+
+The lowRISC style matches [PEP8](https://www.python.org/dev/peps/pep-0008/) with the following options:
+* Bitwise operators should be placed before a line split
+* Logical operators should be placed before a line split
+
+To avoid doubt, the interpretation of PEP8 is done by [yapf](https://github.com/google/yapf) and the style guide is set using a `.style.yapf` file in the top level directory of the repository.
+This just sets the base style to pep8 and overrides with the exceptions given above.
+
+In addition to the basic style, imports must be ordered alphabetically within sections:
+* Future
+* Python Standard Library
+* Third Party
+* Current Python Project
+
+The import ordering matches that enforced by [isort](https://github.com/timothycrosley/isort).
+Currently the `isort` defaults are used.
+If this changes a `.isort.cfg` file will be placed in the top level directory of the repository.
+
+### Lint tool
+
+The `lintpy.py` utility in `util` can be used to check Python code.
+It checks all Python (`.py`) files that are modified in the local repository and will report problems.
+Both `yapf` and `isort` checks are run.
+
+Basic lintpy usage is just to run from the util directory.
+If everything is fine the command produces no output, otherwise it will report the problems.
+Additional information will be printed if the `--verbose` or `-v` flag is given.
+
+```console
+$ cd $REPO_TOP/util
+$ ./lintpy.py
+$ ./lintpy.py -v
+```
+
+Checking can be done on an explicit list of files using the `--file` or `-f` flag.
+In this case the tool will not derive the list from git, so any file can be checked even if it has not been modified.
+
+```console
+$ cd $REPO_TOP/util
+$ ./lintpy.py -f a.py subdir/*.py
+```
+
+Errors may be fixed using the same tool to edit the problem file(s) in-place (you may need to refresh the file(s) in your editor after doing this).
+This uses the same set of files as are being checked, so unless the`--file` or `-f` flag is used this will only affect files that have already been modified (or staged for commit if `-c`is used) and will not fix errors in Python files that have not been touched.
+
+```console
+$ cd $REPO_TOP/util
+$ ./lintpy.py --fix
+```
+
+lintpy.py can be installed as a git pre-commit hook which will prevent commits if there are any lint errors.
+This will normally be a symlink to the tool in util so changes are automatically used (it also works if `lintpy.py` is copied to `.git/hooks/pre-commit` but in that case the hook must be reinstalled each time the tool changes).
+Since git hooks are not automatically installed the symlink hook can be installed if required using the tool:
+
+```console
+$ cd $REPO_TOP/util
+$ ./lintpy.py --hook
+```
+
+
+Fixing style errors for a single file can also be done with `yapf` directly:
+```console
+$ yapf -i file.py
+```
+
+Fixing import ordering errors for a single file can be done with `isort`:
+```console
+$ isort file.py
+```
+
+Yapf and isort are Python packages and should be installed with pip:
+
+```console
+$ pip3 install --user yapt
+$ pip3 install --user isort
+```
+
+### File Extensions
+
+***Use the `.py` extension for Python files***
+
+### General File Appearance
+
+#### Characters
+
+***Use only UTF-8 characters with UNIX-style line endings(`"\n"`).***
+
+Follows PEP8.
+
+#### POSIX File Endings
+
+***All lines on non-empty files must end with a newline (`"\n"`).***
+
+#### Line Length
+
+***Wrap the code at 79 characters per line.***
+
+The maximum line length follows PEP8.
+
+Exceptions:
+
+-   Any place where line wraps are impossible (for example, an include path might extend past 79 characters).
+
+#### No Tabs
+
+***Do not use tabs anywhere.***
+
+Use spaces to indent or align text.
+
+To convert tabs to spaces on any file, you can use the [UNIX `expand`](http://linux.die.net/man/1/expand) utility.
+
+#### No Trailing Spaces
+
+***Delete trailing whitespace at the end of lines.***
+
+### Indentation
+
+***Indentation is four spaces per level.***
+
+Follows PEP8.
+Use spaces for indentation.
+Do not use tabs.
+You should set your editor to emit spaces when you hit the tab key.
+
+### Executable Python tools
+
+Tools that can be executed should use `env` to avoid making assumptions about the location of the Python interpreter.
+Thus they should begin with the line:
+
+```console
+#!/usr/bin/env python3
+```
+
+This should be followed by a comment with the license information and the doc string describing the command.
+
+#### Argument Parsing
+
+***Use argparse to parse command line arguments.***
+
+In command line tools use the [argparse library](https://docs.python.org/3/library/argparse.html) to parse arguments.
+This will provide support for `--help` and `-h` to get usage information.
+
+Every command line program should provide `--version` to provide standard version information.
+This lists the git repository information for the tool and the version numbers of any Python packages that are used.
+The `show_and_exit` routine in `reggen/version.py` can be used to do this.
+
+Options that consume an arbitrary number of command line arguments with `nargs="*"` or `nargs="+"` should be avoided wherever possible.
+Comma separated lists can be passed to form single arguments and can be split after arguments are parsed.
+Args that allow for lists should mention that capability and the separator using the help keyword.
+To display proper delimiting of lists, args that allow for lists may demonstrate the separator with the metavar keyword.
diff --git a/doc/contributing/sw/device_interface_functions.md b/doc/contributing/sw/device_interface_functions.md
new file mode 100644
index 0000000..45ecddf
--- /dev/null
+++ b/doc/contributing/sw/device_interface_functions.md
@@ -0,0 +1,167 @@
+# Device Interface Functions (DIFs)
+
+## Motivation
+
+Every hardware peripheral needs some form of higher-level software to actuate it to perform its intended function.
+Device Interface Functions (DIFs) aim to make it easy to use hardware for its intended purposes.
+DIFs can be seen as a working code reference for interacting with a given piece of hardware.
+
+## Objectives
+
+DIFs provide extensively reviewed APIs for actuating hardware for three separate use cases: design verification, FPGA + post-silicon validation, and providing example code to aid the implementation of non-production firmware.
+DIFs may be illustrative for writing device drivers but should not be considered drivers themselves.
+
+## Requirements
+
+### Common Requirements
+
+#### Language
+
+DIFs **must** be written in C, specifically [C11 (with a few allowed extensions)](../style_guides/c_cpp_coding_style.md#c-style-guide).
+DIFs **must** conform to the style guide in [`sw/device/lib/dif`](../../../sw/device/lib/dif/README.md).
+
+DIFs **must** only depend on:
+
+*   the [freestanding C library headers](https://github.com/lowRISC/opentitan/tree/master/sw/device/lib/base/freestanding),
+*   compiler runtime libraries (e.g. libgcc and compiler-rt),
+*   the bitfield library for manipulating bits in binary values (`sw/device/lib/base/bitfield.h`),
+*   the mmio library for interacting with memory-mapped registers (`sw/device/lib/base/mmio.h`),
+*   the memory library for interacting with non-volatile memory (`sw/device/lib/base/memory.h`), and
+*   any IP-specific register definition files.
+
+DIFs **must not** depend on DIFs for other IP blocks, or other external libraries.
+
+This decision is motivated by the requirement that DIFs must be extremely flexible in their possible execution environments, including being hosted in bare metal, or being called from other languages such as Rust through Foreign Function Interface.
+
+#### Runtime Independence
+
+DIFs **must not** depend on runtime services provided by an operating system.
+If functions must be called in response to system stimulus such as interrupts then the DIF must make these requirements explicit in their documentation.
+This decision makes DIFs appropriate for execution environments without OS support such as DV.
+
+### Architecture Independence
+
+DIFs **should not** depend on architecture-specific constructs such as inline assembly or platform-defined registers, except where a peripheral is integrated into the core in a way making them unavoidable.
+As a concrete example: a SPI DIF must not depend on a pinmux or clock control DIF in order to be operated.
+This highlights a clear separation of concern: making a pin on the package driven from the SPI block involves multiple systems.
+By design, a DIF cannot coordinate cross-functional control.
+
+#### Coverage Requirements
+
+DIFs *must* actuate all of the specification-required functionality of the hardware that they are written for, and no more.
+A DIF cannot be declared complete until it provides an API for accessing all of the functionality that is expected of the hardware.
+This is distinct from mandating that a DIF be required to cover all of the functionality of a given piece of hardware.
+
+## Details
+
+### Verification Stage Allowances
+
+DV, FPGA, and early silicon validation have unique requirements in their use of hardware.
+They might actuate hardware on vastly different timescales, use verification-specific registers, or otherwise manipulate aspects of the hardware that "production"-level code would not (or cannot) do.
+To this end, verification-only functionality may be added to a DIF **only** in modules that are included in verification.
+This functionality **must not** be made accessible outside of verification environments, and is enforced by not including DV-specific code in non-DV builds.
+
+### Separation of Concerns and Stateful Information
+
+In addition to the interface functions themselves, DIFs are expected to usually take the form of a structure that contains instance-specific information such as base register addresses and non-hardware-backed state required to actuate the hardware.
+DIFs **should not** track hardware-backed state in their own state machine.
+
+A DIF instance is expressly an instance of a hardware block, so DIFs **must not** intrinsically know the location of the hardware blocks they're associated with in memory.
+That is:
+
+```c
+dif_timer_init_result_t dif_timer_init(dif_timer_t *timer,
+                                       const timer_config_t *config,
+                                       uintptr_t base_address);
+```
+
+allows the system initialization code to instantiate timers at specific locations, whereas:
+
+```c
+// Don't do this:
+bool dif_timer_init(timer_t *timer, enum timer_num num);
+```
+
+suggests that the timer DIF knows about the number and placement of timers in the system.
+
+DIFs **must not** store or provide information in their implementation or state that is outside of their area of concern.
+The number of timers in a system is the concern of the system, not the timer DIF.
+
+### Naming
+
+All DIF functions **must** have clear direct objects and verbs, be written in the imperative mood, and follow the format of `dif_<object>_<verb_phrase>`.
+The object in the name must be common to the DIF it appears it, and unique among DIFs.
+
+Consider the following examples of good names:
+
+*   `dif_timer_init`,
+*   `dif_timer_reset`,
+*   `dif_timer_set_time_remaining`,
+*   `dif_timer_get_time_remaining`.
+
+The following are bad names:
+
+*   `dif_clear_timer` (wrong object/verb-phrase ordering),
+*   `dif_timer_gets_reset` (passive voice not imperative),
+*   `timer_init` (not correctly prefixed for C).
+
+Prefer common names: `init` is more commonly written than `initialize`, but `reset` is more commonly written than `rst`, for example.
+This is a subjective call on the DIF author and reviewers' parts, so common agreement will be more useful than strict prescription.
+
+### Documentation
+
+All DIF exported types and functions **must** have associated API documentation.
+All function parameters **must** be documented.
+All function semantics **must** be documented, no matter how obvious one suspects the function to be.
+The documentation should be exhaustive enough that the implementation can reasonably be inferred from it and the IP specification.
+
+It is important that the DIFs are documented alongside the hardware IP that they are written for.
+Each hardware IP block also contains a "programmers' guide" section, which tells programmers how to use the hardware interfaces correctly, using prose descriptions and relevant diagrams.
+The prose descriptions should include references to relevant DIF functions.
+
+Programmers' guides **should** primarily refer to the [Software API Documentation](../../../sw/README.md#opentitan-software-api-documentation).
+Programmers' guides **should not** contain standalone examples that do not match how we implement DIFs and can become out of date.
+The software API documentation includes full source code of the respective DIFs, should extra clarity be needed to explain how a hardware interface should be used.
+
+The programmers' guide must also include a list of DIF functions which relate to it, along with a brief description of each, and a link to further API documentation.
+
+### Source Layout
+
+DIFs **must** live in the `sw/device/lib/dif` folder.
+They **must** comprise at least one header unique to the hardware in question, and **may** contain more than one C file providing the implementation, if the complexity of the implementation warrants it (this is subjective).
+Any files for a given IP should have a filename starting `dif_<IP name>`.
+
+Verification-specific extensions and logic **must not** live in `sw/device/lib/dif`.
+This code must be placed in the directory belonging to the verification domain which the extension is applicable to.
+Verification-specific extensions must not be built and included in production firmware builds.
+
+### Testing
+
+Tests **must** live in the `sw/device/tests/dif` folder.
+
+DIFs **must** have unit tests, which cover all their specification-required functionality.
+These unit tests use a mocking system which allows the test to control all hardware reads and writes, without requiring complex management of the hardware.
+These unit tests are written using [googletest](https://github.com/google/googletest), and may be run on a non-RISC-V host.
+
+DIFs **should** also have other tests, including standalone C sanity tests---which can quickly diagnose major issues between the DIF and the hardware but are not required to test all device functionality.
+
+DIFs are also being used by DV for chip-level tests, which should help catch any issues with the DIFs not corresponding to the hardware implementation exactly.
+These tests may not live in `sw/device/tests/dif`.
+
+### DIF Development Stages
+
+As part of the DIF Standardisation process, we've decided to establish more concrete DIF lifecycle stages.
+These are documented with the [Development Stages](../../project_governance/development_stages.md).
+
+In the hardware world, there are checklists for establishing that each stage is complete and the next stage can be moved to.
+
+In software, we don't have to follow the same stability guarantees, because software is much more modifiable.
+It makes sense to have some kind of review process, but this should be much more lightweight in the early stages and not significantly burdensome to the associated HW designer.
+
+The current proposal only covers a DIF being written against a single version of its respective hardware IP block.
+This specifically excludes how to disambiguate DIFs in the repository that are written against different versions of the same IP block, or writing a single DIF that is compatible with multiple versions of an IP block.
+
+### Signoff Review
+
+The DIF lead author is expected to participate in the hardware IP block's L2 signoff review.
+This review can only happen once hardware design and verification are complete, and the DIF itself has reached stage S3.
diff --git a/doc/contributing/sw/otbn_sw.md b/doc/contributing/sw/otbn_sw.md
new file mode 100644
index 0000000..cf17e5a
--- /dev/null
+++ b/doc/contributing/sw/otbn_sw.md
@@ -0,0 +1,68 @@
+# Writing and building software for OTBN
+
+OTBN is the OpenTitan Big Number accelerator and this document describes how to write and build software for it.
+The [OTBN reference manual](../../../hw/ip/otbn/README.md) describes the hardware and associated ISA.
+
+Since OTBN is designed for cryptographic offload, not general computation, it has no C compiler.
+However, it does have the usual binutils programs: an assembler, linker and disassembler.
+The core of OTBN's instruction set is based on RV32I, the RISC-V base integer instruction set.
+As such, we implement the toolchain as a thin wrapper around RISC-V binutils.
+
+## Assembly format and examples
+
+The OTBN ISA and programmer model are described in the [OTBN reference manual](../../../hw/ip/otbn/README.md).
+In particular, note that the register `x1` has special stack-like behaviour.
+There are some example programs at `sw/otbn/code-snippets`.
+These range from simple examples of how to use pseudo-operations up to example cryptographic primitives.
+
+For specific formatting and secure coding guidelines, see the [OTBN style guide](../style_guides/otbn_style_guide.md).
+
+## The tools
+
+### Assembler
+
+The OTBN assembler is called `otbn_as.py` and can be found at `hw/ip/otbn/util/otbn_as.py`.
+This has the same command line interface as `riscv32-unknown-elf-as` (indeed, it's a wrapper around that program).
+The only difference in default flags is that `otbn_as.py` passes `-mno-relax`, telling the assembler not to request linker relaxation.
+This is needed because one of these relaxations generates GP-relative loads, which assume `x3` is treated as a global pointer (not true for OTBN code).
+
+To assemble some code in `foo.s` to an ELF object called `foo.o`, run:
+```shell
+hw/ip/otbn/util/otbn_as.py -o foo.o foo.s
+```
+
+### Linker
+
+The OTBN linker is called `otbn_ld.py` and can be found at `hw/ip/otbn/util/otbn_ld.py`.
+This is a thin wrapper around `riscv32-unknown-elf-ld`, but supplies a default linker script that matches the OTBN memory layout.
+This linker script creates `.start`, `.text` and `.data` output sections.
+The `.start` and `.text` sections go to IMEM, with `.start` coming first.
+The `.data` section goes to DMEM.
+Since OTBN has a strict Harvard architecture with IMEM and DMEM both starting at address zero, the `.start` and the `.data` sections will both start at VMA zero.
+The instruction and data segments have distinct LMAs (for addresses, see the IMEM and DMEM windows at `hw/ip/otbn/data/otbn.hjson`).
+
+Since the entry point for OTBN is always address zero, the entry vector should be the one and only thing in the `.start` section.
+To achieve that, put your entry point (and nothing else) in the `.text.start` input section like this:
+```asm
+.section .text.start
+  jal x0, main
+
+.text
+  ...
+```
+This ensure that, even if there are multiple objects being linked together, the intended entry point will appear in the right place.
+
+To link ELF object files to an OTBN ELF binary, run
+```shell
+hw/ip/otbn/util/otbn_ld.py -o foo foo0.o foo1.o foo2.o
+```
+
+### Objdump
+
+To disassemble OTBN code, use `otbn_objdump.py`, which can be found at `hw/ip/otbn/util/otbn_objdump.py`.
+This wraps `riscv32-unknown-elf-objdump`, but correctly disassembles OTBN instructions when run with the `-d` flag.
+
+To disassemble the ELF binary linked in the previous section, run
+```shell
+hw/ip/otbn/util/otbn_objdump.py -d foo
+```
diff --git a/doc/contributing/system_list.md b/doc/contributing/system_list.md
new file mode 100644
index 0000000..27c2d9a
--- /dev/null
+++ b/doc/contributing/system_list.md
@@ -0,0 +1,10 @@
+# List of Top-Level Designs
+
+This page lists all top-level designs and their targets that are contained within this repository.
+Click on the design name to get more information about the design.
+
+| Design | Internal Name | Simulation Targets | FPGA Targets | ASIC Targets | Description |
+|--------|---------------|--------------------|--------------|--------------|-------------|
+| [Earl Grey](../../hw/top_earlgrey/doc/specification.md) | `top_earlgrey` | Verilator | ChipWhisperer CW310\* | *None yet.* | 0.1 release |
+
+`*` There exists a modified version of the Earl Grey top-level design that can be implemented on the ChipWhisperer CW305 FPGA board usable with a free EDA tool license.
diff --git a/doc/getting_started/_index.md b/doc/getting_started/_index.md
deleted file mode 100644
index 4b9048d..0000000
--- a/doc/getting_started/_index.md
+++ /dev/null
@@ -1,241 +0,0 @@
----
-title: "Getting Started"
-aliases:
-    - /doc/ug/getting_started
-    - /doc/ug/install_instructions
----
-
-Welcome!
-This guide will help you get OpenTitan up and running by instructing you how to:
-
-1. clone the OpenTitan Git repository,
-2. setup an adequate build/testing environment on your machine, and
-3. build OpenTitan software/hardware for the target of your choosing.
-
-## Workflow Options
-
-An important preliminary note: to run OpenTitan software, you will need to not only build the software but somehow simulate the hardware it runs on.
-As shown in the diagram below, we currently support multiple build targets and workflows, including: Verilator, FPGA, and DV (commercial RTL simulators, such as VCS and Xcelium).
-**However, if you are new to the project, we recommend simulation with Verilator.**
-This uses only free tools, and does not require any additional hardware such as an FPGA.
-
-![Getting Started Workflow](getting_started_workflow.svg)
-
-This guide will focus on the Verilator workflow, but indicate when those following FPGA or DV workflows should do something different.
-Just keep in mind, if you're a new user and you don't know you're part of the FPGA or DV crowd, "Verilator" means you!
-
-## Step 0: Clone the OpenTitan Repository
-
-Clone the [OpenTitan repository](https://github.com/lowRISC/opentitan):
-```console
-$ git clone https://github.com/lowRISC/opentitan.git
-```
-
-If you wish to *contribute* to OpenTitan you will need to make a fork on GitHub and may wish to clone the fork instead.
-We have some [notes for using GitHub]({{< relref "github_notes.md" >}}) which explain how to work with your own fork (and perform many other GitHub tasks) in the OpenTitan context.
-
-***Note: throughout the documentation `$REPO_TOP` refers to the path where the OpenTitan repository is checked out.***
-Unless you've specified some other name in the clone, `$REPO_TOP` will be a directory called `opentitan`.
-You can create the environment variable by calling the following command from the same directory where you ran `git clone`:
-```console
-$ export REPO_TOP=$PWD/opentitan
-```
-
-## Step 1: Check System Requirements
-
-**OpenTitan installation requires Linux.**
-If you do not have Linux, please stop right here and use the (experimental) [Docker container](https://github.com/lowRISC/opentitan/tree/master/util/container).
-You can then **skip to step 4** (building software).
-
-If you do have Linux, you are still welcome to try the Docker container.
-However, as the container option is currently experimental, we recommend following the steps below to build manually if you plan on being a long-term user or contributor for the project.
-
-Our continuous integration setup runs on Ubuntu 18.04 LTS, which gives us the most confidence that this distribution works out of the box.
-We do our best to support other distributions, but cannot guarantee they can be used "out of the box" and might require updates of packages.
-Please file a [GitHub issue](https://github.com/lowRISC/opentitan/issues) if you need help or would like to propose a change to increase compatibility with other distributions.
-
-## Step 2: Install Package Manager Dependencies
-
-*Skip this step if using the Docker container.*
-
-A number of software packages from the distribution's package manager are required.
-On Ubuntu 18.04, the required packages can be installed with the following command.
-
-{{< pkgmgr_cmd "apt" >}}
-
-On Ubuntu 18.04 the package `build-essential` includes the compilers `gcc-7` and `g++-7`.
-But for the OpenTitan project `gcc-9`/`g++-9` or higher is required, which has to be installed manually.
-Check that you have version 9 or higher of `gcc` installed by:
-
-```console
-$ gcc --version
-```
-
-If your version is lower, you have to upgrade it manually.
-For this, first, add `ubuntu-toolchain-r/test` PPA to your system using the following commands:
-
-```console
-$ sudo apt install software-properties-common
-$ sudo add-apt-repository ppa:ubuntu-toolchain-r/test
-```
-
-Next, install the necessary GCC and G++ version by:
-
-```console
-$ sudo apt update
-$ sudo apt install gcc-9 g++-9
-```
-
-Finally, update the symbolic links for `gcc` and `g++` using these commands:
-
-```console
-$ sudo ln -sf /usr/bin/gcc-9 /usr/bin/gcc
-$ sudo ln -sf /usr/bin/g++-9 /usr/bin/g++
-```
-
-Some tools in this repository are written in Python 3 and require Python dependencies to be installed through `pip`.
-We recommend installing the latest version of `pip` and `setuptools` (especially if on older systems such as Ubuntu 18.04) using:
-
-```console
-$ python3 -m pip install --user -U pip setuptools
-```
-
-The `pip` installation instructions use the `--user` flag to install without root permissions.
-Binaries are installed to `~/.local/bin`; check that this directory is listed in your `PATH` by running `which pip3`, which should show `~/.local/bin/pip3`.
-If it doesn't, add `~/.local/bin` to your `PATH`, e.g. by modifying your `~/.bashrc` file.
-
-Now install additional Python dependencies:
-
-```console
-$ cd $REPO_TOP
-$ pip3 install --user -r python-requirements.txt
-```
-
-## Step 3: Install the LowRISC RISC-V Toolchain
-
-*Skip this step if using the Docker container.*
-
-To build device software you need a baremetal RISC-V toolchain.
-Even if you already have one installed, we recommend using the prebuilt toolchain provided by lowRISC, because it is built with the specific patches and options that OpenTitan needs.
-You can install the toolchain using the `util/get-toolchain.py` script, which will download and install the toolchain to the default path, `/tools/riscv`.
-
-```console
-$ cd $REPO_TOP
-$ ./util/get-toolchain.py
-```
-
-If you did not encounter errors running the script, **you're done and can go to step 4**.
-If you did, read on.
-
-#### Troubleshooting
-
-If you need to install to a different path than `/tools/riscv` (for instance, if you do not have permission to write to the `/tools` directory), then you can specify a different location using the `--install-dir` option.
-Run `./util/get-toolchain.py --help` for details.
-You can alternatively download the tarball starting with `lowrisc-toolchain-rv32imcb-` from [GitHub releases](https://github.com/lowRISC/lowrisc-toolchains/releases/latest) and unpack it to the desired installation directory.
-
-Assuming one of the above worked and you have installed to a non-standard location, you will need to set the `TOOLCHAIN_PATH` environment variable to match whatever path you used.
-For example, if I wanted to install to `~/ot_tools/riscv`, then I would use:
-```console
-$ ./util/get-toolchain.py --install-dir=~/ot_tools/riscv
-$ export TOOLCHAIN_PATH=~/ot_tools/riscv
-```
-Add the `export` command to your `~/.bashrc` or equivalent to ensure that the `TOOLCHAIN_PATH` variable is set for future sessions.
-Check that it worked by opening a new terminal and running:
-```console
-$ ls $TOOLCHAIN_PATH/bin/riscv32-unknown-elf-as
-```
-If that prints out the file path without errors, then you've successfully installed the toolchain.
-Otherwise, try to find the `riscv32-unknown-elf-as` file in your file system and make sure `$TOOLCHAIN_PATH` is correctly set.
-
-## Step 4: Build OpenTitan Software
-
-Follow the [dedicated guide]({{< relref "build_sw" >}}) to build OpenTitan's software, and then return to this page.
-Some tests might fail because you don't have Verilator installed, which we will do in the next step.
-
-## Step 5: Set up your Simulation Tool or FPGA
-
-*Note: If you are using the pre-built Docker container, Verilator is already installed.
-Unless you know you need the FPGA or DV guides, you can skip this step.*
-
-In order to run the software we built in the last step, we need to have some way to emulate an OpenTitan chip.
-There are a few different options depending on your equipment and use-case.
-Follow the guide(s) that applies to you:
-* **Option 1 (Verilator setup, recommended for new users):** [Verilator guide]({{< relref "setup_verilator.md" >}}), or
-* Option 2 (FPGA setup): [FPGA guide]({{< relref "setup_fpga.md" >}}), or
-* Option 3 (design verification setup): [DV guide]({{< relref "setup_dv.md" >}})
-
-## Step 6: Optional Additional Steps
-
-If you have made it this far, congratulations!
-Hopefully you got a "Hello World!" demo running on OpenTitan using either the Verilator or FPGA targets.
-
-Depending on the specific way you want to use or contribute to OpenTitan, there may be a few extra steps you want to do.
-In particular:
-* *If you want to contribute SystemVerilog code upstream to OpenTitan*, follow step 6a to install Verible.
-* *If you want to debug on-chip OpenTitan software with GDB*, follow step 6b to install OpenOCD.
-* *If you want to run supported formal verification flows for OpenTitan, using tools like JasperGold,* follow step 6c to set up formal verification.
-* *If you want to simulate OpenTitan using Siemens Questa,* follow step 6d to set it up.
-
-It also may make sense to stick with the basic setup and come back to these steps if you find you need them later.
-
-### Step 6a: Install Verible (optional)
-
-Verible is an open source SystemVerilog style linter and formatting tool.
-The style linter is relatively mature and we use it as part of our [RTL design flow]({{< relref "doc/ug/design" >}}).
-The formatter is still under active development, and hence its usage is more experimental in OpenTitan.
-
-You can download and build Verible from scratch as explained on the [Verible GitHub page](https://github.com/google/verible/).
-But since this requires the Bazel build system the recommendation is to download and install a pre-built binary as described below.
-
-Go to [this page](https://github.com/google/verible/releases) and download the correct binary archive for your machine.
-The example below is for Ubuntu 18.04:
-
-```console
-$ export VERIBLE_VERSION={{< tool_version "verible" >}}
-
-$ wget https://github.com/google/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-Ubuntu-18.04-bionic-x86_64.tar.gz
-$ tar -xf verible-${VERIBLE_VERSION}-Ubuntu-18.04-bionic-x86_64.tar.gz
-
-$ sudo mkdir -p /tools/verible/${VERIBLE_VERSION}/
-$ sudo mv verible-${VERIBLE_VERSION}/* /tools/verible/${VERIBLE_VERSION}/
-```
-
-After installation you need to add `/tools/verible/$VERIBLE_VERSION/bin` to your `PATH` environment variable.
-
-Note that we currently use version {{< tool_version "verible" >}}, but it is expected that this version is going to be updated frequently, since the tool is under active develpment.
-
-### Step 6b: Install OpenOCD (optional)
-
-See the [OpenOCD install guide]({{< relref "install_openocd.md" >}}).
-
-### Step 6c: Set up formal verification (optional)
-
-See the [formal verification setup guide]({{< relref "setup_formal.md" >}})
-
-### Step 6d: Set up Siemens Questa (optional)
-
-Once a standard installation of Questa has been completed, add `QUESTA_HOME` as an environment variable which points to the Questa installation directory.
-
-As of Questa version 21.4 there are some code incompatibilities with the OpenTitan code-base.
-See issue [#9514](https://github.com/lowRISC/opentitan/issues/9514) for the list of issues and temporary workarounds.
-
-## Step 7: Additional Resources
-
-As you may have guessed, there are several other pieces of hardware and software, besides a "Hello World!" demo, that are being actively developed for the OpenTitan project.
-If you are interested in these, check out the additional resources below.
-
-### General
-* [Documentation Index]({{< relref "doc/_index.md" >}})
-* [Directory Structure]({{< relref "doc/ug/directory_structure.md" >}})
-* [GitHub Notes]({{< relref "doc/ug/github_notes.md" >}})
-* [Building Documentation]({{< relref "doc/ug/documentation.md" >}})
-* [Design Methodology within OpenTitan]({{< relref "doc/ug/design.md" >}})
-
-### Hardware
-* [Designing Hardware]({{< relref "doc/ug/hw_design.md" >}})
-* [OpenTitan Hardware]({{< relref "/hw" >}})
-
-### Software
-* [OpenTitan Software]({{< relref "/sw" >}})
-* [Writing and Building Software for OTBN]({{< relref "otbn_sw.md" >}})
-* [Rust for Embedded C Programmers]({{< relref "rust_for_c.md" >}})
diff --git a/doc/getting_started/build_sw.md b/doc/getting_started/build_sw.md
deleted file mode 100644
index 5dca0a4..0000000
--- a/doc/getting_started/build_sw.md
+++ /dev/null
@@ -1,341 +0,0 @@
----
-title: Building (and Testing) Software
-aliases:
-    - /doc/ug/getting_started_build_sw
----
-
-_Before following this guide, make sure you have read the_:
-* main [Getting Started]({{< relref "getting_started" >}}) instructions,
-* install Verilator section of the [Verilator guide]({{< relref "setup_verilator.md" >}}), and
-* [OpenTitan Software]({{< relref "sw/" >}}) documentation.
-
-All OpenTitan software is built with [Bazel](https://bazel.build/).
-Additionally, _most_ tests may be run with Bazel too.
-
-## TL;DR
-To install the correct version of bazel, build, and run all on-host tests you can simply run:
-
-```console
-$REPO_TOP/bazelisk.sh test //... --test_tag_filters=-cw310,-verilator --disk_cache=~/bazel_cache
-```
-
-## Installing Bazel
-
-There are two ways to install the correct verion of Bazel:
-1. **automatically**, using the `bazelisk.sh` script provided in the repo, or
-1. **manually**.
-
-### Automatic Installation
-
-To simplify the installation of Bazel, and provide a means to seamlessly update the Bazel version we use in the future, we provide a shell script that acts as a wrapper for invocations of "`bazel ...`".
-To use it, you two options:
-1. use "`./bazelisk.sh ...`" instead of "`bazel ...`" to invoke of Bazel subcommands, or
-1. set the following alias (e.g., in your `.bashrc` file) to accomplish the same:
-```console
-alias bazel="$REPO_TOP/bazelisk.sh"
-```
-
-### Manual Installation
-
-This section is optional and can be skipped if you completed the instructions above in _Automatic Installation_.
-
-While the automatic installation is convenient, by installing Bazel directly, you can get some quality of life features like tab completion.
-If you haven't yet installed Bazel, and would like to, you can add it to apt and install it on Ubuntu systems with the following commands [as described in the Bazel documentation](https://bazel.build/install/ubuntu):
-
-```console
-sudo apt install apt-transport-https curl gnupg
-curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
-sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
-echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" |
-sudo tee /etc/apt/sources.list.d/bazel.list
-sudo apt update && sudo apt install bazel-5.1.1
-sudo ln -s /usr/bin/bazel-5.1.1 /usr/bin/bazel
-```
-
-or by following [instructions for your system](https://bazel.build/install).
-
-## Building Software with Bazel
-
-Running
-```console
-bazel build //sw/...
-```
-will build all software in our repository.
-If you do not have Verilator installed yet, you can use the `--define DISABLE_VERILATOR_BUILD=true` flag to skip the jobs that depend on that.
-
-In general, you can build any software target (and all of it's dependencies) using the following syntax:
-```console
-bazel build @<repository>//<package>:<target>
-```
-Since most targets are within the main Bazel repository (`lowrisc_opentitan`), you can often drop the "`@<repository>`" prefix.
-For example, to build the boot ROM we use for testing (also referred to as the _test ROM_), you can use
-```console
-bazel build //sw/device/lib/testing/test_rom:test_rom
-```
-Additionally, some Bazel syntactic sugar enables dropping the target name when the target name matches the last subcomponent of the package name.
-For example, the following is equivalent to the above
-```console
-bazel build //sw/device/lib/testing/test_rom
-```
-
-For more information on Bazel repositories, packages, and targets, please refer to the Bazel [documentation](https://bazel.build/concepts/build-ref).
-
-## Running Tests with Bazel
-
-In addition to building software, Bazel is also used to build and run tests.
-There are two categories of OpenTitan tests Bazel can build and run:
-1. on-host, and
-1. on-device.
-
-On-host tests are compiled and run on the host machine, while on-device tests are compiled and run on (simulated/emulated) OpenTitan hardware.
-
-Examples of on-host tests are:
-* unit tests for device software, such as [DIF]({{< relref "/sw/device/lib/dif" >}}) and [mask ROM]({{< relref "/sw/device/silicon_creator/mask_rom/docs/" >}}) unit tests.
-* any test for host software, such as `opentitan{lib,tool}`.
-
-Examples of on-device tests are:
-* [chip-level tests]({{< relref "/sw/device/tests/index.md" >}}).
-* [mask ROM functional tests]({{< relref "/sw/device/silicon_creator/mask_rom/docs/" >}})
-
-The remainder of this document will focus on building and running **on-host** tests with Bazel.
-To learn about running **on-device** tests with Bazel, please continue back to the main [Getting Started]({{< relref "getting_started" >}}) instructions, and proceed with the [Verilator]({{< relref "setup_verilator" >}}) and/or [FPGA]({{< relref "setup_fpga" >}}) setup instructions.
-
-### Running on-host DIF Tests
-
-The Device Interface Function or [DIF]({{< relref "/sw/device/lib/dif" >}}) libraries contain unit tests that run on the host and are built and run with Bazel.
-As shown below, you may use Bazel to query which tests are available, build and run all tests, or build and run only one test.
-
-#### Querying which tests are available
-```console
-bazel query 'tests(//sw/device/lib/dif:all)'
-```
-
-#### Building and running **all** tests
-```console
-bazel test //sw/device/lib/dif:all
-```
-
-#### Building and running a **single** test
-For example, building and testing the UART DIF library's unit tests:
-```console
-bazel test //sw/device/lib/dif:uart_unittest
-```
-
-### Running on-host Mask ROM Tests
-
-Similar to the DIF libraries, you can query, build, and run all the [mask ROM]({{< relref "/sw/device/silicon_creator/mask_rom/docs/" >}}) unit tests (which also run on the host) with Bazel.
-
-#### Querying which (on-host) tests are available
-Note, the mask ROM has both on-host and on-device tests.
-This query filters tests by their kind, i.e., only on-host tests.
-```console
-bazel query 'kind(cc_.*, tests(//sw/device/silicon_creator/lib/...))'
-```
-
-#### Building and running **all** (on-host) tests
-```console
-bazel test --test_tag_filters=-cw310,-dv,-verilator //sw/device/silicon_creator/lib/...
-```
-
-#### Building and running a **single** (on-host) test
-For example, building and testing the mask ROM UART driver unit tests:
-```console
-bazel test //sw/device/silicon_creator/lib/drivers:uart_unittest
-```
-
-## OpenTitan Bazel Workspace
-
-The rules for Bazel are described in a language called Starlark, which looks a lot like Python.
-
-The `$REPO_TOP` directory is defined as a Bazel workspace by the `//WORKSPACE` file.
-`BUILD` files provide the information Bazel needs to build the targets in a directory.
-`BUILD` files also manage any subdirectories that don't have their own `BUILD` files.
-
-OpenTitan uses .bzl files to specify custom rules to build artifacts that require specific attention like on-device test rules and project specific binaries.
-
-### WORKSPACE file
-
-The `WORKSPACE` file controls external dependencies such that builds can be made reproducible and hermetic.
-Bazel loads specific external dependencies, such as various language toolchains.
-It uses them to build OpenTitan targets (like it does with bazel\_embedded) or to satisfy dependencies (as it does with abseil).
-To produce increasingly stable releases the external dependencies loaded in `WORKSPACE` file attempts to fix a all external `http_archive`s to a specific SHA.
-As we add more dependencies to the workspace, builds and tests will become less sensitive to external updates, and we will vastly simplify the [Getting Started]({{< relref "getting_started" >}}) instructions.
-
-### BUILD files
-
-Throughout the OpenTitan repository, `BUILD` files describe targets and dependencies in the same directory (and subdirectories that lack their own `BUILD` files).
-`BUILD` files are mostly hand-written.
-To maintain the invariant that hand-written files not be included in autogen directories, there are `BUILD` files that describe how to build and depend on auto-generated files in autogen subdirectories.
-
-## Linting Software
-
-There are several Bazel rules that enable running quality checks and fixers on code.
-The subsections below describe how to use them.
-All of the tools described below are run in CI on every pull request, so it is best to run them before committing code.
-
-### Linting C/C++ Code
-The OpenTitan supported linter for C/C++ files is `clang-format`.
-It can be run with Bazel as shown below.
-
-Run the following to check if all C/C++ code as been formatted correctly:
-```console
-bazel run //:clang_format_check
-```
-and run the following to fix it, if it is not formatted correctly.
-```console
-bazel run //:clang_format_fix
-```
-
-### Linting Starlark
-
-The OpenTitan supported linter for Bazel files is `buildifier`.
-It can be run with Bazel as shown below.
-
-Run the following to check if all `WORKSPACE`, `BUILD`, and `.bzl` files have been formatted correctly:
-```console
-bazel run //:buildifier_check
-```
-and run the following to fix them, if they are not formatted correctly.
-```console
-bazel run //:buildifier_fix
-```
-
-### Checking License Headers
-
-Lastly, the OpenTitan supported linter for checking that every source code file contains a license header may be run with:
-```console
-bazel run //:license_check
-```
-
-## Miscellaneous
-
-### Bazel-built Artifacts
-
-As decribed in the [OpenTitan Software]({{< relref "sw/" >}}) documentation, there are three categories of OpenTitan software, all of which are built with Bazel. These include:
-1. _device_ software,
-1. _OTBN_ software,
-1. _host_ software,
-
-Bazel produces various artifacts depending on the category of software that is built.
-
-#### Device Artifacts
-
-Device software is built and run on OpenTitan hardware.
-There are three OpenTitan "devices" for simulating/emulating OpenTitan hardware:
-1. DV simulation (i.e., RTL simulation with commercial simulators),
-1. Verilator simulation (i.e., RTL simulation with the open source Verilator simulator),
-1. FPGA.
-
-Different software artifacts are built depending on the OpenTitan device above.
-Specifically, building an executable `<target>` destined to run on the OpenTitan device `<device>` will output the following files under `bazel-out/`:
-* `<target>_<device>`: the linked program, in ELF format.
-* `<target>_<device>.bin`: the linked program, as a plain binary with ELF debug information removed.
-* `<target>_<device>.elf.s`: the disassembled program with inline source code.
-* `<target>_<device>.*.vmem`: a Verilog memory file which can be read by `$readmemh()` in Verilog code.
-
-Note, `<device>` will be in {`sim_dv`, `sim_verilator`, `fpga_cw310`}, and `<target>` will end in the suffix "`_prog`" for executable images destined for flash.
-
-#### OTBN Artifacts
-
-OTBN programs use a specialized build flow (defined in `rules/otbn.bzl`).
-OTBN programs produce the following artifacts:
-* `<target>.o`: unlinked object file usually representing a single assembly file
-* `<target>.elf`: standalone executable binary representing one or more assembly/object files
-* `<target>.rv32embed.{a,o}`: artifacts representing an OTBN binary, set up to be linked into a RISC-V program
-
-In terms of Bazel rules:
-* the `otbn_library` rule runs the assembler to create `<target>.o` artifacts, and
-* the `otbn_binary` and `otbn_sim_test` rules run the linker on one or more `.o` files to create the `.elf` and `.rv32embed.{a,o}` artifacts.
-
-Since OTBN has limited instruction memory, the best practice is to list each file individually as an `otbn_library`.
-This way, binary targets can easily include only the files they need.
-
-OTBN programs run on the OTBN coprocessor, unlike standard "on-device" programs that run on the main processor (Ibex).
-There are two ways to run an OTBN program:
-1. Run a standalone binary (`.elf`) on the specialized OTBN simulator.
-1. Include a `.rv32embed` artifact in a C program that runs on Ibex, and create an on-device target as described in the previous section.
-
-You can run `.elf` artifacts directly using the simulator as described in [the OTBN README](https://github.com/lowRISC/opentitan/blob/master/hw/ip/otbn/README.md#run-the-python-simulator).
-The `otbn_sim_test` rule is a thin wrapper around `otbn_binary`.
-If you use it, `bazel test` will run the OTBN simulator for you and check the test result.
-
-To include an OTBN program in a C program, you need to add the desired OTBN `otbn_binary` Bazel target to the `deps` list of the C program's Bazel target.
-No `#include` is necessary, but you will likely need to initialize the symbols from the OTBN program as required by the OTBN driver you are using.
-
-#### Host Artifacts
-
-Host software is built and run on the host hardware (e.g., an x64 Linux machine).
-A final linked program in the ELF format is all that is produced for host software builds.
-Note, like the device ELF, the file will not have an extension.
-
-### Disassembling Device Code
-
-A disassembly of all executable sections is produced by the build system by default.
-It can be found by looking for files with the `.elf.s` extension next to the corresponding ELF file.
-
-To get a different type of disassembly, e.g. one which includes data sections in addition to executable sections, objdump can be called manually.
-For example the following command shows how to disassemble all sections of the UART DIF smoke test interleaved with the actual source code:
-
-```console
-riscv32-unknown-elf-objdump --disassemble-all --headers --line-numbers --source \
-  $(find -L bazel-out/ -type f -name "uart_smoketest_prog_sim_verilator")
-```
-
-Refer to the output of `riscv32-unknown-elf-objdump --help` for a full list of options.
-
-### Locating Bazel-built Artifacts
-
-Bazel built artifacts can be located in the symlinked `bazel-out/` directory that gets automatically created on invocations of Bazel.
-To locate build artifacts, you may use the `find` utility.
-For example, after building the UART smoke test device software with
-```console
-bazel build //sw/device/tests:uart_smoketest_sim_verilator
-```
-to locate the `.bin` file use
-```console
-find -L bazel-bin/ -type f -name "uart_smoketest_prog_sim_verilator.bin"
-```
-
-### Troubleshooting Builds
-
-If you encounter an unexplained error building or running any `bazel` commands, you can issue a subsequent `bazel clean` command to erase any existing building directories to yield a clean build.
-Specifically, according to the Bazel [documentation](https://docs.bazel.build/versions/main/user-manual.html#clean), issuing a
-```console
-bazel clean
-```
-deletes all the output build directories, while running a
-```console
-bazel clean --expunge
-```
-will wipe all disk and memory traces (i.e., any cached intermediate files) produced by Bazel.
-The latter sledgehammer is only intended to be used as a last resort when the existing configuration is seriously broken.
-
-### Disk Cache
-
-Bazel can use a directory on the file system as a remote cache.
-This is useful for sharing build artifacts across multiple [`git` worktrees](https://git-scm.com/docs/git-worktree) or multiple workspaces of the same project, such as multiple checkouts.
-
-Use the `--disk_cache=<filename>` to specify a cache directory.
-For example, running
-```console
-bazel build //... --disk_cache=~/bazel_cache
-```
-will cache all built artifacts.
-
-Alternatively add the following to `$HOME/.bazelrc` to avoid having automatically use the disk cache on every Bazel invocation.
-```
-build --disk_cache=~/bazel_cache
-```
-
-For more documentation on Bazel disk caches see the [official documentation](https://docs.bazel.build/versions/main/remote-caching.html#disk-cache).
-
-### Excluding Verilator Simulation Binary Builds
-
-Many device software targets depend on the Verilator simulation binary,
-The Verilator simulation binary is slow to build.
-To avoid building it, use the
-`--define DISABLE_VERILATOR_BUILD=true` build option.
-For example, to build the UART smoke test artifacts but not the Verilator simulation binary run
-```console
-bazel build --define DISABLE_VERILATOR_BUILD=true //sw/device/tests:uart_smoketest
-```
diff --git a/doc/getting_started/install_openocd.md b/doc/getting_started/install_openocd.md
deleted file mode 100644
index a4082f5..0000000
--- a/doc/getting_started/install_openocd.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-title: Install OpenOCD
----
-
-OpenOCD is a tool to connect with the target chip over JTAG and similar transports.
-It also provides a GDB server which is an "intermediate" when debugging software on the chip with GDB.
-
-At least OpenOCD 0.11.0 is required.
-
-It is recommended to use the regular upstream version of OpenOCD instead of the [RISC-V downstream fork](https://github.com/riscv/riscv-openocd).
-
-As most distributions do not yet include OpenOCD 0.11 in its package repositories building from source is likely to be required.
-The following steps build OpenOCD (this should be done outside the `$REPO_TOP` directory):
-
-```console
-$ wget https://downloads.sourceforge.net/project/openocd/openocd/0.11.0/openocd-0.11.0.tar.bz2
-$ tar -xf openocd-0.11.0.tar.bz2
-$ cd openocd-0.11.0/
-$ mkdir build
-$ cd build
-$ ../configure --enable-ftdi --enable-verbose-jtag-io --disable-vsllink --enable-remote-bitbang --prefix=/tools/openocd
-$ make -j4
-$ sudo make install
-```
diff --git a/doc/getting_started/install_vivado/img/step1.png b/doc/getting_started/install_vivado/img/step1.png
deleted file mode 100644
index b559d23..0000000
--- a/doc/getting_started/install_vivado/img/step1.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step2.png b/doc/getting_started/install_vivado/img/step2.png
deleted file mode 100644
index dbe9e3c..0000000
--- a/doc/getting_started/install_vivado/img/step2.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step3.png b/doc/getting_started/install_vivado/img/step3.png
deleted file mode 100644
index e568d5e..0000000
--- a/doc/getting_started/install_vivado/img/step3.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step4.png b/doc/getting_started/install_vivado/img/step4.png
deleted file mode 100644
index a9c4b2d..0000000
--- a/doc/getting_started/install_vivado/img/step4.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step5.png b/doc/getting_started/install_vivado/img/step5.png
deleted file mode 100644
index 835121e..0000000
--- a/doc/getting_started/install_vivado/img/step5.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step6.png b/doc/getting_started/install_vivado/img/step6.png
deleted file mode 100644
index e047bf4..0000000
--- a/doc/getting_started/install_vivado/img/step6.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step7.png b/doc/getting_started/install_vivado/img/step7.png
deleted file mode 100644
index 97aa2b5..0000000
--- a/doc/getting_started/install_vivado/img/step7.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step8.png b/doc/getting_started/install_vivado/img/step8.png
deleted file mode 100644
index 67a0bec..0000000
--- a/doc/getting_started/install_vivado/img/step8.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/step9.png b/doc/getting_started/install_vivado/img/step9.png
deleted file mode 100644
index e705725..0000000
--- a/doc/getting_started/install_vivado/img/step9.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/img/vivado_download.png b/doc/getting_started/install_vivado/img/vivado_download.png
deleted file mode 100644
index 1f4508e..0000000
--- a/doc/getting_started/install_vivado/img/vivado_download.png
+++ /dev/null
Binary files differ
diff --git a/doc/getting_started/install_vivado/index.md b/doc/getting_started/install_vivado/index.md
deleted file mode 100644
index 81bda2a..0000000
--- a/doc/getting_started/install_vivado/index.md
+++ /dev/null
@@ -1,129 +0,0 @@
----
-title: Install Vivado
----
-
-## About Xilinx Vivado
-
-Generating a bitstream for Xilinx devices requires a Vivado installation.
-Vivado is provided by Xilinx, it is freeware for certain (smaller) FPGA devices but requires a commercial license for larger FPGAs.
-The free version is called "WebPACK", the commercial version "Design Edition".
-The installation instructions below are valid for both installation methods.
-
-## Install Xilinx Vivado
-
-_**Vivado Version:** The recommendation is to use Vivado {{< tool_version "vivado" >}}.
-The following instructions have been tested with Vivado 2020.2.
-Some screenshots are generated from Vivado 2020.1, but the installation process has not changed.
-Vivado 2019.1 and all its minor updates are not compatible with this project._
-
-Vivado can be installed in two ways: either through an "All OS installer Single-File Download", or via the "Linux Self Extracting Web Installer".
-Neither option is great:
-the "All OS installer" is a huge download of around 20 GB (and the Xilinx download servers seem to be overloaded regularly), but it supports an unattended installation.
-The web installer downloads only necessary subsets of the software, which significantly reduces the download size.
-But unfortunately it doesn't support the batch mode for unattended installations, requiring users to click through the GUI and select the right options.
-
-To get started faster we use the web installer in the following.
-
-1. Go to the [Xilinx download page](https://www.xilinx.com/support/download.html). Select Vivado {{< tool_version "vivado" >}} from the left-hand side and download two files:
-   1. The file "Xilinx Unified Installer {{< tool_version "vivado" >}}: Linux Self Extracting Web Installer".
-   2. The "Digests" file below the download.
-
-   ![Vivado download site](img/vivado_download.png)
-
-   You need to register for a free Xilinx account to download the software, and you'll need it again later to install the software.
-   Create a new account if you don't have one yet.
-
-2. Before you proceed ensure that the download didn't get corrupted by verifying the checksum.
-
-    ```console
-    $ sha512sum --check Xilinx_Unified_2020.2_1118_1232_Lin64.bin.digests
-    Xilinx_Unified_2020.2_1118_1232_Lin64.bin: OK
-    sha512sum: WARNING: 22 lines are improperly formatted
-    ```
-
-    If you see an "OK" after the downloaded file proceed to the next step. Otherwise delete the download and start over. (You can ignore the warning produced by `sha512sum`.)
-3. Run the graphical installer. It may warn you that a newer version is available; you can ignore the warning.
-
-    ```console
-    $ sh Xilinx_Unified_2020.2_1118_1232_Lin64.bin
-    ```
-
-4. Now you need to click through the installer.
-   Click "Next" on the first screen.
-
-   ![Vivado installation step 1](img/step1.png)
-
-5. Type in your Xilinx User ID (your email address) and the associated password.
-   Choose the "Download and Install Now" option.
-   Click "Next" to continue.
-
-   ![Vivado installation step 2](img/step2.png)
-
-6. Click all "I Agree" checkboxes, and click on "Next" to continue.
-
-   ![Vivado installation step 3](img/step3.png)
-
-7. Choose "Vivado", and click on "Next" to continue.
-
-   ![Vivado installation step 4](img/step4.png)
-
-8. Choose "Vivado HL Design Edition".
-   This is required to enable support for the Xilinx Kintex 7 XC7K410T FPGA device found on the ChipWhisperer CW310 board.
-   You'll need a commercial Vivado license for this FPGA device.
-   Without a valid license, you are still able to install the Vivado HL Design Edition but you'll only be able to work with devices supported by the free WebPACK license.
-   If you don't have a valid license and if you're only planning to work with devices supported by the free WebPACK license, you can also choose "Vivado HL WebPACK" instead.
-
-   ![Vivado installation step 5](img/step5.png)
-
-9. Choose the features to install.
-   You can restrict the features to the ones shown in the screenshot below.
-   Click "Next" to continue.
-
-   ![Vivado installation step 6](img/step6.png)
-
-10. Choose an installation location.
-    Any location which doesn't have a whitespace in its path and enough free space is fine.
-    We use `/tools` in our example, but a path in `/opt` or within the home directory works equally well.
-    Click "Next" to continue.
-
-    ![Vivado installation step 7](img/step7.png)
-
-11. Double-check the installation summary and click on "Install" to start the installation process.
-
-    ![Vivado installation step 8](img/step8.png)
-
-12. Now Vivado is downloaded and installed, a process which can easily take multiple hours.
-
-    ![Vivado installation step 9](img/step9.png)
-
-13. As soon as the installation has completed close the installer and you're now ready to use Vivado!
-
-## Device permissions: udev rules
-
-To program an FPGAs the user using Vivado typically needs to have permissions to access USB devices connected to the PC.
-Depending on your security policy you can take different steps to enable this access.
-One way of doing so is given in the udev rule outlined below.
-
-To do so, create a file named `/etc/udev/rules.d/90-lowrisc.rules` and add the following content to it:
-
-```
-# Grant access to board peripherals connected over USB:
-# - The USB devices itself (used e.g. by Vivado to program the FPGA)
-# - Virtual UART at /dev/tty/XXX
-
-# NewAE Technology Inc. ChipWhisperer boards e.g. CW310, CW305, CW-Lite, CW-Husky
-ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="2b3e", ATTRS{idProduct}=="ace[0-9]|c[3-6][0-9][0-9]", MODE="0666"
-
-# Future Technology Devices International, Ltd FT2232C/D/H Dual UART/FIFO IC
-# used on Digilent boards
-ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{manufacturer}=="Digilent", MODE="0666"
-
-# Future Technology Devices International, Ltd FT232 Serial (UART) IC
-ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666"
-```
-
-You then need to reload the udev rules:
-
-```console
-$ sudo udevadm control --reload
-```
diff --git a/doc/getting_started/setup_dv.md b/doc/getting_started/setup_dv.md
deleted file mode 100644
index 8d55d6f..0000000
--- a/doc/getting_started/setup_dv.md
+++ /dev/null
@@ -1,118 +0,0 @@
----
-title: "Design Verification Setup"
-aliases:
-    - /doc/ug/getting_started_dv
----
-
-_Before following this guide, make sure you've followed the [dependency installation and software build instructions]({{< relref "getting_started" >}})._
-
-This document aims to enable a contributor to get started with a design verification (DV) effort within the OpenTitan project.
-While most of the focus is on development of a testbench from scratch, it should also be useful to understand how to contribute to an existing effort.
-Please refer to the [DV methodology]({{< relref "doc/ug/dv_methodology" >}}) document for information on how design verification is done in OpenTitan.
-
-## Stages of DV
-
-The life stages of a design / DV effort within the OpenTitan are described in the [Hardware Development Stages]({{< relref "doc/project/development_stages.md" >}}) document.
-It separates the life of DV into three broad stages: Initial Work, Under Test and Testing Complete.
-This document attempts to give guidance on how to get going with the first stage and have a smooth transition into the Under Test stage.
-They are not hard and fast rules but methods we have seen work well in the project.
-DV indeed cannot begin until the design has transitioned from Specification to the Development stage first.
-The design specification, once available, is used as a starting point.
-
-## Getting Started
-
-The very first thing to do in any DV effort is to [document the plan]({{< relref "doc/ug/dv_methodology#documentation" >}}) detailing the overall effort.
-This is done in conjunction with developing the initial testbench.
-It is recommended to use the [uvmdvgen]({{< relref "util/uvmdvgen/doc" >}}) tool, which serves both needs.
-
-The `uvmdvgen` tool provides the ability to generate the outputs in a specific directory.
-This should be set to the root of the DUT directory where the `rtl` directory exists.
-When the tool is run, it creates a `dv` directory, along with `data` and `doc` directories.
-The `dv` directory is where the complete testbench along with the collaterals to build and run tests can be found.
-It puts the documentation sources in `doc` and `data` directories respectively (which also exist alongside the `rtl` directory).
-It is recommended to grep for 'TODO' at this stage in all of these generated files to make some of the required fixes right way.
-One of these for example, is to create appropriate interfaces for the DUT-specific IOs and have them connected in the testbench (`dv/tb/tb.sv`).
-
-## Documentation and Initial Review
-
-The skeleton [DV document]({{< relref "doc/ug/dv_methodology#dv-document" >}}) and the [Hjson testplan]({{< relref "doc/ug/dv_methodology#testplan" >}}) should be addressed first.
-The DV documentation is not expected to be completed in full detail at this point.
-However, it is expected to list all the verification components needed and depict the planned testbench as a block diagram.
-Under the 'design verification' directory in the OpenTitan team drive, some sample testbench block diagrams are available in the `.svg` format, which can be used as a template.
-The Hjson testplan, on the other hand, is required to be completed.
-Please refer to the [testplanner tool]({{< relref "util/dvsim/doc/testplanner.md" >}}) documentation for additional details on how to write the Hjson testplan.
-Once done, these documents are to be reviewed with the designer(s) and other project members for completeness and clarity.
-
-## UVM RAL Model
-
-Before running any test, the [UVM RAL model]({{< relref "doc/ug/dv_methodology#uvm-register-abstraction-layer-ral-model" >}}) needs to exist (if the design contains CSRs).
-The [DV simulation flow]({{< relref "hw/dv/tools/doc" >}}) has been updated to generate the RAL model automatically at the start of the simulation.
-As such, nothing extra needs to be done.
-It can be created manually by invoking [`regtool`]({{< relref "util/reggen/doc" >}}):
-```console
-$ util/regtool.py -s -t /path-to-dv /path-to-module/data/<dut>.hjson
-```
-
-The generated file is placed in the simulation build scratch area instead of being checked in.
-
-## Supported Simulators
-
-The use of advanced verification constructs such as SystemVerilog classes (on which UVM is based on) requires commercial simulators.
-The [DV simulation flow]({{< relref "hw/dv/tools/doc" >}}) fully supports Synopsys VCS.
-There is support for Cadence Xcelium as well, which is being slowly ramped up.
-
-## Building and Running Tests
-
-The `uvmdvgen` tool provides an empty shell sequence at `dv/env/seq_lib/<dut>_sanity_vseq.sv` for developing the sanity test.
-The sanity test can be run as-is by invoking `dvsim.py`, as a "hello world" step to bring the DUT out of reset.
-```console
-$ util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson -i <dut>_sanity [--waves] [--tool xcelium]
-```
-
-The generated initial testbench is not expected to compile and elaborate successfully right away.
-There may be additional fixes required, which can be hopefully be identified easily.
-Once the testbench compiles and elaborates without any errors or warnings, the sanity sequence can be developed further to access a major datapath and test the basic functionality of the DUT.
-
-VCS is used as the default simulator.
-It can be switched to Xcelium by setting `--tool xcelium` on the command line.
-To dump waves from the simulation, pass the `--waves` argument to `dvsim.py`.
-Please refer to the [DV simulation flow]({{< relref "hw/dv/tools/doc" >}}) for additional details.
-
-The `uvmdvgen` script also enables the user to run the full suite of CSR tests, if the DUT does have CSRs in it.
-The most basic CSR power-on-reset check test can be run by invoking:
-```console
-$ util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson -i <dut>_csr_hw_reset [--waves] [--tool xcelium]
-```
-Please refer to [CSR utilities]({{< relref "hw/dv/sv/csr_utils/doc" >}}) for more information on how to add exclusions for the CSR tests.
-
-## Full DV
-
-Running the sanity and CSR suite of tests while making progress toward reaching the [V1 stage]({{< relref "doc/project/development_stages#hardware-verification-stages" >}}) should provide a good reference in terms of how to develop tests as outlined in the testplan and running and debugging them.
-Please refer to the [checklist]({{< relref "doc/project/checklist" >}}) to understand the key requirements for progressing through the subsequent verification stages and final signoff.
-
-The [UART DV](https://github.com/lowRISC/opentitan/tree/master/hw/ip/uart/dv) area can be used as a canonical example for making progress.
-If it is not clear on how to proceed, feel free to file an issue requesting assistance.
-
-## Reproduce a DV failure in CI
-
-Follow these steps to reproduce the failure
-
-1. Make sure the version of VCS is the same as the [one](https://github.com/lowRISC/opentitan-private-ci/blob/master/jobs.yml#L5) running in CI.
-
-2. CI runs against an auto-generated merge commit, which effectively is generated by merging the pull request (PR) into the master branch.
-This "merge" branch is updated automatically by GitHub whenever the PR branch is pushed, or when the PR is closed and re-open.
-Retrieve this exact branch by running the following (assuming "upstream" is the name of your lowrisc/opentian repository).
-```console
-$ git fetch upstream pull/<PR_number>/merge
-$ git checkout -b <temp_branch> FETCH_HEAD
-```
-
-3. This is the command that CI runs for the smoke regression.
-```console
-$ util/dvsim/dvsim.py hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson -i smoke --fixed-seed=1
-```
-We can only run the failed test with `--fixed-seed=1` to reproduce the failure.
-Assume there is a failure in the `uart_smoke` test. Here is the run command to reproduce it.
-```console
-$ util/dvsim/dvsim.py hw/ip/uart/dv/uart_sim_cfg.hjson -i uart_smoke --fixed-seed=1 [--waves]
-```
diff --git a/doc/getting_started/setup_formal.md b/doc/getting_started/setup_formal.md
deleted file mode 100644
index 0db5e79..0000000
--- a/doc/getting_started/setup_formal.md
+++ /dev/null
@@ -1,30 +0,0 @@
----
-title: "Formal Verification Setup"
-aliases:
-    - /doc/ug/getting_started_formal
----
-
-_Before following this guide, make sure you've followed the [dependency installation and software build instructions]({{< relref "getting_started" >}})._
-
-This document aims to enable a contributor to get started with a formal verification effort within the OpenTitan project.
-While most of the focus is on development of a testbench from scratch, it should also be useful to understand how to contribute to an existing effort.
-
-Please refer to the [OpenTitan Assertions]({{< relref "hw/formal/doc" >}}) for information on how formal verification is done in OpenTitan.
-
-## Formal property verification (FPV)
-
-It is recommended to use the [fpvgen]({{< relref "util/fpvgen/doc" >}}) tool to automatically create an FPV testbench template.
-To run the FPV tests in `dvsim`, please add the target in `hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson`, then run with command:
-```console
-$ util/dvsim/dvsim.py hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson --select-cfgs {target_name}
-```
-It is recommended to add the FPV target to [lint]({{< relref "hw/lint/doc" >}}) script `hw/top_earlgrey/lint/top_earlgrey_fpv_lint_cfgs.hjson` to quickly find typos.
-
-## Formal connectivity verification
-
-The connectivity verification is mainly used for exhaustively verifying system-level connections.
-User can specify the connection ports via a CSV format file in `hw/top_earlgrey/formal/conn_csvs` folder.
-User can trigger top_earlgrey's connectivity test using `dvsim`:
-```
-  util/dvsim/dvsim.py hw/top_earlgrey/formal/chip_conn_cfgs.hjson
-```
diff --git a/doc/getting_started/setup_fpga.md b/doc/getting_started/setup_fpga.md
deleted file mode 100644
index 50bda86..0000000
--- a/doc/getting_started/setup_fpga.md
+++ /dev/null
@@ -1,346 +0,0 @@
----
-title: "FPGA Setup"
-aliases:
-    - /doc/ug/getting_started_fpga
----
-
-_Before following this guide, make sure you've followed the [dependency installation and software build instructions]({{< relref "getting_started" >}})._
-
-Do you want to try out OpenTitan, but don't have a couple thousand or million dollars ready for an ASIC tapeout?
-Running OpenTitan on an FPGA board can be the answer!
-
-## Prerequisites
-
-To use the OpenTitan on an FPGA you need two things:
-
-* A supported FPGA board
-* A tool from the FPGA vendor
-
-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 on what exactly is needed.
-
-* [Obtain an FPGA board]({{< relref "fpga_boards.md" >}})
-
-## Obtain an FPGA bitstream
-
-To run OpenTitan on an FPGA, you will need an FPGA bitstream.
-You can either download the latest bitstream for the ChipWhisperer CW310 board or build it yourself.
-
-### Download a Pre-built Bitstream
-
-If you are using the ChipWhisperer CW310 board with the Xilinx Kintex 7 XC7K410T FPGA, you can download the latest passing [pre-built bitstream](http://storage.googleapis.com/opentitan-bitstreams/master/bitstream-latest.tar.gz).
-
-For example, to download and unpack the bitstream, run the following:
-
-```console
-mkdir -p /tmp/bitstream-latest
-cd /tmp/bitstream-latest
-curl https://storage.googleapis.com/opentitan-bitstreams/master/bitstream-latest.tar.gz -o bitstream-latest.tar.gz
-tar -xvf bitstream-latest.tar.gz
-```
-
-By default, the bitstream is built with a version of the boot ROM used for testing (called the _test ROM_; pulled from `sw/device/lib/testing/test_rom`).
-There is also a version of the boot ROM used in production (called the _mask ROM_; pulled from `sw/device/silicon_creator/mask_rom`).
-This can be spliced into the bitstream to overwrite the testing boot ROM as described in the [FPGA Reference Manual]({{< relref "ref_manual_fpga.md#boot-rom-development" >}}).
-However, if you do not want to do the splicing yourself, both versions of the bitstream are available in the download as `*.bit.orig` and `*.bit.splice` (containing the test ROM and the mask ROM respectively).
-The metadata for the latest bitstream (the approximate creation time and the associated commit hash) is also available as a text file and can be [downloaded separately](https://storage.googleapis.com/opentitan-bitstreams/master/latest/latest.txt).
-
-### Build an FPGA bitstream
-
-Synthesizing a design for an FPGA board is simple with Bazel.
-While Bazel is the entry point for kicking off the FPGA sythesis, under the hood, it invokes FuseSoC, the hardware package manager / build system supported by OpenTitan.
-During the build process, the boot ROM is baked into the bitstream.
-As mentioned above, we maintain two boot ROM programs, one for testing (_test ROM_), and one for production (_mask ROM_).
-
-To build an FPGA bitstream with the _test ROM_, use:
-```console
-cd $REPO_TOP
-bazel build //hw/bitstream/vivado:fpga_cw310_test_rom
-```
-
-To build an FPGA bitstream with the _mask ROM_, use:
-```console
-cd $REPO_TOP
-bazel build //hw/bitstream/vivado:fpga_cw310_mask_rom
-```
-
-#### Dealing with FPGA Congestion Issues
-
-The default Vivado tool placement may sometimes result in congested FPGA floorplans.
-When this happens, the implemenation time and results become unpredictable.
-It may become necessary for the user to manually adjust certain placement.
-See [this comment](https://github.com/lowRISC/opentitan/pull/8138#issuecomment-916696830) for a thorough analysis of one such situation and what changes were made to improve congestion.
-
-#### Opening an existing project with the Vivado GUI
-
-Sometimes, it may be desirable to open the generated project in the Vivado GUI for inspection.
-To this end, run:
-
-```console
-. /tools/Xilinx/Vivado/{{< tool_version "vivado" >}}/settings64.sh
-cd $REPO_TOP
-make -C $(dirname $(find bazel-out/* -wholename '*synth-vivado/Makefile')) build-gui
-```
-
-Now the Vivado GUI opens and loads the project.
-
-#### Develop with the Vivado GUI
-
-TODO(lowRISC/opentitan[#13213](https://github.com/lowRISC/opentitan/issues/13213)): the below does not work with the Bazel FPGA bitstream build flow.
-
-Sometimes it is helpful to use the Vivado GUI to debug a design.
-FuseSoC (the tool Bazel invokes) makes that easy, with one small caveat: by default FuseSoC copies all source files into a staging directory before the synthesis process starts.
-This behavior is helpful to create reproducible builds and avoids Vivado modifying checked-in source files.
-But during debugging this behavior is not helpful.
-The `--no-export` option of FuseSoC disables copying the source files into the staging area, and `--setup` instructs fusesoc to not start the synthesis process.
-
-```console
-# Only create Vivado project directory by using FuseSoC directly (skipping Bazel invocation).
-cd $REPO_TOP
-fusesoc --cores-root . run --flag=fileset_top --target=synth --no-export --setup lowrisc:systems:chip_earlgrey_cw310
-```
-
-You can then navigate to the created project directory, and open Vivado
-```console
-. /tools/Xilinx/Vivado/{{< tool_version "vivado" >}}/settings64.sh
-cd $REPO_TOP/build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/
-vivado
-```
-
-Finally, using the Tcl console, you can kick off the project setup with
-```console
-source lowrisc_systems_chip_earlgrey_cw310_0.1.tcl
-```
-
-## Connecting the ChipWhisperer CW310 board
-
-The ChipWhisperer CW310 board supports different power options.
-It is recommended to power the board via the included DC power adapter.
-To this end:
-1. Set the *SW2* switch (to the right of the barrel connector) up to the *5V Regulator* option.
-1. Set the switch below the barrel connector to the right towards the *Barrel* option.
-1. Set the *Control Power* switch (bottom left corner, *SW7*) to the right.
-1. Ensure the *Tgt Power* switch (above the fan, *S1*) is set to the right towards the *Auto* option.
-1. Plug the DC power adapter into the barrel jack (*J11*) in the top left corner of the board.
-1. Use a USB-C cable to connect your PC with the *USB-C Data* connector (*J8*) in the lower left corner on the board.
-
-You can now use the following to monitor output from dmesg:
-```console
-sudo dmesg -Hw
-```
-This should show which serial ports have been assigned, or if the board is having trouble connecting to USB.
-If `dmesg` reports a problem you can trigger a *USB_RST* with *SW5*.
-When properly connected, `dmesg` should identify the board, not show any errors, and the status light should flash.
-They should be named `'/dev/ttyACM*'`, e.g. `/dev/ttyACM1`.
-To ensure that you have sufficient access permissions, set up the udev rules as explained in the [Vivado installation instructions]({{< relref "install_vivado" >}}).
-
-## Flash the bitstream onto the FPGA and bootstrap software into flash
-
-There are two ways to load a bitstream on to the FPGA and bootstrap software into the OpenTitan embedded flash:
-1. **automatically**, on single invocations of `bazel test ...`.
-1. **manually**, using multiple invocations of `opentitantool`, and
-Which one you use, will depend on how the build target is defined for the software you would like to test on the FPGA.
-Specifically, for software build targets defined in Bazel BUILD files uing the `opentitan_functest` Bazel macro, you will use the latter (**automatic**) approach.
-Alternatively, for software build targets defined in Bazel BUILD files using the `opentitan_flash_binary` Bazel macro, you will use the former (**manual**) approach.
-
-See below for details on both approaches.
-
-### Automatically loading FPGA bitstreams and bootstrapping software Bazel
-
-A majority of on-device software tests are defined using the custom `opentitan_functest` Bazel macro, which under the hood, instantiates several Bazel [`native.sh_test` rules](https://docs.bazel.build/versions/main/be/shell.html#sh_test).
-In doing so, this macro provides a convenient interface for developers to run software tests on OpenTitan FPGA instances with a single invocation of `bazel test ...`.
-For example, to run the UART smoke test (which is an `opentitan_functest` defined in `sw/device/tests/BUILD`) on FPGA hardware, and see the output in real time, use:
-```console
-cd $REPO_TOP
-bazel test --test_tag_filters=cw310 --test_output=streamed //sw/device/tests:uart_smoketest
-```
-or
-```console
-cd $REPO_TOP
-bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
-```
-
-Under the hood, Bazel conveniently dispatches `opentitantool` to both:
-* ensure the correct version of the FPGA bitstream has been loaded onto the FPGA, and
-* bootstrap the desired software test image into the OpenTitan embedded flash.
-
-To get a better understanding of the `opentitantool` functions Bazel invokes automatically, follow the instructions for manually loading FPGA bitstreams below.
-
-#### Configuring Bazel to load the Vivado-built bitstream
-
-By default, the above invocations of `bazel test ...` use the pre-built (Internet downloaded) FPGA bitstream.
-To instruct bazel to load the bitstream built earlier, or to have bazel build an FPGA bitstream on the fly, and load that bitstream onto the FPGA, add the `--define bitstream=vivado` flag to either of the above Bazel commands, for example, run:
-```
-bazel test --define bitstream=vivado --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
-```
-
-#### Configuring Bazel to skip loading a bitstream
-
-Alternatively, if you would like to instruct Bazel to skip loading any bitstream at all, and simply use the bitstream that is already loaded, add the `--define bitstream=skip` flag, for example, run:
-```
-bazel test --define bitstream=skip --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
-```
-
-### Manually loading FPGA bitstreams and bootstrapping OpenTitan software with `opentitantool`
-
-Some on-device software targets are defined using the custom `opentitan_flash_binary` Bazel macro.
-Unlike the `opentitan_functest` macro, the `opentitan_flash_binary` macro does **not** instantiate any Bazel test rules under the hood.
-Therefore, to run such software on OpenTitan FPGA hardware, both a bitstream and the software target must be loaded manually onto the FPGA.
-Below, we describe how to accomplish this, and in doing so, we shed some light on the tasks that Bazel automates through the use of `opentitan_functest` Bazel rules.
-
-#### Manually loading a bitstream onto the FPGA with `opentitantool`
-
-Note: The following examples assume that you have a `~/.config/opentitantool/config` with the proper `--interface` and `--conf` options.
-For the CW310, its contents would look like:
-```
---interface=cw310
---conf=<ABS_PATH_TO_YOUR_CLONE>/sw/host/opentitantool/config/opentitan_cw310.json
-```
-
-To flash the bitstream onto the FPGA using `opentitantool`, use the following command:
-
-```console
-cd $REPO_TOP
-
-### If you downloaded the bitstream from the Internet:
-bazel run //sw/host/opentitantool load-bitstream /tmp/bitstream-latest/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig
-
-### If you built the bitstream yourself:
-bazel run //sw/host/opentitantool load-bitstream $(ci/scripts/target-location.sh //hw/bitstream/vivado:fpga_cw310_test_rom)
-```
-
-Depending on the FPGA device, the flashing itself may take several seconds.
-After completion, a message like this should be visible from the UART:
-
-```
-I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
-I00001 test_rom.c:87] TestROM:6b2ca9a1
-I00002 test_rom.c:118] Test ROM complete, jumping to flash!
-```
-
-#### Bootstrapping the demo software
-
-The `hello_world` demo software shows off some capabilities of the OpenTitan hardware.
-To load `hello_world` into the FPGA on the ChipWhisperer CW310 board follow the steps shown below.
-
-1. Generate the bitstream and flash it to the FPGA as described above.
-1. Open a serial console (use the device file determined before) and connect.
-   Settings: 115200 baud, 8 bits per byte, no software flow-control for sending and receiving data.
-   ```console
-   screen /dev/ttyACM1 115200,cs8,-ixon,-ixoff
-   ```
-1. Run `opentitantool`.
-   ```console
-   cd ${REPO_TOP}
-   bazel run //sw/host/opentitantool set-pll # This needs to be done only once.
-   bazel build //sw/device/examples/hello_world:hello_world_fpga_cw310_bin
-   bazel run //sw/host/opentitantool bootstrap $(ci/scripts/target-location.sh //sw/device/examples/hello_world:hello_world_fpga_cw310_bin)
-   ```
-
-   and then output like this should appear from the UART:
-   ```
-   I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
-   I00001 test_rom.c:87] TestROM:6b2ca9a1
-   I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
-   I00001 test_rom.c:87] TestROM:6b2ca9a1
-   I00002 test_rom.c:118] Test ROM complete, jumping to flash!
-   I00000 hello_world.c:66] Hello World!
-   I00001 hello_world.c:67] Built at: Jun 13 2022, 14:16:59
-   I00002 demos.c:18] Watch the LEDs!
-   I00003 hello_world.c:74] Try out the switches on the board
-   I00004 hello_world.c:75] or type anything into the console window.
-   I00005 hello_world.c:76] The LEDs show the ASCII code of the last character.
-   ```
-
-1. Observe the output both on the board and the serial console. Type any text into the console window.
-1. Exit `screen` by pressing CTRL-a k, and confirm with y.
-
-#### Troubleshooting
-
-If the firmware load fails, try pressing the "USB RST" button before loading the bitstream.
-
-## Connect with OpenOCD and debug
-
-To connect the ChipWhisperer CW310 FPGA board with OpenOCD, run the following command
-
-```console
-cd $REPO_TOP
-openocd -s util/openocd -f board/lowrisc-earlgrey-cw310.cfg
-```
-
-See the [install instructions]({{< relref "install_openocd" >}}) for guidance on installing OpenOCD.
-
-To actually debug through OpenOCD, it must either be connected through telnet or GDB.
-
-### Debug with OpenOCD
-
-The following is an example for using telnet
-
-```console
-telnet localhost 4444 // or whatever port that is specificed by the openocd command above
-mdw 0x8000 0x10 // read 16 bytes at address 0x8000
-```
-
-### Debug with GDB
-
-Fist, make sure the device software has been built with debug symbols (by default Bazel does not build software with debug symbols).
-For example, to build and test the UART smoke test with debug symbols, you can add `--copt=-g` flag to the `bazel test ...` command:
-```console
-cd $REPO_TOP
-bazel test --copt=-g --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310
-```
-
-Then a connection between OpenOCD and GDB may be established with:
-```console
-cd $REPO_TOP
-riscv32-unknown-elf-gdb -ex "target extended-remote :3333" -ex "info reg" \
-  $(find -L bazel-out/ -type f -name "uart_smoketest_sim_verilator | head -n 1")
-```
-
-Note, the above will print out the contents of the registers upon successs.
-
-#### Common operations with GDB
-
-Examine 16 memory words in the hex format starting at 0x200005c0
-
-```console
-(gdb) x/16xw 0x200005c0
-```
-
-Press enter again to print the next 16 words.
-Use `help x` to get a description of the command.
-
-If the memory content contains program text it can be disassembled
-
-```console
-(gdb) disassemble 0x200005c0,0x200005c0+16*4
-```
-
-Displaying the memory content can also be delegated to OpenOCD
-
-```console
-(gdb) monitor mdw 0x200005c0 16
-```
-
-Use `monitor help` to get a list of supported commands.
-
-To single-step use `stepi` or `step`
-
-```console
-(gdb) stepi
-```
-
-`stepi` single-steps an instruction, `step` single-steps a line of source code.
-When testing debugging against the hello_world binary it is likely you will break into a delay loop.
-Here the `step` command will seem to hang as it will attempt to step over the whole delay loop with a sequence of single-step instructions which may take quite some time!
-
-To change the program which is debugged the `file` command can be used.
-This will update the symbols which are used to get information about the program.
-It is especially useful in the context of our `rom.elf`, which resides in the ROM region, which will eventually jump to a different executable as part of the flash region.
-
-```console
-(gdb) file sw/device/examples/hello_world/sw.elf
-(gdb) disassemble 0x200005c0,0x200005c0+16*4
-```
-
-The output of the disassemble should now contain additional information.
diff --git a/doc/getting_started/setup_verilator.md b/doc/getting_started/setup_verilator.md
deleted file mode 100644
index 0361299..0000000
--- a/doc/getting_started/setup_verilator.md
+++ /dev/null
@@ -1,242 +0,0 @@
----
-title: Verilator Setup
-aliases:
-    - /doc/ug/getting_started_verilator
----
-
-_Before following this guide, make sure you've followed the [dependency installation and software build instructions]({{< relref "getting_started" >}})._
-
-## About Verilator
-
-Verilator is a cycle-accurate simulation tool.
-It translates synthesizable Verilog code into a simulation program in C++, which is then compiled and executed.
-
-### Install Verilator
-
-Even though Verilator is packaged for most Linux distributions these versions tend to be too old to be usable.
-We recommend compiling Verilator from source, as outlined here.
-
-Fetch, build and install Verilator itself (this should be done outside the `$REPO_TOP` directory).
-
-```console
-export VERILATOR_VERSION={{< tool_version "verilator" >}}
-
-git clone https://github.com/verilator/verilator.git
-cd verilator
-git checkout v$VERILATOR_VERSION
-
-autoconf
-./configure --prefix=/tools/verilator/$VERILATOR_VERSION
-make
-make install
-```
-
-After installation you need to add `/tools/verilator/$VERILATOR_VERSION/bin` to your `PATH` environment variable.
-Also add it to your `~/.bashrc` or equivalent so that it's on the `PATH` in the future, like this:
-```console
-export PATH=/tools/verilator/$VERILATOR_VERSION/bin:$PATH
-```
-
-Check your installation by running:
-```console
-$ verilator --version
-Verilator 4.210 2021-07-07 rev v4.210 (mod)
-```
-
-#### Troubleshooting
-
-If you need to install to a different location than `/tools/verilator/...`, you can pass a different directory to `./configure --prefix` above and add `your/install/location/bin` to `PATH` instead.
-
-## Running Software on a Verilator Simulation with Bazel
-
-First the RTL must be built into a simulator binary.
-This is done by running fusesoc, which collects up RTL code and passes it to Verilator to generate and then compile a C++ model.
-Next software must be built to run on the simulated hardware.
-There are 4 memory types on OpenTitan hardware: ROM, Flash, OTP, and SRAM.
-Software images need to be provided for ROM, Flash, and OTP (SRAM is populated at runtime).
-By default, the system will first execute out of ROM and then jump to Flash.
-The OTP image does not contain executable code, rather it contains root secrets, runtime configuration data, and life cycle state.
-(By default, the life cycle state is set to RMA, which enables debugging features such as the JTAG interface for the main processor.)
-Lastly, the Verilator simulation binary must be run with the correct arguments.
-
-Thankfully, Bazel (and `opentitantool`) simplify this process by providing a single interface for performing all of the above steps.
-Moreover, Bazel automatically connects to the simulated UART (via `opentitantool`) to print the test output in real time.
-
-For example, to run the UART smoke test on Verilator simulated hardware, and see the output in real time, use
-```console
-cd $REPO_TOP
-bazel test --test_tag_filters=verilator --test_output=streamed //sw/device/tests:uart_smoketest
-```
-or
-```console
-cd $REPO_TOP
-bazel test --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator
-```
-
-You should expect to see something like:
-```console
-Invoking: sw/host/opentitantool/opentitantool --rcfile= --logging=info --interface=verilator --conf=sw/host/opentitantool/config/opentitan_verilator.json --verilator-bin=hw/build.verilator_real/sim-verilator/Vchip_sim_tb --verilator-rom=sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem --verilator-flash=sw/device/tests/uart_smoketest_prog_sim_verilator.64.scr.vmem --verilator-otp=hw/ip/otp_ctrl/data/img_rma.vmem console --exit-failure=(FAIL|FAULT).*\n --exit-success=PASS.*\n --timeout=3600s
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::subprocess] Spawning verilator: "hw/build.verilator_real/sim-verilator/Vchip_sim_tb" ["--meminit=rom,sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem", "--meminit=flash,sw/device/tests/uart_smoketest_prog_sim_verilator.64.scr.vmem", "--meminit=otp,hw/ip/otp_ctrl/data/img_rma.vmem"]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Simulation of OpenTitan Earl Grey
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] =================================
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Tracing can be toggled by sending SIGUSR1 to this process:
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ kill -USR1 3422749
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: FIFO pipes created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read (read) and $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write (write) for 32-bit wide GPIO.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: To measure the values of the pins as driven by the device, run
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ cat $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read  # '0' low, '1' high, 'X' floating
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: To drive the pins, run a command like
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ echo 'h09 l31' > $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write  # Pull the pin 9 high, and pin 31 low.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] SPI: Created /dev/pts/9 for spi0. Connect to it with any terminal program, e.g.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ screen /dev/pts/9
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] NOTE: a SPI transaction is run for every 4 characters entered.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] SPI: Monitor output file created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/spi0.log. Works well with tail:
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ tail -f $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/spi0.log
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] UART: Created /dev/pts/10 for uart0. Connect to it with any terminal program, e.g.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ screen /dev/pts/10
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] UART: Additionally writing all UART output to 'uart0.log'.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] USB: Monitor output file created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/usb0.log. Works well with tail:
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ tail -f $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/usb0.log
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] JTAG: Virtual JTAG interface dmi0 is listening on port 44853. Use
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] OpenOCD and the following configuration to connect:
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   interface remote_bitbang
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   remote_bitbang_host localhost
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   remote_bitbang_port 44853
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Simulation running, end by pressing CTRL-c.
-[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
-[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] Verilator started with the following interaces:
-[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] gpio_read = $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read
-[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] gpio_write = $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write
-[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] uart = /dev/pts/10
-[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] spi = /dev/pts/9
-Starting interactive console
-[CTRL+C] to exit.
-
-I00000 test_rom.c:81] Version:    earlgrey_silver_release_v5-5775-gefa09d3b8
-Build Date: 2022-06-09, 00:12:35
-
-I00001 test_rom.c:118] Test ROM complete, jumping to flash!
-I00000 status.c:28] PASS!
-
-
-Exiting interactive console.
-[2022-06-09T08:09:38Z INFO  opentitantool::command::console] ExitSuccess("PASS!\r\n")
-```
-
-**For most use cases, interacting with the UART is all you will need and you can stop here.**
-However, if you want to interact with the simulation in additional ways, there are more options listed below.
-
-## Execution Log
-
-All executed instructions in the loaded software into Verilator simulations are logged to the file `trace_core_00000000.log`.
-By default this file is stored somewhere in `~/.cache/bazel`, you can find it using the following command:
-
-```console
-find ~/.cache/bazel -name "trace_core_00000000.log"
-```
-
-The columns in this file are tab separated; change the tab width in your editor if the columns don't appear clearly, or open the file in a spreadsheet application.
-
-## Interact with GPIO (optional)
-
-The simulation includes a DPI module to map general-purpose I/O (GPIO) pins to two POSIX FIFO files: one for input, and one for output.
-Observe the `gpio0-read` file for outputs (in the same directory as the trace):
-
-```console
-cat gpio0-read
-```
-
-To drive input pins write to the `gpio0-write` file.
-A command consists of the desired state: `h` for high, and `l` for low, and the decimal pin number.
-Multiple commands can be issued by separating them with a single space.
-
-```console
-echo 'h09 l31' > gpio0-write  # Pull the pin 9 high, and pin 31 low.
-```
-
-## Connect with OpenOCD to the JTAG port and use GDB (optional)
-
-The simulation includes a "virtual JTAG" port to which OpenOCD can connect using its `remote_bitbang` driver.
-All necessary configuration files are included in this repository.
-
-See the [OpenOCD install instructions]({{< relref "install_openocd.md" >}}) for guidance on installing OpenOCD.
-
-Run the simulation with Bazel, making sure to build the device software with debug symbols using
-```console
-cd $REPO_TOP
-bazel test --copt=-g --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator
-```
-
-Then, connect with OpenOCD using the following command.
-
-```console
-cd $REPO_TOP
-/tools/openocd/bin/openocd -s util/openocd -f board/lowrisc-earlgrey-verilator.cfg
-```
-
-Lastly, connect GDB using the following command (noting it needs to be altered to point to the sw binary in use).
-
-```console
-cd $REPO_TOP
-riscv32-unknown-elf-gdb -ex "target extended-remote :3333" -ex "info reg" \
-  $(find -L bazel-out/ -type f -name "uart_smoketest_sim_verilator | head -n 1")
-```
-
-## SPI device test interface (optional)
-
-The simulation contains code to monitor the SPI bus and provide a host interface to allow interaction with the `spi_device`.
-When starting the simulation you should see a message like
-
-```console
-SPI: Created /dev/pts/4 for spi0. Connect to it with any terminal program, e.g.
-$ screen /dev/pts/4
-NOTE: a SPI transaction is run for every 4 characters entered.
-SPI: Monitor output file created at /auto/homes/mdh10/github/opentitan/spi0.log. Works well with tail:
-$ tail -f /auto/homes/mdh10/github/opentitan/spi0.log
-```
-
-Use any terminal program, e.g. `screen` or `microcom` to connect to the simulation.
-
-```console
-screen /dev/pts/4
-```
-
-Microcom seems less likely to send unexpected control codes when starting:
-```console
-microcom -p /dev/pts/4
-```
-
-The terminal will accept (but not echo) characters.
-After 4 characters are received a 4-byte SPI packet is sent containing the characters.
-The four characters received from the SPI transaction are echoed to the terminal.
-The `hello_world` code will print out the bytes received from the SPI port (substituting _ for non-printable characters).
-The `hello_world` code initially sets the SPI transmitter to return `SPI!` (so that should echo after the four characters are typed) and when bytes are received it will invert their bottom bit and set them for transmission in the next transfer (thus the Nth set of four characters typed should have an echo of the N-1th set with bottom bit inverted).
-
-The SPI monitor output is written to a file.
-It may be monitored with `tail -f` which conveniently notices when the file is truncated on a new run, so does not need restarting between simulations.
-The output consists of a textual "waveform" representing the SPI signals.
-
-## Generating waveforms (optional)
-
-TODO(lowRISC/opentitan[#13042](https://github.com/lowRISC/opentitan/issues/13042)): the below does not work with the Bazel test running flow.
-
-With the `--trace` argument the simulation generates a FST signal trace which can be viewed with Gtkwave (only).
-Tracing slows down the simulation by roughly factor of 1000.
-
-```console
-cd $REPO_TOP
-build/lowrisc_dv_chip_verilator_sim_0.1/sim-verilator/Vchip_sim_tb \
-  --meminit=rom,build-bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem \
-  --meminit=flash,build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.64.scr.vmem \
-  --meminit=otp,build-bin/sw/device/otp_img/otp_img_sim_verilator.vmem \
-  --trace
-gtkwave sim.fst
-```
diff --git a/doc/guides/getting_started/book.toml b/doc/guides/getting_started/book.toml
new file mode 100644
index 0000000..119fd08
--- /dev/null
+++ b/doc/guides/getting_started/book.toml
@@ -0,0 +1,22 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+[book]
+authors = ["lowRISC"]
+language = "en"
+multilingual = false
+src = "src"
+title = "Getting Started"
+
+[output.html]
+site-url = "guides/getting_started/"
+git-repository-url = "https://github.com/lowrisc/opentitan"
+edit-url-template = "https://github.com/lowrisc/opentitan/edit/master/doc/guides/getting_started/{path}"
+curly-quotes = true
+
+[preprocessor.readme2index]
+command = "../../../util/mdbook_readme2index.py"
+
+[preprocessor.toolversion]
+command = "../../../util/mdbook_toolversion.py"
diff --git a/doc/guides/getting_started/src/README.md b/doc/guides/getting_started/src/README.md
new file mode 100644
index 0000000..442f502
--- /dev/null
+++ b/doc/guides/getting_started/src/README.md
@@ -0,0 +1,257 @@
+# Getting Started
+
+Welcome!
+This guide will help you get OpenTitan up and running.
+
+## Workflow Options
+
+An important preliminary note: to run OpenTitan software, you will need to not only build the software but somehow simulate the hardware it runs on.
+As shown in the diagram below, we currently support multiple build targets and workflows, including: Verilator, FPGA, and DV (commercial RTL simulators, such as VCS and Xcelium).
+**However, if you are new to the project, we recommend simulation with Verilator.**
+This uses only free tools, and does not require any additional hardware such as an FPGA.
+
+![Getting Started Workflow](getting_started_workflow.svg)
+
+This guide will focus on the Verilator workflow, but indicate when those following FPGA or DV workflows should do something different.
+Just keep in mind, if you're a new user and you don't know you're part of the FPGA or DV crowd, "Verilator" means you!
+
+## Step 0: Clone the OpenTitan Repository
+
+Clone the [OpenTitan repository](https://github.com/lowRISC/opentitan):
+```console
+git clone https://github.com/lowRISC/opentitan.git
+```
+
+If you wish to *contribute* to OpenTitan you will need to make a fork on GitHub and may wish to clone the fork instead.
+We have some [notes for using GitHub](https://opentitan.org/book/doc/contributing/github_notes.md) which explain how to work with your own fork (and perform many other GitHub tasks) in the OpenTitan context.
+
+***Note: throughout the documentation `$REPO_TOP` refers to the path where the OpenTitan repository is checked out.***
+Unless you've specified some other name in the clone, `$REPO_TOP` will be a directory called `opentitan`.
+You can create the environment variable by calling the following command from the same directory where you ran `git clone`:
+```console
+export REPO_TOP=$PWD/opentitan
+```
+
+## Step 1: Check System Requirements
+
+**OpenTitan installation requires Linux.**
+If you do not have Linux, please stop right here and use the (experimental) [Docker container](https://github.com/lowRISC/opentitan/tree/master/util/container).
+You can then **skip to step 4** (building software).
+
+If you do have Linux, you are still welcome to try the Docker container.
+However, as the container option is currently experimental, we recommend following the steps below to build manually if you plan on being a long-term user or contributor for the project.
+
+Our continuous integration setup runs on Ubuntu 20.04 LTS, which gives us the most confidence that this distribution works out of the box.
+We do our best to support other distributions, but cannot guarantee they can be used "out of the box" and might require updates of packages.
+Please file a [GitHub issue](https://github.com/lowRISC/opentitan/issues) if you need help or would like to propose a change to increase compatibility with other distributions.
+
+You will need at least **7GiB of available RAM** in order to build the Verilator simulation.
+If you have an FPGA and download the bitstream from our cloud bucket rather than building it locally (the default setup) then this constraint does not apply.
+
+If you are specifying a new machine to run top-level simulations of the whole of OpenTitan using Verilator, it is recommended that you
+have a minimum of **32GiB of physical RAM** and at least **512GiB of SSD/HDD storage** for the build tools, repository and Ubuntu installation.
+
+## Step 2: Install Package Manager Dependencies
+
+*Skip this step if using the Docker container.*
+
+A number of software packages from the distribution's package manager are required.
+On Ubuntu 20.04, the required packages can be installed with the following command.
+
+```sh
+sed -e '/^$/d' -e '/^#/d' ./apt-requirements.txt | sudo apt install -y
+```
+
+Some tools in this repository are written in Python 3 and require Python dependencies to be installed through `pip`.
+We recommend installing the latest version of `pip` and `setuptools` (especially if on older systems such as Ubuntu 18.04) using:
+
+```console
+python3 -m pip install --user -U pip "setuptools<66.0.0"
+```
+
+The `pip` installation instructions use the `--user` flag to install without root permissions.
+Binaries are installed to `~/.local/bin`; check that this directory is listed in your `PATH` by running `which pip3`.
+It should show `~/.local/bin/pip3`.
+If it doesn't, add `~/.local/bin` to your `PATH`, e.g. by adding the following line to your `~/.bashrc` file:
+
+```console {title=~/.bashrc}
+export PATH=$PATH:~/.local/bin
+```
+
+Now install additional Python dependencies:
+
+```console
+cd $REPO_TOP
+pip3 install --user -r python-requirements.txt
+```
+
+### Adjust GCC version (if needed)
+
+On Ubuntu 18.04 the package `build-essential` includes the compilers `gcc-7` and `g++-7`.
+But for the OpenTitan project `gcc-9`/`g++-9` or higher is required, which has to be installed manually.
+Check that you have version 9 or higher of `gcc` installed by:
+
+```console
+gcc --version
+```
+
+If your version is lower, you have to upgrade it manually.
+For this, first, add `ubuntu-toolchain-r/test` PPA to your system using the following commands:
+
+```console
+sudo apt install software-properties-common
+sudo add-apt-repository ppa:ubuntu-toolchain-r/test
+```
+
+Next, install the necessary GCC and G++ version by:
+
+```console
+sudo apt update
+sudo apt install gcc-9 g++-9
+```
+
+Finally, update the symbolic links for `gcc` and `g++` using these commands:
+
+```console
+sudo ln -sf /usr/bin/gcc-9 /usr/bin/gcc
+sudo ln -sf /usr/bin/g++-9 /usr/bin/g++
+```
+
+## Step 3: Install the LowRISC RISC-V Toolchain
+
+*Skip this step if using the Docker container.*
+
+To build device software you need a baremetal RISC-V toolchain (including, for example, a C compiler).
+Even if you already have one installed, we recommend using the prebuilt toolchain provided by lowRISC, because it is built with the specific patches and options that OpenTitan needs.
+You can install the toolchain using the `util/get-toolchain.py` script, which will download and install the toolchain to the default path, `/tools/riscv`.
+
+```console
+cd $REPO_TOP
+./util/get-toolchain.py
+```
+
+If you did not encounter errors running the script, **you're done and can go to step 4**.
+If you did, read on.
+
+#### Troubleshooting
+
+If you need to install to a different path than `/tools/riscv` (for instance, if you do not have permission to write to the `/tools` directory), then you can specify a different location using the `--install-dir` option.
+Run `./util/get-toolchain.py --help` for details.
+You can alternatively download the tarball starting with `lowrisc-toolchain-rv32imcb-` from [GitHub releases](https://github.com/lowRISC/lowrisc-toolchains/releases/latest) and unpack it to the desired installation directory.
+
+Assuming one of the above worked and you have installed to a non-standard location, you will need to set the `TOOLCHAIN_PATH` environment variable to match whatever path you used.
+For example, if I wanted to install to `~/ot_tools/riscv`, then I would use:
+```console
+./util/get-toolchain.py --install-dir ~/ot_tools/riscv
+export TOOLCHAIN_PATH=~/ot_tools/riscv
+```
+Add the `export` command to your `~/.bashrc` or equivalent to ensure that the `TOOLCHAIN_PATH` variable is set for future sessions.
+Check that it worked by opening a new terminal and running:
+```console
+ls $TOOLCHAIN_PATH/bin/riscv32-unknown-elf-as
+```
+If that prints out the file path without errors, then you've successfully installed the toolchain.
+Otherwise, try to find the `riscv32-unknown-elf-as` file in your file system and make sure `$TOOLCHAIN_PATH` is correctly set.
+
+## Step 4: Set up your Simulation Tool or FPGA
+
+*Note: If you are using the pre-built Docker container, Verilator is already installed.
+Unless you know you need the FPGA or DV guides, you can skip this step.*
+
+In order to run the software, we need to have some way to emulate an OpenTitan chip.
+There are a few different options depending on your equipment and use-case.
+Follow the guide(s) that applies to you:
+* **Option 1 (Verilator setup, recommended for new users):** [Verilator guide](./setup_verilator.md), or
+* Option 2 (FPGA setup): [FPGA guide](./setup_fpga.md), or
+* Option 3 (design verification setup): [DV guide](./setup_dv.md)
+
+## Step 5: Build OpenTitan Software
+
+Follow the [dedicated guide](./build_sw.md) to build OpenTitan's software and run tests.
+
+## Step 6: Optional Additional Steps
+
+If you have made it this far, congratulations!
+Hopefully you got a "Hello World!" demo running on OpenTitan using either the Verilator or FPGA targets.
+
+Depending on the specific way you want to use or contribute to OpenTitan, there may be a few extra steps you want to do.
+In particular:
+* *If you want to contribute SystemVerilog code upstream to OpenTitan*, follow step 6a to install Verible.
+* *If you want to debug on-chip OpenTitan software with GDB*, follow step 6b to install OpenOCD.
+* *If you want to run supported formal verification flows for OpenTitan, using tools like JasperGold,* follow step 6c to set up formal verification.
+* *If you want to simulate OpenTitan using Siemens Questa,* follow step 6d to set it up.
+
+It also may make sense to stick with the basic setup and come back to these steps if you find you need them later.
+
+### Step 6a: Install Verible (optional)
+
+Verible is an open source SystemVerilog style linter and formatting tool.
+The style linter is relatively mature and we use it as part of our [RTL design flow](https://opentitan.org/book/doc/contributing/hw/methodology.md).
+The formatter is still under active development, and hence its usage is more experimental in OpenTitan.
+
+You can download and build Verible from scratch as explained on the [Verible GitHub page](https://github.com/google/verible/).
+But since this requires the Bazel build system the recommendation is to download and install a pre-built binary as described below.
+
+Go to [this page](https://github.com/google/verible/releases) and download the correct binary archive for your machine.
+
+The example below is for Ubuntu 20.04:
+
+```
+export VERIBLE_VERSION={{#tool-version verible }}
+wget https://github.com/google/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-Ubuntu-20.04-focal-x86_64.tar.gz
+tar -xf verible-${VERIBLE_VERSION}-Ubuntu-20.04-focal-x86_64.tar.gz
+```
+
+If you are using Ubuntu 18.04 then instead use:
+
+```console
+export VERIBLE_VERSION={{#tool-version verible }}
+wget https://github.com/google/verible/releases/download/${VERIBLE_VERSION}/verible-${VERIBLE_VERSION}-Ubuntu-18.04-bionic-x86_64.tar.gz
+tar -xf verible-${VERIBLE_VERSION}-Ubuntu-18.04-bionic-x86_64.tar.gz
+```
+
+Then install Verible within 'tools' using:
+
+```
+sudo mkdir -p /tools/verible/${VERIBLE_VERSION}/
+sudo mv verible-${VERIBLE_VERSION}/* /tools/verible/${VERIBLE_VERSION}/
+```
+
+After installation you need to add `/tools/verible/$VERIBLE_VERSION/bin` to your `PATH` environment variable.
+
+Note that we currently use version {{#tool-version verible }}, but it is expected that this version is going to be updated frequently, since the tool is under active development.
+
+### Step 6b: Install OpenOCD (optional)
+
+See the [OpenOCD install guide](./install_openocd.md).
+
+### Step 6c: Set up formal verification (optional)
+
+See the [formal verification setup guide](./setup_formal.md)
+
+### Step 6d: Set up Siemens Questa (optional)
+
+Once a standard installation of Questa has been completed, add `QUESTA_HOME` as an environment variable which points to the Questa installation directory.
+
+As of Questa version 21.4 there are some code incompatibilities with the OpenTitan code-base.
+See issue [#9514](https://github.com/lowRISC/opentitan/issues/9514) for the list of issues and temporary workarounds.
+
+## Step 7: Additional Resources
+
+As you may have guessed, there are several other pieces of hardware and software, besides a "Hello World!" demo, that are being actively developed for the OpenTitan project.
+If you are interested in these, check out the additional resources below.
+
+### General
+* [Directory Structure](https://opentitan.org/book/doc/contributing/directory_structure.md)
+* [GitHub Notes](https://opentitan.org/book/doc/contributing/github_notes.md)
+* [Building Documentation](./build_docs.md)
+* [Design Methodology within OpenTitan](https://opentitan.org/book/doc/contributing/hw/methodology.md)
+
+### Hardware
+* [Designing Hardware](https://opentitan.org/book/doc/contributing/hw/design.md)
+* [OpenTitan Hardware](https://opentitan.org/book/hw/README.md)
+
+### Software
+* [OpenTitan Software](https://opentitan.org/book/sw/README.md)
+* [Writing and Building Software for OTBN](https://opentitan.org/book/doc/contributing/sw/otbn_sw.md)
+* [Rust for Embedded C Programmers](https://opentitan.org/book/doc/rust_for_c_devs.md)
diff --git a/doc/guides/getting_started/src/SUMMARY.md b/doc/guides/getting_started/src/SUMMARY.md
new file mode 100644
index 0000000..3210cd1
--- /dev/null
+++ b/doc/guides/getting_started/src/SUMMARY.md
@@ -0,0 +1,17 @@
+# Summary
+
+[Getting Started](README.md)
+
+# Workflows
+
+- [Design Verification](setup_dv.md)
+- [Formal Verification](setup_formal.md)
+- [Building (and Testing) Software](build_sw.md)
+- [Building Documentation](build_docs.md)
+
+# Tools Setup
+
+- [FPGA Setup](setup_fpga.md)
+- [Verilator Setup](setup_verilator.md)
+- [Installing OpenOCD](install_openocd.md)
+- [Installing Vivado](install_vivado/README.md)
diff --git a/doc/guides/getting_started/src/build_docs.md b/doc/guides/getting_started/src/build_docs.md
new file mode 100644
index 0000000..b26613b
--- /dev/null
+++ b/doc/guides/getting_started/src/build_docs.md
@@ -0,0 +1,23 @@
+# Building documentation
+
+The documentation for OpenTitan is available [online](https://opentitan.org).
+The creation of documentation is mainly based around the conversion from Markdown to HTML files with [mdbook](https://rust-lang.github.io/mdBook/).
+Rules for how to write correct Markdown files can be found in the [reference manual](https://opentitan.org/book/doc/contributing/style_guides/markdown_usage_style.md).
+
+## Building locally
+
+There are a few project specific [preprocessors](https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html).
+These preprocessors require the installation of the dependencies outlined in the [previous section](README.md#step-2-install-package-manager-dependencies).
+`util/site/build-docs.sh` handles building all books in the repository and other auto-generated content, such as the API documentation generated by [Doxygen](https://www.doxygen.nl/).
+
+### Running the server
+
+In order to run a local instance of the documentation server run the following command from the root of the project repository.
+
+```sh
+./util/site/build-docs.sh serve
+```
+
+This will execute the preprocessing, build the documentation and finally start a local server.
+The output will indicate at which address the local instance can be accessed.
+The default is [http://0.0.0.0:9000](http://0.0.0.0:9000).
diff --git a/doc/guides/getting_started/src/build_sw.md b/doc/guides/getting_started/src/build_sw.md
new file mode 100644
index 0000000..510b96a
--- /dev/null
+++ b/doc/guides/getting_started/src/build_sw.md
@@ -0,0 +1,330 @@
+# Building (and Testing) Software
+
+_Before following this guide, make sure you have read the_:
+* main [Getting Started](README.md) instructions,
+* install Verilator section of the [Verilator guide](./setup_verilator.md), and
+* [OpenTitan Software](https://opentitan.org/book/sw/README.md) documentation.
+
+All OpenTitan software is built with [Bazel](https://bazel.build/).
+Additionally, _most_ tests may be run with Bazel too.
+
+## TL;DR
+To install the correct version of bazel, build, and run a single test with Verilator, run:
+
+```console
+$REPO_TOP/bazelisk.sh test --test_output=streamed --disk_cache=~/bazel_cache //sw/device/tests:uart_smoketest_sim_verilator
+```
+
+This will take a while (an hour on a laptop is typical) if it's your first build or the first after a `git pull`, because Bazel has to build the chip simulation.
+Future builds will be much faster; go get a coffee and come back later.
+
+If the test worked, your OpenTitan setup is functional; you can build the software and run on-device tests using the Verilator simulation tool.
+See [Running Tests with Bazel](#running-tests with Bazel) for information on how to find and run other tests.
+
+If the test didn't work, read the full guide, especially the [Troubleshooting](#troubleshooting) section.
+
+## Installing Bazel
+
+There are two ways to install the correct version of Bazel:
+1. **automatically**, using the `bazelisk.sh` script provided in the repo, or
+1. **manually**.
+
+### Automatic Installation
+
+To simplify the installation of Bazel, and provide a means to seamlessly update the Bazel version we use in the future, we provide a shell script that acts as a wrapper for invocations of "`bazel ...`".
+To use it, you have two options:
+1. use "`./bazelisk.sh ...`" instead of "`bazel ...`" to invoke of Bazel subcommands, or
+1. set the following alias (e.g., in your `.bashrc` file) to accomplish the same:
+```console
+alias bazel="$REPO_TOP/bazelisk.sh"
+```
+
+### Manual Installation
+
+This section is optional and can be skipped if you completed the instructions above in _Automatic Installation_.
+
+While the automatic installation is convenient, by installing Bazel directly, you can get some quality of life features like tab completion.
+If you haven't yet installed Bazel, and would like to, you can add it to apt and install it on Ubuntu systems with the following commands [as described in the Bazel documentation](https://bazel.build/install/ubuntu):
+
+```console
+sudo apt install apt-transport-https curl gnupg
+curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
+sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
+echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" |
+sudo tee /etc/apt/sources.list.d/bazel.list
+sudo apt update && sudo apt install bazel-5.1.1
+sudo ln -s /usr/bin/bazel-5.1.1 /usr/bin/bazel
+```
+
+or by following [instructions for your system](https://bazel.build/install).
+
+## Building Software with Bazel
+
+Running
+```console
+bazel build //sw/...
+```
+will build all software in our repository.
+If you do not have Verilator installed yet, you can use the `--define DISABLE_VERILATOR_BUILD=true` flag to skip the jobs that depend on that.
+
+In general, you can build any software target (and all of it's dependencies) using the following syntax:
+```console
+bazel build @<repository>//<package>:<target>
+```
+Since most targets are within the main Bazel repository (`lowrisc_opentitan`), you can often drop the "`@<repository>`" prefix.
+For example, to build the boot ROM we use for testing (also referred to as the _test ROM_), you can use
+```console
+bazel build //sw/device/lib/testing/test_rom:test_rom
+```
+Additionally, some Bazel syntactic sugar enables dropping the target name when the target name matches the last subcomponent of the package name.
+For example, the following is equivalent to the above
+```console
+bazel build //sw/device/lib/testing/test_rom
+```
+
+For more information on Bazel repositories, packages, and targets, please refer to the Bazel [documentation](https://bazel.build/concepts/build-ref).
+
+## Running Tests with Bazel
+
+In addition to building software, Bazel is also used to build and run tests.
+There are two categories of OpenTitan tests Bazel can build and run:
+1. on-host, and
+1. on-device.
+
+On-host tests are compiled and run on the host machine, while on-device tests are compiled and run on (simulated/emulated) OpenTitan hardware.
+
+Examples of on-host tests are:
+* unit tests for device software, such as [DIF](https://opentitan.org/book/sw/device/lib/dif/README.md) and [ROM](https://opentitan.org/book/sw/device/silicon_creator/rom/README.md) unit tests.
+* any test for host software, such as `opentitan{lib,tool}`.
+
+Examples of on-device tests are:
+* [chip-level tests](https://opentitan.org/book/sw/device/tests/README.md).
+* [ROM functional tests](https://opentitan.org/book/sw/device/silicon_creator/rom/README.md)
+
+Test target names normally match file names (for instance, `//sw/device/tests:uart_smoketest` corresponds to `sw/device/test/uart_smoketest.c`).
+You can see all tests available under a given directory using `bazel query`, e.g.:
+
+```console
+bazel query 'tests(//sw/device/tests/...)'
+```
+### Tags and wildcards
+
+TLDR: `bazelisk.sh test --test_tag_filters=-cw310,-verilator,-vivado,-jtag,-eternal,-broken --build_tag_filters=-vivado,-verilator //...`
+*Should* be able to run all the tests and build steps in OpenTitan that don't require optional setup steps, and
+
+You may find it useful to use wildcards to build/test all targets in the OpenTitan repository instead of individual targets.
+`//sw/...` is shorthand for all targets in `sw` that isn't tagged with manual.
+If a a target (a test or build artifact) relies on optional parts of the "Getting Started" guide they should be tagged so they can be filtered out and users can `bazelisk.sh test //...` once they filter out the appropriate tags.
+We maintain or use the following tags to support this:
+* `broken` is used to tag tests that are committed but should not be expected by CI or others to pass.
+* `cw310`, `cw310_test_rom`, and `cw310_rom` are used to tag tests that depend on a correctly setup cw310 "Bergen Board" to emulate OpenTitan.
+  The `cw310` tag may be used in `--test_tag_filters` to enable concise filtering to select tests that run on this board and include or exclude them.
+  Loading the bitstream is the slowest part of the test, so these tags can group tests with common bitstreams to accelerate the tests tagged `cw310_test_rom`.
+* `verilator` is used to tag tests that depend on a verilated model of OpenTitan that can take a significant time to build.
+  Verilated tests can still be built with `--define DISABLE_VERILATOR_BUILD`, but they will skip the invocation of Verilator and cannot be run.
+* `vivado` is used to tag tests that critically depend on Vivado.
+* `jtag` is used to tag tests that rely on a USB JTAG adapter connected like we have in CI.
+* `manual` is a Bazel builtin that prevents targets from matching wildcards.
+  Test suites are typically tagged manual so their contents match, but test suites don't get built or run unless they're intentionally invoked.
+  Intermediate build artifacts may also be tagged with manual to prevent them from being unintentionally built if they cause other problems.
+* `skip_in_ci` is used to tag ROM end-to-end tests that we currently skip in CI.
+  ROM end-to-end tests are typically written for five life cycle states: TEST\_UNLOCKED0, DEV, PROD, PROD\_END, and RMA.
+  This tag allows us to limit the tests run in CI to a single life cycle state, allowing CW310 tests to finish in a reasonable timeframe.
+  We run tests for the remaining lifecycle states outside the CI in a less frequent manner.
+
+`ci/scripts/check-bazel-tags.sh` performs some useful queries to ensure these tags are applied.
+These tags can then be used to filter tests using `--build_tests_only --test_tag_filters=-cw310,-verilator,-vivado`.
+These tags can also be used to filter builds using `--build_tag_filters=-cw310,-verilator,-vivado`.
+
+`--build_tests_only` is important when matching wildcards if you aren't using
+`--build_tag_filters` to prevent `bazelisk.sh test //...` from building targets that are filtered out by `--test_tag_filters`.
+
+There is no way to filter out dependencies of a test\_suite such as `//sw/device/tests:uart_smoketest` (Which is a suite that's assembled by the `opentitan_functest` rule) from a build.
+
+### Running on-device Tests
+
+On-device tests such as `//sw/device/tests:uart_smoketest` include multiple targets for different device simulation/emulation tools.
+Typically, you will only want to run one of these test targets at a time (for instance, only Verilator or only FPGA).
+Add `_sim_verilator` to the test name to run the test on Verilator only, and `_fpga_cw310_rom` or `_fpga_cw310_test_rom` to run the test on FPGA only.
+
+You can check which Verilator tests are available under a given directory using:
+```console
+bazel query 'attr(tags, verilator, tests(//sw/device/tests/...))'
+```
+
+For FPGA tests, just change the tag:
+```console
+bazel query 'attr(tags, cw310, tests(//sw/device/tests/...))'
+```
+
+For more information, please refer to the [Verilator](./setup_verilator.md) and/or [FPGA](./setup_fpga.md) setup instructions.
+
+### Running on-host DIF Tests
+
+The Device Interface Function or [DIF](https://opentitan.org/book/sw/device/lib/dif/README.md) libraries contain unit tests that run on the host and are built and run with Bazel.
+As shown below, you may use Bazel to query which tests are available, build and run all tests, or build and run only one test.
+
+#### Querying which tests are available
+```console
+bazel query 'tests(//sw/device/lib/dif:all)'
+```
+
+#### Building and running **all** tests
+```console
+bazel test //sw/device/lib/dif:all
+```
+
+#### Building and running a **single** test
+For example, building and testing the UART DIF library's unit tests:
+```console
+bazel test //sw/device/lib/dif:uart_unittest
+```
+
+### Running on-host ROM Tests
+
+Similar to the DIF libraries, you can query, build, and run all the [ROM](https://opentitan.org/book/sw/device/silicon_creator/rom/README.md) unit tests (which also run on the host) with Bazel.
+
+#### Querying which (on-host) tests are available
+Note, the ROM has both on-host and on-device tests.
+This query filters tests by their kind, i.e., only on-host tests.
+```console
+bazel query 'kind(cc_.*, tests(//sw/device/silicon_creator/lib/...))'
+```
+
+#### Building and running **all** (on-host) tests
+```console
+bazel test --test_tag_filters=-cw310,-dv,-verilator //sw/device/silicon_creator/lib/...
+```
+
+#### Building and running a **single** (on-host) test
+For example, building and testing the ROM UART driver unit tests:
+```console
+bazel test //sw/device/silicon_creator/lib/drivers:uart_unittest
+```
+
+## Miscellaneous
+
+### Bazel-built Software Artifacts
+
+As described in the [OpenTitan Software](https://opentitan.org/book/sw/README.md) documentation, there are three categories of OpenTitan software, all of which are built with Bazel. These include:
+1. _device_ software,
+1. _OTBN_ software,
+1. _host_ software,
+
+Bazel produces various artifacts depending on the category of software that is built.
+
+#### Device Artifacts
+
+Device software is built and run on OpenTitan hardware.
+There are three OpenTitan "devices" for simulating/emulating OpenTitan hardware:
+1. "sim\_dv": DV simulation (i.e., RTL simulation with commercial simulators),
+1. "sim\_verilator": Verilator simulation (i.e., RTL simulation with the open source Verilator simulator),
+1. "fpga\_cw310": FPGA.
+
+Additionally, for each device, there are two types of software images that can be built, depending on the memory type the software is destined for, i.e.:
+1. ROM,
+1. flash,
+
+To facilitate instantiating all build rules required to build the same artifacts across several devices and memories, we implement two OpenTitan-specific Bazel macros.
+These macros include:
+* `opentitan_rom_binary`
+* `opentitan_flash_binary`
+
+Both macros instantiate build rules to produce software artifacts for each OpenTitan device above.
+Specifically, building either an `opentitan_rom_binary` or `opentitan_flash_binary` named `<target>`, destined to run on the OpenTitan device `<device>`, will output the following files under `bazel-out/`:
+* `<target>_<device>.elf`: the linked program, in ELF format.
+* `<target>_<device>.bin`: the linked program, as a plain binary with ELF debug information removed.
+* `<target>_<device>.dis`: the disassembled program with inline source code.
+* `<target>_<device>.logs.txt`: a textual database of addresses where `LOG_*` macros are invoked (for DV backdoor logging interface).
+* `<target>_<device>.rodata.txt`: same as above, but contains the strings that are logged.
+* `<target>_<device>.*.vmem`: a Verilog memory file which can be read by `$readmemh()` in Verilog code.
+Note, `<device>` will be in {`sim_dv`, `sim_verilator`, `fpga_cw310`}.
+
+Additionally, if the `opentitan_flash_binary` is signed, then these files will also be under `bazel-out/`:
+* `<target>_<device>.<signing key name>.signed.bin`: the same `.bin` file above, but with a valid signature field in the manifest.
+* `<target>_<device>.<signing key name>.signed.*.vmem`: the same `*.vmem` file above, but with a valid signature field in the manifest.
+
+#### OTBN Artifacts
+
+OTBN programs use a specialized build flow (defined in `rules/otbn.bzl`).
+OTBN programs produce the following artifacts:
+* `<target>.o`: unlinked object file usually representing a single assembly file
+* `<target>.elf`: standalone executable binary representing one or more assembly/object files
+* `<target>.rv32embed.{a,o}`: artifacts representing an OTBN binary, set up to be linked into a RISC-V program
+
+In terms of Bazel rules:
+* the `otbn_library` rule runs the assembler to create `<target>.o` artifacts, and
+* the `otbn_binary` and `otbn_sim_test` rules run the linker on one or more `.o` files to create the `.elf` and `.rv32embed.{a,o}` artifacts.
+
+Since OTBN has limited instruction memory, the best practice is to list each file individually as an `otbn_library`.
+This way, binary targets can easily include only the files they need.
+
+OTBN programs run on the OTBN coprocessor, unlike standard "on-device" programs that run on the main processor (Ibex).
+There are two ways to run an OTBN program:
+1. Run a standalone binary (`.elf`) on the specialized OTBN simulator.
+1. Include a `.rv32embed` artifact in a C program that runs on Ibex, and create an on-device target as described in the previous section.
+
+You can run `.elf` artifacts directly using the simulator as described in [the OTBN README](https://github.com/lowRISC/opentitan/blob/master/hw/ip/otbn/README.md#run-the-python-simulator).
+The `otbn_sim_test` rule is a thin wrapper around `otbn_binary`.
+If you use it, `bazel test` will run the OTBN simulator for you and check the test result.
+
+To include an OTBN program in a C program, you need to add the desired OTBN `otbn_binary` Bazel target to the `deps` list of the C program's Bazel target.
+No `#include` is necessary, but you will likely need to initialize the symbols from the OTBN program as required by the OTBN driver you are using.
+
+#### Host Artifacts
+
+Host software is built and run on the host hardware (e.g., an x64 Linux machine).
+A final linked program in the ELF format is all that is produced for host software builds.
+Note, the file will **not** have an extension.
+
+### Disassembling Device Code
+
+A disassembly of all executable sections is produced by the build system by default.
+It can be found by looking for files with the `.dis` extension next to the corresponding ELF file.
+
+```console
+./bazelisk.sh build //sw/device/tests:uart_smoketest_prog_sim_verilator_dis
+
+less "$(./bazelisk.sh outquery //sw/device/tests:uart_smoketest_prog_sim_verilator_dis)"
+```
+
+To get a different type of disassembly, e.g. one which includes data sections in addition to executable sections, objdump can be called manually.
+For example the following command shows how to disassemble all sections of the UART DIF smoke test interleaved with the actual source code:
+
+```console
+./bazelisk.sh build --config=riscv32 //sw/device/tests:uart_smoketest_prog_sim_verilator.elf
+
+riscv32-unknown-elf-objdump --disassemble-all --headers --line-numbers --source \
+  "$(./bazelisk.sh outquery --config=riscv32 //sw/device/tests:uart_smoketest_prog_sim_verilator.elf)"
+```
+
+Refer to the output of `riscv32-unknown-elf-objdump --help` for a full list of options.
+
+## Troubleshooting {#troubleshooting}
+
+### Check CI {#troubleshooting-check-ci}
+
+First, [check the GitHub repository](https://github.com/lowRISC/opentitan/commits/master) to make sure the CI check is succeeding for the commit you cloned.
+If there's an issue with that commit (it would have a red "X" next to it), check out the most recent commit that passed CI (indicated by a green check mark).
+We try to always keep the main branch healthy, but the project is in active development and we're not immune to temporary breaks.
+
+### Debugging a failed verilator test {#troubleshooting-verilator-test-failure}
+
+If your `bazelisk.sh` build failed trying to run a test on Verilator, the first step is to see if you can build the chip simulation on its own:
+
+```console
+./bazelisk.sh build //hw:verilator
+```
+This build can take a long time; it's creating a simulation for the entire OpenTitan SoC.
+Expect up to an hour for a successful build, depending on your machine.
+
+If the `//hw:verilator` build above ran for a while and then failed with a bunch of warnings about various `.sv` files, it may have run out of RAM.
+At the time of writing, our CI has 7GB of RAM, so that should be sufficient.
+If your system is close to that limit, you may want to exit web browsers or other RAM-intensive applications while the Verilator build runs.
+
+If the `//hw:verilator` build failed pretty much immediately, try running `util/check_tool_requirements.py` to make sure you meet the tool requirements.
+
+If the `//hw:verilator` build succeeeded, but running a particular test fails, try running a different test (you can find many options under `sw/device/tests/`).
+If that works, then it may be a problem with the specific test you're running.
+See if you can build, but not run, the test with `./bazelisk.sh build` instead of `./bazelisk.sh test`.
+If the test fails to build, that indicates some issue with the source code or possibly the RISC-V toolchain installation.
diff --git a/doc/getting_started/getting_started_workflow.svg b/doc/guides/getting_started/src/getting_started_workflow.svg
similarity index 100%
rename from doc/getting_started/getting_started_workflow.svg
rename to doc/guides/getting_started/src/getting_started_workflow.svg
diff --git a/doc/guides/getting_started/src/install_openocd.md b/doc/guides/getting_started/src/install_openocd.md
new file mode 100644
index 0000000..0a2e2e1
--- /dev/null
+++ b/doc/guides/getting_started/src/install_openocd.md
@@ -0,0 +1,22 @@
+# Install OpenOCD
+
+OpenOCD is a tool to connect with the target chip over JTAG and similar transports.
+It also provides a GDB server which is an "intermediate" when debugging software on the chip with GDB.
+
+It is recommended to use the regular upstream version of OpenOCD instead of the [RISC-V downstream fork](https://github.com/riscv/riscv-openocd).
+
+It is trivial to install OpenOCD because we manage the dependency with Bazel.
+The Bazel-built OpenOCD binary lives at `//third_party/openocd:openocd_bin`.
+It's not runnable, but we also provide a runnable wrapper: `//third_party/openocd`.
+
+OpenOCD also ships with a library of config files.
+Instead of using use whichever config files happen to be installed on the system, prefer the Bazel-exposed config files that match OpenOCD source.
+Currently, we only expose OpenTitan's default JTAG adapter config as `//third_party/openocd:jtag_adapter_cfg`.
+
+```console
+# Manually run OpenOCD:
+./bazelisk.sh run //third_party/openocd -- arg1 arg2
+
+# Get the path of the OpenOCD binary:
+./bazelisk.sh outquery //third_party/openocd:openocd_bin
+```
diff --git a/doc/guides/getting_started/src/install_vivado/README.md b/doc/guides/getting_started/src/install_vivado/README.md
new file mode 100644
index 0000000..7533c6d
--- /dev/null
+++ b/doc/guides/getting_started/src/install_vivado/README.md
@@ -0,0 +1,62 @@
+# Install Vivado
+
+Generating a bitstream for Xilinx devices requires a
+[Vivado](https://www.xilinx.com/products/design-tools/vivado.html) installation.
+Please note that the "WebPACK" edition __does not__ support the Xilinx Kintex 7
+XC7K410T used on the CW310 board.
+
+For software development, Vivado is still necessary for most workflows.
+However, the (free) Lab Edition is sufficient, and it has a significantly smaller installation footprint.
+For example, Vivado's `updatemem` tool is used to splice ROM images into the bitstream, and this is included in the Lab Edition.
+
+## Install Xilinx Vivado
+
+_**Vivado Version:** The recommendation is to use Vivado {{#tool-version vivado }}._
+
+Following the arrival of Vivado ML Edition, you will need to follow the links for that, eg. Products -> Hardware Development -> Vivado ML.
+Then click on 'Vivado Archive' in the Version list and locate version {{#tool-version vivado }} of Vivado Design Suite.
+
+See [Download and
+Installation](https://docs.xilinx.com/r/{{#tool-version vivado }}-English/ug973-vivado-release-notes-install-license/Download-and-Installation)
+for installation instructions.
+
+When asked what edition to install, choose "Vivado HL Design Edition".
+_Note: If you are only developing software, you may select the "Lab Edition" instead._
+On the feature selection screen, select at least the following features:
+
+![Vivado features selection screen](features.png)
+
+After installing Vivado, you will need to add Vivado's paths to your shell
+environment. See [Launching the Vivado IDE from the Command Line on Windows or
+Linux](https://docs.xilinx.com/r/{{#tool-version vivado }}-English/ug892-vivado-design-flows-overview/Launching-the-Vivado-IDE-from-the-Command-Line-on-Windows-or-Linux)
+for instructions.
+
+## Device permissions: udev rules
+
+To program any FPGAs the user using Vivado typically needs to have permissions to access USB devices connected to the PC.
+Depending on your security policy you can take different steps to enable this access.
+One way of doing so is given in the udev rule outlined below.
+
+To do so, create a file named `/etc/udev/rules.d/90-lowrisc.rules` and add the following content to it:
+
+```
+# Grant access to board peripherals connected over USB:
+# - The USB devices itself (used e.g. by Vivado to program the FPGA)
+# - Virtual UART at /dev/tty/XXX
+
+# NewAE Technology Inc. ChipWhisperer boards e.g. CW310, CW305, CW-Lite, CW-Husky
+ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="2b3e", ATTRS{idProduct}=="ace[0-9]|c[3-6][0-9][0-9]", MODE="0666"
+
+# Future Technology Devices International, Ltd FT2232C/D/H Dual UART/FIFO IC
+# used on Digilent boards
+ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{manufacturer}=="Digilent", MODE="0666"
+
+# Future Technology Devices International, Ltd FT232 Serial (UART) IC
+ACTION=="add|change", SUBSYSTEM=="usb|tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666"
+```
+
+You then need to reload the udev rules:
+
+```console
+sudo udevadm control --reload
+```
diff --git a/doc/guides/getting_started/src/install_vivado/features.png b/doc/guides/getting_started/src/install_vivado/features.png
new file mode 100644
index 0000000..08e00be
--- /dev/null
+++ b/doc/guides/getting_started/src/install_vivado/features.png
Binary files differ
diff --git a/doc/guides/getting_started/src/setup_dv.md b/doc/guides/getting_started/src/setup_dv.md
new file mode 100644
index 0000000..2bc0314
--- /dev/null
+++ b/doc/guides/getting_started/src/setup_dv.md
@@ -0,0 +1,123 @@
+# Design Verification Setup
+
+_Before following this guide, make sure you've followed the [dependency installation and software build instructions](https://opentitan.org/book/doc/guides/getting_started/)._
+
+This document aims to enable a contributor to get started with a design verification (DV) effort within the OpenTitan project.
+While most of the focus is on development of a testbench from scratch, it should also be useful to understand how to contribute to an existing effort.
+Please refer to the [DV methodology](https://opentitan.org/book/doc/contributing/dv/methodology/README.md) document for information on how design verification is done in OpenTitan.
+
+## Stages of DV
+
+The life stages of a design / DV effort within the OpenTitan are described in the [Hardware Development Stages](https://opentitan.org/book/doc/project_governance/development_stages.md) document.
+It separates the life of DV into three broad stages: Initial Work, Under Test and Testing Complete.
+This document attempts to give guidance on how to get going with the first stage and have a smooth transition into the Under Test stage.
+They are not hard and fast rules but methods we have seen work well in the project.
+DV indeed cannot begin until the design has transitioned from Specification to the Development stage first.
+The design specification, once available, is used as a starting point.
+
+## Getting Started
+
+The very first thing to do in any DV effort is to [document the plan](https://opentitan.org/book/doc/contributing/dv/methodology/README.md#documentation) detailing the overall effort.
+This is done in conjunction with developing the initial testbench.
+It is recommended to use the [uvmdvgen](https://opentitan.org/book/util/uvmdvgen/README.md) tool, which serves both needs.
+
+The `uvmdvgen` tool provides the ability to generate the outputs in a specific directory.
+This should be set to the root of the DUT directory where the `rtl` directory exists.
+When the tool is run, it creates a `dv` directory, along with `data` and `doc` directories.
+The `dv` directory is where the complete testbench along with the collaterals to build and run tests can be found.
+It puts the documentation sources in `doc` and `data` directories respectively (which also exist alongside the `rtl` directory).
+It is recommended to grep for 'TODO' at this stage in all of these generated files to make some of the required fixes right way.
+One of these for example, is to create appropriate interfaces for the DUT-specific IOs and have them connected in the testbench (`dv/tb/tb.sv`).
+
+## Documentation and Initial Review
+
+The skeleton [DV document](https://opentitan.org/book/doc/contributing/dv/methodology/README.md#dv-document) and the [Hjson testplan](https://opentitan.org/book/doc/contributing/dv/methodology/README.md#testplan) should be addressed first.
+The DV documentation is not expected to be completed in full detail at this point.
+However, it is expected to list all the verification components needed and depict the planned testbench as a block diagram.
+Under the 'design verification' directory in the OpenTitan team drive, some sample testbench block diagrams are available in the `.svg` format, which can be used as a template.
+The Hjson testplan, on the other hand, is required to be completed.
+Please refer to the [testplanner tool](https://opentitan.org/book/util/dvsim/README.md) documentation for additional details on how to write the Hjson testplan.
+Once done, these documents are to be reviewed with the designer(s) and other project members for completeness and clarity.
+
+## UVM RAL Model
+
+Before running any test, the [UVM RAL model](https://opentitan.org/book/doc/contributing/dv/methodology/README.md#uvm-register-abstraction-layer-ral-model) needs to exist (if the design contains CSRs).
+The [DV simulation flow](https://opentitan.org/book/util/dvsim/README.md) has been updated to generate the RAL model automatically at the start of the simulation.
+As such, nothing extra needs to be done.
+It can be created manually by invoking [`regtool`](https://opentitan.org/book/util/reggen/doc/setup_and_use.md):
+```console
+$ util/regtool.py -s -t /path-to-dv /path-to-module/data/<dut>.hjson
+```
+
+The generated file is placed in the simulation build scratch area instead of being checked in.
+
+## Supported Simulators
+
+The use of advanced verification constructs such as SystemVerilog classes (on which UVM is based on) requires commercial simulators.
+The [DV simulation flow](https://opentitan.org/book/util/dvsim/README.md) fully supports Synopsys VCS.
+There is support for Cadence Xcelium as well, which is being slowly ramped up.
+
+## Building and Running Tests
+
+The `uvmdvgen` tool provides an empty shell sequence at `dv/env/seq_lib/<dut>_sanity_vseq.sv` for developing the sanity test.
+The sanity test can be run as-is by invoking `dvsim.py`, as a "hello world" step to bring the DUT out of reset.
+```console
+$ util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson -i <dut>_sanity [--waves <format>] [--tool xcelium]
+```
+
+The generated initial testbench is not expected to compile and elaborate successfully right away.
+There may be additional fixes required, which can hopefully be identified easily.
+Once the testbench compiles and elaborates without any errors or warnings, the sanity sequence can be developed further to access a major datapath and test the basic functionality of the DUT.
+
+VCS is used as the default simulator. It can be switched to Xcelium by setting `--tool xcelium` on the command line.
+
+To dump waves from the simulation, pass the `--waves <format>` argument to `dvsim.py`.
+If you are using Verdi for waveform viewing, then '--waves fsdb' is probably the best option. For use with other viewers, '--waves shm' is probably the best choice for Xcelium, and '--waves vpd' with vcs.
+
+Please refer to the [DV simulation flow](https://opentitan.org/book/util/dvsim/README.md) for additional details.
+
+The `uvmdvgen` script also enables the user to run the full suite of CSR tests, if the DUT does have CSRs in it.
+The most basic CSR power-on-reset check test can be run by invoking:
+```console
+$ util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson -i <dut>_csr_hw_reset [--waves <format>] [--tool xcelium]
+```
+Please refer to [CSR utilities](https://opentitan.org/book/hw/dv/sv/csr_utils/README.md) for more information on how to add exclusions for the CSR tests.
+
+## Full DV
+
+Running the sanity and CSR suite of tests while making progress toward reaching the [V1 stage](https://opentitan.org/book/doc/project_governance/development_stages.md#hardware-verification-stages) should provide a good reference in terms of how to develop tests as outlined in the testplan and running and debugging them.
+Please refer to the [checklist](https://opentitan.org/book/doc/project_governance/checklist/README.md) to understand the key requirements for progressing through the subsequent verification stages and final signoff.
+
+The [UART DV](https://github.com/lowRISC/opentitan/tree/master/hw/ip/uart/dv) area can be used as a canonical example for making progress.
+If it is not clear on how to proceed, feel free to file an issue requesting assistance.
+
+## Reproduce a DV failure that CI reported
+
+Follow these steps to reproduce the failure
+
+1. Make sure the version of VCS is the same as the [one](https://github.com/lowRISC/opentitan-private-ci/blob/master/jobs.yml#L5) running in CI.
+
+2. CI runs against an auto-generated merge commit, which effectively is generated by merging the pull request (PR) into the master branch.
+This "merge" branch is updated automatically by GitHub whenever the PR branch is pushed, or when the PR is closed and re-open.
+Retrieve this exact branch by running the following (assuming "upstream" is the name of your lowRISC/opentitan repository).
+```console
+$ git fetch upstream pull/<PR_number>/merge
+$ git checkout -b <temp_branch> FETCH_HEAD
+```
+
+3. This is the command that CI runs for the smoke regression.
+```console
+$ util/dvsim/dvsim.py hw/top_earlgrey/dv/top_earlgrey_sim_cfgs.hjson -i smoke --fixed-seed=1
+```
+Since the CI runs tests with pseudo-random behaviour driven from 'seed' numbers, to be confident of reproducing the failure we must supply the exact seed that CI used.
+
+Assume there is a failure in the `uart_smoke` test. To reproduce this with the DV simulation environment we use the following command, remembering to replace '<seed>' with the seed number, and to choose an appropriate waveform '<format>':
+
+
+```console
+$ util/dvsim/dvsim.py hw/ip/uart/dv/uart_sim_cfg.hjson -i uart_smoke --fixed-seed=<seed> [--waves <format>]
+```
+
+It is recommended to use '--waves fsdb' if you are using Verdi, or '--waves vpd' if you are using vcs but a different waveform viewer. With Xcelium ('--tool xcelium') but not Verdi, then '--waves shm' is the preferred format.
+
+For maximal portability, the standard 'vcd' format is supported by all simulators and waveform viewers, but please be forewarned that vcd files can become extremely large and are generally best avoided.
diff --git a/doc/guides/getting_started/src/setup_formal.md b/doc/guides/getting_started/src/setup_formal.md
new file mode 100644
index 0000000..d9b7c57
--- /dev/null
+++ b/doc/guides/getting_started/src/setup_formal.md
@@ -0,0 +1,35 @@
+# Formal Verification Setup
+
+_Before following this guide, make sure you've followed the [dependency installation and software build instructions](README.md)._
+
+This document aims to enable a contributor to get started with a formal verification effort within the OpenTitan project.
+While most of the focus is on development of a testbench from scratch, it should also be useful to understand how to contribute to an existing effort.
+
+Please refer to the [OpenTitan Assertions](https://opentitan.org/book/hw/formal/README.md) for information on how formal verification is done in OpenTitan.
+
+## Formal property verification (FPV)
+
+The formal property verification is used to prove assertions in the target.
+There are three sets of FPV jobs in OpenTitan. They are all under the directory `hw/top_earlgrey/formal`.
+* `top_earlgrey_fpv_ip_cfgs.hjson`: List of IP targets.
+* `top_earlgrey_fpv_prim_cfgs.hjson`: List of prim targets (such as counters, fifos, etc) that are usually imported by an IP.
+* `top_earlgrey_fpv_sec_cm_cfgs.hjson`: List of IPs that contains standard security countermeasure assertions. This FPV environment only proves these security countermeasure assertions. Detailed description of this FPV use case is documented in [Running FPV on security blocks for common countermeasure primitives](https://opentitan.org/book/hw/formal/README.md#running-fpv-on-security-blocks-for-common-countermeasure-primitives).
+
+To automatically create a FPV testbench, it is recommended to use the [fpvgen](https://opentitan.org/book/util/fpvgen/README.md) tool to create a template.
+To run the FPV tests in `dvsim`, please add the target to the corresponding `top_earlgrey_fpv_{category}_cfgs.hjson` file , then run with command:
+```console
+util/dvsim/dvsim.py hw/top_earlgrey/formal/top_earlgrey_fpv_{category}_cfgs.hjson --select-cfgs {target_name}
+```
+
+It is recommended to add the FPV target to [lint](https://opentitan.org/book/hw/lint/README.md) script `hw/top_earlgrey/lint/top_earlgrey_fpv_lint_cfgs.hjson` to quickly find typos.
+
+## Formal connectivity verification
+
+The connectivity verification is mainly used for exhaustively verifying system-level connections.
+User can specify the connection ports via a CSV format file in `hw/top_earlgrey/formal/conn_csvs` folder.
+User can trigger top_earlgrey's connectivity test using `dvsim`:
+```
+util/dvsim/dvsim.py hw/top_earlgrey/formal/chip_conn_cfgs.hjson
+```
+
+The connectivity testplan is documented under `hw/top_earlgrey/data/chip_conn_testplan.hjson`.
diff --git a/doc/guides/getting_started/src/setup_fpga.md b/doc/guides/getting_started/src/setup_fpga.md
new file mode 100644
index 0000000..118ab84
--- /dev/null
+++ b/doc/guides/getting_started/src/setup_fpga.md
@@ -0,0 +1,496 @@
+# FPGA Setup
+
+_Before following this guide, make sure you've followed the [dependency installation and software build instructions](README.md)._
+
+Do you want to try out OpenTitan, but don't have a couple thousand or million dollars ready for an ASIC tapeout?
+Running OpenTitan on an FPGA board can be the answer!
+
+## Prerequisites
+
+To use the OpenTitan on an FPGA you need two things:
+
+* A supported FPGA board
+* A tool from the FPGA vendor
+
+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 on what exactly is needed.
+
+* [Obtain an FPGA board](https://opentitan.org/book/doc/contributing/fpga/get_a_board.md)
+
+## Obtain an FPGA bitstream
+
+To run OpenTitan on an FPGA, you will need an FPGA bitstream.
+You can either download the latest bitstream for the ChipWhisperer CW310 board or build it yourself.
+
+### Download a Pre-built Bitstream
+
+If you are using the ChipWhisperer CW310 board with the Xilinx Kintex 7 XC7K410T FPGA, you can download the latest passing [pre-built bitstream](https://storage.googleapis.com/opentitan-bitstreams/master/bitstream-latest.tar.gz).
+
+For example, to download and unpack the bitstream, run the following:
+
+```console
+mkdir -p /tmp/bitstream-latest
+cd /tmp/bitstream-latest
+curl https://storage.googleapis.com/opentitan-bitstreams/master/bitstream-latest.tar.gz -o bitstream-latest.tar.gz
+tar -xvf bitstream-latest.tar.gz
+```
+
+By default, the bitstream is built with a version of the boot ROM used for testing (called the _test ROM_; pulled from `sw/device/lib/testing/test_rom`).
+There is also a version of the boot ROM used in production (called the _ROM_; pulled from `sw/device/silicon_creator/rom`).
+When the bitstream cache is used in bazel flows, the ROMs from the cache are not used.
+Instead, the bazel-built ROMs are spliced into the image to create new bitstreams, using the mechanism described in the [FPGA Reference Manual](https://opentitan.org/book/doc/contributing/fpga/ref_manual_fpga.md#boot-rom-development).
+The metadata for the latest bitstream (the approximate creation time and the associated commit hash) is also available as a text file and can be [downloaded separately](https://storage.googleapis.com/opentitan-bitstreams/master/latest.txt).
+
+### Using the `@bitstreams` repository
+
+OpenTitan's build system automatically fetches pre-built bitstreams via the `@bitstreams` repository.
+
+To keep the `@bitstreams` repository in sync with the current Git revision, install the Git post-checkout hook:
+
+```sh
+cp util/git/hooks/post-checkout .git/hooks/
+```
+
+### Build an FPGA bitstream
+
+Synthesizing a design for an FPGA board is simple with Bazel.
+While Bazel is the entry point for kicking off the FPGA synthesis, under the hood, it invokes FuseSoC, the hardware package manager / build system supported by OpenTitan.
+During the build process, the boot ROM is baked into the bitstream.
+As mentioned above, we maintain two boot ROM programs, one for testing (_test ROM_), and one for production (_ROM_).
+
+To build an FPGA bitstream with the _test ROM_, use:
+```console
+cd $REPO_TOP
+bazel build //hw/bitstream/vivado:fpga_cw310_test_rom
+```
+
+To build an FPGA bitstream with the _ROM_, use:
+```console
+cd $REPO_TOP
+bazel build //hw/bitstream/vivado:fpga_cw310_rom
+```
+
+Note, building these bitstreams will require Vivado be installed on your system, with access to the proper licenses, described [here](./install_vivado/README.md).
+For general software development on the FPGA, Vivado must still be installed, but the Lab Edition is sufficient.
+
+#### Dealing with FPGA Congestion Issues
+
+The default Vivado tool placement may sometimes result in congested FPGA floorplans.
+When this happens, the implementation time and results become unpredictable.
+It may become necessary for the user to manually adjust certain placement.
+See [this comment](https://github.com/lowRISC/opentitan/pull/8138#issuecomment-916696830) for a thorough analysis of one such situation and what changes were made to improve congestion.
+
+#### Opening an existing project with the Vivado GUI
+
+Sometimes, it may be desirable to open the generated project in the Vivado GUI for inspection.
+To this end, run:
+
+```console
+. /tools/Xilinx/Vivado/{{#tool-version vivado }}/settings64.sh
+cd $REPO_TOP
+make -C $(dirname $(find bazel-out/* -wholename '*synth-vivado/Makefile')) build-gui
+```
+
+Now the Vivado GUI opens and loads the project.
+
+#### Develop with the Vivado GUI
+
+TODO(lowRISC/opentitan[#13213](https://github.com/lowRISC/opentitan/issues/13213)): the below does not work with the Bazel FPGA bitstream build flow.
+
+Sometimes it is helpful to use the Vivado GUI to debug a design.
+FuseSoC (the tool Bazel invokes) makes that easy, with one small caveat: by default FuseSoC copies all source files into a staging directory before the synthesis process starts.
+This behavior is helpful to create reproducible builds and avoids Vivado modifying checked-in source files.
+But during debugging this behavior is not helpful.
+The `--no-export` option of FuseSoC disables copying the source files into the staging area, and `--setup` instructs fusesoc to not start the synthesis process.
+
+```console
+# Only create Vivado project directory by using FuseSoC directly (skipping Bazel invocation).
+cd $REPO_TOP
+fusesoc --cores-root . run --flag=fileset_top --target=synth --no-export --setup lowrisc:systems:chip_earlgrey_cw310
+```
+
+You can then navigate to the created project directory, and open Vivado
+```console
+. /tools/Xilinx/Vivado/{{#tool-version vivado }}/settings64.sh
+cd $REPO_TOP/build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/
+vivado
+```
+
+Finally, using the Tcl console, you can kick off the project setup with
+```console
+source lowrisc_systems_chip_earlgrey_cw310_0.1.tcl
+```
+
+## Connecting the ChipWhisperer CW310 board
+
+The ChipWhisperer CW310 board supports different power options.
+It is recommended to power the board via the included DC power adapter.
+To this end:
+1. Set the *SW2* switch (to the right of the barrel connector) up to the *5V Regulator* option.
+1. Set the switch below the barrel connector to the right towards the *Barrel* option.
+1. Set the *Control Power* switch (bottom left corner, *SW7*) to the right.
+1. Ensure the *Tgt Power* switch (above the fan, *S1*) is set to the right towards the *Auto* option.
+1. Plug the DC power adapter into the barrel jack (*J11*) in the top left corner of the board.
+1. Use a USB-C cable to connect your PC with the *USB-C Data* connector (*J8*) in the lower left corner on the board.
+
+You can now use the following to monitor output from dmesg:
+```console
+sudo dmesg -Hw
+```
+This should show which serial ports have been assigned, or if the board is having trouble connecting to USB.
+If `dmesg` reports a problem you can trigger a *USB_RST* with *SW5*.
+When properly connected, `dmesg` should identify the board, not show any errors, and the status light should flash.
+They should be named `'/dev/ttyACM*'`, e.g. `/dev/ttyACM1`.
+To ensure that you have sufficient access permissions, set up the udev rules as explained in the [Vivado installation instructions](./install_vivado/README.md).
+
+You will then need to run this command to configure the board. You only need to run it once.
+```console
+bazel run //sw/host/opentitantool -- --interface=cw310 fpga set-pll
+```
+
+Check that it's working by [running the demo](#hello-world-demo) or a test, such as the `uart_smoketest` below.
+```console
+cd $REPO_TOP
+bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310_test_rom
+```
+
+### Troubleshooting
+
+If the tests/demo aren't working on the FPGA (especially if you get an error like `SFDP header contains incorrect signature`) then try adding `--rcfile=` to the `set-pll` command:
+```console
+bazel run //sw/host/opentitantool -- --rcfile= --interface=cw310 fpga set-pll
+```
+It's also worth pressing the `USB_RST` and `USR_RESET` buttons on the FPGA if you face continued errors.
+
+## Flash the bitstream onto the FPGA and bootstrap software into flash
+
+There are two ways to load a bitstream on to the FPGA and bootstrap software into the OpenTitan embedded flash:
+1. **automatically**, on single invocations of `bazel test ...`.
+1. **manually**, using multiple invocations of `opentitantool`, and
+Which one you use, will depend on how the build target is defined for the software you would like to test on the FPGA.
+Specifically, for software build targets defined in Bazel BUILD files using the `opentitan_functest` Bazel macro, you will use the latter (**automatic**) approach.
+Alternatively, for software build targets defined in Bazel BUILD files using the `opentitan_flash_binary` Bazel macro, you will use the former (**manual**) approach.
+
+See below for details on both approaches.
+
+### Automatically loading FPGA bitstreams and bootstrapping software with Bazel
+
+A majority of on-device software tests are defined using the custom `opentitan_functest` Bazel macro, which under the hood, instantiates several Bazel [`native.sh_test` rules](https://docs.bazel.build/versions/main/be/shell.html#sh_test).
+In doing so, this macro provides a convenient interface for developers to run software tests on OpenTitan FPGA instances with a single invocation of `bazel test ...`.
+For example, to run the UART smoke test (which is an `opentitan_functest` defined in `sw/device/tests/BUILD`) on FPGA hardware, and see the output in real time, use:
+```console
+cd $REPO_TOP
+bazel test --test_tag_filters=cw310 --test_output=streamed //sw/device/tests:uart_smoketest
+```
+or
+```console
+cd $REPO_TOP
+bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310_test_rom
+```
+
+Under the hood, Bazel conveniently dispatches `opentitantool` to both:
+* ensure the correct version of the FPGA bitstream has been loaded onto the FPGA, and
+* bootstrap the desired software test image into the OpenTitan embedded flash.
+
+To get a better understanding of the `opentitantool` functions Bazel invokes automatically, follow the instructions for manually loading FPGA bitstreams below.
+
+#### Configuring Bazel to load the Vivado-built bitstream
+
+By default, the above invocations of `bazel test ...` use the pre-built (Internet downloaded) FPGA bitstream.
+To instruct bazel to load the bitstream built earlier, or to have bazel build an FPGA bitstream on the fly, and load that bitstream onto the FPGA, add the `--define bitstream=vivado` flag to either of the above Bazel commands, for example, run:
+```
+bazel test --define bitstream=vivado --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310_test_rom
+```
+
+#### Configuring Bazel to skip loading a bitstream
+
+Alternatively, if you would like to instruct Bazel to skip loading any bitstream at all, and simply use the bitstream that is already loaded, add the `--define bitstream=skip` flag, for example, run:
+```
+bazel test --define bitstream=skip --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310_test_rom
+```
+
+### Manually loading FPGA bitstreams and bootstrapping OpenTitan software with `opentitantool`
+
+Some on-device software targets are defined using the custom `opentitan_flash_binary` Bazel macro.
+Unlike the `opentitan_functest` macro, the `opentitan_flash_binary` macro does **not** instantiate any Bazel test rules under the hood.
+Therefore, to run such software on OpenTitan FPGA hardware, both a bitstream and the software target must be loaded manually onto the FPGA.
+Below, we describe how to accomplish this, and in doing so, we shed some light on the tasks that Bazel automates through the use of `opentitan_functest` Bazel rules.
+
+#### Manually loading a bitstream onto the FPGA with `opentitantool`
+
+Note: The following examples assume that you have a `~/.config/opentitantool/config` with the proper `--interface` option.
+For the CW310, its contents would look like:
+```
+--interface=cw310
+```
+
+To flash the bitstream onto the FPGA using `opentitantool`, use the following command:
+
+```console
+cd $REPO_TOP
+
+### If you downloaded the bitstream from the Internet:
+bazel run //sw/host/opentitantool fpga load-bitstream /tmp/bitstream-latest/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig
+
+### If you built the bitstream yourself:
+bazel run //sw/host/opentitantool fpga load-bitstream $(ci/scripts/target-location.sh //hw/bitstream/vivado:fpga_cw310_test_rom)
+```
+
+Depending on the FPGA device, the flashing itself may take several seconds.
+After completion, a message like this should be visible from the UART:
+
+```
+I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
+I00001 test_rom.c:87] TestROM:6b2ca9a1
+I00002 test_rom.c:118] Test ROM complete, jumping to flash!
+```
+
+#### Bootstrapping the demo software {#hello-world-demo}
+
+The `hello_world` demo software shows off some capabilities of the OpenTitan hardware.
+To load `hello_world` into the FPGA on the ChipWhisperer CW310 board follow the steps shown below.
+
+1. Generate the bitstream and flash it to the FPGA as described above.
+1. Open a serial console (use the device file determined before) and connect.
+   Settings: 115200 baud, 8 bits per byte, no software flow-control for sending and receiving data.
+   ```console
+   screen /dev/ttyACM1 115200,cs8,-ixon,-ixoff
+   ```
+1. Run `opentitantool`.
+   ```console
+   cd ${REPO_TOP}
+   bazel run //sw/host/opentitantool -- --interface=cw310 fpga set-pll # This needs to be done only once.
+   bazel build //sw/device/examples/hello_world:hello_world_fpga_cw310_bin
+   bazel run //sw/host/opentitantool bootstrap $(ci/scripts/target-location.sh //sw/device/examples/hello_world:hello_world_fpga_cw310_bin)
+   ```
+
+   and then output like this should appear from the UART:
+   ```
+   I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
+   I00001 test_rom.c:87] TestROM:6b2ca9a1
+   I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
+   I00001 test_rom.c:87] TestROM:6b2ca9a1
+   I00002 test_rom.c:118] Test ROM complete, jumping to flash!
+   I00000 hello_world.c:66] Hello World!
+   I00001 hello_world.c:67] Built at: Jun 13 2022, 14:16:59
+   I00002 demos.c:18] Watch the LEDs!
+   I00003 hello_world.c:74] Try out the switches on the board
+   I00004 hello_world.c:75] or type anything into the console window.
+   I00005 hello_world.c:76] The LEDs show the ASCII code of the last character.
+   ```
+
+1. Observe the output both on the board and the serial console. Type any text into the console window.
+1. Exit `screen` by pressing CTRL-a k, and confirm with y.
+
+#### Troubleshooting
+
+If the firmware load fails, try pressing the "USR-RST" button before loading the bitstream.
+
+## Connect with OpenOCD and debug
+
+The CW310 supports JTAG-based debugging with OpenOCD and GDB via the standard ARM JTAG headers on the board (labeled USR Debug Headers).
+To use it, program the bitstream and bootstrap the desired firmware, then connect a JTAG adapter to one of the headers.
+For this guide, the `Olimex ARM-USB-TINY-H` JTAG adapter was used.
+
+After bootstrapping the firmware, the TAP straps may need to be set.
+As of this writing, the FPGA images are typically programmed to be in the RMA lifecycle state, and the TAP straps are sampled continuously in that state.
+To connect the JTAG chain to the CPU's TAP, adjust the strap values with opentitantool.
+Assuming opentitantool has been built and that the current directory is the root of the workspace, run these commands:
+
+```console
+./bazel-bin/sw/host/opentitantool/opentitantool \
+        --interface cw310 \
+        gpio write TAP_STRAP0 false
+./bazel-bin/sw/host/opentitantool/opentitantool \
+        --interface cw310 \
+        gpio write TAP_STRAP1 true
+```
+
+Connect a JTAG adapter to one of the headers.
+For the `Olimex ARM-USB-TINY-H`, use the classic ARM JTAG header (J13) and make sure switch S2 is set to 3.3 V.
+Depending on the adapter's default state, OpenTitan may be held in reset when the adapter is initially connected.
+This reset will come under software control once OpenOCD initializes the driver.
+
+The JTAG adapter's device node in `/dev` must have read-write permissions.
+Otherwise, OpenOCD will fail because it's unable to open the USB device.
+The udev rule below matches the ARM-USB-TINY-H adapter, sets the octal mode mask to `0666`, and creates a symlink at `/dev/jtag_adapter_arm_usb_tiny_h`.
+
+```
+# [/etc/udev/rules.d/90-jtag-adapter.rules]
+
+SUBSYSTEM=="usb", ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="002a", MODE="0666", SYMLINK+="jtag_adapter_arm_usb_tiny_h"
+```
+
+Now, reload the udev rules and reconnect the JTAG adapter.
+
+```bash
+# Reload the udev rules.
+sudo udevadm control --reload-rules
+sudo udevadm trigger
+
+# Physically disconnect and reconnect the JTAG adapter, or fake it:
+sudo udevadm trigger --verbose --type=subsystems --action=remove --subsystem-match=usb --attr-match="idVendor=15ba"
+sudo udevadm trigger --verbose --type=subsystems --action=add --subsystem-match=usb --attr-match="idVendor=15ba"
+
+# Print the permissions of the USB device. This command should print "666".
+stat --dereference -c '%a' /dev/jtag_adapter_arm_usb_tiny_h
+```
+
+To connect the ChipWhisperer CW310 FPGA board with OpenOCD, run the following command:
+
+```console
+cd $REPO_TOP
+openocd -f <adapter-config.cfg> \
+        -c "adapter speed 500; transport select jtag; reset_config trst_and_srst" \
+        -f util/openocd/target/lowrisc-earlgrey.cfg
+```
+
+For the `Olimex ARM-USB-TINY-H` with a Debian-based distro, the adapter configuration would be at `/usr/share/openocd/scripts/interface/ftdi/olimex-arm-usb-tiny-h.cfg`.
+So for that particular case, the command would be the following:
+
+```console
+cd $REPO_TOP
+openocd -f /usr/share/openocd/scripts/interface/ftdi/olimex-arm-usb-tiny-h.cfg \
+        -c "adapter speed 500; transport select jtag; reset_config trst_and_srst" \
+        -f util/openocd/target/lowrisc-earlgrey.cfg
+```
+
+Example OpenOCD output:
+```
+Open On-Chip Debugger 0.11.0
+Licensed under GNU GPL v2
+For bug reports, read
+	http://openocd.org/doc/doxygen/bugs.html
+trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
+
+Info : Hardware thread awareness created
+force hard breakpoints
+Info : Listening on port 6666 for tcl connections
+Info : Listening on port 4444 for telnet connections
+Info : clock speed 1000 kHz
+Info : JTAG tap: riscv.tap tap/device found: 0x04f5484d (mfg: 0x426 (Google Inc), part: 0x4f54, ver: 0x0)
+Info : datacount=2 progbufsize=8
+Info : Examined RISC-V core; found 1 harts
+Info :  hart 0: XLEN=32, misa=0x40101106
+Info : starting gdb server for riscv.tap.0 on 3333
+Info : Listening on port 3333 for gdb connections
+```
+
+Note that the `reset_config` command may need to be adjusted for the particular JTAG adapter in use.
+TRSTn is available on the 20-pin ARM JTAG header only.
+Use `srst_only` if the adapter only supports SRSTn.
+
+See the [install instructions](./install_openocd.md) for guidance on installing OpenOCD.
+
+To actually debug through OpenOCD, it must either be connected through telnet or GDB.
+
+### Debug with OpenOCD
+
+The following is an example for using telnet
+
+```console
+telnet localhost 4444 // or whatever port that is specificed by the openocd command above
+mdw 0x8000 0x10 // read 16 bytes at address 0x8000
+```
+
+### Debug with GDB
+
+First, make sure the device software has been built with debug symbols (by default Bazel does not build software with debug symbols).
+For example, to build and test the UART smoke test with debug symbols, you can add `--copt=-g` flag to the `bazel test ...` command:
+```console
+cd $REPO_TOP
+bazel test --copt=-g --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310_test_rom
+```
+
+Then a connection between OpenOCD and GDB may be established with:
+```console
+cd $REPO_TOP
+./bazelisk.sh build --config=riscv32 //sw/device/tests:uart_smoketest_prog_fpga_cw310.elf
+riscv32-unknown-elf-gdb -ex "target extended-remote :3333" -ex "info reg" \
+  "$(./bazelisk.sh outquery --config=riscv32 //sw/device/tests:uart_smoketest_prog_fpga_cw310.elf)"
+```
+
+The above will print out the contents of the registers upon successs.
+Note that you should have the RISC-V toolchain installed and on your `PATH`.
+For example, if you followed the [Getting Started](README.md#step-3-install-the-lowrisc-risc-v-toolchain) instructions, then make sure `/tools/riscv/bin` is on your `PATH`.
+
+#### Common operations with GDB
+
+Examine 16 memory words in the hex format starting at 0x200005c0
+
+```console
+(gdb) x/16xw 0x200005c0
+```
+
+Press enter again to print the next 16 words.
+Use `help x` to get a description of the command.
+
+If the memory content contains program text it can be disassembled
+
+```console
+(gdb) disassemble 0x200005c0,0x200005c0+16*4
+```
+
+Displaying the memory content can also be delegated to OpenOCD
+
+```console
+(gdb) monitor mdw 0x200005c0 16
+```
+
+Use `monitor help` to get a list of supported commands.
+
+To single-step use `stepi` or `step`
+
+```console
+(gdb) stepi
+```
+
+`stepi` single-steps an instruction, `step` single-steps a line of source code.
+When testing debugging against the hello_world binary it is likely you will break into a delay loop.
+Here the `step` command will seem to hang as it will attempt to step over the whole delay loop with a sequence of single-step instructions which may take quite some time!
+
+To change the program which is debugged the `file` command can be used.
+This will update the symbols which are used to get information about the program.
+It is especially useful in the context of our `rom.elf`, which resides in the ROM region, which will eventually jump to a different executable as part of the flash region.
+
+```console
+(gdb) file sw/device/examples/hello_world/sw.elf
+(gdb) disassemble 0x200005c0,0x200005c0+16*4
+```
+
+The output of the disassemble should now contain additional information.
+
+## Reproducing FPGA CI Failures Locally
+
+When an FPGA test fails in CI, it can be helpful to run the tests locally with the version of the bitstream generated by the failing CI run.
+To avoid rebuilding the bitstream, you can download the bitstream artifact from the Azure Pipeline CI run and use opentitantool to load the bitstream manually.
+
+To download the bitstream:
+
+1. Open your PR on Github and navigate to the "Checks" tab.
+1. On the left sidebar, expand the "Azure Pipelines" menu.
+1. Open the "CI (CW310's Earl Grey Bitstream)" job and click on "View more details on Azure Pipelines".
+1. Click on "1 artifact produced".
+1. Click on the three dots for "partial-build-bin-chip_earlgrey_cw310".
+1. You can either download the artifact directly or download with the URL.
+
+Note that Azure does not allow you to download the artifact with `wget` or `curl` by default, so to use the download URL, you need to specify a `user-agent` header.
+For example, to download with `curl`, you can use the following command
+
+```console
+curl --output /tmp/artifact.tar.gz -H 'user-agent: Mozilla/5.0' <download_URL>
+```
+
+After extracting the artifact, the bitstream is located at `build-bin/hw/top_earlgrey/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.{splice,orig}`.
+The `.splice` bitstream has the ROM spliced in, and the `.orig` bitstream has the test ROM.
+
+Next, load the bitstream with opentitantool, and run the test.
+The FPGA tests attempt to load the latest bitstream by default, but because we wish to use the bitstream that we just loaded, we need to tell Bazel to skip the automatic bitstream loading.
+
+```console
+# Load the bitstream with opentitantool
+bazel run //sw/host/opentitantool --interface=cw310 fpga load-bitstream <path_to_your_bitstream>
+
+# Run the broken test locally, showing all test output and skipping the bitstream loading
+bazel test <broken_test_rule> --define bitstream=skip --test_output=streamed
+```
diff --git a/doc/guides/getting_started/src/setup_verilator.md b/doc/guides/getting_started/src/setup_verilator.md
new file mode 100644
index 0000000..b7f9b8a
--- /dev/null
+++ b/doc/guides/getting_started/src/setup_verilator.md
@@ -0,0 +1,241 @@
+# Verilator Setup
+
+_Before following this guide, make sure you've followed the [dependency installation instructions](README.md)._
+
+## About Verilator
+
+Verilator is a cycle-accurate simulation tool.
+It translates synthesizable Verilog code into a simulation program in C++, which is then compiled and executed.
+
+### Install Verilator
+
+Even though Verilator is packaged for most Linux distributions these versions tend to be too old to be usable.
+We recommend compiling Verilator from source, as outlined here.
+
+Fetch, build and install Verilator itself (this should be done outside the `$REPO_TOP` directory).
+Note that Verilator 4.210 will not build with GCC 12.0 or later, so it will need to be built with an older toolchain.
+The example below assumes gcc-11 and g++-11 are installed on the system.
+
+```console
+export VERILATOR_VERSION={{#tool-version verilator }}
+
+git clone https://github.com/verilator/verilator.git
+cd verilator
+git checkout v$VERILATOR_VERSION
+
+autoconf
+CC=gcc-11 CXX=g++-11 ./configure --prefix=/tools/verilator/$VERILATOR_VERSION
+CC=gcc-11 CXX=g++-11 make
+sudo CC=gcc-11 CXX=g++-11 make install
+```
+The `make` step can take several minutes.
+
+After installation you need to add `/tools/verilator/$VERILATOR_VERSION/bin` to your `PATH` environment variable.
+Also add it to your `~/.bashrc` or equivalent so that it's on the `PATH` in the future, like this:
+```console
+export PATH=/tools/verilator/$VERILATOR_VERSION/bin:$PATH
+```
+
+Check your installation by running:
+```console
+$ verilator --version
+Verilator 4.210 2021-07-07 rev v4.210 (mod)
+```
+
+#### Troubleshooting
+
+If you need to install to a different location than `/tools/verilator/...`, you can pass a different directory to `./configure --prefix` above and add `your/install/location/bin` to `PATH` instead.
+
+## Running Software on a Verilator Simulation with Bazel
+
+First the RTL must be built into a simulator binary.
+This is done by running fusesoc, which collects up RTL code and passes it to Verilator to generate and then compile a C++ model.
+Next software must be built to run on the simulated hardware.
+There are 4 memory types on OpenTitan hardware: ROM, Flash, OTP, and SRAM.
+Software images need to be provided for ROM, Flash, and OTP (SRAM is populated at runtime).
+By default, the system will first execute out of ROM and then jump to Flash.
+The OTP image does not contain executable code, rather it contains root secrets, runtime configuration data, and life cycle state.
+(By default, the life cycle state is set to RMA, which enables debugging features such as the JTAG interface for the main processor.)
+Lastly, the Verilator simulation binary must be run with the correct arguments.
+
+Thankfully, Bazel (and `opentitantool`) simplify this process by providing a single interface for performing all of the above steps.
+Moreover, Bazel automatically connects to the simulated UART (via `opentitantool`) to print the test output in real time.
+
+For example, to run the UART smoke test on Verilator simulated hardware, and see the output in real time, use
+```console
+cd $REPO_TOP
+bazel test --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator
+```
+or
+```console
+cd $REPO_TOP
+bazel test --test_tag_filters=verilator --test_output=streamed //sw/device/tests:uart_smoketest
+```
+
+You should expect to see something like:
+```console
+Invoking: sw/host/opentitantool/opentitantool --rcfile= --logging=info --interface=verilator --verilator-bin=hw/build.verilator_real/sim-verilator/Vchip_sim_tb --verilator-rom=sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem --verilator-flash=sw/device/tests/uart_smoketest_prog_sim_verilator.64.scr.vmem --verilator-otp=hw/ip/otp_ctrl/data/img_rma.vmem console --exit-failure=(FAIL|FAULT).*\n --exit-success=PASS.*\n --timeout=3600s
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::subprocess] Spawning verilator: "hw/build.verilator_real/sim-verilator/Vchip_sim_tb" ["--meminit=rom,sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem", "--meminit=flash,sw/device/tests/uart_smoketest_prog_sim_verilator.64.scr.vmem", "--meminit=otp,hw/ip/otp_ctrl/data/img_rma.vmem"]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Simulation of OpenTitan Earl Grey
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] =================================
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Tracing can be toggled by sending SIGUSR1 to this process:
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ kill -USR1 3422749
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: FIFO pipes created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read (read) and $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write (write) for 32-bit wide GPIO.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: To measure the values of the pins as driven by the device, run
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ cat $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read  # '0' low, '1' high, 'X' floating
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] GPIO: To drive the pins, run a command like
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ echo 'h09 l31' > $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write  # Pull the pin 9 high, and pin 31 low.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] SPI: Created /dev/pts/9 for spi0. Connect to it with any terminal program, e.g.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ screen /dev/pts/9
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] NOTE: a SPI transaction is run for every 4 characters entered.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] SPI: Monitor output file created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/spi0.log. Works well with tail:
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ tail -f $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/spi0.log
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] UART: Created /dev/pts/10 for uart0. Connect to it with any terminal program, e.g.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ screen /dev/pts/10
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] UART: Additionally writing all UART output to 'uart0.log'.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] USB: Monitor output file created at $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/usb0.log. Works well with tail:
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] $ tail -f $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/usb0.log
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] JTAG: Virtual JTAG interface dmi0 is listening on port 44853. Use
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] OpenOCD and the following configuration to connect:
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   interface remote_bitbang
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   remote_bitbang_host localhost
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]   remote_bitbang_port 44853
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout] Simulation running, end by pressing CTRL-c.
+[2022-06-09T08:08:16Z INFO  opentitanlib::transport::verilator::stdout]
+[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] Verilator started with the following interaces:
+[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] gpio_read = $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-read
+[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] gpio_write = $HOME/.cache/bazel/_bazel_ttrippel/3d92022c091a734228e22679f3ac7c7f/execroot/lowrisc_opentitan/bazel-out/k8-fastbuild/bin/sw/device/tests/uart_smoketest_sim_verilator.runfiles/lowrisc_opentitan/gpio0-write
+[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] uart = /dev/pts/10
+[2022-06-09T08:08:17Z INFO  opentitanlib::transport::verilator::transport] spi = /dev/pts/9
+Starting interactive console
+[CTRL+C] to exit.
+
+I00000 test_rom.c:81] Version:    earlgrey_silver_release_v5-5775-gefa09d3b8
+Build Date: 2022-06-09, 00:12:35
+
+I00001 test_rom.c:118] Test ROM complete, jumping to flash!
+I00000 status.c:28] PASS!
+
+
+Exiting interactive console.
+[2022-06-09T08:09:38Z INFO  opentitantool::command::console] ExitSuccess("PASS!\r\n")
+```
+
+**For most use cases, interacting with the UART is all you will need and you can stop here.**
+However, if you want to interact with the simulation in additional ways, there are more options listed below.
+
+## Execution Log
+
+All executed instructions in the loaded software into Verilator simulations are logged to the file `trace_core_00000000.log`.
+By default this file is stored somewhere in `~/.cache/bazel`, you can find it using the following command:
+
+```console
+find ~/.cache/bazel -name "trace_core_00000000.log"
+```
+
+The columns in this file are tab separated; change the tab width in your editor if the columns don't appear clearly, or open the file in a spreadsheet application.
+
+## Interact with GPIO (optional)
+
+The simulation includes a DPI module to map general-purpose I/O (GPIO) pins to two POSIX FIFO files: one for input, and one for output.
+Observe the `gpio0-read` file for outputs (in the same directory as the trace):
+
+```console
+cat gpio0-read
+```
+
+To drive input pins write to the `gpio0-write` file.
+A command consists of the desired state: `h` for high, and `l` for low, and the decimal pin number.
+Multiple commands can be issued by separating them with a single space.
+
+```console
+echo 'h09 l31' > gpio0-write  # Pull the pin 9 high, and pin 31 low.
+```
+
+## Connect with OpenOCD to the JTAG port and use GDB (optional)
+
+The simulation includes a "virtual JTAG" port to which OpenOCD can connect using its `remote_bitbang` driver.
+All necessary configuration files are included in this repository.
+
+See the [OpenOCD install instructions](./install_openocd.md) for guidance on installing OpenOCD.
+
+Run the simulation with Bazel, making sure to build the device software with debug symbols using
+```console
+cd $REPO_TOP
+bazel test --copt=-g --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator
+```
+
+Then, connect with OpenOCD using the following command.
+
+```console
+cd $REPO_TOP
+/tools/openocd/bin/openocd -s util/openocd -f board/lowrisc-earlgrey-verilator.cfg
+```
+
+Lastly, connect GDB using the following command (noting it needs to be altered to point to the sw binary in use).
+
+```console
+cd $REPO_TOP
+riscv32-unknown-elf-gdb -ex "target extended-remote :3333" -ex "info reg" \
+  "$(./bazelisk.sh outquery --config=riscv32 //sw/device/tests:uart_smoketest_prog_sim_verilator.elf)"
+```
+
+## SPI device test interface (optional)
+
+The simulation contains code to monitor the SPI bus and provide a host interface to allow interaction with the `spi_device`.
+When starting the simulation you should see a message like
+
+```console
+SPI: Created /dev/pts/4 for spi0. Connect to it with any terminal program, e.g.
+$ screen /dev/pts/4
+NOTE: a SPI transaction is run for every 4 characters entered.
+SPI: Monitor output file created at /auto/homes/mdh10/github/opentitan/spi0.log. Works well with tail:
+$ tail -f /auto/homes/mdh10/github/opentitan/spi0.log
+```
+
+Use any terminal program, e.g. `screen` or `microcom` to connect to the simulation.
+
+```console
+screen /dev/pts/4
+```
+
+Microcom seems less likely to send unexpected control codes when starting:
+```console
+microcom -p /dev/pts/4
+```
+
+The terminal will accept (but not echo) characters.
+After 4 characters are received a 4-byte SPI packet is sent containing the characters.
+The four characters received from the SPI transaction are echoed to the terminal.
+The `hello_world` code will print out the bytes received from the SPI port (substituting _ for non-printable characters).
+The `hello_world` code initially sets the SPI transmitter to return `SPI!` (so that should echo after the four characters are typed) and when bytes are received it will invert their bottom bit and set them for transmission in the next transfer (thus the Nth set of four characters typed should have an echo of the N-1th set with bottom bit inverted).
+
+The SPI monitor output is written to a file.
+It may be monitored with `tail -f` which conveniently notices when the file is truncated on a new run, so does not need restarting between simulations.
+The output consists of a textual "waveform" representing the SPI signals.
+
+## Generating waveforms (optional)
+
+TODO(lowRISC/opentitan[#13042](https://github.com/lowRISC/opentitan/issues/13042)): the below does not work with the Bazel test running flow.
+
+With the `--trace` argument the simulation generates a FST signal trace which can be viewed with Gtkwave (only).
+Tracing slows down the simulation by roughly factor of 1000.
+
+```console
+cd $REPO_TOP
+build/lowrisc_dv_chip_verilator_sim_0.1/sim-verilator/Vchip_sim_tb \
+  --meminit=rom,build-bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem \
+  --meminit=flash,build-bin/sw/device/examples/hello_world/hello_world_sim_verilator.64.scr.vmem \
+  --meminit=otp,build-bin/sw/device/otp_img/otp_img_sim_verilator.vmem \
+  --trace
+gtkwave sim.fst
+```
diff --git a/doc/introduction.md b/doc/introduction.md
new file mode 100644
index 0000000..a82a16b
--- /dev/null
+++ b/doc/introduction.md
@@ -0,0 +1,46 @@
+# OpenTitan
+
+## Introduction to OpenTitan
+
+[OpenTitan](https://opentitan.org) is an open source silicon Root of Trust (RoT) project.
+OpenTitan will make the silicon RoT design and implementation more transparent, trustworthy, and secure for enterprises, platform providers, and chip manufacturers.
+OpenTitan is administered by lowRISC CIC as a collaborative [project](./project_governance/README.md) to produce high quality, open IP for instantiation as a full-featured product.
+This repository exists to enable collaboration across partners participating in the OpenTitan project.
+
+## Getting Started
+
+To get started with OpenTitan, see the [Getting Started](./guides/getting_started/src/README.md) page.
+For additional resources when working with OpenTitan, see the [list of user guides](https://docs.opentitan.org/doc/guides/getting_started/).
+For details on coding styles or how to use our project-specific tooling, see the [reference manuals](../util/README.md).
+Lastly, the [Hardware Dashboard page](../hw/README.md) contains technical documentation on the SoC, the Ibex processor core, and the individual IP blocks.
+For questions about how the project is organized, see the [project](./project_governance/README.md) landing spot for more information.
+
+## Understanding OpenTitan
+
+* [Use Cases](./use_cases/README.md)
+* [Threat Model](./security/threat_model/README.md)
+* [Security](./security/README.md)
+
+## Datasheets
+
+* [OpenTitan Earl Grey Chip Datasheet](../hw/top_earlgrey/doc/specification.md)
+
+## Documentation
+
+* [Hardware](../hw/README.md)
+* [Software](../sw/README.md)
+
+## Development
+
+* [Getting Started](./guides/getting_started/src/README.md)
+* [User Guides](https://docs.opentitan.org/doc/guides/getting_started/)
+* [Reference Manuals](../util/README.md)
+* [Style Guides](./contributing/style_guides/README.md)
+
+## Repository Structure
+
+The underlying
+[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.
+
+Unless otherwise noted, everything in the repository is covered by the Apache License, Version 2.0. See the [LICENSE](https://github.com/lowRISC/opentitan/blob/master/LICENSE) file and [repository README](https://github.com/lowRISC/opentitan/blob/master/README.md) for more information on licensing and see the user guides below for more information on development methodology.
diff --git a/doc/project/_index.md b/doc/project/_index.md
deleted file mode 100644
index 82348ae..0000000
--- a/doc/project/_index.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: "Introduction to the OpenTitan Project"
----
-
-OpenTitan is a collaborative hardware and software development program with contributors from many organizations.
-This area gives some more information about how the project itself is organized and how to contribute.
-More information will be added over time.
-
-## Quality standards for open hardware IP
-
-In order to gauge the quality of the different IP that is in our repository, we define a series of [Hardware Development Stages]({{< relref "development_stages" >}}) to track the designs.
-The current status of different IP is reflected in the [Hardware Dashboard]({{< relref "hw" >}}).
-The final state for developed IP is *Signed Off*, indicating that design and verification is complete, and the IP should be bug free.
-To make it to that stage, a [Hardware Signoff Checklist]({{< relref "checklist.md" >}}) is used to confirm completion.
-[Here](https://github.com/lowRISC/opentitan/blob/master/util/uvmdvgen/checklist.md.tpl) is a template that can be used as a checklist item.
-
-## Contributing
-
-See our documentation on [Contributing to OpenTitan]({{< relref "contributing.md" >}}) (and the more in-depth [Detailed Contribution Guide]({{<relref "detailed_contribution_guide/_index.md" >}})) for general guidance on how we work, making bug reports, dealing with security issues, and contributing code.
-
-## Governance
-
-OpenTitan is stewarded by lowRISC CIC, a not-for-profit company that uses collaborative engineering to develop and maintain open source silicon designs and tools for the long term.
-As a lowRISC CIC Chartered Project, OpenTitan governance is handled via lowRISC's default Technical Charter.
-
-As described in full detail in the [OpenTitan Technical Charter](https://static.opentitan.org/technical-charter.pdf), our governance structure consists of:
-* The Project Director, Dominic Rizzo, who is a representative of the lowRISC CIC's Board of Directors within the Steering Committee.
-* The Steering Committee, responsible for project oversight and agreeing the technical roadmap.
-* The [Technical Committee]({{< relref "technical_committee" >}}), responsible for technical decision making required to implement the technical roadmap.
-
-## Initiating new development
-
-The [OpenTitan RFC process]({{< relref "rfc_process" >}}) guides developers on how to initiate new development within the program.
-
-## Committers
-
-Committers are individuals with repository write access.
-Everyone is able and encouraged to contribute and to help with code review, but committers are responsible for the final approval and merge of contributions.
-See the [Committers]({{< relref "committers.md" >}}) definition and role description for more information.
diff --git a/doc/project/checklist.md b/doc/project/checklist.md
deleted file mode 100644
index 53a3039..0000000
--- a/doc/project/checklist.md
+++ /dev/null
@@ -1,691 +0,0 @@
----
-title: "Signoff Checklist"
----
-
-This document explains the recommended checklist items to review when transitioning from one [Development Stage]({{<relref "/doc/project/development_stages.md" >}}) to another, for design, verification, and [software device interface function (DIF)]({{< relref "doc/rm/device_interface_functions.md" >}}) stages.
-It is expected that the items in each stage (D1, V1, S1, etc) are completed.
-
-## D1
-
-For a transition from D0 to D1, the following items are expected to be completed.
-
-### SPEC_COMPLETE
-
-The specification is 90% complete, all features are defined.
-The specification is submitted into the repository as a markdown document.
-It is acceptable to make changes for further clarification or more details after the D1 stage.
-
-### CSR_DEFINED
-
-The CSRs required to implement the primary programming model are defined.
-The Hjson file defining the CSRs is checked into the repository.
-It is acceptable to add or modify registers during the D2 stage in order to complete implementation.
-
-### CLKRST_CONNECTED
-
-Clock(s) and reset(s) are connected to all sub-modules.
-
-### IP_TOP
-
-The unit `.sv` exists and meets [comportability]({{< relref "doc/rm/comportability_specification" >}}) requirements.
-
-### IP_INSTANTIABLE
-
-The unit is able to be instantiated and connected in top level RTL files.
-The design must compile and elaborate cleanly without errors.
-The unit must not break top level functionality such as propagating X through TL-UL interfaces, continuously asserting alerts or interrupts, or creating undesired TL-UL transactions.
-To that end, the unit must fulfill the following V1 checklist requirements:
-- TB_TOP_CREATED
-- SIM_RAL_MODEL_GEN_AUTOMATED
-- CSR_CHECK_GEN_AUTOMATED
-- SIM_CSR_MEM_TEST_SUITE_PASSING
-
-### PHYSICAL_MACROS_DEFINED_80
-
-All expected memories have been identified and representative macros instantiated.
-All other physical elements (analog components, pads, etc) are identified and represented with a behavioral model.
-It is acceptable to make changes to these physical macros after the D1 stage as long as they do not have a large impact on the expected resulting area (roughly "80% accurate").
-
-### FUNC_IMPLEMENTED
-
-The mainline functional path is implemented to allow for a basic functionality test by verification.
-("Feature complete" is the target for D2 status.)
-
-### ASSERT_KNOWN_ADDED
-
-All the outputs of the IP have `ASSERT_KNOWN` assertions.
-
-### LINT_SETUP
-
-A lint flow is set up which compiles and runs.
-It is acceptable to have lint warnings at this stage.
-
-## D2
-
-### NEW_FEATURES
-
-Any new features added since D1 are documented and reviewed with DV/SW/FPGA.
-The GitHub Issue, Pull Request, or RFC where the feature was discussed should be linked in the `Notes` section.
-
-### BLOCK_DIAGRAM
-
-Block diagrams have been updated to reflect the current design.
-
-### DOC_INTERFACE
-
-All IP block interfaces that are not autogenerated are documented.
-
-### DOC_INTEGRATION_GUIDE
-
-Any integration specifics that are not captured by the [comportability]({{< relref "doc/rm/comportability_specification" >}}) specification have been documented.
-Examples include special synthesis constraints or clock requirements for this IP.
-
-### MISSING_FUNC
-
-Any missing functionality is documented.
-
-### FEATURE_FROZEN
-
-Feature requests for this IP version are frozen at this time.
-
-### FEATURE_COMPLETE
-
-All features specified are implemented.
-
-### PORT_FROZEN
-
-All ports are implemented and their specification is frozen, except for security / countermeasure related ports that will not affect functionality or architectural behavior (the addition of such ports can be delayed only until D2S).
-
-### ARCHITECTURE_FROZEN
-
-All architectural state (RAMs, CSRs, etc) is implemented and the specification frozen.
-
-### REVIEW_TODO
-
-All TODOs have been reviewed and signed off.
-
-### STYLE_X
-
-The IP block conforms to the [style guide regarding X usage](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md#dont-cares-xs).
-
-### CDC_SYNCMACRO
-
-All CDC synchronization flops use behavioral synchronization macros (e.g. `prim_flop_2sync`) not manually created flops.
-
-### LINT_PASS
-
-The lint flow passes cleanly.
-Any lint waiver files have been reviewed.
-
-### CDC_SETUP
-
-A CDC checking run has been set up (if tooling is available).
-The CDC checking run shows no must-fix errors, and a waiver file has been created.
-
-If there is no CDC flow at the block-level, this requirement can be waived.
-
-### RDC_SETUP
-
-An RDC checking run has been set up (if tooling is available).
-The RDC checking run shows no must-fix errors, and a waiver file has been created.
-
-If there is no RDC flow at the block-level, this requirement can be waived.
-
-### AREA_CHECK
-
-An area check has been completed either with an FPGA or ASIC synthesis flow.
-
-### TIMING_CHECK
-
-A timing check has been completed either with an FPGA or ASIC synthesis flow.
-
-### SEC_CM_DOCUMENTED
-
-Any custom security countermeasures other than standardized countermeasures listed under [SEC_CM_IMPLEMENTED]({{< relref "#sec_cm_implemented" >}})  have been identified, documented and their implementation has been planned.
-The actual implementation can be delayed until D2S.
-
-Where the area impact of countermeasures can be reliably estimated, it is recommended to insert dummy logic at D2 in order to better reflect the final area complexity of the design.
-
-## D2S
-
-### SEC_CM_ASSETS_LISTED
-
-List the assets and corresponding countermeasures in canonical format in the IP Hjson (the canonical naming is checked by the reggen tool for correctness).
-
-This list could look, for example, as follows:
-```python
-# Inside the rstmgr.hjson
-
-countermeasures: [
-  { name: "BUS.INTEGRITY",
-    desc: "Bus integrity check."
-  },
-  { name: "RST.INTEGRITY",
-    desc: "Reset integrity checks."
-  },
-  { name: "RST.SHADOW_LOGIC",
-    desc: "Shadow reset logic."
-  }
-  # ...
-]
-
-```
-
-For a full list of permitted asset and countermeasure types, see the [countermeasure.py](https://github.com/lowRISC/opentitan/blob/master/util/reggen/countermeasure.py) script that implements the name checks.
-
-Note the SEC_CM_DOCUMENTED item in the D2 checklist, which is a precursor to this step.
-
-### SEC_CM_IMPLEMENTED
-
-Any appropriate security counter-measures are implemented.
-Implementations must follow the [OpenTitan Secure Hardware Design Guidelines]({{< relref "../security/implementation_guidelines/hardware" >}}).
-
-In particular, note that:
-
-- For duplicated counters [`prim_count`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_count.sv) must be used.
-- For duplicated LFSRs [`prim_double_lfsr`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_double_lfsr.sv) must be used.
-- For redundantly encoded FSMs, [the sparse-fsm-encode.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py) must be used to generate the encoding (in conjunction with the [`PRIM_FLOP_SPARSE_FSM`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_flop_macros.sv)) macro.
-- For multibit signals, the `mubi` types in [`prim_mubi_pkg`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_mubi_pkg.sv) should be used if possible.
-
-### SEC_CM_RND_CNST
-
-Compile-time random netlist constants (such as LFSR seeds or scrambling constants) are exposed to topgen via the `randtype` parameter mechanism in the comportable IP Hjson file.
-Default random seeds and permutations for LFSRs can be generated with [the gen-lfsr-seed.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/gen-lfsr-seed.py).
-See also the related [GitHub issue #2229](https://github.com/lowRISC/opentitan/issues/2229).
-
-### SEC_CM_NON_RESET_FLOPS
-
-A review of sensitive security-critical storage flops was completed.
-Where appropriate, non-reset flops are used to store secure material.
-
-### SEC_CM_SHADOW_REGS
-
-Shadow registers are implemented for all appropriate storage of critical control functions.
-
-### SEC_CM_RTL_REVIEWED
-
-If deemed necessary by the security council, an offline review of the RTL code sections pertaining to the assets and countermeasures listed in SEC_CM_ASSETS_LISTED has been performed.
-
-### SEC_CM_COUNCIL_REVIEWED
-
-Security council has reviewed the asset list and associated documentation (SEC_CM_ASSETS_LISTED, SEC_CM_DOCUMENTED) and deems the defenses implemented appropriate.
-
-The security council decides whether an additional RTL review of the relevant code sections is necessary (SEC_CM_RTL_REVIEWED).
-
-## D3
-
-### NEW_FEATURES_D3
-
-Any approved new features since D2 have been documented and reviewed with DV/SW/FPGA
-
-### TODO_COMPLETE
-
-All TODOs are resolved.
-Deferred TODOs have been marked as "iceboxed" in the code and have been attached an issue as follows: `// ICEBOX(#issue-nr)`.
-
-### LINT_COMPLETE
-
-The lint checking flow is clean.
-Any lint waiver files have been reviewed and signed off by the technical steering committee.
-
-### CDC_COMPLETE
-
-The CDC checking flow is clean.
-CDC waiver files have been reviewed and understood.
-If there is no CDC flow at the block-level, this requirement can be waived.
-
-### RDC_COMPLETE
-
-The RDC checking flow is clean.
-RDC waiver files have been reviewed and understood.
-If there is no RDC flow at the block-level, this requirement can be waived.
-
-### REVIEW_RTL
-
-A simple design review has been conducted by an independent designer.
-
-### REVIEW_DELETED_FF
-
-Any deleted flops have been reviewed and signed off.
-If there is no synthesis flow at the block-level, this requirement can be waived.
-
-### REVIEW_SW_CHANGE
-
-Any software-visible design changes have been reviewed by the software team.
-
-### REVIEW_SW_ERRATA
-
-All known "Won't Fix" bugs and "Errata" have been reviewed by the software team.
-
-## V1
-
-To transition from V0 to V1, the following items are expected to be completed.
-The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
-
-### DV_DOC_DRAFT_COMPLETED
-
-A DV document has been drafted, indicating the overall DV goals, strategy, the testbench environment details with diagram(s) depicting the flow of data, UVCs, checkers, scoreboard, interfaces, assertions and the rationale for the chosen functional coverage plan.
-Details may be missing since most of these items are not expected to be fully understood at this stage.
-
-### TESTPLAN_COMPLETED
-
-A testplan has been written (in Hjson format) indicating:
-- Testpoints (a list of planned tests), each mapping to a design feature, with a description highlighting the goal of the test and optionally, the stimulus and the checking procedure.
-- The functional coverage plan captured as a list of covergroups, with a description highlighting which feature is expected to be covered by each covergroup.
-  It may optionally contain additional details such as coverpoints and crosses of individual aspects of the said feature that is covered.
-
-If the DUT has a CPU for which a ROM firmware is developed (burnt-in during manufacturing):
-- A detailed ROM firmware testplan has been written to adequately verify all functions of the ROM firmware in a pre-silicon simulation environment (i.e. a DV testbench).
-- The testing framework may validate each functionality of the ROM firmware discretely as an individual unit test.
-- Depending on the ROM firmware development progress, this may be postponed for V2.
-
-### TB_TOP_CREATED
-
-A top level testbench has been created with the DUT instantiated.
-The following interfaces are connected (as applicable): TileLink, clocks and resets, interrupts and alerts.
-Other interfaces may not be connected at this point (connecting these is part of SIM_TB_ENV_CREATED).
-Inputs for which interfaces have not yet been created are tied off to 0.
-
-### PRELIMINARY_ASSERTION_CHECKS_ADDED
-
-All available interface assertion monitors are connected (example: tlul_assert).
-
-### SIM_TB_ENV_CREATED
-
-A UVM environment has been created with major interface agents and UVCs connected and instantiated.
-TLM port connections have been made from UVC monitors to the scoreboard.
-
-### SIM_RAL_MODEL_GEN_AUTOMATED
-
-A RAL model is generated using [regtool]({{< relref "/util/reggen/doc" >}}) and instantiated in the UVM environment.
-
-### CSR_CHECK_GEN_AUTOMATED
-
-A CSR check is generated using [regtool]({{< relref "/util/reggen/doc" >}}) and bound in the TB environment.
-
-### TB_GEN_AUTOMATED
-
-Full testbench automation has been completed if applicable.
-This may be required for verifying multiple flavors of parameterized designs.
-
-### SIM_SMOKE_TEST_PASSING
-
-A smoke test exercising the basic functionality of the main DUT datapath is passing.
-The functionality to test (and to what level) may be driven by higher level (e.g. chip) integration requirements.
-These requirements are captured when the testplan is reviewed by the key stakeholders, and the test(s) updated as necessary.
-
-### SIM_CSR_MEM_TEST_SUITE_PASSING
-
-CSR test suites have been added for ALL interfaces (including, but not limited to the DUT's SW device access port, JTAG access port etc.) that have access to the system memory map:
-- HW reset test (test all resets)
-- CSR read/write
-- Bit Bash
-- Aliasing
-
-Memory test suites have been added for ALL interfaces that have access to the system memory map if the DUT has memories:
-- Mem walk
-
-All these tests should verify back to back accesses with zero delays, along with partial reads and partial writes.
-
-### FPV_MAIN_ASSERTIONS_PROVEN
-
-Each input and each output of the module is part of at least one assertion.
-Assertions for the main functional path are implemented and proven.
-
-### SIM_ALT_TOOL_SETUP
-
-The smoke regression passes cleanly (with no warnings) with one additional tool apart from the primary tool selected for signoff.
-
-### SIM_SMOKE_REGRESSION_SETUP
-
-A small suite of tests has been identified as the smoke regression suite and is run regularly to check code health.
-If the testbench has more than one build configuration, then each configuration has at least one test added to the smoke regression suite.
-
-### SIM_NIGHTLY_REGRESSION_SETUP
-
-A nightly regression for running all constrained-random tests with multiple random seeds (iterations) has been setup.
-Directed, non-random tests need not be run with multiple iterations.
-Selecting the number of iterations depends on the coverage, the mean time between failure and the available compute resources.
-For starters, it is recommended to set the number of iterations to 100 for each test.
-It may be trimmed down once the test has stabilized, and the same level of coverage is achieved with fewer iterations.
-The nightly regression should finish overnight so that the results are available the next morning for triage.
-
-### FPV_REGRESSION_SETUP
-
-An FPV regression has been set up by adding the module to the `hw/formal/fpv_all` script.
-
-### SIM_COVERAGE_MODEL_ADDED
-
-A structural coverage collection model has been checked in.
-This is a simulator-specific file (i.e. proprietary format) that captures which hierarchies and what types of coverage are collected.
-For example, pre-verified sub-modules (including some `prim` components pre-verified thoroughly with FPV) can be black-boxed - it is sufficient to only enable the IO toggle coverage of their ports.
-A functional coverage shell object has been created - this may not contain coverpoints or covergroups yet, but it is primed for development post-V1.
-
-### TB_LINT_SETUP
-
-[VeribleLint](https://google.github.io/verible/verilog_lint.html) for the testbench is [set up]({{< relref "hw/lint/doc" >}}) to run in nightly regression, with appropriate waivers.
-* For a constrained random testbench, an entry has been added to `hw/<top-level-design>/lint/<top-level-design>_dv_lint_cfgs.hjson`
-* For an FPV testbench, an entry has been added to `hw/<top-level-design>/lint/<top-level-design>_fpv_lint_cfgs.hjson`
-
-### PRE_VERIFIED_SUB_MODULES_V1
-
-Sub-modules that are pre-verified with their own testbenches have already reached V1 or a higher stage.
-The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification milestones), meets or exceeds the V1 milestone requirement.
-They are clearly cited in the DV document and the coverage of these sub-modules can be excluded in the IP-level testbench.
-
-### DESIGN_SPEC_REVIEWED
-
-The design / micro-architecture specification has been reviewed and signed off.
-If a product requirements document (PRD) exists, then ensure that the design specification meets the product requirements.
-
-### TESTPLAN_REVIEWED
-
-The draft DV document (proposed testbench architecture) and the complete testplan have been reviewed with key stakeholders (as applicable):
-- DUT designer(s)
-- 1-2 peer DV engineers
-- Software engineer (DIF developer)
-- Chip architect / design lead
-- Chip DV lead
-- Security architect
-
-### STD_TEST_CATEGORIES_PLANNED
-
-The following categories of post-V1 tests have been focused on during the testplan review (as applicable):
-- Security / leakage
-- Error scenarios
-- Power
-- Performance
-- Debug
-- Stress
-
-### V2_CHECKLIST_SCOPED
-
-The V2 checklist has been reviewed to understand the scope and estimate effort.
-
-## V2
-
-To transition from V1 to V2, the following items are expected to be completed.
-The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
-
-### DESIGN_DELTAS_CAPTURED_V2
-
-It is possible for the design to have undergone some changes since the DV document and testplan were reviewed in the V0 stage.
-All design deltas have been captured adequately and appropriately in the DV document and the testplan.
-
-### DV_DOC_COMPLETED
-
-The DV document is fully complete.
-
-### FUNCTIONAL_COVERAGE_IMPLEMENTED
-
-The functional coverage plan is fully implemented.
-All covergoups have been created and sampled in the reactive components of the testbench (passive interfaces, monitors and scoreboards).
-
-### ALL_INTERFACES_EXERCISED
-
-For simulations, interfaces are connected to all ports of the DUT and are exercised.
-For an FPV testbench, assertions have been added for all interfaces including sidebands.
-
-### ALL_ASSERTION_CHECKS_ADDED
-
-All planned assertions have been written and enabled.
-
-### SIM_TB_ENV_COMPLETED
-
-A UVM environment has been fully developed with end-to-end checks in the scoreboard enabled.
-
-### SIM_ALL_TESTS_PASSING
-
-All tests in the testplan have been written and are passing with at least one random seed.
-
-### FPV_ALL_ASSERTIONS_WRITTEN
-
-All assertions (except security countermeasure assertions) are implemented and are 90% proven.
-Each output of the module has at least one forward and one backward assertion check.
-The FPV proof run converges within reasonable runtime.
-
-### FPV_ALL_ASSUMPTIONS_REVIEWED
-
-All assumptions have been implemented and reviewed.
-
-### SIM_FW_SIMULATED
-
-If the DUT has a CPU for which a ROM firmware is developed (burnt-in during manufacturing):
-- The ROM firmware testplan is fully written.
-- SIM_ALL_TESTS_PASSING checklist item is met, including these tests.
-
-This checklist item is marked N.A. if the DUT does not have a CPU.
-
-### SIM_NIGHTLY_REGRESSION_V2
-
-A nightly regression with multiple random seeds is 90% passing.
-
-### SIM_CODE_COVERAGE_V2
-
-Line, toggle, fsm (state & transition), branch and assertion code coverage has reached 90%.
-Toggle coverage of the ports of the DUT and all pre-verified sub-modules have individually reached 90% in both directions (1->0 and 0->1).
-
-### SIM_FUNCTIONAL_COVERAGE_V2
-
-Functional coverage has reached 70%.
-
-### FPV_CODE_COVERAGE_V2
-
-Branch, statement and functional code coverage for FPV testbenches has reached 90%.
-
-### FPV_COI_COVERAGE_V2
-
-COI coverage for FPV testbenches has reached 75%.
-
-### PRE_VERIFIED_SUB_MODULES_V2
-
-Sub-modules that are pre-verified with their own testbenches have already reached V2 or a higher stage.
-The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification milestones), meets or exceeds the V2 milestone requirement.
-
-### SEC_CM_PLANNED
-
-Security countermeasures are planned and documented.
-- Common countermeasure features (such as shadowed reg, hardened counter etc) can be tested by importing common sec_cm testplans, tests and adding the bind file `cm_sec_bind`.
-- Additional checks and sequences may be needed to verify those features. Document those in the individual testplan.
-- Create testplan for non-common countermeasures.
-
-### NO_HIGH_PRIORITY_ISSUES_PENDING
-
-All high priority (tagged P0 and P1) design bugs have been addressed and closed.
-If the bugs were found elsewhere, ensure that they are reproduced deterministically in DV (through additional tests or by tweaking existing tests as needed) and have the design fixes adequately verified.
-
-### ALL_LOW_PRIORITY_ISSUES_ROOT_CAUSED
-
-All low priority (tagged P2 and P3) design bugs have been root-caused.
-They may be deferred to post V2 for closure.
-
-### DV_DOC_TESTPLAN_REVIEWED
-
-The DV document and testplan are complete and have been reviewed by key stakeholders (as applicable):
-- DUT designer(s)
-- 1-2 peer DV engineers
-- Chip architect / design lead
-- Chip DV lead
-- Security architect
-
-This review will focus on the design deltas captured in the testplan since the last review.
-In addition, the fully implemented functional coverage plan, the observed coverage and the coverage exclusions are expected to be scrutinized to ensure there are no verification holes or any gaps in achieving the required stimulus quality, before the work towards progressing to V3 can commence.
-
-### V3_CHECKLIST_SCOPED
-
-The V3 checklist has been reviewed to understand the scope and estimate effort.
-
-## V2S
-
-### SEC_CM_TESTPLAN_COMPLETED
-
-The testplan has been updated with the necessary testpoints and covergroups to adequately verify all security countermeasures implemented in the DUT.
-These countermeasures are listed in the comportable IP Hjson file located at `hw/ip/<ip>/data/<ip>.hjson` (or equivalent).
-
-On OpenTitan, a security countermeasures testplan is auto-generated (the first time) by the `reggen` tool for each DUT, and is placed at `hw/ip/<ip>/data/<ip>_sec_cm_testplan.hjson` (or equivalent).
-This testplan has been imported into the main testplan written for the DUT.
-Tests implemented to verify the security countermeasures have been mapped to these testpoints.
-
-Common countermeasures can be fully verified or partially handled by cip_lib.
-Follow this [document]({{< relref "hw/dv/sv/cip_lib/doc#security-verification-in-cip_lib" >}}) to enable them.
-Make sure to import the applicable common sec_cm [tests](https://github.com/lowRISC/opentitan/tree/master/hw/dv/tools/dvsim/tests) and [testplans](https://github.com/lowRISC/opentitan/tree/master/hw/dv/tools/dvsim/testplans).
-
-### FPV_SEC_CM_PROVEN
-
-All security countermeasure assertions are proven in FPV.
-The required assertions for countermeasure are defined in [Security Countermeasure Verification Framework]({{< relref "doc/ug/sec_cm_dv_framework" >}}).
-
-### SIM_SEC_CM_VERIFIED
-
-All security countermeasures are verified in simulation.
-Common countermeasures can be fully verified or partially handled by cip_lib.
-Refer to the [cip_lib document]({{< relref "hw/dv/sv/cip_lib/doc#security-verification-in-cip_lib" >}}) for details.
-
-### SIM_COVERAGE_REVIEWED
-
-Security countermeasure blocks may have been excluded in order to satisfy the V2 sign-off criteria.
-If so, these exclusions should be removed.
-
-If UNR exclusion has been generated, it needs to be re-generated and reviewed after all security countermeasure tests have been implemented, as fault injection can exercise countermeasures which are deemed as unreachable code.
-The V2S coverage requirement is the same as V2 (90% code coverage and 70% functional coverage).
-
-### SEC_CM_DV_REVIEWED
-
-The security countermeasures testplan and the overall DV effort has been reviewed by key stakeholders (as applicable):
-- DUT designer(s)
-- 1-2 peer DV engineers
-- Security architect (optional)
-
-This review may be waived if not deemed necessary.
-
-## V3
-
-To transition from V2 to V3, the following items are expected to be completed.
-The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
-
-### DESIGN_DELTAS_CAPTURED_V3
-
-Although rare, it is possible for the design to have undergone some last-minute changes since V2.
-All additional design deltas have been captured adequately and appropriately in the DV document and the testplan.
-
-### X_PROP_ANALYSIS_COMPLETED
-
-X Propagation analysis has been completed.
-
-### FPV_ASSERTIONS_PROVEN_AT_V3
-
-All assertions are implemented and 100% proven.
-There are no undetermined or unreachable properties.
-
-### SIM_NIGHTLY_REGRESSION_AT_V3
-
-A nightly regression with multiple random seeds is 100% passing (with 1 week minimum soak time).
-
-### SIM_CODE_COVERAGE_AT_100
-
-Line, toggle, fsm (state & transition), branch and assertion code coverage has reached 100%.
-
-### SIM_FUNCTIONAL_COVERAGE_AT_100
-
-Functional coverage has reached 100%.
-
-### FPV_CODE_COVERAGE_AT_100
-
-Branch, statement and functional code coverage for FPV testbenches has reached 100%.
-
-### FPV_COI_COVERAGE_AT_100
-
-COI coverage for FPV testbenches has reached 100%.
-
-### ALL_TODOS_RESOLVED
-
-There are no remaining TODO items anywhere in the testbench code, including common components and UVCs.
-
-### NO_TOOL_WARNINGS_THROWN
-
-There are no compile-time or run-time warnings thrown by the simulator.
-
-### TB_LINT_COMPLETE
-
-The lint flow for the testbench is clean.
-Any lint waiver files have been reviewed and signed off by the technical steering committee.
-
-### PRE_VERIFIED_SUB_MODULES_V3
-
-Sub-modules that are pre-verified with their own testbenches have already reached the V3 stage.
-The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification milestones), meets the V3 milestone requirement.
-
-### NO_ISSUES_PENDING
-
-All design and testbench bugs have been addressed and closed.
-
-## S1
-
-For a transition from S0 to S1, the following items are expected to be completed.
-
-### DIF_EXISTS
-
-Autogenerated IRQ and Alert DIFs have been created with the `util/make_new_dif.py` tool, and exist in `sw/device/lib/dif/autogen/`.
-Additionally, a header file, `dif_<ip>.h` and, optionally, `dif_<ip>.c` exist in `sw/device/lib/dif/`.
-
-### DIF_USED_IN_TREE
-
-All existing **non-production** code in the tree which uses the device does so via the DIF or a production driver.
-
-### DIF_TEST_SMOKE
-
-Smoke tests exist for the DIF in `sw/device/tests` named `<ip>_smoketest.c`.
-
-This should perform a basic test of the main datapath of the hardware module by the embedded core, via the DIF, and should be able to be run on all OpenTitan platforms (including FPGA, simulation, and DV).
-This test will be shared with DV.
-
-## S2
-
-For a transition from S1 to S2, the following items are expected to be completed.
-
-### DIF_HW_FEATURE_COMPLETE
-
-The DIF's respective device IP is at least stage D2.
-
-### DIF_FEATURES
-
-The DIF has functions to cover all specified hardware functionality.
-
-### DIF_DV_TESTS
-
-Chip-level DV testing for the IP using DIFs has been started.
-
-## S3
-
-For a transition from S2 to S3, the following items are expected to be completed.
-
-### DIF_HW_DESIGN_COMPLETE
-
-The DIF's respective device IP is at least stage D3.
-
-### DIF_HW_VERIFICATION_COMPLETE
-
-The DIF's respective device IP is at least stage V3.
-
-### DIF_DOC_HW
-
-The HW IP Programmer's guide references specific DIF APIs that can be used for operations.
-
-### DIF_CODE_STYLE
-
-The DIF follows DIF-specific guidelines in [`sw/device/lib/dif`]({{< relref "sw/device/lib/dif" >}}) and the OpenTitan C style guidelines.
-
-### DIF_TEST_UNIT
-
-Software unit tests exist for the DIF in `sw/device/tests/dif` named `dif_<ip>_unittest.cc`.
-Unit tests exist to cover (at least):
-
-- Device Initialisation
-- All Device FIFOs (including when empty, full, and adding data)
-- All Device Registers
-- All DIF Functions
-- All DIF return codes
-
-### DIF_TODO_COMPLETE
-
-All DIF TODOs are complete.
diff --git a/doc/project/committers.md b/doc/project/committers.md
deleted file mode 100644
index bc2bcef..0000000
--- a/doc/project/committers.md
+++ /dev/null
@@ -1,49 +0,0 @@
----
-title: "Committers"
----
-
-Committers are individuals with repository write access.
-While everyone can and is encouraged to contribute by reviewing code, committers are responsible for final approval and merge.
-Committers must ensure that any code merged meets a high quality bar, has been properly discussed, and the design rationale adequately explained and documented.
-Making a judgement on when these requirements have been met is fundamental to the role.
-
-Committers are proposed by and voted on by the TC.
-Committers should:
-* Be active contributors to the project, with a history of high quality contributions, including code reviews.
-* Be familiar with the project processes and rules, and be able to apply them fairly.
-* Be responsive to feedback from TC members on their review approach and interpretation of project policy.
-* Work to ensure that stakeholders are properly consulted on any code or design changes.
-
-If you meet the above criteria and are interested in becoming a committer, then approach a TC member to see if they would be willing to propose you.
-TC members may also reach out to you to ask if you would be interested in becoming a committer.
-
-Under certain exceptional circumstances, the Steering Committee may vote to revoke a committer's status.
-This may happen in cases such as a committer failing to act as a good citizen within the project and the issue cannot be resolved through discussion.
-It may also be a natural function of "pruning the tree" as an individual's involvement in the project changes over time.
-
-The list of committers is maintained within the project's Git repository and is available in the [COMMITTERS file](https://github.com/lowRISC/opentitan/blob/master/COMMITTERS) in the repository root.
-
-## Commit Escalation Guidelines
-
-When reviewing a pull request, there are a range of options in giving feedback.
-Although committers have the final say on patch approval or requiring changes, the task of reviewing is shared by all contributors, who may make similar requests.
-Options for reviewing a pull request include:
-* Approval, with no further conditions.
-  * This is appropriate when you are confident the changes are correct, sufficiently explained, in line with the project coding styles, and don't need further review by others or a companion [RFC]({{< relref "rfc_process" >}}) to be written.
-    You should only use this for cases where you have sufficient expertise in the areas being modified.
-  * Note that if the PR came from someone without commit rights, you will need to rebase and merge for them.
-* Approval, but with a request to get an additional approval from other named reviewers.
-  * This is appropriate when you believe the changes are correct, but would either like a second opinion or to ensure that another contributor is aware of and approves of the changes.
-* Request for changes.
-  * This is appropriate when problems or potential improvements are spotted that can be addressed by the original submitter updating the pull request.
-* Request that further design rationale be written up and shared (but a full [RFC]({{< relref "rfc_process" >}}) isn't necessary).
-  * This is appropriate when the rationale for a change is not clear, or a lack of accompanying documentation makes reviewing the code challenging.
-    This can commonly occur when further explanation would be valuable for people working in that area but no project-wide consensus is needed.
-* Request that an RFC be written up and submitted.
-  * This is appropriate when adding a major new piece of code (e.g. an IP block) or when a change is cross-cutting and likely to require input from many stakeholders.
-    The expectation is that in many cases, project contributors will recognize when an RFC is needed before getting to the point of submitting a pull request.
-
-In all cases, clarity in your feedback will be valued.
-For instance, if you are giving code style feedback but not reviewing higher level design decisions (perhaps because you are expecting another committer/contributor to do so), it is useful to say so.
-
-Where Committers disagree on the path forwards for a given PR and are unable to reach an agreement, the review moves to the TC.
diff --git a/doc/project/contributing.md b/doc/project/contributing.md
deleted file mode 100644
index 946a6bf..0000000
--- a/doc/project/contributing.md
+++ /dev/null
@@ -1,62 +0,0 @@
----
-title: Contributing to OpenTitan
----
-
-Thank you for your interest in contributing to OpenTitan.
-This document provides some guidelines to making those contributions.
-Important points before getting started:
-* We consider honest feedback crucial to quality.
-  We work hard to thoroughly review changes and provide actionable feedback.
-  We do this to ensure a high quality open source design.
-* Always assume good intent.
-  Our feedback may be demanding and may even feel disheartening.
-  Again, this is to support a high quality silicon design, and we definitely appreciate all OpenTitan contributions.
-* Please be friendly and patient in your communications.
-* All OpenTitan interactions are covered by [lowRISC's code of conduct](https://www.lowrisc.org/code-of-conduct/).
-* When communicating, remember OpenTitan is a security-focused project.
-  Because of this, certain issues may need to be discussed in a small group first.
-  See the [Security Issues Process]({{<ref "#security-issues" >}}) described below for more details.
-* OpenTitan involves both hardware and software.
-  We follow a hybrid approach involving both silicon and software design practices.
-* OpenTitan is a work in progress.
-  We are always looking for ways to improve and welcome feedback on any project matter, technical or not.
-
-**Important**: Please read the next three, short sections on reporting bugs, reporting security issues, and contributing code in preparation for making your first contribution to OpenTitan.
-If you would like more details, see the [Detailed Contribution Guide]({{<relref "detailed_contribution_guide/_index.md" >}}).
-
-## Bug reports
-
-**To report a security issue, please follow the [Security Issues Process]({{<ref "#security-issues" >}})**.
-
-Ideally, all designs are bug free.
-Realistically, each piece of collateral in our repository is in a different state of maturity with some still under active testing and development.
-See the [Hardware Development Stages]({{< relref "development_stages" >}}) for an example of how hardware progress is tracked.
-
-We are happy to receive bug reports and eager to fix them.
-Please make reports by opening a new issue in our [GitHub issue tracker](https://github.com/lowRISC/opentitan/issues).
-
-## Security issues
-
-Security is of major importance to the OpenTitan project.
-When dealing with security matters, and in keeping with standard industry practice, there are reasons why it makes sense to be cautious and have a non-public discussion within a small group of experts before full disclosure.
-For example,
-* to ensure responsible disclosure of vulnerabilities,
-* or to discuss the security impact of new features or proposed changes to an existing feature.
-
-If you believe you have found a security issue or intend to work on potentially security sensitive matters, please first reach out to our experienced security team at security@opentitan.org before starting a public discussion.
-That will enable us to engage successfully without creating undue risk to the project or its consumers.
-
-## Contributing code
-
-The information below aims at helping you get involved in the OpenTitan project by guiding you through our process of preparing your contribution and getting it integrated.
-
-For straight-forward and non-invasive contributions, a high level of coordination is unlikely to be necessary.
-In these cases, please open a pull request.
-
-For larger proposed changes we ask contributors to:
-* Discuss the matter with the team, either through the [opentitan-dev@opentitan.org](https://groups.google.com/a/opentitan.org/forum/#!forum/opentitan-dev) mailing list or through discussions in issues on GitHub.
-  Agree on a course of action and document this in a GitHub issue.
-* Implement the contribution, i.e., the solution previously agreed on, and reference the discussion when submitting the contribution.
-* Have the implementation reviewed by the team, address any feedback, and finally have it integrated into the project.
-
-Note that contributions must be accompanied by sign-off text which indicates acceptance of the project's Contributor License Agreement - see [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md) for details.
diff --git a/doc/project/detailed_contribution_guide/_index.md b/doc/project/detailed_contribution_guide/_index.md
deleted file mode 100644
index c557327..0000000
--- a/doc/project/detailed_contribution_guide/_index.md
+++ /dev/null
@@ -1,341 +0,0 @@
----
-title: "In-depth guide to contributing to OpenTitan"
----
-
-The way we work on OpenTitan is very similar to what is done in other collaborative open-source projects.
-For a brief overview see [Contributing to OpenTitan]({{< relref "contributing.md" >}}).
-This document provides a detailed reference on how we collaborate within the OpenTitan project and is organized as follows:
-* [Communication](#communication)
-* [Working with Issues](#working-with-issues)
-* [Overview on Contributing Code](#overview-on-contributing-code)
-* [Writing Code](#writing-code)
-* [Working with pull requests (PRs)](#working-with-pull-requests)
-
-# Communication
-
-All OpenTitan interactions, on all platforms and face to face, are covered by lowRISC's [code of conduct](https://www.lowrisc.org/code-of-conduct/).
-We believe in creating a welcoming and respectful community, so we have a few ground rules we expect contributors to follow:
-
-* be friendly and patient,
-* be welcoming,
-* be considerate,
-* be respectful,
-* be careful in the words that you choose and be kind to others, and
-* when we disagree, try to understand why.
-
-We list these principles as general guidance to make it easier to communicate and participate in the OpenTitan (and lowRISC) community.
-
-## When to file an issue vs. sending an email vs. creating a document?
-
-GitHub issues and shared Google Docs are generally preferable to email, as these can be more easily tracked, cross-referenced, archived and shared, e.g., with people joining later.
-
-Emails (e.g. to the [opentitan-dev@opentitan.org](https://groups.google.com/a/opentitan.org/forum/#!forum/opentitan-dev) mailing list) are suitable to raise awareness of discussions and to call for participation.
-Emails between members of a smaller group are also useful for preliminary evaluations before starting a public issue or document.
-
-When it comes to technical discussions, either shared documents on Google Docs or GitHub issues may be used.
-The former are more suitable for initial, broader discussions, for comparing different options and for soliciting comments from a wider audience on a proposal over a longer period of time, whereas the latter are more suitable for cross-referencing in pull requests, and for presenting the final proposal.
-The outcome of such discussions should always be summarized in a GitHub issue for later reference.
-
-## Where do we discuss implementation details/proposals before creating PRs?
-
-A shared Google Doc is suitable for initial, broader discussions, for comparing different design options and for a wider audience and agile commenting, but not for revisioning, referencing and storage in the repository.
-Therefore, such a shared document should always be linked from a GitHub issue and the outcome of this discussion should be summarized in that issue.
-For short proposals, the entire discussion can be had in a GitHub issue, without a linked document.
-
-## How to/why use Google Docs?
-
-Collaborative documents are more useful than GitHub issues for initial, broader discussions, for comparing different design options and for a wider audience commenting on a proposal over a longer period of time, or when interactive editing is required.
-We often make use of a Google Doc to start the discussion of an idea or proposal, before later converting it to Markdown and moving to GitHub (e.g. as a PR adding new documentation).
-
-## When to assign issues or request specific reviewers?
-
-We typically rely on contributors self-assigning to an issue when they start working on it, so it is best to leave issues unassigned when creating them.
-If you think a particular issue might be relevant for someone, you can bring it to their attention by mentioning the username in a comment.
-Team members are regularly scanning through open issues and will also help ensure issues are brought to the attention of the right people.
-
-For PRs, you should always request reviews unless the PR is work-in-progress or just in draft state (in these cases, please explicitly label the PR as a [draft](https://github.blog/2019-02-14-introducing-draft-pull-requests/)).
-In many cases, there will be a default review assignment depending on the files modified in the PR.
-You are also strongly encouraged to ask for reviews from anyone you know to be working in that area.
-
-# Working with Issues
-
-## Labeling and assigning issues
-
-Issue and PR labels can be useful as they help categorizing, prioritizing and assigning open issues/PRs.
-When filing a new issue, you are welcome to assign labels if you feel comfortable about selecting suitable labels.
-If no labels are assigned, committers and other team members might do that when browsing through the open issues/PR from time to time.
-If possible, you should also add a meaningful tag to the subject line of your issue as outlined in [subject tags](#subject-tags).
-
-In this project, we use the assignee field to indicate people who are actively working on an issue, so it's best to let people assign themselves when they start working on it.
-If you'd like to bring an issue to the attention of a particular contributor or ask if they might be able to help solve it, simply mention them in a comment.
-
-## Who should respond to filed issues?
-
-Anyone who feels qualified to help, though if you're working outside your usual area of expertise and making a best-effort guess it's polite to say so.
-If you see an issue that might be of relevance for a particular team member, you are welcome to ping this person by commenting on the issue mentioning that person (*e.g.* "@*githubname* what do you think?").
-
-## How to reference issues in commit messages and PRs?
-
-Commits and PRs related to existing issues or PRs should reference those.
-References should always be accompanied by a brief summary of the outcome of the referenced discussion and how the current PR/commit/issue relates to that.
-References are not only relevant for automatically closing issues but also and especially for documentation.
-
-Both long references such as "lowRISC/opentitan#1" and short references "#1" can be used in commit messages.
-However, short references should not be used in the subject line as they might accidentally create links to unrelated issues if commits containing such references end up in other repositories, e.g., when vendoring in external sources.
-
-See also [References and URLs on GitHub](https://help.github.com/en/github/writing-on-github/autolinked-references-and-urls).
-
-# Overview on Contributing Code
-
-Depending on the size and impact of your contribution, it may need to go through the OpenTitan RFC process.
-If your contribution is large, cross cutting, or potentially contentious, we will ask you to follow the procedure documented in the [Request for Comments (RFC) Process]({{< relref "doc/project/rfc_process" >}}).
-
-If you believe an RFC is not required, a lighter weight process will suffice.
-Note that if a core committer suggests that a contributor follow the RFC process, you will likely need to do so.
-The lightweight process is:
-
-![Code contribution process flowchart](contributing_code_flow_chart.svg)
-
-1. Check if an issue or PR addressing the same matter already exists.
-   If so, consider contributing to the existing issue/PR instead of opening a new one.
-2. Assess whether the matter may be security sensitive.
-   If so, follow the [[Security Issues Process]({{< relref "doc/project/contributing.md#security-issues" >}}).
-3. [Create a GitHub issue](#working-with-issues) to raise awareness, start the discussion, and build consensus that the issue needs to be addressed.
-   For more information, refer to [Communication](#communication).
-4. Start discussing possible solutions in a smaller group, possibly outside of GitHub, but in a shared document (we typically use Google Docs for convenience) that is linked to the original GitHub issue.
-   Find consensus inside the interest group and come up with a proposal.
-   For short proposals, the entire discussion can be had in a GitHub issue, without a linked document.
-   For more information, refer to [Communication](#communication).
-5. Summarize the outcome of the discussion or the proposed solution in the original GitHub issue.
-   For bug fixes, proceed to Step 8.
-6. Share the proposal with the wider team and collect feedback.
-   For more information, refer to [Communication](#communication).
-7. If the feedback is negative, go back to Step 4.
-   If agreement with the wider team cannot be found even after a second or third iteration, consider starting the [Request for Comments (RFC) Process]({{< relref "doc/project/rfc_process" >}}).
-8. Start implementation and prepare a first PR.
-   Create the PR from a fork rather than making new branches in `github.com/lowRISC/opentitan`.
-   Reference the GitHub issue in your PR description and commit messages (see also [Working with Issues](#working-with-issues), [Writing Code](#writing-code)).
-   To simplify and speed up code review, we expect larger contributions like new features and major changes to be broken into multiple, smaller PRs wherever possible.
-   For more information refer to [Working with PRs](#working-with-pull-requests).
-
-Further information can be found in [Getting Started with a Design]({{< relref "doc/ug/hw_design" >}}) and in [Request for Comments (RFC) Process]({{< relref "doc/project/rfc_process" >}}).
-
-# Writing Code
-
-## Licensing
-
-The main license used by OpenTitan is the Apache License, Version 2.0, marked by the following license header in all source files:
-
-    // Copyright lowRISC contributors.
-    // Licensed under the Apache License, Version 2.0, see LICENSE for details.
-    // SPDX-License-Identifier: Apache-2.0
-
-Do not attempt to commit code with a non-Apache license without discussing first.
-
-## Coding style guides, formatting tools
-
-All source code contributions must adhere to project style guides.
-We use separate style guides for different languages:
-* [SystemVerilog](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md)
-* [C/C++]({{< relref "c_cpp_coding_style.md" >}})
-* [Python]({{< relref "python_coding_style.md" >}})
-* [Markdown]({{< relref "markdown_usage_style.md" >}})
-* [Hjson]({{< relref "hjson_usage_style.md" >}})
-* [RISC-V Assembly]({{< relref "asm_coding_style.md" >}})
-
-If unsure about the style, be consistent with the existing code and do your best to match its style.
-
-For C/C++ code, we recommend to run "git-clang-format" on your changes.
-
-## What should commit messages look like?
-
-There are many [useful guides](https://chris.beams.io/posts/git-commit/) available on how to write commit messages and why.
-
-The following list contains a set of simple guidelines that we consider most useful.
-* A commit message consists of a subject line followed by an optional body.
-* Keep the subject line short. Ideally under 50 characters.
-* Capitalize the subject line.
-* Use the imperative mood in the subject line.
-* Use the present tense ("Add feature" not "Added feature").
-* When changes are restricted to a specific area, you should add a tag to the start of the subject line in square brackets, e.g. "\[uart/rtl\] Rework Tx FSM". See also [subject tags](#subject-tags).
-* Do not end the subject line with a period.
-* Separate subject from body with a blank line.
-* Wrap the body at 72 characters.
-* Use the body to explain what and why (rather than how).
-
-To indicate acceptance of our Contributor License Agreement (CLA), commits should also include a Signed-off-by line (which can be generated by `git commit -s`).
-See [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md) for more information.
-
-## Subject tags
-
-If code changes, issues or pull requests (PRs) are restricted to a specific area, we recommend adding a tag in square brackets, *e.g.*, "\[uart/rtl\]", to the subject line of your commit message, issue or PR.
-Such tags help to identify areas affected by changes.
-Tags are particularly helpful when dealing with pull requests (PRs) as they can guide team members for code review and simplify later retrieval of specific PRs through the GitHub web interface.
-
-You can add multiple tags separated by a comma, e.g., "\[top, dv\]" to indicate that multiple separated areas are affected.
-In addition, you can add further levels of hierarchy to make the indication more precise such as "\[uart/rtl\]" instead of just "\[uart\]", "\[sw/device\]" instead of "\[sw\]".
-
-## What about TODO comments?
-
-We track non-trivial pieces of work via GitHub issues rather than TODO comments in the code.
-We do not use TODO(name) comments as used in some code bases - if it is important enough to assign someone, it is worth tracking via an issue.
-
-If you as a code reviewer come across TODOs, you should encourage the author to create a GitHub issue to track the work item, which can be referenced using a comment such as `// TODO(lowrisc/<project_name>#<issue_number>): <brief description>`.
-Code authors are encouraged to use TODO rather than FIXME.
-
-## How are auto-generated files treated?
-
-We leverage a set of utilities to automatically generate parts of the source code based on configuration files and templates.
-Examples for auto-generated RTL code include the register interface of the peripheral IP cores (using the register tool reggen), the top level (using topgen) and the main TL-UL crossbar (using tlgen).
-Auto-generated source files are typically checked into the OpenTitan repository.
-This allows people to build the system without having to invoke the utilities.
-
-To prevent people from editing auto-generated top-level files, these files both contain a header marking them as such and they live in subfolders called `autogen/`.
-When implementing changes that affect auto-generated files, the configuration or template file must be edited, the auto-generated files need to be regenerated and everything needs to be committed to the repository.
-
-## Vendored Code
-
-Not all source code included in the OpenTitan repository has been
-developed as part of the project.
-OpenTitan also leverages code from other repositories (e.g. [Ibex](https://github.com/lowrisc/ibex) or code from third parties released under compatible open-source licenses.
-The process of incorporating such code into the OpenTitan repository, which besides creating a copy may also involve the application of patches, is managed through the "vendor" script.
-
-The "vendored" code is usually found in a vendor subdirectory such as `hw/vendor`.
-It should never be modified directly in the OpenTitan repository.
-Instead, modifications should be discussed and implemented upstream and then be vendored into OpenTitan.
-However, when dealing with third-party code, please first raise awareness and discuss inside OpenTitan before creating issues and pull requests upstream.
-
-Further information regarding vendor code can be found in the [Vendor Tool User Guide]({{< relref "doc/ug/vendor_hw.md" >}})
-
-
-# Working with Pull Requests
-
-## What should a PR look like (single/multiple commits)
-
-A pull request (PR) should contain all the code changes required to address an issue, or all the code changes leading to the implementation of a new feature or a major change in the design.
-At the same time, a PR should be as small as possible to simplify and speed up the review process.
-It may thus make sense to break down larger contributions into multiple, self-contained PRs.
-This does not hold for bug fixes: A single PR is sufficient to fix a bug.
-
-Independent of how a contribution is structured, each individual commit should be atomic.
-The code base should remain usable even with only the first commit of a PR, or the first PR of a series of PRs, merged.
-
-Code changes not strictly related to the target feature, major change or issue should go into a separate PR.
-
-In some cases, preparatory code changes may be required before a change is implemented, or a new feature may require changes in a somewhat unrelated part of the code.
-For example, it may be the case that the register interface of an IP must be updated using the latest version of the register tool, before a new register required for the targeted feature can be added.
-Such changes, if small, can be in the same PR, but they should be in a separate commit with a reasonable commit message explaining why this change is necessary.
-
-PRs with more, smaller commits instead of a few large commits simplifies debugging in the case of regressions.
-
-For more detailed information on how to submit a pull request, see our [GitHub notes]({{< relref "doc/ug/github_notes.md" >}}).
-
-## What are committers?
-
-Committers are the only people in the project that can definitively approve contributions for inclusion.
-
-See the [Committers]({{< relref "committers.md" >}}) definition and role description for a fuller explanation.
-
-## Labeling and assigning PRs
-
-Labels can be useful as they help categorizing, prioritizing and assigning open issues/PRs.
-When creating a PR, you are welcome to assign labels if you feel comfortable about selecting suitable labels.
-If no labels are assigned, committers and other team members might do that when browsing through the open PR from time to time.
-If possible, you should also add a meaningful tag to the subject line of your PR as outlined in [subject tags](#subject-tags).
-
-For PRs, it does not make sense to assign people.
-But when creating a PR, you should request a review from committers and team members.
-Some reviewers may be assigned by default depending on the paths of changed files.
-To balance the review load, you are welcome to also request a review from other people familiar in the corresponding area.
-However, you should not request a review from more than 2 to 5 team members, as this increases the review load to unsustainable levels.
-
-## Who should review PRs?
-
-Contributors should request reviews from:
-* people regularly working on the affected parts of the code, and
-* people involved in discussing the solution of the problem addressed by the PR.
-
-However, review requests are not obligations to perform a review.
-It is understood that reviewers may not be able to review every single PR where a review has been requested by them.
-
-## How to give good code reviews?
-
-Code review is not design review and doesn't remove the need for discussing implementation options.
-If you would like to make a large-scale change or discuss multiple implementation options, follow the procedure outlined under [How to Contribute Code](#how-to-contributed-code).
-
-The main purpose of code review is to make sure the code changes appropriately solve the problem the PR intends to address.
-It is thus vital to provide meaningful commit messages and PR descriptions with references to issues where problems and the proposed solutions are outlined.
-
-Code review is a core part of OpenTitan's focus on high quality implementation.
-It helps ensure that:
-* The design follows good practices.
-* The code matches the style guides.
-* The code is appropriately commented.
-* Code changes are reflected in the documentation.
-* The changes are reasonably structured.
-* Meaningful commit messages are provided.
-
-When doing code review, you should check if these guidelines are followed.
-If some of the points are not met, or if you see potential improvements in the modified areas, bring this up in a comment.
-The author of the PR will then address or comment on all feedback.
-It is generally the responsibility of the reviewer who started the discussion to decide whether the feedback has been addressed appropriately.
-If the author and the reviewer cannot find agreement, another committer or team member should be brought into the discussion.
-
-When reviewing code and giving/discussing feedback, keep in mind that:
-* Code review is not design review.
-  If a PR comes without clear motivation or does not follow a course of action previously agreed on, this should be discussed first and separately from the actual code review.
-* This is a collaborative, distributed open-source project bringing lots of people together with different motivations and backgrounds.
-  Some people work full-time on this project and might see the same mistakes repeatedly while others can only contribute in their spare time.
-  This means that many people will see the same issue from different viewpoints.
-  Always be friendly and patient and remember to adhere to our [code of conduct](https://www.lowrisc.org/code-of-conduct/).
-
-See also: [reviewing pull requests on GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/reviewing-proposed-changes-in-a-pull-request) and the [OpenTitan Commit Escalation Guidelines]({{< relref "committers.md#commit-escalation-guidelines" >}}).
-
-## How to receive a code review?
-
-Code review may be an intimidating process, especially if you're new to the open source community.
-Getting lots of comments on even minor style violations can be disheartening.
-Know that your contributions are always appreciated - the reality is that in many cases initial code review is the most scrutiny a given piece of code will get.
-Therefore, it is worth pushing to ensure it meets all coding standards rather than hoping it will be fixed in the future.
-
-## Do we rebase and merge or squash multiple commits into a single one?
-
-Feedback addressed during the review process should go into the corresponding commit in the PR instead of having a single commit containing all changes addressing the feedback.
-Try to keep a clean, linear history.
-This means no merge commits and no long series of "fixup" patches.
-Instead, use rebase to restructure your work as a series of logically ordered, atomic patches (`git rebase -i` helps).
-PRs should not be squashed when merging to keep the individual commits including commit messages making up the PR.
-
-Also see our [GitHub notes]({{< relref "doc/ug/github_notes.md" >}}).
-
-
-## When to merge a PR after it has been approved?
-
-To reduce the risk of accidentally introducing bugs or breaking existing functionality, we leverage continuous integration (CI) checks on every PR in addition to nightly regression tests.
-Depending on the target area of the PR, these automated CI checks can include checking code format and lint rules, checking commit metadata, building and running simulation environments, generating an FPGA bitstream, compilation and execution of smoke tests on an FPGA emulator.
-The results of these CI checks can be viewed through the GitHub web interface (check the conversation pane of the PR of interest).
-
-In case the CI checks do not succeed, it is not okay to self-approve the PR or merge it.
-PRs addressing CI failures themselves may be exempted from this rule.
-However, such PRs are rare and they must always involve senior project committers.
-
-If the CI checks for a PR succeed and if the PR affects larger parts of the code base, it is best to wait 24 hours before merging.
-This allows project contributors spread across their many time zones an opportunity to provide feedback.
-
-## Who should merge the PR once it is approved?
-
-If the author of the PR has commit access, they should merge it once it has been reviewed and approved.
-Otherwise, a committer will need to merge it.
-Normally a committer involved in the review will do this.
-Note that irrespective of who merges the PR, the original authorship of the commit is preserved.
-
-## What to do if a PR gets merged and breaks things?
-
-The `master` branch should always be stable.
-If a PR is merged that causes breakage that is likely to prevent others from making progress (e.g. breaking simulation or CI infrastructure), this failing PR should be [reverted](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/reverting-a-pull-request) unless the required fix is obvious and trivial.
-We see two primary advantages to a quick-revert policy:
-* It brings the master branch back into a stable state.
-  This means it immediately unblocks other people affected by the problems introduced with the failing PR and allows them to continue their work.
-* It removes time pressure for finding the right fix.
-  This allows us to track down and fix the root cause of the failure, including getting proper review.
-  It also prevents the creation of dirty hotfixes, which are possibly incomplete and lead to other failures requiring follow-up patches themselves.
diff --git a/doc/project/development_stages.md b/doc/project/development_stages.md
deleted file mode 100644
index cd203c3..0000000
--- a/doc/project/development_stages.md
+++ /dev/null
@@ -1,308 +0,0 @@
----
-aliases: [/doc/project/hw_stages/]
-title: OpenTitan Hardware Development Stages
----
-
-## Document Goals
-
-This document describes development stages for hardware within the OpenTitan program.
-This includes design and verification stages meant to give a high-level view of the status of a design.
-OpenTitan being an open-source program aimed at a high quality silicon release, the intent is to find a balance between the rigor of a heavy tapeout process and the more fluid workings of an open source development.
-
-This document also serves as a guide to the [Hardware Dashboard]({{< relref "hw" >}}), which gives the status of all of the designs in the OpenTitan repository.
-
-This document aims to mostly give a more defined structure to the process that is already followed.
-Proper versioning of RTL designs is a complex topic.
-This document proposes a sensible path forwards, but this may be revisited as we gain further experience and get more feedback.
-
-
-## Life Stages (L)
-
-The stages listed here are created to give insight into where a design is in its life from specification to silicon-ready sign-off.
-At the moment, this is strictly limited to a hardware design, but could be expanded to other components of development within OpenTitan.
-Transitions between these stages are decided by the Technical Committee via the [RFC process]({{< relref "doc/project/rfc_process" >}}).
-
-The first life stage is **Specification**.
-The proposed design is written up and submitted through the [RFC process]({{< relref "doc/project/rfc_process" >}}).
-Depending on the complexity of the design and the guidance of the Technical Committee, it is possible a single design might require multiple RFCs.
-For example, a first RFC for the rationale, feature list, and a rough overview; followed by a more detailed RFC to get approval for the draft technical specification.
-As part of the specification process, the design author might reach out for feedback from a smaller group of reviewers while formulating an RFC proposal.
-RFCs are always shared with the wider OpenTitan community prior to vote by the Technical Committee.
-However, wherever there is potentially sensitive material from a future certification standpoint it should be passed through the security review team.
-Once the specification has been shared with the OpenTitan audience and sufficient review has been completed, this phase is exited.
-
-The next life stage is **Development**.
-The hardware IP is being developed in GitHub, the specification is converted to Markdown, and design and verification planning is underway.
-This is a long phase expected to last until a more formal review is requested for full completion sign-off.
-When in Development phase, the stage tracking of the design and verification milestones are valid.
-See those sections that follow for details there.
-To exit this stage, a sign-off review must occur.
-See the section on sign-off for details.
-
-The final life stage is **Signed-Off**.
-At this point, a design is frozen and not expected to be updated.
-There are exceptions if post-sign-off bugs are found, in which case the stage returns to Development and the version number is not updated.
-Feature requests towards a signed-off design requires review and approval by the Technical Committee.
-Once accepted, it results in creating a new version and return a design to the appropriate life stage, based upon the size of the change.
-See the _Versioning_ section of the document for more discussion.
-Signed-off fully-functioning (read: not buggy) designs stay in the "Signed-Off" life stage as an available complete IP, with an associated revision ID.
-
-There exists a [template for IP checklists](https://github.com/lowRISC/opentitan/blob/master/util/uvmdvgen/checklist.md.tpl).
-The DIF stages use a separate, [software-specific checklist](https://github.com/lowRISC/opentitan/blob/master/doc/project/sw_checklist.md.tpl).
-All the checklist items are listed in the [Sign-off Checklist]({{< relref "doc/project/checklist.md" >}}).
-
-| **Stage** | **Name** | **Definition** |
-| --- | --- | --- |
-| L0 | Specification | Specification is being written, is in review process |
-| L1 | Development | Design is in development in GitHub, possibly integrated in top level |
-| L2 | Signed-Off | Design has been frozen at version number, signed-off, available for tapeout |
-
-We may later evaluate adding a **Silicon Proven** stage, after deciding criteria for a tapeout to qualify as proven.
-
-
-## Hardware Design Stages (D)
-
-The following development milestones are for hardware peripheral designs, i.e. SystemVerilog RTL development.
-They are similar to typical chip design milestones, but less rigid in the movement from one stage to the next.
-The metric here is the quality bar and feature completeness of the design.
-
-The first design stage is **Initial Work**.
-This indicates the period of time between the Specification life stage and the RTL being functional enough to pass smoke checks.
-The RTL is still in progress, registers being defined, etc.
-Once the device has passed a basic smoke check, has the lint flow setup, has the registers completely defined, it has completed the Initial Work stage.
-
-The second design stage is **Functional**.
-In this stage, the design is functional but not complete.
-Once all of the features in the specification are implemented, it has completed this stage.
-
-The third design stage is **Feature Complete**.
-In this phase, no changes are expected on the design except for bug fixes and security countermeasure implementations.
-Once all of the countermeasures in the specification are implemented, the design moves into: **Security Countermeasures Complete**.
-
-The fourth design stage is **Security Countermeasures Complete**.
-In this phase, no changes are expected on the design except for bug fixes.
-Once all bugs have been fixed, lint and CDC violations cleaned up, the design moves into its final stage: **Design Complete**.
-
-| **Stage** | **Name** | **Definition** |
-| --- | --- | --- |
-| D0  | Initial Work | RTL being developed, not functional |
-| D1  | Functional | <ul> <li> Feature set finalized, spec complete <li> CSRs identified; RTL/DV/SW collateral generated <li> SW interface automation completed <li> Clock(s)/reset(s) connected to all sub modules <li> Lint run setup <li> Ports Frozen </ul> |
-| D2  | Feature Complete | <ul> <li> Full Feature Complete: all features implemented.  <li> Feature frozen </ul> |
-| D2S | Security Countermeasures Complete | <ul> <li> All security countermeasures implemented. </ul> |
-| D3  | Design Complete | <ul> <li> Lint/CDC clean, waivers reviewed <li> Design optimization for power and/or performance complete </ul> |
-
-## Hardware Verification Stages (V)
-
-The following development milestones are for hardware peripheral verification work.
-They are similar to typical chip verification milestones, but less rigid in the movement from one stage to the next.
-The metric here is the progress towards testing completion and proof of testing coverage.
-The verification stages can be applied to simulation-based DV and formal property verification (FPV) approaches.
-
-The first verification stage is **Initial Work**.
-This indicates the period of time between the beginning of verification planning and the testbench up and running.
-The testbench is still being created, scoreboards implemented, DV document and testplan being written, nightly regressions running, etc.
-Once the verification environment is available for writing tests, with a testplan written including a testplan and a functional coverage plan, it has completed the Initial Work stage.
-
-The second verification stage is **Under Test**.
-In this stage, the verification environment is available but not all tests in the testplan are are completed and the coverpoints are not implemented.
-Once all of the items in the testplan are implemented, it exits this stage.
-
-The third verification stage is **Testing Complete**.
-In this phase, no changes are expected on the testplan, no changes expected on the testbench, and no new tests are expected except to verify security countermeasures and to close coverage on the design.
-Once all coverage metrics have been met, waivers checked, the verification moves into: **Security Countermeasures Verified**.
-
-The fourth verification stage is **Security Countermeasures Verified**.
-In this phase, no changes are expected on the testplan, no changes expected on the testbench, and no new tests are expected except to close coverage on the design.
-Once all coverage metrics have been met, waivers checked, the verification moves into its final stage: **Verification Complete**.
-
-**Stages for simulation-based DV**:
-
-| **Stage** | **Name** | **Definition** |
-| --- | --- | --- |
-| V0 | Initial Work | Testbench being developed, not functional; testplan being written; decided which methodology to use (sim-based DV, FPV, or both). |
-| V1 | Under Test | <ul> <li> Documentation: <ul> <li> [DV document]({{< relref "doc/ug/dv_methodology#documentation" >}}) available, <li> [Testplan]({{< relref "doc/ug/dv_methodology#documentation" >}}) completed and reviewed </ul> <li> Testbench: <ul> <li> DUT instantiated with major interfaces hooked up <li> All available interface assertion monitors hooked up <li> X / unknown checks on DUT outputs added <li> Skeleton environment created with UVCs <li> TLM connections made from interface monitors to the scoreboard </ul> <li> Tests (written and passing): <ul> <li> Sanity test accessing basic functionality <li> CSR / mem test suite </ul> <li> Regressions: Sanity and nightly regression set up</ul> |
-| V2 | Testing Complete | <ul> <li> Documentation: <ul> <li> DV document completely written </ul> <li> Design Issues: <ul> <li> all high priority bugs addressed <li> low priority bugs root-caused </ul> <li> Testbench: <ul> <li> all interfaces hooked up and exercised <li> all assertions written and enabled </ul> <li> UVM environment: fully developed with end-to-end checks in scoreboard <li> Tests (written and passing): all tests planned for in the testplan  <li> Functional coverage (written): all covergroups planned for in the testplan <li> Regression: all tests passing in nightly regression with multiple seeds (> 90%)  <li> Coverage: 90% code coverage across the board, 100% functional coverpoints covered and 75% crosses covered</ul></ul> |
-| V2S | Security Countermeasures Verified | <ul> <li> Tests (written and passing): all tests for security countermeasures (if any) </ul> |
-| V3 | Verification Complete | <ul> <li> Design Issues: all bugs addressed <li> Tests (written and passing): all tests including newly added post-V2 tests (if any) <li> Regression: all tests with all seeds passing <li> Coverage: 100% code and 100% functional coverage with waivers </ul> </ul> |
-
-**Stages for FPV approaches**:
-
-| **Stage** | **Name** | **Definition** |
-| --- | --- | --- |
-| V0 | Initial Work | Testbench being developed, not functional; testplan being written; decided which methodology to use (sim-based DV, FPV, or both). |
-| V1 | Under Test | <ul> <li> Documentation: <ul> <li> [DV document]({{< relref "doc/ug/dv_methodology#documentation" >}}) available, [Testplan]({{< relref "doc/ug/dv_methodology#documentation" >}}) completed and reviewed </ul> <li> Testbench: <ul> <li> Formal testbench with DUT bound to assertion module(s) <li> All available interface assertion monitors hooked up <li> X / unknown assertions on DUT outputs added </ul> <li> Assertions (written and proven): <ul> <li> All functional properties identified and described in testplan <li> Assertions for main functional path implemented and passing (smoke check)<li> Each input and each output is part of at least one assertion</ul> <li> Regressions: Sanity and nightly regression set up</ul> |
-| V2 | Testing Complete | <ul> <li> Documentation: <ul> <li> DV document completely written </ul> <li> Design Issues: <ul> <li> all high priority bugs addressed <li> low priority bugs root-caused </ul> <li> Testbench: <ul> <li> all interfaces have assertions checking the protocol <li> all functional assertions written and enabled <li> assumptions for FPV specified and reviewed </ul> <li> Tests (written and passing): all tests planned for in the testplan <li> Regression: 90% of properties proven in nightly regression <li> Coverage: 90% code coverage and 75% logic cone of influence (COI) coverage </ul> |
-| V2S | Security Countermeasures Verified | <ul> <li> Tests (written and passing): all tests for security countermeasures (if any) </ul> |
-| V3 | Verification Complete | <ul> <li> Design Issues: all bugs addressed <li> Assertions (written and proven): all assertions including newly added post-V2 assertions (if any) <li> Regression: 100% of properties proven (with reviewed assumptions) <li> Coverage: 100% code coverage and 100% COI coverage</ul> |
-
-## Device Interface Function Stages (S)
-
-The following development stages are for [Device Interface Function (DIF)]({{< relref "doc/rm/device_interface_functions.md" >}}) work.
-These milestones have a slightly different emphasis to the hardware design and verification milestones, because software is much easier to change if bugs are found.
-The metric they are trying to capture is the stability and completeness of a low-level software interface to hardware design.
-We are aiming to keep this process fairly lightweight in the early stages, and not significantly burdeonsome to the associated HW designer through all stages.
-
-There are explicit checkpoints in these stages to ensure that DIF development does not overtake design and verification.
-
-The first DIF stage is **Initial Work**.
-This indicates the period of time between starting the software API, and it being complete enough for other software to start using it.
-The exact API is still being defined.
-Once the DIF is complete enough to cover all existing in-tree uses of the device, and has mock tests, it has completed the Initial Work stage.
-
-The second stage is **Functional**.
-In this stage, the DIF can be used for basic operations, but may not cover all the specified functionality of the device.
-Once the DIF is complete enough to cover all the functionality of the device, in the way the hardware designer envisioned, and is used for DV testing, it has completed this stage.
-
-The third stage is **Complete**.
-In this stage, no changes to the interface are expected.
-Once testing is complete, and we are satisfied that the interface will not change (except for bug fixes), the DIF moves into its final stage: **Stable**.
-
-| **Stage** | **Name** | **Definition** |
-| --- | --- | --- |
-| S0 | Initial Work | Work has started on a DIF for the given IP block. |
-| S1 | Functional | <ul> <li> DIF has been reviewed and merged <li> DIF is used by all appropriate non-production code in the tree <li> DIF has (mocked) unit tests <li> DIF has smoke test </ul> |
-| S2 | Complete | <ul> <li> DIF API is now Complete <li> The respective IP block is feature complete (at least D2) <li> DIF matches HW designer's agreed IP block usage <li> DIF covers all specified functionality of the IP block <li> DIF is used for chip-level DV <li> DIF documented in IP documentation </ul> |
-| S3 | Stable | <ul> <li> DIF API Reviewed and Stable <li> The respective IP block is at D3/V3 <li> DIF tested fully (DV + Unit tests, full functional coverage) </ul> |
-
-## Sign-off Review
-
-At the end of the final design and verification phase, the IP block should be proposed to the Technical Committee as ready for sign-off.
-This will be done by submitting an RFC (possibly following a suggested template).
-The Technical Committee may decide to nominate individuals to evaluate the codebase and make a recommendation or may review directly.
-Generally, this process would involve:
-
-*   Specification and RTL are re-reviewed for readability, consistency, and code coverage standards
-*   All design items are complete, reviewed against committed specification
-*   All lint and CDC errors and warnings are waived, waivers reviewed
-*   Testplan is re-reviewed for completeness
-*   All test items are confirmed complete
-*   All code coverage items are completed or waived
-*   Performance requirements are reviewed, performance metrics met
-*   Software interface (DIF) files are completed, tested, and signed off by software representative
-
-The process will be refined by the Technical Committee as necessary.
-
-## Indicating Stages and Making Transitions
-
-Stages are indicated via a text file checked into the GitHub and thus transitions can be reviewed through the standard pull request process.
-Transitions for Design and Verification stages are _self-nominated_ in the sense that the design or verification owner can modify the text file and submit a pull request (PR) to transition the stage.
-In this manner other reviewers can challenge the transition in the standard pull request review process.
-These transitions should be done in their own PR (i.e. not interspersed with other changes), and the PR summary and commit message should give any necessary detail on how the transition criteria have been met, as well as any other notes useful for a reviewer.
-
-The content below shows the format of the project file that contains the stage information.
-The file for a design named `name` should be placed under `hw/ip/name/data/name.prj.hjson`.
-For example, `file: gpio.prj.hjson`:
-
-```hjson
-{
-    name:               "gpio"
-    version:            "1.0"
-    life_stage:         "L1"
-    design_stage:       "D2"
-    verification_stage: "V1"
-    dif_stage:          "S0"
-    notes:              "information shown on the dashboard"
-}
-```
-
-### Commit ID
-
-When a design transitions from one stage to another, the project file can optionally provide a commit ID for the transition to be able to recreate the repository at the point of that transition.
-This is optional for all transitions except for sign-off, where it is required.
-The commit ID has its own entry in the project Hjson file, as shown below.
-
-```hjson
-{
-    name:               "gpio"
-    version:            "1.0"
-    life_stage:         "L1"
-    design_stage:       "D2"
-    verification_stage: "V1"
-    dif_stage:          "S0"
-    commit_id:          "92e4298f8c2de268b2420a2c16939cd0784f1bf8"
-    notes:              "information shown on the dashboard"
-}
-```
-
-### Other optional fields
-
-Additionally, the tool that generates the dashboard accepts the following optional fields: the design specification, the DV document and the checklist.
-They are set as partial paths (reference relative to the top of the repository) to the respective documents as shown below.
-They are converted to complete URLs in the generated dashboard.
-
-```hjson
-{
-    design_spec:  "hw/ip/gpio/doc"
-    dv_doc:      "hw/ip/gpio/doc/dv"
-    hw_checklist: "hw/ip/gpio/doc/checklist"
-    sw_checklist: "sw/device/lib/dif/dif_gpio"
-}
-```
-
-## Versioning
-
-The _Version_ of a design element indicates its progress towards its _final feature set for expected product_.
-Typically all designs are expected to simply be in 1.0 version, but there are reasons for exceptions.
-Designs which have a specification that defines an _intermediate goal_ are indicated as < 1.0 version.
-There are many times where this is useful: when the intermediate goal is a beneficial subset of functionality to enable other development; when the final feature set is not known but a sufficient set is ready for development; when the final feature set is postponed until a future date, but owners are keen to get the design started; etc.
-In essence, the sub-1.0 designation indicates that it is understood that the stage metrics are temporary pending a final feature set.
-Rarely will a sub-1.0 design be taken past Feature Complete and Testing Complete stages.
-An exception is a proof of concept to show what a sign-off process looks like for a design that has modifications expected in the future.
-This was the case with public launch, where we took five designs to completion to test out the sign-off process, the verification methodology, and the checklist system.
-In several of these cases, the feature set was not final product complete.
-
-Once a design has completed all stages for a product feature set, the sign-off process intends to end all development for that design.
-Its Life Stage should transition to sign-off after the review process, and no more modifications should be made.
-The commit ID of the version that was signed off is recorded in the `commit_id` field of the `.prj.hjson` file.
-
-After sign-off, three events could cause a change.
-
-* Possibility 1: If a bug is found in top-level testing, software development, etc., the design should stay in its current revision (assumedly 1.0) but revert in design and/or verification staging until the bug is fixed and a new sign-off process occurs, followed by a new tag to replace the previous one.
-* Possibility 2: if a small collection of new features are requested, a new version increment of 0.1 would be created, and the design and verification stages would be reset.
-The expectation is that this would create its life as a newly tracked revision number, while the previous (assumedly 1.0) version retains its status.
-* Possibility 3: if enough new features are requested to greatly change the spirit of the design, a new version increment of 1.0 would be created in a fashion similar to above.
-This would require a new RFC process, and thus the Life Stage would start again as L0 - Specification.
-
-### Multiple versions of a design
-
-Over the course of the project, designs may *regress* from signed-off to an earlier stage when new features are added.
-When regressing a signed-off design, the old signed-off version should remain in the project file as a retrievable version.
-This is indicated as two versions as shown in this example.
-
-```hjson
-{
-    name:                   "uart",
-    revisions: [
-      {
-        version:            "1.0",
-        life_stage:         "L2",
-        design_stage:       "D3",
-        verification_stage: "V3",
-        dif_stage:          "S0",
-        commit_id:          "92e4298f8c2de268b2420a2c16939cd0784f1bf8",
-        notes:              ""
-      }
-      {
-        version:            "1.1",
-        life_stage:         "L1",
-        design_stage:       "D2",
-        verification_stage: "V2",
-        dif_stage:          "S0",
-        commit_id:          "f3039d7006ca8ebd45ae0b52b22864983876175d",
-        notes:              "Rolled back to D2 as the register module is updated",
-      }
-    ]
-}
-```
-
-One may choose to commemorate a non-signed-off version of a design if it reached enough maturity to be a useful version to checkpoint before regressing.
-In this case the version number should also be incremented.
-
-No hard rules for version numbering are mandated at this stage.
-The subject will be revisited as we get closer to locking down the design to take it to a silicon implementation milestone.
-
-## Reporting of Stages
-
-The stages are reported externally via a script-generated table exposed on the external website.
-This status is a summary of all `prj.hjson` files of all designs in the system, with multiple lines where there are multiple versions.
-The link to that table is [here]({{< relref "hw" >}}).
diff --git a/doc/project/hw_checklist.md.tpl b/doc/project/hw_checklist.md.tpl
deleted file mode 120000
index b6b3415..0000000
--- a/doc/project/hw_checklist.md.tpl
+++ /dev/null
@@ -1 +0,0 @@
-../../util/uvmdvgen/checklist.md.tpl
\ No newline at end of file
diff --git a/doc/project/rfc_process.md b/doc/project/rfc_process.md
deleted file mode 100644
index 2a6c777..0000000
--- a/doc/project/rfc_process.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: OpenTitan RFC Process
----
-
-## Introduction
-
-The OpenTitan RFC process is the mechanism by which technical proposals can be reviewed and voted upon by the Technical Committee (TC).
-More generally, the RFC process ensures changes are appropriately reviewed, gives the contributor community opportunity to give feedback, and helps ensure alternative implementation options are also evaluated.
-Many other open source projects implement such a process with varying degrees of formality, e.g. Python Enhancement Proposals, RFCs in the LLVM community, and more.
-
-RFCs can't be used for resource allocation or to "force" other people to do some work.
-E.g. "Implement IP block X" is not a useful RFC unless either the proposer is willing to do that work or someone else is interested in pursuing it.
-
-This document outlines the basic structure of the RFC process, with the expectation that it will evolve over time based on the experience of active contributors.
-
-## RFC lifetime
-
-* [Optional: Seek feedback on whether an RFC is desired.
-  This is recommended for a lengthy "out of the blue" proposal, to ensure time isn't wasted writing up a detailed proposal for something that is out of scope or that can't be decided upon at this stage.]
-* An author (or set of authors) writes an RFC.
-  This might be triggered simply by an idea, by a discussion in a meeting, or by a request from a Committer after submitting a PR.
-  The TC will maintain examples of high quality RFCs to refer to in terms of structure and approach.
-* The RFC authors may solicit early feedback while preparing an RFC, possibly before sharing publicly.
-   * If requested, the TC could help to nominate a small group to shepherd the RFC.
-* If the RFC potentially contains information that could be certification-sensitive (guidance to be shared), send a note to security@opentitan.org first for feedback.
-* The RFC is shared publicly by filing a GitHub issue and tagging with the `RFC:Proposal` label.
-* Once the author is happy that the RFC is complete, they submit it to the Technical Committee by converting the label to `RFC:TC Review`.
-* The Technical Committee will consider active RFCs in each meeting (those that have been marked with `RFC:TC Review` for at least a week).
-  If an RFC saw large changes in the week it has been "ready" the TC may postpone judgement in order to allow more comment.
-  They will decide whether to:
-   * **Accept**: Indicate they are happy for this proposal to be implemented.
-     May be conditional on certain changes.
-     The TC will change the label from `RFC:TC Review` to `RFC:Approved` in this outcome.
-   * **Reject**: Decided against the proposal at this time.
-     The label `RFC:TC Review` will be removed by the TC.
-   * **Postpone**: The proposal seemed to have the right level of detail and form, but the project won't be making a decision on it at this time (e.g. refers to work that is scheduled too far in the future).
-     The TC will revert the label from `RFC:TC Review` back to `RFC:Proposal`.
-     In the case where the TC has not had sufficient time to complete review during a meeting, the label `RFC:TC Review` will remain in place until the review has completed.
-   * **Request revision**: The TC feel they could make an accept/reject decision if certain changes were made (e.g. fleshing out a description of alternate approaches, considering changes to the approach etc).
-     The TC will revert the label from `RFC:TC Review` back to `RFC:Proposal` until revisions have been completed.
-   * **Abstain with comment**: The TC feel that the issue does not require TC arbitration.
-     The TC may leave comment suggesting the next action for the RFC, perhaps indicating where the issue should be resolved, such as in one of the other committees.
-     The TC will remove the `RFC:TC Review` label in this case.
-
-RFCs should be created at least for any major new piece of code (e.g. IP block), or cross-cutting changes that need careful consideration.
-Generally speaking, an RFC should be created when there is demand for one (especially when that request comes from a Committer).
-Although it is a power that is not expected to be deployed regularly, RFCs may be unilaterally approved/rejected by the Project Director.
-Even in these cases, they would likely still follow the normal review and feedback process.
-
-## Potential future RFC process refinements
-
-The process has been kept as simple as possible to start with, closely mimicking the informal consensus approach being used in the early stages of the OpenTitan project.
-However, there are a range of potential improvements that it may make sense for the Technical Committee and wider contributor community to explore:
-
-* We currently have no particular rule on RFC format and structure.
-  It may be useful to at least suggest a structure for certain categories of changes (e.g. outline of headings).
-* [Rust RFCs](https://rust-lang.github.io/rfcs/) and the [Kotlin Evolution and Enhancement Process](https://github.com/Kotlin/KEEP) both introduce the idea of a "Shepherd", which we may want to adopt.
-* If the volume of RFCs increased to the point it was too time consuming for the Technical Committee to handle each review request, the TC may delegate some responsibility for pre-filtering.
-  E.g. the Chair/Vice-Chair may make a determination on whether an RFC has attracted sufficient support for further discussion.
diff --git a/doc/project/sw_checklist.md.tpl b/doc/project/sw_checklist.md.tpl
deleted file mode 100644
index 605cbd0..0000000
--- a/doc/project/sw_checklist.md.tpl
+++ /dev/null
@@ -1,64 +0,0 @@
----
-title: "${ip.name_long_upper} DIF Checklist"
----
-
-<%doc>
-    This templates requires the following Python objects to be passed:
-
-    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
-</%doc>
-
-<!--
-NOTE: This is a template checklist document that is required to be copied over
-to `sw/device/lib/dif/dif_${ip.name_snake}.md` for a new DIF that transitions
-from L0 (Specification) to L1 (Development) stage, and updated as needed.
-Once done, please remove this comment before checking it in.
--->
-This checklist is for [Development Stage]({{< relref "/doc/project/development_stages.md" >}}) transitions for the [${ip.name_long_upper} DIF]({{< relref "hw/ip/${ip.name_snake}/doc" >}}).
-All checklist items refer to the content in the [Checklist]({{< relref "/doc/project/checklist.md" >}}).
-
-<h2>DIF Checklist</h2>
-
-<h3>S1</h3>
-
-Type           | Item                 | Resolution  | Note/Collaterals
----------------|----------------------|-------------|------------------
-Implementation | [DIF_EXISTS][]       | Not Started |
-Implementation | [DIF_USED_IN_TREE][] | Not Started |
-Tests          | [DIF_TEST_SMOKE][]   | Not Started |
-
-[DIF_EXISTS]:       {{< relref "/doc/project/checklist.md#dif_exists" >}}
-[DIF_USED_IN_TREE]: {{< relref "/doc/project/checklist.md#dif_used_in_tree" >}}
-[DIF_TEST_SMOKE]:   {{< relref "/doc/project/checklist.md#dif_test_smoke" >}}
-
-<h3>S2</h3>
-
-Type           | Item                        | Resolution  | Note/Collaterals
----------------|-----------------------------|-------------|------------------
-Coordination   | [DIF_HW_FEATURE_COMPLETE][] | Not Started | [HW Dashboard]({{< relref "hw" >}})
-Implementation | [DIF_FEATURES][]            | Not Started |
-Coordination   | [DIF_DV_TESTS][]            | Not Started |
-
-[DIF_HW_FEATURE_COMPLETE]: {{< relref "/doc/project/checklist.md#dif_hw_feature_complete" >}}
-[DIF_FEATURES]:            {{< relref "/doc/project/checklist.md#dif_features" >}}
-[DIF_DV_TESTS]:            {{< relref "/doc/project/checklist.md#dif_dv_tests" >}}
-
-<h3>S3</h3>
-
-Type           | Item                             | Resolution  | Note/Collaterals
----------------|----------------------------------|-------------|------------------
-Coordination   | [DIF_HW_DESIGN_COMPLETE][]       | Not Started |
-Coordination   | [DIF_HW_VERIFICATION_COMPLETE][] | Not Started |
-Documentation  | [DIF_DOC_HW][]                   | Not Started |
-Code Quality   | [DIF_CODE_STYLE][]               | Not Started |
-Tests          | [DIF_TEST_UNIT][]                | Not Started |
-Review         | [DIF_TODO_COMPLETE][]            | Not Started |
-Review         | Reviewer(s)                      | Not Started |
-Review         | Signoff date                     | Not Started |
-
-[DIF_HW_DESIGN_COMPLETE]:       {{< relref "/doc/project/checklist.md#dif_hw_design_complete" >}}
-[DIF_HW_VERIFICATION_COMPLETE]: {{< relref "/doc/project/checklist.md#dif_hw_verification_complete" >}}
-[DIF_DOC_HW]:                   {{< relref "/doc/project/checklist.md#dif_doc_hw" >}}
-[DIF_CODE_STYLE]:               {{< relref "/doc/project/checklist.md#dif_code_style" >}}
-[DIF_TEST_UNIT]:                {{< relref "/doc/project/checklist.md#dif_test_unit" >}}
-[DIF_TODO_COMPLETE]:            {{< relref "/doc/project/checklist.md#dif_todo_complete" >}}
diff --git a/doc/project/technical_committee.md b/doc/project/technical_committee.md
deleted file mode 100644
index e858dc3..0000000
--- a/doc/project/technical_committee.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-title: "OpenTitan Technical Committee"
----
-
-The OpenTitan Technical Committee is part of the [OpenTitan governance structure]({{< relref "doc/project" >}}) and the definitive description of its operations and responsibilities are stated in the [OpenTitan Technical Charter](https://static.opentitan.org/technical-charter.pdf).
-Key responsibilities defined in that document include:
-* Oversight of the [RFC process]({{< relref "rfc_process" >}}), including voting to approve, reject, postpone or revise RFCs.
-* Creating [Commit Escalation Guidelines]({{< relref "committers" >}}) setting out when Committers should escalate a particular Commit to the TC (e.g. for cross-cutting changes), and when Committers should recommend a design rationale write-up or RFC for wider feedback.
-* Maintaining the list of [committers]({{< relref "committers" >}}).
-
-Expectations of Technical Committee members include the following:
-* Be active participants in development discussions within the OpenTitan project, beyond just Technical Committee meetings.
-* Consider comments and feedback that have been made on proposals during the project-wide review process and ensure any concerns are adequately considered and addressed.
-* Ensure that feedback has been sought from all relevant stakeholders, and appropriate effort has been made to find consensus within the OpenTitan contributor community.
-* Put aside sufficient time on a week-by-week basis to active proposals and give feedback.
-* Act in the best interests of the OpenTitan project.
-* Collaborate to deliver on the responsibilities of the Technical Committee (see above).
-* Proactively seek to identify contributors who should be nominated for committer status.
-
-## Membership
-The OpenTitan Technical Committee membership is:
-* Michael Schaffner (chair)
-* Richard Bohn
-* Cyril Guyot
-* Felix Miller
-* Dominic Rizzo (observer)
-* Alphan Ulusoy
-* Michael Munday
-* Rupert Swarbrick
-* Michael Tempelmeier
-* Neeraj Upasani
-* Nir Tasher
-* Arnon Sharlin
-* Sivakumar
diff --git a/doc/project_governance/README.md b/doc/project_governance/README.md
new file mode 100644
index 0000000..5c0ac23
--- /dev/null
+++ b/doc/project_governance/README.md
@@ -0,0 +1,37 @@
+# Introduction to the OpenTitan Project
+
+OpenTitan is a collaborative hardware and software development program with contributors from many organizations.
+This area gives some more information about how the project itself is organized and how to contribute.
+More information will be added over time.
+
+## Quality standards for open hardware IP
+
+In order to gauge the quality of the different IP that is in our repository, we define a series of [Hardware Development Stages](./development_stages.md) to track the designs.
+The current status of different IP is reflected in the [Hardware Dashboard](../../hw/README.md).
+The final state for developed IP is *Signed Off*, indicating that design and verification is complete, and the IP should be bug free.
+To make it to that stage, a [Hardware Signoff Checklist](./checklist/README.md) is used to confirm completion.
+[Here](https://github.com/lowRISC/opentitan/blob/master/util/uvmdvgen/checklist.md.tpl) is a template that can be used as a checklist item.
+
+## Contributing
+
+See our documentation on [Contributing to OpenTitan](../contributing/README.md) (and the more in-depth [Detailed Contribution Guide](../contributing/detailed_contribution_guide/README.md)) for general guidance on how we work, making bug reports, dealing with security issues, and contributing code.
+
+## Governance
+
+OpenTitan is stewarded by lowRISC CIC, a not-for-profit company that uses collaborative engineering to develop and maintain open source silicon designs and tools for the long term.
+As a lowRISC CIC Chartered Project, OpenTitan governance is handled via lowRISC's default Technical Charter.
+
+As described in full detail in the [OpenTitan Technical Charter](https://static.opentitan.org/technical-charter.pdf), our governance structure consists of:
+* The Project Director, Dominic Rizzo, who is a representative of the lowRISC CIC's Board of Directors within the Steering Committee.
+* The Steering Committee, responsible for project oversight and agreeing the technical roadmap.
+* The [Technical Committee](./technical_committee.md), responsible for technical decision making required to implement the technical roadmap.
+
+## Initiating new development
+
+The [OpenTitan RFC process](./rfc_process.md) guides developers on how to initiate new development within the program.
+
+## Committers
+
+Committers are individuals with repository write access.
+Everyone is able and encouraged to contribute and to help with code review, but committers are responsible for the final approval and merge of contributions.
+See the [Committers](./committers.md) definition and role description for more information.
diff --git a/doc/project_governance/checklist/README.md b/doc/project_governance/checklist/README.md
new file mode 100644
index 0000000..a45a9ba
--- /dev/null
+++ b/doc/project_governance/checklist/README.md
@@ -0,0 +1,686 @@
+# Signoff Checklist
+
+This document explains the recommended checklist items to review when transitioning from one [Development Stage](../development_stages.md) to another, for design, verification, and [software device interface function (DIF)](../../contributing/sw/device_interface_functions.md) stages.
+It is expected that the items in each stage (D1, V1, S1, etc) are completed.
+
+## D1
+
+For a transition from D0 to D1, the following items are expected to be completed.
+
+### SPEC_COMPLETE
+
+The specification is 90% complete, all features are defined.
+The specification is submitted into the repository as a markdown document.
+It is acceptable to make changes for further clarification or more details after the D1 stage.
+
+### CSR_DEFINED
+
+The CSRs required to implement the primary programming model are defined.
+The Hjson file defining the CSRs is checked into the repository.
+It is acceptable to add or modify registers during the D2 stage in order to complete implementation.
+
+### CLKRST_CONNECTED
+
+Clock(s) and reset(s) are connected to all sub-modules.
+
+### IP_TOP
+
+The unit `.sv` exists and meets [comportability](../../contributing/hw/comportability/README.md) requirements.
+
+### IP_INSTANTIABLE
+
+The unit is able to be instantiated and connected in top level RTL files.
+The design must compile and elaborate cleanly without errors.
+The unit must not break top level functionality such as propagating X through TL-UL interfaces, continuously asserting alerts or interrupts, or creating undesired TL-UL transactions.
+To that end, the unit must fulfill the following V1 checklist requirements:
+- TB_TOP_CREATED
+- SIM_RAL_MODEL_GEN_AUTOMATED
+- CSR_CHECK_GEN_AUTOMATED
+- SIM_CSR_MEM_TEST_SUITE_PASSING
+
+### PHYSICAL_MACROS_DEFINED_80
+
+All expected memories have been identified and representative macros instantiated.
+All other physical elements (analog components, pads, etc) are identified and represented with a behavioral model.
+It is acceptable to make changes to these physical macros after the D1 stage as long as they do not have a large impact on the expected resulting area (roughly "80% accurate").
+
+### FUNC_IMPLEMENTED
+
+The mainline functional path is implemented to allow for a basic functionality test by verification.
+("Feature complete" is the target for D2 status.)
+
+### ASSERT_KNOWN_ADDED
+
+All the outputs of the IP have `ASSERT_KNOWN` assertions.
+
+### LINT_SETUP
+
+A lint flow is set up which compiles and runs.
+It is acceptable to have lint warnings at this stage.
+
+## D2
+
+### NEW_FEATURES
+
+Any new features added since D1 are documented and reviewed with DV/SW/FPGA.
+The GitHub Issue, Pull Request, or RFC where the feature was discussed should be linked in the `Notes` section.
+
+### BLOCK_DIAGRAM
+
+Block diagrams have been updated to reflect the current design.
+
+### DOC_INTERFACE
+
+All IP block interfaces that are not autogenerated are documented.
+
+### DOC_INTEGRATION_GUIDE
+
+Any integration specifics that are not captured by the [comportability](../../contributing/hw/comportability/README.md) specification have been documented.
+Examples include special synthesis constraints or clock requirements for this IP.
+
+### MISSING_FUNC
+
+Any missing functionality is documented.
+
+### FEATURE_FROZEN
+
+Feature requests for this IP version are frozen at this time.
+
+### FEATURE_COMPLETE
+
+All features specified are implemented.
+
+### PORT_FROZEN
+
+All ports are implemented and their specification is frozen, except for security / countermeasure related ports that will not affect functionality or architectural behavior (the addition of such ports can be delayed only until D2S).
+
+### ARCHITECTURE_FROZEN
+
+All architectural state (RAMs, CSRs, etc) is implemented and the specification frozen.
+
+### REVIEW_TODO
+
+All TODOs have been reviewed and signed off.
+
+### STYLE_X
+
+The IP block conforms to the [style guide regarding X usage](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md#dont-cares-xs).
+
+### CDC_SYNCMACRO
+
+All CDC synchronization flops use behavioral synchronization macros (e.g. `prim_flop_2sync`) not manually created flops.
+
+### LINT_PASS
+
+The lint flow passes cleanly.
+Any lint waiver files have been reviewed.
+
+### CDC_SETUP
+
+A CDC checking run has been set up (if tooling is available).
+The CDC checking run shows no must-fix errors, and a waiver file has been created.
+
+If there is no CDC flow at the block-level, this requirement can be waived.
+
+### RDC_SETUP
+
+An RDC checking run has been set up (if tooling is available).
+The RDC checking run shows no must-fix errors, and a waiver file has been created.
+
+If there is no RDC flow at the block-level, this requirement can be waived.
+
+### AREA_CHECK
+
+An area check has been completed either with an FPGA or ASIC synthesis flow.
+
+### TIMING_CHECK
+
+A timing check has been completed either with an FPGA or ASIC synthesis flow.
+
+### SEC_CM_DOCUMENTED
+
+Any custom security countermeasures other than standardized countermeasures listed under [SEC_CM_IMPLEMENTED](#sec_cm_implemented)  have been identified, documented and their implementation has been planned.
+The actual implementation can be delayed until D2S.
+
+Where the area impact of countermeasures can be reliably estimated, it is recommended to insert dummy logic at D2 in order to better reflect the final area complexity of the design.
+
+## D2S
+
+### SEC_CM_ASSETS_LISTED
+
+List the assets and corresponding countermeasures in canonical format in the IP Hjson (the canonical naming is checked by the reggen tool for correctness).
+
+This list could look, for example, as follows:
+```python
+# Inside the rstmgr.hjson
+
+countermeasures: [
+  { name: "BUS.INTEGRITY",
+    desc: "Bus integrity check."
+  },
+  { name: "RST.INTEGRITY",
+    desc: "Reset integrity checks."
+  },
+  { name: "RST.SHADOW_LOGIC",
+    desc: "Shadow reset logic."
+  }
+  # ...
+]
+
+```
+
+For a full list of permitted asset and countermeasure types, see the [countermeasure.py](https://github.com/lowRISC/opentitan/blob/master/util/reggen/countermeasure.py) script that implements the name checks.
+
+Note the SEC_CM_DOCUMENTED item in the D2 checklist, which is a precursor to this step.
+
+### SEC_CM_IMPLEMENTED
+
+Any appropriate security counter-measures are implemented.
+Implementations must follow the [OpenTitan Secure Hardware Design Guidelines](../../security/implementation_guidelines/hardware/README.md).
+
+In particular, note that:
+
+- For duplicated counters [`prim_count`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_count.sv) must be used.
+- For duplicated LFSRs [`prim_double_lfsr`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_double_lfsr.sv) must be used.
+- For redundantly encoded FSMs, [the sparse-fsm-encode.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py) must be used to generate the encoding (in conjunction with the [`PRIM_FLOP_SPARSE_FSM`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_flop_macros.sv)) macro.
+- For multibit signals, the `mubi` types in [`prim_mubi_pkg`](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_mubi_pkg.sv) should be used if possible.
+
+### SEC_CM_RND_CNST
+
+Compile-time random netlist constants (such as LFSR seeds or scrambling constants) are exposed to topgen via the `randtype` parameter mechanism in the comportable IP Hjson file.
+Default random seeds and permutations for LFSRs can be generated with [the gen-lfsr-seed.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/gen-lfsr-seed.py).
+See also the related [GitHub issue #2229](https://github.com/lowRISC/opentitan/issues/2229).
+
+### SEC_CM_NON_RESET_FLOPS
+
+A review of sensitive security-critical storage flops was completed.
+Where appropriate, non-reset flops are used to store secure material.
+
+### SEC_CM_SHADOW_REGS
+
+Shadow registers are implemented for all appropriate storage of critical control functions.
+
+### SEC_CM_RTL_REVIEWED
+
+If deemed necessary by the security council, an offline review of the RTL code sections pertaining to the assets and countermeasures listed in SEC_CM_ASSETS_LISTED has been performed.
+
+### SEC_CM_COUNCIL_REVIEWED
+
+Security council has reviewed the asset list and associated documentation (SEC_CM_ASSETS_LISTED, SEC_CM_DOCUMENTED) and deems the defenses implemented appropriate.
+
+The security council decides whether an additional RTL review of the relevant code sections is necessary (SEC_CM_RTL_REVIEWED).
+
+## D3
+
+### NEW_FEATURES_D3
+
+Any approved new features since D2 have been documented and reviewed with DV/SW/FPGA
+
+### TODO_COMPLETE
+
+All TODOs are resolved.
+Deferred TODOs have been marked as "iceboxed" in the code and have been attached an issue as follows: `// ICEBOX(#issue-nr)`.
+
+### LINT_COMPLETE
+
+The lint checking flow is clean.
+Any lint waiver files have been reviewed and signed off by the technical steering committee.
+
+### CDC_COMPLETE
+
+The CDC checking flow is clean.
+CDC waiver files have been reviewed and understood.
+If there is no CDC flow at the block-level, this requirement can be waived.
+
+### RDC_COMPLETE
+
+The RDC checking flow is clean.
+RDC waiver files have been reviewed and understood.
+If there is no RDC flow at the block-level, this requirement can be waived.
+
+### REVIEW_RTL
+
+A simple design review has been conducted by an independent designer.
+
+### REVIEW_DELETED_FF
+
+Any deleted flops have been reviewed and signed off.
+If there is no synthesis flow at the block-level, this requirement can be waived.
+
+### REVIEW_SW_CHANGE
+
+Any software-visible design changes have been reviewed by the software team.
+
+### REVIEW_SW_ERRATA
+
+All known "Won't Fix" bugs and "Errata" have been reviewed by the software team.
+
+## V1
+
+To transition from V0 to V1, the following items are expected to be completed.
+The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
+
+### DV_DOC_DRAFT_COMPLETED
+
+A DV document has been drafted, indicating the overall DV goals, strategy, the testbench environment details with diagram(s) depicting the flow of data, UVCs, checkers, scoreboard, interfaces, assertions and the rationale for the chosen functional coverage plan.
+Details may be missing since most of these items are not expected to be fully understood at this stage.
+
+### TESTPLAN_COMPLETED
+
+A testplan has been written (in Hjson format) indicating:
+- Testpoints (a list of planned tests), each mapping to a design feature, with a description highlighting the goal of the test and optionally, the stimulus and the checking procedure.
+- The functional coverage plan captured as a list of covergroups, with a description highlighting which feature is expected to be covered by each covergroup.
+  It may optionally contain additional details such as coverpoints and crosses of individual aspects of the said feature that is covered.
+
+If the DUT has a CPU for which a ROM firmware is developed (burnt-in during manufacturing):
+- A detailed ROM firmware testplan has been written to adequately verify all functions of the ROM firmware in a pre-silicon simulation environment (i.e. a DV testbench).
+- The testing framework may validate each functionality of the ROM firmware discretely as an individual unit test.
+- Depending on the ROM firmware development progress, this may be postponed for V2.
+
+### TB_TOP_CREATED
+
+A top level testbench has been created with the DUT instantiated.
+The following interfaces are connected (as applicable): TileLink, clocks and resets, interrupts and alerts.
+Other interfaces may not be connected at this point (connecting these is part of SIM_TB_ENV_CREATED).
+Inputs for which interfaces have not yet been created are tied off to the default value.
+
+### PRELIMINARY_ASSERTION_CHECKS_ADDED
+
+All available interface assertion monitors are connected (example: tlul_assert).
+
+### SIM_TB_ENV_CREATED
+
+A UVM environment has been created with major interface agents and UVCs connected and instantiated.
+TLM port connections have been made from UVC monitors to the scoreboard.
+
+### SIM_RAL_MODEL_GEN_AUTOMATED
+
+A RAL model is generated using [regtool](../../../util/reggen/doc/setup_and_use.md) and instantiated in the UVM environment.
+
+### CSR_CHECK_GEN_AUTOMATED
+
+A CSR check is generated using [regtool](../../../util/reggen/doc/setup_and_use.md) and bound in the TB environment.
+
+### TB_GEN_AUTOMATED
+
+Full testbench automation has been completed if applicable.
+This may be required for verifying multiple flavors of parameterized designs.
+
+### SIM_SMOKE_TEST_PASSING
+
+A smoke test exercising the basic functionality of the main DUT datapath is passing.
+The functionality to test (and to what level) may be driven by higher level (e.g. chip) integration requirements.
+These requirements are captured when the testplan is reviewed by the key stakeholders, and the test(s) updated as necessary.
+
+### SIM_CSR_MEM_TEST_SUITE_PASSING
+
+CSR test suites have been added for ALL interfaces (including, but not limited to the DUT's SW device access port, JTAG access port etc.) that have access to the system memory map:
+- HW reset test (test all resets)
+- CSR read/write
+- Bit Bash
+- Aliasing
+
+Memory test suites have been added for ALL interfaces that have access to the system memory map if the DUT has memories:
+- Mem walk
+
+All these tests should verify back to back accesses with zero delays, along with partial reads and partial writes.
+
+### FPV_MAIN_ASSERTIONS_PROVEN
+
+Each input and each output of the module is part of at least one assertion.
+Assertions for the main functional path are implemented and proven.
+
+### SIM_ALT_TOOL_SETUP
+
+The smoke regression passes cleanly (with no warnings) with one additional tool apart from the primary tool selected for signoff.
+
+### SIM_SMOKE_REGRESSION_SETUP
+
+A small suite of tests has been identified as the smoke regression suite and is run regularly to check code health.
+If the testbench has more than one build configuration, then each configuration has at least one test added to the smoke regression suite.
+
+### SIM_NIGHTLY_REGRESSION_SETUP
+
+A nightly regression for running all constrained-random tests with multiple random seeds (iterations) has been setup.
+Directed, non-random tests need not be run with multiple iterations.
+Selecting the number of iterations depends on the coverage, the mean time between failure and the available compute resources.
+For starters, it is recommended to set the number of iterations to 100 for each test.
+It may be trimmed down once the test has stabilized, and the same level of coverage is achieved with fewer iterations.
+The nightly regression should finish overnight so that the results are available the next morning for triage.
+
+### FPV_REGRESSION_SETUP
+
+An FPV regression has been set up by adding the module to the `hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson` file.
+
+### SIM_COVERAGE_MODEL_ADDED
+
+A structural coverage collection model has been checked in.
+This is a simulator-specific file (i.e. proprietary format) that captures which hierarchies and what types of coverage are collected.
+For example, pre-verified sub-modules (including some `prim` components pre-verified thoroughly with FPV) can be black-boxed - it is sufficient to only enable the IO toggle coverage of their ports.
+A functional coverage shell object has been created - this may not contain coverpoints or covergroups yet, but it is primed for development post-V1.
+
+### TB_LINT_SETUP
+
+[VeribleLint](https://google.github.io/verible/verilog_lint.html) for the testbench is [set up](../../../hw/lint/README.md) to run in nightly regression, with appropriate waivers.
+* For a constrained random testbench, an entry has been added to `hw/<top-level-design>/lint/<top-level-design>_dv_lint_cfgs.hjson`
+* For an FPV testbench, an entry has been added to `hw/<top-level-design>/lint/<top-level-design>_fpv_lint_cfgs.hjson`
+
+### PRE_VERIFIED_SUB_MODULES_V1
+
+Sub-modules that are pre-verified with their own testbenches have already reached V1 or a higher stage.
+The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification stages), meets or exceeds the V1 stage requirement.
+They are clearly cited in the DV document and the coverage of these sub-modules can be excluded in the IP-level testbench.
+
+### DESIGN_SPEC_REVIEWED
+
+The design / micro-architecture specification has been reviewed and signed off.
+If a product requirements document (PRD) exists, then ensure that the design specification meets the product requirements.
+
+### TESTPLAN_REVIEWED
+
+The draft DV document (proposed testbench architecture) and the complete testplan have been reviewed with key stakeholders (as applicable):
+- DUT designer(s)
+- 1-2 peer DV engineers
+- Software engineer (DIF developer)
+- Chip architect / design lead
+- Chip DV lead
+- Security architect
+
+### STD_TEST_CATEGORIES_PLANNED
+
+The following categories of post-V1 tests have been focused on during the testplan review (as applicable):
+- Security / leakage
+- Error scenarios
+- Power
+- Performance
+- Debug
+- Stress
+
+### V2_CHECKLIST_SCOPED
+
+The V2 checklist has been reviewed to understand the scope and estimate effort.
+
+## V2
+
+To transition from V1 to V2, the following items are expected to be completed.
+The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
+
+### DESIGN_DELTAS_CAPTURED_V2
+
+It is possible for the design to have undergone some changes since the DV document and testplan were reviewed in the V0 stage.
+All design deltas have been captured adequately and appropriately in the DV document and the testplan.
+
+### DV_DOC_COMPLETED
+
+The DV document is fully complete.
+
+### FUNCTIONAL_COVERAGE_IMPLEMENTED
+
+The functional coverage plan is fully implemented.
+All covergroups have been created and sampled in the reactive components of the testbench (passive interfaces, monitors and scoreboards).
+
+### ALL_INTERFACES_EXERCISED
+
+For simulations, interfaces are connected to all ports of the DUT and are exercised.
+For an FPV testbench, assertions have been added for all interfaces including sidebands.
+
+### ALL_ASSERTION_CHECKS_ADDED
+
+All planned assertions have been written and enabled.
+
+### SIM_TB_ENV_COMPLETED
+
+A UVM environment has been fully developed with end-to-end checks in the scoreboard enabled.
+
+### SIM_ALL_TESTS_PASSING
+
+All tests in the testplan have been written and are passing with at least one random seed.
+
+### FPV_ALL_ASSERTIONS_WRITTEN
+
+All assertions (except security countermeasure assertions) are implemented and are 90% proven.
+Each output of the module has at least one forward and one backward assertion check.
+The FPV proof run converges within reasonable runtime.
+
+### FPV_ALL_ASSUMPTIONS_REVIEWED
+
+All assumptions have been implemented and reviewed.
+
+### SIM_FW_SIMULATED
+
+If the DUT has a CPU for which a ROM firmware is developed (burnt-in during manufacturing):
+- The ROM firmware testplan is fully written.
+- SIM_ALL_TESTS_PASSING checklist item is met, including these tests.
+
+This checklist item is marked N.A. if the DUT does not have a CPU.
+
+### SIM_NIGHTLY_REGRESSION_V2
+
+A nightly regression with multiple random seeds is 90% passing.
+
+### SIM_CODE_COVERAGE_V2
+
+Line, toggle, fsm (state & transition), branch and assertion code coverage has reached 90%.
+Toggle coverage of the ports of the DUT and all pre-verified sub-modules have individually reached 90% in both directions (1->0 and 0->1).
+
+### SIM_FUNCTIONAL_COVERAGE_V2
+
+Functional coverage has reached 90%.
+
+### FPV_CODE_COVERAGE_V2
+
+Branch, statement and functional code coverage for FPV testbenches has reached 90%.
+
+### FPV_COI_COVERAGE_V2
+
+COI coverage for FPV testbenches has reached 75%.
+
+### PRE_VERIFIED_SUB_MODULES_V2
+
+Sub-modules that are pre-verified with their own testbenches have already reached V2 or a higher stage.
+The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification stages), meets or exceeds the V2 stage requirement.
+
+### SEC_CM_PLANNED
+
+Security countermeasures are planned and documented.
+- Common countermeasure features (such as shadowed reg, hardened counter etc) can be tested by importing common sec_cm testplans, tests and adding the bind file `cm_sec_bind`.
+- Additional checks and sequences may be needed to verify those features. Document those in the individual testplan.
+- Create testplan for non-common countermeasures.
+
+### NO_HIGH_PRIORITY_ISSUES_PENDING
+
+All high priority (tagged P0 and P1) design bugs have been addressed and closed.
+If the bugs were found elsewhere, ensure that they are reproduced deterministically in DV (through additional tests or by tweaking existing tests as needed) and have the design fixes adequately verified.
+
+### ALL_LOW_PRIORITY_ISSUES_ROOT_CAUSED
+
+All low priority (tagged P2 and P3) design bugs have been root-caused.
+They may be deferred to post V2 for closure.
+
+### DV_DOC_TESTPLAN_REVIEWED
+
+The DV document and testplan are complete and have been reviewed by key stakeholders (as applicable):
+- DUT designer(s)
+- 1-2 peer DV engineers
+- Chip architect / design lead
+- Chip DV lead
+- Security architect
+
+This review will focus on the design deltas captured in the testplan since the last review.
+In addition, the fully implemented functional coverage plan, the observed coverage and the coverage exclusions are expected to be scrutinized to ensure there are no verification holes or any gaps in achieving the required stimulus quality, before the work towards progressing to V3 can commence.
+
+### V3_CHECKLIST_SCOPED
+
+The V3 checklist has been reviewed to understand the scope and estimate effort.
+
+## V2S
+
+### SEC_CM_TESTPLAN_COMPLETED
+
+The testplan has been updated with the necessary testpoints and covergroups to adequately verify all security countermeasures implemented in the DUT.
+These countermeasures are listed in the comportable IP Hjson file located at `hw/ip/<ip>/data/<ip>.hjson` (or equivalent).
+
+On OpenTitan, a security countermeasures testplan is auto-generated (the first time) by the `reggen` tool for each DUT, and is placed at `hw/ip/<ip>/data/<ip>_sec_cm_testplan.hjson` (or equivalent).
+This testplan has been imported into the main testplan written for the DUT.
+Tests implemented to verify the security countermeasures have been mapped to these testpoints.
+
+Common countermeasures can be fully verified or partially handled by cip_lib.
+Follow this [document](../../../hw/dv/sv/cip_lib/README.md#security-verification-in-cip_lib) to enable them.
+Make sure to import the applicable common sec_cm [tests](https://github.com/lowRISC/opentitan/tree/master/hw/dv/tools/dvsim/tests) and [testplans](https://github.com/lowRISC/opentitan/tree/master/hw/dv/tools/dvsim/testplans).
+
+### FPV_SEC_CM_PROVEN
+
+All security countermeasure assertions are proven in FPV.
+The required assertions for countermeasure are defined in [Security Countermeasure Verification Framework](../../contributing/dv/sec_cm_dv_framework/README.md).
+Follow this [document](../../../hw/formal/README.md#running-fpv-on-security-blocks-for-common-countermeasure-primitives) to setup the FPV sec_cm testbench.
+
+### SIM_SEC_CM_VERIFIED
+
+All security countermeasures are verified in simulation.
+Common countermeasures can be fully verified or partially handled by cip_lib.
+Refer to the [cip_lib document](../../../hw/dv/sv/cip_lib/README.md#security-verification-in-cip_lib) for details.
+
+### SIM_COVERAGE_REVIEWED
+
+Security countermeasure blocks may have been excluded in order to satisfy the V2 sign-off criteria.
+If so, these exclusions should be removed.
+
+If UNR exclusion has been generated, it needs to be re-generated and reviewed after all security countermeasure tests have been implemented, as fault injection can exercise countermeasures which are deemed as unreachable code.
+The V2S coverage requirement is the same as V2 (90% code coverage and 70% functional coverage).
+
+### SEC_CM_DV_REVIEWED
+
+The security countermeasures testplan and the overall DV effort has been reviewed by key stakeholders (as applicable):
+- DUT designer(s)
+- 1-2 peer DV engineers
+- Security architect (optional)
+
+This review may be waived if not deemed necessary.
+
+## V3
+
+To transition from V2 to V3, the following items are expected to be completed.
+The prefix "SIM" is applicable for simulation-based DV approaches, whereas the prefix "FPV" is applicable for formal property-based verification approaches.
+
+### DESIGN_DELTAS_CAPTURED_V3
+
+Although rare, it is possible for the design to have undergone some last-minute changes since V2.
+All additional design deltas have been captured adequately and appropriately in the DV document and the testplan.
+
+### X_PROP_ANALYSIS_COMPLETED
+
+[X-propagation](https://docs.opentitan.org/doc/ug/dv_methodology/#x-propagation-xprop) is enabled in DV simulations.
+There are no pieces of logic that are reported unsuitable for X-propagation instrumentation by the simulator during the build step.
+
+### FPV_ASSERTIONS_PROVEN_AT_V3
+
+All assertions are implemented and 100% proven.
+There are no undetermined or unreachable properties.
+
+### SIM_NIGHTLY_REGRESSION_AT_V3
+
+A nightly regression with multiple random seeds is 100% passing (with 1 week minimum soak time).
+
+### SIM_CODE_COVERAGE_AT_100
+
+Line, toggle, fsm (state & transition), branch and assertion code coverage has reached 100%.
+
+### SIM_FUNCTIONAL_COVERAGE_AT_100
+
+Functional coverage has reached 100%.
+
+### FPV_CODE_COVERAGE_AT_100
+
+Branch, statement and functional code coverage for FPV testbenches has reached 100%.
+
+### FPV_COI_COVERAGE_AT_100
+
+COI coverage for FPV testbenches has reached 100%.
+
+### ALL_TODOS_RESOLVED
+
+There are no remaining TODO items anywhere in the testbench code, including common components and UVCs.
+
+### NO_TOOL_WARNINGS_THROWN
+
+There are no compile-time or run-time warnings thrown by the simulator.
+
+### TB_LINT_COMPLETE
+
+The lint flow for the testbench is clean.
+Any lint waiver files have been reviewed and signed off by the technical steering committee.
+
+### PRE_VERIFIED_SUB_MODULES_V3
+
+Sub-modules that are pre-verified with their own testbenches have already reached the V3 stage.
+The coverage level of the pre-verified sub-modules that are not tracked (i.e., not taken through the verification stages), meets the V3 stage requirement.
+
+### NO_ISSUES_PENDING
+
+All design and testbench bugs have been addressed and closed.
+
+## S1
+
+For a transition from S0 to S1, the following items are expected to be completed.
+
+### DIF_EXISTS
+
+Autogenerated IRQ and Alert DIFs have been created with the `util/make_new_dif.py` tool, and exist in `sw/device/lib/dif/autogen/`.
+Additionally, a header file, `dif_<ip>.h` and, optionally, `dif_<ip>.c` exist in `sw/device/lib/dif/`.
+
+### DIF_USED_IN_TREE
+
+All existing **non-production** code in the tree which uses the device does so via the DIF or a production driver.
+
+### DIF_TEST_ON_DEVICE
+
+An on-device test exists (in `sw/device/tests`) that uses the DIF.
+
+This test should excercise the main datapath of the hardware module via the DIF, and should be able to be run on at least one OpenTitan platform (either on FPGA or in simulation).
+
+## S2
+
+For a transition from S1 to S2, the following items are expected to be completed.
+
+### DIF_HW_FEATURE_COMPLETE
+
+The DIF's respective device IP is at least stage D2.
+
+### DIF_FEATURES
+
+The DIF has functions to cover all specified hardware functionality.
+
+## S3
+
+For a transition from S2 to S3, the following items are expected to be completed.
+
+### DIF_HW_DESIGN_COMPLETE
+
+The DIF's respective device IP is at least stage D3.
+
+### DIF_HW_VERIFICATION_COMPLETE
+
+The DIF's respective device IP is at least stage V3.
+
+### DIF_DOC_HW
+
+The HW IP Programmer's guide references specific DIF APIs that can be used for operations.
+
+### DIF_CODE_STYLE
+
+The DIF follows DIF-specific guidelines in [`sw/device/lib/dif`](../../../sw/device/lib/dif/README.md) and the OpenTitan C style guidelines.
+
+### DIF_TEST_UNIT
+
+Software unit tests exist for the DIF in `sw/device/tests/dif` named `dif_<ip>_unittest.cc`.
+Unit tests exist to cover (at least):
+
+- Device Initialisation
+- All Device FIFOs (including when empty, full, and adding data)
+- All Device Registers
+- All DIF Functions
+- All DIF return codes
+
+### DIF_TODO_COMPLETE
+
+All DIF TODOs are complete.
diff --git a/doc/project_governance/checklist/hw_checklist.md.tpl b/doc/project_governance/checklist/hw_checklist.md.tpl
new file mode 120000
index 0000000..1d5eee0
--- /dev/null
+++ b/doc/project_governance/checklist/hw_checklist.md.tpl
@@ -0,0 +1 @@
+../../../util/uvmdvgen/checklist.md.tpl
\ No newline at end of file
diff --git a/doc/project_governance/checklist/sw_checklist.md.tpl b/doc/project_governance/checklist/sw_checklist.md.tpl
new file mode 100644
index 0000000..51eb629
--- /dev/null
+++ b/doc/project_governance/checklist/sw_checklist.md.tpl
@@ -0,0 +1,62 @@
+---
+title: "${ip.name_long_upper} DIF Checklist"
+---
+
+<%doc>
+    This templates requires the following Python objects to be passed:
+
+    1. ip: See util/make_new_dif.py for the definition of the `ip` obj.
+</%doc>
+
+<!--
+NOTE: This is a template checklist document that is required to be copied over
+to `sw/device/lib/dif/dif_${ip.name_snake}.md` for a new DIF that transitions
+from L0 (Specification) to L1 (Development) stage, and updated as needed.
+Once done, please remove this comment before checking it in.
+-->
+This checklist is for [Development Stage]({{< relref "/doc/project/development_stages.md" >}}) transitions for the [${ip.name_long_upper} DIF]({{< relref "hw/ip/${ip.name_snake}/doc" >}}).
+All checklist items refer to the content in the [Checklist]({{< relref "/doc/project/checklist.md" >}}).
+
+<h2>DIF Checklist</h2>
+
+<h3>S1</h3>
+
+Type           | Item                   | Resolution  | Note/Collaterals
+---------------|------------------------|-------------|------------------
+Implementation | [DIF_EXISTS][]         | Not Started |
+Implementation | [DIF_USED_IN_TREE][]   | Not Started |
+Tests          | [DIF_TEST_ON_DEVICE][] | Not Started |
+
+[DIF_EXISTS]:         {{< relref "/doc/project/checklist.md#dif_exists" >}}
+[DIF_USED_IN_TREE]:   {{< relref "/doc/project/checklist.md#dif_used_in_tree" >}}
+[DIF_TEST_ON_DEVICE]: {{< relref "/doc/project/checklist.md#dif_test_on_device" >}}
+
+<h3>S2</h3>
+
+Type           | Item                        | Resolution  | Note/Collaterals
+---------------|-----------------------------|-------------|------------------
+Coordination   | [DIF_HW_FEATURE_COMPLETE][] | Not Started | [HW Dashboard]({{< relref "hw" >}})
+Implementation | [DIF_FEATURES][]            | Not Started |
+
+[DIF_HW_FEATURE_COMPLETE]: {{< relref "/doc/project/checklist.md#dif_hw_feature_complete" >}}
+[DIF_FEATURES]:            {{< relref "/doc/project/checklist.md#dif_features" >}}
+
+<h3>S3</h3>
+
+Type           | Item                             | Resolution  | Note/Collaterals
+---------------|----------------------------------|-------------|------------------
+Coordination   | [DIF_HW_DESIGN_COMPLETE][]       | Not Started |
+Coordination   | [DIF_HW_VERIFICATION_COMPLETE][] | Not Started |
+Documentation  | [DIF_DOC_HW][]                   | Not Started |
+Code Quality   | [DIF_CODE_STYLE][]               | Not Started |
+Tests          | [DIF_TEST_UNIT][]                | Not Started |
+Review         | [DIF_TODO_COMPLETE][]            | Not Started |
+Review         | Reviewer(s)                      | Not Started |
+Review         | Signoff date                     | Not Started |
+
+[DIF_HW_DESIGN_COMPLETE]:       {{< relref "/doc/project/checklist.md#dif_hw_design_complete" >}}
+[DIF_HW_VERIFICATION_COMPLETE]: {{< relref "/doc/project/checklist.md#dif_hw_verification_complete" >}}
+[DIF_DOC_HW]:                   {{< relref "/doc/project/checklist.md#dif_doc_hw" >}}
+[DIF_CODE_STYLE]:               {{< relref "/doc/project/checklist.md#dif_code_style" >}}
+[DIF_TEST_UNIT]:                {{< relref "/doc/project/checklist.md#dif_test_unit" >}}
+[DIF_TODO_COMPLETE]:            {{< relref "/doc/project/checklist.md#dif_todo_complete" >}}
diff --git a/doc/project_governance/committers.md b/doc/project_governance/committers.md
new file mode 100644
index 0000000..658cd15
--- /dev/null
+++ b/doc/project_governance/committers.md
@@ -0,0 +1,47 @@
+# Committers
+
+Committers are individuals with repository write access.
+While everyone can and is encouraged to contribute by reviewing code, committers are responsible for final approval and merge.
+Committers must ensure that any code merged meets a high quality bar, has been properly discussed, and the design rationale adequately explained and documented.
+Making a judgment on when these requirements have been met is fundamental to the role.
+
+Committers are proposed by and voted on by the TC.
+Committers should:
+* Be active contributors to the project, with a history of high quality contributions, including code reviews.
+* Be familiar with the project processes and rules, and be able to apply them fairly.
+* Be responsive to feedback from TC members on their review approach and interpretation of project policy.
+* Work to ensure that stakeholders are properly consulted on any code or design changes.
+
+If you meet the above criteria and are interested in becoming a committer, then approach a TC member to see if they would be willing to propose you.
+TC members may also reach out to you to ask if you would be interested in becoming a committer.
+
+Under certain exceptional circumstances, the Steering Committee may vote to revoke a committer's status.
+This may happen in cases such as a committer failing to act as a good citizen within the project and the issue cannot be resolved through discussion.
+It may also be a natural function of "pruning the tree" as an individual's involvement in the project changes over time.
+
+The list of committers is maintained within the project's Git repository and is available in the [COMMITTERS file](https://github.com/lowRISC/opentitan/blob/master/COMMITTERS) in the repository root.
+
+## Commit Escalation Guidelines
+
+When reviewing a pull request, there are a range of options in giving feedback.
+Although committers have the final say on patch approval or requiring changes, the task of reviewing is shared by all contributors, who may make similar requests.
+Options for reviewing a pull request include:
+* Approval, with no further conditions.
+  * This is appropriate when you are confident the changes are correct, sufficiently explained, in line with the project coding styles, and don't need further review by others or a companion [RFC](./rfc_process.md) to be written.
+    You should only use this for cases where you have sufficient expertise in the areas being modified.
+  * Note that if the PR came from someone without commit rights, you will need to rebase and merge for them.
+* Approval, but with a request to get an additional approval from other named reviewers.
+  * This is appropriate when you believe the changes are correct, but would either like a second opinion or to ensure that another contributor is aware of and approves of the changes.
+* Request for changes.
+  * This is appropriate when problems or potential improvements are spotted that can be addressed by the original submitter updating the pull request.
+* Request that further design rationale be written up and shared (but a full [RFC](./rfc_process.md) isn't necessary).
+  * This is appropriate when the rationale for a change is not clear, or a lack of accompanying documentation makes reviewing the code challenging.
+    This can commonly occur when further explanation would be valuable for people working in that area but no project-wide consensus is needed.
+* Request that an RFC be written up and submitted.
+  * This is appropriate when adding a major new piece of code (e.g. an IP block) or when a change is cross-cutting and likely to require input from many stakeholders.
+    The expectation is that in many cases, project contributors will recognize when an RFC is needed before getting to the point of submitting a pull request.
+
+In all cases, clarity in your feedback will be valued.
+For instance, if you are giving code style feedback but not reviewing higher level design decisions (perhaps because you are expecting another committer/contributor to do so), it is useful to say so.
+
+Where Committers disagree on the path forwards for a given PR and are unable to reach an agreement, the review moves to the TC.
diff --git a/doc/project_governance/development_stages.md b/doc/project_governance/development_stages.md
new file mode 100644
index 0000000..0b99c8e
--- /dev/null
+++ b/doc/project_governance/development_stages.md
@@ -0,0 +1,306 @@
+# OpenTitan Hardware Development Stages
+
+## Document Goals
+
+This document describes development stages for hardware within the OpenTitan program.
+This includes design and verification stages meant to give a high-level view of the status of a design.
+OpenTitan being an open-source program aimed at a high quality silicon release, the intent is to find a balance between the rigor of a heavy tapeout process and the more fluid workings of an open source development.
+
+This document also serves as a guide to the [Hardware Dashboard](../../hw/README.md), which gives the status of all of the designs in the OpenTitan repository.
+
+This document aims to mostly give a more defined structure to the process that is already followed.
+Proper versioning of RTL designs is a complex topic.
+This document proposes a sensible path forwards, but this may be revisited as we gain further experience and get more feedback.
+
+
+## Life Stages (L)
+
+The stages listed here are created to give insight into where a design is in its life from specification to silicon-ready sign-off.
+At the moment, this is strictly limited to a hardware design, but could be expanded to other components of development within OpenTitan.
+Transitions between these stages are decided by the Technical Committee via the [RFC process](./rfc_process.md).
+
+The first life stage is **Specification**.
+The proposed design is written up and submitted through the [RFC process](./rfc_process.md).
+Depending on the complexity of the design and the guidance of the Technical Committee, it is possible a single design might require multiple RFCs.
+For example, a first RFC for the rationale, feature list, and a rough overview; followed by a more detailed RFC to get approval for the draft technical specification.
+As part of the specification process, the design author might reach out for feedback from a smaller group of reviewers while formulating an RFC proposal.
+RFCs are always shared with the wider OpenTitan community prior to vote by the Technical Committee.
+However, wherever there is potentially sensitive material from a future certification standpoint it should be passed through the security review team.
+Once the specification has been shared with the OpenTitan audience and sufficient review has been completed, this phase is exited.
+
+The next life stage is **Development**.
+The hardware IP is being developed in GitHub, the specification is converted to Markdown, and design and verification planning is underway.
+This is a long phase expected to last until a more formal review is requested for full completion sign-off.
+When in Development phase, the stage tracking of the design and verification stages are valid.
+See those sections that follow for details there.
+To exit this stage, a sign-off review must occur.
+See the section on sign-off for details.
+
+The final life stage is **Signed-Off**.
+At this point, a design is frozen and not expected to be updated.
+There are exceptions if post-sign-off bugs are found, in which case the stage returns to Development and the version number is not updated.
+Feature requests towards a signed-off design requires review and approval by the Technical Committee.
+Once accepted, it results in creating a new version and return a design to the appropriate life stage, based upon the size of the change.
+See the _Versioning_ section of the document for more discussion.
+Signed-off fully-functioning (read: not buggy) designs stay in the "Signed-Off" life stage as an available complete IP, with an associated revision ID.
+
+There exists a [template for IP checklists](https://github.com/lowRISC/opentitan/blob/master/util/uvmdvgen/checklist.md.tpl).
+The DIF stages use a separate, [software-specific checklist](https://github.com/lowRISC/opentitan/blob/master/doc/project/sw_checklist.md.tpl).
+All the checklist items are listed in the [Sign-off Checklist](./checklist/README.md).
+
+| **Stage** | **Name** | **Definition** |
+| --- | --- | --- |
+| L0 | Specification | Specification is being written, is in review process |
+| L1 | Development | Design is in development in GitHub, possibly integrated in top level |
+| L2 | Signed-Off | Design has been frozen at version number, signed-off, available for tapeout |
+
+We may later evaluate adding a **Silicon Proven** stage, after deciding criteria for a tapeout to qualify as proven.
+
+
+## Hardware Design Stages (D)
+
+The following development stages are for hardware peripheral designs, i.e. SystemVerilog RTL development.
+They are similar to typical chip design stages, but less rigid in the movement from one stage to the next.
+The metric here is the quality bar and feature completeness of the design.
+
+The first design stage is **Initial Work**.
+This indicates the period of time between the Specification life stage and the RTL being functional enough to pass smoke checks.
+The RTL is still in progress, registers being defined, etc.
+Once the device has passed a basic smoke check, has the lint flow setup, has the registers completely defined, it has completed the Initial Work stage.
+
+The second design stage is **Functional**.
+In this stage, the design is functional but not complete.
+Once all of the features in the specification are implemented, it has completed this stage.
+
+The third design stage is **Feature Complete**.
+In this phase, no changes are expected on the design except for bug fixes and security countermeasure implementations.
+Once all of the countermeasures in the specification are implemented, the design moves into: **Security Countermeasures Complete**.
+
+The fourth design stage is **Security Countermeasures Complete**.
+In this phase, no changes are expected on the design except for bug fixes.
+Once all bugs have been fixed, lint and CDC violations cleaned up, the design moves into its final stage: **Design Complete**.
+
+| **Stage** | **Name** | **Definition** |
+| --- | --- | --- |
+| D0  | Initial Work | RTL being developed, not functional |
+| D1  | Functional | <ul> <li> Feature set finalized, spec complete <li> CSRs identified; RTL/DV/SW collateral generated <li> SW interface automation completed <li> Clock(s)/reset(s) connected to all sub modules <li> Lint run setup <li> Ports Frozen </ul> |
+| D2  | Feature Complete | <ul> <li> Full Feature Complete: all features implemented.  <li> Feature frozen </ul> |
+| D2S | Security Countermeasures Complete | <ul> <li> All security countermeasures implemented. </ul> |
+| D3  | Design Complete | <ul> <li> Lint/CDC clean, waivers reviewed <li> Design optimization for power and/or performance complete </ul> |
+
+## Hardware Verification Stages (V)
+
+The following development stages are for hardware peripheral verification work.
+They are similar to typical chip verification stages, but less rigid in the movement from one stage to the next.
+The metric here is the progress towards testing completion and proof of testing coverage.
+The verification stages can be applied to simulation-based DV and formal property verification (FPV) approaches.
+
+The first verification stage is **Initial Work**.
+This indicates the period of time between the beginning of verification planning and the testbench up and running.
+The testbench is still being created, scoreboards implemented, DV document and testplan being written, nightly regressions running, etc.
+Once the verification environment is available for writing tests, with a testplan written including a testplan and a functional coverage plan, it has completed the Initial Work stage.
+
+The second verification stage is **Under Test**.
+In this stage, the verification environment is available but not all tests in the testplan are are completed and the coverpoints are not implemented.
+Once all of the items in the testplan are implemented, it exits this stage.
+
+The third verification stage is **Testing Complete**.
+In this phase, no changes are expected on the testplan, no changes expected on the testbench, and no new tests are expected except to verify security countermeasures and to close coverage on the design.
+Once all coverage metrics have been met, waivers checked, the verification moves into: **Security Countermeasures Verified**.
+
+The fourth verification stage is **Security Countermeasures Verified**.
+In this phase, no changes are expected on the testplan, no changes expected on the testbench, and no new tests are expected except to close coverage on the design.
+Once all coverage metrics have been met, waivers checked, the verification moves into its final stage: **Verification Complete**.
+
+**Stages for simulation-based DV**:
+
+| **Stage** | **Name** | **Definition** |
+| --- | --- | --- |
+| V0 | Initial Work | Testbench being developed, not functional; testplan being written; decided which methodology to use (sim-based DV, FPV, or both). |
+| V1 | Under Test | <ul> <li> Documentation: <ul> <li> [DV document](../contributing/dv/methodology/README.md#documentation) available, <li> [Testplan](../contributing/dv/methodology/README.md#documentation) completed and reviewed </ul> <li> Testbench: <ul> <li> DUT instantiated with major interfaces hooked up <li> All available interface assertion monitors hooked up <li> X / unknown checks on DUT outputs added <li> Skeleton environment created with UVCs <li> TLM connections made from interface monitors to the scoreboard </ul> <li> Tests (written and passing): <ul> <li> Sanity test accessing basic functionality <li> CSR / mem test suite </ul> <li> Regressions: Sanity and nightly regression set up</ul> |
+| V2 | Testing Complete | <ul> <li> Documentation: <ul> <li> DV document completely written </ul> <li> Design Issues: <ul> <li> all high priority bugs addressed <li> low priority bugs root-caused </ul> <li> Testbench: <ul> <li> all interfaces hooked up and exercised <li> all assertions written and enabled </ul> <li> UVM environment: fully developed with end-to-end checks in scoreboard <li> Tests (written and passing): all tests planned for in the testplan  <li> Functional coverage (written): all covergroups planned for in the testplan <li> Regression: all tests passing in nightly regression with multiple seeds (> 90%)  <li> Coverage: 90% code coverage across the board and 90% functional coverage</ul></ul> |
+| V2S | Security Countermeasures Verified | <ul> <li> Tests (written and passing): all tests for security countermeasures (if any) </ul> |
+| V3 | Verification Complete | <ul> <li> Design Issues: all bugs addressed <li> Tests (written and passing): all tests including newly added post-V2 tests (if any) <li> Regression: all tests with all seeds passing <li> Coverage: 100% code and 100% functional coverage with waivers </ul> </ul> |
+
+**Stages for FPV approaches**:
+
+| **Stage** | **Name** | **Definition** |
+| --- | --- | --- |
+| V0 | Initial Work | Testbench being developed, not functional; testplan being written; decided which methodology to use (sim-based DV, FPV, or both). |
+| V1 | Under Test | <ul> <li> Documentation: <ul> <li> [DV document](../contributing/dv/methodology/README.md#documentation) available, [Testplan](../contributing/dv/methodology/README.md#documentation) completed and reviewed </ul> <li> Testbench: <ul> <li> Formal testbench with DUT bound to assertion module(s) <li> All available interface assertion monitors hooked up <li> X / unknown assertions on DUT outputs added </ul> <li> Assertions (written and proven): <ul> <li> All functional properties identified and described in testplan <li> Assertions for main functional path implemented and passing (smoke check)<li> Each input and each output is part of at least one assertion</ul> <li> Regressions: Sanity and nightly regression set up</ul> |
+| V2 | Testing Complete | <ul> <li> Documentation: <ul> <li> DV document completely written </ul> <li> Design Issues: <ul> <li> all high priority bugs addressed <li> low priority bugs root-caused </ul> <li> Testbench: <ul> <li> all interfaces have assertions checking the protocol <li> all functional assertions written and enabled <li> assumptions for FPV specified and reviewed </ul> <li> Tests (written and passing): all tests planned for in the testplan <li> Regression: 90% of properties proven in nightly regression <li> Coverage: 90% code coverage and 75% logic cone of influence (COI) coverage </ul> |
+| V2S | Security Countermeasures Verified | <ul> <li> Tests (written and passing): all tests for security countermeasures (if any) </ul> |
+| V3 | Verification Complete | <ul> <li> Design Issues: all bugs addressed <li> Assertions (written and proven): all assertions including newly added post-V2 assertions (if any) <li> Regression: 100% of properties proven (with reviewed assumptions) <li> Coverage: 100% code coverage and 100% COI coverage</ul> |
+
+## Device Interface Function Stages (S)
+
+The following development stages are for [Device Interface Function (DIF)](../contributing/sw/device_interface_functions.md) work.
+These stages have a slightly different emphasis to the hardware design and verification stages, because software is much easier to change if bugs are found.
+The metric they are trying to capture is the stability and completeness of a low-level software interface to hardware design.
+We are aiming to keep this process fairly lightweight in the early stages, and not significantly burdensome to the associated HW designer through all stages.
+
+There are explicit checkpoints in these stages to ensure that DIF development does not overtake design and verification.
+
+The first DIF stage is **Initial Work**.
+This indicates the period of time between starting the software API, and it being complete enough for other software to start using it.
+The exact API is still being defined.
+Once the DIF is complete enough to cover all existing in-tree uses of the device, and has mock tests, it has completed the Initial Work stage.
+
+The second stage is **Functional**.
+In this stage, the DIF can be used for basic operations, but may not cover all the specified functionality of the device.
+Once the DIF is complete enough to cover all the functionality of the device, in the way the hardware designer envisioned, and is used for DV testing, it has completed this stage.
+
+The third stage is **Complete**.
+In this stage, no changes to the interface are expected.
+Once testing is complete, and we are satisfied that the interface will not change (except for bug fixes), the DIF moves into its final stage: **Stable**.
+
+| **Stage** | **Name** | **Definition** |
+| --- | --- | --- |
+| S0 | Initial Work | Work has started on a DIF for the given IP block. |
+| S1 | Functional | <ul> <li> DIF has been reviewed and merged <li> DIF is used by all appropriate non-production code in the tree <li> DIF has (mocked) unit tests <li> DIF has smoke test </ul> |
+| S2 | Complete | <ul> <li> DIF API is now Complete <li> The respective IP block is feature complete (at least D2) <li> DIF matches HW designer's agreed IP block usage <li> DIF covers all specified functionality of the IP block <li> DIF is used for chip-level DV <li> DIF documented in IP documentation </ul> |
+| S3 | Stable | <ul> <li> DIF API Reviewed and Stable <li> The respective IP block is at D3/V3 <li> DIF tested fully (DV + Unit tests, full functional coverage) </ul> |
+
+## Sign-off Review
+
+At the end of the final design and verification phase, the IP block should be proposed to the Technical Committee as ready for sign-off.
+This will be done by submitting an RFC (possibly following a suggested template).
+The Technical Committee may decide to nominate individuals to evaluate the codebase and make a recommendation or may review directly.
+Generally, this process would involve:
+
+*   Specification and RTL are re-reviewed for readability, consistency, and code coverage standards
+*   All design items are complete, reviewed against committed specification
+*   All lint and CDC errors and warnings are waived, waivers reviewed
+*   Testplan is re-reviewed for completeness
+*   All test items are confirmed complete
+*   All code coverage items are completed or waived
+*   Performance requirements are reviewed, performance metrics met
+*   Software interface (DIF) files are completed, tested, and signed off by software representative
+
+The process will be refined by the Technical Committee as necessary.
+
+## Indicating Stages and Making Transitions
+
+Stages are indicated via a text file checked into the GitHub and thus transitions can be reviewed through the standard pull request process.
+Transitions for Design and Verification stages are _self-nominated_ in the sense that the design or verification maintainer can modify the text file and submit a pull request (PR) to transition the stage.
+In this manner other reviewers can challenge the transition in the standard pull request review process.
+These transitions should be done in their own PR (i.e. not interspersed with other changes), and the PR summary and commit message should give any necessary detail on how the transition criteria have been met, as well as any other notes useful for a reviewer.
+
+The content below shows the format of the project related metadata, which can either be placed in the comportable IP Hjson file, or an Hjson file with the extension `.prj.hjson` if such a comportable IP Hjson file does not exist.
+Both files have to be placed under `hw/ip/name/data/name.hjson` or `hw/ip/name/data/name.prj.hjson` for a design named `name`.
+
+For example, `file: gpio.hjson`:
+
+```hjson
+{
+    name:               "gpio"
+    version:            "1.0"
+    life_stage:         "L1"
+    design_stage:       "D2"
+    verification_stage: "V1"
+    dif_stage:          "S0"
+    notes:              "information shown on the dashboard"
+}
+```
+
+### Commit ID
+
+When a design transitions from one stage to another, the metadata can optionally provide a commit ID for the transition to be able to recreate the repository at the point of that transition.
+This is optional for all transitions except for sign-off, where it is required.
+The commit ID has its own entry in the comportable IP Hjson file, as shown below.
+
+```hjson
+{
+    name:               "gpio"
+    version:            "1.0"
+    life_stage:         "L1"
+    design_stage:       "D2"
+    verification_stage: "V1"
+    dif_stage:          "S0"
+    commit_id:          "92e4298f8c2de268b2420a2c16939cd0784f1bf8"
+    notes:              "information shown on the dashboard"
+}
+```
+
+### Other optional fields
+
+Additionally, the tool that generates the dashboard accepts the following optional fields: the design specification, the DV document and the checklist.
+They are set as partial paths (reference relative to the top of the repository) to the respective documents as shown below.
+They are converted to complete URLs in the generated dashboard.
+
+```hjson
+{
+    design_spec:  "hw/ip/gpio/doc"
+    dv_doc:      "hw/ip/gpio/doc/dv"
+    hw_checklist: "hw/ip/gpio/doc/checklist"
+    sw_checklist: "sw/device/lib/dif/dif_gpio"
+}
+```
+
+## Versioning
+
+The _Version_ of a design element indicates its progress towards its _final feature set for expected product_.
+Typically all designs are expected to simply be in 1.0 version, but there are reasons for exceptions.
+Designs which have a specification that defines an _intermediate goal_ are indicated as < 1.0 version.
+There are many times where this is useful: when the intermediate goal is a beneficial subset of functionality to enable other development; when the final feature set is not known but a sufficient set is ready for development; when the final feature set is postponed until a future date, but maintainers are keen to get the design started; etc.
+In essence, the sub-1.0 designation indicates that it is understood that the stage metrics are temporary pending a final feature set.
+Rarely will a sub-1.0 design be taken past Feature Complete and Testing Complete stages.
+An exception is a proof of concept to show what a sign-off process looks like for a design that has modifications expected in the future.
+This was the case with public launch, where we took five designs to completion to test out the sign-off process, the verification methodology, and the checklist system.
+In several of these cases, the feature set was not final product complete.
+
+Once a design has completed all stages for a product feature set, the sign-off process intends to end all development for that design.
+Its Life Stage should transition to sign-off after the review process, and no more modifications should be made.
+The commit ID of the version that was signed off is recorded in the `commit_id` field of the `.prj.hjson` file.
+
+After sign-off, three events could cause a change.
+
+* Possibility 1: If a bug is found in top-level testing, software development, etc., the design should stay in its current revision (assumedly 1.0) but revert in design and/or verification staging until the bug is fixed and a new sign-off process occurs, followed by a new tag to replace the previous one.
+* Possibility 2: if a small collection of new features are requested, a new version increment of 0.1 would be created, and the design and verification stages would be reset.
+The expectation is that this would create its life as a newly tracked revision number, while the previous (assumedly 1.0) version retains its status.
+* Possibility 3: if enough new features are requested to greatly change the spirit of the design, a new version increment of 1.0 would be created in a fashion similar to above.
+This would require a new RFC process, and thus the Life Stage would start again as L0 - Specification.
+
+### Multiple versions of a design
+
+Over the course of the project, designs may *regress* from signed-off to an earlier stage when new features are added.
+When regressing a signed-off design, the old signed-off version should remain in the project file as a retrievable version.
+This is indicated as two versions as shown in this example.
+
+```hjson
+{
+    name:                   "uart",
+    revisions: [
+      {
+        version:            "1.0",
+        life_stage:         "L2",
+        design_stage:       "D3",
+        verification_stage: "V3",
+        dif_stage:          "S0",
+        commit_id:          "92e4298f8c2de268b2420a2c16939cd0784f1bf8",
+        notes:              ""
+      }
+      {
+        version:            "1.1",
+        life_stage:         "L1",
+        design_stage:       "D2",
+        verification_stage: "V2",
+        dif_stage:          "S0",
+        commit_id:          "f3039d7006ca8ebd45ae0b52b22864983876175d",
+        notes:              "Rolled back to D2 as the register module is updated",
+      }
+    ]
+}
+```
+
+One may choose to commemorate a non-signed-off version of a design if it reached enough maturity to be a useful version to checkpoint before regressing.
+In this case the version number should also be incremented.
+
+No hard rules for version numbering are mandated at this stage.
+The subject will be revisited as we get closer to locking down the design to take it to a silicon implementation milestone.
+
+## Reporting of Stages
+
+The stages are reported externally via a script-generated table exposed on the external website.
+This status is a summary of all `prj.hjson` files of all designs in the system, with multiple lines where there are multiple versions.
+The link to that table is [here](../../hw/README.md).
diff --git a/doc/project_governance/priority_definitions.md b/doc/project_governance/priority_definitions.md
new file mode 100644
index 0000000..1eeafa7
--- /dev/null
+++ b/doc/project_governance/priority_definitions.md
@@ -0,0 +1,27 @@
+---
+title: Generalized Priority Definitions
+---
+
+## Definitions
+
+The priorities described below draw inspiration from the [Google Issue Tracker](https://developers.google.com/issue-tracker/concepts/issues#priority) and have been completely reformulated to fit the OpenTitan setting.
+The following definitions are used:
+
+*  **Work streams**: OpenTitan sub-projects which typically have their own associated working group, e.g.: discrete chip, integrated IP.
+*  **Milestones**: Milestones (M1, M2 etc)  associated with a specific work stream.
+*  **Core function**: Core function of OpenTitan in any of the relevant engineering domains (HW, SW, security).
+
+### Priority Definitions Table
+
+
+| Issue Priority | Description
+| ---------------|------------
+| **P0 - Blocking** | An issue that requires immediate resolution. Examples include top-of-tree CI outages or merge skew causing compilation simulation or synthesis breakages.
+| **P1 - High** | An issue requiring quick resolution since it significantly impacts a large percentage of functionality or maintainers; existing workarounds are only partial or exceedingly painful. The issue impacts a core function, and/or fundamentally impedes progress towards target milestones on any of the work streams.
+| **P2 - Default** | An issue that needs to be resolved within a reasonable amount of time. This could be:
+| | (a) an issue that would have a higher priority, but has a reasonable workaround,
+| | (b) an issue that impacts a large percentage of maintainers and is linked with a core function,
+| | (c) an issue that needs to be addressed to reach the next milestone on a given work stream.
+| | This is the default priority level.
+| **P3 - Best effort** | An issue that should be resolved on a best effort basis. Such an issue is relevant to core functions of OpenTitan, but does not impede progress towards target milestones on a given work stream or else has a reasonable workaround
+| **P4 - Deferrable** | An issue that should be resolved eventually. Such an issue is not relevant to core functions or upcoming milestones on any of the work streams; or it only addresses cosmetic aspects of the underlying subject.
diff --git a/doc/project_governance/rfc_process.md b/doc/project_governance/rfc_process.md
new file mode 100644
index 0000000..7071300
--- /dev/null
+++ b/doc/project_governance/rfc_process.md
@@ -0,0 +1,57 @@
+# OpenTitan RFC Process
+
+## Introduction
+
+The OpenTitan RFC process is the mechanism by which technical proposals can be reviewed and voted upon by the Technical Committee (TC).
+More generally, the RFC process ensures changes are appropriately reviewed, gives the contributor community opportunity to give feedback, and helps ensure alternative implementation options are also evaluated.
+Many other open source projects implement such a process with varying degrees of formality, e.g. Python Enhancement Proposals, RFCs in the LLVM community, and more.
+
+RFCs can't be used for resource allocation or to "force" other people to do some work.
+E.g. "Implement IP block X" is not a useful RFC unless either the proposer is willing to do that work or someone else is interested in pursuing it.
+
+This document outlines the basic structure of the RFC process, with the expectation that it will evolve over time based on the experience of active contributors.
+
+## RFC lifetime
+
+* [Optional: Seek feedback on whether an RFC is desired.
+  This is recommended for a lengthy "out of the blue" proposal, to ensure time isn't wasted writing up a detailed proposal for something that is out of scope or that can't be decided upon at this stage.]
+* An author (or set of authors) writes an RFC.
+  This might be triggered simply by an idea, by a discussion in a meeting, or by a request from a Committer after submitting a PR.
+  The TC will maintain examples of high quality RFCs to refer to in terms of structure and approach.
+* The RFC authors may solicit early feedback while preparing an RFC, possibly before sharing publicly.
+   * If requested, the TC could help to nominate a small group to shepherd the RFC.
+* If the RFC potentially contains information that could be certification-sensitive (guidance to be shared), send a note to security@opentitan.org first for feedback.
+* The RFC is shared publicly by filing a GitHub issue and tagging with the `RFC:Proposal` label.
+* Once the author is happy that the RFC is complete, they submit it to the Technical Committee by converting the label to `RFC:TC Review`.
+* The Technical Committee will consider active RFCs in each meeting (those that have been marked with `RFC:TC Review` for at least a week).
+  If an RFC saw large changes in the week it has been "ready" the TC may postpone judgment in order to allow more comment.
+  They will decide whether to:
+   * **Accept**: Indicate they are happy for this proposal to be implemented.
+     May be conditional on certain changes.
+     The TC will change the label from `RFC:TC Review` to `RFC:Approved` in this outcome.
+   * **Reject**: Decided against the proposal at this time.
+     The label `RFC:TC Review` will be removed by the TC.
+   * **Postpone**: The proposal seemed to have the right level of detail and form, but the project won't be making a decision on it at this time (e.g. refers to work that is scheduled too far in the future).
+     The TC will revert the label from `RFC:TC Review` back to `RFC:Proposal`.
+     In the case where the TC has not had sufficient time to complete review during a meeting, the label `RFC:TC Review` will remain in place until the review has completed.
+   * **Request revision**: The TC feel they could make an accept/reject decision if certain changes were made (e.g. fleshing out a description of alternate approaches, considering changes to the approach etc).
+     The TC will revert the label from `RFC:TC Review` back to `RFC:Proposal` until revisions have been completed.
+   * **Abstain with comment**: The TC feel that the issue does not require TC arbitration.
+     The TC may leave comment suggesting the next action for the RFC, perhaps indicating where the issue should be resolved, such as in one of the other committees.
+     The TC will remove the `RFC:TC Review` label in this case.
+
+RFCs should be created at least for any major new piece of code (e.g. IP block), or cross-cutting changes that need careful consideration.
+Generally speaking, an RFC should be created when there is demand for one (especially when that request comes from a Committer).
+Although it is a power that is not expected to be deployed regularly, RFCs may be unilaterally approved/rejected by the Project Director.
+Even in these cases, they would likely still follow the normal review and feedback process.
+
+## Potential future RFC process refinements
+
+The process has been kept as simple as possible to start with, closely mimicking the informal consensus approach being used in the early stages of the OpenTitan project.
+However, there are a range of potential improvements that it may make sense for the Technical Committee and wider contributor community to explore:
+
+* We currently have no particular rule on RFC format and structure.
+  It may be useful to at least suggest a structure for certain categories of changes (e.g. outline of headings).
+* [Rust RFCs](https://rust-lang.github.io/rfcs/) and the [Kotlin Evolution and Enhancement Process](https://github.com/Kotlin/KEEP) both introduce the idea of a "Shepherd", which we may want to adopt.
+* If the volume of RFCs increased to the point it was too time consuming for the Technical Committee to handle each review request, the TC may delegate some responsibility for pre-filtering.
+  E.g. the Chair/Vice-Chair may make a determination on whether an RFC has attracted sufficient support for further discussion.
diff --git a/doc/project_governance/technical_committee.md b/doc/project_governance/technical_committee.md
new file mode 100644
index 0000000..ab70209
--- /dev/null
+++ b/doc/project_governance/technical_committee.md
@@ -0,0 +1,32 @@
+# OpenTitan Technical Committee
+
+The OpenTitan Technical Committee is part of the [OpenTitan governance structure](./README.md) and the definitive description of its operations and responsibilities are stated in the [OpenTitan Technical Charter](https://static.opentitan.org/technical-charter.pdf).
+Key responsibilities defined in that document include:
+* Oversight of the [RFC process](./rfc_process.md), including voting to approve, reject, postpone or revise RFCs.
+* Creating [Commit Escalation Guidelines](./committers.md) setting out when Committers should escalate a particular Commit to the TC (e.g. for cross-cutting changes), and when Committers should recommend a design rationale write-up or RFC for wider feedback.
+* Maintaining the list of [committers](./committers.md).
+
+Expectations of Technical Committee members include the following:
+* Be active participants in development discussions within the OpenTitan project, beyond just Technical Committee meetings.
+* Consider comments and feedback that have been made on proposals during the project-wide review process and ensure any concerns are adequately considered and addressed.
+* Ensure that feedback has been sought from all relevant stakeholders, and appropriate effort has been made to find consensus within the OpenTitan contributor community.
+* Put aside sufficient time on a week-by-week basis to active proposals and give feedback.
+* Act in the best interests of the OpenTitan project.
+* Collaborate to deliver on the responsibilities of the Technical Committee (see above).
+* Proactively seek to identify contributors who should be nominated for committer status.
+
+## Membership
+The OpenTitan Technical Committee membership is:
+* Michael Schaffner (chair)
+* Richard Bohn
+* Cyril Guyot
+* Felix Miller
+* Dominic Rizzo (observer)
+* Alphan Ulusoy
+* Michael Munday
+* Rupert Swarbrick
+* Michael Tempelmeier
+* Neeraj Upasani
+* Nir Tasher
+* Arnon Sharlin
+* Sivakumar
diff --git a/doc/rm/_index.md b/doc/rm/_index.md
deleted file mode 100644
index b4f93c9..0000000
--- a/doc/rm/_index.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: "Reference Manuals"
----
-
-* [Comportability Definition and Specification]({{< relref "comportability_specification" >}})
-* [Device Interface Function (DIF) Specification]({{< relref "device_interface_functions" >}})
-* Tool Guides
-   * [Topgen Tool]({{< relref "topgen_tool" >}}): Describes `topgen.py` and its Hjson format source. Used to generate rtl and validation files for top specific modules such as PLIC, Pinmux and crossbar.
-   * [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.
-   * [Ipgen Tool]({{< relref "ipgen_tool" >}}): Describes `ipgen.py` and its Hjson control file. Used to generate IP blocks from IP templates.
-   * [Crossbar Tool]({{< relref "crossbar_tool" >}}): Describes `tlgen.py` and its Hjson format source. Used to generate self-documentation, rtl files of the crossbars at the toplevel.
-   * [Vendor-In Tool]({{< relref "vendor_in_tool" >}}): Describes `util/vendor.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 (_To avoid conflict, toil and confusion regarding code style, OpenTitan's CI enforces
-what it can of the following 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" >}})
-  * [RISC-V Assembly Style Guide]({{< relref "asm_coding_style.md" >}})
-* [FPGA Reference Manual]({{< relref "ref_manual_fpga.md" >}})
-* [OpenTitan Continuous Integration]({{< relref "continuous_integration.md" >}})
diff --git a/doc/rm/asm_coding_style.md b/doc/rm/asm_coding_style.md
deleted file mode 100644
index 7634576..0000000
--- a/doc/rm/asm_coding_style.md
+++ /dev/null
@@ -1,365 +0,0 @@
----
-title: "RISC-V Assembly Style Guide"
----
-
-## Basics
-
-### Summary
-
-OpenTitan needs to implement substantial functionality directly in RISC-V assembly.
-This document describes best practices for both assembly `.S` files and inline assembly statements in C and C++.
-It also codifies otherwise unwritten style guidelines in one central location.
-
-This document is not an introduction to RISC-V assembly; for that purpose, see the [RISC-V Assembly Programmer's Manual](https://github.com/riscv/riscv-asm-manual/blob/master/riscv-asm.md).
-
-Assembly is typically very specialized; the following rules do not presume to describe every use-case, so use your best judgement.
-
-This style guide is specialized for R32IMC, the ISA implemented by Ibex. 
-As such, no advice is provided for other RISC-V extensions, though this style guide is written such that advice for other extensions could be added without conflicts.
-
-## General Advice
-
-### Register Names
-
-***When referring to a RISC-V register, they must be referred to by their ABI names.***
-
-See the [psABI Reference](https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#register-convention) for a reference to these names.
-
-Example:
-```S
-  // Correct:
-  li a0, 42
-  // Wrong:
-  li x10, 42
-```
-
-This rule can be ignored when the ABI meaning of a register is unimportant, e.g., such as when clobbering all 31 general-purpose registers.
-
-### Pseudoinstrucions
-
-***When performing an operation for which a pseudoinstruction exists, that pseudoinstruction must be used.***
-
-Pseudoinstructions make RISC-V's otherwise verbose RISC style more readable; for consistency, these must be used where possible.
-
-Example:
-```S
-  // Correct:
-  sw t0, _my_global, t1
-  // Wrong:
-  la t1, _my_global
-  sw t0, 0(t1)
-
-  // Correct:
-  ret
-  // Wrong:
-  jr ra
-```
-
-### Operation-with-immediate mnemonics
-
-***Do not use aliases for operation-with-immediate instructions, like `add rd, rs, imm`.***
-
-Assemblers usually recognize instructions like `add t0, t1, 5` as an alias for `addi`. These should be avoided, since they are confusing and a potential source of errors.
-
-Example:
-```S
-  // Correct:
-  addi t0, t1, 0xf
-  ori  a0, a0, 0x4
-  // Wrong:
-  add  t0, t1, 0xf
-  or   a0, a0, 0x4
-```
-
-### Loading Addresses
-
-***Always use `la` to load the address of a symbol; always use `li` to load an address stored in a `#define`.***
-
-Some assemblers allow `la` with an immediate expression instead of a symbol, allowing a form of symbol+offset.
-However, support for this behavior is patchy, and the semantics of PIC `la` with immediate are unclear (in PIC mode, `la` should perform a GOT lookup, not a `pc`-relative load).
-
-### Jumping into C
-
-***Jumping into a C function must be done either with a `call` instruction, or, if that function is marked `noreturn`, a `tail` instruction.***
-
-The RISC-V jump instructions take a "link register", which holds the return address (this should always be `zero` or `ra`), and a small `pc`-relative immediate.
-For jumping to a symbol, there are two user-controlled settings: "near" or "far", and "returnable" (i.e., a link register of `zero` or `ra`).
-The mnemonics for these are:
-- `j sym`, for a near non-returnable jump.
-- `jal sym`, for a near returnable jump.
-- `tail sym`, for a far non-returnable jump (i.e., a non-unwinding tail-call).
-- `call sym`, for a far returnable jump (i.e., function calls).
-
-Far jumps are implemented in the assembler by emitting `auipc` instructions as necessary (since the jump-and-link instruction takes only a small immediate).
-Jumps into C should always be treated as far jumps, and as such use the `call` instruction, unless the C function is marked `noreturn`, in which case `tail` can be used.
-
-Example:
-```S
-  call _syscall_start
-
-  tail _crt0
-```
-
-### Control and Status Register (CSR) Names
-
-***CSRs defined in the RISC-V spec must be refered to by those names (like `mstatus`), while custom non-standard ones must be encapsulated in a `#define`.***
-
-Naturally, if a pseudoinstruction exists to read that CSR (like `rdtime`) that one should be used, instead.
-
-`#define`s for CSRs should be prefixed with `CSR_<design>_`, where `<design>` is the name of the design the CSR corresponds to.
-
-Recognized CSR prefixes:
-- `CSR_IBEX_` - A CSR specific to the Ibex core.
-- `CSR_OT_` - A CSR specific to the OpenTitan chip, beyond the Ibex core.
-
-Example:
-```S
-  csrr t0, mstatus
-
-  #define CSR_OT_HMAC_ENABLED ...
-  csrw CSR_OT_HMAC_ENABLED, 0x1
-```
-
-### Load and Store From Pointer in Register
-
-***When loading and storing from a pointer in a register, prefer to use `n(reg)` shorthand.***
-
-In the case that a pointer is being read without an offset, prefer `0(reg)` over `(reg)`.
-
-```S
-  // Correct:
-  lw t3, 8(sp)
-  sb t3, 0(a0)
-  // Wrong:
-  lw t3, sp, 8
-  sb t3, a0
-```
-
-### Compressed Instruction Mnemonics
-
-***Do not use compressed instruction mnemonics.***
-
-While Ibex implements the RISC-V C extension, it is expected that the toolchain will automatically compress instructions where possible.
-
-Of course, this advice should be ignored when it is necessary to prove that a certain block of instructions does not exceed a particular width.
-
-### "Current Point" Label
-
-***Do not use the current point (`.`) label.***
-
-The current point label does not look like a label, and can be easily missed during review.
-
-### Label Names
-
-***Local labels (for control flow) should start with `.L_`.***
-
-This is the convention for private symbols in ELF files.
-After the prefix, labels should be `snake_case` like other symbols.
-
-```S
-.L_my_label:
-  beqz a0, .L_my_label
-```
-
-## `.S` Files
-
-This advice applies specifically to `.S` files, as well as globally-scoped assembly in `.c` and `.cc` files.
-
-While this is is already implicit, we only use the `.S` extension for assembly files; not `.s` or `.asm`. Note that `.s` actually means something else; `.S`
-files have the preprocessor run on them; `.s` files do not.
-
-### Indentation
-
-Assembly files must be formatted with all directives indented two spaces, except for labels.
-Comments should be indented as usual.
-
-There is no mandated requirement on aligning instruction operands.
-
-Example:
-```S
-_trap_start:
-  csrr a0, mcause
-  sw x1, 0(sp)
-  sw x2, 4(sp)
-  // ...
-```
-
-### Comments
-
-***Comments must use either the `//` or `/* */` syntaxes.***
-
-Every function-like label which is meant to be called like a function (*especially* `.global`s) should be given a Doxygen-style comment.
-While Doxygen is not suited for assembly, that style should be used for consistency.
-See the [C/C++ style guide]({{< relref "c_cpp_coding_style" >}}) for more information.
-
-Comments should be indented to match the line immediately after. For example:
-```S
-  // This comment is correctly indented.
-  call foo
-
-// This one is not.
-  call foo
-```
-
-All other advice for writing comments, as in the C/C++ style guide, also applies.
-
-
-### Declaring a Symbol
-
-***All "top-level" symbols must have the correct preamble and footer of directives.***
-
-To aid the disassembler, every function must follow the following template:
-
-```S
-  /**
-   * Comment describing what my function does
-   */
-  .section .some_section  // Optional if the previous symbol is in this setion.
-  .balign 4
-  .global my_function  // Only for exported symbols.
-  .type my_function, @function
-my_function:
-  // Instructions and stuff.
-  .size my_function, .-my_function
-```
-
-Note that `.global` is not spelled with the legacy `.globl` spelling.
-If the symbol represents a global variable that does not consist of encoded RISC-V instructions, `@function` should be replaced with `@object`, so that the disassembler does not disassemble it as code.
-Thus, interrupt vectors, although not actually functions, are marked with  `@function`.
-
-The first instruction in the function should immediately follow the opening label.
-
-### Register useage
-
-***Register usage in a "function" that diverges from the RISC-V function call ABI must be documented.***
-
-This includes non-standard calling conventions, non-standard clobbers, and other behavior not expected of a well-behaved RISC-V function.
-Non-standard input and output registers should use Doxygen's `param[in] reg` and
-`param[out] reg` annotations, respectively.
-
-Within a function, whether or not it conforms to RISC-V's calling convention, comments should be present to describe the assignment of logical values to registers.
-
-Example:
-```S
-  /**
-   * Compute some stuff, outputing a 96-bit integer.
-   *
-   * @param[out] a0 bits [31:0] of the result.
-   * @param[out] a1 bits [63:32] of the result.
-   * @param[out] a2 bits [95:64] of the result.
-   */
-  .balign 4
-  .global compute_stuff
-  .type compute_stuff, @function
-compute_stuff:
-  // a0 is to be used as an accumulator, which will be returned as-is.
-  li a0, 0xdeadbeef
-  // t0 is a loop variable.
-  li t0, 0x0
-1:
-  // ...
-  bnez t0, 1b
-
-  li   a1, 0xbeefcafe
-  li   a2, 0xcafedead
-  ret
-  .size compute_stuff, .-compute_stuff
-```
-
-### Ending an Instruction Sequence
-
-***Every code path within an assembly file must end in a non-linking jump.***
-
-Assembly should be written such that the program counter can't wander off past the written instructions.
-As such, all assembly should be ended with `ret` (or any of the protection ring returns like `mret`), an infinite `wfi` loop, or an instruction that is guaranteed to trap and not return, like an `exit`-like syscall or `unimp`.
-
-Example:
-```S
-loop_forever:
-  wfi
-  j loop_forever
-```
-
-Functions may end without a terminator instruction if they are intended to fall
-through to the next one, so long as this is explicitly noted in a comment.
-
-### Alignment Directives
-
-***Do not use `.align`; use `.p2align` and `.balign` as the situation requires.***
-
-The exact meaning of `.align` depends on architecture; rather than asking readers to second-guess themselves, use alignment directives with strongly-typed arguments.
-
-Example:
-```S
-  // Correct:
-  .balign 8 // 8-byte aligned.
-  tail _magic_symbol
-
-  // Wrong:
-  .align 8 // Is this 8-byte aligned, or 256-byte aligned?
-  tail _magic_symbol
-```
-
-### Inline Binary Directives
-
-***Always use `.byte`/`.2byte`/`.4byte`/`.8byte` for inline binary data.***
-
-`.word`, `.long`, and friends are confusing, for the same reason `.align` is.
-
-If a sequence of zeroes is required, use `.zero count`, instead.
-
-### The `.extern` Directive
-
-***All symbols are implicitly external unless defined in the current file; there is no need to use the `.extern` directive.***
-
-`.extern` was previously allowed to "bring" symbols into scope, but GNU-flavored assemblers ignore it.
-Because it is not checked, it can bit-rot, and thus provides diminishing value.
-
-## Inline Assembly
-
-This advice applies to function-scope inline assembly in `.c` and `.cc` files.
-For an introduction on this syntax, check out [GCC's documentation](https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html).
-
-### When to Use
-
-***Avoid inline assembly as much as possible, as long as correctness and readability are not impacted.***
-
-Inline assembly is best reserved for when a high-level language cannot express what we need to do, such as expressing complex control flow or talking to the hardware.
-If a compiler intrinsic can achieve the same effect, such as `__builtin_clz()`, then that should be used instead.
-
-> The compiler is *always* smarter than you; only in the rare case where it is not, assembly should be used instead.
-
-### Formatting
-
-Inline assembly statements must conform to the following formatting requirements, which are chosen to closely resemble how Google's clang-format rules format function calls.
-- Neither the `asm` or `__asm__` keyword is specified in C; the former must be used, and should be `#define`d into existence if not supported by the compiler.
-  C++ specifies `asm` to be part of the grammar, and should be used exclusively.
-- There should not be a space after the `asm` qualfiers and the opening parentheses:
-  ```c
-  asm(...);
-  asm volatile(...);
-  ```
-- Single-instruction `asm` statements should be written on one line, if possible:
-  ```c
-  asm volatile("wfi");
-  ```
-- Multiple-instruction `asm` statements should be written with one instruction per line, formatted as follows:
-  ```c
-  asm volatile(
-    "my_label:"
-    "  la   sp, _stack_start;"
-    "  tail _crt0;"
-    ::: "memory");
-  ```
-- The colons separating register constraints should be surrounded with spaces, unless there are no constraints between them, in which case they should be adjacent.
-  ```c
-  asm("..." : "=a0"(foo) :: "memory");
-  ```
-
-### Non-returning `asm`
-
-***Functions with non-returning `asm` must be marked as `noreturn`.***
-
-C and C++ compilers are, in general, not supposed to introspect `asm` blocks, and as such cannot determine that they never return.
-Functions marked as never returning should end in `__builtin_unreachable()`, which the compiler will usually turn into an `unimp`.
-
diff --git a/doc/rm/c_cpp_coding_style.md b/doc/rm/c_cpp_coding_style.md
deleted file mode 100644
index a7c6d09..0000000
--- a/doc/rm/c_cpp_coding_style.md
+++ /dev/null
@@ -1,455 +0,0 @@
----
-title: "C and C++ Coding Style Guide"
----
-
-## Basics
-
-### Summary
-
-C and C++ are widely used languages for (embedded) software.
-
-Our C and C++ style guide follows the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), with some exceptions and clarifications.
-
-As with all style guides the intention is to:
-*   promote consistency across projects
-*   promote best practices
-*   increase code sharing and re-use
-
-
-### Terminology Conventions
-
-Unless otherwise noted, the following terminology conventions apply to this
-style guide:
-
-*   The word ***must*** indicates a mandatory requirement.
-    Similarly, ***do not*** indicates a prohibition.
-    Imperative and declarative statements correspond to ***must***.
-*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
-    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
-    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
-*   The word ***may*** indicates a course of action is permitted and optional.
-*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
-
-## Shared C and C++ Style Guide
-
-We use the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) for both C and C++ code.
-The following exceptions and additions to this style guide apply to both C and C++ code.
-
-### Pointers
-
-***When declaring pointer types, the asterisk (`*`) should be placed next to the variable name, not the type.***
-
-Example:
-
-```c
-int *ptr;
-```
-
-### Formatting of loops and conditionals
-
-***Single-statement blocks are not allowed. All conditions and loops must use braces.***
-
-Example:
-```c
-if (foo) {
-  do_something();
-}
-```
-
-### Comments
-
-***Comments should be `// C99-style` for consistency with C++.***
-
-<!-- To render a backtick in inline code in markdown, you need to double the surrounding backticks.
-https://daringfireball.net/projects/markdown/syntax#code -->
-***Variables mentioned in comments should be delimited with backtick (`` ` ``) characters.***
-
-Example:
-
-```c
-// `ptr` can never be NULL for reasons.
-```
-
-Note also [Public function (API) documentation](#public-function-api-documentation) below.
-
-### TODO Comments
-***TODO comments should be in the format `TODO: message`.***
-
-***TODO comments which require more explanation should reference an issue.***
-
-It is recommended to use fully-qualified issue numbers or URLs when referencing issues or pull requests.
-
-TODO comments should not indicate an assignee of the work.
-
-Example:
-
-```c
-// TODO: This algorithm should be rewritten to be more efficient.
-// (Bug lowrisc/reponame#27)
-```
-
-### Included files
-
-***`#include` directives must, with exceptions, be rooted at `$REPO_TOP`.***
-
-Every `#include` directive must be rooted at the repository base, including files in the same directory.
-This helps the reader quickly find headers in the repository, without having to worry about local include-search rules.
-
-Example: `my/device/library.c` would start with a directive like the following:
-
-```c
-#include "my/device/library.h"
-```
-
-This rule does not apply to generated headers, since they do not yet have a designated location in the source tree, and are instead included from ad-hoc locations.
-Until these questions are resolved, these includes must be marked as follows:
-```c
-#include "my_generated_file.h"  // Generated.
-```
-This convention helps readers distinguish which files they should not expect to find in-tree.
-
-The above rules also do not apply to system includes, which should be included by the names dictated by the ISO standard, e.g. `#include <stddef.h>`.
-
-### Linker Script- and Assembly-Provided Symbols
-
-Some C/C++ programs may need to use symbols that are defined by a linker script or in an external assembly file.
-Referring to linker script- and assembly-provided symbols can be complex and error-prone, as they don't quite work like C's global variables.
-We have chosen the following approach based on the examples in [the binutils ld manual](https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html).
-
-If you need to refer to the symbol `_my_linker_symbol`, the correct way to do so is with an incomplete extern char array declaration, as shown below.
-It is good practice to provide a comment that directs others to where the symbol is actually defined, and whether the symbol should be treated as a memory address or another kind of value.
-```c
-/**
- * `_my_linker_symbol` is defined in the linker script `sw/device/my_feature.ld`.
- *
- * `_my_linker_symbol` is a memory address in RAM.
- */
-extern char _my_linker_symbol[];
-```
-
-A symbol's value is exposed using its address, and declaring it this way allows you to use the symbol where you need a pointer.
-```c
-char my_buffer[4];
-memcpy(my_buffer, _my_linker_symbol, sizeof(my_buffer));
-```
-
-If the symbol has been defined to a non-address value (usually using `ABSOLUTE()` in a linker script, or `.set` in assembly), you must cast the symbol to obtain its value using `(intptr_t)_my_linker_symbol`.
-You must not dereference a symbol that has non-address value.
-
-### Public function (API) documentation
-
-***It is recommended to document public functions, classes, methods, and data structures in the header file with a Doxygen-style comment.***
-
-The first line of the comment is the summary, followed by a new line, and an optional longer description.
-Input arguments and return arguments can be documented with `@param` and `@return` if they are not self-explanatory from the name.
-
-The documentation tool will also render markdown within descriptions, so backticks should be used to get monospaced text.
-It can also generate references to other named declarations using `#other_function` (for C-style declarations), or `ns::foo` (for C++ declarations).
-
-Example:
-
-```c
-/**
- * Do something amazing
- *
- * Create a rainbow and place a unicorn at the bottom of it. `pots_of_gold`
- * pots of gold will be positioned on the east end of the rainbow.
- *
- * Can be recycled with #recycle_rainbow.
- *
- * @param pots_of_gold Number of gold pots to place next to the rainbow
- * @param unicorns Number of unicorns to position on the rainbow
- * @return 0 if the function was successful, -1 otherwise
- */
-int create_rainbow(int pots_of_gold, int unicorns);
-```
-
-### Polyglot headers
-
-***Headers intended to be included from both languages must contain `extern` guards; `#include`s should not be wrapped in `extern "C" {}`.***
-
-A *polyglot header* is a header file that can be safely included in either a `.c` or `.cc` file.
-In particular, this means that the file must not depend on any of the places where C and C++ semantics disagree.
-For example:
-- `sizeof(struct {})` and `sizeof(true)` are different in C and C++.
-- Function-scope `static` variables generate lock calls in C++.
-- Some libc macros, like `static_assert`, may not be present in C++.
-- Character literals type as `char` in C++ but `int` in C.
-
-Such files must be explictly marked with `extern` guards like the following, starting after the file's `#include`s.
-```
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// Declarations...
-
-#ifdef __cplusplus
-}  // extern "C"
-#endif
-```
-Moreover, all non-system `#includes` in a polyglot header must also be of other polyglot headers.
-(In other words, all C system headers may be assumed to be polyglot, even if they lack guards.)
-
-Additionally, it is forbidden to wrap `#include` statements in `extern "C"` in a C++ file.
-While this does correctly set the ABI for a header implemented in C, that header may contain code that subtly depends on the peculiarities of C.
-
-This last rule is waived for third-party headers, which may be polyglot but not declared in our style.
-
-### X Macros
-
-In order to avoid repetitive definitions or statements, we allow the use of [X macros](https://en.wikipedia.org/wiki/X_Macro) in our C and C++ code.
-
-Uses of X Macros should follow the following example, which uses this pattern in a switch definition:
-```c
-#define MANY_FIELDS(X) \
-  X(1, 2, 3)           \
-  X(4, 5, 6)
-
-int get_field2(int identifier) {
-#define ITEM_(id_field, data_field1, data_field2) \
-  case id_field:                               \
-    return data_field2;
-
-  switch (identifier) {
-    MANY_FIELDS(ITEM_)
-    default:
-      return 0;
-  }
-}
-```
-This example expands to a case statement for each item, which returns the `data_field2` value where the passed in identifier matches `id_field`.
-
-X macros that are not part of a header's API should be `#undef`ed after they are not needed.
-Similarly, the arguments to an X macro, if they are defined in a header, should be `#undef`ed too.
-This is not necessary in a `.c` or `.cc` file, where this cannot cause downstream namespace pollution.
-
-## C++ Style Guide {#cxx-style-guide}
-
-### C++ Version {#cxx-version}
-
-C++ code should target C++14.
-
-### Aggregate Initializers
-
-***C++20-style designated initializers are permitted in C++ code, when used with POD types.***
-
-While we target C++14, both GCC and Clang allow C++20 designated initializers in C++14-mode, as an extension:
-```
-struct Foo { int a, b; };
-
-Foo f = { .a = 1, .b = 42, };
-```
-
-This feature is fairly mature in both compilers, though it varies from the C11 variant in two ways important ways:
-  - It can only be used with structs and unions, not arrays.
-  - Members must be initialized in declaration order.
-
-Because it is especially useful with types declared for C, we allow designatued initializers whenever the type is a plain-old-data type, and:
-  - All members are public.
-  - It has no non-trivial constructors.
-  - It has no `virtual` members.
-
-Furthermore, designated initializers do not play well with type deduction and overload resolution.
-As such, they are forbidden in the following contexts:
-- Do not call overloaded functions with a designated initializer: `overloaded({ .foo = 0 })`.
-  Instead, disambiguate with syntax like `T var = { .foo = 0 }; overloaded(var);`.
-- Do not use designated initializers in any place where they would be used for type defuction.
-  This includes `auto`, such as `auto var = { .foo = 0 };`, and a templated argument in a template function.
-
-It is recommended to only use designated initializers with types which use C-style declarations.
-
-## C Style Guide
-
-The [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) targets C++, but it can also be used for C code with minor adjustments.
-Consequently, C++-specific rules don't apply.
-In addition to the shared C and C++ style guide rules outlined before, the following C-specific rules apply.
-
-### C Version
-
-***C code should target C11.***
-
-The following nonstandard extensions may be used:
-*   Inline assembly
-*   Nonstandard attributes
-*   Compiler builtins
-
-It is recommended that no other nonstandard extensions are used.
-
-Any nonstandard features that are used must be compatible with both GCC and Clang.
-
-### Function, enum, struct and typedef naming
-
-***Names of functions, `enum`s, `struct`s, and `typedef`s must be `lower_snake_case`.***
-
-This rule deviates from the Google C++ style guide to align closer with a typical way of writing C code.
-
-***All symbols in a particular header must share the same unique prefix.***
-
-"Prefix" in this case refers to the identifying string of words, and not the specific type/struct/enum/constant/macro-based capitalisation.
-This rule also deviates from the Google C++ style guide, because C does not have namespaces, so we have to use long names to avoid name clashes.
-Symbols that have specific, global meaning imparted by an external script or specification may break this rule.
-For example:
-```c
-// in my_unit.h
-extern const int kMyUnitMaskValue = 0xFF;
-
-typedef enum { kMyUnitReturnOk } my_unit_return_t;
-
-my_unit_return_t my_unit_init(void);
-```
-
-***The names of enumeration constants must be prefixed with the name of their respective enumeration type.***
-
-Again, this is because C does not have namespaces.
-The exact capitalisation does not need to match, as enumeration type names have a different capitalisation rule to enumeration constants.
-For example:
-```c
-typedef enum my_wonderful_option {
-  kMyWonderfulOptionEnabled,
-  kMyWonderfulOptionDisabled,
-  kMyWonderfulOptionOnlySometimes
-} my_wonderful_option_t;
-```
-
-### C-specific Keywords
-
-C11 introduces a number of undescore-prefixed keywords, such as `_Static_assert`, `_Bool`, and `_Noreturn`, which do not have a C++ counterpart.
-These should be avoided in preference for macros that wrap them, such as `static_assert`, `bool`, and `noreturn`.
-
-### Preprocessor Macros
-
-Macros are often necessary and reasonable coding practice C (as opposed to C++) projects.
-In contrast to the recommendation in the Google C++ style guide, exporting macros as part of the public API is allowed in C code.
-A typical use case is a header with register definitions.
-
-### Aggregate Initialization
-
-C99 introduces designated initializers: when initializing a type of struct, array, or union type, it is possible to *designate* an initializer as being for a particular field or array index.
-For example:
-```c
-my_struct_t s = { .my_field = 42 };
-int arr[5] = { [3] = 0xff, [4] = 0x1b };
-```
-With judicious use, designated initializers can make code more readable and robust; struct field reordering will not affect downstream users, and weak typing will not lead to surprising union initialization.
-
-When initializing a struct or union, initializers within *must* be designated; array-style initialization (or mixing designated and undesignated initializers) is forbidden.
-
-Furthermore, the nested forms of designated initialization are forbidden (e.g., `.x.y = foo` and `.x[0] = bar`), to discourage initialization of deeply nested structures with flat syntax.
-This may change if we find cases where this initialization improves readability.
-
-When initializing an array, initializers *may* be designated when that makes the array more readable (e.g., lookup tables that are mostly zeroed).
-Mixing designated and undesignated initializers, or using nested initializers, is still forbidden.
-
-### Function Declarations
-
-***All function declarations in C must include a list of the function's parameters, with their types.***
-
-C functions declared as `return_t my_function()` are called "K&R declarations", and are type compatible with any list of arguments, with any types.
-Declarations of this type allow type confusion, especially if the function definition is not available.
-
-The correct way to denote that a function takes no arguments is using the parameter type `void`.
-For example `return_t my_function(void)` is the correct way to declare that `my_function` takes no arguments.
-
-The parameter names in every declaration should match the parameter names in the function definition.
-
-### Inline Functions
-
-Functions that we strongly wish to be inlined, and which are part of a public interface, should be declared as an inline function.
-This annotation serves as an indication to the programmer that the function has a low calling overhead, despite being part of a public interface.
-Presence---or lack---of an `inline` annotation does not imply a function will---or will not---be inlined by the compiler.
-
-[C11](#c-version) standardised inline functions, learning from the mistakes in C++ and various nonstandard extensions.
-This means there are many legacy ways to define an inline function in C.
-We have chosen to follow how C11 designed the `inline` keyword.
-
-The function definition is written in a header file, with the keyword `inline`:
-```c
-// in my_inline.h
-inline int my_inline_function(long param1) {
-  // implementation
-}
-```
-
-There should be exactly one compilation unit with a compatible `extern` declaration of the same function:
-```c
-// in my_inline.c
-#include <my_inline.h>
-extern int my_inline_function(long param1);
-```
-
-Any compilation unit that includes `my_inline.h` must be linked to the compilation unit with the extern declarations.
-This ensures that if the compiler chooses not to inline `my_inline_function`, there is a function definition that can be called.
-This also ensures that the function can be used via a function pointer.
-
-### Static Declarations
-
-Declarations marked `static` must not appear in header files.
-Header files are declarations of public interfaces, and `static` definitions are copied, not shared, between compilation units.
-
-This is especially important in the case of a polyglot header, since function-local static declarations have different, incompatible semantics in C and C++.
-
-Functions marked `static` must not be marked `inline`.
-The compiler is capable of inlining static functions without the `inline` annotation.
-
-### Nonstandard Attributes
-
-The following nonstandard attributes may be used:
-*   `section(<name>)` to put a definition into a given object section.
-*   `weak` to make a symbol definition have weak linkage.
-*   `interrupt` to ensure a function has the right prolog and epilog for interrupts (this involves saving and restoring more registers than normal).
-*   `packed` to ensure a struct contains no padding.
-*   `warn_unused_result`, to mark functions that return error values that should be checked.
-
-It is recommended that other nonstandard attributes are not used, especially where C11 provides a standard means to accomplish the same thing.
-
-All nonstandard attributes must be supported by both GCC and Clang.
-
-Nonstandard attributes must be written before the declaration, like the following example, so they work with both declarations and definitions.
-
-```c
-__attribute__((section(".crt"))) void _crt(void);
-```
-
-### Nonstandard Compiler Builtins
-
-In order to avoid a total reliance on one single compiler, any nonstandard compiler builtins (also known as intrinsics) should be used via a single canonical definition.
-This ensures changes to add compatibilty for other compilers are less invasive, as we already have a function to include a full implementation within.
-
-All nonstandard builtins should be supported by both GCC and Clang.
-Compiler builtin usage is complex, and it is recommended that a compiler engineer reviews any code that adds new builtins.
-
-In the following, `__builtin_foo` is the *builtin name* and `foo` is the corresponding *general name*.
-
-***The use of nonstandard compiler builtins must be hidden using a canonical, compatible definition.***
-
-There are two ways of providing this canonical definition, depending on what the builtin does.
-
-For builtins that correspond to a C library function, the general name must be available to the linker, as the compiler may still insert a call to this function.
-Unfortunately, older versions of GCC do not support the `__has_builtin()` preprocessor function, so compiler detection of support for these builtins is next to impossible.
-In this case, a standards-compliant implementation of the general name must be provided, and the compilation unit should be compiled with `-fno-builtins`.
-
-For builtins that correspond to low-level byte and integer manipulations, an [inline function](#inline-functions) should be provided with a general name, which contains a call to the builtin name itself, or an equivalent implementation.
-Only the general name may be called by users: for instance, `uint32_t __builtin_bswap32(uint32_t)` must not be called, instead users should use `inline uint32_t bswap32(uint32_t x)`.
-Where the general name is already taken by an incompatible host or device library symbol, the general name can be prefixed with the current C namespace prefix, for instance `inline uint32_t bitfield_bswap32(uint32_t x)` for a function in `bitfield.h`.
-Where the general name is a short acronym, the name may be expanded for clarity, for instance `__builtin_ffs` may have a canonical definition named `bitfield_find_first_set`.
-Where there are compatible typedefs that convey additional meaning (e.g. `uint32_t` vs `unsigned int`), these may be written instead of the official builtin types.
-
-For builtins that cannot be used via a compatible function definition (e.g. if an argument is a type or identifier), there should be a single canonical preprocessor definition with the general name, which expands to the builtin.
-
-## Code Lint
-
-The clang-format tool can check for adherence to this style guide.
-The repository contains a `.clang-format` file which configures clang-format according to the rules outlined in this style guide.
-
-You can run clang-format on you changes by calling `git clang-format`.
-
-```sh
-cd $REPO_TOP
-# make changes to the code ...
-git add your_modified_file.c
-# format the staged changes
-git clang-format
-```
-
-To reformat the whole tree the command `./bazelisk.sh run //:clang_format_fix` can be used.
diff --git a/doc/rm/comportability_specification/index.md b/doc/rm/comportability_specification/index.md
deleted file mode 100644
index 5d93d73..0000000
--- a/doc/rm/comportability_specification/index.md
+++ /dev/null
@@ -1,652 +0,0 @@
----
-title: "Comportability Definition and Specification"
----
-
-## Document Goals
-
-This document is aimed at laying out the definition of a *Comportable IP design*, i.e. one that is ported to conform to the framework of lowRISC ecosystem IP, suitable for inclusion in compliant designs.
-This is primarily a technical discussion and specification of interface compliance within the framework.
-Separate documents contain or will contain critical elements like coding style, verification, and documentation, but are not the purview of this specification.
-
-A good definition of Comportable can be found in
-[Johnson's Dictionary of the English Language](https://en.wikipedia.org/wiki/A_Dictionary_of_the_English_Language).
-The 1808 miniature edition gives
-["Comportable, a. consistent, suitable, fit"](https://books.google.co.uk/books?id=JwC-GInMrW4C&dq=%22comportable%22&pg=PA45&ci=31%2C225%2C415%2C42&source=bookclip)
-
-![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)
-
-
-
-## Definitions
-
-The table below lists some keywords used in this specification.
-
-| Keyword | Definition |
-| --- | --- |
-| alerts      | Interrupt-type outputs of IP designs that are classified as security critical. These have special handling in the outer chip framework. |
-| comportable | A definition of compliance on the part of IP that is able to plug and play with other IP to form the full chip framework. |
-| CSRs        | Control and Status Registers; loosely the collection of registers within a peripheral which are addressable by the (local) host processor via a chip-wide address map.  Special care is dedicated to the definition and handling of CSRs to maximize software uniformity and re-use, as well as documentation consistency. |
-| framework   | this project concerns itself not only with compliant IP, but also provides a full chip framework suitable for FPGA implementation, and prepared to be the foundation for a full silicon implementation. This could roughly be translated as Top Level Netlist. |
-| interrupts  | Non-security critical signals from peripheral devices to the local host processor within the framework SOC. |
-| MIO         | Multiplexable IO; a pad at the top chip level which can be connected to one of the peripherals' MIO-ready inputs or outputs. |
-| peripheral  | Any comportable IP that is part of the library, outside of the local host processor. |
-
-## Non-Technical Comportability Requirements
-
-All comportable IP must adhere to a few requirements, briefly discussed here.
-
-### License and copyright
-
-All files should include a comment with a copyright message.
-This is normally "lowRISC contributors".
-The style is to not include a year in the notice.
-Files adapted from other sources should retain any copyright messages and include details of the upstream location.
-
-The Apache License, Version 2.0 is the default for all files in the repository.
-Use of other licenses must be noted (and care is needed to ensure compatibility with the rest of the code).
-All files should include a comment line with the SPDX-License-Identifier: tag and the Identifier from the [License List](https://spdx.org/licenses/).
-An additional "Licensed under" line may be used to give a more human readable version.
-If the file is not covered by a SPDX license then the "Licensed under" line is required (note that such files are unlikely to be permitted in the main open source repository).
-
-All files that use the default copyright and license should therefore include the following header (change the comment character as appropriate):
-
-```
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-```
-
-The project has adopted [Hjson](https://hjson.org) for JSON files, extending JSON to allow comments.
-Thus the Hjson files can include the header above.
-If pure JSON must be used for some reason, the "SPDX-License-Identifier:" can be added as the first key after the opening "{".
-Tools developed by the project should accept and ignore this key.
-
-### Coding Style
-
-All IP must follow the [lowRISC Verilog Coding Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
-This style guide sets the definition of agreed-upon SystemVerilog style, requirements and preferences.
-See that document for details.
-It is the goal of lowRISC to create technical collateral to inform when an IP does not conform, as well as assist in the formating of Verilog to this style.
-The methods and details for this collateral have not been agreed upon yet.
-
-### Documentation
-
-All lowRISC IP must conform to a common specification and documentation format.
-lowRISC will release a template for IP specifications in a separate document for reference.
-It is notable that register tooling auto-creates documentation material for register definitions, address maps, hardware interfaces, etc.
-The hardware interfaces of this process is discussed later in this document.
-
-## Comportable Peripheral Definition
-
-All comportable IP peripherals must adhere to a minimum set of functionality in order to be compliant with the framework that is going to be set around it.
-(An example framework is the [earlgrey top level design](../../hw/top_earlgrey/doc/top_earlgrey.md).)
-This includes several mandatory features as well as several optional ones.
-It is notable that the framework contains designs that are neither the local host processor nor peripherals \- for example the power management unit, clock generators.
-These are handled as special case designs with their own specifications.
-Similarly the memory domain is handled separately and in its own specification.
-
-Examples of peripherals that are expected to be in this category include ones with primary inputs and outputs (SPI, I2C, etc);
-offload and specialty units (crypto, TRNG, key manager); timers; analog designs (temperature sensor); as well as bus hosts<sup>1</sup> (DMA).
-
-<sup>1</sup>lowRISC is avoiding the fraught terms master/slave and defaulting to host/device where applicable.
-
-### Feature List
-
-All comportable designs must specify and conform to a list of mandatory features, and can optionally specify and conform to a list of optional features.
-These are briefly summarized in the table below, and are covered individually in the sections that follow.
-For most of these, the definition of the feature is in the form of a configuration file.
-This file is specified later within this document.
-
-| Feature | Mand/Opt | Description |
-| ---     | ---      | --- |
-| Clocking     | mandatory | Each peripheral must specify what its primary functional clock is, and any other clocks needed.  The primary clock is the one driving the bus the peripheral is receiving.  The clocking section lists the available clocks. Other clocks can be designated as needed. |
-| Bus Interfaces | mandatory | A list of the bus interfaces that the peripheral supports. This must contain at least one device entry. More details in the Bus Interfaces section. |
-| Available IO | optional  | Peripherals can optionally make connections to dedicated or multiplexed IO pins; the chip peripheral needs to indicate its module inputs and outputs that are available for this purpose. Details are available in the peripheral IO section below. |
-| Registers    | mandatory | Each peripheral must define its collection of registers in the specified register format.  The registers are automatically generated in the form of hardware, software, and documentation collateral. Details are available in the register section. |
-| Interrupts   | optional  | Peripherals have the option of generating signals that can be used to interrupt the primary processor.  These are designated as a list of signals, and each results in a single wire or bundled output that is sent to the processor to be gathered as part of its interrupt vector input.  Details can be found in the interrupt and alert section. |
-| Alerts       | optional  | Peripherals have the option of generating signals that indicate a potential security threat. These are designated as a list of signals, and each results in a complementary signal pair that is sent to the alert handling module.  Details are found in the interrupt and alert section. |
-| Inter Signal | optional  | Peripherals have the option of struct signals that connect from/to other peripherals, which are called as Inter-Module Signals in OpenTitan. Details are found in the inter module section.
-| (more)       |           | More will come later, including special handling for testability, power management, device entropy, etc. |
-
-![Typical Peripheral Block Diagram](comportability_diagram_peripheral.svg)
-
-**Figure 1**: Typical peripheral communication channels within full chip framework.
-
-In this diagram the auto-generated register submodule is shown within the peripheral IP, communicating with the rest of the chip framework using the TL-UL (see below) bus protocol.
-This register block communicates with the rest of the peripheral logic to manage configuration and status communication with software.
-Also shown is the mandatory clock, and the optional bus (TL-UL) host, interrupts, alerts, and chip IO.
-
-Additionally an optional input `devmode` is shown which represents an indication to the peripheral what mode the SOC is operating in.
-For now this includes only two modes: development (`devmode = 1`) and production (`devmode = 0`).
-This is the beginning of a security feature that will convey the full life cycle mode status to the peripheral.
-In its current form, only the distinction of development mode vs. production mode is required in order to determine how to handle software errors (see the [Register Tooling]({{< relref "doc/rm/register_tool" >}}) documentation for details).
-The full definition of life cycle modes will come upon further refinement of the security properties of the SOC.
-
-## Peripheral Feature Details
-
-### Configuration File
-
-Each peripheral contains a configuration file that describes the peripheral features that are mandatory and optional in the above comportability feature list.
-The configuration file format is given below.
-
-### Clocking
-
-Each peripheral specifies how it is clocked and reset.
-This is done with the `clocking` element, whose value is a nonempty list of dictionaries.
-Each dictionary (called a *clocking item*) defines a clock and reset signal.
-The clock signal is the field called `clock`.
-The reset signal is the field called `reset`.
-
-One of the clocking items is called the *primary clock*.
-If there is just one item, that is the primary clock.
-If there are several, exactly one of the items' dictionaries should also have a boolean field called `primary` set to true.
-This primary clock used to clock any device interfaces, indicating to the top level if asynchronous handling of the bus interface is needed.
-
-Resets within the design are **asynchronous active low** (see below).
-Special care will be required for security sensitive storage elements.
-Further instructions on the handling of these storage elements will come at a later date.
-
-For most blocks, each clocking item has one clock and one reset signal.
-However, there are a few blocks where this might not be true (blocks that generate clocks or resets).
-To allow these blocks to be described, the `clock` and `reset` fields of an item are optional.
-However, the primary clock must have both.
-
-#### Details and rationale for asynchronous active low reset strategy
-
-Resets within the design are asynchronous active low, where the assertion of the reset is asynchronous to any clock, but deassertion is synchronized to the clock of the associated storage element.
-The selection of asynchronous active low (as opposed to say synchronous active high) was made based upon a survey of existing design IP, comfort level of project team members, and through security analysis.
-The conclusion roughly was the following:
-
-1. Security storage elements might "leak" sensitive state content, and should be handled with care regardless of reset methodology.
-By "care" an example would be to reset their value synchronously at a time after chip-wide reset, to a value that is randomized so that the Hamming distance between the register value and all zeros cannot produce information available to an attacker.
-2. For control path and other storage elements, the selection of asynchronous active low vs. synchronous active high is often a "religious" topic, with both presenting pros and cons.
-3. Asynchronous active low incurs slightly more area and requires more hand-holding, but is more common.
-4. Synchronous active high is slightly more efficient, but requires the existence of a clock edge to take effect.
-
-Based upon this and the fact that much of the team history was with asynchronous active low reset, we chose that methodology with added requirements that special care be applied for security state, the details of which will come at a later date.
-
-### Bus Interfaces {#bus-interfaces}
-
-Peripherals can connect to the chip bus.
-All peripherals are assumed to have registers, and are thus required to expose at least one device interface on the chip bus.
-Peripherals can act as a bus host on some occasions, though for full chip simplicity the preferred model is for the processor to be primary host.
-An example of a peripheral that acts as a host is a DMA unit.
-
-The arrangement of peripherals' device interfaces into a system-wide address map is controlled by a higher level full-chip configuration file.
-Addresses within the block of addresses assigned to a device interface are defined by the peripheral's configuration file.
-
-The `bus_interfaces` attribute in the configuration file should contain a list of dictionaries, describing the interfaces that the peripheral exposes.
-The full syntax for an entry in the list looks like this:
-```
-{ protocol: "tlul", direction: "device", name: "my_interface" }
-```
-
-For each entry, the `protocol` field gives the protocol that the peripheral uses to connect to the bus.
-All peripherals use TileLink-UL (TileLink-Uncached-Lite, aka TL-UL) as their interface to the framework.
-To signify this, the peripheral must have a protocol of `tlul`.
-The TileLink-UL protocol and its usage within lowRISC devices is given in the
-[TileLink-UL Bus Specification]({{< relref "hw/ip/tlul/doc" >}}).
-As of this writing, there are no other options, but this field leaves an option for extension in the future.
-
-Each entry must also contain a `direction`.
-This must either be `host` or `device`.
-All bus hosts must use the same clock as the defined primary host clock.
-Each bus host is provided a 4-bit host ID to distinguish hosts within the system.
-This is done by the framework in order to ensure uniqueness.
-The use of the ID within the bus fabric is discussed in the bus specification.
-
-An entry may also include a name.
-This name is added as a prefix to the module's top-level port names.
-This is optional unless there is another entry in the list with the same direction (in which case, the port names would collide).
-
-### Available IO
-
-Each peripheral has the option of designating signals (inputs, outputs, or inouts) available to be used for chip IO.
-The framework determines for each signal if it goes directly to a dedicated chip pin or is multiplexed with signal(s) from other peripherals before reaching a pin.
-
-Designation of available IO is given with the configuration file entries of `available_input_list`, `available_output_list`, and `available_inout_list`.
-These can be skipped, or contain an empty list `[]`, or a comma-separated list of signal names.
-Items on the input list of the form `name` incur a module input of the form `cio_name_i`.
-Items on the output list of the form `name` incur a module output of the form `cio_name_o` as well as an output enable `cio_name_en_o`.
-Items on the inout list of the form `name` incur all three.
-
-#### Multiplexing Feature and Pad Control
-
-In the top level chip framework there is a [pin multiplexing unit (`pinmux`)]({{< relref "/hw/ip/pinmux/doc" >}}) which provides flexible assignment to/from peripheral IO and chip pin IO.
-Comportable peripherals do not designate whether their available IO are hardwired to chip IO, or available for multiplexing.
-That is done at the top level with an Hjson configuration file.
-See the top level specification for information about that configuration file.
-
-In addition, full pad control is not done by the peripheral logic, but is done, by the `pinmux` as well.
-The `pinmux` module provides software configuration control over pad drive strength, pin mapping, pad type (push/pull, open drain, etc).
-
-### Interrupts
-
-Each peripheral has the option of designating output signals as interrupts destined for the local host processor.
-These are non-security-critical signals sent to the processor for it to handle with its interrupt service routines.
-The peripheral lists its collection of interrupts with the `interrupt_list` attribute in the configuration file.
-Each item of the form `name` in the interrupt list expects a module output named `intr_name_o`.
-
-See the section on [Interrupt Handling](#interrupt-handling) below, which defines details on register, hardware, and software uniformity for interrupts within the project.
-
-### Alerts
-
-Each peripheral has the option of designating output signals as security critical alerts destined for the hardware [alert handler module]({{< relref "/hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}).
-These are differential signals (to avoid single point of failure) sent to the alert handler for it to send to the processor for first-line defense handling, or hardware security response if the processor does not act.
-The peripheral lists its collection of alerts with the `alert_list` attribute in the configuration file.
-For each alert in the full system, a corresponding set of signals will be generated in the alert handler to carry out this communication between alert sender and handler.
-
-See the section on [Alert Handling](#alert-handling) below, which defines details on register, hardware, and software uniformity for alerts within the project.
-
-### Inter-module signal
-
-The peripherals in OpenTitan have optional signals connecting between the peripherals other than the interrupts and alerts.
-The peripheral lists its collection of inter-module signals with the `inter_signal_list` attribute in the configuration file.
-The peripheral defines the type of inter-module signals.
-The connection between the modules are defined in the top-level configuration file.
-
-See the section on [Inter Signal Handling](#inter-signal-handling) below for detailed data structure in the configuration file.
-
-### Security countermeasures {#countermeasures}
-
-If this IP block is considered security-critical, it will probably have design features that try to mitigate against attacks like fault injection or side channel analysis.
-These features can be loosely categorised and labelled with pairs of the form `ASSET.CM_TYPE`.
-Here, `ASSET` is the asset that is being protected.
-This might be secret information like a key, or it might be internal state like a processor's control flow.
-The countermeasure that is providing the protection is named with `CM_TYPE`.
-
-The following standardised assets are defined:
-
-| Asset name | Intended meaning |
-| --- | --- |
-| KEY         | A key (secret data) |
-| ADDR        | An address |
-| DATA_REG    | A configuration data register that doesn't come from software (such as Keccak state) |
-| DATA_REG_SW | A data holding register that is manipulated by software |
-| CTRL_FLOW   | The control flow of software or a module |
-| CTRL        | Logic used to steer hardware behavior |
-| CONFIG      | Software-supplied configuration, programmed through the comportable register interface |
-| LFSR        | A linear feedback shift register |
-| RNG         | A random number generator |
-| CTR         | A counter |
-| FSM         | A finite state machine |
-| MEM         | A generic data memory; volatile or non-volatile |
-| CLK         | A clock |
-| RST         | A reset signal |
-| BUS         | Data transferred on a bus |
-| INTERSIG    | A non-bus signal between two IP blocks |
-| MUX         | A multiplexer that controls propagation of sensitive data |
-| CONSTANTS   | A netlist constant |
-| STATE       | An internal state signal (other than FSM state, which is covered by the FSM label) |
-| TOKEN       | A cryptographic token |
-| LOGIC       | Any logic. This is a very broad category: avoid if possible and give an instance or net name if not. |
-
-The following standardised countermeasures are defined:
-
-| Countermeasure name | Intended meaning | Commonly associated assets |
-| --- | --- | --- |
-| MUBI           | A signal is multi-bit encoded | CTRL, CONFIG, CONSTANTS, INTERSIG |
-| SPARSE         | A signal is sparsely encoded  | FSM |
-| DIFF           | A signal is differentially encoded | CTRL, CTR |
-| REDUN          | There are redundant versions of the asset | ADDR, CTRL, CONFIG, CTR
-| REGWEN         | A register write enable is used to protect the asset from write access | CONFIG, MEM
-| SHADOW         | The asset has a shadow replica to cross-check against | CONFIG
-| REGREN         | A register write enable is used to protect the asset from read access | CONFIG, MEM
-| SCRAMBLE       | The asset is scrambled | CONFIG, MEM
-| INTEGRITY      | The asset has integrity protection from a computed value such as a checksum | CONFIG, REG, MEM
-| CONSISTENCY    | This asset is checked for consistency other than by associating integrity bits | CTRL, RST
-| DIGEST         | Similar to integrity but more computationally intensive, implying a full hash function | CONFIG, REG, MEM
-| LC_GATED       | Access to the asset is qualified by life-cycle state | REG, MEM, CONSTANTS, CONFIG
-| BKGN_CHK       | The asset is protected with a continuous background health check |
-| GLITCH_DETECT  | The asset is protected by an analog glitch detector | CTRL, FSM, CLK, RST
-| SW_UNREADABLE  | The asset is not readable by software | MEM, KEY
-| SW_UNWRITABLE  | The asset is not writable by software | MEM, KEY
-| SW_NOACCESS    | The asset is not writable nor readable by software (SW_UNWRITABLE and SW_UNREADABLE at the same time) | MEM, KEY
-| SIDELOAD       | The asset can be loaded without exposing it to software | KEY
-| SEC_WIPE       | The asset is initialized or cleared using pseudo-random data | KEY, DATA_REG, MEM
-| SCA            | A countermeasure that provides side-channel attack resistance |
-| MASKING        | A more specific version of SCA where an asset is split into shares |
-| LOCAL_ESC      | A local escalation event is triggered when an attack is detected |
-| GLOBAL_ESC     | A global escalation event is triggered when an attack is detected |
-| UNPREDICTABLE  | Behaviour is unpredictable to frustrate repeatable FI attacks |
-| TERMINAL       | The asset goes into a terminal statet that no longer responds to any stimulus |
-| COUNT          | The number of operations or items processed is counted which can be checked by software to ensure the correct number have occurred |
-| CM             | Catch-all for countermeasures that cannot be further specified. This is a very broad category: avoid if possible and give an instance or net name if not. |
-
-## Register Handling
-
-The definition and handling of registers is a topic all on its own, and is specified in its [own document]({{< relref "doc/rm/register_tool" >}}).
-All lowRISC peripheral designs must conform to this register specification.
-
-## Configuration description Hjson
-
-The description of the IP block and its registers is done in an Hjson file that is specified in the
-[Register Tool document]({{< relref "doc/rm/register_tool" >}}).
-All lowRISC peripheral designs must conform to this configuration and register specification.
-
-A description of Hjson (a variant of json) and the recommended style is in the [Hjson Usage and Style Guide]({{< relref "doc/rm/hjson_usage_style.md" >}}).
-
-### Configuration information in the file
-
-The configuration part of the file has the following elements, with a comment as to if required or optional.
-In this example, the IP name is `uart`, though the other configuration fields are contrived and not in-line with the expected functionality of a UART but are shown for edification.
-
-```hjson
-  {
-    name: "uart",
-    clocking: [
-      {clock: "clk_fixed", reset: "rst_fixed_n", primary: true},
-      {clock: "clk", reset: "rst_n"},
-      {clock: "clk_lowpower", reset: "rst_lowpower_n"}
-    ],
-    bus_interfaces: [
-      { protocol: "tlul", direction: "device", name: "regs" }
-    ],
-    available_input_list: [          // optional; default []
-      { name: "rx", desc: "Receive bit" }
-    ],
-    available_output_list: [         // optional; default []
-      { name: "tx", desc: "Transmit bit" }
-    ],
-    available_inout_list: [],        // optional; default []
-    interrupt_list: [                // optional; default []
-      { name: "tx_watermark",  desc: "raised if the transmit FIFO..."}
-      { name: "rx_watermark",  desc: "raised if the receive FIFO..."}
-      { name: "tx_overflow",   desc: "raised if the transmit FIFO..."}
-      { name: "rx_overflow",   desc: "raised if the receive FIFO..."}
-      { name: "rx_frame_err",  desc: "raised if a framing error..."}
-      { name: "rx_break_err",  desc: "raised if break condition..."}
-      { name: "rx_timeout",    desc: "raised if the receiver..."}
-      { name: "rx_parity_err", desc: "raised if the receiver..."}
-    ],
-    alert_list: [                    // optional; default []
-      { name: "fatal_uart_breach", desc: "Someone has attacked the ..."}
-      { name: "recov_uart_frozen", desc: "The UART lines are frozen..." }
-    ],
-    inter_signal_list: [
-      { name: "msg_fifo",
-        struct: "fifo",
-        package: "msg_fifo_pkg",
-        type: "req_rsp",
-        act: "req",
-        width: 1
-      }
-      { name: "suspend",
-        struct: "logic",
-        package: "",
-        type: "uni",
-        act: "rcv",
-        width: 1
-      }
-    ]
-    regwidth: "32", // standard register width
-    register: [
-      // Register information...
-    ]
-  }
-```
-
-### Documentation Output
-
-The following shows the expected documentation format for this example.
-
-*Primary Clock:* `clk_fixed`
-
-*Other clocks:* `clk, clk_lowpower`
-
-*Bus Device Interfaces (TL-UL):* `regs_tl`
-
-*Bus Host Interfaces (TL-UL): none*
-
-*Peripheral Pins available for chip-level IO:*
-
-| Pin name | direction | Description |
-| --- | --- | --- |
-| tx | output | Transmit bit |
-| rx | input | Receive bit |
-
-*Interrupts:*
-
-| Intr Name | Description |
-| --- | --- |
-| `tx_watermark`  | Raised if the transmit FIFO is past the high water mark |
-| `rx_watermark`  | Raised if the receive FIFO is past the high water mark |
-| `tx_overflow`   | Raised if the transmit FIFO has overflowed |
-| `rx_overflow`   | Raised if the receive FIFO has overflowed |
-| `rx_frame_err`  | Raised if a framing error has been detected on receive |
-| `rx_break_err`  | Raised if a break condition is detected on receive |
-| `rx_timeout`    | Raised if the receiver has not received any characters programmable time period |
-| `rx_parity_err` | Raised if the receiver has detected a parity error |
-
-*Security alerts:*
-
-| Alert name | Description |
-| --- | --- |
-| `fatal_uart_breach` | Someone has attacked the UART module |
-| `recov_uart_frozen` | The UART lines are frozen and might be under attack |
-
-### Specifying countermeasures
-
-Countermeasure information can be specified in the register Hjson format.
-This is done with a list with key `countermeasures`.
-Each item is a dictionary with keys `name` and `desc`.
-The `desc` field is a human-readable description of the countermeasure.
-The `name` field should be either of the form `ASSET.CM_TYPE` or `INSTANCE.ASSET.CM_TYPE`.
-Here, `ASSET` and `CM_TYPE` should be one of the values given in the tables in the [Security countermeasures]({{< relref "#countermeasures" >}}) section.
-If specified, `INSTANCE` should name a submodule of the IP block holding the asset.
-It can be used to disambiguate in situations such as where there are two different keys that are protected with different countermeasures.
-
-Here is an example specification:
-```hjson
-  countermeasures: [
-    {
-      name: "BUS.INTEGRITY",
-      desc: "End-to-end bus integrity scheme."
-    }
-    {
-      name: "STATE.SPARSE",
-      desc: "Sparse manufacturing state encoding."
-    }
-    {
-      name: "MAIN.FSM.SPARSE",
-      desc: "The main state FSM is sparsely encoded."
-    }
-  ]
-```
-
-## Interrupt Handling
-
-Interrupts are critical and common enough to attempt to standardize across the project.
-Where possible (exceptions for inherited IP that is too tricky to convert) all interrupts shall have common naming, hardware interface, and software interface.
-These are described in this section.
-
-Interrupts are latched indications of defined peripheral events that have occurred and not yet been addressed by the local processor.
-All interrupts are sent to the processor as active-high level (as opposed to edge) interrupts.
-Events themselves can be edge or level, active high or low, as defined by the associated peripheral.
-For instance, the GPIO module might detect the rising or falling edge of one its input bits as an interrupt event.
-
-The latching of the event is done by the auto-generated register file as described below.
-The clearing of the event is done by a processor write when the handling of the event is completed.
-The waveform below shows the timing of the event occurrence, its latched value, and the clearing by the processor.
-More details follow.
-
-{{< wavejson >}}
-{
-  signal: [
-    { name: 'Clock',             wave: 'p.............' },
-    { name: 'event',             wave: '0..10.........' },
-    { name: 'INTR_ENABLE',       wave: '1.............' },
-    { name: 'INTR_STATE',        wave: '0...1....0....' },
-    { name: 'intr_o',            wave: '0...1....0....' },
-    { name: 'SW write to clear', wave: '0.......10....' },
-  ],
-  head: {
-    text: 'Interrupt Latching and Clearing',
-  },
-  foot: {
-    text: 'event signaled at cycle 3, state bit cleared in cycle 8',
-    tock: 0
-  },
-}
-{{< /wavejson >}}
-
-### Interrupts per module
-
-A peripheral generates a separate interrupt for each event and sends them all as bundle to the local processor's interrupt module.
-"Disambiguation", or the determining of which interrupt has woken the processor, is done at the processor in its handler (to be specified eventually in the core processor specification).
-This is as distinct from a model in which each peripheral would send only one interrupt, and the processor would disambiguate by querying the peripheral to figure out which interrupt was triggered.
-
-### Defining Interrupts
-
-The configuration file defined above specifies all that needs to be known about the interrupts in the standard case.
-The following sections specify what comes out of various tools based upon the simple list defined in the above example.
-
-### Register Creation
-
-For every peripheral, by default, three registers are **automatically** created to manage each of the interrupts for that peripheral (as defined in the `interrupt_list` portion of the Hjson file).
-Every interrupt has one field bit for each of three registers.
-(It is an error condition if there are more than 32 interrupts per peripheral.)
-The three registers are the `INTR_STATE` register, the `INTR_ENABLE` register, and the `INTR_TEST` register.
-They are placed at the top of the peripheral's address map in that order automatically by the `reggen` tool.
-
-The `INTR_ENABLE` register is readable and writeable by the CPU (`rw`), with one bit per interrupt which, when true, enables the interrupt of the module to be reported to the output to the processor.
-The `INTR_STATE` register is readable by the CPU and each bit may be written with `1` to clear it (`rw1c`), so that a read of the register indicates the current state of all latched interrupts, and a write of `1` to any field clears the state of the corresponding interrupt.
-`INTR_TEST` is a write-only (`wo`) register that allows software to test the reporting of the interrupt, simulating a trigger of the original event, the setting of the `INTR_STATE` register, and the raised level of the interrupt output to the processor (modulo the effect of `INTR_ENABLE`).
-No modifications to other portions of the hardware (eg. clearing of FIFO pointers) occurs.
-See the next section for the hardware implementation.
-
-The contents of the `INTR_STATE` register do **not** take into consideration the enable value, but rather show the raw state of all latched hardware interrupt events.
-The output interrupt to the processor ANDs the interrupt state with the interrupt enable register before sending to the processor for consideration.
-
-### Interrupt Hardware Implementation
-
-All interrupts as sent to the processor are active-high level interrupts of equal severity<sup>2</sup>.
-Taking an interrupt `foo` as an example, the block diagram below shows the hardware implementation.
-The assumption is that there is an internal signal (call it `event_foo`) that indicates the detection of the event that is to trigger the interrupt.
-The block diagram shows the interaction between that event, the three defining software-facing registers, and the output interrupt `intr_foo_o`.
-
-<sup>2</sup> Higher priority interrupts in the form of a Non-Maskable Interrupt (NMI) are expected to be overlaid in the future.
-
-![Example Interrupt HW](comportability_diagram_intr_hw.svg)
-
-**Figure 2**: Example interrupt `foo` with its three registers and associated HW
-
-In this figure the event is shown coming in from another part of the peripheral hardware.
-The assumption is this event `foo` is one of multiple interrupt events in the design.
-Within the register file, the event triggers the setting of the associated bit in the `INTR_STATE` register to `1`.
-Additionally, a write of `1` of the associated `foo` bit of the `INTR_TEST` register can set the corresponding `INTR_STATE` bit.
-The output of the `INTR_STATE` register becomes the outgoing interrupt to the processor after masking (ANDing) with the value of `INTR_ENABLE`.
-
-Note that the handling of the `ro/rw1c` functionality of the `INTR_STATE` register allows software to control the clearing of the `INTR_STATE` content.
-A write of `1` to the corresponding bit of `INTR_STATE` clears the latched value, but if the event itself is still active, the `INTR_STATE` register will return to true.
-The hardware does not have the ability to clear the latched interrupt state, only software does.
-
-Interrupts sent to the processor are handled by its interrupt controller.
-Within that logic there may be another level of control for enabling, prioritizing, and enumeration.
-Specification of this control is defined in the rv_plic documentation of the corresponding toplevel design.
-
-## Alert Handling
-
-Alerts are another critical and common implementation to standardize for all peripherals.
-Unlike interrupts, there is no software component to alerts at the peripheral, though there is at the hardware alert handler.
-See that [specification]({{< relref "/hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}) for full details.
-A general description of the handling of alerts at the hardware level is given here.
-
-### Alerts per Module
-
-Alerts are sent as a bundled output from a peripheral to the hardware alert handler.
-Each peripheral can send zero or more alerts, where each is a distinguishable security threat.
-Each alert originates in some internal event, and must be specially handled within the peripheral, and then within the alert handler module.
-
-Alerts of comportable IPs in the system must be in either of the following two categories:
-
-1. *Recoverable*, one-time triggered alerts.
-This category is for regular alerts that are due to recoverable error conditions.
-The alert sender transmits one single alert event when the corresponding error condition is asserted.
-
-2. *Fatal* alerts that are continuously triggered until reset.
-This category is for highly critical alerts that are due to terminal error conditions.
-The alert sender continuously transmits alert events until the system is reset.
-
-It is recommended that fatal alerts also trigger local security countermeasures, if they exist.
-For example, a redundantly encoded FSM that is glitched into an invalid state is typically considered to be a fatal error condition.
-In this case, a local countermeasure could be to move the FSM into a terminal error state in order to render the FSM inoperable until the next reset.
-
-The table below lists a few common error conditions and the recommended alert type for each of those errors.
-
-Error Event                                                             | Regular IRQ | Recoverable Alert | Fatal Alert
-------------------------------------------------------------------------|-------------|-------------------|-------------
-ECC correctable in NVM (OTP, Flash)                                     | (x)         | x                 |
-ECC uncorrectable in Flash                                              | (x)         | x                 |
-ECC uncorrectable in OTP                                                | (x)         |                   | x
-Any ECC / Parity error in SRAMs or register files                       | (x)         |                   | x
-Glitch detectors (e.g., invalid FSM encoding)                           | (x)         |                   | x
-Incorrect usage of security IP (e.g., shadowed control register in AES) | (x)         | x                 |
-Incorrect usage of regular IP                                           | x           |                   |
-
-(x): optional
-
-The column "Regular IRQ" indicates whether the corresponding error condition should also send out a regular IRQ.
-A peripheral may optionally send out an IRQ for any alert event, depending on whether this is needed by the programming model to make forward progress.
-Note that while alerts may eventually lead to a system wide reset, this is not guaranteed since the alert response depends on the alert handler configuration.
-
-### Defining Alerts
-
-The Hjson configuration file defined above specifies all that needs to be known about the alerts in the standard case.
-The following sections specify what comes out of various tools based upon the simple list defined in the above example.
-
-In terms of naming convention, alerts shall be given a meaningful name that is indicative of its cause.
-Recoverable alerts must be prefixed with `recov_*`, whereas fatal alerts must be prefixed with `fatal_*`.
-For instance, an uncorrectable parity error in SRAM could be named `fatal_parity_error`.
-
-In cases where many diverse alert sources are bundled into one alert event (see [Alert Hardware Implementation]({{< relref "#alert-hardware-implementation" >}})), it may sometimes be difficult to assign the alert event a meaningful and descriptive name.
-In such cases, it is permissible to default the alert names to just `recov` and/or `fatal`.
-Note that this implies that the peripheral does not expose more than one alert for that type.
-
-### Test Alert Register Creation
-
-For every peripheral, by default, one register named `ALERT_TEST` is **automatically** created.
-
-`ALERT_TEST` is a write-only (`wo`) register that allows software to test the reporting of alerts in the alert handler.
-Every alert of a peripheral has one field bit inside the `ALERT_TEST` register, and each field bit is meant to be connected to the test input of the corresponding `prim_alert_sender` (see next subsection).
-
-### Alert Hardware Implementation
-
-Internal events are sent active-high to a piece of IP within the peripheral called the `prim_alert_sender`.
-One `prim_alert_sender` must be instantiated per distinct alert event, and the `IsFatal` parameter of the alert sender must be set to 1 for fatal alerts (this causes the alert sender to latch the alert until the next system reset).
-
-It is up to the peripheral owner to determine what are distinct alert events;
-multiple ones can be bundled depending upon the distinction required within the module (i.e.  high priority threat vs. low level threat).
-As a general guideline, it is recommended that each peripheral bundles alert sources into one or two distinct alerts, for example one fatal and one recoverable alert.
-This helps to keep the total number of alerts (and their physical impact) low at the system level.
-
-It is recommended that comportable IPs with multiple bundled alerts expose a cause register for disambiguation, which is useful for debugging and crash dumps.
-Cause registers for recoverable alerts must either be clearable by SW, or the HW must provide an automatic mechanism to clear them (e.g., upon starting a new transaction initiated by SW).
-Cause registers for fatal alerts must not be clearable in any way and must hence be read-only.
-
-The `prim_alert_sender` converts the event into a differentially encoded signal pair to be routed to the hardware alert handler, as dictated by the details in the
-[alert handler specification]({{< relref "/hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}).
-The alert handler module is automatically generated to have enough alert ports to represent each alert declared in the different included peripheral IP configuration files.
-
-## Inter Signal Handling
-
-Inter-module signal is a term that describes bundled signals connecting instances in the top.
-A few peripherals can be stand-alone such as GPIO and UART peripherals.
-They don't need to talk with other modules other than reporting the interrupts to the main processor.
-By contrast, many peripherals and the main processing unit in OpenTitan communicate between the modules.
-For example, `flash_ctrl` sends requests to the flash macro for read/ program/ erase operations.
-
-Inter-module signal aims to handle the connection by the tool [topgen]({{<relref "/doc/rm/topgen_tool" >}})
-
-### Defining the inter-module signal
-
-The example configuaration file above specifies two cases of inter-module signals, `msg_fifo` and `suspend`.
-
-| Attribute | Mand/Opt  | Description |
-| --------- | --------- | ----------- |
-| name      | mandatory | `name` attribute specifies the port name of the inter-module signal. If the type is `req_rsp`, it indicates the peripheral has `name`_req , `name`_rsp ports (with `_i` and `_o` suffix) |
-| struct    | mandatory | The `struct` field defines the signal's data structure. The inter-module signal is generally bundled into `struct packed` type. This `struct` is used with `package` for topgen tool to define the signal. If the inter-module signal is `logic` type, `package` field can be omitted. |
-| package   | optional  |             |
-| type      | mandatory | There are two types of inter-module signal. `req_rsp` is a connection that a module sends requests and the other module returns with responses. `uni` is one-way signal, which can be used as a broadcasting signal or signals that don't need the response. |
-| act       | mandatory | `act` attribute pairs with the `type`. It specifies the input/output of the signal in the peripheral. `req_rsp` type can have `req`(requester) or `rsp`(responder) in `act` field. `uni` type can have `req` or `rcv`(receiver) in `act`. |
-| width     | optional  | If `width` is not 1 or undefined, the port is defined as a vector of struct. It, then, can be connected to multiple peripherals. Currently, `logic` doesn't support the connection to multiple modules if `width` is not 1. |
diff --git a/doc/rm/continuous_integration/index.md b/doc/rm/continuous_integration/index.md
deleted file mode 100644
index 3e55385..0000000
--- a/doc/rm/continuous_integration/index.md
+++ /dev/null
@@ -1,85 +0,0 @@
----
-title: "OpenTitan Continuous Integration"
----
-
-All changes to the OpenTitan source code are tested thoroughly in a continuous integration system.
-Tests run automatically when changes are proposed for inclusion by submitting a pull request, and on the `master` branch after changes are merged.
-This ensures that the OpenTitan source code meets certain quality criteria at all points in time, as defined by the tests which are executed.
-
-Read on to learn more about the types of tests, and the infrastructure that runs these tests.
-
-## How to report CI problems
-
-If you detect CI failures which look like they might not be related to the tested code, but the test infrastructure, please file an [issue on GitHub](https://github.com/lowRISC/opentitan/issues).
-In urgent cases also reach out on Slack and send an email to lowRISC IT at [internal-tech@lowrisc.org](mailto:internal-tech@lowrisc.org).
-Note that lowRISC is based in the UK and most active during European business hours.
-
-## Overview
-
-<!--
-Source: https://docs.google.com/drawings/d/1-Zjm3k2S0TNmne3F9z3rpTFJfLJJvvmrBAsfx_HG5lk/edit
-
-Download the SVG from Google Draw, open it in Inkscape once and save it without changes to add width/height information to the image.
--->
-![CI Overview](continuous_integration_overview.svg)
-
-OpenTitan uses [Azure Pipelines](https://azure.microsoft.com/en-gb/services/devops/pipelines/) as continuous integration provider: test jobs are described in an Azure Pipelines-specific way, and then executed on compute resources, some of which are provided by Azure Pipelines, and others of which are provided by lowRISC.
-
-Two things are special in the way OpenTitan does continuous integration: private CI, and testing on FPGA boards.
-
-"Private CI" is a term we use for a subset of test jobs which require tighter access control.
-The primary use case for private CI are tests using proprietary EDA tools, where the license agreement prevents us from testing arbitrary code with it, from showing the configuration or the output in public, etc.
-We run such test jobs in a separate environment where only OpenTitan project members have access.
-The test result (pass/fail) is still shared publicly to enable outside contributors to at least get some feedback if their pull request passed our tests.
-
-To test OpenTitan (both the hardware and the software) on FPGAs we have various FPGA boards connected to a machine at lowRISC.
-Azure Pipelines is configured to schedule test jobs on this machine when FPGA testing is required.
-The results and logs of these test runs are shown publicly.
-
-## Azure Pipelines projects
-
-OpenTitan CI uses two Azure DevOps projects (which Azure Pipelines is a part of):
-
-* https://dev.azure.com/lowrisc/opentitan/ for public CI.
-* https://dev.azure.com/lowrisc/opentitan-private/ for private CI.
-
-## Test descriptions
-
-All tests are described in a Azure Pipelines-specific YAML syntax.
-`$REPO_TOP/azure-pipelines.yml` is the main configuration file for all public CI jobs.
-The private CI jobs are described in a separate private repository, [lowrisc/opentitan-private-ci](https://github.com/lowRISC/opentitan-private-ci), to keep the job descriptions internal for legal reasons.
-
-The [YAML schema](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema) is part of the Azure Pipelines documentation.
-
-## Compute resources: agent pools and agents
-
-Each test in the YAML file also specifies which type of compute resource it wants to run on.
-Identical compute resources are grouped into *agent pools*, and an individual compute resource is called an *agent*.
-
-For OpenTitan, we have the following agent pools available, which can also be seen in the [Azure Pipelines UI](https://dev.azure.com/lowrisc/opentitan/_settings/agentqueues):
-* The *Azure Pipelines* pool is a Microsoft-provided pool of VMs which are free of charge for us.
-  They are described in more detail in the [Azure Pipelines documentation](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted).
-* The *ci-public* pool has a lowRISC-specific setup with tools such as Xilinx Vivado installed, but has no access to tools with special license restrictions.
-* The *ci-eda* pool has proprietary EDA tools installed and access to the respective licenses.
-* The *FPGA* pool currently consists of a single machine with our FPGA boards connected to it.
-
-All pools except for the *Azure Pipelines* pool are managed by lowRISC IT.
-
-All agents provide ephemeral test environments: the test environment is initialized at the start of a test job and completely destroyed at the end.
-This is achieved by running tests in Docker containers which are recreated after each run.
-The base image used for all lowRISC-hosted agent pools is available [as lowrisc/container-ci-eda on DockerHub](https://hub.docker.com/r/lowrisc/container-ci-eda).
-(The build rules/Dockerfile for this image are lowRISC-internal.)
-
-lowRISC-provided agents run in a Kubernetes cluster on Google Cloud (GCP), where we also define the resources allocated for the individual agents.
-The agent pools are static in size, i.e. the number of agents doesn't increase and decrease depending on the number of scheduled jobs.
-
-## Job scheduling, build triggers and status reporting
-
-Builds are triggered by GitHub, which sends notifications to Azure Pipelines on a range of events, e.g. the creation of new pull requests, the merge of code into a branch, etc.
-
-The Azure Pipelines scheduler consumes these events and compares them with the configured pipeline triggers in the `azure-pipelines.yml` file.
-It then processes the pipeline description and adds test jobs to the respective agent pool queues, taking test dependencies into account.
-
-After the agent has completed a test job it reports back the result to the Azure Pipelines scheduler, which makes this information (build artifacts and logs) available to users through the web UI.
-
-Azure Pipelines also reports the test status back to GitHub, where it is displayed below a pull request, as marks next to commits, and in various other places.
diff --git a/doc/rm/crossbar_tool/index.md b/doc/rm/crossbar_tool/index.md
deleted file mode 100644
index 3a315d9..0000000
--- a/doc/rm/crossbar_tool/index.md
+++ /dev/null
@@ -1,172 +0,0 @@
----
-title: "Crossbar Generation Tool"
----
-
-The crossbar tool `tlgen.py` is used to build the TL-UL crossbar RTL.
-It can be used standalone or invoked as part of top module generation process (details of top generation forthcoming).
-The RTL files found `hw/top_earlgrey/ip/xbar/rtl/autogen` are generated with the crossbar tool.
-This document does not specify the details of the internal blocks.
-See the [bus specification]({{< relref "hw/ip/tlul/doc" >}}) for details on the protocol and the components used in the crossbar.
-
-## Standalone tlgen.py
-
-The standalone utility `tlgen.py` is a Python script to read a crossbar Hjson configuration file and generate a crossbar package, crossbar RTL, and DV test collateral for checking the connectivity.
-
-The `--help` flag is provided for details on how to run the tool.
-
-### Example and Results
-
-An example of the crossbar Hjson is given in `util/example/tlgen/xbar_main.hjson`.
-
-The package file and RTL can be created by the command below:
-
-```console
-    $ util/tlgen.py -t util/example/tlgen/xbar_main.hjson -o /tmp/
-```
-
-This creates files in `/tmp/{rtl|dv}`.
-While generating the RTL, the tool adds detailed connection information in the form of comments to the RTL header.
-
-## Configuration File Format
-
-The `tlgen` script reads an Hjson file containing the crossbar connections and the host and device information.
-The two main sections of the configuration file are the list of nodes and the list of connections.
-Together, these describe a generic Directed Acyclic Graph (DAG) with some additional clock information and steering information.
-
-If the tool is used in the process of top generation (`topgen.py`, details forthcoming), a few fields 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]({{< relref "doc/rm/hjson_usage_style" >}}).
-
-An item in the `nodes` list corresponds to a host or device interface on an instantiated IP block.
-The name of such a node should be of the form `<instance_name>.<interface_name>`, so the `my_if` interface on the instance `my_instance` would be denoted `my_instance.my_if`.
-However, many instances have a single, unnamed, device interface.
-For these devices, the node should just be named with the name of the instance (just `my_instance` in the example above).
-For details of how interfaces are defined using the register tool, see the [Bus Interfaces]({{< relref "doc/rm/comportability_specification#bus-interfaces" >}}) section of the Comportability Specification.
-
-Edges in the DAG are specified in the `connections` map.
-This uses an adjacency list format.
-The keys are the names of host nodes.
-The value at a host a list of the names of device nodes which that host should be able to see.
-
-### Configuration file syntax
-
-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.
-
-{{% selfdoc "tlgen" %}}
-
-## Fabrication process
-
-The tool fabricates a sparse crossbar from the given Hjson configuration file.
-In the first step, the tool creates Vertices (Nodes) and Edges.
-Then it creates internal building blocks such as Asynchronous FIFO, Socket `1:N`, or Socket `M:1` at the elaboration stage.
-Please refer to `util/tlgen/elaborate.py` for details.
-
-### Traversing DAG
-
-The tool, after building Nodes and Edges, traverses downstream from every Host node downstream during elaboration.
-In the process of traversal, it adds internal blocks as necessary.
-Once all Nodes are visited, the tool completes traversing then moves to RTL generation stage.
-
-1. Generates Nodes and Edges from the Hjson.
-   Start node should be Host and end node should be Device.
-2. (`for loop`) Visit every host
-3. If a node has different clock from main clock and not Async FIFO:
-    1. (New Node) Create Async FIFO Node.
-    2. If the Node is host, revise every edge from the node to have start node in Async FIFO.
-      (New Edge) Create an Edge from the Node to Async FIFO.
-       Then go to Step 3 with Async FIFO Node.
-       Eventually, the connection is changed to `host -> async_fifo -> downstream` from `host -> downstream`.
-    3. Revise every Edge to the Node to have Async FIFO as a downstream port.
-       (New Edge) Create an Edge from the Async FIFO to the Node.
-    4. If it is not Device, raise Error.
-       If it is, repeat from Step 2 with next item.
-4. If a Node has multiple Edges pointing to it as a downstream port, set `nodes -> this node` and create Socket `M:1`,
-    1. (New Node) Create Socket `M:1`.
-    2. Revise every Edge to the Node to point to this Socket `M:1` as a downstream port
-    3. (New Edge) Create an Edge from Socket `M:1` to the Node.
-       The new connection appears as `nodes -> socket_m1 -> this node`.
-    4. Repeat from Step 3 with the Node.
-5. If a Node has multiple Edges and is not already a Socket `1:N`,
-    1. (New Node) Create Socket `1:N` Node.
-    2. Revise every Edge from the Node to point to Socket `1:N` as an upstream port
-    3. (New Edge) Create an Edge from the Node to Socket `1:N`.
-    4. (for loop) Repeat from Step 3 with Socket `1:N`'s downstream Nodes.
-
-Below shows an example of 2 Hosts and 2 Devices connectivity.
-
-![Example Topology](crossbar_example_1.svg)
-
-Each circle represents a Node and an arrow represents an Edge that has downward direction.
-The tool starts from `H0` Node.
-As the Node has two downstream Edges and not Socket `1:N`, the tool creates Socket `1:N` based on the condition #5 above.
-Then repeat the process from Socket `1:N` Node's children Nodes, `D0` and `D1`.
-
-![Socket 1:N instantiated](crossbar_example_2.svg)
-
-For `D0`, the tool creates Socket `M:1` based on the condition #4.
-It then visit its downstream Node, `D0` again.
-In this case, it doesn't create an Async FIFO as the clock is same as main clock.
-So it reached the terminal Node.
-Then it visits `D1`.
-It repeats the same step (condition #4) as `D0`, which creates another Socket `M:1`.
-
-![Socket M:1 instantiated to D0, D1](crossbar_example_3.svg)
-
-As all Nodes from `H0` have been visited, the tool repeats all steps from `H1`.
-It applies condition #3 above as `H1` has a peripheral clock rather than main clock.
-So the tool creates an Async FIFO and moves the pointer to the Node and repeats.
-
-![Async FIFO instantiated to H1](crossbar_example_4.svg)
-
-The tool applies rule #5 as Async FIFO has multiple downstream Nodes (Edges) and it is not Socket `1:N`.
-The tool creates a Socket `1:N` and visits every downstream Node.
-
-![Socket 1:N instantiated to `AS_a`](crossbar_example_5.svg)
-
-Both Nodes have been processed, so no condition is hit.
-The tool completes traversing.
-
-### Numbering
-
-After the traversing is completed, Hosts and Devices only have one Edge and internal Sockets have multiple Edges.
-The tool assigns increasing numbers to each Edge starting from 0.
-This helps the tool to connect between Nodes.
-
-### Propagating the steering information (Address)
-
-After the numbering is done, the tool propagates the steering information, addressing every Device Node to the upstream node until it hits a Socket `1:N`.
-Socket `1:N` is the only module that requires `base_addr` and `size_bytes` information.
-
-It is determined that at most one Socket `1:N` exists on the path from a Host to a Device within a crossbar.
-If any SoC requires a multi-tiered crossbar design, it should create multiple crossbars to communicate with each other.
-This condition does not exist in the current design.
-
-### Connection information
-
-The tool creates DAG connections when it creates RTL to help understanding the fabric.
-The information is put as a comment in the header of the RTL.
-For instance, with the above 2x2 example, the following information is created.
-
-```console
-    $ util/tlgen.py -t util/example/tlgen/xbar_2x2.hjson -o /tmp/
-    $ cat /tmp/rtl/xbar_2x2.sv
-// ...
-// Interconnect
-// h0
-//   -> s1n_4
-//     -> sm1_5
-//       -> d0
-//     -> sm1_6
-//       -> d1
-// h1
-//   -> asf_7
-//     -> s1n_8
-//       -> sm1_5
-//         -> d0
-//       -> sm1_6
-//         -> d1
-// ...
-```
diff --git a/doc/rm/device_interface_functions.md b/doc/rm/device_interface_functions.md
deleted file mode 100644
index 68b68d2..0000000
--- a/doc/rm/device_interface_functions.md
+++ /dev/null
@@ -1,168 +0,0 @@
----
-title: "Device Interface Functions (DIFs)"
----
-
-## Motivation
-
-Every hardware peripheral needs some form of higher-level software to actuate it to perform its intended function.
-Device Interface Functions (DIFs) aim to make it easy to use hardware for its intended purposes while making it difficult, or impossible, to misuse.
-DIFs can be seen as a living best-practices document for interacting with a given piece of hardware.
-
-## Objectives
-
-DIFs provide extensively reviewed APIs for actuating hardware for three separate use cases: design verification, FPGA + post-silicon validation, and providing example code to aid the implementation of reference firmware including end-consumer applications.
-
-## Requirements
-
-### Common Requirements
-
-#### Language
-
-DIFs **must** be written in C, specifically [C11 (with a few allowed extensions)]({{< relref "doc/rm/c_cpp_coding_style.md#c-style-guide" >}}).
-DIFs **must** conform to the style guide in [`sw/device/lib/dif`]({{< relref "sw/device/lib/dif" >}}).
-
-DIFs **must** only depend on:
-
-*   the [freestanding C library headers](https://github.com/lowRISC/opentitan/tree/master/sw/device/lib/base/freestanding),
-*   compiler runtime libraries (e.g. libgcc and compiler-rt),
-*   the bitfield library for manipulating bits in binary values (`sw/device/lib/base/bitfield.h`),
-*   the mmio library for interacting with memory-mapped registers (`sw/device/lib/base/mmio.h`),
-*   the memory library for interacting with non-volatile memory (`sw/device/lib/base/memory.h`), and
-*   any IP-specific register definition files.
-
-DIFs **must not** depend on DIFs for other IP blocks, or other external libraries.
-
-This decision is motivated by the requirement that DIFs must be extremely flexible in their possible execution environments, including being hosted in bare metal, or being called from other languages such as Rust through Foreign Function Interface.
-
-#### Runtime Independence
-
-DIFs **must not** depend on runtime services provided by an operating system.
-If functions must be called in response to system stimulus such as interrupts then the DIF must make these requirements explicit in their documentation.
-This decision makes DIFs appropriate for execution environments without OS support such as DV.
-
-### Architecture Independence
-
-DIFs **should not** depend on architecture-specific constructs such as inline assembly or platform-defined registers, except where a peripheral is integrated into the core in a way making them unavoidable.
-As a concrete example: a SPI DIF must not depend on a pinmux or clock control DIF in order to be operated.
-This highlights a clear separation of concern: making a pin on the package driven from the SPI block involves multiple systems.
-By design, a DIF cannot coordinate cross-functional control.
-
-#### Coverage Requirements
-
-DIFs *must* actuate all of the specification-required functionality of the hardware that they are written for, and no more.
-A DIF cannot be declared complete until it provides an API for accessing all of the functionality that is expected of the hardware.
-This is distinct from mandating that a DIF be required to cover all of the functionality of a given piece of hardware.
-
-## Details
-
-### Verification Stage Allowances
-
-DV, FPGA, and early silicon validation have unique requirements in their use of hardware.
-They might actuate hardware on vastly different timescales, use verification-specific registers, or otherwise manipulate aspects of the hardware that "production"-level code would not (or cannot) do.
-To this end, verification-only functionality may be added to a DIF **only** in modules that are included in verification.
-This functionality **must not** be made accessible outside of verification environments, and is enforced by not including DV-specific code in non-DV builds.
-
-### Separation of Concerns and Stateful Information
-
-In addition to the interface functions themselves, DIFs are expected to usually take the form of a structure that contains instance-specific information such as base register addresses and non-hardware-backed state required to actuate the hardware.
-DIFs **should not** track hardware-backed state in their own state machine.
-
-A DIF instance is expressly an instance of a hardware block, so DIFs **must not** intrinsically know the location of the hardware blocks they're associated with in memory.
-That is:
-
-```c
-dif_timer_init_result_t dif_timer_init(dif_timer_t *timer,
-                                       const timer_config_t *config,
-                                       uintptr_t base_address);
-```
-
-allows the system initialization code to instantiate timers at specific locations, whereas:
-
-```c
-// Don't do this:
-bool dif_timer_init(timer_t *timer, enum timer_num num);
-```
-
-suggests that the timer DIF knows about the number and placement of timers in the system.
-
-DIFs **must not** store or provide information in their implementation or state that is outside of their area of concern.
-The number of timers in a system is the concern of the system, not the timer DIF.
-
-### Naming
-
-All DIF functions **must** have clear direct objects and verbs, be written in the imperative mood, and follow the format of `dif_<object>_<verb_phrase>`.
-The object in the name must be common to the DIF it appears it, and unique among DIFs.
-
-Consider the following examples of good names:
-
-*   `dif_timer_init`,
-*   `dif_timer_reset`,
-*   `dif_timer_set_time_remaining`,
-*   `dif_timer_get_time_remaining`.
-
-The following are bad names:
-
-*   `dif_clear_timer` (wrong object/verb-phrase ordering),
-*   `dif_timer_gets_reset` (passive voice not imperative),
-*   `timer_init` (not correctly prefixed for C).
-
-Prefer common names: `init` is more commonly written than `initialize`, but `reset` is more commonly written than `rst`, for example.
-This is a subjective call on the DIF author and reviewers' parts, so common agreement will be more useful than strict prescription.
-
-### Documentation
-
-All DIF exported types and functions **must** have associated API documentation.
-All function parameters **must** be documented.
-All function semantics **must** be documented, no matter how obvious one suspects the function to be.
-The documentation should be exhaustive enough that the implementation can reasonably be inferred from it and the IP specification.
-
-It is important that the DIFs are documented alongside the hardware IP that they are written for.
-Each hardware IP block also contains a "programmers' guide" section, which tells programmers how to use the hardware interfaces correctly, using prose descriptions and relevant diagrams.
-The prose descriptions should include references to relevant DIF functions.
-
-Programmers' guides **should** primarily refer to the [Software API Documentation]({{< relref "/sw#opentitan-software-api-documentation" >}}).
-Programmers' guides **should not** contain standalone examples that do not match how we implement DIFs and can become out of date.
-The software API documentation includes full source code of the respective DIFs, should extra clarity be needed to explain how a hardware interface should be used.
-
-The programmers' guide must also include a list of DIF functions which relate to it, along with a brief description of each, and a link to further API documentation.
-
-### Source Layout
-
-DIFs **must** live in the `sw/device/lib/dif` folder.
-They **must** comprise at least one header unique to the hardware in question, and **may** contain more than one C file providing the implementation, if the complexity of the implementation warrants it (this is subjective).
-Any files for a given IP should have a filename starting `dif_<IP name>`.
-
-Verification-specific extensions and logic **must not** live in `sw/device/lib/dif`.
-This code must be placed in the directory belonging to the verification domain which the extension is applicable to.
-Verification-specific extensions must not be built and included in production firmware builds.
-
-### Testing
-
-Tests **must** live in the `sw/device/tests/dif` folder.
-
-DIFs **must** have unit tests, which cover all their specification-required functionality.
-These unit tests use a mocking system which allows the test to control all hardware reads and writes, without requiring complex management of the hardware.
-These unit tests are written using [googletest](https://github.com/google/googletest), and may be run on a non-RISC-V host.
-
-DIFs **should** also have other tests, including standalone C sanity tests---which can quickly diagnose major issues between the DIF and the hardware but are not required to test all device functionality.
-
-DIFs are also being used by DV for chip-level tests, which should help catch any issues with the DIFs not corresponding to the hardware implementation exactly.
-These tests may not live in `sw/device/tests/dif`.
-
-### DIF Development Stages
-
-As part of the DIF Standardisation process, we've decided to establish more concrete DIF lifecycle stages.
-These are documented with the [Development Stages]({{< relref "doc/project/development_stages.md" >}}).
-
-In the hardware world, there are checklists for establishing that each stage is complete and the next stage can be moved to.
-
-In software, we don't have to follow the same stability guarantees, because software is much more modifiable.
-It makes sense to have some kind of review process, but this should be much more lightweight in the early stages and not significantly burdensome to the associated HW designer.
-
-The current proposal only covers a DIF being written against a single version of its respective hardware IP block.
-This specifically excludes how to disambiguate DIFs in the repository that are written against different versions of the same IP block, or writing a single DIF that is compatible with multiple versions of an IP block.
-
-### Signoff Review
-
-The DIF lead author is expected to participate in the hardware IP block's L2 signoff review.
-This review can only happen once hardware design and verification are complete, and the DIF itself has reached stage S3.
diff --git a/doc/rm/hjson_usage_style.md b/doc/rm/hjson_usage_style.md
deleted file mode 100644
index 9d4f1a8..0000000
--- a/doc/rm/hjson_usage_style.md
+++ /dev/null
@@ -1,224 +0,0 @@
----
-title: "Hjson Usage and Style Guide"
----
-
-## Basics
-
-### Summary
-
-Json files are used to provide input data to many of the tools.
-The preference is to use [Hjson](https://hjson.org/), which is a variation of regular JSON that is easier to write.
-In particular, it allows the quote marks to be left off the key names, it allows a single string to be quoted with triple quote marks and flow over multiple lines (which is often needed in text descriptions) and it allows comments using the # or // style.
-
-This guide covers the enhancements provided by Hjson 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
-
-
-### Terminology Conventions
-
-Unless otherwise noted, the following terminology conventions apply to this style guide:
-
-*   The word ***must*** indicates a mandatory requirement.
-    Similarly, ***do not*** indicates a prohibition.
-    Imperative and declarative statements correspond to ***must***.
-*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
-    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
-    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
-*   The word ***may*** indicates a course of action is permitted and optional.
-*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
-
-### Style Guide Exceptions
-
-***Justify exceptions with a comment.***
-
-No style guide is perfect.
-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.
-
-
-## Hjson file format
-
-Hjson is a variation of regular JSON that is easier to write.
-There are parsers in a number of languages and the tools make extensive use of the `hjson` package provided for Python 3.
-A full description can be found on the [Hjson website](https://hjson.org/), but the main features that make it convenient are that it keeps files cleaner by allowing the quote marks to be left off the key names, it enables long descriptive text by allowing a single string to flow over multiple lines and it allows comments using the # or // style.
-
-For example:
-
-```hjson
-  key1: "value1",
-  // Now a key with a long value
-  key2: '''
-        A long descriptive value2 that can
-        span over multiple lines
-        '''
-```
-
-### Text Format
-
-Where possible, please restrict Hjson text to the ASCII character set to avoid downstream tool issues.
-Unicode may be used when referring to proper names.
-
-### File delimiters and header
-
-***Use `{}` to delimit the file***
-
-***Include a header comment with copyright and license information***
-
-The file must start with a `{` and end with a `}` to be well-formed json.
-In both cases, these should be on a single line and have no indentation.
-Anything enclosed should have two space indentation.
-(Hjson allows these to be omitted but this is not recommended style.)
-
-In most cases, before the opening `{` the file should start with a comment containing the copyright and license details and the SPDX-License-Identifier.
-
-```hjson {.good}
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  // details...
-}
-```
-
-In cases where the file may need to be parsed by a standard JSON parser, the comments must be omitted, but the SPDX license information should be provided as a top-level key/value using pure JSON syntax and ignored by any tool.
-
-```json {.good}
-
-{
-  "SPDX-License-Identifier": "Apache-2.0",
-  ...
-}
-```
-
-### Simple key-value entries
-
-***Use unquoted alphanumeric strings for keys***
-
-***Include all values in quote marks***
-
-Single entries are of the form `key: "value"` Keys must be alphanumeric strings and should not be in quotes.
-(Hjson allows this.
-The quotes may be included as an exception to the style guide if there is an expectation that the file needs to be parsed with a more traditional JSON parser.)
-The valid keys for each tool are described in the tool documentation.
-The style is for a simple value to be in quotes (even if it is a number) and the tool should manage any type conversions.
-
-In some cases Hjson allows the quotes to be omitted from values, but this is not recommended because the value string will not be terminated by a comma so there is potential for confusion when multiple key-value pairs are put on the same line.
-For example:
-
-```hjson {.good}
-      // This is recommended usage and will work as expected
-      { name: "fred", tag: "2", desc: "fred has tag 2" }
-```
-
-But:
-
-```hjson {.bad}
-      // This will cause confusion. The value for tag
-      // is the rest of the line after the colon
-      // so desc is not defined and the close } is lost
-      { name: "fred", tag: 2, desc: "fred has tag 2" }
-```
-
-### Groups of entries
-
-***Use two character indentation for items in groups***
-
-Groups of entries are made by enclosing a comma separated list of key/value pairs in {}.
-For example
-
-```hjson {.good}
-    { name:"odd", value:"1", desc:"odd parity"}
-```
-
-In most cases, a group will be too long for a single line, particularly where it has a good descriptive string.
-In that case, the entry should contain one key-value pair per line with a 2 character indent from the opening bracket.
-The closing bracket should not have the extra indentation.
-
-```hjson {.good}
-    {
-      name:"odd",
-      value:"1",
-      desc:"odd parity"
-    }
-```
-
-The first entry in a group may be on the same line as the opening bracket, with a single space.
-
-```hjson
-    { name:"odd",
-      value:"1",
-      desc:"odd parity"
-    }
-```
-
-Unless the group fits on a single line, the closing bracket should not be on the same line as the final item in the group.
-
-```hjson {.bad}
-    { name:"odd",
-      value:"1",
-      desc:"odd parity"}
-```
-
-
-### Lists
-
-***Use two character indentation for items in lists***
-
-A list value is a comma-separated list of entries or groups enclosed in [].
-For example, a list of groups could be presented:
-```hjson
-  registers: [{name:"reg1", desc:"description 1"},
-              {name:"reg2", desc:"description 2"}]
-```
-
-Longer lists and anywhere elements split over multiple lines should use a 2 character indent from the opening bracket.
-The closing bracket should not have the extra indentation.
-
-
-```hjson {.good}
-  registers: [
-    {
-      name:"reg1",
-      desc:"description 1"
-    },
-    {
-      name:"reg2",
-      desc:"description 2"
-    }
-  ]
-```
-
-Hjson allows commas to be omitted when an item that would be terminated with a comma is terminated by the end of a line, and will tolerate extra "trailing" commas.
-There is currently no style-guide rule on this.
-In general, use the comma if it is likely items may be combined on a single line and omit it if it keeps the file cleaner.
-
-
-### Long strings
-
-Long strings are encouraged for descriptive text that will be presented to users.
-They are delimited by three single-quote marks.
-
-The string should be indented to line up under the opening quote marks and the closing quote marks should be on their own line with the same indentation.
-
-```hjson {.good}
-       key: '''
-            A long descriptive value that can
-            span over multiple lines
-            '''
-```
-
-The first line of the string may be on the same line as the opening quotes.
-
-```hjson
-       key: '''A long descriptive value that can
-            span over multiple lines
-            '''
-```
-### Comments
-
-Comments should be used where needed.
-The use of `//` as the comment delimiter is recommended, but `#` may also be used.
diff --git a/doc/rm/ipgen_tool.md b/doc/rm/ipgen_tool.md
deleted file mode 100644
index 41baf9c..0000000
--- a/doc/rm/ipgen_tool.md
+++ /dev/null
@@ -1,231 +0,0 @@
----
-title: "Ipgen: Generate IP blocks from IP templates"
----
-
-Ipgen is a tool to produce IP blocks from IP templates.
-
-IP templates are highly customizable IP blocks which need to be pre-processed before they can be used in a hardware design.
-In the pre-processing ("rendering"), which is performed by the ipgen tool, templates of source files, written in the Mako templating language, are converted into "real" source files.
-The templates can be customized through template parameters, which are available within the templates.
-
-Ipgen is a command-line tool and a library.
-Users wishing to instantiate an IP template or query it for template parameters will find the command-line application useful.
-For use in higher-level scripting, e.g. within [topgen](topgen_tool.md) using ipgen as Python library is recommended.
-
-
-## Anatomy of an IP template
-
-An IP template is a directory with a well-defined directory layout, which mostly mirrors the standard layout of IP blocks.
-
-An IP template directory has a well-defined structure:
-
-* The IP template name (`<templatename>`) equals the directory name.
-* The directory contains a file `data/<templatename>.tpldesc.hjson` containing all configuration information related to the template.
-* The directory also contains zero or more files ending in `.tpl`.
-  These files are Mako templates and rendered into an file in the same location without the `.tpl` file extension.
-
-### The template description file
-
-Each IP template comes with a description itself.
-This description is contained in the `data/<templatename>.tpldesc.hjson` file in the template directory.
-The file is written in Hjson.
-
-It contains a top-level dictionary, the keys of which are documented next.
-
-#### List of template parameters: `template_param_list`
-
-Keys within `template_param_list`:
-
-* `name` (string): Name of the template parameter.
-* `desc` (string): Human-readable description of the template parameter.
-* `type` (string): Data type of the parameter. Valid values: `int`, `str`
-* `default` (string|int): The default value of the parameter. The data type should match the `type` argument. As convenience, strings are converted into integers on demand (if possible).
-
-#### Example template description file
-
-An exemplary template description file with two parameters, `src` and `target` is shown below.
-
-```hjson
-// data/<templatename>.tpldesc.hjson
-{
-  "template_param_list": [
-    {
-      "name": "src",
-      "desc": "Number of Interrupt Source",
-      "type": "int",
-      "default": "32"
-    },
-    {
-      "name": "target",
-      "desc": "Number of Interrupt Targets",
-      "type": "int",
-      "default": "32"
-    },
-  ],
-}
-```
-
-### Source file templates (`.tpl` files)
-
-Templates are written in the [Mako templating language](https://www.makotemplates.org/).
-All template parameters are available in the rendering context.
-For example, a template parameter `src` can be used in the template as `{{ src }}`.
-
-Furthermore, the following functions are available:
-
-* `instance_vlnv(vlnv)`: Transform a FuseSoC core name, expressed as VLNV string, into an instance-specific name.
-  The `vendor` is set to `lowrisc`, the `library` is set to `opentitan`, and the `name` is prefixed with the instance name.
-  The optional version segment is retained.
-  Use this function on the `name` of all FuseSoC cores which contain sources generated from templates and which export symbols into the global namespace.
-
-### Templating FuseSoC core files
-
-FuseSoC core files can be templated just like any other file.
-Especially handy is the `instance_vlnv()` template function, which transforms a placeholder VLNV (a string in the form `vendor:library:name:version`) into a instance-specific one.
-
-For example, a `rv_plic.core.tpl` file could look like this:
-
-```yaml
-CAPI=2:
-name: ${instance_vlnv("lowrisc:ip:rv_plic")}
-```
-
-After processing, the `name` key is set to e.g. `lowrisc:opentitan:top_earlgrey_rv_plic`.
-
-The following rules should be applied when creating IP templates:
-
-* Template and use an instance-specific name for all FuseSoC cores which reference templated source files (e.g. SystemVerilog files).
-* Template and use an instance-specific name at least the top-level FuseSoC core.
-* If a FuseSoC core with an instance-specific name exposes a well-defined public interface (see below) add a `provides: lowrisc:ip_interfaces:<name>` line to the core file to allow other cores to refer to it without knowning the actual core name.
-
-#### Templating core files to uphold the "same name, same interface" principle
-
-FuseSoC core files should be written in a way that upholds the principle "same name, same public interface", i.e. if a FuseSoC core has the same name as another one, it must also provide the same public interface.
-
-Since SystemVerilog does not provide no strong control over which symbols become part of the public API developers must be carefully evaluate their source code.
-At least, the public interface is comprised of
-- module header(s), e.g. parameter names, ports (names, data types),
-- package names, and all identifiers within it, including enum values (but not the values assigned to them),
-- defines
-
-If any of those aspects of a source file are templated, the core name referencing the files must be made instance-specific.
-
-## Library usage
-
-Ipgen can be used as Python library by importing from the `ipgen` package.
-Refer to the comments within the source code for usage information.
-
-The following example shows how to produce an IP block from an IP template.
-
-```python
-from pathlib import Path
-from ipgen import IpConfig, IpTemplate, IpBlockRenderer
-
-# Load the template
-ip_template = IpTemplate.from_template_path(Path('a/ip/template/directory'))
-
-# Prepare the IP template configuration
-params = {}
-params['src'] = 17
-ip_config = IpConfig("my_instance", params)
-
-# Produce an IP block
-renderer = IpBlockRenderer(ip_template, ip_config)
-renderer.render(Path("path/to/the/output/directory"))
-```
-
-The output produced by ipgen is determined by the chosen renderer.
-For most use cases the `IpBlockRenderer` is the right choice, as it produces a full IP block directory.
-Refer to the `ipgen.renderer` module for more renderers available with ipgen.
-
-## Command-line usage
-
-The ipgen command-line tool lives in `util/ipgen.py`.
-The first argument is typically the action to be executed.
-
-```console
-$ cd $REPO_TOP
-$ util/ipgen.py --help
-usage: ipgen.py [-h] ACTION ...
-
-optional arguments:
-  -h, --help  show this help message and exit
-
-actions:
-  Use 'ipgen.py ACTION --help' to learn more about the individual actions.
-
-  ACTION
-    describe  Show details about an IP template
-    generate  Generate an IP block from an IP template
-```
-
-## `ipgen generate`
-
-```console
-$ cd $REPO_TOP
-$ util/ipgen.py generate --help
-usage: ipgen.py generate [-h] [--verbose] -C TEMPLATE_DIR -o OUTDIR [--force] [--config-file CONFIG_FILE]
-
-Generate an IP block from an IP template
-
-optional arguments:
-  -h, --help            show this help message and exit
-  --verbose             More info messages
-  -C TEMPLATE_DIR, --template-dir TEMPLATE_DIR
-                        IP template directory
-  -o OUTDIR, --outdir OUTDIR
-                        output directory for the resulting IP block
-  --force, -f           overwrite the output directory, if it exists
-  --config-file CONFIG_FILE, -c CONFIG_FILE
-                        path to a configuration file
-```
-
-## `ipgen describe`
-
-```console
-$ cd $REPO_TOP
-$ util/ipgen.py generate --help
-usage: ipgen.py describe [-h] [--verbose] -C TEMPLATE_DIR
-
-Show all information available for the IP template.
-
-optional arguments:
-  -h, --help            show this help message and exit
-  --verbose             More info messages
-  -C TEMPLATE_DIR, --template-dir TEMPLATE_DIR
-                        IP template directory
-```
-
-
-## Limitations
-
-### Changing the IP block name is not supported
-
-Every IP block has a name, which is reflected in many places: in the name of the directory containing the block, in the base name of various files (e.g. the Hjson files in the `data` directory), in the `name` key of the IP description file in `data/<ipname>.hjson`, and many more.
-
-To "rename" an IP block, the content of multiple files, and multiple file names, have to be adjusted to reflect the name of the IP block, while keeping cross-references intact.
-Doing that is possible but a non-trivial amount of work, which is currently not implemented.
-This limitation is of lesser importance, as each template may be used only once in every toplevel design (see below).
-
-What is supported and required for most IP templates is the modification of the FuseSoC core name, which can be achieved by templating relevant `.core` files (see above).
-
-
-### Each template may be used only once per (top-level) design
-
-Each template may be used to generate only once IP block for each top-level design.
-The generated IP block can still be instantiated multiple times from SystemVerilog, including with different SystemVerilog parameters passed to it.
-It is not possible, however, to, for example, use one IP block template to produce two different flash controllers with different template parameters.
-
-IP templates generally contain code which exports symbols into the global namespace of the design: names of SystemVerilog modules and packages, defines, names of FuseSoC cores, etc.
-Such names need to be unique for each design, i.e. we cannot have multiple SystemVerilog modules with the same name in one design.
-To produce multiple IP blocks from the same IP template within a top-level design, exported symbols in generated IP blocks must be made "unique" within the design.
-Making symbols unique can be either done manually by the author of the template or automatically.
-
-In the manual approach, the template author writes all global identifiers in templates in a unique way, e.g. `module {prefix}_flash_ctrl` in SystemVerilog code, and with similar approaches wherever the name of the IP block is used (e.g. in cross-references in `top_<toplevelname>.hjson`).
-This is easy to implement in ipgen, but harder on the IP template authors, as it is easy to miss identifiers.
-
-In the automated approach, a tool that understands the source code transforms all identifiers.
-C++ name mangling uses a similar approach.
-The challenge is, however, to create a tool which can reliably do such source code transformations on a complex language like SystemVerilog.
-
-Finally, a third approach could be a combination of less frequently used/supported SystemVerilog language features like libraries, configs, and compilation units.
diff --git a/doc/rm/markdown_usage_style.md b/doc/rm/markdown_usage_style.md
deleted file mode 100644
index f7e3683..0000000
--- a/doc/rm/markdown_usage_style.md
+++ /dev/null
@@ -1,315 +0,0 @@
----
-title: "Markdown Usage and Style Guide"
----
-
-## Basics
-
-### Summary
-
-Markdown files are used to write most documentation.
-The main Markdown tool is [Hugo](https://gohugo.io).
-
-The Markdown processing is done using the `build_docs.py` tool in the `util` directory.
-
-As with all style guides the intention is to:
-
-*   promote consistency across projects
-*   promote best practices
-*   increase code sharing and re-use
-
-
-### Terminology Conventions
-
-Unless otherwise noted, the following terminology conventions apply to this style guide:
-
-*   The word ***must*** indicates a mandatory requirement.
-    Similarly, ***do not*** indicates a prohibition.
-    Imperative and declarative statements correspond to ***must***.
-*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
-    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
-    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
-*   The word ***may*** indicates a course of action is permitted and optional.
-*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
-
-### Style Guide Exceptions
-
-***Justify exceptions with a comment.***
-
-No style guide is perfect.
-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.
-
-## General Markdown Style
-
-### Line length
-
-In OpenTitan, most--but not all--Markdown documents will be rendered to HTML before they are presented to the reader.
-However, README files are an important exception, and so the recommended line-wrapping style differs for these two types of files.
-
-1. ***Rendered Files***:
-Files which are intended to be rendered before viewing should have exactly one sentence per line, with no line breaks in the middle of a sentence.
-This way change reviews will highlight only those sentences which are modified.
-Though the long line lengths make the files slightly less convenient to read from the command-line, this greatly simplifies the review process.
-When reviewing Markdown changes, every altered sentence will be included in its entirety in the file diff.
-
-2. ***README Files***:
-README files should wrap lines at under 80 characters.
-This ensures that the source is readable without any Markdown processing.
-Please note, however, that re-wrapping a paragraph after an insertion or deletion tends to cause longer diffs when the change is reviewed.
-When making changes to a document using this style, please consider allowing short lines rather than a full re-wrap after minor edits.
-Then occasionally separate commits can be used that only do re-wrapping of the paragraphs.
-
-### Headings and sections
-
-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.
-
-### Images
-
-Pictures can be included using the standard Markdown syntax (`![Alt Text](url)`).
-The preferred format is Scalable Vector Graphics (`.svg`), alternatively Portable Network Graphics (`.png`).
-
-### Waveforms
-
-Waveforms can be included by adding [wavejson](https://github.com/wavedrom/schema/blob/master/WaveJSON.md) code surrounded by `{{</* wavejson */>}}` shortcode tags.
-
-### Text Format
-
-Where possible, please restrict Markdown text to the ASCII character set to avoid downstream tool issues.
-Unicode may be used when referring to proper names.
-
-### Comments
-
-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` file extension.
-
-## Markdown file format for IP module descriptions
-
-Typically the Markdown file for an IP block follows the same outline.
-
-The header instantiates the standard document header and reads the Hjson description of the module.
-
-```
----
-title: "Example IP Block"
----
-```
-
-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.]({{</* relref "doc/rm/comportability_specification" */>}})
-See that document for integration overview within the broader top level system.
-```
-
-The next section summarizes the feature set of the IP block.
-
-```
-## Features
-
-* Bulleted list
-* Of main features
-```
-
-There then follows a general description of the IP
-
-```
-## Description
-
-Description of the IP.
-```
-The Compatibility information will allow device driver writers to identify existing code that can be used directly or with minor changes.
-
-_This section is primarily of interest to software engineers._
-
-
-```
-## Compatability
-
-Notes on if the IP register interface is compatible with any existing register interface.
-Also note any differences.
-For example: Matches 16550 UART interface but registers are at 32-bit word offsets.
-```
-
-The next major section is a more detailed operational description of the module.
-
-```
-# Theory of Operations
-
-```
-
-Conventionally one of the first sections includes a block diagram and a description.
-
-_Should be useful to hardware designers, verification engineers and software engineers._
-
-
-```
-
-## Block Diagram
-
-![Name Block Diagram](block_diagram.svg)
-
-```
-
-There should be a section containing the automatically generated description of the IP including the signals, interrupts and alerts that it uses.
-
-_Primary user is the SoC integrator, but useful for everyone._
-
-Note that the interrupt descriptions are also automatically placed in the interrupt status register bit descriptions, which is the most likely place for software engineers to reference them.
-
-
-```
-
-## Hardware Interfaces
-
-
-```
-
-The organization of the design details section is done to suit the module.
-
-```
-
-## Design Details
-
-Details of the design.
-
-### Many third level headings
-```
-There are probably waveforms embedded here:
-
-```
-
-{{</* 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.
-
-_This section is primarily for software engineers, but it is expected that the code fragments are used by verification engineers._
-
-```
-
-# 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!
-
-```
-## Initialization
-```
-```c
-
- if (...) {
-   a = ...
- }
-```
-
-Other sections cover different use cases and example code fragments.
-
-```
-
-## Use case A (eg Transmission)
-
-## Use case B (eg Reception)
-
-```
-
-It is important to include a discussion of error conditions.
-
-```
-## Error conditions
-
-```
-
-Also comment on anything special about interrupts, potentially including the priority order for servicing interrupts.
-
-
-```
-
-## Interrupt Handling
-
-```
-
-The document should end with the automatically generated register tables.
-
-```
-## Register Table
-
-{{</* registers "hw/ip/component/data/component.hjson" */>}}
-
-```
-
-To allow cut/paste of the default structure, here is an uncommented version:
-
-```
----
-title: Name HWIP Technical Specification
----
-
-# Overview
-
-This document specifies Name hardware IP functionality.
-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.
-
-## Features
-
-* Bulleted list
-
-## Description
-
-
-## Compatibility
-
-
-# Theory of Operations
-
-
-## Block Diagram
-
-![Name Block Diagram](block_diagram.svg)
-
-## Hardware Interfaces
-
-{{</* incGenFromIpDesc "../data/component.hjson" "hwcfg" */>}}
-
-## Design Details
-
-### Many third level headings
-
-# Programmers Guide
-
-## Initialization
-
-## Use case A (eg Transmission)
-
-## Use case B (eg Reception)
-
-## Error conditions
-
-## Interrupt Handling
-
-## Register Table
-
-{{</* incGenFromIpDesc "../data/component.hjson" "registers" */>}}
-
-```
diff --git a/doc/rm/otbn_style_guide.md b/doc/rm/otbn_style_guide.md
deleted file mode 100644
index bcdb807..0000000
--- a/doc/rm/otbn_style_guide.md
+++ /dev/null
@@ -1,246 +0,0 @@
----
-title: "OTBN Assembly Style Guide"
----
-
-Where possible, OTBN assembly should follow the same principles as the [RISC-V assembly style guide]({{< relref "asm_coding_style" >}}).
-This guide describes additional OTBN-specific guidelines, and places where OTBN assembly can or should diverge from the RISC-V style guidelines.
-
-## General Advice
-
-### Register Names
-
-There's no ABI, so OTBN registers cannot be referred to by ABI names.
-
-### Capitalization
-
-Lower case for mnemonics and registers.
-
-Example:
-```S
-  /* Correct */
-  bn.addi   w3, w3, 2
-
-  /* Wrong */
-  BN.ADDI   W3, W3, 2
-```
-
-Upper case for flags, flag groups, and half word identifiers in multiplication instructions.
-Example:
-```S
-  /* Correct */
-  bn.mulqacc.so  w27.L, w30.0, w25.1, 64
-  bn.sel         w3, w4, w2, FG1.C
-
-  /* Wrong */
-  bn.mulqacc.so  w27.l, w30.0, w25.1, 64
-  bn.sel         w3, w4, w2, fg1.c
-```
-
-### Pseudoinstructions
-
-As in RISC-V, prefer pseudoinstructions (e.g. `ret`, `li`) in OTBN code where they exist.
-However, `loop` and `loopi` instructions require counting the number of instructions in the loop body, which can be tricky for pseudoinstructions that can expand to multiple instructions. 
-(For example, `li` can expand to either 1 or 2 instructions depending on the immediate value.)
-Therefore, it is permitted to avoid pseudoinstructions that can expand to multiple instructions within loop bodies.
-
-### Wrappers and ecall
-
-Generally, OTBN routines should be written so that they finish in `ret`.
-Use of `ecall` should be restricted to thin wrapper files which serve as an interface for the more substantial assembly files, usually just reading one or two arguments and then calling subroutines from the other files.
-
-### Comments
-
-The `//` syntax is not currently available for OTBN because it is not supported by `riscv32-unknown-as`.
-Use the `/* */` syntax instead.
-
-### Labels
-
-It is not required to use an `L_` prefix for internal labels.
-Labels should not be indented.
-
-### Operand Alignment
-
-***Operands should be aligned within blocks of OTBN code.***
-
-The exact spacing betweeen mnemonic and operand is not important, as long as it is consistent within the block.
-
-Example:
-```S
-  /* Correct */
-  bn.add  w4, w7, w10
-  bn.addc w5, w8, w11
-  bn.sel  w2, w2, w3, C
-
-  /* Correct */
-  bn.add        w4, w7, w10
-  bn.addc       w5, w8, w11
-  bn.sel        w2, w2, w3, C
-
-  /* Wrong */
-  bn.add w4, w7, w10
-  bn.addc w5, w8, w11
-  bn.sel w2, w2, w3, C
-```
-
-### Register and flag group clobbering
-
-***Always document which registers and flag groups are clobbered by an OTBN "function", and which flags have meaning.***
-
-OTBN subroutines should always document the registers and flag groups whose values they overwrite, including those used for output.
-In addition to Doxygen-style `@param[in]` and `@param[out]` notations, OTBN subroutines should also state whether the flags have meaning at the end of the subroutine.
-If a subroutine jumps to another subroutine that clobbers additional registers or flag groups, these additional names should be added to the caller's list.
-
-Example:
-```S
-/**
- * Compute the sum of two 2048-bit numbers.
- *
- *   Returns C = A + B.
- *
- * Flags: When leaving this subroutine, the C flag of FG0 depends on the
- *        addition.
- *
- * @param[in]  [w10:w3]: A, first 2048-bit operand
- * @param[in]  [w18:w11]: B, second 2048-bit operand
- * @param[in]  w31: all-zero
- * @param[out] [w26:w19]: C, result
- *
- * clobbered registers: x9 to x13, w19 to w28
- * clobbered flag groups: FG0
- */
-wide_sum:
-  /* Prepare temporary registers. */
-  li       x9, 3
-  li       x10, 11
-  li       x11, 19
-  li       x12, 27
-  li       x13, 28
-
-  /* Clear flags. */
-  bn.add   w31, w31, 0
-
-  /* Addition loop. */
-  loopi    8, 4
-    /* w27 <= A[i] */
-    bn.movr  x12, x9++
-    /* w28 <= B[i] */
-    bn.movr  x13, x10++
-    /* w27 <= w27 + w28 */
-    bn.addc  w27, w27, w28
-    /* C[i] <= w27 */
-    bn.movr  x11++, x12
-
-  ret
-```
-
-### Alignment Directives
-
-Big-number load/store instructions (`bn.lid`, `bn.sid`) require DMEM addresses
-to be 256-bit aligned; use `.balign 32` for any big-number inline binary data
-to ensure this. For non-big-number data, `.balign 4` (32-bit alignment) is
-recommended.
-
-### Inline Binary Directives
-
-Prefer `.word` over alternatives such as `.quad`.
-
-## Secure Coding for Cryptography
-
-The following guidelines address cryptography-specific concerns for OTBN assembly.
-
-### Copying register values
-
-Prefer `bn.mov <wrd>, <wrs>` to `bn.addi <wrd>, <wrs>, 0` when copying data between registers.
-Because `bn.addi` passes data through the ALU, it will strip off integrity protection, while `bn.mov` will copy the integrity protection bits.
-
-### Constant time code
-
-***In cryptographic code, subroutines should always document whether they run in constant or variable time.***
-
-In situations where constant- and variable- time code is mixed, it is recommended to name variable-time subroutines something that makes it clear they are variable-time, such as a name that ends in `_var`.
-If a piece of code is constant-time with respect to some inputs but not others (e.g. with respect to the `MOD` register but not to the operands), this should be documented.
-
-Example:
-```S
-/**
- * Determine if two 2048-bit operands are equal.
- *
- *   Returns 1 if A = B, otherwise 0.
- *
- * This routine runs in constant time.
- *
- * Flags: Flags have no meaning beyond the scope of this subroutine.
- *
- * @param[in]  [w10:w3]: A, first 2048-bit operand
- * @param[in]  [w18:w11]: B, second 2048-bit operand
- * @param[in]  w31: all-zero
- * @param[out] x3: result, 1 or 0
- *
- * clobbered registers: x2, x3, x6, x9 to x12
- * clobbered flag groups: FG0
- */
-eq_2048:
-
-  /* Prepare temporary registers. */
-  li       x9, 3
-  li       x10, 11
-  li       x11, 27
-  li       x12, 28
-
-  /* Check if all limbs are equal. */
-  li       x3, 1
-  loopi    8, 4
-    /* w27 <= A[i] */
-    bn.movr  x11, x9++
-    /* w28 <= B[i] */
-    bn.movr  x12, x10++
-    /* x2 <= (w27 == w28) */
-    jal      x1, eq_256
-    /* x3 <= x3 & x2 */
-    and      x3, x2, x2
-
-  ret
-
-/**
- * Determine if two 2048-bit operands are equal.
- *
- *   Returns 1 if A = B, otherwise 0.
- *
- * This routine runs in variable time.
- *
- * Flags: Flags have no meaning beyond the scope of this subroutine.
- *
- * @param[in]  [w10:w3]: A, first 2048-bit operand
- * @param[in]  [w18:w11]: B, second 2048-bit operand
- * @param[in]  w31: all-zero
- * @param[out] x2: result, 1 or 0
- *
- * clobbered registers: x2, x6, x9 to x12
- * clobbered flag groups: FG0
- */
-eq_2048_var:
-
-  /* Prepare temporary registers. */
-  li       x9, 3
-  li       x10, 11
-  li       x11, 27
-  li       x12, 28
-
-  /* Check if all limbs are equal. */
-  li       x2, 1
-  loopi    8, 5
-    /* If x2 is 0, skip to the end of the loop. (It's still necessary to
-       complete the same number of loop iterations to avoid polluting the loop
-       stack, but we can skip all instructions except the last.) */
-    beq      x2, x0, loop_end
-    /* w27 <= A[i] */
-    bn.movr  x11, x9++
-    /* w28 <= B[i] */
-    bn.movr  x12, x10++
-    /* x2 <= (w27 == w28) */
-    jal      x1, eq_256
-loop_end:
-    nop
-
-  ret
-```
diff --git a/doc/rm/python_coding_style.md b/doc/rm/python_coding_style.md
deleted file mode 100644
index 68b9473..0000000
--- a/doc/rm/python_coding_style.md
+++ /dev/null
@@ -1,200 +0,0 @@
----
-title: "Python Coding Style Guide"
----
-
-## Basics
-
-### Summary
-
-Python3 is the main language used for simple tools.
-As tools grow in complexity, or the requirements on them develop, they serve as valuable prototypes to re-implement tools or their behaviors more maintainably.
-
-Python can be written in vastly different styles, which can lead to code conflicts and code review latency.
-This style guide aims to promote Python readability across groups.
-To quote the C++ style guide: "Creating common, required idioms and patterns makes code much easier to understand."
-
-This guide defines the lowRISC style for Python version 3.
-The goals are to:
-
-*   promote consistency across hardware development projects
-*   promote best practices
-*   increase code sharing and re-use
-
-
-### Terminology Conventions
-
-Unless otherwise noted, the following terminology conventions apply to this style guide:
-
-*   The word ***must*** indicates a mandatory requirement.
-    Similarly, ***do not*** indicates a prohibition.
-    Imperative and declarative statements correspond to ***must***.
-*   The word ***recommended*** indicates that a certain course of action is preferred or is most suitable.
-    Similarly, ***not recommended*** indicates that a course of action is unsuitable, but not prohibited.
-    There may be reasons to use other options, but the implications and reasons for doing so must be fully understood.
-*   The word ***may*** indicates a course of action is permitted and optional.
-*   The word ***can*** indicates a course of action is possible given material, physical, or causal constraints.
-
-### Style Guide Exceptions
-
-***Justify exceptions with a comment.***
-
-No style guide is perfect.
-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, as well as a lint waiver pragma where appropriate.
-
-A common case where you may wish to disable tool-enforced reformatting is for large manually formatted data literals.
-In this case, no explanatory comment is required and yapf can be disabled for that literal [with a single pragma](https://github.com/google/yapf#why-does-yapf-destroy-my-awesome-formatting).
-
-## Python Conventions
-
-### Summary
-
-The lowRISC style matches [PEP8](https://www.python.org/dev/peps/pep-0008/) with the following options:
-* Bitwise operators should be placed before a line split
-* Logical operators should be placed before a line split
-
-To avoid doubt, the interpretation of PEP8 is done by [yapf](https://github.com/google/yapf) and the style guide is set using a `.style.yapf` file in the top level directory of the repository.
-This just sets the base style to pep8 and overrides with the exceptions given above.
-
-In addition to the basic style, imports must be ordered alphabetically within sections:
-* Future
-* Python Standard Library
-* Third Party
-* Current Python Project
-
-The import ordering matches that enforced by [isort](https://github.com/timothycrosley/isort).
-Currently the `isort` defaults are used.
-If this changes a `.isort.cfg` file will be placed in the top level directory of the repository.
-
-### Lint tool
-
-The `lintpy.py` utility in `util` can be used to check Python code.
-It checks all Python (`.py`) files that are modified in the local repository and will report problems.
-Both `yapf` and `isort` checks are run.
-
-Basic lintpy usage is just to run from the util directory.
-If everything is fine the command produces no output, otherwise it will report the problems.
-Additional information will be printed if the `--verbose` or `-v` flag is given.
-
-```console
-$ cd $REPO_TOP/util
-$ ./lintpy.py
-$ ./lintpy.py -v
-```
-
-Checking can be done on an explicit list of files using the `--file` or `-f` flag.
-In this case the tool will not derive the list from git, so any file can be checked even if it has not been modified.
-
-```console
-$ cd $REPO_TOP/util
-$ ./lintpy.py -f a.py subdir/*.py
-```
-
-Errors may be fixed using the same tool to edit the problem file(s) in-place (you may need to refresh the file(s) in your editor after doing this).
-This uses the same set of files as are being checked, so unless the`--file` or `-f` flag is used this will only affect files that have already been modifed (or staged for commit if `-c`is used) and will not fix errors in Python files that have not been touched.
-
-```console
-$ cd $REPO_TOP/util
-$ ./lintpy.py --fix
-```
-
-lintpy.py can be installed as a git pre-commit hook which will prevent commits if there are any lint errors.
-This will normally be a symlink to the tool in util so changes are automatically used (it also works if `lintpy.py` is copied to `.git/hooks/pre-commit` but in that case the hook must be reinstalled each time the tool changes).
-Since git hooks are not automatically installed the symlink hook can be installed if required using the tool:
-
-```console
-$ cd $REPO_TOP/util
-$ ./lintpy.py --hook
-```
-
-
-Fixing style errors for a single file can also be done with `yapf` directly:
-```console
-$ yapf -i file.py
-```
-
-Fixing import ordering errors for a single file can be done with `isort`:
-```console
-$ isort file.py
-```
-
-Yapf and isort are Python packages and should be installed with pip:
-
-```console
-$ pip3 install --user yapt
-$ pip3 install --user isort
-```
-
-### File Extensions
-
-***Use the `.py` extension for Python files***
-
-### General File Appearance
-
-#### Characters
-
-***Use only UTF-8 characters with UNIX-style line endings(`"\n"`).***
-
-Follows PEP8.
-
-#### POSIX File Endings
-
-***All lines on non-empty files must end with a newline (`"\n"`).***
-
-#### Line Length
-
-***Wrap the code at 79 characters per line.***
-
-The maximum line length follows PEP8.
-
-Exceptions:
-
--   Any place where line wraps are impossible (for example, an include path might extend past 79 characters).
-
-#### No Tabs
-
-***Do not use tabs anywhere.***
-
-Use spaces to indent or align text.
-
-To convert tabs to spaces on any file, you can use the [UNIX `expand`](http://linux.die.net/man/1/expand) utility.
-
-#### No Trailing Spaces
-
-***Delete trailing whitespace at the end of lines.***
-
-### Indentation
-
-***Indentation is four spaces per level.***
-
-Follows PEP8.
-Use spaces for indentation.
-Do not use tabs.
-You should set your editor to emit spaces when you hit the tab key.
-
-### Executable Python tools
-
-Tools that can be executed should use `env` to avoid making assumptions about the location of the Python interpreter.
-Thus they should begin with the line:
-
-```console
-#!/usr/bin/env python3
-```
-
-This should be followed by a comment with the license information and the doc string describing the command.
-
-#### Argument Parsing
-
-***Use argparse to parse command line arguments.***
-
-In command line tools use the [argparse library](https://docs.python.org/3/library/argparse.html) to parse arguments.
-This will provide support for `--help` and `-h` to get usage information.
-
-Every command line program should provide `--version` to provide standard version information.
-This lists the git repositary information for the tool and the version numbers of any Python packages that are used.
-The `show_and_exit` routine in `reggen/version.py` can be used to do this.
-
-Options that consume an arbitrary number of command line arguments with `nargs="*"` or `nargs="+"` should be avoided wherever possible.
-Comma separated lists can be passed to form single arguments and can be split after arguments are parsed.
-Args that allow for lists should mention that capability and the separator using the help keyword.
-To display proper delimiting of lists, args that allow for lists may demonstrate the separator with the metavar keyword.
diff --git a/doc/rm/ref_manual_fpga.md b/doc/rm/ref_manual_fpga.md
deleted file mode 100644
index 29a4df6..0000000
--- a/doc/rm/ref_manual_fpga.md
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: "FPGA Reference Manual"
----
-
-This manual provides additional usage details about the FPGA.
-Specifically, it provides instructions on SW development flows and testing procedures.
-Refer to the [FPGA Setup]({{< relref "doc/getting_started/setup_fpga" >}}) guide for more information on initial setup.
-
-## FPGA SW Development Flow
-
-The FPGA is meant for both boot ROM and general software development.
-The flow for each is different, as the boot ROM is meant to be fairly static while general software can change very frequently.
-
-### Boot ROM development
-
-The FPGA bitstream is built after compiling whatever code is sitting in `sw/device/lib/testing/test_rom`.
-This binary is used to initialize internal FPGA memory and is part of the bitstream directly.
-
-To update this content without rebuilding the FPGA, a flow is required to splice a new boot ROM binary into the bitstream.
-There are two prerequisites in order for this flow to work:
-* The boot ROM during the build process must be correctly inferred by the tool.
-  * See [prim_rom](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim_generic/rtl/prim_generic_rom.sv) and [vivado_hook_opt_design_post.tcl](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/util/vivado_hook_opt_design_post.tcl).
-* The MMI file outlining the physical boot ROM placement and mapping to FPGA block RAM primitives needs to be generated by the tool.
-  * See [vivado_hook_write_bitstream_pre.tcl](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/util/vivado_hook_write_bitstream_pre.tcl).
-
-With these steps in place, a script can be invoked to take a new binary and push its contents into an existing bitfile.
-For details, please see the [`splice_rom.sh`](https://github.com/lowRISC/opentitan/blob/master/util/fpga/splice_rom.sh) script.
-
-See example below:
-
-```console
-$ cd $REPO_TOP
-$ ./util/fpga/splice_rom.sh
-$ bazel run //sw/host/opentitantool load-bitstream build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit
-```
-
-The script assumes that there is an existing bitfile `build/lowrisc_systems_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit` (this is created after following the steps in [FPGA Setup]({{< relref "doc/getting_started/setup_fpga" >}})).
-
-The script assumes that there is an existing boot ROM image under `build-bin/sw/device/lib/testing/test_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_chip_earlgrey_cw310_0.1/synth-vivado/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig`.
-
-`opentitantool` can then be used to directly flash the updated bitstream to the FPGA.
-
-### General Software Development
-
-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, `opentitantool` is required.
-
-Also the binary you wish to load needs to be built first.
-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.
-The example below builds the `hello_world` image and loads it onto the FPGA.
-
-```console
-$ cd ${REPO_TOP}
-$ bazel run //sw/host/opentitantool set-pll # This needs to be done only once.
-$ bazel build //sw/device/examples/hello_world:hello_world_fpga_cw310_bin
-$ bazel run //sw/host/opentitantool bootstrap $(ci/scripts/target-location.sh //sw/device/examples/hello_world:hello_world_fpga_cw310_bin)
-```
-
-Uart output:
-```
-I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
-I00001 test_rom.c:87] TestROM:6b2ca9a1
-I00000 test_rom.c:81] Version: earlgrey_silver_release_v5-5886-gde4cb1bb9, Build Date: 2022-06-13 09:17:56
-I00001 test_rom.c:87] TestROM:6b2ca9a1
-I00002 test_rom.c:118] Test ROM complete, jumping to flash!
-I00000 hello_world.c:66] Hello World!
-I00001 hello_world.c:67] Built at: Jun 13 2022, 14:16:59
-I00002 demos.c:18] Watch the LEDs!
-I00003 hello_world.c:74] Try out the switches on the board
-I00004 hello_world.c:75] or type anything into the console window.
-I00005 hello_world.c:76] The LEDs show the ASCII code of the last character.
-```
-
-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](https://github.com/lowRISC/opentitan/tree/master/sw/device/lib/testing/test_rom).
diff --git a/doc/rm/register_tool/index.md b/doc/rm/register_tool/index.md
deleted file mode 100644
index b536dab..0000000
--- a/doc/rm/register_tool/index.md
+++ /dev/null
@@ -1,727 +0,0 @@
----
-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.
-
-
-## Running standalone regtool.py
-
-The standalone `regtool.py` is a Python 3 tool to read configuration and register descriptions in Hjson and generate various output formats.
-Currently it can output HTML documentation, standard JSON, compact standard JSON (whitespace removed), Hjson, Verilog RTL and various forms of C header files.
-
-The standard `--help` and `--version` command line flags are supported to print the usage and version information.
-Because the version includes information on libraries (which may be different between systems) reporting the version output is sometimes useful when issues are reported.
-
-### Setup and Examples
-
-Setup and examples of the tool are given in the README.md file in the `util/reggen` directory.
-
-## Configuration and Register Definition File Format
-
-The tool input is an Hjson file containing the Comportable description of the IP block and its registers.
-
-A description of Hjson (a variant of JSON) and the recommended style is in the [Hjson Usage and Style Guide]({{< relref "doc/rm/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.
-
-For more detail on the non-register entries of the Hjson configuration file, see [this section]({{< relref "doc/rm/comportability_specification/index.md#configuration-description-hjson" >}}) of the Comportability Specification.
-
-{{% selfdoc "reggen" %}}
-
-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.
-
-Space may be held for future registers (or to match some other layout) by reserving register slots.
-A group containing just the reserved key can be inserted in the list of registers to reserve space.
-For example to reserve space for four registers between REGA and REGB (thus make REGB offset be REGA offset plus 5 times the size in bytes of a register):
-
-```hjson
-    { name: "REGA",
-      ...register definition...
-    }
-    { reserved: "4" }
-    { name: "REGB",
-      ...register definition...
-    }
-```
-
-In other cases, such as separating functional groups of registers, the absolute offset can be specified.
-The next register will have the offset specified.
-It is an error if the requested offset is less than the current offset.
-For example to place ITCR at offset 0x100:
-
-```hjson
-    { skipto: "0x100" }
-    { name: "ITCR",
-      ...register definition...
-    }
-
-```
-
-The tool can reserve an area of the memory space for something that is not a simple register, for example access to a buffer memory.
-This is done with a `window` declaration.
-The window size is specified as `items:` where each item is a `regwidth` wide word.
-The size in bytes is thus `(items * (regwidth/8))` bytes.
-If byte writes are supported the `byte-write: "True"` flag can be given.
-The tool will normally increment the offset to align the region based on its size.
-
-```hjson
-    {window: {
-         name: "win1"
-         items: "64"
-         swaccess: "rw"
-         desc: '''
-               A simple 256 byte window that will be aligned.
-           '''
-      }
-    },
-
-```
-
-The tool will give a warning if the size is not a power of 2.
-The tool will also give a warning if the window has software access other than read-only, write-only or read-write.
-Both of these warnings are supressed if the description acknowledges there is something special about this window by setting `unusual: "True"` in the window declaration.
-
-The tool will increment the offset to align the region based on its size.
-The start address is aligned such that the base item in the window is at an address with all zeros in the low bits.
-For instance, if the current offset is 0x104, and the window size in 32-bit words is between 0x11 and 0x20 (inclusive) (i.e. 65-128 bytes), the window base will be set to 0x180.
-The next register will immedately follow the window, so will be at the window base address plus the window size in bytes.
-
-Sometimes the window may need to map a structure that is not a full word wide (for example providing debug access to a the memory in a 12-bit wide FIFO).
-In this case it may be convenient to have only the low bits of each word valid and use the word address directly as an index (rather than presenting a "packed" structure with the sub-word items packed into as few words as possible).
-The window declaration can be annotated to document this.
-For example debug access to a 64 entry 12-bit wide FIFO could use a window:
-
-```hjson
-    {window: {
-         name: "fifodebug"
-         items: "64"
-         validbits: "12"
-         swaccess: "ro"
-         desc: '''
-               The 64 entry FIFO is mapped into the low 12-bits
-               of each regwidth bit wide word.
-           '''
-      }
-    },
-
-```
-
-The tool can generate registers that follow a base pattern, for example when there are configuration fields for multiple instances.
-The base pattern defines the bits (which need not be contiguous) used for the first instance and the tool uses this to pack the required number of instances into one or more registers.
-
-For example a fancy GPIO interrupt configuration may have 4 bits per GPIO to allow generation on rising and falling edge and a two bit enum to determine the interrupt severity.
-In this case the multireg can be used to build the multiple registers needed.
-The description below shows the fields given for GPIO0 and requests generation of 32 instances.
-If the registers are 32 bits wide then the tool will pack the four bit instances into four registers `INT_CTRL_0`, `INT_CTRL_1`, `INT_CTRL_2` and `INT_CTRL_3`.
-
-```hjson
-    { 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: "3:2", name: "TYPE", resval: "0",
-            desc: "Type of interrupt to raise"
-        enum: [
-          {value: "0", name: "none", desc: "no interrupt, only log" },
-          {value: "1", name: "low", desc: "low priotiry interrupt" },
-          {value: "2", name: "high", desc: "high priotiry interrupt" },
-          {value: "3", name: "nmi", desc: "non maskable interrupt" }
-        ]
-          }
-      ]
-      }
-    },
-
-```
-
-Note that the definition bits for the base instance need not be contiguous.
-In this case the tool will match the patten for the other instances.
-For example the data bits and mask bits could be in the lower and upper parts of a register:
-
-```hjson
-    { multireg: {
-          name: "WDATA",
-      desc: "Write with mask to GPIO out register",
-      count: "32",
-      cname: "GPIO",
-      swaccess: "rw",
-      fields: [
-          { bits: "0", name: "D", resval: "0",
-            desc: "Data to write if mask bit is 1"
-          }
-          { bits: "16", name: "M", resval: "0",
-            desc: "Mask, set to allow data write"
-          }
-      ]
-      }
-    }
-```
-
-In this case instance 1 will use bits 1 and 17, instance 2 will use 2 and 18 and so on.
-Instance 16 does not fit, so will start a new register.
-
-### Verification Tags Definition and Format
-
-This section documents the usage of tags in the register Hjson file.
-`Tags` is a list of strings that could add into a register, field, or memory.
-It can store special information such as csr register/field exclusion, memory exclusion, reset test exclusion, etc.
-Adding a tag follows the string format `"tag_name:item1:item2..."`.
-For example:
-```hjson
-    tags: [// don't write to wdata - it affects several other csrs
-             "excl:CsrNonInitTests:CsrExclWrite"]
-```
-
-Current `tags` supports:
-* CSR tests exclusions:
-  Simulation based verification will run four CSR tests (if applicable to the module) through automation.
-  Detailed description of this methodology is documented in [CSR utilities]({{< relref "hw/dv/sv/csr_utils/doc" >}}).
-  The tag name is `excl`, and items are enum values for what CSR tests to exclude from, and what type of exclusions.
-  The enum types for exclusion test are:
-  ```systemverilog
-  // csr test types
-  typedef enum bit [NUM_CSR_TEST-1:0] {
-    CsrNonTest        = 5'h0,
-    // elementary test types
-    CsrHwResetTest    = 5'h1,
-    CsrRwTest         = 5'h2,
-    CsrBitBashTest    = 5'h4,
-    CsrAliasingTest   = 5'h8,
-    // combinational test types (combinations of the above), used for exclusion tagging
-    CsrNonInitTests   = 5'he, // all but HwReset test
-    CsrAllTests       = 5'hf  // all tests
-  } csr_test_type_e;
-  ```
-  The enum types for exclusion type are:
-  ``` systemverilog
-  typedef enum bit[2:0] {
-    CsrNoExcl         = 3'b000, // no exclusions
-    CsrExclInitCheck  = 3'b001, // exclude csr from init val check
-    CsrExclWriteCheck = 3'b010, // exclude csr from write-read check
-    CsrExclCheck      = 3'b011, // exclude csr from init or write-read check
-    CsrExclWrite      = 3'b100, // exclude csr from write
-    CsrExclAll        = 3'b111  // exclude csr from init or write or write-read check
-  } csr_excl_type_e;
-  ```
-
-## Register Tool Hardware Generation
-
-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]({{< relref "doc/rm/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.
-As an example the tool can be invoked to generate the uart registers with:
-
-```console
-$ cd hw/ip/uart/doc
-$ ../../../../util/regtool.py -r -t ../rtl uart.hjson
-```
-
-The first created file (`name_reg_pkg.sv`, from `name.hjson`) contains a SystemVerilog package definition that includes type definitions for two packed structures that have details of the registers and fields (all names are converted to lowercase).
-The `name_reg2hw_t` structure contains the signals that are driven from the register module to the rest of the hardware (this contains any required `.q, .qe`, and `.re` signals described below).
-The `name_hw2reg_t` structure contains the signals that are driven from the rest of the hardware to the register module (this contains any required `.d` and `.de` signals described below).
-The file also contains parameters giving the byte address offsets of the registers (these are prefixed with the peripheral `name` and converted to uppercase).
-
-The second file (`name_reg_top.sv`) is a SystemVerilog file that contains a module (`name_reg_top`) that instantiates the registers.
-This module connects to the TL-UL system bus interface and provides the register connections to the rest of the hardware.
-If the register definition contains memory windows then there will be subordinate TL-UL bus connections for each window.
-The module signature is:
-
-```systemverilog
-module name_reg_top (
-  input clk_i,
-  input rst_ni,
-
-  // Below Regster interface can be changed
-  input  tlul_pkg::tl_h2d_t tl_i,
-  output tlul_pkg::tl_d2h_t tl_o,
-
-  // This section is only provided if the definition includes
-  // 1 or more "window" definitions and contains an array of
-  // secondary TL-UL bus connectors for each window
-  // Output port for window
-  output tlul_pkg::tl_h2d_t tl_win_o  [1],
-  input  tlul_pkg::tl_d2h_t tl_win_i  [1],
-
-  // To HW
-  output uart_reg_pkg::uart_reg2hw_t reg2hw, // Write
-  input  uart_reg_pkg::uart_hw2reg_t hw2reg  // Read
-);
-```
-
-The sections below describe the hardware functionality of each register type both in terms of the RTL created, and the wires in the structures that will come along with the register.
-
-## Overall block diagram
-
-The diagram below gives an overview of the register module, `name_reg_top`.
-
-![reg_top](reg_top.svg)
-
-In this diagram, the TL-UL bus is shown on the left.
-Logic then breaks down individual write requests and read requests based upon the assigned address of the bus requests.
-Writes that match an address create an internal write enable to an individual register (or collection of registers in the case of a field), and return a successful write response.
-Reads that match an address return the associated data content for that register.
-See the section below on requests that don't match any register address.
-
-In the middle are the collections of registers, which are a function of the `hjson` input, and a definition of the functionality of each register (read-only, read-write, etc), detailed below.
-These are instantiations of the primitives `prim_subreg`, `prim_subreg_ext` and `prim_subreg_shadow` found in the lowRISC primitive library (lowrisc:prim:all).
-These take as inputs the write requests from the bus as well as the hardware struct inputs associated with that register.
-They create as output the current state of the register and a potential write enable.
-The `prim_subreg` module takes a parameter `SwAccess` that is used to adjust the implementation to the access type required.
-
-On the right are the typedef structs that gather the `q` and `qe`s into one output bundle, and receive the bundled `d` and `de` inputs.
-
-The addess decode and TL-UL 1:N adapter shown at the bottom are created only if the register definition includes one or more `window:` descriptions.
-Each window is given its own TL-UL connection and the implementation must provide a device interface.
-
-It is notable that in the current definition, each field of a register has its own register instantiation.
-This is required because the definitions allow unique `swaccess` and `hwaccess` values per field, but could be done at the register level otherwise.
-The individual bundled wires are associated with the fields rather than the full register, so the designer of the rest of the peripheral does not need to know the bit range association of the individual fields.
-
-### Error responses
-
-Writes and reads that target addresses that are not represented within the register list typically return an error.
-However, for security modules (Comportability definition forthcoming), this is under the control of a register module input signal `devmode_i`.
-This signal indicates whether the whole SOC device is in development or production mode.
-For security peripherals in production mode, it is desired to **not** send an error response, so write misses silently fail, and read misses silently fail, but return either random data (TBD) or all `1`s for response data (i.e. `0xFFFFFFFF` for a 32b register).
-For non-security peripherals, or when in development mode (`devmode_i == 1`) these writes and reads to undefined addresses will return with TL-UL error response.
-
-Other error responses (always sent, regardless of `devmode_i`) include for the following reasons:
-
-* TL-UL `a_opcode` illegal value
-* TL-UL writes of size smaller than register size
-  * I.e. writes of size 8b to registers > 8b will cause error (explicitly: if it has field bits within `[31:08]`)
-  * I.e. writes of size 16b to registers > 16b will cause error (explicitly: if it has field bits within `[31:16]`)
-* TL-UL writes of size smaller than 32b that are not word-aligned
-  * I.e. writes of size 8b or 16b that are not to an address that is 4B aligned return in error.
-
-Reads of size smaller than full word (32b) return the full register content and do not signal error.
-Reads response data is always in its byte-channel, i.e. a one-byte read to `address 0x3` will return the full word with the correct MSB in bits `[31:24]` on the TL-UL response bus (as well as the not-asked-for bytes 2:0 in `[23:0]`).
-
-Note with the windowing option, a new TL-UL bus (or more) is spawned and managed outside of this register module.
-Any window that makes use of the byte masks will include the `byte-write: "true"` keyword in their definition.
-Error handling by that TL-UL bus is **completely under the control of the logic that manages this bus.**
-It is recommended to follow the above error rules (including `devmode_i` for address misses on security peripherals) based on the declared number of `validbits`: for the window, but there are some cases where this might be relaxed.
-For example, if the termination of the TL-UL bus is a memory that handles byte and halfword writes via masking, errors do not need be returned for unaligned sub-word writes.
-
-## Register definitions per type
-
-The definition of what exactly is in each register type is described in this section.
-As shown above, the maximally featured register has inputs and outputs to/from both the bus interface side of the design as well as the hardware interface side.
-Some register types don't require all of these inputs and outputs.
-For instance, a read-only register does not require write data from the bus interface (this is configured by the `SwAccess` parameter to the `prim_subreg` module).
-The maximally defined inputs to this register block (termed the `subreg` from here forward) are given in the table below.
-Note that these are instantiated per field, not per register, so the width is the width of the field.
-The direction is the Verilog signal definition of `subreg` for that type.
-
-<table>
-  <tr>
-   <td><strong>name</strong>
-   </td>
-   <td><strong>direction</strong>
-   </td>
-   <td><strong>description</strong>
-   </td>
-  </tr>
-  <tr>
-   <td><code>we</code>
-   </td>
-   <td>input
-   </td>
-   <td>Write enable from the bus interface, scalar
-   </td>
-  </tr>
-  <tr>
-   <td><code>wd[]</code>
-   </td>
-   <td>input
-   </td>
-   <td>Write data from the bus interface, size == field size
-   </td>
-  </tr>
-  <tr>
-   <td><code>qs[]</code>
-   </td>
-   <td>output
-   </td>
-   <td>Output to read response data mux, size == field_size. This is typically the same as <code>q[]</code> except for <code>hwext</code> registers.
-   </td>
-  </tr>
-  <tr>
-   <td><code>de</code>
-   </td>
-   <td>input
-   </td>
-   <td>Hardware data enable from peripheral logic, scalar, should be set when the hardware wishes to update the register field
-   </td>
-  </tr>
-  <tr>
-   <td><code>d[]</code>
-   </td>
-   <td>input
-   </td>
-   <td>Hardware data from peripheral logic, size == field size
-   </td>
-  </tr>
-  <tr>
-   <td><code>qe</code>
-   </td>
-   <td>output
-   </td>
-   <td>Output register enable, scalar, true when bus interface writes to subreg
-   </td>
-  </tr>
-  <tr>
-   <td><code>q[]</code>
-   </td>
-   <td>output
-   </td>
-   <td>Output register content, size == field size. This output typically goes to both the hardware bundle output <code>q</code> as well as the software read response mux output <code>qs[]</code>.
-   </td>
-  </tr>
-  <tr>
-   <td><code>re</code>
-   </td>
-   <td>output
-   </td>
-   <td>Indicates to hwext registers that the bus is reading the register.
-   </td>
-  </tr>
-</table>
-
-
-
-### Type RW
-
-The first register type is the read-write register, invoked with an `hjson` attribute `swaccess` type of `rw`.
-There is a variant of this below, this is the default variant.
-This uses the `prim_subreg` with the connections shown.
-The connectivity to the hardware struct bundles are a function of the `hwaccess` and `hwqe` attributes, and will be discussed here as well.
-
-![subreg_rw](subreg_rw.svg)
-
-
-In this diagram, the maximum connection for subreg_rw is shown.
-Coming in from the left (bus) are the software write enable and write data, which has the highest priority in modifying the register contents.
-These are present for all RW types.
-The "final answer" for the register content is stored in the subreg module, and presented to the peripheral hardware as the output `q` and to bus reads as the output `qs`.
-Optionally, if the `hwaccess` attribute allows writes from the hardware, the hardware can present updated values in the form of data enable (`de`) and update data (`d`).
-If the data enable is true, the register content is updated with the update data.
-If both software and hardware request an update in the same clock cycle (i.e. both `de` and `we` are true), the software updated value is used, as shown in the diagram.
-
-The `hwaccess` attribute value does not change the contents of the subreg, but the connections are potentially modified.
-The attribute `hwaccess` has four potential values, as shown earlier in the document: `hrw, hro, hwo, none`.
-A `hwaccess` value of `hrw` means that the hardware wants the ability to update the register content (i.e. needs connection to `d` and `de`), as well as see the updated output (`q`).
-`hwo` doesn't care about the output `q`, but wants to update the register value.
-This is the default for registers marked for software read-only access.
-`hro` conversely indicates the hardware doesn't need to update the content, but just wants to see the value written by software.
-This is the default for fields where the software access is read-write or write-only.
-Finally an attribute value of `none` asks for no interface to the hardware, and might be used for things like scratch registers or DV test registers where only software can modify the value, or informational registers like version numbers that are read-only by the software.
-
-Another attribute in the register description `hwqe`, when true indicates that the hardware wants to see the software write enable exported to the peripheral logic.
-This is just a registered version of the bus side write-enable `we` so that its rising edge aligns with the change in the `q` output.
-There only needs to be one instantiated `qe` flop per register, but it is provided in the reg2hw structure for each field.
-
-### Type RW HWExt
-
-There is an attribute called `hwext` which indicates, when true, that the register value will be maintained **outside** the auto-generated logic.
-It is up to the external logic to implement the correct functionality as indicated by the `swaccess` attribute.
-In other words, **there is no guarantee** that the custom logic will correctly implement `rw`, or whichever attribute is included in the register definition.
-It is expected that this functionality is only needed for custom features outside the scope of the list of supported swaccess features, such as masked writes or access to FIFOs.
-Note that these could be done non-hwext as well, with `swaccess==rw` and `hwaccess=rw`, but could lose atomicity due to the register included within the autogenerated region.
-The block diagram below shows the maximally functional `hwext` `RW` register, with some assumption of the implementation of the register on the outside.
-This is implemented by the `prim_subreg_ext` module which is implemented with `assign` statements just as the wiring shown suggests.
-In this diagram the `q` is the `q` output to the hardware, while the `qs` is the output to the read response mux on the software side.
-The storage register is shown in the custom portion of the logic.
-Finally, note that no `de` input is required from the rest of the peripheral hardware, only the `d` is added to the struct bundle.
-
-![subreg_ext](subreg_ext.svg)
-
-Note the timing of `qe` is one cycle earlier in this model than in the non-hwext model.
-
-
-### Type RO, with hwext and zero-gate options
-
-Read-only type registers can be thought of as identical as `RW` types with no `wd` and `we` input.
-They are implemented as `prim_subreg` with those inputs disabled.
-Similarly `hwext RO` registers simply pass the d input from the outside world to the data mux for software read response.
-
-There is one special case here [not yet implemented] where `swaccess` is `ro` and `hwaccess` is `none` or `hro` and `hwext` is true.
-In this case, a hardwired value is returned for a software read equal to the default value assigned to the register this can be useful for auto-generated register values with no storage register required.
-
-### Type RC
-
-Registers of software access type `rc` are special cases of `RO`, but require an additional signal from the address decode logic.
-This signal `re` indicates that this register is being read, in which case the contents should be set to zero.
-Note this register is not recommended but might be required for backwards compatibility to other IP functionality.
-At the moment `hwext` is not allowed to be true for `RC` since there is no exporting of the `re` signal.
-If this is required, please add a feature request.
-
-### Type WO
-
-Write only registers are variants of `prim_subreg` where there is no output back to the software read response mux, so the `d` and `de` pins are tied off.
-If there is no storage required, only an indication of the act of writing, then `hwext` should be set to true and the outside hardware can handle the write event.
-
-### Type R0W1C, RW1S, RW1C and RW0C
-
-Certain `RW` register types must be implemented with special configuration of `prim_subreg` since the act of writing causes the values to be set in unique ways.
-These types are shown in the block diagrams below.
-Type `R0W1C` not shown is just a special case of `RW1C` where the q output is not sent back to the software read response mux, but the value `0` is sent instead.
-Note the `qe` is removed for readability but is available with the hwqe attribute.
-
-![subreg_rw1c](subreg_rw1c.svg)
-
-![subreg_rw0c](subreg_rw0c.svg)
-
-![subreg_rw1s](subreg_rw1s.svg)
-
-
-#### Simultaneous SW and HW access
-
-As shown in the module descriptions, the subreg needs to handle the case when both hardware and software attempt to write at the same time.
-As is true with the RW type, the software has precedence, but it is more tricky here.
-The goal for these types of registers is to have software clear or set certain bits at the same time hardware is clearing or setting other bits.
-So in theory what software is clearing, hardware is setting, or vice-versa.
-An example would be where hardware is setting interrupt status bits, and software is clearing them, using RW1C.
-The logic for RW1C shows how this is implemented in the module:
-
-```systemverilog
-q <= (de ? d : q) & (we ? ~wd : '1)
-```
-
-In this description if the hardware is writing, its value is sent to the logic that potentially clears that value or the stored value.
-So if the hardware accidentally clears fields that the software hasn't cleared yet, there is a risk that events will not be seen by software.
-The recommendation is that the hardware feed the `q` value back into `d`, only setting bits with new events.
-Then there will be no "collision" between hardware setting events and software clearing events.
-The HW could have chosen to simply treat `d` and `de` as set-only, but the preference is to leave the `subreg` simple and allow the hardware to do either "the right thing" or whatever it feels is appropriate for its needs.
-(Perhaps it is a feature to clear all events in the hardware.)
-
-The one "conflict" that is common and worth mentioning is `RW1C` on an interrupt vector.
-This is the typical scenario where hardware sets bits (representing an interrupt event), and software clears bits (indicating the event has been handled).
-The assumption is that between the hardware setting and software clearing, **software has cleaned up whatever caused the event in the first place**.
-But if the event is still true (the HW `d` input is still `1`) then the clear should still have effect for one cycle in order to create a new interrupt edge.
-Since `d` is still `1` the `q` will return to `1` after one cycle, since the clean up was not successful.
-
-#### HWExt RW1C etc.
-
-It is legal to create `RW1C`, `RW1S`, etc. with `hwext` true.
-In these cases the auto-generated hardware is simply the same as the hwext `RW` register shown earlier.
-This causes all of the implementation to be done outside of the generated register block.
-There is no way to guarantee that hardware is doing the right thing, but at least the `RW1C` conveys the notion to software the intended effect.
-
-Similarly it is legal to set `hwqe` true for any of these register types if the clearing wants to be monitored by the hardware outside.
-
-## Shadow Registers
-
-If in the `.hjson` file the optional key `shadowed` is set to `true` for a particular register group, the register tool will implement the corresponding registers including all fields as shadow registers using the `prim_subreg_shadow` module.
-The following section describes the concept in detail.
-
-### Overview
-
-For Comportable designs used in sensitive security environments, for example AES or HMAC peripherals, there may be a need to guard against fault injection attacks.
-One of the surfaces under threat are critical configuration settings that may be used to control keys, region access control and system security configuration.
-These registers do not typically contain secret values, but can be vulnerable to fault attacks if a fault can alter the state of a register to a specific state.
-
-The fault attacks targeting these registers can come in a few forms:
-- A fault attack directly on the storage element.
-- A fault attack on peripheral logic (for example resets) that cause the registers to revert to a default state.
-- A fault attack during the process of the write to disrupt the actual written value.
-
-Shadow registers are a mechanism to guard sensitive registers against this specific type of attack.
-They come at a cost of increased area, and a modified SW interaction, see below.
-Usage of shadow registers should be evaluated with this impact in mind.
-
-### Description
-
-The diagram below visualizes the internals of the `prim_subreg_shadow` module used to implement shadow registers.
-
-![subreg_shadow](subreg_shadow.svg)
-
-When compared to a normal register, the `prim_subreg_shadow` module is comprised of two additional storage components, giving three storage flops in total.
-The **staged** register holds a written value before being committed.
-The **shadow** register holds a second copy after being committed.
-And the the standard storage register holds the **committed** value.
-
-Under the hood, these registers are implemented using the `prim_subreg` module.
-This means a shadow register can implement any of the types described in [this section]({{< relref "doc/rm/register_tool/index.md#register-definitions-per-type" >}}).
-
-The general behavior of updating a shadow register is as follows:
-1. Software performs a first write to the register.
-
-   The staged register takes on the new value.
-   The committed register is **not** updated, so the hardware still sees the old value.
-
-1. Software performs a second write to the register (same address):
-
-   - If the data of the second write matches the value in the staged register, **both** the shadow register and the committed register are updated.
-   - If the data of the second write does **not** match the contents stored in the staged register, an update error is signaled to the hardware with a pulse on `err_update`.
-
-   The phase is tracked internally to the module.
-   If needed, software can put the shadow register back into the first phase by performing a read operation.
-   If the register is of type `RO`, software cannot interfere with the update process and read operations do not clear the phase tracker.
-   If the register is of type `RW1S` or `RW0C`, the staged register will latch the raw data value coming from the bus side.
-   The raw data value of the second write is compared with the raw data value in the staged register before being filtered by the `RW1S` or `RW0C` set/clear logic.
-
-1. If the value of the shadow register and the committed register are **ever** mismatched, a storage error is signaled to the hardware using the `err_storage` signal.
-
-For added security, the staged and shadow registers store the ones' complement values of the write data (if the write data is `0x33`, `0xCC` is stored in the staged and shadow register).
-This is done such that it is more difficult for a localized glitch to cause shadow and committed registers to become the same value.
-
-### Programmer's model
-
-The programmer needs to be aware of the functional behavior when dealing with shadow registers.
-To this end, the naming of registers which contain a shadowed copy is suffixed by `_SHADOWED` in the generated collateral.
-This is ensured by the register tool.
-
-Software should ensure that shadow registers are always accessed exclusively.
-If the two write operations required for an update are for some reason interleaved by a third write operation, this can trigger an update error depending on the written data.
-Such situations can be avoided by using some form of mutex at the driver level for example.
-Alternatively, software can enforce exclusive shadow register access by temporarily disabling interrupts with a sequence as follows:
-1. Disable interrupt context.
-1. Perform 1st write to shadow register.
-1. Perform 2nd write to shadow register.
-1. Read back the shadow register to ensure data matches the expected value.
-   This step is not strictly required.
-   It helps to identify update errors and can unveil potential fault attacks on the write data lines during process of the write.
-1. Restore interrupt context.
-
-By performing a read operation, software can at any time put the shadow register back into the first phase, thereby preventing that a subsequent write triggers an update error, and recovering from situations where it is not clear in which phase the register is.
-However, if the register is of type `RO`, meaning if only the hardware can update the register, read operations do not clear the phase tracker.
-
-In addition, error status registers can be added to inform software about update errors.
-
-### Design integration
-
-The following aspects need to be considered when integrating shadow registers into the system.
-
-- Non-permissive reset values:
-
-  A fault attack on the reset line can reset the staged, shadow and committed registers altogether and not generate an error.
-  It may remain undetected by the system.
-  It is thus **highly recommended** to use non-permissive reset values and use software for configuring more permissive values.
-
-- Error hook-ups:
-  - Storage errors signaled to the hardware in `err_storage` are fatal, and should be hooked up directly into the [alert system]({{< relref "hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}).
-  - Update errors signaled to the hardware in `err_update` may be software errors and do not affect the current committed value; thus they can be considered informational bugs that are collected into error status or interrupts, and optionally alerts.
-    They do not generate error responses on the TL-UL system bus interface.
-
-- Hardware and software cost:
-
-  Shadow registers are costly as they imply considerable overheads in terms of circuit area as well as software complexity and performance.
-  They should not be used lightly nor extensively.
-  For example, if a register is reconfigured frequently, using restrictive reset values, possibly combined with parity bits and/or with verification of the register value after writing, might be more suitable.
-  In contrast, for registers that are more static the performance overhead of shadow registers is negligible.
-  Also, for some registers it is not possible to define restrictive reset values, e.g., due to process variation.
-  The use of shadow registers is more justified in such cases.
-  Example use cases for shadow registers are:
-  - Main control registers of cryptographic accelerators such as AES and HMAC where fault attacks could enforce potentially less secure operating modes.
-  - Critical and security-related infrastructure such as:
-    - Alert escalation and sensor configuration registers, and
-    - Countermeasure tuning and functional/bandgap calibration registers.
-
-### DV shadow register alert test automation
-
-In DV, the `shadow_reg_errors` automated test will check if shadow registers' update and storage errors trigger the correct alerts.
-This alert automation test requires the user to add the following items in `.hjson` file under each shadow register:
-- Update_err_alert: Alert triggered by a shadow register's update error
-- Storage_err_alert: Alert triggered by a shadow register's storage error
-
-### Future enhancements
-
-The following features are currently not implemented but might be added in the future.
-
-- Lightweight implementations:
-
-  Depending on the use case and security requirements, lightweight shadow register implementations can be more suitable.
-  Possible options include:
-  - Storing only a hash value or parity bits in the staged and/or shadow register instead of the full data word.
-    This can help to reduce circuit area overheads but may weaken security.
-  - Removing the staged register and relying on software to verify the written value.
-    This helps to reduce circuit area but increases software complexity.
-    If lower security can be tolerated, avoiding the read back after a write can make this implementation transparent to software.
-  - A combination of the two options.
-    For example, removing the staged register and storing parity bits only in the shadow register may be suitable for physical memory protection (PMP) registers.
-
-- Ones' complement inversion in software:
-
-  The ones' complement inversion could be done in software.
-  This means software would first write the ones' complement of the data and only the second write would be the real data.
-  This may make following bus dumps easier because the data values will be different.
-
-- Shadow resets:
-
-  Staged and shadow registers could use a separate shadow reset from the committed register.
-  This would imply that each module which contains shadow registers will receive two reset lines from the reset controller.
-  The benefit is that a localized reset glitch attack will need to hit both reset lines, otherwise a storage error is triggered.
-
-  Reset skew may need to be handled separately.
-  Depending on the design, the reset propagation **may** be multi-cycle, and thus it is possible for the shadow register and committed register to not be reset at the same time when resets are asserted.
-  This may manifest as an issue if the reset skews are **not** balanced, and may cause register outputs to change on the opposite sides of a clock edge for the receiving domain.
-  It is thus important to ensure the skews of the committed and shadow reset lines to be roughly balanced and/or downstream logic to correctly ignore errors when this happens.
-  Note that a module may need to be reset during operation of the system, i.e., when the [alert system]({{< relref "hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}) is running.
-
-  Software-controllable resets should be avoided.
-  If a single reset bit drives both normal and shadow resets, then the problem is simply moved to a different place.
-  If multiple reset bits drive the resets, then it becomes a question of what resets those reset bits, which may itself have the same issue.
-
-## Generating C Header Files
-
-The register tool can be used to generate C header files.
-It is intended that there will be several generators to output different formats of header file.
-
-### Simple hello_world test headers
-
-The register generation tool will generate simple headers if it is invoked with the `-D` flag.
-The `-o <file.h>` flag may be used to specify the output file.
-As an example the tool can be invoked to generate the uart headers with:
-
-```console
-$ cd hw/ip/uart/doc
-$ ../../../../util/regtool.py -D -o ~/src/uart.h uart.hjson
-```
-
-This format assumes that there is a base address `NAME`n`_BASE_ADDR` defined where n is an identifying number to allow for multiple instantiations of peripherals.
-It provides a definition `NAME_REG(n)` that provides the address of the register in instantiation n.
-Single-bit fields have a define with their bit offset.
-Multi-bit fields have a define for the bit offset and an mask and may have defines giving the enumerated names and values.
-For example:
-
-```c
-// UART control register
-#define UART_CTRL(id)                    (UART ## id ## _BASE_ADDR  + 0x0)
-# define UART_CTRL_TX                    0
-# define UART_CTRL_RX                    1
-# define UART_CTRL_NF                    2
-# define UART_CTRL_SLPBK                 4
-# define UART_CTRL_LLPBK                 5
-# define UART_CTRL_PARITY_EN             6
-# define UART_CTRL_PARITY_ODD            7
-# define UART_CTRL_RXBLVL_MASK           0x3
-# define UART_CTRL_RXBLVL_OFFSET         8
-# define UART_CTRL_RXBLVL_BREAK2         0
-# define UART_CTRL_RXBLVL_BREAK4         1
-# define UART_CTRL_RXBLVL_BREAK8         2
-# define UART_CTRL_RXBLVL_BREAK16        3
-```
-
-## Generating documentation
-
-The register tool can be used standalone to generate HTML documentation of the registers.
-However, this is normally done as part of the Markdown documentation using the special tags to include the register definition file and insert the configuration and register information.
diff --git a/doc/rm/topgen_tool.md b/doc/rm/topgen_tool.md
deleted file mode 100644
index 19754f6..0000000
--- a/doc/rm/topgen_tool.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: "Top Generation Tool"
----
-
-The top generation tool `topgen.py` is used to build top specific modules for a specific OpenTitan design - for example [`top_earlgrey`](https://github.com/lowRISC/opentitan/tree/master/hw/top_earlgrey).
-Currently, as part of this generation process, the following top specific modules are created
-* Overall top module
-* Platform level interrupt controller
-* Pinmux
-* Crossbar
-
-This document explains the overall generation process, the required inputs, the output locations, as well as how the tool should be invoked.
-
-## Generation Process
-
-### Overview
-The details of a particular OpenTitan variant is described in a top specific Hjson file.
-For example see [`top_earlgrey`](https://github.com/lowRISC/opentitan/tree/master/hw/top_earlgrey/data/top_earlgrey.hjson).
-
-The top specific Hjson describes how the design looks and how it should connect, for example:
-* Overall fabric data width
-* Clock sources
-* Reset sources
-* List of instantiated peripherals
-  * Module type of each peripheral (it is possible to have multiple instantitations of a particular module)
-  * Clock / reset connectivity of each peripheral
-  * Base address of each peripheral
-* System memories
-* Fabric construction
-  * Clock / reset connectivity of each fabric component
-* Interrupt sources
-* Pinmux construction
-  * List of dedicated or muxed pins
-
-The top level Hjson however, does not contain details such as:
-* Specific clock / reset port names for each peripheral
-* Number of interrupts in each peripheral
-* Number of input or output pins in each peripheral
-* Details of crossbar connection and which host can reach which device
-
-The topgen tool thus hierarchically gathers and generates the missing information from additional Hjson files that describe the detail of each component.
-These are primarily located in two places:
-* `hw/ip/data/*.hjson`
-* `hw/top_*/data/xbar_*.hjson`
-
-In the process of gathering, each individual Hjson file is validated for input correctness and then merged into a final generated Hjson output that represents the complete information that makes up each OpenTitan design.
-For example, see [`top_earlgrey`](https://github.com/lowRISC/opentitan/tree/master/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson).
-Note specifically the generated interrupt list, the pinmux connections, and the port-to-net mapping of clocks and resets, all of which were not present in the original input.
-
-The purpose for this two step process, instead of describing the design completely inside one Hjson file, is to decouple the top and components development while allowing re-use of components by multiple tops.
-
-This process also clearly separates what information needs to be known by top vs what needs to be known by a specific component.
-For example, a component does not need to know how many clock sources a top has or how many muxed pins it contains.
-Likewise, the top does not need to know the details of why an interrupt is generated, just how many there are.
-The user supplied `top_*.hjson` thus acts like a integration specification while the remaining details are filled in through lower level inputs.
-
-In addition to design collateral, the tool also generates all the top level RAL (Register Abstraction Layer) models necessary for verification.
-
-### Validation, Merge and Output
-
-As stated previously, each of the gathered component Hjson files is validated for correctness.
-For the peripherals, this is done by invoking `util/reggen/validate.py`, while the  xbar components are validated through `util/tlgen/validate.py`.
-The peripheral and xbar components are then validated through `topgen/validate.py`, which checks for interrupt, pinmux, clock and reset consistency.
-
-Once all validation is passed, the final Hjson is created.
-This Hjson is then used to generate the final top RTL.
-
-As part of this process, topgen invokes other tools, please see the documentation for [`reggen`](register_tool.md) and [`tlgen`](crossbar_tool.md) for more tool specific details.
-
-## Usage
-
-The most generic use of topgen is to let it generate everything.
-This can be done through direct invocation, or the `${REPO_TOP}/hw` makefile.
-The example below shows the latter:
-```console
-$ cd ${REPO_TOP}
-$ make -C hw top
-
-```
-
-It is possible to restrict what the tool should generate.
-
-```console
-$ cd ${REPO_TOP}
-$ ./util/topgen.py -h
-usage: topgen [-h] --topcfg TOPCFG [--outdir OUTDIR] [--verbose] [--no-top]
-              [--no-xbar] [--no-plic] [--no-gen-hjson] [--top-only] [--xbar-only]
-              [--plic-only] [--hjson-only] [--top_ral]
-
-optional arguments:
-  -h, --help            show this help message and exit
-  --topcfg TOPCFG, -t TOPCFG
-                        `top_{name}.hjson` file.
-  --outdir OUTDIR, -o OUTDIR
-                        Target TOP directory. Module is created under rtl/. (default:
-                        dir(topcfg)/..)
-  --verbose, -v         Verbose
-  --no-top              If defined, topgen doesn't generate top_{name} RTLs.
-  --no-xbar             If defined, topgen doesn't generate crossbar RTLs.
-  --no-plic             If defined, topgen doesn't generate the interrup controller RTLs.
-  --no-gen-hjson        If defined, the tool assumes topcfg as a generated hjson. So it
-                        bypasses the validation step and doesn't read ip and xbar
-                        configurations
-  --top-only            If defined, the tool generates top RTL only
-  --xbar-only           If defined, the tool generates crossbar RTLs only
-  --plic-only           If defined, the tool generates RV_PLIC RTL and Hjson only
-  --hjson-only          If defined, the tool generates complete Hjson only
-  --top_ral, -r         If set, the tool generates top level RAL model for DV
-
-```
diff --git a/doc/rm/vendor_in_tool.md b/doc/rm/vendor_in_tool.md
deleted file mode 100644
index a17d2b5..0000000
--- a/doc/rm/vendor_in_tool.md
+++ /dev/null
@@ -1,148 +0,0 @@
----
-title: "util/vendor.py: Vendor-in Components"
----
-
-Not all code contained in this repository is actually developed within this repository.
-Code which we include from external sources is placed in `vendor` sub-directories (e.g. `hw/vendor`) and copied over from upstream sources.
-The process of copying the upstream sources is called vendoring, and it is automated by the `util/vendor` tool.
-
-The `util/vendor` tool can go beyond simply copying in source files: it can patch them, it can export patches from commits in a Git repository, and it can commit the resulting changes with a meaningful commit message.
-
-## Tool usage overview
-
-```text
-usage: vendor [-h] [--refresh-patches] [--commit] [--verbose] file
-
-vendor, copy source code from upstream into this repository
-
-positional arguments:
-  file               vendoring description file (*.vendor.hjson)
-
-optional arguments:
-  -h, --help         show this help message and exit
-  --update, -U       Update locked version of repository with upstream changes
-  --refresh-patches  Refresh the patches from the patch repository
-  --commit, -c       Commit the changes
-  --verbose, -v      Verbose
-```
-
-## The vendor description file
-
-For each vendored-in component a description file must be created, which serves as input to the `util/vendor` tool.
-The vendor description file is stored in `vendor/<vendor>_<name>.vendor.hjson`.
-By convention all imported code is named `<vendor>_<name>`, with `<vendor>` typically being the GitHub user or organization name, and `<name>` the project name.
-It is recommended to use only lower-case characters.
-
-A full commented example of a vendor description file is given below.
-All relative paths are relative to the description file.
-Optional parts can be removed if they are not used.
-
-```
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  // Name of the vendored-in project
-  name: "pulp_riscv_dbg",
-
-  // Target directory: typically equal to the name
-  // All imported code is copied into this directory
-  target_dir: "pulp_riscv_dbg",
-
-  // Git upstream source code repository
-  upstream: {
-    // Upstream Git repository URL. HTTPS URLs are preferred.
-    url: "https://github.com/pulp-platform/riscv-dbg",
-    // Upstream revision or branch. Can be a commit hash or a branch name.
-    rev: "pulpissimo_integration",
-  },
-
-  // Optional: Pick specific files or subdirectories from upstream and
-  // specify where to put them.
-  mapping: [
-    {from: 'src', to: 'the-source'},
-    {from: 'doc', to: 'some/documentation', patch_dir: 'doc_patches'}
-  ]
-
-  // Optional: Apply patches from the following directory to the upstream
-  // sources
-  patch_dir: "patches/pulp_riscv_dbg",
-
-  // Optional: Update patches in |patch_dir| from a Git repository
-  // If util/vendor is run with --refresh-patches, all commits in the repository
-  // at |url| between |rev_base| and |rev_patched| are exported into the
-  // |patch_dir|, replacing all existing patches.
-  patch_repo: {
-    url: "git@github.com:lowRISC/riscv-dbg.git",
-    rev_base: "pulpissimo_integration",
-    rev_patched: "ot",
-  },
-
-  // Optional: Exclude files or directories from the upstream sources
-  // The standard glob wildcards (*, ?, etc.) are supported.
-  exclude_from_upstream: [
-    "src/dm_top.sv",
-    "src_files.yml",
-  ]
-}
-```
-
-If only the contents of a single subdirectory (including its children) of an upstream repository are to be copied in, the optional `only_subdir` key of can be used in the `upstream` section to specify the subdirectory to be copied.
-The contents of that subdirectory will populate the `target_dir` directly (without any intervening directory levels).
-
-For a more complicated set of copying rules ("get directories `A/B` and `A/C` but not anything else in `A`"), use a `mapping` list.
-Each element of the list should be a dictionary with keys `from` and `to`.
-The value of `from` should be a path relative to the source directory (either the top of the cloned directory, or the `only_subdir` subdirectory, if set).
-The value of `to` should be a path relative to `target_dir`.
-
-If `patch_dir` is supplied, it names a directory containing patches to be applied to the vendored code.
-If there is no `mapping` list, this directory's patches are applied in lexicographical order relative to `target_dir`.
-If there is a mapping list, each element of the list may contain a `patch_dir` key.
-The value at that key is a directory, relative to the global `patch_dir` and patches in that directory are applied in lexicographical order relative to the target directory of the mapping, `to`.
-
-In the example vendor description file below, the mpsse directory is populated from the chromiumos platform2 repository, extracting just the few files in the trunks/ftdi subdirectory.
-
-```
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-{
-  name: "mpsse",
-  target_dir: "mpsse",
-
-  upstream: {
-    url: "https://chromium.googlesource.com/chromiumos/platform2/",
-    rev: "master",
-    only_subdir: "trunks/ftdi",
-  },
-}
-```
-
-## Updating and The Vendor Lock File
-
-In order to document which version of a repositoy has been cloned and committed to the repository with the vendor tool, a vendor lock file is stored in `vendor/<vendor>_<name>.lock.hjson`.
-This contains only the upstream information, including the URL and the exact git revision that was cloned.
-
-Beyond just documentation, this enables users to re-clone the previously-cloned upstream repository -- including re-applying patches, choosing subdirectories, and excluding additional files -- without having to integrate any upstream changes.
-Indeed the default behaviour of the vendor tool is to use the upstream information from `<vendor>_<name>.lock.hjson` if this file exists.
-
-Once the lock file exists, the vendor tool will only use the upstream information in `<vendor>_<name>.vendor.json` if the `--update` command-line option is used.
-
-## Examples
-
-### Re-clone code and apply new file exclusions or patches
-
-```command
-$ cd $REPO_TOP
-$ ./util/vendor.py hw/vendor/google_riscv-dv.vendor.hjson -v
-```
-
-### Update code and commit the new code
-
-This will generate a commit message based off the git shortlog between the
-previously cloned revision and the newly cloned revision of the repository.
-
-```command
-$ cd $REPO_TOP
-$ ./util/vendor.py hw/vendor/google_riscv-dv.vendor.hjson -v --update --commit
-```
diff --git a/doc/rust_for_c_devs.md b/doc/rust_for_c_devs.md
new file mode 100644
index 0000000..6450f18
--- /dev/null
+++ b/doc/rust_for_c_devs.md
@@ -0,0 +1,2344 @@
+# Rust for Embedded C Programmers
+
+## Foreword
+
+This document is intended as an introduction to Rust, targeted at engineers with deep exposure to embedded systems C, and little to no experience with C++ and no knowledge of Rust.
+This document will:
+
+*   Provide Rust analogues of everything in the embedded C toolkit.
+*   Discuss how some of those analogues might be importantly different from C's.
+*   Point out where Rust's memory and execution models materially differ from C.
+*   Introduce Rust-specific features that are either key to using Rust at all or otherwise extremely useful (references, lifetimes, generics, traits).
+
+While this document takes a casual approach to Rust, language-lawyer-ey notes are included in footnotes.
+These are not required to get the most out of the document.
+
+Playing with the compiler, and seeing what compiles, is a great way to learn Rust.
+`rustc`, the standard Rust compiler, has excellent error messages.
+Matt Godbolt's [Compiler Explorer](https://rust.godbolt.org/) is useful for getting a feel for the assembly that Rust produces.
+The [Rust Playground](https://play.rust-lang.org/) can also be used to see what Rust code does when _executed_, though it is even more limited.
+
+This document is written with a view towards embedded development.
+We will not be discussing any non-embedded Rust features: see [https://rust-embedded.github.io/book/intro/no-std.html](https://rust-embedded.github.io/book/intro/no-std.html)[^1].
+
+C++ users are cautioned: Rust shares a lot of terminology and concepts (ownership, lifetimes, destructors, polymorphism) with it, but Rust's realization of them tends to have significantly different semantics.
+Experience in C++ should not be expected to translate cleanly.
+
+
+## What is Rust? {#what-is-rust}
+
+Rust is a general-purpose programming language with a focus on maximum programmer control and zero runtime overhead, while eliminating the sharp edges traditionally associated with such languages.
+It is also sometimes called a "systems language".
+
+Syntactically and philosophically, Rust most resembles C++ and ML (a family of functional programming languages), though semantically it is very different from both.
+Rust is the first popular, well-supported language that provides absolute memory safety[^2] without the use of automatic reference counting or a garbage collector.
+This is primarily achieved through a technology called the _borrow checker_, which uses source code annotations to statically prove that memory is not accessed after it is no longer valid, with no runtime checking.
+
+Rust compiles to native code and rivals C and C++ for memory and compute performance, and can seamlessly integrate with anything using a C calling convention.
+It also statically eliminates a large class of memory bugs associated with security vulnerabilities.
+The Rust toolchain is built on LLVM, so all work towards LLVM performance is surfaced in Rust.
+
+Rust also contains a special dialect called Unsafe Rust, which disables some static checking for the rare times when it is necessary to perform low-level operations.
+We will make reference to this dialect throughout the document.
+
+
+## The Rust Toolchain {#the-rust-toolchain}
+
+A complete Rust toolchain consists of a few major parts:
+
+*   `rustc`, the Rust compiler[^3].
+*   `rustup`, the Rust toolchain installer[^4].
+*   `cargo`, a build system for Rust (though rustc can be invoked directly or by other tools).
+*   `std` and `core`, the standard libraries.
+
+The Rust toolchain is on a six-week release cycle, similar to Chrome's: every six weeks, a release branch is cut as the next beta, and after six weeks it becomes the next stable.
+Nightly Rust is cut from `master` every day; it is on nightly that unstable features can be enabled.
+Some unstable features[^5] are very useful for embedded, so it is not uncommon for embedded Rust projects to use a nightly compiler.
+
+`rustup` is used for managing Rust installations.
+This is mostly necessary because Rust releases too frequently for operating system package managers to keep up, and projects can pin specific versions of Rust.
+When the Rust toolchain is installed through rustup, components such as rustc and cargo will be aware of it; `rustc +nightly-2020-03-22` will defer to rustup to download and execute the `rustc` nightly built on March 22.
+A file named `rust-toolchain` in a project directory can be used to the same effect.[^6]
+
+Cargo is a build system/package manager for Rust.
+It can build projects (that is, directories with a `Cargo.toml` file in them) and their dependencies automatically.
+Individual units of compilation in Rust are called "crates", which are either static libraries[^7] (i.e., comparable to a `.a` file) or fully linked native binaries.
+This is in contrast to C, where each `.c` file generates a separate object file.[^8] Rust also does not have headers, though it provides a module system for intra-crate code organization that will be discussed later on.
+Tock boards are a good example of what a more-complex cargo file looks like: [https://github.com/tock/tock/blob/master/boards/opentitan/Cargo.toml](https://github.com/tock/tock/blob/master/boards/opentitan/Cargo.toml)
+
+Some useful `cargo` subcommands include:
+*   `cargo check` runs rustc's checks, but stops before it starts emitting code and optimizing it.
+    This is useful for checking for errors during development.
+*   `cargo build` builds a library or binary, depending on the crate type.
+*   `cargo clippy` runs the Rust linter, Clippy.
+*   `cargo doc --open` builds crate documentation and then opens it in the browser.
+*   `cargo fmt` runs the Rust formatter[^9].
+
+Also, the contents of the `RUSTFLAGS` environment variable are passed to `rustc`, as a mechanism for injecting flags.
+
+The Rust standard library, like libc, is smaller in embedded environments.
+The standard library consists of three crates: `core`[^10], `alloc`, and `std`.
+`core`, sometimes called libcore, is all of the fundamental definitions, which don't rely on operating system support.
+Nothing in `core` can heap-allocate.
+`alloc` does not require OS support, but does require `malloc` and `free` symbols.
+`std` is `core` + `alloc`, as well as OS APIs like file and thread support.
+The `#[no_std]` pragma disables `std` and `alloc`, leaving behind `core`.
+Throughout this document, we will only use `core` types, though we may refer to them through the `std` namespace (they're aliases).
+That is, we may refer to `std::mem::drop`, though in `#[no_std]` code it must be named `core::mem::drop`.
+
+rustc has a number of flags. The most salient of these are:
+
+
+
+*   `--emit asm` and `--emit llvm-ir`, useful for inspecting compiler output.
+*   `--target`, which sets the cross-compilation target.
+     It has a similar role to Clang's `-target`, `-march`, and `-mabi` flags.
+     It accepts a target definition (which in many cases resembles an LLVM target triple) that defines the platform.
+     For example, OpenTitan software uses the `riscv32imc-unknown-none-elf` target.
+     Using a target that isn't the host target (e.g.,`x86_64-unknown-linux-musl`) requires installing the corresponding standard library build with `rustup component install rust-std-<target>`.
+     See `rustc --print targets`.
+     `--target` is also accepted directly by Cargo, unlike most rustc flags.
+*   `-C link-arg`, equivalent to Clang's `-T`.
+*   `-C opt-level`, equivalent to Clang's `-O` (we mostly use `-C opt-level=z` for embedded).
+*   `-C lto`, equivalent to Clang's `-flto`.
+*   `-C force-frame-pointers`, equivalent to Clang's `-fno-omit-frame-pointer`.
+*   `-D warnings` is roughly equivalent to `-Werror`.
+
+Other interesting flags can be found under `rustc -C help` and, on nightly, under `rustc -Z help`.
+
+
+## Part I: Rewriting your C in Rust {#part-i-rewriting-your-c-in-rust}
+
+Before diving into Rust's specific features, we will begin by exploring how C concepts map onto Rust, as well as Unsafe Rust, a dialect of Rust that is free of many of Rust's restrictions, but also most of its safety guarantees.
+
+
+### Types {#types}
+
+Rust and C have roughly the same approach to types, though Rust has few implicit conversions (for example, it lacks integer promotion like C).
+In this section, we will discuss how to translate C types into Rust types.
+
+
+#### Integers {#integers}
+
+Rust lacks C's `int`, `long`, `unsigned`, and other types with an implementation-defined size.
+Instead, Rust's primitive integer types are exact-size types: `i8`, `i16`, `i32`, `i64`, and `i128` are signed integers of 8, 16, 32, 64, and 128 bits, respectively, while `u8`, `u16`, `u32`, `u64`, and `u128` are their unsigned variants.
+Rust also provides `isize` and `usize`, which correspond to `intptr_t` and `uintptr_t`[^11].
+Alignment requirements are exactly the same as in C.
+
+Rust supports all of the usual binary operations on all of its integer types[^12][^13], though you can't mix different types when doing arithmetic, and, unlike in C, there is no integer promotion.
+Overflow in Rust is different from C[^14]: it is implementation-defined, and must either crash the program[^15] or wrap around[^16].
+Casting is done with the `as` keyword, and behaves exactly the same way as in C: `(uint8_t) x` is written `x as u8`. Integer types never implicitly convert between each other, even between signed and unsigned variants.
+
+Rust has the usual integer literals: `123` for decimal, `0xdead` for hex, `0b1010` for binary, and `0o777` for octal.
+Underscores may be arbitrarily interspersed throughout an integer literal to separate groups of digits: `0xdead_beef`, `1_000_000`.
+They may also be suffixed with the name of a primitive integer type to force their type: `0u8`, `0o777i16`, `12_34_usize`; they will otherwise default to whatever type inference (more on this later) chooses, or `i32` if unconstrained.
+
+Rust also has a dedicated `bool` type.
+It is not implicitly convertible with integers, and is otherwise a `u8` that is guaranteed to have either the value `0x00` or `0x01`[^17], with respective literals `false` and `true`.
+`bool` supports all of the bitwise operations, and is the only type compatible with short-circuiting `&&` and `||`.
+It is also the only type that can be used in `if` and `while` conditions.
+
+Integers have an extensive set of built-in operations for bit-twiddling, exposed as methods, such as `x.count_zeros()` and `x.next_power_of_two()`[^18].
+See [https://doc.rust-lang.org/std/primitive.u32.html](https://doc.rust-lang.org/std/primitive.u32.html) for examples.
+
+#### Structs and Tuples {#structs-and-tuples}
+
+Structs are declared almost like in C:
+```rust
+struct MyStruct {
+  pub foo: i32,
+  pub bar: u8,
+}
+```
+Rust has per-field visibility modifiers (`pub`); we'll give visibility a more thorough treatment later.
+
+Struct values can be created using an analogue of C's designated initialization[^19] syntax:
+```rust
+MyStruct { foo: -42, bar: 0xf, }
+```
+
+Rust structs are not laid out like C structs, though; in fact, Rust does not specify the layout of its structs[^20].
+A C struct can be specified in Rust using the `#[repr(C)]` attribute:
+```rust
+#[repr(C)]
+struct MyCStruct {
+  a: u8,
+  b: u32,
+  c: u8,
+}
+```
+This is guaranteed to lay out fields in declaration order, adding padding for alignment.
+`#[repr(Rust)]` is the implicit default.
+`#[repr(packed)]` is analogous to `__attribute__((packed))`, and will not produce any padding[^21].
+The alignment of the whole struct can be forced to a larger value using `#[repr(align(N))]`, similar to `_Alignas`.
+
+Fields can be accessed using the same dot syntax as C: `my_struct.foo`, `my_struct.bar = 5;`.
+
+Rust also provides "tuple-like structs", which are structs with numbered, rather than named, fields:
+```rust
+struct MyTuple(pub u32, pub u8);
+```
+
+Fields are accessed with a similar dot syntax: `tuple.0`, `tuple.1`, and are constructed with function-call-like syntax: `MyTuple(1, 2)`.
+Other than syntax, they are indistinguishable from normal structs. The fields on a tuple-like struct may be omitted to declare a zero-byte[^22] struct:
+```rust
+struct MyEmpty;
+```
+
+Anonymous versions of tuples are also available: `(u32, u8)`.
+These are essentially anonymous structs with unnamed fields.
+The empty tuple type, `()`, is called "unit", and serves as Rust's `void` type (unlike `void`, `()` has exactly one value, also named `()`, which is zero-sized).
+Rust has another void-like type, `!`, which we'll discuss when we talk about functions.
+
+If every field of a struct can be compared with `==`, the compiler can generate an equality function for your struct:
+```rust
+#[derive(PartialEq, Eq)]
+struct MyStruct {
+  a: u32,
+  b: u8,
+}
+```
+
+This makes it possible to use `==` on `MyStruct` values, which will just perform field-wise equality.
+The same can be done for ordering operations like `<` and `>=`: `#[derive(PartialOrd, Ord)]` will define comparison functions that compare structs lexicographically.
+
+#### Enums and Unions
+
+Much like C, Rust has enumerations for describing a type with a fixed set of values:
+```rust
+enum MyEnum {
+    Banana, Apple, Pineapple,
+}
+```
+
+Unlike C, though, `MyEnum` is a real type, and not just an alias for an integer type.
+Also unlike C, the enum's variants are not dumped into the global namespace, and must instead be accessed through the enum type: `MyEnum::Banana`.
+Note that, unlike structs, the variants of an enum are automatically public.
+
+While Rust does represent enum values with integers (these integers are called _discriminants_), the way they're laid out is unspecified.
+To get an enum whose discriminants are allocated like in C, we can employ a `repr` attribute:
+```rust
+#[repr(C)]
+enum MyCEnum {
+  Banana = 0,
+  Apple = 5,
+  Pineapple = 7,
+}
+```
+Unlike C, though, Rust will only guarantee discriminant values that are explicitly written down.
+Such enums may be safely cast into integer types (like `MyCEnum::Apple as u32`), but not back: the compiler always assumes that the underlying value of a `MyCEnum` is `0`, `5`, or `7`, and violating this constraint is Undefined Behavior (UB)[^23].
+If we want to require that an enum is an exact integer width, we can use `#[repr(T)]`, where `T` is an integral type like `u16` or `i8`.
+
+Unions in Rust are a fairly recent feature, and are generally not used for much in normal code.
+They are declared much like structs:
+```rust
+union MyUnion {
+  pub foo: i32,
+  pub bar: u8,
+}
+```
+and created much like structs:
+```rust
+MyUnion { bar: 0xa, }  // `bar` is the active variant.
+```
+
+Assigning to union variants is the same as in structs, but reading a variant requires Unsafe Rust, since the compiler can't prove that you're not reading uninitialized or invalid data[^24], so you'd need to write
+```rust
+unsafe { my_union.bar }  // I assert that bar is the active variant.
+```
+Unions also have restrictions on what types can be used as variants, due to concerns about destructors.
+
+Since unions are so useful in C, but utterly unsafe, Rust provides built-in _tagged unions_[^25], which are accessed through the `enum` syntax:
+```rust
+enum MyEnum {
+  FooVariant { foo: i32 },
+  BarVariant(u8),
+}
+```
+
+Tagged-union enum variants use the same syntax as Rust structs; enum consists of a tag value (the discriminant) big enough to distinguish between all the variants, and a compiler-tracked union of variants.
+However, effective use of such enums requires _pattern matching_, which requires its own section to explain.
+We'll see these enums again when we discuss patterns.
+
+Just like with structs, `#[derive]` can be used on enums to define comparison operators, which are defined analogously to the struct case.
+
+#### Arrays {#arrays}
+
+Rust arrays are just C arrays: inline storage of a compile-time-known number of values.
+`T[N]` in C is spelled `[T; N]` in Rust.
+Arrays are created with `[a, b, c]` syntax, and an array with lots of copies of the same value can be created using `[0x55u8; 1024]`[^26].
+A multi-dimensional array can be declared as an array of arrays: `[[T; N]; M]`.
+
+Array elements can be accessed with `x[index]`, exactly like in C.
+Note, however, that Rust automatically inserts bounds checks around every array access[^27]; failing the bounds check will trigger a panic in the program.
+Unsafe Rust can be used to cheat the bounds check when it is known (to the programmer, not rustc!) to be unnecessary to perform it, but when it is performance critical to elide it.
+
+Rust arrays are "real" types, unlike in C. They can be passed by value into functions, and returned by value from functions.
+They also don't decay into pointers when passed into functions.
+
+#### Pointers {#pointers}
+
+Like every other embedded language, Rust has pointers.
+These are usually referred to as _raw pointers_, to distinguish them from the myriad of smart pointer types.
+Rust spells `T*` and `const T*` as `*mut T` and `*const T`.
+Unlike in C, pointers do not need to be aligned to their pointee type[^28] until they are dereferenced (like C, Rust assumes that all pointer reads/writes are well-aligned).
+
+Note that C's type-based strict aliasing does not exist in Rust[^29].
+As we'll learn later, Rust has different aliasing rules for references that are both more powerful and which the compiler can check automatically.
+
+`usize`, `isize`, and all pointer types may be freely cast back and forth.[^30] Null pointers may be created using the `std::ptr::null()` and `std::ptr::null_mut()` functions[^31].
+Rust pointers do not support arithmetic operators; instead, a method fills this role: instead of `ptr + 4`, write `ptr.offset(4)`.
+Equality among pointers is simply address equality.
+
+Pointers can be dereferenced with the `*ptr` syntax[^32], though this is Unsafe Rust and requires uttering `unsafe`.
+When pointers are dereferenced, they must be well-aligned and point to valid memory, like in C; failure to do so is UB.
+Unlike in C, the address-of operator, `&x`, produces a reference[^33], rather than a pointer.
+`&x as *const T` will create a pointer, instead.
+
+Pointer dereference is still subject to move semantics, like in normal Rust[^34].
+the `read()` and `write()` methods on pointers can be used to ignore these rules[^35].
+`read_unaligned()`[^36] and `write_unaligned()`[^37] can be used to perform safe[^38] unaligned access, and `copy_to()`[^39] and `copy_nonoverlapping_to()`[^40] are analogous to `memmove()` and `memcpy()`, respectively.
+See [https://doc.rust-lang.org/std/primitive.pointer.html](https://doc.rust-lang.org/std/primitive.pointer.html) for other useful pointer methods.
+Volatile operations are also performed using pointer methods, which are discussed separately later on.
+
+Since all of these operations dereference a pointer, they naturally are restricted to Unsafe Rust.
+
+As we will discover later, Rust has many other pointer types beyond raw pointers.
+In general, raw pointers are only used in Rust to point to potentially uninitialized memory[^41] and generally denote _addresses_, rather than references to actual memory.
+For that, we use _references_, which are discussed much later.
+
+We will touch on function pointers when we encounter functions.
+
+
+### Items {#items}
+
+Like C, Rust has globals and functions.
+These, along with the type definitions above, are called _items_ in the grammar, to avoid confusion with C's declaration/definition distinction.
+Unlike C, Rust does not have forward declaration or declaration-order semantics; everything is visible to the entire file.
+Items are imported through dedicated import statements, rather than textual inclusion; more on this later.
+
+
+#### Constants and Globals {#constants-and-globals}
+
+Rust has dedicated syntax for compile-time constants, which serve the same purpose as `#define`d constants do in C.
+Their syntax is
+```rust
+const MY_CONSTANT: u32 = 0x42;
+```
+The type is required here, and the right-hand side must be a _constant expression_, which is roughly any combination of literals, numeric operators, and `const fn`s (more on those in a moment).
+
+Constants do not exist at runtime.
+They can be thought of best as fixed expressions that get copy+pasted into wherever they get used, similar to `#define`s and `enum` declarators in C.
+It is possible to take the address of a constant, but it is not guaranteed to be consistent.[^42]
+
+Globals look like constants, but with the keyword `static`[^43]:
+```rust
+static MY_GLOBAL: u8 = 0x00;
+static mut MY_MUTABLE_GLOBAL: Foo = Foo::new();
+```
+Globals are guaranteed to live in `.rodata`, `.data`, or `.bss`, depending on their mutability and initialization.
+Unlike constants, they have unique addresses, but as with constants, they must be initialized with constant expressions.[^44]
+
+Mutable globals are particularly dangerous, since they can be a source of data races in multicore systems.
+Mutable globals can also be a source other racy behavior due to IRQ control flow.
+As such, reading or writing to a mutable global, or creating a reference to one, requires Unsafe Rust.
+
+
+#### Functions {#functions}
+
+In C and Rust, functions are the most important syntactic construct. Rust declares functions like so:
+```rust
+fn my_function(x: u32, y: *mut u32) -> bool {
+  // Function body.
+}
+```
+The return type, which follows the `->` token, can be omitted when it is `()` ("unit", the empty tuple), which serves as Rust's equivalent of the `void` type.
+Functions are called with the usual `foo(a, b, c)` syntax.
+
+The body of a function consists of a list of statements, potentially ending in an expression; that expression is the function's return value (no `return` keyword needed).
+If the expression is missing, then `()` is assumed as the return type. Items can be mixed in with the statements, which are local to their current scope but visible in all of it.
+
+Rust functions can be marked as `unsafe fn`.
+This means the function cannot be called normally, and must instead be called using Unsafe Rust.
+The body of an `unsafe fn` behaves like an `unsafe` block; we'll go more into this when we discuss Unsafe Rust in detail.
+
+Rust functions have an unspecified calling convention[^45].
+To declare a function with a different calling convention, the function is declared `extern "ABI" fn foo()`, where ABI is a supported ABI.
+`"C"` is the only one we really care about, which switches the calling convention to the system's C ABI. The default, implicit calling convention is `extern "Rust"`[^46].
+
+Marking functions as `extern` does not disable name mangling[^47]; this must be done by adding the `#[no_mangle]` attribute to the function.
+Unmangled functions can then be called by C, allowing a Rust library to have a C interface.
+A mangled, `extern "C"` function's main use is to be turned into a function pointer to pass to C.
+
+Function pointer types look like functions with all the variable names taken out: `fn(u32, *mut u32) -> bool`.
+Function pointers cannot be null, and must always point to a valid function with the correct ABI.
+Function pointers can be created by implicitly converting a function into one (no `&` operator).
+Function pointers also include the `unsafe`-ness and `extern`-ness of a function: `unsafe extern "C" fn() -> u32`.
+
+Functions that never return have a special return type `!`, called the "never type".
+This is analogous to the `noreturn` annotation in C.
+However, using an expression of type `!` is necessarily dead code, and, as such, `!` will implicitly coerce to all types (this simplifies type-checking, and is perfectly fine since this all occurs in provably-dead code).
+
+Functions can also be marked as `const`.
+This makes the function available for constant evaluation, but greatly restricts the operations available.
+The syntax available in `const` functions increases in every release, though.
+Most standard library functions that can be `const` are already `const`.
+
+
+#### Macros {#macros}
+
+Rust, like C, has macros.
+Rust macros are much more powerful than C macros, and operate on Rust syntax trees, rather than by string replacement.
+Macro calls are differentiated from function calls with a `!` following the macro name.
+For example, `file!()` expands to a string literal with the file name.
+To learn more about macros, see [https://danielkeep.github.io/tlborm/book/index.html](https://danielkeep.github.io/tlborm/book/index.html).
+
+
+#### Aliases {#aliases}
+
+Rust has `type`, which works exactly like `typedef` in `C`.
+Its syntax is
+```rust
+type MyAlias = u32;
+```
+
+### Expressions and Statements {#expressions-and-statements}
+
+Very much unlike C, Rust has virtually no statements in its grammar: almost everything is an expression of some kind, and can be used in expression context.
+Roughly speaking, the only statement in the language is creating a binding:
+```rust
+let x: u32 = foo();
+```
+The type after the `:` is optional, and if missing, the compiler will use all information in the current scope, both before and after the `let`, to infer one[^48].
+
+An expression followed by a semicolon simply evaluates the expression for side-effects, like in other languages[^49].
+Some expressions, like `if`s, `whiles`, and `for`s, don't need to be followed by a semicolon.
+If they are not used in an expression, they will be executed for side-effects.
+
+`let` bindings are immutable by default, but `let mut x = /* ... */;` will make it mutable.
+
+Like in C, reassignment is an expression, but unlike in C, it evaluates to `()` rather than to the assigned value.
+
+Like in almost all other languages, literals, operators, function calls, variable references, and so on are all standard expressions, which we've already seen how Rust spells already.
+Let's dive into some of Rust's other expressions.
+
+
+#### Blocks {#blocks}
+
+Blocks in Rust are like better versions of C's blocks; in Rust, every block is an expression.
+A block is delimited by `{ }`, consists of a set of a list of statements and items, and potentially an ending expression, much like a function.
+The block will then evaluate to the final expression. For example:
+```rust
+let foo = {
+  let bar = 5;
+  bar ^ 2
+};
+```
+Blocks are like local functions that execute immediately, and can be useful for constraining the scope of variables.
+
+If a block does not end in an expression (that is, every statement within ends in a semicolon), it will implicitly return `()`, just like a function does.
+This automatic `()` is important when dealing with constructs like `if` and `match` expressions that need to unify the types of multiple branches of execution into one.
+
+#### Conditionals: `if` and `match` {#conditionals-if-and-match}
+
+Rust's `if` expression is, syntactically, similar to C's. The full syntax is
+```rust
+if cond1 {
+  // ...
+} else if cond2 {
+  // ...
+} else {
+  // ...
+}
+```
+
+Conditions must be expressions that evaluate to `bool`.
+A conditional may have zero or more `else if` clauses, and the `else` clause is optional.
+Because of this, Rust does not need (and, thus, does not have) a ternary operator:
+```rust
+let x = if c { a } else { b };
+```
+
+Braces are required on `if` expressions in Rust.
+
+`if` expressions evaluate to the value of the block that winds up getting executed.
+As a consequence, all blocks must have the same type[^50].
+For example, the following won't compile, because an errant semicolon causes type checking to fail:
+```rust
+if cond() {
+  my_int(4)   // Type is i32.
+} else {
+  my_int(7);  // Type is (), due to the ;
+}
+```
+`i32` and `()` are different types, so the compiler can't unify them into an overall type for the whole `if`.
+
+In general, it is a good idea to end all final expressions in `if` clauses with a semicolon, unless its value is needed.
+
+Like C, Rust has a `switch`-like construct called `match`. You can match integers:
+```rust
+let y = match x {
+  0 => 0x00,       // Match 0.
+  1..=10 => 0x0f,  // Match all integers from 1 to 10, inclusive.
+  _ => 0xf0,       // Match anything, like a `default:` case.
+};
+```
+Like `if` expressions, `match` expressions produce a value.
+The syntax `case val: stuff; break;` roughly translates into `val => stuff,` in Rust.
+Rust calls these case clauses "match arms"[^51].
+
+`match` statements do not have fallthrough, unlike C.
+In particular, only one arm is ever executed.
+Rust, however, allows a single match arm to match multiple values:
+```rust
+match x {
+  0 | 2 | 4 => /* ... */,
+  _ => /* ... */,
+}
+```
+
+Rust will statically check that every possible case is covered.
+This is especially useful when matching on an enum:
+```rust
+enum Color { Red, Green, Blue, }
+let c: Color = /* ... */;
+match c {
+  Color::Red =>   /* ... */,
+  Color::Green => /* ... */,
+  Color::Blue =>  /* ... */,
+}
+```
+No `_ =>` case is required, like a `default:` would in C, since Rust statically knows that all cases are covered (because enums cannot take on values not listed as a variant).
+If this behavior is not desired in an enum (because more variants will be added in the future), the `#[non_exhaustive]` attribute can be applied to an enum definition to require a default arm.
+
+We will see later that pattern matching makes `match` far more powerful than C's `switch`.
+
+
+#### Loops: `loop` and `while` {#loops-loop-and-while}
+
+Rust has three kinds of loops: `loop`, `while`, and `for`.
+`for` is not a C-style `for`, so we'll discuss it later.
+`while` is simply the standard C `while` loop, with slightly different syntax:
+```rust
+while loop_condition { /* Stuff. */ }
+```
+It can be used as an expression, but it always has type `()`; this is most notable when it is the last expression in a block.
+
+`loop` is unique to Rust; it is simply an infinite loop[^52]:
+```rust
+loop { /* Stuff. */ }
+```
+Because infinite loops can never end, the type of the `loop` expression (without a `break` in it!) is `!`, since any code after the loop is dead.
+Having an unconditional infinite loop allows Rust to perform better type and lifetime analysis on loops.
+Under the hood, all of Rust's control flow is implemented in terms of `loop`, `match`, and `break`.
+
+
+#### Control Flow {#control-flow}
+
+Rust has `return`, `break`, and `continue`, which have their usual meanings from C.
+They are also expressions, and, much like `loop {}`, have type `!` because all code that follows them is never executed (since they yank control flow).
+
+`return x` exits a function early with the value `x`.
+`return` is just syntax for `return ()`.
+`break` and `continue` do the usual things to loops.
+
+All kinds of loops may be annotated with labels (the only place where Rust allows labels):
+```rust
+'a: loop {
+  // ...
+}
+```
+`break` and `continue` may be used with those labels (e.g. `break 'a`), which will break or continue the enclosing loop with that label (rather than the closest enclosing loop).
+While C lacks this feature, most languages without `goto` have it.
+
+It is also possible to `break`-with-value out of an infinite loop, which will cause the `loop` expression to evaluate to that value, rather than `!`[^53].
+```rust
+let value = loop {
+  let attempt = get();
+  if successful(attempt) {
+    break attempt;
+  }
+};
+```
+
+
+### Talking to C {#talking-to-c}
+
+One of Rust's great benefits is mostly-seamless interop with existing C libraries.
+Because Rust essentially has no runtime, Rust types that correspond to C types can be trivially shared, and Rust can call C functions with almost no overhead[^54].
+The names of external symbols can be "forward-declared" using an `extern` block, which allows Rust to name, and later link with, those symbols:
+``` rust
+extern "C" {
+  fn malloc(bytes: usize) -> *mut u8;
+  static mut errno: i32;
+}
+```
+When the ABI specified is `"C"`, it can be left off; `extern {}` is implicitly `extern "C" {}`.
+
+It is the linker's responsibility to make sure those symbols wind up existing.
+Also, some care must be taken with what types are sent over the boundary.
+See [https://doc.rust-lang.org/reference/items/external-blocks.html](https://doc.rust-lang.org/reference/items/external-blocks.html) for more details.
+
+
+### Analogues of other functionality {#analogues-of-other-functionality}
+
+#### Volatile {#volatile}
+
+Rust does not have a `volatile` qualifier.
+Instead, volatile reads can be performed using the `read_volatile()`[^55] and `write_volatile()`[^56] methods on pointers, which behave exactly like volatile pointer dereference in C.
+
+Note that these methods work on types wider than the architecture's volatile loads and stores, which will expand into a series of volatile accesses, so beware.
+The same caveat applies in C: `volatile uint64_t` will emit multiple accesses on a 32-bit machine.
+
+
+#### Inline Assembly {#inline-assembly}
+
+Rust does not quite support inline assembly yet.
+Clang's inline assembly syntax is available behind the unstable macro `llvm_asm!()`, which will eventually be replaced with a Rust-specific syntax that better integrates with the language.
+`global_asm!()` is the same, but usable in global scope, for defining whole functions.
+Naked functions can be created using `#[naked]`. See [https://doc.rust-lang.org/1.8.0/book/inline-assembly.html](https://doc.rust-lang.org/1.8.0/book/inline-assembly.html).
+
+[Note that this syntax is currently in the process of being redesigned and stabilized.](https://blog.rust-lang.org/inside-rust/2020/06/08/new-inline-asm.html)
+
+#### Bit Casting {#bit-casting}
+
+Rust provides a type system trap-door for bitcasting any type to any other type of the same size:
+```rust
+let x = /* ... */;
+let y = std::mem::transmute<A, B>(x);
+```
+This trap-door is extremely dangerous, and should only be used when `as` casts are insufficient.
+The primary embedded-relevant use-case is for summoning function pointers from the aether, since Rust does not allow casting raw pointers into function pointers (since the latter are assumed valid).
+
+[https://doc.rust-lang.org/std/mem/fn.transmute.html](https://doc.rust-lang.org/std/mem/fn.transmute.html) has a list of uses, many of which do not actually require transmute.
+
+
+#### Linker Shenanigans and Other Attributes {#linker-shenanigans-and-other-attributes}
+
+Below are miscellaneous attributes relevant to embedded programming.
+Many of these subtly affect linker/optimizer behavior, and are very much in the "you probably don't need to worry about it" category.
+
+*   `#[link_section = ".my_section"]` is the rust spelling of `__attribute__((section(".my_section")))`, which will stick a symbol in the given ELF (or equivalent) section.
+*   `#[used]` can be used to force a symbol to be kept by the linker[^57] (this is often done in C by marking the symbol as `volatile`). The usual caveats for `__attribute__((used))`, and other linker hints, apply here.
+*   `#[inline]` is analogous to C's `inline`, and is merely a hint; `#[inline(always)]` and `#[inline(never)]` will always[^58] or never be inlined, respectively.
+*   `#[cold]` can also be used to pessimize inlining for functions that are unlikely to ever be called.
+
+
+## Part II: Rust-Specific Features {#part-ii-rust-specific-features}
+
+The previous part established enough vocabulary to take roughly any embedded C program and manually translate it into Rust.
+However, those Rust programs are probably about as safe and ergonomic as the C they came from.
+This section will focus on introducing features that make Rust safer and easier to write.
+
+
+### Ownership {#ownership}
+
+Double-free or, generally, double-use, are a large class of insidious bugs in C, which don't look obviously wrong at a glance:
+```c
+// `handle` is a managed resource to a peripheral, that should be
+// destroyed to signal to the hardware that the resource is not in use.
+my_handle_t handle = new_handle(0x40000);
+use_for_scheduling(handle);  // Does something with `handle` and destroys it.
+// ... 200 lines of scheduler code later ...
+use_for_scheduling(handle);  // Oops double free.
+```
+Double-free and use-after-free are a common source of crashes and security vulnerabilities in C code.
+Let's see what happens if we were to try this in Rust.
+
+Consider the equivalent code written in Rust:
+```rust
+let handle = new_handle(0x40000);
+use_for_scheduling(handle);
+// ...
+use_for_scheduling(handle);
+```
+
+If you then attempt to compile this code, you get an error:
+```rust
+error[E0382]: use of moved value: `handle`
+  --> src/main.rs:10:24
+   |
+7  |     let handle = new_handle(0x40000);
+   |         ------ move occurs because `handle` has type `Handle`,
+   |                which does not implement the `Copy` trait
+8  |     use_for_scheduling(handle);
+   |                        ------ value moved here
+9  |     // ...
+10 |     use_for_scheduling(handle);
+   |                        ^^^^^^ value used here after move
+```
+
+Use-after-free errors (and double-free errors) are impossible in Safe Rust.
+This particular class of errors (which don't directly involve pointers) are prevented by _move semantics_.
+As the error above illustrates, using a variable marks it as having been "moved from": the variable is now an empty slot of uninitialized memory.
+The compiler tracks this statically, and compilation fails if you try to move out again.
+The variable in which a value is currently stored is said to be its "owner"[^59]; an owner is entitled to hand over ownership to another variable[^60], but may only do so once.
+
+The error also notes that "`Handle` does not implement the `Copy` trait."
+Traits proper are a topic for later; all this means right now is that `Handle` has move semantics (the default for new types).
+Types which _do_ implement `Copy` have _copy semantics_; this is how all types passed-by-value in C behave: in C, passing a struct-by-value always copies the whole struct, while passing a struct-by-reference merely makes a copy of the pointer to the struct.
+This is why moves are not relevant when handling integers and raw pointers: they're all `Copy` types.
+
+Note that any structs and enums you define won't be `Copy` by default, even if all of their fields are.
+If you want a struct whose fields are all `Copy` to also be `Copy`, you can use the following special syntax:
+```rust
+#[derive(Clone, Copy)]
+struct MyPodType {
+  // ...
+}
+```
+
+Of course, the copy/move distinction is something of a misnomer: reassignment due to copy and move semantics compiles to the same `memcpy` or register move code.
+The distinction is purely for static analysis.
+
+
+### References and Lifetimes {#references-and-lifetimes}
+
+Another class of use-after-free involves stack variables after the stack is destroyed.
+Consider the following C:
+```c
+const int* alloc_int(void) {
+  int x = 0;
+  return &x;
+}
+```
+This function is obviously wrong, but such bugs, where a pointer outlives the data it points to, are as insidious as they are common in C.
+
+Rust's primary pointer type, _references_, make this impossible.
+References are like raw pointers, except they are always well-aligned, non-null, and point to valid memory; they also have stronger aliasing restrictions than C pointers.
+Let's explore how Rust achieves this last guarantee.
+
+Consider the following Rust program:
+```rust
+fn alloc_int() -> &i32 {
+  let x = 0i32;
+  &x
+}
+```
+This program will fail to compile with a cryptic error: `missing lifetime specifier`.
+Clearly, we're missing something, but at least the compiler didn't let this obviously wrong program through.
+
+A lifetime, denoted by a symbol like `'a`[^61] (the apostrophe is often pronounced "tick"), labels a region of source code[^62].
+Every reference in Rust has a lifetime attached, representing the region in which a reference is known to point to valid memory: this is specified by the syntax `&'a i32`: reference to `i32` during `'a`.
+Lifetimes, much like types, do not exist at runtime; they only exist for the compiler to perform _borrow checking_, in which the compiler ensures that references only exist within their respective lifetimes.
+A special lifetime, `'static` represents the entire program.
+It is the lifetime of constants and global variables.
+
+Consider the following Rust code:
+```rust
+let x: i32 = 42;
+let y: &'a i32 = &x;  // Start of 'a.
+use_reference(y);
+use_value(x);  // End of 'a, because x has been moved.
+use_reference(y);  // Error: use of y outside of 'a.
+```
+Reference lifetimes start when the reference is taken, and end either when the lifetime goes out of scope or when the value referenced is moved.
+Trying to use the reference outside of the lifetime is an error, since it is now a dangling pointer.
+
+Rust often refers to references as _borrows_: a reference can borrow a value from its owner for a limited time (the lifetime), but must return it before the owner gives the value up to someone else.
+It is also possible for a reference to be a borrow of a borrow, or a _reborrow_: it is always possible to create a reference with a shorter lifetime but with the same value as another one.
+Reborrowing is usually performed implicitly by the compiler, usually around call sites, but can be performed explicitly by writing `&*x`.
+
+Lifetimes can be elided in most places they are used:
+```rust
+fn get_field(m: &MyStruct) -> &u32 {
+  &m.field  // For references, unlike for raw pointers, . acts the same way -> does in C.
+}
+```
+Here, the compiler assumes that the lifetime of the return type should be the same as the lifetime of the `m`[^63].
+However, we can write this out explicitly:
+```rust
+fn get_field<'a>(m: &'a MyStruct) -> &'a u32 { /* ... */ }
+```
+The `<'a>` syntax introduces a new named lifetime for use in the function signature, so that we can explicitly tell the compiler "these two references have the same lifetime".
+This is especially useful for specifying many lifetimes, when the compiler can't make any assumptions:
+```rust
+fn get_fields<'a, 'b>(m1: &'a MyStruct, m2: &'b MyStruct) -> (&'a u32, &'b u32) {
+  (&m1.field, &m2.field)
+}
+```
+Now we can try to fix our erroneous stack-returning function.
+We need to introduce a new lifetime for this function, since there's no function arguments to get a lifetime from:
+```rust
+fn alloc_int<'a>() -> &'a i32 {
+  let x = 0i32;
+  &x
+}
+```
+This now gives us a straightforward error, showing the borrow-checking prevents erroneous stack returns:
+```rust
+error[E0515]: cannot return reference to local variable `x`
+ --> src/lib.rs:9:3
+  |
+9 |   &x
+  |   ^^ returns a reference to data owned by the current function
+```
+
+This `<'a>` syntax can also be applied to items like structs: If you're creating a type containing a reference, the `<'a>` is required:
+```rust
+struct MyRef<'a> {
+  meta: MyMetadata,
+  ptr: &'a u32,  // Lifetime elision not allowed here.
+}
+```
+
+Rust's references come in two flavors: shared and unique.
+A shared reference, `&T`, provides immutable access to a value of type `T`, and can be freely duplicated: `&T` is `Copy`.
+A unique reference, `&mut T`, provides mutable access to a value of type `T`, but is subject to Rust's aliasing rules, which are far more restrictive than C's strict aliasing, and can't be turned off.
+
+There can only be one `&mut T` active for a given value at a time.
+This means that no other references may be created within the lifetime of the unique reference.
+However, a `&mut T` may be reborrowed, usually for passing to a function.
+During the reborrow lifetime, the original reference cannot be used.
+This means the following code works fine:
+```rust
+fn do_mut(p: &mut Handle) { /* ... */ }
+
+let handle: &mut Handle = /* ... */;
+do_mut(handle);  // Reborrow of handle for the duration of do_mut.
+// handle is valid again.
+do_mut(handle);  // Reborrow again.
+```
+In other words, Rust does not have a safe equivalent to `int*`; it only has equivalents for `const int*` and `int* restrict` ... plus casting away `const` is instant Undefined Behavior[^64].
+Rust will aggressively collapse loads and stores, and assume that no mutable references alias for the purpose of its alias analysis.
+This means more optimization opportunities, without safe code needing to do anything.[^65]
+
+Finally, it should go without saying that references are only useful for main memory; Rust is entitled to generate spurious loads and stores for (possibly unused!) references[^66], so MMIO should be performed exclusively through raw pointers.
+
+[https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) elaborates further on the various lifetime rules.
+
+
+#### Operations on References {#operations-on-references}
+
+Rust references behave much more like scalar values than like pointers (borrow-checking aside).
+Because it is statically known that every reference, at all times, points to a valid, initialized value of type `T`, explicit dereferencing is elided most of the time (though, when necessary, they can be dereferenced: `*x` is an lvalue that can be assigned to).
+
+Rust does not have a `->` operator, but, for `x: &T`, the dot operator behaves as if `x` were a `T`.
+If `field` is a field of `T`, for example, `x.field` is the lvalue of `field` (which would be spelled `x->field` in C).
+This applies even for heavily nested references: the dot operator on `&&&T` will trigger three memory lookups.
+This is called the "auto-deref" behavior.
+
+Equality of references is defined as equality of underlying values: `x == y`, for `x: &T` and `y: &T`, becomes `*x == *y`.
+Pointer equality is still possible with `std::ptr::eq(x, y)`.
+References can be coerced into raw pointers: `x as *const T`, and compared directly.
+
+
+### Methods {#methods}
+
+While Rust is not an object-oriented language, it does provide a mechanism for namespacing functions under types: `impl` (for implementation) blocks.
+These also allow you to make use of Rust's visibility annotations, to make implementation details and helpers inaccessible to outside users.
+
+Here's an example of a type with methods.
+```rust
+pub struct Counter(u64);  // Non-public field!
+impl Counter {
+  /// Creates a new `Counter`.
+  pub fn new() -> Self {
+    Counter(0)
+  }
+
+  /// Private helper.
+  fn add(&mut self, x: u64) {
+    self.0 += x;
+  }
+
+  /// Get the current counter value.
+  pub fn get(&self) -> u64 {
+    self.0
+  }
+
+  /// Increment the counter and return the previous value.
+  pub fn inc(&mut self) -> u64 {
+    let prev = self.get();
+    self.add(1);
+    prev
+  }
+
+  /// Consumes the counter, returning its final value.
+  pub fn consume(self) -> u64 {
+    self.get()
+  }
+}
+```
+Outside modules cannot access anything not marked as `pub`, allowing us to enforce an invariant on `Counter`: it is monotonic.
+Let's unpack the syntax.
+
+Functions in an `impl` block are called "inherent functions" or "methods", depending on whether they take a `self` parameter.
+Inherent functions don't take a `self`, and are called like `Counter::new()`[^67].
+
+A `self` parameter is a parameter named `self` (which is a keyword) and having a type involving `Self`[^68] (another keyword, which is a type alias for the type the `impl` block is for), such as `&Self`[^69].
+The syntax `self`, `&self`, `mut self`, and `&mut self` are sugar for the common forms `self: Self`, `self: &Self`, `mut self: Self` and `self: &mut Self`, representing self-by-value, self-by-reference, self-by-mut-value, and self-by-mut-reference.
+
+Methods can, thus, be called like so: `my_counter.inc()`.
+Methods are really just normal functions: you could also have called this like `Counter::inc(&mut my_counter)`.
+Note that calling a function that takes `&self` or `&mut self` triggers a borrow of the receiving type; if a `&self` function is called on a non-reference value, the value will have its address taken, which gets passed into the method.
+
+`impl` blocks, like other items, can be parameterized by lifetimes.
+In order to add methods to a struct with a reference in it, the following syntax can be used:
+```rust
+impl<'a> MyStruct<'a> { /* ... */ }
+```
+If `'a` is never actually used inside the `impl` block, this can be written using a _placeholder lifetime_:
+```rust
+impl MyStruct<'_> { /* ... */ }
+```
+As we've already seen, many primitive types have methods, too; these are defined in special `impl` blocks in the standard library.
+
+
+### Slices and `for` {#slices-and-for}
+
+References also do not allow for pointer arithmetic, so a `&u32` cannot be used to point to a buffer of words.
+Static buffers can be passed around as arrays, like `&[u32; 1024]`, but often we want to pass a pointer to contiguous memory of a runtime-known value.
+_Slices_ are Rust's solution to pointer+length.
+
+A slice of `T` is the type `[T]`; this type is most like a "flexible array member" in C:
+```rust
+struct slice {
+  size_t len;
+  T values[];
+}
+```
+Then, a `slice*` would point to a length followed by that many `T`s; it can't reasonably exist except behind a pointer.
+Similarly, `[T]` is what Rust calls a dynamically sized type[^70], which needs to exist behind a reference: it is much more common to see `&[T]` and `&mut [T]`.
+
+However, Rust still differs from the C version: `&[T]` is a _fat pointer_, being two words wide.
+It essentially looks like this:
+```rust
+struct Slice {
+  len: usize,
+  values: *const T,
+}
+```
+A reference to a slice otherwise works like an array reference: `&x[n]` extracts a reference to the `n`th element in the slice (with bounds checking), `x[n] = y` assigns to it.
+The length of a slice can also be extracted with the `len` method: `x.len()`.
+
+`str`[^71] is a slice-like type that is guaranteed to contain UTF-8 string data.
+
+Slices can be created from arrays and other slices using a "ranged index operation": `&x[a..b]`[^72]`.` This takes the array or slice `x` and creates a slice with the elements from index `a` to index `b` (inclusive of `a`, exclusive of `b`), of length `b - a`.
+`&x[a..]` is the suffix starting at `a`, `&x[..b]` is the prefix ending at `b`, and `&x[..]` is the whole slice, useful for converting arrays into slices.
+Inclusive ranges are also available, with the syntax `a..=b`.
+
+Slices can be iterated over, using `for` loops:
+```rust
+let slice: &[u32] = /* ... */;
+for x in slice {
+  // x is a reference to the nth element in slice.
+}
+```
+
+If an index is desired, it is possible to iterate over a range directly:
+```rust
+for i in 0..slice.len() {
+  let x = &slice[i];
+  // ...
+}
+```
+
+This can be combined with the `_` pattern to simply repeat an operation `n` times:
+```rust
+for _ in 0..n {
+  // ...
+}
+```
+
+One important note with slices, as pertains to borrowing, is unique references.
+If we have a unique reference to a slice, it's not possible to take unique references to multiple elements at once:
+```
+let slice: &mut [u32] = /* ... */;
+let x = &mut slice[0];
+let y = &mut slice[1];  // Error: slice is already borrowed.
+```
+The method `split_at_mut()`[^73] can be used to split a unique slice reference into two non-overlapping unique slice references:
+```rust
+let slice: &mut [u32] = /* ... */;
+let (slice1, slice2) = slice.split_at_mut(1);
+let x = &mut slice1[0];  // slice[0]
+let y = &mut slice2[0];  // slice[1]
+```
+It is usually possible to structure code in such a way that avoids it, but this escape hatch exists for when necessary.
+Slices can also be decomposed into their pointer and length parts, using the `as_ptr()` and `len()` functions, and reassembled with `std::slice::from_raw_parts()`[^74].
+This operation is unsafe, but useful for bridging C and Rust, or Rust and Rust across a syscall or IPC boundary.
+
+More slice operations can be found at [https://doc.rust-lang.org/std/slice/index.html](https://doc.rust-lang.org/std/slice/index.html) and [https://doc.rust-lang.org/std/primitive.slice.html](https://doc.rust-lang.org/std/primitive.slice.html).
+
+
+#### String Literals {#string-literals}
+
+Rust string literals are much like C string literals: `"abcd..."`.
+Arbitrary ASCII-range bytes can be inserted with `\xNN`, and supports most of the usual escape sequences.
+However, all Rust strings are UTF-8 encoded byte slices: `&str` is a wrapper type around `&[u8]` that guarantees that the bytes inside are valid UTF-8.
+The type of all string literals is `&'static str`.
+
+Rust string literals can contain arbitrary newlines in them, which can be escaped:
+```rust
+// Equivalent to "foo\n  bar".
+let s = "foo
+  bar";
+// Equivalent to "foo  bar".
+let s = "foo\
+  bar";
+```
+
+Raw strings disable escape sequences, and are delimited by an arbitrary, matching number of pound signs:
+```rust
+let s = r"...";
+let s = r#" ..."#;
+let s = r#####"..."#####;
+```
+If instead a byte string with no encoding is required, byte strings can be used: `b"..."`.
+Their contents must be ASCII (or escaped bytes), and their type is `&[u8]`.
+Raw strings can also be byte strings: `br#"..."#`.
+
+Rust also has character literals in the form of `'z'`[^75], though their type is `char`, a 32-bit Unicode code-point.
+To get an ASCII byte of type `u8`, instead, use `b'z'`.
+
+### Destructors and RAII {#destructors-and-raii}
+
+Destructors are special functions that perform cleanup logic when a value has become unreachable (i.e., both the `let` that originally declared it can no longer be named, and the last reference to it expired).
+After the destructor is run, each of the value's fields, if it's a struct or enum, are also destroyed (or "dropped").
+
+Destructors are declared with a special kind of `impl` block (we'll see more like this, later):
+```rust
+impl Drop for MyType {
+  fn drop(&mut self) {
+    // Dtor code.
+  }
+}
+```
+If several values go out of scope simultaneously, they are dropped in reverse order of declaration.
+
+The `drop` method can't be called manually; however, the standard library function `std::mem::drop()` [^76] can be used to give up ownership of a value and immediately destroy it.
+Unions[^77] and types with copy semantics cannot have destructors.
+
+Destructors enable the _resource acquisition is initialization_ (RAII) idiom.
+A type that holds some kind of temporary resource, like a handle to a peripheral, can have a destructor to automatically free that resource.
+The resource is cleaned up as soon as the handle goes out of scope.
+
+The classic example of RAII is dynamic memory management: you allocate memory with `malloc`, stash the returned pointer in a struct, and then that struct's destructor calls `free` on that pointer.
+Since the struct has gone out of scope when `free` is called, UAF is impossible.
+Thanks to Rust's move semantics, this struct can't be duplicated, so the destructor can't be called twice.
+Thus, double-free is also impossible[^78].
+
+In some situations, calling a destructor might be undesirable (for example, during certain Unsafe Rust operations).
+The standard library provides the special function `std::mem::forget()`[^79], which consumes a value without calling its destructor.
+The `std::mem::ManuallyDrop<T>`[^80] type is a smart pointer[^81] that holds a `T`, while inhibiting its destructor.
+For this reason, there is no expectation that a destructor actually runs.
+
+The function `std::mem::needs_drop()`[^82] can be used to discover if a type needs to be dropped; even if it doesn't have a `drop` method, it may recursively have a field which does.
+`std::ptr::drop_in_place()`[^83] can be used to run the destructor in the value behind a raw pointer, without technically giving up access to it.
+
+
+### Pattern Matching {#pattern-matching}
+
+References cannot be null, but it turns out that a null value is sometimes useful.
+`Option<T>` is a standard library type representing a "possibly absent `T`"[^84].
+It is implemented as an enum:
+```rust
+enum Option<T> {
+  Some(T),
+  None,
+}
+```
+
+The `<T>` is similar to the lifetime syntax we saw before; it means that `Option<T>` is a _generic type_; we'll dig into those soon.
+
+If we have a value of type `Option<T>` (or, any other enum, really), we can write code conditioned on the value's discriminant using _pattern matching_, which is accessed through the `match` expression:
+```rust
+let x: Option<u32> = /* ... */;
+let y = match x {
+  Some(val) => val,  // If `x` is a `Some`, bind the value inside to `val`.
+  None => 42,  // If `x` is a `None`, do this instead.
+};
+```
+
+The key thing pattern matching gives us is the ability to inspect the union within an enum safely: the tag check is enforced by the compiler.
+
+Patterns are like expressions, forming a mini-language.
+If expressions build up a value by combining existing values, patterns do the opposite: they build up values by deconstructing values.
+In particular, a pattern, applied to an expression, performs the following operations:
+*   Checks that the expression's value actually matches that pattern.
+    (Note that type-checking doesn't go into this; patterns can't be used for conditioning on the type of an expression.)
+*   Optionally binds that expression's value to a name.
+*   Optionally recurs into subpatterns.
+
+Here's a few examples of patterns.
+Keep in mind the matching, binding, and recurrence properties of each.
+In general, patterns look like the value of the expression they match.
+
+*   `_`, an underscore[^85] pattern.
+    The match always succeeds, but it throws away the matched value.
+    This is the `_` in the equivalent of a `default:` case.
+*   `foo`, an identifier pattern.
+    This pattern is exactly like `_`, but it binds the matched value to its name.
+    This is the `val` in the `Some(val)` above.
+    This can also be used by itself as a default case that wants to do something with the matched-on value.
+    The binding can be made mutable by writing `Some(mut val)` instead.
+*   Any numeric literal, for a literal pattern.
+    This match compares the matched value against the literal value, and doesn't match anything.
+    These can also be inclusive ranges: `5..=16`[^86].
+*   `(pat1, pat2, /* etc */)`, a tuple pattern.
+    This match operates on tuple types, and always succeeds: it extracts the individual elements of a tuple, and applies them to the pattern's subpatterns.
+    In particular, the `()` pattern matches the unit value `()`.
+```rust
+let x: (u32, u32) = /* ... */;
+match x {
+  (5, u) => /* ... */,  // Check that first element is five,
+                        // bind the second element to `u`.
+  (u, _) => /* ... */,  // Bind the first element to `u`,
+                        // discard the second element.
+}
+
+let y: (u32, (u32, u32)) = /* ... */;
+match y {
+  // All patterns can nest arbitrarily, like expressions.
+  (42, (u, _)) =>  /* ... */,
+  // `..` can be used to match either a head or a tail of tuple.
+  (.., u) => /* ... */,
+  (u, ..) => /* ... */,
+  (..) =>    /* ... */,  // Synonymous with _.
+}
+```
+*   Struct patterns are analogous to tuple patterns.
+    For a tuple-like struct, they have the exact same syntax, but start with the name of the struct: `MyTuple(a, b, _)`.
+    Regular structs are much more interesting syntax-wise:
+```rust
+struct MyStruct { a: i32, b: u32 }
+match my_struct {
+  MyStruct { a, b } => /* ... */,  // Bind the fields `a` and `b` to
+                                   // names `a` and `b`, respectively.
+  MyStruct { a: foo, b: _ } => /* ... */,  // Bind the field `a` to the name
+                                           // `foo`, and discard the field `b`.
+  MyStruct { a: -5, .. } => /* ... */  // Check that `a` is -5, and ignore
+                                       // other fields.
+}
+```
+*   Enum patterns are probably the most important kind of pattern, and are what we saw in the `match` statement with `Option` above.
+    They're very similar to struct patterns, except that instead of always succeeding, they check that the enum discriminant is the one named in the pattern.
+```rust
+enum MyEnum { A, B{u32), C { a: i32, b: i32 }, }
+match my_enum {
+  MyEnum::A =>    /* ... */,  // Match for variant `A`.
+  MyEnum::B(7) => /* ... */,  // Match for variant `B`, with 7 as the value inside.
+  MyEnum::B(x) => /* ... */,  // Match for variant `B`, binding the value inside to
+                              // `x`.
+
+  MyEnum::C { a: 7, .. } => /* ... */,  // Match for variant `C`, with 7 as the
+                                        // value in `a` and all other fields ignored.
+
+  MyEnum::C { b, .. } => /* ... */,  // Match for variant `C`, binding b to b.
+}
+```
+A complete treatment of the pattern syntax can be found at [https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html).
+
+A `match` expression will evaluate each pattern against a value until one matches, in order; the compiler will warn about patterns which are unreachable[^87].
+The compiler will also ensure that every value will get matched with one of the match arms, either because every case is covered (e.g., every enum variant is present) or an _irrefutable pattern_ is present (i.e., a pattern which matches all values). `_`, `foo`, `(a, _)`, and `MyStruct { a, .. }` are all examples of irrefutable patterns.
+
+If the value being matched on is a reference of some kind, bound names will be references, too.
+
+For example:
+```rust
+match &my_struct {
+  MyStruct { a, .. } => {
+    // Here, `a` is a `&i32`, which is a reference to the `a` field in my_struct.
+  },
+}
+```
+This feature is sometimes called _match ergonomics_, since before it was added, explicit dereference and special `ref` pattern qualifiers had to be added to matches on references.
+
+In addition, `match` statements support two additional features on top of the pattern syntax discussed above:
+*   _Multi-match arms_ can allow a match arm to match on one of multiple patterns: `a | b | c => /* ... */,`.
+    If any of the patterns match, the arm executes.
+*   _Match guards_ give you a shorthand for conditioning an arm on some expression: `Some(foo) if foo.has_condition() => /* ... */,`.
+
+Also, the standard library provides the `matches!()` macro as a shorthand for the following common match expression:
+```rust
+match expr {
+  <some_complex_match_arm> => true,
+  _ => false,
+}
+// ... can be replaced with ...
+matches!(expr, some_complex_match_arm)
+```
+`matches!` supports multi-match and match guards as well.
+
+Irrefutable patterns can be used with normal variable declaration.
+The syntax `let x = /* ... */;` actually uses a pattern: `x` is a pattern.
+When we write `let mut x = /* ... */;`, we are using a `mut x` pattern instead.
+Other irrefutable patterns can be used there:
+```rust
+// Destructure a tuple, rather than using clunky `.0` and `.1` field names.
+let (a, b) = /* ... */;
+
+// Destructure a struct, to access its fields directly.
+let Foo { foo, bar, baz } = /* ... */;
+
+// Syntactically valid but not allowed: `42` is not an irrefutable pattern.
+let 42 = /* ... */;
+```
+
+Special variants of `if` and `while` exist to take advantage of patterns, too:
+```rust
+if let Some(x) = my_option {
+  // If the pattern succeeds, the body will be executed, and `x` will be bound
+  // to the value inside the Option.
+  do_thing(x);
+} else {
+  // Else block is optional; `x` is undefined here.
+  // do_thing(x);  // Error.
+}
+
+while let Some(x) = some_func() {
+  // Loop terminates once the pattern match fails. Again, `x` is bound
+  // to the value inside the Option.
+}
+```
+Unlike normal `let` statements, `if let` and `while let` expressions are meant to be used with refutable patterns.
+
+In general, almost every place where a value is bound can be an irrefutable pattern, such as function parameters and `for` loop variables:
+```rust
+fn get_first((x, _): (u32, u32)) -> u32 { x }
+
+for (k, v) in my_key_values {
+  // ...
+}
+```
+
+### Traits {#traits}
+
+Traits are Rust's core code-reuse abstraction.
+Rust traits are like interfaces in other languages: a list of methods that a type must implement.
+Traits themselves, however, are _not_ types.
+
+A very simple trait from the standard library is `Clone`[^88]:
+```rust
+trait Clone {
+  fn clone(&self) -> Self;
+}
+```
+
+A type satisfying `Clone`'s interface (in Rust parlance, "implements `Clone`") has a `clone` method with the given signature, which returns a duplicate of `self`.
+To implement a trait, you use a slightly funny `impl` syntax:
+```rust
+impl Clone for MyType {
+  fn clone(&self) -> Self { /* implementation */ }
+}
+```
+
+This gives us a consistent way to spell "I want a duplicate of this value".
+The standard library provides traits for a number of similar operations, such as `Default`[^89], for providing a default value, `PartialEq`[^90] and `Eq`, for equality, `PartialOrd`[^91] and `Ord`, for ordering, and `Hash`[^92], for non-cryptographic hashing.
+
+The above traits are special in that they have trivial implementations for a struct or enum, assuming that all fields of that struct or enum implement it.
+The `#[derive()]` syntax described in the "Ownership" section can be used with any of these traits to automatically implement them for a type.
+It is not uncommon for Plain Old Data (POD) types to look like this:
+```rust
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct MyPod {
+  pub a: u8,
+  pub b: u8,
+  // The following line wouldn't compile, because `derive(Eq)` requires
+  // all fields to be `Eq`.
+  // c: NonEq,
+}
+```
+
+Traits can also provide built-in methods implemented in terms of other methods, to provide a default implementation (which can be overridden if a more efficient one is available for a particular type).
+The full `Clone` trait actually looks like this:
+```rust
+pub trait Clone {
+  fn clone(&self) -> Self;
+  fn clone_from(&mut self, source: &Self) {
+    *self = source.clone();
+  }
+}
+```
+
+Implementers don't need to provide `clone_from`, but are allowed to do so if the default implementation isn't good enough.
+
+Traits, and types that implement them, can be defined in different modules, so long as the implementing module defines either the trait or the type.
+This means that trait methods aren't really part of the type, but rather part of the trait plus the type.
+Thus, in order to call trait methods on a particular type, that trait has to be in scope, too.
+When unambiguous, trait functions can be called as either `foo.trait_fn()`, `Foo::trait_fn(foo)`, or `Trait::trait_fn(foo)`.
+However, since names can sometimes be ambiguous, there is a fully unambiguous syntax[^93]: `<Foo as Trait>::trait_fn(foo)`.
+This last syntax is also useful in generic contexts, or for being precise about the exact function being referred to.
+
+Traits are also the vehicle for operator overloading: these traits are found in the std::ops[^94] module of the standard library.
+
+
+#### Trait Objects {#trait-objects}
+
+Traits can be used for dynamic dispatch (also known as virtual polymorphism) through a mechanism called _trait objects_.
+Given a trait `Trait`, and a type `T` that implements it, we can `as`-cast a reference `&T` into a dynamic trait object: `&dyn Trait`.
+For example:
+```rust
+trait Id {
+    fn get_id(&self) -> usize;
+}
+impl Id for Device {
+  // ...
+}
+
+let device: Device = /* ... */;
+let dyn_id = &device as &dyn Id;  // Create a vtable.
+let id = dyn_id.get_id();  // Indirect procedure call.
+```
+
+`dyn Trait` is a dynamically-sized type, much like slices, and can only exist behind a pointer.
+The reference `&dyn Trait` looks something like this:
+```rust
+struct TraitObject {
+  value: *mut (),
+  vtable: *mut Vtable,
+}
+
+struct Vtable {
+  size: usize,
+  align: usize,
+  dtor: fn(&mut T),
+  // Other trait methods.
+}
+```
+Thus, the dynamic function call to `get_id` would compile to something like the following:
+```rust
+let device: Device = /* ... */;
+let dyn_id = &device as IdTraitObject;
+let id = (dyn_id.vtable.get_id)(dyn_id.value);
+```
+
+There are some limitations on what traits can be made into trait objects: namely, functions cannot take or return functions of type `Self`; only `&Self` or `&mut Self`[^95].
+In other words, all of the functions must treat `Self` as if it were not sized and only accessible through a pointer.[^96] A trait that can be made into a trait object is called _object safe_.
+The type `dyn Trait` always behaves as if it implemented `Trait`, which is relevant for generics, discussed below.
+
+
+#### Unsafe Traits {#unsafe-traits}
+
+It is possible to mark a trait as `unsafe` by writing `unsafe trait MyTrait { /* ... */ }`; the only difference with normal traits is that it requires `unsafe impl` to be implemented.
+Unsafe traits typically enforce some kind of additional constraint in addition to their methods; in fact, unsafe traits frequently don't have methods at all.
+For example, the standard library trait `Sync` is implemented by all types which synchronize access[^97].
+Because the invariant this trait asserts is beyond what the compiler can check, it is an unsafe trait.
+
+Trait methods may separately be marked as `unsafe`.
+This is usually done to indicate that not only does care need to be taken in implementing the trait, but calling the function also requires care (and uttering `unsafe`).
+This is separate from marking a trait as `unsafe`, and it is not necessary to mark a trait as `unsafe` for it to have `unsafe` methods.
+
+
+#### Auto Traits
+
+Auto traits are a compiler mechanism for automatically implementing certain traits; in the standard library's source code, this shows up as `auto trait Foo {}` (though this syntax is unavailable for normal libraries).
+Auto traits are implemented automatically for a struct or enum type if all of its fields also implement that trait, and are used for exposing transitive properties to the trait system.
+For example, `Send` and `Sync` are auto traits; a number of other marker traits[^98] are also auto traits.
+
+Auto traits are always markers that you don't really want to opt out of.
+They're like the opposite of `derive()` traits, which you need to opt into, since they meaningfully affect the API of your type in a way that it is important to be able to control.
+
+
+### Generics {#generics}
+
+_Generic programming_ is writing source code that can be compiled for many types.
+Generics are one of Rust's core features, which enable polymorphic[^99] static dispatch.
+
+Functions can be made generic by introducing _type parameters_, using a syntax similar to explicit lifetimes[^100]:
+```rust
+fn identity<T>(x: T) -> T {
+  x
+}
+```
+This function accepts a value of any type and immediately returns it.
+It can then be called like this: `identity::<i32>(42)`[^101].
+Using a generic function with all of its type parameters filled in causes it to be _instantiated_ (or _monomorphized_), resulting in code being generated for it.
+This process essentially consists of replacing each occurrence of `T` with its concrete value.
+
+Each distinct instantiation is a separate function at runtime, with a separate address, though for functions which generate identical code, like `identity::<i32>` and `identity::<u32>`, the linker may deduplicate them.
+Overzealous use of generic code can lead to binary bloat.
+
+Most of the time, the `::<>` bit (referred to by some reference materials as the "turbofish") is unnecessary, since Rust type deduction can infer it: `let x: u64 = identity(42);` will infer that `T = u64`.
+It can, however, be useful to include when otherwise unnecessary to help with readability.
+
+Types can also be generic, like the  `Option<T>` type from before[^102]:
+```rust
+struct MyWrapper<T> {
+  foo: usize,
+  bar: T,
+}
+```
+The concrete type `MyWrapper<i32>` consists of replacing all occurrences of `T` in the definition with `i32`, which we can otherwise use as a normal type:
+```
+fn get_foo(mw: MyWrapper<i32>) -> usize {
+  mw.foo
+}
+```
+Note that `MyWrapper` on its own is not a type.
+
+Note that different generic instantiations are different types, with different layouts and sizes, which cannot be converted between each other in general.[^103]
+
+Unsurprisingly, we can combine generic functions with generic types.
+In this case, we don't really need to know that `T = i32`, so we factor it out.
+```rust
+fn get_foo<T>(mw: MyWrapper<T>) -> usize {
+  mw.foo
+}
+```
+We can also build a generic function to extract the generic field:
+```rust
+fn get_bar<T>(mw: MyWrapper<T>) -> T {
+  mw.bar
+}
+```
+
+Just like with lifetimes, `impl` blocks need to introduce type parameters before they are used:
+```rust
+impl<T> MyWrapper<T> {
+  // ...
+}
+```
+
+
+#### Generic Bounds {#generic-bounds}
+
+However, generics alone have one limitation: the function is only type and borrow checked once, in its generic form, rather than per instantiation; this means that generic code can't just call inherent methods of `T` and expect the lookup to succeed[^104].
+For example, this code won't compile:
+```rust
+fn generic_add<T>(x: T, y: T) -> T {
+  x + y
+}
+```
+
+The error looks like this:
+```rust
+error[E0369]: cannot add `T` to `T`
+ --> src/lib.rs:2:6
+  |
+2 |     x+y
+  |     -^- T
+  |     |
+  |     T
+  |
+  = note: T might need a bound for std::ops::Add
+```
+The compiler helpfully suggests that we need a "bound" of some sort.
+Generic bounds are where traits really shine.
+
+`Add` is a standard library trait, that looks something like the following:
+```rust
+trait Add<Rhs> {
+  type Output;
+  fn add(self, other: Rhs) -> Self::Output;
+}
+```
+Not only is this trait generic, but it also defines an _associated type_, which allows implementations to choose the return type of the addition operation[^105].
+Thus, for any types `T` and `U`, we can add them together if `T` implements `Add<U>`; the return type of the operation is the type `<T as Add<U>>::Output`[^106].
+
+Thus, our `generic_add` function should be rewritten into
+```rust
+fn generic_add<T: Add<T>>(x: T, y: T) -> T::Output {
+  x + y
+}
+```
+The `T: Add<T>` part is a generic bound, asserting that this function can only compile when the chosen `T` implements `Add<T>`.
+
+If we want to ensure we return a T, we can change the bound to require that `Output` be `T`:
+```rust
+fn generic_add<T>(x: T, y: T) -> T
+  where T: Add<T, Output=T>
+{
+  // ...
+}
+```
+Note that this bound is included in a `where` clause, after the return type.
+This is identical to placing it in the angle brackets, but is recommended for complicated bounds to keep them out of the way.
+In-bracket bounds and `where` clauses are available for all other items that can have generic bounds, such as traits, impls, structs, and enums[^107].
+
+Bound generics can be used to emulate all kinds of other behavior.
+For example, the `From` and `Into` traits represent lossless conversions, so a function that wants any value that can be converted into `MyType` might look like
+```rust
+fn foo<T: Into<MyType>>(x: T) {
+  // ...
+}
+```
+You could then implement `From<T>` on `MyType` for all `T` that can be converted into `MyType`.
+When `U` implements `From<T>`, a generic `impl` in the standard library causes `T` to implement `Into<U>`.
+At the call-site, this looks like an overloaded function[^108].
+
+Bound generics can also be used to pass in constants. Imagine that we define a trait like
+```rust
+trait DriverId {
+  const VALUE: u8;
+}
+```
+This trait could then be implemented by various zero-sized types that exist only to be passed in as type parameters:
+```rust
+struct GpioDriverId;
+impl DriverId for GpioDriverId {
+  const VALUE: u8 = 0x4a;
+}
+```
+Then, functions that need to accept a constant id for a driver can be written and called like this:
+```rust
+fn get_device_addr<Id: DriverId>() -> usize {
+  // use Id::VALUE somehow ...
+}
+// ...
+get_device_addr::<GpioDriverId>()
+```
+Types can also be bound by lifetimes.
+The bound `T: 'a` says that every reference in `T` is longer than `'a`; this kind of bound will be implicitly inserted whenever a generic `&'a T` is passed around.
+Bounds may be combined: `T: Clone + Default` and `T: Clone + 'a` are both valid bounds.
+Finally, lifetimes may be bound by other lifetimes: `'a: 'b` means that the lifetime `'a` is longer than `'b`.
+
+
+#### Phantom Data
+
+The following is an error in Rust:
+```rust
+error[E0392]: parameter `T` is never used
+ --> src/lib.rs:2:12
+  |
+2 | struct Foo<T>;
+  |            ^ unused parameter
+  |
+  = help: consider removing `T`, referring to it in a field,
+    or using a marker such as `std::marker::PhantomData`
+```
+
+Rust requires that all lifetime and type parameters be used, since generating code to call destructors requires knowing if a particular type owns a `T`.
+This is not always ideal, since it's sometimes useful to expose a `T` in your type even though you don't own one; we can work around this using the compiler's suggestion: `PhantomData`.
+For more information on how to use it, refer to the type documentation[^109] or the relevant Rustonomicon entry[^110].
+
+
+### Smart Pointers {#smart-pointers}
+
+In Rust, a "smart pointer"[^1112] is any type that implements `std::ops::Deref`[^112], the dereference operator[^113].
+`Deref` is defined like this:
+```rust
+trait Deref {
+  type Target;
+  fn deref(&self) -> &Self::Target;
+}
+```
+Types which implement `Deref` can also implement the mutable variant:
+```rust
+trait DerefMut: Deref {
+  fn deref_mut(&mut self) -> &mut Self::Target;
+}
+```
+
+Implementing the `Deref` trait gives a type `T` two features:
+*   It can be dereferenced: `*x` becomes syntax sugar for `*(x.deref())` or `*(x.deref_mut())`, depending on whether the resulting lvalue is assigned to.
+*   It gains auto-deref: if `x.foo` is not a field or method of `T`, then it expands into `x.deref().foo` or `x.deref_mut().foo`, again depending on use.
+
+Furthermore, `deref` and `deref_mut` are called by doing an explicit reborrow: `&*x` and `&mut *x`.
+
+One example of a smart pointer is `ManuallyDrop<T>`.
+Even though this type contains a `T` directly (rather than through a reference), it's still called a "smart pointer", because it can be dereferenced to obtain the `T` inside, and methods of `T` can be called on it.
+As we will see later, the `RefCell<T>` type also produces smart pointers.
+It is not uncommon for generic wrapper types, which restrict access to a value, to be smart pointers.
+
+Note that, because `Target` is an associated type, a type can only dereference to one other type.
+
+While not quite as relevant to smart pointers, the `Index` and `IndexMut` traits are analogous to the `Deref` and `DerefMut` traits, which enable the `x[foo]` subscript syntax.
+`Index` looks like this:
+```rust
+trait Index<Idx> {
+  type Output;
+  fn index(&self, index: Idx) -> &Self::Output;
+}
+```
+
+An indexing operation, much like a dereference operation, expands from `x[idx]` into `*(x.index(idx))`.
+Note that indexing _can_ be overloaded, and is a useful example of how this overloading through traits can be useful.
+For example, `<[u8] as Index<usize>>::Output` is `u8`, while `<[u8] as Index<Range>>::Output` is `[u8]`.
+Indexing with a single index produces a single byte, while indexing with a range produces another slice.
+
+
+### Closures {#closures}
+
+Closures (sometimes called "lambda expressions" in other languages) are function literals that capture some portion of their environment, which can be passed into other functions to customize behavior.
+
+Closures are not mere function pointers, because of this captured state.
+The closest analogue to this in C is a function that takes a function pointer and some "context".
+For example, the Linux `pthread_create()` function takes a `void* (*start_routine)(void*)` argument and a `void* arg` argument.
+`arg` represents state needed by `start_routine` to execute.
+In a similar way, Rust closures need extra state to execute, except `arg` becomes part of the `start_routine` value.
+Not only that, Rust will synthesize a bespoke context struct for `arg`, where normally the programmer would need to do this manually.
+Rust makes this idiom much easier to use, and, as such, much more common.
+
+As we'll see, Rust has a number of different ABIs for closures, some of which closely resemble what `pthread_create` does; in some cases, the function pointer and its context can even be inlined.
+
+In Rust, the syntax for creating a closure is `|arg1, arg2| expr`.
+They can be very simple, like `|(k, _)| k` (which uses pattern-matching to extract the first element of a tuple) or complex, using a block expression to create a longer function: `|foo| { /* ... */ }`.
+The types of arguments can be optionally specified as `|foo: Foo| { /* ... */ }`, and the return type as `|foo| -> Bar { /* ... */ }`, though in almost all cases type inference can figure them out correctly.
+A closure that takes no arguments can be written as `|| /* ... */`.
+
+Closures capture their environment by reference; the mutable-ness of that reference is inferred from use.
+For example:
+```rust
+let x = /* ... */;
+let y = /* ... */;
+let f = |arg| {
+  x.do_thing(arg);  // Takes &self, so this implicitly produces a shared reference.
+  y.do_mut_thing(arg);  // Takes &mut self, so it takes a unique reference instead.
+};
+// Note: f holds a unique borrow of y.
+let z = &mut y;  // Error!
+```
+
+Above, `f` captures `x` by shared reference and `y` by unique reference.
+The actual closure value `f` is a synthetic struct containing the captures:
+```rust
+struct MyClosure<'a> {
+  x: &'a X,
+  y: &'a mut Y,
+}
+```
+Calling a closure, like `f()`, calls a synthetic function that takes `MyClosure` as its first argument.
+It's possible to instead capture by moving into the closure; this can be done with the `move |arg| { /* ... */ }` syntax.
+If it were applied to `f`, `MyClosure` would become
+```rust
+struct MyClosure<'a> {
+  x: X,
+  y: Y,
+}
+```
+
+Rust does not cleanly support mixing capture-by-move and capture-by-reference, but it is possible to mix them by capturing references by move:
+```rust
+let x = /* ... */;
+let y = /* ... */;
+let x_ref = &x;
+let f = move |arg| {
+  x_ref.do_thing(arg);  // Capture x_ref by move, aka capture x by shared ref.
+  y.do_mut_thing(arg);  // Capture y by move.
+};
+```
+The distinction between capture-by-move and capture-by-ref is mostly irrelevant for `Copy` types.
+
+To be polymorphic over different closure types, we use the special `Fn`, `FnMut`, and `FnOnce` traits.
+These represent functions that can be called by shared reference, unique reference, or by move.
+Closures that only capture shared references implement all three; closures that capture by unique reference implement only the latter two, and closures that capture by move implement only the last one[^114].
+Function pointers, function items[^115], and closures that don't capture also implement all three, and can all be converted to function pointers.
+
+These traits use special syntax similar to function pointers[^116].
+For example, `Fn(i32) -> i32` represents taking an `i32` argument and returning another `i32`.
+Closures also implement `Copy` and `Clone` if all of their captures do, too.
+
+
+#### Closures as Function Arguments {#closures-as-function-arguments}
+
+There are roughly two ways of writing a function that accepts a closure argument: through dynamic dispatch, or through static dispatch, which have a performance and a size penalty, respectively.
+
+`Fn` and `FnMut` closures can be accepted using trait objects:
+```rust
+fn my_do_thing(func: &dyn Fn(i32) -> i32) -> i32 {
+  func(MY_CONST)
+}
+```
+This is completely identical to the C approach: the synthetic function lives in the trait object vtable, while the captures are behind the actual trait object pointer itself.
+In other words:
+```rust
+struct DynClosure {
+  vtable: *mut Vtable,
+  captures: *mut Captures,
+}
+```
+Of course, the vtable call has a performance penalty, but avoids the code size overhead of generic instantiation.
+
+Using generics allows passing in closures implementing `Fn`, `FnMut`, or `FnOnce`, by specializing the called function for each function type:
+```rust
+fn my_do_thing<F: Fn(i32) -> i32>(func: F) -> i32 {
+  func(MY_CONST)
+}
+```
+This will translate to a direct call to the synthetic closure function with no overhead, but will duplicate the function for each closure passed in, which can result in a big size hit if used on large functions.
+
+It is possible to use a shorthand for declaring this type of function, that avoids having to declare a type parameter:
+```rust
+fn my_do_thing(func: impl Fn(i32) -> i32) -> i32 { /* ... */ }
+```
+
+The pseudotype `impl Trait` can be used in function argument position to say "this parameter can be of any type that implements `Trait`", which effectively declares an anonymous generic parameter.
+Note that `Trait` can technically be any generic bound involving at least one trait: `impl Clone + Default` and `impl Clone + 'a` are valid.
+
+
+#### Closures as Function Returns {#closures-as-function-returns}
+
+Closure types are generally unnameable. The canonical way to return closures is with `impl Trait` in return position:
+```rust
+fn new_fn() -> impl Fn(i32) -> i32 {
+  |x| x * x
+}
+```
+
+Return-position `impl Trait` means "this function returns some unspecified type that implements `Trait`".
+Callers of the function are not able to use the actual type, only functions provided through `Trait`.
+`impl Trait` can also be used to hide implementation details, when a returned value only exists to implement some trait.
+
+Return-position `impl Trait` has a major caveat: it cannot return multiple types that implement the trait.
+For example, the following code is a type error:
+```rust
+fn new_fn(flag: bool) -> impl Fn(i32) -> i32 {
+  if flag {
+    |_| 0
+  } else {
+    |x| x * x
+  }
+}
+```
+The resulting compiler error looks like this:
+```rust
+  = note: expected type `[closure@src/lib.rs:3:5: 3:10]`
+          found closure `[closure@src/lib.rs:5:5: 5:14]`
+  = note: no two closures, even if identical, have the same type
+  = help: consider boxing your closure and/or using it as a trait object
+```
+In a non-embedded context, the solution (as suggested by the compiler) would be to allocate the closures on the heap, and use trait objects.
+However, allocation is limited in embedded contexts, so this solution is unavailable.
+
+If none of the closures capture, returning a function pointer may be an acceptable solution:
+```rust
+fn new_fn(flag: bool) -> fn(i32) -> i32 {
+  if flag {
+    |_| 0
+  } else {
+    |x| x * x
+  }
+}
+```
+#### Closures as Struct Fields {#closures-as-struct-fields}
+
+Using closures as struct fields is fairly limited without being able to easily allocate.
+The two options are to either make the trait generic on the closure type (which needs to be propagated through everything that uses the struct), or to require that closures not capture, and use function pointers instead:
+```rust
+struct MyStruct<F>
+  where F: Fn(i32) -> i32 {
+  val: usize,
+  func: F,
+}
+// Vs.
+struct MyStruct {
+  val: usize,
+  func: fn(i32) -> i32,
+}
+```
+In general, function pointers are easiest, and the requirement of no captures is not particularly onerous.
+The generic variant tends to be more useful for short-lived types, like combinators.
+
+Short-lived structs can also try to use trait objects, but the lifetime requirement can be fairly limiting:
+```rust
+struct MyStruct<'a> {
+  val: usize,
+  func: &'a dyn Fn(i32) -> i32,
+}
+```
+
+
+### `Option` and `Result`: Error Handling in Rust {#option-and-result-error-handling-in-rust}
+
+As we saw above, `Option` is a type that lets us specify a "potentially uninitialized" value.
+While it is common to work with `Option` using `match` expressions, it also has a number of convenience functions that shorten common sequences of code.
+`is_none()` can be used to check that an `Option` is empty; `map` can be used to convert the value inside an Option:
+```rust
+opt.map(|t| t + 1)  // Increments the value inside, if there is one.
+```
+
+`unwrap_or()` can be used to extract the value inside, with a default:
+```rust
+opt.unwrap_or(42)  // Get the value inside, or the value 42 if the former is unavailable.
+```
+
+The documentation for `Option` describes a number of other potential uses and operations on `Option`: [https://doc.rust-lang.org/std/option](https://doc.rust-lang.org/std/option/).
+The type documentation itself has a full list of all of the convenience functions defined for `Option`: [https://doc.rust-lang.org/std/option/enum.Option.html](https://doc.rust-lang.org/std/option/enum.Option.html).
+
+One key application of `Option` is safe nullable references: `Option<&T>`.
+The Rust language guarantees that `Option<&T>` is identical to a nullable pointer at the ABI layer[^117], so it can be safely passed that way into C code.
+This optimization is also available for structs which contain at least one reference: the `is_none()` bit will be compressed into one of the struct's reference fields.
+Some other types are also eligible for memory layout optimization, such as `NonZeroI32`[^118].
+
+`Result<T, E>` is similar to `Option<T>`, but rather than having an "empty" state, it has an "error" state:
+```rust
+enum Result<T, E> {
+  Ok(T),
+  Err(E),
+}
+```
+
+A `Result<T, E>` represents a completed computation of a value of type `T` that might have gone wrong.
+The "gone wrong" part is represented by some user-specified type `E`.
+`E` is usually some kind of enum type, since Rust does not have a single error type for all situations.
+```rust
+enum MyError {
+  DeadlineExceeded,
+  BufferExhausted(usize),
+  OtherError(ErrorCode),
+}
+```
+It is fairly common to define custom `Result` types using a common error enum for your code:
+```rust
+type Result<T> = std::result::Result<T, MyError>;
+```
+
+In a way, `Option<T>` is just a `Result<T, ()>`, where the error type is just the trivial unit tuple.
+Rust provides a number of functions for converting between them:
+```rust
+opt.ok_or(error)  // Converts Option<T> into Result<T, E>, using the provided error if
+// the Option is empty.
+res.ok()  // Discards the error portion and returns an Option<T>.
+res.err()  // Discards the ok portion and returns an Option<E>.
+```
+Computations that are executed for their side-effects which can fail, such as a write, tend to return `Result<(), E>`.
+This is unlike `C`, where the handling of functions that can fail is inconsistent when they return `void`, because `void` is not a real type.
+
+Sometimes, it is necessary to produce a `Result`, due to some trait's interface, for an operation that cannot fail.
+Current practice is to use the type `Result<T, std::convert::Infallible>`[^119], which can be matched on as follows:
+```rust
+let res: Result<T, Infallible> = /* ... */;
+match res {
+  Ok(t) => { /* ... */ },
+  Err(x) => match x {},
+}
+```
+
+`Result` supports a special early-return syntax.
+When in a function returning `Result<T, E>`, and you have a value `res` of type `Result<U, E>`, the expression `res?` will unwrap the `Result`, and get the "ok" value inside if it's present, or immediately return with the error if it isn't.
+That is, `res?` is translated to
+```result
+match res {
+  Ok(x) => x,
+  Err(e) => return Err(e),
+}
+```
+This question mark operator can be chained with methods, so that straightforward code that early-returns on the first error can be written without explicit control flow:
+```
+let x = my_thing.foo()?.bar()?.baz()?;
+```
+See [https://doc.rust-lang.org/std/result/index.html](https://doc.rust-lang.org/std/result/index.html) for more `Result` operations.
+
+
+### `for` Revisited: Iterators {#for-revisited-iterators}
+
+An _iterator_ is any type that implements the `Iterator` trait, which looks like this:
+```rust
+trait Iterator {
+  type Item;
+  fn next(&mut self) -> Option<Self::Item>;
+}
+```
+
+An iterator produces a sequence of `Option<Item>` values; the `next()` method allows an iterator to advance some internal state and produce the next value in the sequence.
+
+For example, a very simple iterator simply produces every nonnegative integer value, in sequence:
+```rust
+struct Counter { state: u64 }
+impl Iterator for Counter {
+  type Item = u64;
+  fn next(&mut self) -> Option<u64> {
+    let current = self.state;
+    self.state += 1;
+    Some(current)
+  }
+}
+```
+
+This iterator will produce values forever: it always returns `Some`.
+An iterator that eventually produces `None`, and then forever more returns `None`, is called "fused"[^120].
+Some iterators may start returning `Some` again after returning `None`, but most Rust constructs treat all iterators as if they are fused.
+
+A related trait is the `IntoIter` trait:
+
+```rust
+trait IntoIter {
+  type Iter: Iterator;
+  fn into_iter(self) -> Self::Iter;
+}
+```
+
+An `IntoIter` type can be converted into an iterator.
+This type is used to drive the `for` loops we saw for iterating slices:
+```rust
+for pattern in expr {
+  // ...
+}
+// is syntactic sugar for
+let iter = expr.into_iter();
+while let Some(pattern) = iter.next() {
+  // ...
+}
+```
+In the slice example, `&'a [T]` implements `IntoIter`, which produces an iterator that produces each element of the slice, in sequence; the `Range<i32>` type (which is what the syntax `0..32` produces) also implements `IntoIter`.
+
+All this machinery allows users to build their own iterators for use with `for` loops for their own types, or using existing iterators and _iterator combinators_.
+The `Iterator` trait defines dozens of provided methods, which can be used to build more complex iterators.
+Here are a few examples of particularly useful combinators:
+
+*   `iter.chain(iter2)`.
+    Chains two iterators with the same `Item` type together.
+     The second iterator starts when `iter` produces `None`.
+*   `iter.peekable()`.
+    Converts the iterator into an iterator with a `.peek()` function, which returns a reference to the next value in the sequence (but does not advance it).
+*   `iter.enumerate()`.
+    Changes the `Item` type from `T` into `(usize, T)`, tracking the current index in the sequence along with the value.
+*   `iter.step_by(n)`.
+    Changes the iterator to return every `n`th element.
+*   `iter.take(n)`.
+    Shortens the iterator to return `n` elements before fusing.
+*   `iter.map(|x| /* ... */)`.
+     Applies a closure to each element lazily.
+*   `iter.filter(|x| /* ... */)`.
+     Applies a predicate to each element; if the predicate returns `false`, it is skipped by `next()`.
+
+A number of other traits can enhance the properties of an iterator, enabling further methods: `ExactSizeIterator` iterators produce a known, fixed number of values; `DoubleEndedIterators` can have elements pulled from both the front, and the back of the sequence.
+While many of the operations above have naive implementations in terms of `next`, standard library iterators will override them when a more efficient algorithm is available.
+In general, iterators can produce very efficient code similar to that emitted by `while` loops, but care should be taken when using especially complex chains of combinators.
+
+See [https://doc.rust-lang.org/std/iter/trait.Iterator.html](https://doc.rust-lang.org/std/iter/trait.Iterator.html) and [https://doc.rust-lang.org/std/iter](https://doc.rust-lang.org/std/iter/) for full details on available operations.
+
+
+### Modules and Crate Layout {#modules-and-crate-layout}
+
+Each Rust crate is (from the compiler's perspective) given a unique, single-identifier name.
+This name is used to namespace a crate's symbols[^121].
+`core` and `std` are crates.
+
+Each crate is rooted at a `lib.rs` or `main.rs` file, depending on whether it is a library or a binary.
+This file can declare _modules_, which are sub-namespaces of a crate:
+```rust
+// Declares a public module named `devices`. Its definition is found in
+// either `devices.rs` or `devices/mod.rs`, relative to the current file.
+pub mod devices;
+
+// Declares a private module named `tests`. Its definition is found
+// within the curly braces.
+mod tests {
+  // ...
+}
+
+// Declares a private module named `generated`. Its definition is found
+// in the given path.
+#[path = "relative/path/to/file.rs"]
+mod generated;
+```
+Modules can nest arbitrarily: a module can declare more modules.
+
+Symbols in a module can be referred to by paths: `std::mem::drop` refers to the symbol `drop` in the module `std::mem` of crate `std`.
+`crate::devices::gpio::Gpio` refers to the symbol `Gpio` in the module `devices::gpio` of the current crate.
+
+`use` items can be used to create symbol aliases in the current scope:
+```rust
+// Pull in std::mem::drop, aliased to `drop`.
+use std::mem::drop;
+
+// Pull in the module std::mem, so we can now write `mem::drop` for `std::mem::drop`.
+use std::mem;
+
+// Pull in both size_of and drop:
+use std::mem::{size_of, drop};
+
+// Pull in all symbols in `std::mem`, including `drop`. Should typically be best
+// avoided, for readability.
+use std::mem::*;
+
+// Pull in all symbols from the parent module:
+use super::*;
+
+// Pull in a symbol from a submodule (synonymous with using the full
+// path starting with `crate`).
+use self::devices::Gpio;
+
+// Pull in a name, but rename it.
+use std::io::Result as IoResult;
+
+// Pull in a trait to enable its methods, but without pulling its name
+// into scope.
+use std::io::Write as _;
+```
+
+Note that this is subject to visibility restrictions.
+By default, all symbols are "private", only visible in the current module and its child modules.
+This can be spelled explicitly as `pub(self)`.
+A symbol can be restricted to the parent module and child modules with `pub(super)`, and to the current crate with `pub(crate)`.
+Finally, a symbol can be restricted to a particular path using `pub(in that::path)`.
+`pub` simply means "public everywhere".
+
+Pretty much all items can be marked with visibilities, except `impl`s.
+Marking a module with visibility restricts the visibility of all items within.
+A `pub` symbol in a `pub(crate)` module is `pub(crate)`.
+`use` statements can also be marked with visibility: this will cause the imported symbols to become part of the module.
+For example, `std` is full of instances of `pub use core::Symbol;`, to enable `core` symbols to be imported through `std`.
+
+Even `use` items can be marked with visibility:
+```rust
+// mod my_mod
+pub use std::mem::size_of;
+```
+This means that other modules can now access the symbol `size_of` through `my_mod::size_of`, effectively re-exporting the symbol.
+This is how many fundamental `core` types are accessible through the `std` crate as well.
+
+Rust does not have headers, or declaration order constraints; modules within a crate can freely form cyclic dependencies, since they are not units of compilation, merely namespaces.
+Rust crate namespaces are closed: after a crate is fully compiled, no other symbols can be added to it.
+
+
+### Interior Mutability {#interior-mutability}
+
+_Interior mutability_ is a borrow check escape hatch for working around Rust's aliasing rules.
+
+Normally, Rust requires that you prove statically that you have unique access to a value before you mutate it.
+`UnsafeCell<T>`[^122] is a special, compiler-blessed[^123] type which contains a single `T`, and has a method `fn get(&self) -> *mut T`.
+When you can guarantee at runtime that a shared reference to an `UnsafeCell` is actually unique, the raw pointer returned by `get()` can be converted into a unique reference.
+This makes it possible to safely mutate code that is known, at runtime, to be uniquely owned.
+Of course, it is very unsafe to use `UnsafeCell` directly, and exists to form the basis of other abstractions.
+
+There are two common strategies for exposing interior mutability safely: the `Cell`[^124] approach and the `RefCell`[^125] approach.
+
+The `Cell` approach simply never creates a unique reference at all: instead, it holds a valid `T` at all times, and provides a `swap` primitive for taking out the `T` and leaving behind another one.
+This way, no aliasing rules need to be enforced, since no reference actually points to that `T`.
+
+The `RefCell` approach instead does basic borrow checking at runtime.
+In addition to holding a `T`, a `RefCell` holds a counter of the number of outstanding shared references (or a sentinel value for an outstanding unique reference).
+The `try_borrow()` and `try_borrow_mut()` methods dynamically check if such a borrow is valid (no outstanding unique references, or no outstanding references at all, respectively), and return a `Result` to indicate success or failure.
+On success, the returned value is a smart pointer wrapping a reference, whose destructor will decrement the shared/unique reference count in the original `RefCell`.
+In other words, a `RefCell` is like a single-threaded read-write mutex, without the cost of atomicity.
+
+Other abstractions can be built on top of `UnsafeCell` that maintain the aliasing invariants with other strategies, but they will ultimately be analogous to one of `Cell` or `RefCell`.
+
+Interior mutability is also one of the main differences between a constant and a static:
+```rust
+static S: MyCell<u32> = MyCell::new(0);
+const C: MyCell<u32> = MyCell::new(0);
+
+S.set(1);
+S.get();  // value = 1, because `set` modified the memory location.
+C.set(1);
+C.get()  // value = 0, because `set` modified an inlined copy.
+```
+Note that the memory behind `S` changed, so it must be allocated in a `.data` or `.bss` section.
+This illustrates another property of `UnsafeCell`: it causes data that is otherwise declared as immutable to be allocated as mutable.
+
+See [https://doc.rust-lang.org/std/cell/index.html](https://doc.rust-lang.org/std/cell/index.html) for more details; as with all aliasing-related topics, it requires careful attention t detail, and this section is far from exhaustive.
+
+
+### Unsafe Rust {#unsafe-rust}
+
+Unsafe Rust is a dialect of Rust that is denoted by the keyword `unsafe`: `unsafe` blocks, unsafe functions, unsafe traits.
+Importantly, all of these actions require uttering the keyword `unsafe`, so that they can be easily detected in code review.
+Code inside unsafe blocks says to the  reader that the programmer has checked subtle safety guarantees that the compiler cannot on its own.
+
+Unsafe Rust fundamentally works by "turning off" certain checks the compiler normally enforces, whenever inside a `unsafe { /* ... */ }` block expression or the body of an `unsafe fn`.
+The things that Unsafe Rust can do that Safe Rust cannot are:
+*   Call `unsafe` functions.
+*   Dereference raw pointers.
+*   Mutate global state through a mutable `static`.
+*   Read union fields.
+*   Call the `asm!` macro.
+
+Additionally, `unsafe impl`s may implement `unsafe trait`s, but don't need to be inside an `unsafe` block.
+
+The canonical reference is the Rustonomicon[^126], a non-normative document describing common uses for Unsafe Rust.
+It is required reading (mostly the first half) for embedded programming.
+It contains detailed examples of correct and incorrect Unsafe Rust usage, and guidance on when to invoke Unsafe Rust.
+
+Throughout this document, references to Unsafe Rust are made, mostly around calling unsafe functions and referencing raw pointers, which are roughly all that Unsafe Rust can do that normal Rust can't.
+With these powers comes responsibility: Unsafe Rust is not safe from Undefined Behavior, and can leave the machine in a state where actions allowed in normal, safe Rust would trigger Undefined Behavior.
+In general, a few rules of thumb are useful:
+*   Every `unsafe fn` should declare, in documentation, what invariants it assumes that the caller will uphold, and what state it will leave the machine in.
+    For example, `<[T]>::get_unchecked(n)` elides the bounds check for the indexing operation, and it is up to the caller to uphold it instead.
+*   Every time `unsafe` code calls into a non-`unsafe` function, it must ensure that no violated invariants, which could trigger Undefined Behavior, are observable in that safe code.
+    For example, if we have a type that maintains the invariant that `len > 0`, and we temporarily set it to `0` during an `unsafe` block, it must be restored to `> 0` before any safe methods can be called on that type.
+*   Unsafe code should be kept to the absolute minimum, and wrapped in safe interfaces that assert invariants, either through static type-system guarantees or through runtime checks.
+    Every line of unsafe code is a place where the engineering cost of Rust's guarantees are wasted.
+
+In other words, Safe Rust is able to freely assume that Rust's guarantees on aliasing, ownership, and representation of values hold at all times.
+This assumption is pervasive: not only does the compiler use it aggressively optimize code for speed and size, but other library code, such as the destructors of wrapper types, assume it, too.
+Unsafe Rust is responsible for upholding this core guarantee.
+In a way, Unsafe Rust is responsible for protecting Safe Rust.
+
+## Footnotes
+
+[^1]: The "embedded Rust book" is a useful resource for existing rust programmers.
+This document assumes no knowledge of Rust or C++.
+
+[^2]: In particular, use-after-frees, double frees, null dereferences, and data races are all impossible in Rust without using the keyword `unsafe`; this applies for most other things traditionally considered Undefined Behavior in C.
+However, Unsafe Rust has Undefined Behavior, and, due to its stronger invariants, Rust is generally more punishing than C compilers when Undefined Behavior occurs.
+
+[^3]: Alternative implementations exist, though `rustc` is the reference implementation.
+Rust is still in the process of being specified, so, for now, `rustc`'s behavior is mostly normative.
+
+[^4]: Not every project uses rustup, but it's frequently necessary for "nightly" features.
+
+[^5]: Inline assembly is the most salient of these.
+Nightly also provides support for the sanitizers, such as ASAN and TSAN.
+
+[^6]: Example from Tock: [https://github.com/tock/tock/blob/master/rust-toolchain](https://github.com/tock/tock/blob/master/rust-toolchain)
+
+[^7]: Rust libraries use their own special "rlib" format, which carries extra metadata past what a normal .a file would.
+
+[^8]: While each crate is essentially a giant object file, Rust will split up large crates into smaller translation units to aid compilation time.
+
+[^9]: The formatter is kind of a new component, so may require separate installation through `rustup`.
+See [https://github.com/rust-lang/rustfmt#on-the-stable-toolchain](https://github.com/rust-lang/rustfmt#on-the-stable-toolchain).
+
+[^10]: [https://doc.rust-lang.org/core/index.html](https://doc.rust-lang.org/core/index.html)
+
+[^11]: They are also used analogously to `ptrdiff_t` and `size_t`.
+In practice, these types are all the same width on modern systems, though C draws a distinction for portability reasons.
+
+[^12]: Note that Rust spells bitwise negation on integers as `!x` rather than `~x`.
+This is because Rust has a separate boolean type, for which `!x` is logical not.
+
+[^13]: Rust has slightly less absurd precedence rules around bitwise operators; `x ^ y == z` is `(x ^ y) == z`.
+
+[^14]: In C, signed overflow is UB, and unsigned overflow always wraps around.
+
+[^15]: Really, this causes a "panic", which triggers unwinding of the stack.
+In embedded contexts, unwinding is disabled, so this is just a jump to the panic handler, which performs a platform-specific abort.
+
+[^16]: rustc performs the former in debug builds and the latter in release builds.
+
+[^17]: Creating `bool`s with any other representation, using Unsafe Rust, is instant UB.
+This is sometimes called the "validity invariant", and applies to other restricted-range types, such as padding bytes in structs.
+["What The Hardware Does" is not What Your Program Does: Uninitialized Memory](https://www.ralfj.de/blog/2019/07/14/uninit.html) provides a short explanation about why this is important.
+
+[^18]: These functions are frequently entry-points for LLVM intrinsics, similar to GCC/Clangs `__builtin_clz()` and similar.
+
+[^19]: This syntax: `(my_struct_t) { .a = 0, .b = 1 }`.
+
+[^20]: Rust allows trailing commas basically everywhere.
+
+[^21]: Currently, the compiler will reorder struct fields to minimize alignment padding, though there is ongoing discussion to add a "struct field layout randomization" flag as an ASLR-like hardening.
+
+[^22]: Unlike C, Rust has true zero-sized types (ZSTs).
+They are completely elided from the program at runtime, but can be useful for creating abstractions.
+
+[^23]: This is similar to how a `bool` must always be `0` or `1`.
+One could even imagine that `bool` is defined as `enum bool { false = 0, true = 1 }`.
+
+[^24]: Without uttering `unsafe`, it is impossible to witness uninitialized or invalid data.
+Doing so even with `unsafe` is Undefined Behavior.
+
+[^25]: This feature is also sometimes known as "algebraic data types".
+
+[^26]: This syntax requires that the array component type be "copyable", which we will get to later.
+
+[^27]: These accesses are often elided.
+LLVM is very good at optimizing them away, but not perfect.
+
+[^28]: In C, pointers must _always_ be well-aligned.
+Rust only requires this when dereferencing the pointer.
+
+[^29]: In other words, Rust's aliasing model for pointers is the same as that of `-fno-strict-aliasing`.
+Rust allows `*mut u32` and `*mut u64`to alias, for example.
+
+[^30]: In C, it is technically UB to forge raw pointers (such as creating a pointer to an MMIO register) and then dereference them, although all embedded programs need to do this anyway.
+While Rust has not yet formalized what this means, it seems unlikely that they will make forging raw pointers to MMIO regions UB.
+
+[^31]: Or by casting zero.
+
+[^32]: Rust currently does not have an equivalent of the `->` operator, though it may get one some day.
+As such, it is not possible to get pointers to fields of a pointer-to-struct without creating a reference along the way.
+
+[^33]: We will get into references later.
+
+[^34]: We will learn more about "move semantics" when we discuss ownership.
+
+[^35]: However, great care should be taken when using these methods on types that track ownership of a resource, since this can result in a double-free when the pointee is freed.
+
+[^36]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.read_unaligned](https://doc.rust-lang.org/std/primitive.pointer.html#method.read_unaligned)
+
+[^37]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.write_unaligned](https://doc.rust-lang.org/std/primitive.pointer.html#method.write_unaligned)
+
+[^38]: These are effectively memcpys: they will behave as if they read each byte individually with no respect for alignment.
+
+[^39]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to](https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to)
+
+[^40]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to_nonoverlapping](https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to_nonoverlapping)
+
+[^41]: Rust also provides an abstraction for dealing with uninitialized memory that has somewhat fewer sharp edges: [https://doc.rust-lang.org/std/mem/union.MaybeUninit.html](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html).
+
+[^42]: Rust also allows taking the addresses of rvalues, which simply shoves them onto `.rodata` or the stack, depending on mutation.
+For example, `&0` is valid, and produces an address in `.rodata`, while `&mut 0` will push into the stack. The mechanism that converts `&0` into a reference to a constant is called *rvalue promotion*, since `0`, an rvalue of limited lifetime, is promoted into a constant, which has `'static` lifetime.
+
+[^43]: Completely unrelated to the meaning of this keyword in C, where it specifies a symbol's linkage.
+
+[^44]: Immutable statics seem pretty useless: why not make them constants, since you can't mutate them?
+This distinction will arise again when we discuss _interior mutability_ towards the end of the document.
+Also, like globals in `C`, these static variables can be made visible to other libraries.
+
+[^45]: In practice, this is just the C calling convention, except that `#[repr(Rust)]` structs and enums are aggressively broken up into words so they can be passed in registers.
+On 32-bit RISC-V, `(u64, u32, i32)` is passed in four registers.
+Rust functions also do some handling of "panics" in their prologues that is mostly irrelevant in an embedded context.
+
+[^46]: Supported calling conventions: [https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions](https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions)
+
+[^47]: For those familiar with C++, extern blocks *do* disable name mangling there.
+
+[^48]: Rust's type inference is very powerful, which is part of its ML heritage.
+Readable Rust code should include type annotations where necessary to ensure that the type of every `let` can be deduced without having to look at distant context; this is analogous to similar advice for `auto` in C++.
+
+[^49]: It is also possible to leave off the expression in a `let` binding: `let x;`.
+The variable cannot be used until Rust can prove that, in all branches of the program, the variable has been assigned to (and, of course, all the assignments must produce the same type).
+
+[^50]: A missing else block implicitly gets replaced by `else {}`, so the type of all blocks needs to be `()`.
+
+[^51]: The value being matched on is called the "scrutinee" (as in scrutinize) in some compiler errors.
+
+[^52]: Rust has a long-standing miscompilation where `loop {}` triggers UB; this is an issue with LLVM that is actively being worked on, and which in practice is rarely an issue for users.
+
+[^53]: Needless to say, all `break`s must have the same type.
+
+[^54]: Rust's execution model is far more constrained than C, so C function calls are basically black boxes that inhibit optimization.
+However, there is no runtime cost compared to cross-library-calls in C.
+
+[^55]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.read_volatile](https://doc.rust-lang.org/std/primitive.pointer.html#method.read_volatile)
+
+[^56]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile](https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile)
+
+[^57]: Of course, due to an LLVM bug, this is not guaranteed to work...
+
+[^58]: Caveats apply as with `__attribute__((inline_always))`.
+
+[^59]: This coincides with the similar notion in C++: the _owner_ of a value is whomever is responsible for destroying it. We'll cover Rust destructors later on.
+
+[^60]: "Owners" don't need to be stack variables: they can be heap allocations, global variables, function parameters for a function call, or an element of an array.
+
+[^61]: Suggested pronunciations include: "tick a", "apostrophe a", and "lifetime a".
+
+[^62]: These days, it's a subgraph of the control flow graph.
+
+[^63]: The compiler does so using strict "elision rules", and does not actually peek into the function body to do so.
+
+[^64]: Rust is so strict about this that it will mark code as unreachable and emit illegal instructions if it notices that this is happening.
+
+[^65]: Also, note that strict aliasing does not apply to shared references, either.
+`&u32` and `&u64` may alias, but `&mut u32` and `&mut u64` cannot, because a `&mut T` can never alias with any other active reference.
+
+[^66]: To put it in perspective, all references are marked as "dereferenceable" at the LLVM layer, which gives them the same optimization semantics as C++ references.
+Merely materializing an invalid reference is Undefined Behavior, because LLVM will treat it as a pointer to valid memory that it can cache reads from and combine writes to.
+
+[^67]: It is common convention in Rust to name the default function for creating a new value `new`; it is not a reserved word.
+
+[^68]: Note the capital S.
+
+[^69]: This is currently fairly restricted; for our purposes, only `Self`, `&Self`, and `&mut Self` are allowed.
+
+[^70]: There's only a couple of dynamically sized types built into the language; user-defined DSTs exist, but they're a very advanced topic.
+
+[^71]: https://doc.rust-lang.org/std/str/index.html
+
+[^72]: It should be noted that `a..b` is itself an expression, which creates a `Range<T>` of the chosen numeric type.
+
+[^73]: [https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut)
+
+[^74]: [https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html)
+
+[^75]: The second quote disambiguates them from lifetime names.
+
+[^76]: [https://doc.rust-lang.org/std/mem/fn.drop.html](https://doc.rust-lang.org/std/mem/fn.drop.html)
+
+[^77]: Unions also cannot contain types with destructors in them.
+
+[^78]: While not available in embedded environments, `Box<T>` in the standard library is an implementation of this idea: [https://doc.rust-lang.org/std/boxed/index.html](https://doc.rust-lang.org/std/boxed/index.html).
+
+[^79]: [https://doc.rust-lang.org/std/mem/fn.forget.html](https://doc.rust-lang.org/std/mem/fn.forget.html)
+
+[^80]: [https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)
+
+[^81]: We'll get to these later.
+
+[^82]: [https://doc.rust-lang.org/std/mem/fn.needs_drop.html](https://doc.rust-lang.org/std/mem/fn.needs_drop.html)
+
+[^83]:
+[https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html](https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html)
+
+[^84]: Though naively, it might seem like this would make `Option<T>` bigger than `T` (twice as big, for an integer type, where alignment == size), the compiler can optimize the size down.
+For example, as we'll see later, `Option::<&T>::None` is represented as a null pointer, since `&T` can never be null.
+
+[^85]: The single underscore is a keyword in Rust.
+
+[^86]: Inclusive ranges are currently an unstable feature.
+
+[^87]: Of course, the compiler has no problem optimizing match expressions into C switch statements when possible.
+
+[^88]: [https://doc.rust-lang.org/std/clone/trait.Clone.html](https://doc.rust-lang.org/std/clone/trait.Clone.html)
+
+[^89]: [https://doc.rust-lang.org/std/default/trait.Default.html](https://doc.rust-lang.org/std/default/trait.Default.html)
+
+[^90]: [https://doc.rust-lang.org/std/cmp/trait.PartialEq.html](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
+
+[^91]: [https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
+
+[^92]: [https://doc.rust-lang.org/std/hash/trait.Hash.html](https://doc.rust-lang.org/std/hash/trait.Hash.html)
+
+[^93]: The so-called Universal Function Call Syntax.
+
+[^94]: [https://doc.rust-lang.org/std/ops/index.html](https://doc.rust-lang.org/std/ops/index.html)
+
+[^95]: Associated types and constants will also disqualify a trait.
+
+[^96]: The trait `Sized` is implemented by all types with a compile-time known size.
+In the language of generic bounds, trait object methods cannot rely on the fact that `Self: Sized`.
+
+[^97]: This is sometimes called being "thread compatible".
+
+[^98]: [https://doc.rust-lang.org/std/marker/index.html](https://doc.rust-lang.org/std/marker/index.html)
+
+[^99]: I.e., write once, use for many types.
+
+[^100]: When mixing lifetime and type parameters, lifetimes go first: `<'a, T>`.
+
+[^101]: This syntax is necessary to disambiguate a generic call from the `<` operator, while keeping the grammar simple.
+It is also required when naming generic types in expression position, like `Option::<u32>::Some(42)`.
+
+[^102]: The `::<>` syntax is not used in type position, because it is not necessary for disambiguation.
+
+[^103]: The symbol `MyWrapper`, without angle brackets, is sometimes called a _type constructor_.
+Empty angle brackets are also allowed to follow non-generic symbols, mostly for syntactic completeness.
+`i32<>` is equivalent to `i32`.
+
+[^104]: Contrast this to C++: C++ template metaprogramming is similar to Rust generics, but significantly harder to use, because callsites can trigger compiler errors in the definition.
+Rust side-steps this issue completely.
+
+[^105]: A note on generic parameters vs associated types.
+These are similar, but distinct ways of attaching type information to a trait.
+While it is completely possible for a single type `T` to implement `Add<U1>` and `Add<U2>`, it is not possible to implement `Add<U1, Output=u32>` and `Add<U1, Output=u64>` at the same time.
+
+[^106]: Note the Universal Function Call Syntax to refer to a particular trait implementation.
+
+[^107]: `where` clauses can also be empty, which looks silly but is useful for macro authors: `fn foo() -> u32 where { /* ... */ }`.
+
+[^108]: While not present in C (except in C11 via the `_Generic` keyword), function overloading is a popular feature in many other languages.
+
+[^109]: [https://doc.rust-lang.org/std/marker/struct.PhantomData.html](https://doc.rust-lang.org/std/marker/struct.PhantomData.html)
+
+[^110]: [https://doc.rust-lang.org/nomicon/phantom-data.html](https://doc.rust-lang.org/nomicon/phantom-data.html)
+
+[^111]: C++ also has "smart pointers", though it is not as strict about what that means as Rust (none of the smart pointers used in embedded programming in Rust allocate, for example).
+
+[^112]: [https://doc.rust-lang.org/stable/std/ops/trait.Deref.html](https://doc.rust-lang.org/stable/std/ops/trait.Deref.html)
+
+[^113]: Even though raw pointers can be dereferenced with `*ptr`, they do _not_ implement `Deref`.
+Similarly, although references implement `Deref`, they are not typically called smart pointers.
+
+[^114]: These traits actually have an inheritance relation: calling by `&self` means you can obviously call by `&mut self`, by reborrowing the unique reference as a shared reference; similarly, if you can call by `&mut self`, you can call by `self`, by taking a unique reference to `self`.
+In practice, the compiler is pretty good at inlining away the extra calls and references.
+
+[^115]: Functions defined like `fn foo()` each have a unique, closure-like type that coerces to a function pointer when necessary.
+
+[^116]: This is currently magic, but is likely to become less magic in future versions.
+
+[^117]: See [https://doc.rust-lang.org/std/option/index.html#options-and-pointers-nullable-pointers](https://doc.rust-lang.org/std/option/index.html#options-and-pointers-nullable-pointers)
+
+[^118]: See [https://doc.rust-lang.org/std/num/struct.NonZeroI32.html](https://doc.rust-lang.org/std/num/struct.NonZeroI32.html)
+
+[^119]: See [https://doc.rust-lang.org/std/convert/enum.Infallible.html](https://doc.rust-lang.org/std/convert/enum.Infallible.html)
+
+[^120]: As in an electrical fuse: eventually, the fuse goes off and stops working.
+
+[^121]: These being Rust source-code symbols, _not_ linker symbols.
+
+[^122]: [https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html](https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html)
+
+[^123]: `UnsafeCell` (or, the effect it has on data, at any rate) is well-known to the optimizer and the code generator.
+As we'll see below, the presence of `UnsafeCell` can radically change how a value is laid out in memory, even though it logically only contains a `T`.
+
+[^124]: [https://doc.rust-lang.org/std/cell/struct.Cell.html](https://doc.rust-lang.org/std/cell/struct.Cell.html)
+
+[^125]: [https://doc.rust-lang.org/std/cell/struct.RefCell.html](https://doc.rust-lang.org/std/cell/struct.RefCell.html)
+
+[^126]: [https://doc.rust-lang.org/stable/nomicon/](https://doc.rust-lang.org/stable/nomicon/)
diff --git a/doc/security/README.md b/doc/security/README.md
new file mode 100644
index 0000000..3bcd51a
--- /dev/null
+++ b/doc/security/README.md
@@ -0,0 +1,123 @@
+# Security
+
+## Overview
+
+OpenTitan's mission is to create a trustworthy, vendor-agnostic open source
+silicon Root of Trust (RoT) widely adopted across the industry. We do this by
+implementing strong logical security integrity guarantees in the hardware and
+firmware components, and restricting licensing of the OpenTitan trademark to
+those implementations conforming to OpenTitan standards.
+
+## [OpenTitan Security Model Specification][security_model]
+
+The [OpenTitan Security Model Specification][security_model] defines the logical
+security properties of the discrete IC. It covers device and software
+attestation, provisioning, secure boot, chip lifecycle, firmware update, chip
+identity, and chip ownership transfer.
+
+## [Logical Security Model][logical_security_model]
+
+The [OpenTitan Security Model][logical_security_model] provides a high level
+framework for device provisioning and run-time operations. It starts by
+enumerating the range of logical entities supported by the architecture, and
+their mapping into software stages. Runtime isolation properties and baseline
+identity concepts are introduced in this document.
+
+## [Secure Hardware Design Guidelines][implementation_guidelines]
+
+Silicon designs for security devices require special guidelines to protect the designs against myriad attacks.
+To that end, the team established [Secure Hardware Design Guidelines][implementation_guidelines] which are followed when developing OpenTitan security IP.
+
+## Functional Guarantees
+
+At the functional level OpenTitan aims to provide the following guarantees:
+
+*   Silicon Owners shall be able to deploy their own Root of Trust (RoT) Public
+    Key Infrastructure (PKI) after taking ownership of the device.
+*   Silicon Creators shall endorse the authenticity of the hardware. Endorsement
+    is contingent on the silicon adhering to the physical implementation
+    guidelines and standard requirements stipulated by the project. The
+    endorsement shall be measurable via a Transport Certificate.
+*   OpenTitan shall provide full boot attestation measurements to allow Silicon
+    Owners to verify the boot chain configuration. The attestation chain shall
+    be anchored in the Silicon Owner's RoT PKI.
+*   OpenTitan shall provide a key manager implementation strongly bound to the
+    boot chain. Only a boot chain signed with the expected set of keys shall be
+    able to unlock stored keys/secrets.
+*   OpenTitan shall provide a key versioning scheme with support for key
+    migration bound to the firmware versioning and update implementation.
+
+## Use Cases
+
+The security goals of the project are derived from a list of target
+[use cases][use_cases]. The security goals are used to define OpenTitan's
+[threat model][threat_model], as well as functional and assurance security
+requirements. Such requirements influence the system architecture, as well as
+the certification strategy for silicon implementations.
+
+## Security Primitives
+
+All hardware security primitives adhere to the OpenTitan
+[comportable][comportable_ip] peripheral interface specification.
+Implementations for some of these components are available for reference and
+may not meet production or certification criteria yet.
+
+### [Entropy source][entropy_source]
+
+Digital wrapper for a NIST SP 800-90B compliant entropy source. An additional
+emulated entropy source implementation will be available for FPGA functional
+testing.
+
+### [CSRNG][csrng]
+
+Cryptographically Secure Random Number Generator (CSRNG) providing support for
+both deterministic (DRBG) and true random number generation (TRNG).
+
+The DRBG is implemented using the `CTR_DRBG` construction specified in
+NIST SP 800-90A.
+
+### [AES][aes]
+
+Advanced Encryption Standard (AES) supporting Encryption/Decryption using
+128/192/256 bit key sizes in the following cipher block modes:
+
+*   Electronic Codebook (ECB) mode,
+*   Cipher Block Chaining (CBC) mode,
+*   Cipher Feedback (CFB) mode with fixed data segment size of 128 bits,
+*   Output Feedback (OFB) mode, and
+*   Counter (CTR) mode.
+
+### [HMAC][hmac]
+
+HMAC with SHA-2 FIPS 180-4 compliant hash function, supporting both
+HMAC-SHA256 and SHA256 modes of operation.
+
+### [Key Manager][keymgr]
+
+Hardware backed symmetric key generation and storage providing key isolation
+from software.
+
+### [OTBN][otbn]
+
+Public key algorithm accelerator with support for bignum operations in hardware.
+
+### [Alert Handler][alert_handler]
+
+Aggregates alert signals from other system components designated as potential
+security threats, converting them to processor interrupts. It also supports
+alert policy assignments to handle alerts completely in hardware depending on
+the assigned severity.
+
+[aes]: ../../hw/ip/aes/README.md
+[alert_handler]: ../../hw/top_earlgrey/ip_autogen/alert_handler/README.md
+[comportable_ip]: ../contributing/hw/comportability/README.md
+[csrng]: ../../hw/ip/csrng/README.md
+[entropy_source]: ../../hw/ip/entropy_src/README.md
+[hmac]: ../../hw/ip/hmac/README.md
+[keymgr]: ../../hw/ip/keymgr/README.md
+[logical_security_model]: ./logical_security_model/README.md
+[implementation_guidelines]: ./implementation_guidelines/hardware/README.md
+[otbn]: ../../hw/ip/otbn/README.md
+[security_model]: ./specs/README.md
+[use_cases]: ../use_cases/README.md
+[threat_model]: ./threat_model/README.md
diff --git a/doc/security/_index.md b/doc/security/_index.md
deleted file mode 100644
index 4d5bcdc..0000000
--- a/doc/security/_index.md
+++ /dev/null
@@ -1,124 +0,0 @@
----
-title: "OpenTitan Security"
----
-
-## Overview
-
-OpenTitan's mission is to create a trustworthy, vendor-agnostic open source
-silicon Root of Trust (RoT) widely adopted across the industry. We do this by
-implementing strong logical security integrity guarantees in the hardware and
-firmware components, and restricting licensing of the OpenTitan trademark to
-those implementations conforming to OpenTitan standards.
-
-## [OpenTitan Security Model Specification][security_model]
-
-The [OpenTitan Security Model Specification][security_model] defines the logical
-security properties of the discrete IC. It covers device and software
-attestation, provisioning, secure boot, chip lifecycle, firmware update, chip
-identity, and chip ownership transfer.
-
-## [Logical Security Model][logical_security_model]
-
-The [OpenTitan Security Model][logical_security_model] provides a high level
-framework for device provisioning and run-time operations. It starts by
-enumerating the range of logical entities supported by the architecture, and
-their mapping into software stages. Runtime isolation properties and baseline
-identity concepts are introduced in this document.
-
-## [Secure Hardware Design Guidelines][implementation_guidelines]
-
-Silicon designs for security devices require special guidelines to protect the designs against myriad attacks.
-To that end, the team established [Secure Hardware Design Guidelines][implementation_guidelines] which are followed when developing OpenTitan security IP.
-
-## Functional Guarantees
-
-At the functional level OpenTitan aims to provide the following guarantees:
-
-*   Silicon Owners shall be able to deploy their own Root of Trust (RoT) Public
-    Key Infrastructure (PKI) after taking ownership of the device.
-*   Silicon Creators shall endorse the authenticity of the hardware. Endorsement
-    is contingent on the silicon adhering to the physical implementation
-    guidelines and standard requirements stipulated by the project. The
-    endorsement shall be measurable via a Transport Certificate.
-*   OpenTitan shall provide full boot attestation measurements to allow Silicon
-    Owners to verify the boot chain configuration. The attestation chain shall
-    be anchored in the Silicon Owner's RoT PKI.
-*   OpenTitan shall provide a key manager implementation strongly bound to the
-    boot chain. Only a boot chain signed with the expected set of keys shall be
-    able to unlock stored keys/secrets.
-*   OpenTitan shall provide a key versioning scheme with support for key
-    migration bound to the firmware versioning and update implementation.
-
-## Use Cases
-
-The security goals of the project are derived from a list of target use cases.
-See [OpenTitan's Use Cases][use_cases] for more details. The security goals are
-used to define OpenTitan's threat model, as well as functional and assurance
-security requirements. Such requirements influence the system architecture, as
-well as the certification strategy for silicon implementations.
-
-## Security Primitives
-
-All hardware security primitives adhere to the OpenTitan
-[comportable][comportable_ip] peripheral interface specification.
-Implementations for some of these components are available for reference and
-may not meet production or certification criteria yet.
-
-### [Entropy source][entropy_source]
-
-Digital wrapper for a NIST SP 800-90B compliant entropy source. An additional
-emulated entropy source implementation will be available for FPGA functional
-testing.
-
-### [CSRNG][csrng]
-
-Cryptographically Secure Random Number Generator (CSRNG) providing support for
-both deterministic (DRBG) and true random number generation (TRNG).
-
-The DRBG is implemented using the `CTR_DRBG` construction specified in
-NIST SP 800-90A.
-
-### [AES][aes]
-
-Advanced Encryption Standard (AES) supporting Encryption/Decryption using
-128/192/256 bit key sizes in the following cipher block modes:
-
-*   Electronic Codebook (ECB) mode,
-*   Cipher Block Chaining (CBC) mode,
-*   Cipher Feedback (CFB) mode with fixed data segment size of 128 bits,
-*   Output Feedback (OFB) mode, and
-*   Counter (CTR) mode.
-
-### [HMAC][hmac]
-
-HMAC with SHA-2 FIPS 180-4 compliant hash function, supporting both
-HMAC-SHA256 and SHA256 modes of operation.
-
-### [Key Manager][keymgr]
-
-Hardware backed symmetric key generation and storage providing key isolation
-from software.
-
-### [OTBN][otbn]
-
-Public key algorithm accelerator with support for bignum operations in hardware.
-
-### [Alert Handler][alert_handler]
-
-Aggregates alert signals from other system components designated as potential
-security threats, converting them to processor interrupts. It also supports
-alert policy assignments to handle alerts completely in hardware depending on
-the assigned severity.
-
-[aes]: {{< relref "hw/ip/aes/doc" >}}
-[alert_handler]: {{< relref "hw/top_earlgrey/ip_autogen/alert_handler/doc" >}}
-[comportable_ip]: {{< relref "doc/rm/comportability_specification" >}}
-[csrng]: {{< relref "hw/ip/csrng/doc" >}}
-[entropy_source]: {{< relref "hw/ip/entropy_src/doc" >}}
-[hmac]: {{< relref "hw/ip/hmac/doc" >}}
-[keymgr]: {{< relref "hw/ip/keymgr/doc" >}}
-[logical_security_model]: {{< relref "doc/security/logical_security_model" >}}
-[implementation_guidelines]: {{< relref "doc/security/implementation_guidelines/hardware" >}}
-[otbn]: {{< relref "hw/ip/otbn/doc" >}}
-[security_model]: {{< relref "doc/security/specs" >}}
-[use_cases]: {{< relref "doc/security/use_cases" >}}
diff --git a/doc/security/implementation_guidelines/README.md b/doc/security/implementation_guidelines/README.md
new file mode 100644
index 0000000..db2af7f
--- /dev/null
+++ b/doc/security/implementation_guidelines/README.md
@@ -0,0 +1,3 @@
+# Implementation Guidelines
+
+{{% sectionContent %}}
diff --git a/doc/security/implementation_guidelines/_index.md b/doc/security/implementation_guidelines/_index.md
deleted file mode 100644
index ca03df7..0000000
--- a/doc/security/implementation_guidelines/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Implementation Guidelines
----
-
-{{% sectionContent %}}
diff --git a/doc/security/implementation_guidelines/hardware/README.md b/doc/security/implementation_guidelines/hardware/README.md
new file mode 100644
index 0000000..96a49b7
--- /dev/null
+++ b/doc/security/implementation_guidelines/hardware/README.md
@@ -0,0 +1,401 @@
+# Secure Hardware Design Guidelines
+
+## Overview
+
+Silicon designs for security devices require special guidelines to protect the
+designs against myriad attacks. For OpenTitan, the universe of potential attacks
+is described in our threat model. In order to have the most robust defensive
+posture, a general approach to secure hardware design should rely on the
+concepts of (1) defense in depth, (2) consideration of recovery methods
+post-breach, and (3) thinking with an attacker mindset.
+
+In all cases, as designers, we need to think of equalizing the difficulty of any
+particular attack for the adversary. If a design has a distribution of attack
+vectors (sometimes called the "attack surface" or "attack surface area"), it is
+not the strength of the strongest defenses that is particularly of interest but
+rather the weakest, since these will be the most likely to be exploited by the
+adversary. For example, it's unlikely that an attacker will try to brute-force a
+system based on AES-128 encryption, as the difficulty level of such an attack is
+high, and our confidence in the estimate of the difficulty is also high. But, if
+the security of the AES-128 depends on a global secret, more mundane attacks
+like theft or bribery become more likely avenues for the adversary to exploit.
+
+Defense in depth means having multiple layers of defenses/controls acting
+independently. Classically in information security, these are grouped into three
+main categories: physical, technical and administrative[^1]. We map these into
+slightly different elements when considering secure hardware design:
+
+*   Physical security typically maps to sensors and shields, but also separation
+    of critical information into different locations on the die.
+*   Technical security includes techniques like encrypting data-at-rest,
+    scrambling buses for data-in-motion, and integrity checking for all kinds of
+    data.
+*   Administrative security encompasses architectural elements like permissions,
+    lifecycle states, and key splits (potentially also linked to physical
+    security).
+
+Consideration of recovery methods means assuming that some or all of the
+defenses will fail, with an eye to limiting the extent of the resulting system
+failure/compromise. If an adversary gains control over a sub-block, but cannot
+use this to escalate to full-chip control, we have succeeded. If control over a
+sub-block is detected, but an alert is generated that ultimately causes a device
+reset or other de-escalation sequence, we have created a recovery strategy. If
+the software is compromised but access to keys/secrets is prevented by hardware
+controls, we have succeeded. If compromise of secrets from a single device
+cannot be leveraged into attacks on other (or all) devices, again we have
+succeeded. If compromised devices can be identified and quarantined when
+enrolled into a larger system, then we have a successful recovery strategy.
+
+Thinking with an attacker mindset means "breaking the rules" or violating
+assumptions: what if two linked state machines no longer are "in sync" - how
+will they operate, and how can they recover? What happens if the adversary
+manipulates an internal value (fault injection)? What happens if the adversary
+can learn some or all of a secret value (side channel leakage)? This document
+will primarily try to give generic guidance for defense against the latter two
+attacks (fault injection, and side channel information leakage). It also
+discusses ways to either prevent attacks, mitigate them, or alert of their
+existence. Other attack vectors (especially software compromises or operational
+security failures) are not in the scope of this document, or will be addressed
+at a later stage.
+
+In general, when thinking of protecting against fault injection attacks, the
+designer should consider the consequences of any particular net/node being
+inverted or forced by an adversary. State of the art fault attacks can stimulate
+two nodes in close succession; robustness to this type of attack depends on the
+declared threat model. Designers need to be well aware of the power of an attack
+like SIFA [[15](#ref-15)], which can bypass "conventional" fault countermeasures (e.g.
+redundancy/detectors) and requires only modest numbers of traces.
+
+For increased resistance against side channel leakage (typically: power,
+electromagnetic radiation, or timing), designs in general should ensure that the
+creation or transmission of *secret material* is handled in such a way as to not
+work with "small" subsets of bits of sensitive information. Attacks like DPA are
+very powerful because they are able to "divide and conquer" an AES operation
+(regardless of key size) into its 8-bit S-Boxes and enumerate all 256 possible
+values to evaluate hypotheses. Evaluating/processing information in 32-bit
+quanta (or larger) will make these kinds of enumerations much more difficult;
+operating on a single bit at a time makes them almost trivial.
+
+Below we will go deeper into these recommendations for general design practices.
+Individual module guidance for particular IP (processor, AES, SHA, etc) will be
+handled in addenda to this document.
+
+## General Module Level Design Guidance
+
+These guidelines are for sub-block / module level design. System architecture,
+identity management, and protocol design are outside the scope of this document,
+but may create some dependencies here. For general reading, the slides of [[10](#ref-10)]
+are considered a useful companion to these guidelines.
+
+### **Recommendation 1**: Identify sensitive/privileged operations
+
+Identify any sensitive/privileged operations performed by the module
+(non-exhaustive list of examples: working with secret keys, writing to OTP,
+potentially writing to flash, enabling debug functionality, lifting/releasing
+access restrictions, changing lifecycle state)
+
+1.  Having these operations documented helps to analyze the potential issues of
+    any attack discussed below.
+2.  Subsequent design/verification reviews can use these sensitive operations as
+    focus areas or even coverage points.
+
+### **Recommendation 2**: Side-channel leakage considerations
+
+Consider side-channel leakage of any secret information (side channels include
+timing, power, EM radiation, caches, and micro-architectural state, among
+others)
+
+1.  Process secret information in at least a 32-bit wide datapath
+2.  Use fixed/constant time operations when handling secrets (see [[6](#ref-6)] and [[11](#ref-11)])
+3.  Don't branch/perform conditional operations based on secret values
+4.  Incorporate temporal randomness (example: add delay cycles based on LFSR
+    around critical operations, see [[9](#ref-9)])
+5.  Cryptographic operations should incorporate entropy (via masking/blinding,
+    see [[9](#ref-9)]), especially if the key is long-lived, or a global/class-wide value.
+    Short-lived keys may not require this, but careful study of the information
+    leakage rate is necessary
+6.  Noise generation - run other "chaff" switching actions in parallel with
+    sensitive calculations, if power budget permits (see [[9](#ref-9)])
+7.  Secrets should not be stored in a processor cache (see [[3](#ref-3)])
+8.  Speculative execution in a processor can lead to leakage of secrets via
+    micro-architectural state (see [[4](#ref-4)]/[[5](#ref-5)])
+9.  When clearing secrets, use an LFSR to wipe values to prevent a Hamming
+    weight leakage that would occur if clearing to zero. For secrets stored in
+    multiple shares, use different permutations (or separate LFSRs) to perform
+    the clearing of the shares.
+
+### **Recommendation 3**: Fault injection countermeasures
+
+Consider defenses to fault injection / glitching attacks (survey and overview of
+attacks, see [[12](#ref-12)] and [[13](#ref-13)])
+
+1.  Initially assume that the adversary can glitch any node arbitrarily, and
+    determine the resulting worst-case scenario. This is a very conservative
+    approach and might lead to over-pessimism, but serves to highlight potential
+    issues. Then, to ease implementation burden, assume the adversary can glitch
+    to all-1's or all-0's (since these are considered "easier" to reach), and
+    that reset can be asserted semi-arbitrarily.
+2.  Use parity/ECC on memories and data paths (note here that ECC is not true
+    integrity, have to use hash to prevent forgery, see [[1](#ref-1)]). For memories, ECC
+    is helpful to protect instruction streams or values that can cause
+    "branching control flows" that redirect execution flow. Parity is
+    potentially helpful if detection of corruption is adequate (though
+    double-glitch fault injection can fool parity, so Hsiao or other
+    detect-2-error codes can be used, even without correction circuitry
+    implemented). When committing to an irrevocable action (e.g. burning into
+    OTP, unlocking part of the device/increasing permissions), ECC is probably
+    more appropriate.
+    1.  When selecting a specific ECC implementation, the error detection
+        properties are likely more important than error correction (assuming
+        memory lifetime retention/wear are not considered). For a good example
+        of how to consider the effectiveness of error correction, see
+        [this PR comment](https://github.com/lowRISC/opentitan/pull/3899#issuecomment-716799810).
+3.  State machines:
+    1.  Have a minimum Hamming distance for state machine transitions, to make
+        single bit faults non-effective
+    2.  Use a
+        [sparsely populated state encoding](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py),
+        with all others marked invalid - see 11.1 about optimization concerns
+        when doing this though
+    3.  All states could have the same Hamming weight, then can constantly check
+        for this property (or use ECC-type coding on state variable and check
+        this)
+    4.  If waiting for a counter to expire to transition to the next state,
+        better if the terminal count that causes the transition is not
+        all-0/all-1. One could use an LFSR instead of a binary counter, but
+        debugging this can be a bit painful then
+4.  Maintain value-and-its-complement throughout datapath (sometimes called
+    "dual rail" logic), especially if unlocking/enabling something sensitive,
+    and continually check for validity/consistency of representation
+5.  Incorporate temporal randomness where possible (example: add delay cycles
+    based on LFSR around sensitive operations)
+6.  Run-it-twice and compare results for sensitive calculations
+7.  Redundancy - keep/store multiple copies of sensitive checks/data
+8.  For maximum sensitivity, compare combinational and sequential paths with
+    hair-trigger/one-shot latch of miscompare
+9.  Empty detection for OTP/flash (if needed, but especially for lifecycle
+    determination)
+10. Avoid local resets / prefer larger reset domains, since a glitch on this
+    larger reset keeps more of the design "in sync." But, consider the
+    implications of any block with more than one reset domain (see also 9.1).
+11. Similar to the "mix in" idea of 4.4, in any case where multiple contributing
+    "votes" are going to an enable/unlock decision, consider mixing them into
+    some cryptographic structure over time that will be diverted from its path
+    by attempts to glitch each vote. (Note: if the final outcome of this is
+    simply a wide-compare that produces a single-bit final unlock/enable vote
+    then this is only marginally helpful - since that vote is now the glitch
+    target. Finding a way to bind the final cryptographic result to the vote is
+    preferred, but potentially very difficult / impossible, depending on the
+    situation.)
+12. When checking/creating a signal to
+    <span style="text-decoration:underline;">permit</span> some sensitive
+    operation, prefer that the checking logic is maximally volatile (e.g.
+    performs a lot of the calculation in a single cycle after a register), such
+    that a glitch prevents the operation. Whereas, when checking to
+    <span style="text-decoration:underline;">deny</span> a sensitive operation,
+    prefer that the checking logic is minimally volatile (is directly following
+    a register with minimal combinational logic), such that a glitch will be
+    recovered on the next clock and the denial will be continued/preserved.
+13. CFI (control flow integrity) hardware can help protect a processor /
+    programmable peripheral from some types of glitch attacks. This topic is
+    very involved and beyond the scope of these guidelines, consult [[2](#ref-2)]
+    for an introduction to previous techniques.
+14. Analog sensors (under/over-voltage, laser light, mesh breach, among others)
+    can be used to generate SoC-level alerts and/or inhibit sensitive
+    operations. Many of these sensors require calibration/trimming, or require
+    hysteresis circuits to prevent false-positives, so they may not be usable in
+    fast-reacting situations.
+15. Running an operation (e.g. AES or KMAC) to completion, even with a detected
+    fault, is sometimes useful since it suppresses information for the adversary
+    about the success/failure of the attempted fault, and minimizes any timing
+    side channel. However, for some operations (e.g. ECDSA sign), operations on
+    faulty inputs can have catastrophic consequences. These guidelines cannot
+    recommend a default-safe posture, but each decision about handling detected
+    faults should be carefully considered.
+16. For request-acknowledge interfaces, monitor the acknowledge line for
+    spurious pulses at all times (not only when pending request) and use this
+    as a glitch/fault detector to escalate locally and/or generate alerts.
+17. When arbitrating between two or more transaction sources with different
+    privilege/access levels, consider how to protect a request from one source
+    being glitched/forged to masquerade as being sourced from another
+    higher-privilege source (for example, to return side-loaded
+    hardware-visible-only data via a software read path). At a minimum,
+    redundant arbitration and multiple-bit encoding of the arbitration "winner"
+    can help to mitigate this type of attack.
+
+### **Recommendation 4**: Handling of secrets
+
+1.  Diversify types/sources of secrets (e.g. use combination of RTL constants +
+    OTP + flash) to prevent a single compromise from being effective
+2.  Rather than "check an unlock value directly" - use a hash function with a
+    user-supplied input, and check the output of the hash matches. This way the
+    unlock value is not contained in the netlist.
+3.  Qualify operations with allowed lifecycle state (even if redundant with
+    other checks)
+4.  Where possible, mix in operating modes to calculation of derived secrets to
+    create parallel/non-substitutable operating/keying domains. (i.e. mixing in
+    devmode, lifecycle state)
+    1.  If defenses can be bypassed for debugging/recovery, considering mixing
+        in activation vector/bypass bits of defenses as well, consider this like
+        small-scale attestation of device state
+5.  Encrypt (or at least scramble) any secrets stored at-rest in flash/OTP, to
+    reduce risks of static/offline inspection.
+
+### **Recommendation 5**: Alerts
+
+1.  Generate alerts on any detected anomaly (need to define what
+    priority/severity should be assigned)
+2.  Where possible, prefer to take a local action (clearing/randomizing state,
+    cease processing) in addition to generating the alert
+
+### **Recommendation 6**: Safe default values
+
+1.  All case statements, if statements, and ternaries should consider what the
+    safest default value is. Having an "invalid" state/value is nice to have for
+    this purpose, but isn't always possible.
+2.  Operate in a general policy/philosophy of starting with lowest allowed
+    privilege and augmenting by approvals/unlocks.
+3.  Implement enforcement of inputs on CSRs - qualify/force data attempted to be
+    written based on lifecycle state, peripheral state, or other values. The
+    designer must determine the safest remapping, e.g. write --> read, read -->
+    nop, write --> nop and so forth. Blanket implementation of input enforcement
+    complicates verification, so this style of design should be chosen only
+    where the inputs are particularly sensitive (requests to unlock, privilege
+    increase requests, debug mode enables, etc).
+
+### **Recommendation 7**: DFT issues
+
+1.  Entry and exit from scan mode should cause a reset to prevent insertion or
+    exfiltration of sensitive values
+2.  Ensure that when in production (e.g. not in lab debug) environments, scan
+    chains are disabled
+3.  Processor debug paths (via JTAG) may need to be disabled in production modes
+4.  Beware of self-repair or redundant-row/columns schemes for memories (SRAM
+    and OTP), as they can be exploited to misdirect reads to
+    adversary-controlled locations
+
+### **Recommendation 8**: Power management issues
+
+1.  If module is not in an always-on power domain, consider that a sleep/wake
+    sequence can be used to force a re-derivation of secrets needed in the
+    module, as many times as desired by the adversary
+2.  Fine-grained clock gating should never be used for any module that processes
+    secret data, only coarse-grained (module-level) gating is acceptable. (Fine
+    grained gates essentially compute `clock_gate = D ^ Q` which often acts as
+    an SCA "amplifier").
+
+### **Recommendation 9**: "Synchronization" (consistency) issues
+
+1.  If a module interacts with other modules in a stateful way (think of two
+    data-transfer counters moving in ~lockstep, but the counts are not sent back
+    and forth for performance optimization), what happens if:
+    1.  One side is reset and the other is not
+    2.  One side is clock-gated and the other is not
+    3.  One side is power-gated and the other is not
+    4.  The counter on one side is glitched
+2.  Generally these kind of blind lockstep situations should be avoided where
+    possible, and current module/interface status should exchanged in both
+    directions and constantly checked for validity/consistency
+
+### **Recommendation 10**: Recovery mechanism considerations
+
+1.  What happens if a security mechanism fails? (Classic problem of this variety
+    is on-die sensors being too sensitive and resetting the chip) Traditionally,
+    fuses can disable some mechanisms if they are faulty.
+2.  Could an adversary exploit a recovery mechanism? (If a sensor can be
+    fuse-disabled, wouldn't the adversary just do that? See 4.4 above.)
+
+### **Recommendation 11**: Optimization concerns
+
+1.  Sometimes synthesis will optimize away redundant (but necessary for
+    security) logic - `dont_touch` or `size_only` attributes may sometimes be
+    needed, or even more aggressive preservation strategies. Example: when using
+    the sparse FSM encoding, use the `prim_flop` component for the state vector
+    register.
+2.  Value-and-complement strategies can also be optimized away, or partially
+    disconnected such that only half of the datapath is contributing to the
+    logic, or a single register with both Q & Qbar outputs becomes the source of
+    both values to save area.
+3.  Retiming around pipeline registers can create DPA issues, due to inadvertent
+    combination of shares, or intra-cycle glitchy evaluation. For DPA-resistant
+    logic, explicitly declare functions and registers using `prim_*` components,
+    and make sure that pipeline retiming is not enabled in synthesis.
+
+### **Recommendation 12**: Entropy concerns
+
+1.  Verify that all nonces are truly only used once
+2.  If entropy is broadcast, verify list of consumers and arbitration scheme to
+    prevent reuse / duplicate use of entropy in sensitive calculations
+3.  Seeds for local LFSRs need to be unique/diversified
+
+### **Recommendation 13**: Global secrets
+
+1.  Avoid if at all possible
+2.  If not possible, have a process to generate/re-generate them; make sure this
+    process is used/tested many times before final netlist; process must be
+    repeatable/deterministic given some set of inputs
+3.  If architecturally feasible, install a device-specific secret to override
+    the global secret once boot-strapped (and disable the global secret)
+
+### **Recommendation 14**: Sensors
+
+1.  Sensors need to be adjusted/tweaked so that they actually fire. It is
+    challenging to set the sensors at levels that detect "interesting"
+    glitches/environmental effects, but don't fire constantly or cause yield
+    issues. Security team should work with the silicon supplier to determine the
+    best course of action here.
+2.  Sensor configuration / calibration data should be integrity-protected.
+
+## References and further reading
+
+[<span id="ref-1">1</span>]: Overview of checksums and hashes -
+https://cybergibbons.com/reverse-engineering-2/checksums-hashes-and-security/
+
+[<span id="ref-2">2</span>]: A Survey of hardware-based Control Flow Integrity -
+https://arxiv.org/pdf/1706.07257.pdf
+
+[<span id="ref-3">3</span>]: Cache-timing attacks on AES -
+https://cr.yp.to/antiforgery/cachetiming-20050414.pdf
+
+[<span id="ref-4">4</span>]: Meltdown: Reading Kernel Memory from User Space -
+https://meltdownattack.com/meltdown.pdf
+
+[<span id="ref-5">5</span>]: Spectre Attacks: Exploiting Speculative Execution -
+https://spectreattack.com/spectre.pdf
+
+[<span id="ref-6">6</span>]: Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other
+Systems - https://www.rambus.com/wp-content/uploads/2015/08/TimingAttacks.pdf
+
+[<span id="ref-7">7</span>]: Differential Power Analysis -
+https://paulkocher.com/doc/DifferentialPowerAnalysis.pdf
+
+[<span id="ref-8">8</span>]: SoC it to EM: electromagnetic side-channel attacks on a complex
+system-on-chip - https://www.iacr.org/archive/ches2015/92930599/92930599.pdf
+
+[<span id="ref-9">9</span>]: Introduction To differential power analysis -
+https://link.springer.com/content/pdf/10.1007/s13389-011-0006-y.pdf
+
+[<span id="ref-10">10</span>]: Principles of Secure Processor Architecture Design -
+https://caslab.csl.yale.edu/tutorials/hpca2019/ and
+https://caslab.csl.yale.edu/tutorials/hpca2019/tutorial_principles_sec_arch_20190217.pdf
+
+[<span id="ref-11">11</span>]: Time Protection - https://ts.data61.csiro.au/projects/TS/timeprotection/
+
+[<span id="ref-12">12</span>]: Fault Attacks on Secure Embedded Software: Threats, Design and Evaluation -
+https://arxiv.org/pdf/2003.10513.pdf
+
+[<span id="ref-13">13</span>]: The Sorcerer's Apprentice Guide to Fault Attacks -
+https://eprint.iacr.org/2004/100.pdf
+
+[<span id="ref-14">14</span>]: Fault Mitigation Patterns -
+https://www.riscure.com/uploads/2020/05/Riscure_Whitepaper_Fault_Mitigation_Patterns_final.pdf
+
+[<span id="ref-15">15</span>]: SIFA: Exploiting Ineffective Fault Inductions on Symmetric Cryptography -
+https://eprint.iacr.org/2018/071.pdf
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+[^1]: In other OpenTitan documents, the combination of technical and
+    administrative defense are often referred to as "logical security"
diff --git a/doc/security/implementation_guidelines/hardware/index.md b/doc/security/implementation_guidelines/hardware/index.md
deleted file mode 100644
index aea9e31..0000000
--- a/doc/security/implementation_guidelines/hardware/index.md
+++ /dev/null
@@ -1,403 +0,0 @@
----
-title: "Secure Hardware Design Guidelines"
----
-
-## Overview
-
-Silicon designs for security devices require special guidelines to protect the
-designs against myriad attacks. For OpenTitan, the universe of potential attacks
-is described in our threat model. In order to have the most robust defensive
-posture, a general approach to secure hardware design should rely on the
-concepts of (1) defense in depth, (2) consideration of recovery methods
-post-breach, and (3) thinking with an attacker mindset.
-
-In all cases, as designers, we need to think of equalizing the difficulty of any
-particular attack for the adversary. If a design has a distribution of attack
-vectors (sometimes called the "attack surface" or "attack surface area"), it is
-not the strength of the strongest defenses that is particularly of interest but
-rather the weakest, since these will be the most likely to be exploited by the
-adversary. For example, it's unlikely that an attacker will try to brute-force a
-system based on AES-128 encryption, as the difficulty level of such an attack is
-high, and our confidence in the estimate of the difficulty is also high. But, if
-the security of the AES-128 depends on a global secret, more mundane attacks
-like theft or bribery become more likely avenues for the adversary to exploit.
-
-Defense in depth means having multiple layers of defenses/controls acting
-independently. Classically in information security, these are grouped into three
-main categories: physical, technical and administrative[^1]. We map these into
-slightly different elements when considering secure hardware design:
-
-*   Physical security typically maps to sensors and shields, but also separation
-    of critical information into different locations on the die.
-*   Technical security includes techniques like encrypting data-at-rest,
-    scrambling buses for data-in-motion, and integrity checking for all kinds of
-    data.
-*   Administrative security encompasses architectural elements like permissions,
-    lifecycle states, and key splits (potentially also linked to physical
-    security).
-
-Consideration of recovery methods means assuming that some or all of the
-defenses will fail, with an eye to limiting the extent of the resulting system
-failure/compromise. If an adversary gains control over a sub-block, but cannot
-use this to escalate to full-chip control, we have succeeded. If control over a
-sub-block is detected, but an alert is generated that ultimately causes a device
-reset or other de-escalation sequence, we have created a recovery strategy. If
-the software is compromised but access to keys/secrets is prevented by hardware
-controls, we have succeeded. If compromise of secrets from a single device
-cannot be leveraged into attacks on other (or all) devices, again we have
-succeeded. If compromised devices can be identified and quarantined when
-enrolled into a larger system, then we have a successful recovery strategy.
-
-Thinking with an attacker mindset means "breaking the rules" or violating
-assumptions: what if two linked state machines no longer are "in sync" - how
-will they operate, and how can they recover? What happens if the adversary
-manipulates an internal value (fault injection)? What happens if the adversary
-can learn some or all of a secret value (side channel leakage)? This document
-will primarily try to give generic guidance for defense against the latter two
-attacks (fault injection, and side channel information leakage). It also
-discusses ways to either prevent attacks, mitigate them, or alert of their
-existence. Other attack vectors (especially software compromises or operational
-security failures) are not in the scope of this document, or will be addressed
-at a later stage.
-
-In general, when thinking of protecting against fault injection attacks, the
-designer should consider the consequences of any particular net/node being
-inverted or forced by an adversary. State of the art fault attacks can stimulate
-two nodes in close succession; robustness to this type of attack depends on the
-declared threat model. Designers need to be well aware of the power of an attack
-like SIFA [[15](#ref-15)], which can bypass "conventional" fault countermeasures (e.g.
-redundancy/detectors) and requires only modest numbers of traces.
-
-For increased resistance against side channel leakage (typically: power,
-electromagnetic radiation, or timing), designs in general should ensure that the
-creation or transmission of *secret material* is handled in such a way as to not
-work with "small" subsets of bits of sensitive information. Attacks like DPA are
-very powerful because they are able to "divide and conquer" an AES operation
-(regardless of key size) into its 8-bit S-Boxes and enumerate all 256 possible
-values to evaluate hypotheses. Evaluating/processing information in 32-bit
-quanta (or larger) will make these kinds of enumerations much more difficult;
-operating on a single bit at a time makes them almost trivial.
-
-Below we will go deeper into these recommendations for general design practices.
-Individual module guidance for particular IP (processor, AES, SHA, etc) will be
-handled in addenda to this document.
-
-## General Module Level Design Guidance
-
-These guidelines are for sub-block / module level design. System architecture,
-identity management, and protocol design are outside the scope of this document,
-but may create some dependencies here. For general reading, the slides of [[10](#ref-10)]
-are considered a useful companion to these guidelines.
-
-### **Recommendation 1**: Identify sensitive/priviledged operations
-
-Identify any sensitive/privileged operations performed by the module
-(non-exhaustive list of examples: working with secret keys, writing to OTP,
-potentially writing to flash, enabling debug functionality, lifting/releasing
-access restrictions, changing lifecycle state)
-
-1.  Having these operations documented helps to analyze the potential issues of
-    any attack discussed below.
-2.  Subsequent design/verification reviews can use these sensitive operations as
-    focus areas or even coverage points.
-
-### **Recommendation 2**: Side-channel leakage considerations
-
-Consider side-channel leakage of any secret information (side channels include
-timing, power, EM radiation, caches, and micro-architectural state, among
-others)
-
-1.  Process secret information in at least a 32-bit wide datapath
-2.  Use fixed/constant time operations when handling secrets (see [[6](#ref-6)] and [[11](#ref-11)])
-3.  Don't branch/perform conditional operations based on secret values
-4.  Incorporate temporal randomness (example: add delay cycles based on LFSR
-    around critical operations, see [[9](#ref-9)])
-5.  Cryptographic operations should incorporate entropy (via masking/blinding,
-    see [[9](#ref-9)]), especially if the key is long-lived, or a global/class-wide value.
-    Short-lived keys may not require this, but careful study of the information
-    leakage rate is necessary
-6.  Noise generation - run other "chaff" switching actions in parallel with
-    sensitive calculations, if power budget permits (see [[9](#ref-9)])
-7.  Secrets should not be stored in a processor cache (see [[3](#ref-3)])
-8.  Speculative execution in a processor can lead to leakage of secrets via
-    micro-architectural state (see [[4](#ref-4)]/[[5](#ref-5)])
-9.  When clearing secrets, use an LFSR to wipe values to prevent a Hamming
-    weight leakage that would occur if clearing to zero. For secrets stored in
-    multiple shares, use different permutations (or separate LFSRs) to perform
-    the clearing of the shares.
-
-### **Recommendation 3**: Fault injection countermeasures
-
-Consider defenses to fault injection / glitching attacks (survey and overview of
-attacks, see [[12](#ref-12)] and [[13](#ref-13)])
-
-1.  Initially assume that the adversary can glitch any node arbitrarily, and
-    determine the resulting worst-case scenario. This is a very conservative
-    approach and might lead to over-pessimism, but serves to highlight potential
-    issues. Then, to ease implementation burden, assume the adversary can glitch
-    to all-1's or all-0's (since these are considered "easier" to reach), and
-    that reset can be asserted semi-arbitrarily.
-2.  Use parity/ECC on memories and data paths (note here that ECC is not true
-    integrity, have to use hash to prevent forgery, see [[1](#ref-1)]). For memories, ECC
-    is helpful to protect instruction streams or values that can cause
-    "branching control flows" that redirect execution flow. Parity is
-    potentially helpful if detection of corruption is adequate (though
-    double-glitch fault injection can fool parity, so Hsiao or other
-    detect-2-error codes can be used, even without correction circuitry
-    implemented). When committing to an irrevocable action (e.g. burning into
-    OTP, unlocking part of the device/increasing permissions), ECC is probably
-    more appropriate.
-    1.  When selecting a specific ECC implementation, the error detection
-        properties are likely more important than error correction (assuming
-        memory lifetime retention/wear are not considered). For a good example
-        of how to consider the effectiveness of error correction, see
-        [this PR comment](https://github.com/lowRISC/opentitan/pull/3899#issuecomment-716799810).
-3.  State machines:
-    1.  Have a minimum Hamming distance for state machine transitions, to make
-        single bit faults non-effective
-    2.  Use a
-        [sparsely populated state encoding](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py),
-        with all others marked invalid - see 11.1 about optimization concerns
-        when doing this though
-    3.  All states could have the same Hamming weight, then can constantly check
-        for this property (or use ECC-type coding on state variable and check
-        this)
-    4.  If waiting for a counter to expire to transition to the next state,
-        better if the terminal count that causes the transition is not
-        all-0/all-1. One could use an LFSR instead of a binary counter, but
-        debugging this can be a bit painful then
-4.  Maintain value-and-its-complement throughout datapath (sometimes called
-    "dual rail" logic), especially if unlocking/enabling something sensitive,
-    and continually check for validity/consistency of representation
-5.  Incorporate temporal randomness where possible (example: add delay cycles
-    based on LFSR around sensitive operations)
-6.  Run-it-twice and compare results for sensitive calculations
-7.  Redundancy - keep/store multiple copies of sensitive checks/data
-8.  For maximum sensitivity, compare combinational and sequential paths with
-    hair-trigger/one-shot latch of miscompare
-9.  Empty detection for OTP/flash (if needed, but especially for lifecycle
-    determination)
-10. Avoid local resets / prefer larger reset domains, since a glitch on this
-    larger reset keeps more of the design "in sync." But, consider the
-    implications of any block with more than one reset domain (see also 9.1).
-11. Similar to the "mix in" idea of 4.4, in any case where multiple contributing
-    "votes" are going to an enable/unlock decision, consider mixing them into
-    some cryptographic structure over time that will be diverted from its path
-    by attempts to glitch each vote. (Note: if the final outcome of this is
-    simply a wide-compare that produces a single-bit final unlock/enable vote
-    then this is only marginally helpful - since that vote is now the glitch
-    target. Finding a way to bind the final cryptographic result to the vote is
-    preferred, but potentially very difficult / impossible, depending on the
-    situation.)
-12. When checking/creating a signal to
-    <span style="text-decoration:underline;">permit</span> some sensitive
-    operation, prefer that the checking logic is maximally volatile (e.g.
-    performs a lot of the calculation in a single cycle after a register), such
-    that a glitch prevents the operation. Whereas, when checking to
-    <span style="text-decoration:underline;">deny</span> a sensitive operation,
-    prefer that the checking logic is minimally volatile (is directly following
-    a register with minimal combinational logic), such that a glitch will be
-    recovered on the next clock and the denial will be continued/preserved.
-13. CFI (control flow integrity) hardware can help protect a processor /
-    programmable peripheral from some types of glitch attacks. This topic is
-    very involved and beyond the scope of these guidelines, consult [[2](#ref-2)]
-    for an introduction to previous techniques.
-14. Analog sensors (under/over-voltage, laser light, mesh breach, among others)
-    can be used to generate SoC-level alerts and/or inhibit sensitive
-    operations. Many of these sensors require calibration/trimming, or require
-    hysteresis circuits to prevent false-positives, so they may not be usable in
-    fast-reacting situations.
-15. Running an operation (e.g. AES or KMAC) to completion, even with a detected
-    fault, is sometimes useful since it suppresses information for the adversary
-    about the success/failure of the attempted fault, and minimizes any timing
-    side channel. However, for some operations (e.g. ECDSA sign), operations on
-    faulty inputs can have catastrophic consequences. These guidelines cannot
-    recommend a default-safe posture, but each decision about handling detected
-    faults should be carefully considered.
-16. For request-acknowledge interfaces, monitor the acknowledge line for
-    spurious pulses at all times (not only when pending request) and use this
-    as a glitch/fault detector to escalate locally and/or generate alerts.
-17. When arbitrating between two or more transaction sources with different
-    privilege/access levels, consider how to protect a request from one source
-    being glitched/forged to masquerade as being sourced from another
-    higher-privilege source (for example, to return side-loaded
-    hardware-visible-only data via a software read path). At a minimum,
-    redundant arbitration and multiple-bit encoding of the arbitration "winner"
-    can help to mitigate this type of attack.
-
-### **Recommendation 4**: Handling of secrets
-
-1.  Diversify types/sources of secrets (e.g. use combination of RTL constants +
-    OTP + flash) to prevent a single compromise from being effective
-2.  Rather than "check an unlock value directly" - use a hash function with a
-    user-supplied input, and check the output of the hash matches. This way the
-    unlock value is not contained in the netlist.
-3.  Qualify operations with allowed lifecycle state (even if redundant with
-    other checks)
-4.  Where possible, mix in operating modes to calculation of derived secrets to
-    create parallel/non-substitutable operating/keying domains. (i.e. mixing in
-    devmode, lifecycle state)
-    1.  If defenses can be bypassed for debugging/recovery, considering mixing
-        in activation vector/bypass bits of defenses as well, consider this like
-        small-scale attestation of device state
-5.  Encrypt (or at least scramble) any secrets stored at-rest in flash/OTP, to
-    reduce risks of static/offline inspection.
-
-### **Recommendation 5**: Alerts
-
-1.  Generate alerts on any detected anomaly (need to define what
-    priority/severity should be assigned)
-2.  Where possible, prefer to take a local action (clearing/randomizing state,
-    cease processing) in addition to generating the alert
-
-### **Recommendation 6**: Safe default values
-
-1.  All case statements, if statements, and ternaries should consider what the
-    safest default value is. Having an "invalid" state/value is nice to have for
-    this purpose, but isn't always possible.
-2.  Operate in a general policy/philosophy of starting with lowest allowed
-    privilege and augmenting by approvals/unlocks.
-3.  Implement enforcement of inputs on CSRs - qualify/force data attempted to be
-    written based on lifecycle state, peripheral state, or other values. The
-    designer must determine the safest remapping, e.g. write --> read, read -->
-    nop, write --> nop and so forth. Blanket implementation of input enforcement
-    complicates verification, so this style of design should be chosen only
-    where the inputs are particularly sensitive (requests to unlock, privilege
-    increase requests, debug mode enables, etc).
-
-### **Recommendation 7**: DFT issues
-
-1.  Entry and exit from scan mode should cause a reset to prevent insertion or
-    exfiltration of sensitive values
-2.  Ensure that when in production (e.g. not in lab debug) environments, scan
-    chains are disabled
-3.  Processor debug paths (via JTAG) may need to be disabled in production modes
-4.  Beware of self-repair or redundant-row/columns schemes for memories (SRAM
-    and OTP), as they can be exploited to misdirect reads to
-    adversary-controlled locations
-
-### **Recommendation 8**: Power management issues
-
-1.  If module is not in an always-on power domain, consider that a sleep/wake
-    sequence can be used to force a re-derivation of secrets needed in the
-    module, as many times as desired by the adversary
-2.  Fine-grained clock gating should never be used for any module that processes
-    secret data, only coarse-grained (module-level) gating is acceptable. (Fine
-    grained gates essentially compute `clock_gate = D ^ Q` which often acts as
-    an SCA "amplifier").
-
-### **Recommendation 9**: "Synchronization" (consistency) issues
-
-1.  If a module interacts with other modules in a stateful way (think of two
-    data-transfer counters moving in ~lockstep, but the counts are not sent back
-    and forth for performance optimization), what happens if:
-    1.  One side is reset and the other is not
-    2.  One side is clock-gated and the other is not
-    3.  One side is power-gated and the other is not
-    4.  The counter on one side is glitched
-2.  Generally these kind of blind lockstep situations should be avoided where
-    possible, and current module/interface status should exchanged in both
-    directions and constantly checked for validity/consistency
-
-### **Recommendation 10**: Recovery mechanism considerations
-
-1.  What happens if a security mechanism fails? (Classic problem of this variety
-    is on-die sensors being too sensitive and resetting the chip) Traditionally,
-    fuses can disable some mechanisms if they are faulty.
-2.  Could an adversary exploit a recovery mechanism? (If a sensor can be
-    fuse-disabled, wouldn't the adversary just do that? See 4.4 above.)
-
-### **Recommendation 11**: Optimization concerns
-
-1.  Sometimes synthesis will optimize away redundant (but necessary for
-    security) logic - `dont_touch` or `size_only` attributes may sometimes be
-    needed, or even more aggressive preservation strategies. Example: when using
-    the sparse FSM encoding, use the `prim_flop` component for the state vector
-    register.
-2.  Value-and-complement strategies can also be optimized away, or partially
-    disconnected such that only half of the datapath is contributing to the
-    logic, or a single register with both Q & Qbar outputs becomes the source of
-    both values to save area.
-3.  Retiming around pipeline registers can create DPA issues, due to inadvertent
-    combination of shares, or intra-cycle glitchy evaluation. For DPA-resistant
-    logic, explicitly declare functions and registers using `prim_*` components,
-    and make sure that pipeline retiming is not enabled in synthesis.
-
-### **Recommendation 12**: Entropy concerns
-
-1.  Verify that all nonces are truly only used once
-2.  If entropy is broadcast, verify list of consumers and arbitration scheme to
-    prevent reuse / duplicate use of entropy in sensitive calculations
-3.  Seeds for local LFSRs need to be unique/diversified
-
-### **Recommendation 13**: Global secrets
-
-1.  Avoid if at all possible
-2.  If not possible, have a process to generate/re-generate them; make sure this
-    process is used/tested many times before final netlist; process must be
-    repeatable/deterministic given some set of inputs
-3.  If architecturally feasible, install a device-specific secret to override
-    the global secret once boot-strapped (and disable the global secret)
-
-### **Recommendation 14**: Sensors
-
-1.  Sensors need to be adjusted/tweaked so that they actually fire. It is
-    challenging to set the sensors at levels that detect "interesting"
-    glitches/environmental effects, but don't fire constantly or cause yield
-    issues. Security team should work with the silicon supplier to determine the
-    best course of action here.
-2.  Sensor configuration / calibration data should be integrity-protected.
-
-## References and further reading
-
-[<span id="ref-1">1</span>]: Overview of checksums and hashes -
-https://cybergibbons.com/reverse-engineering-2/checksums-hashes-and-security/
-
-[<span id="ref-2">2</span>]: A Survey of hardware-based Control Flow Integrity -
-https://arxiv.org/pdf/1706.07257.pdf
-
-[<span id="ref-3">3</span>]: Cache-timing attacks on AES -
-https://cr.yp.to/antiforgery/cachetiming-20050414.pdf
-
-[<span id="ref-4">4</span>]: Meltdown: Reading Kernel Memory from User Space -
-https://meltdownattack.com/meltdown.pdf
-
-[<span id="ref-5">5</span>]: Spectre Attacks: Exploiting Speculative Execution -
-https://spectreattack.com/spectre.pdf
-
-[<span id="ref-6">6</span>]: Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other
-Systems - https://www.rambus.com/wp-content/uploads/2015/08/TimingAttacks.pdf
-
-[<span id="ref-7">7</span>]: Differential Power Analysis -
-https://paulkocher.com/doc/DifferentialPowerAnalysis.pdf
-
-[<span id="ref-8">8</span>]: SoC it to EM: electromagnetic side-channel attacks on a complex
-system-on-chip - https://www.iacr.org/archive/ches2015/92930599/92930599.pdf
-
-[<span id="ref-9">9</span>]: Introduction To differential power analysis -
-https://link.springer.com/content/pdf/10.1007/s13389-011-0006-y.pdf
-
-[<span id="ref-10">10</span>]: Principles of Secure Processor Architecture Design -
-https://caslab.csl.yale.edu/tutorials/hpca2019/ and
-https://caslab.csl.yale.edu/tutorials/hpca2019/tutorial_principles_sec_arch_20190217.pdf
-
-[<span id="ref-11">11</span>]: Time Protection - https://ts.data61.csiro.au/projects/TS/timeprotection/
-
-[<span id="ref-12">12</span>]: Fault Attacks on Secure Embedded Software: Threats, Design and Evaluation -
-https://arxiv.org/pdf/2003.10513.pdf
-
-[<span id="ref-13">13</span>]: The Sorcerer's Apprentice Guide to Fault Attacks -
-https://eprint.iacr.org/2004/100.pdf
-
-[<span id="ref-14">14</span>]: Fault Mitigation Patterns -
-https://www.riscure.com/uploads/2020/05/Riscure_Whitepaper_Fault_Mitigation_Patterns_final.pdf
-
-[<span id="ref-15">15</span>]: SIFA: Exploiting Ineffective Fault Inductions on Symmetric Cryptography -
-https://eprint.iacr.org/2018/071.pdf
-
-<!-- Footnotes themselves at the bottom. -->
-
-## Notes
-
-[^1]: In other OpenTitan documents, the combination of technical and
-    administrative defense are often referred to as "logical security"
diff --git a/doc/security/logical_security_model/README.md b/doc/security/logical_security_model/README.md
new file mode 100644
index 0000000..e01c3d4
--- /dev/null
+++ b/doc/security/logical_security_model/README.md
@@ -0,0 +1,245 @@
+# OpenTitan Logical Security Model
+
+## Overview
+
+This document explains the overall OpenTitan logical security model as it relates to secure provisioning and run-time operations.
+Due to the open source nature of OpenTitan, this document articulates fundamental requirements to be considered OpenTitan-compliant.
+
+## Logical Entities
+
+At a high level, the OpenTitan logical security model is composed of 4 entities - Silicon Creator, Silicon Owner, Application Provider and the End User.
+This document focuses on the first 3 entities.
+These entities are directly involved in creating a fully trademark compliant OpenTitan.
+
+Depending on the use case, these entities are not required to be distinct.
+
+### Silicon Creator
+
+The Silicon Creator is the **logical entity**, or **logical collection of entities** that manufactures, packages, tests and provisions the chip with its first identity.
+This **"Creator Identity"** is a cryptographically produced unique seed that proves the chip has been properly manufactured, tested, provisioned and is thus authentic.
+
+The creator may be a collection of different entities.
+For example:
+* Fabless semiconductors companies that design a chip and prepare a GDSII for silicon foundries
+* A silicon foundry which manufactures and wafer-tests the chip
+* An OSAT (see glossary) which packages and performs final tests
+* A provisioning entity that manages the chip's provisioning process
+
+A creator may also be a single, vertically integrated entity that performs all the above functions.
+
+The steps in <span style="color:green">**Green**</span> below represent the stages for which the Silicon Creator is responsible.
+
+<img src="logical_sec_model_fig1.svg" alt="Fig1" style="width: 800px;"/>
+
+### Silicon Owner
+
+The Silicon Owner is the **current logical entity** that has ownership of the chip.
+It is responsible for two primary functions, **ownership assignment** and **functional provisioning**.
+The ownership assignment process provisions a unique and attestable identity separate from the Creator Identity; while the functional provisioning process deploys a functional software stack to the silicon.
+
+The steps in <span style="color:purple">**Purple**</span> below represent stages for which the Silicon Owner is responsible.
+Detailed descriptions are given in the following sections.
+
+<img src="logical_sec_model_fig2.svg" alt="Fig2" style="width: 800px;"/>
+
+#### Owner Assignment
+
+A potential owner validates the chip through a **Transport Creator Certificate** tied to the **"Creator Identity"**.
+After the Creator Identity is validated, the Silicon Owner assigns a second unique identity known as the **"Owner Identity"**.
+This identity is unique to the chip and owner combination,  optionally including the major version of the owner software stack, and is separate from the previous **"Creator Identity"**.
+It is the cryptographically produced unique seed that is owned by a specific owner for a given hardware and owner software configuration.
+
+The two identities (Owner and Creator) form a trusted chain analogous to certificate chaining which can be traced back to the root.
+
+In specific use cases, the owner of the chip is allowed to change in a process known as **Ownership Transfer**.
+During this process, the existing owner relinquishes the provisioned Owner Identity, wipes any stored secrets and transfers the chip to a new owner.
+The new owner will not be able to access the previous identity.
+The new owner must then provision a new **Owner Identity** by following the same owner assignment procedure.
+
+#### Functional Provisioning
+
+In addition to assigning the chip an **Owner Identity**, the Silicon Owner is responsible for supplying a functional software stack.
+This can range from a full kernel / operating system to a bare-metal environment depending on application needs.
+
+In order to support secure boot without including verification keys directly in the silicon design, the owner creates an **"Owner Assignment Blob"** which is endorsed by the creator.
+This blob is loaded into the device as a part of ownership assignment and contains public keys used to verify the owner's initial boot stage.
+
+#### Overlap between Silicon Owner and Silicon Creator
+
+The prior sections describe a case where the Silicon Creator and Silicon Owner are completely distinct.
+The Silicon Creator could be a vertically integrated silicon entity (design & fabrication & manufacturing), while the Silicon Owner could be an entity that builds systems out of such components.
+This relationship follows the conventional silicon supplier / consumer model.
+The supplier creates trusted silicon and the consumer validates this trust and provisions an Owner Identity that is unknown to the creator.
+In this scenario, there are **two distinct identities**, Creator then Owner.
+See the diagram below.
+
+<img src="logical_sec_model_fig5.svg" alt="Fig5" style="width: 800px;"/>
+
+The Silicon Creator and owner however, are not required to be separate entities.
+There can be significant overlap.
+
+In the fully overlapping case, a single entity would be solely responsible for creating the silicon and provisioning its identity and software.
+This is analogous to the TPM model where downstream consumers cannot alter the chip or its software contents.
+In this case there is only a single identity instead of two (Creator and Owner).
+This is represented by the diagram below.
+
+<img src="logical_sec_model_fig3.svg" alt="Fig3" style="width: 800px;"/>
+
+Alternatively,  an overlapping number of legal and logical entities may participate in the Silicon Creator and Silicon Owner processes.
+For example, A SIM card provisioner may sit at both the tail-end of the creator process and function as the owner.
+There is assumed trust between the creator and the owner, making a distinct identity for the owner unnecessary.
+Again there is only one silicon identity, even though the line between the creator and the owner is different than the example above.
+See diagram below.
+
+<img src="logical_sec_model_fig4.svg" alt="Fig4" style="width: 800px;"/>
+
+
+### Application Provider
+
+The Application Provider refers to the **logical entity**, or **logical collection of entities** who supply applications that operate on the Silicon Owner-supplied software stack.
+There may be more than one Application Provider.
+
+Applications can request a unique cryptographic identity derived from the Silicon Owner Identity.
+Identity isolation guarantees are implemented and enforced in the Silicon Owner software stack.
+The Silicon Owner is discouraged from exposing its root identity at the application layer since this could potentially compromise isolation between applications, and isolation between the Silicon Owner and Application Provider.
+
+It is important to note that the application identity or versioning does not impact either the Creator Identity or the Owner Identity.
+An update to an application does not cause the Owner Identity to change, but it can cause the application identity to change.
+
+#### Overlap With Silicon Owner
+The Silicon Owner and Application Provider may not be separate entities.
+In fact there is a gradient of overlap expected.
+
+The owner can supply something similar to a runtime environment, while the Application Providers supply applications that run on top.
+In these scenarios, there may be complete separation between owner and providers.
+
+Alternatively, the system may have only one primary function authored by the owner (such as TPM).
+In these scenarios, it is not necessary to have separation between owner and Application Providers.
+
+### Summary of Logical Entities
+
+An OpenTitan chip may interact with up to 3 distinct logical entities or logical collection of entities over the course of its lifecycle.
+The End User (not discussed in this document), is not expected to impact the identities and stages described in this document.
+The model and underlying hardware are flexible enough to support distinct or overlapping entities.
+It is expected that most use cases will have some overlap between phases; between Silicon Creator and owner, or owner and Application Provider.
+Above all, consumers of OpenTitan have the ability to map the security model to their use case.
+
+## Software Stage Mapping
+
+Since OpenTitan software can contain components authored by up to 3 entities, this section describes the software mapping in the most generic case when all 3 entities are distinct.
+Further examples on how different stages might merge are shown below.
+The overall construction is shown below.
+
+<img src="logical_sec_model_fig6.svg" alt="Fig6" style="width: 800px;"/>
+
+The software stack is assumed to be constructed in stages.
+Each entity "owns" specific stages and thus has a certain amount of trust granted.
+Below, a description is given for each entity and its associated stage.
+Ownership here is defined as control of the signing key associated to a specific stage of software.
+
+### Silicon Creator Software
+
+The Silicon Creator owns the **ROM** and **ROM_ext**.
+**ROM** is **R**ead **O**nly **M**emory and cannot be modified after silicon is manufactured.
+**ROM_ext** is **R**ead **O**nly **M**emory extensions, which represent modifiable code that extends the functionality of the ROM.
+
+The ROM is responsible for initial bootstrap, secure boot and certain security configuration of the silicon.
+It is understood that security configuration during ROM can be risky, so only the most critical security settings are expected to be allocated to ROM.
+
+The ROM_ext has multiple distinct functions
+* Apply patches to correct any fundamental and critical HW issues
+* Apply critical security settings that are too risky to apply in ROM
+* Perform Silicon Creator Identity provisioning
+* Provide Creator Identity attestation functions and assist in ownership assignment and ownership functional provisioning.
+
+As ROM and ROM_ext execute, they shall lock out portions of the chip that are not required by later software stages.
+Lock out is defined here as the process of making a specific silicon resource unmodifiable, unreadable or both.
+Once ROM / ROM_ext are completed, they **SHALL NOT** execute again until the next system reset.
+
+Creator software is always inspectable by the Silicon Owner, and a measurement of the code is used as a component of the Creator Identity.
+This provides strong transparency guarantees during Ownership Transfer.
+Once ownership transfer is complete, the Silicon Owner becomes the entity in charge of managing software updates.
+
+It is assumed that creator software operates in **Machine Mode** and has full visibility into the memory map unless it chooses to lock out certain regions.
+Machine mode is the highest privilege state of a RISC-V processor.
+
+### Silicon Owner Software
+
+The Silicon Owner owns the various boot loader stages (BL{N}) and kernel.
+The boot loader stages continue the secure boot process and lock out any privilege not needed by later stages of software.
+
+The kernel provides central services to downstream applications such as key management, non-volatile storage program / erase and general chip control.
+Unlike the previous stages, owner and subsequent application stages are  expected to jump between owner and downstream user code.
+Therefore privilege lock down at this stage is done dynamically via the physical memory protection function.
+
+The kernel is also responsible for the verification and sandboxing of application software if required.
+
+It is assumed that owner software operates in **Machine Mode** and has full visibility into the memory map except regions locked out by previous boot stages or kernel configuration code.
+Machine mode is the highest privilege state of a RISC-V processor.
+
+### Application Provider Software
+
+Application Provider software is the highest layer and least privileged software.
+It is generally not expected for software at this stage to have direct access to silicon peripherals.
+Instead, it is a pure software implementation that calls into kernel for specific services and functions.
+
+It is assumed that provider's software operates in **User Mode** and has only visibility to unlocked memory granted by the owner and creator software.
+User mode is the lowest privilege state of a RISC-V processor.
+Multiple applications may be resident at once and may have different memory access permissions.
+The owner software shall be responsible for ensuring the isolation between multiple applications.
+
+### Logical Entity Overlap
+
+As discussed [previously](#summary-of-logical-entities), it is expected, based on use case, for the logical entities to have some overlap.
+When stages overlap, the software stages can be simplified.
+For example, in the SIM card provisioning scenario, where parts of the creator flow overlaps with the owner flow, it may be expected for ROM_ext and BL{N} to merge into a single simplified stage.
+
+<img src="logical_sec_model_fig7.svg" alt="Fig7" style="width: 800px;"/>
+
+It may also be possible for the owner and Application Provider to become one entity for chips that support only first party functions.
+In this case, the kernel and application may merge to become one entity.
+For example, a typical laptop embedded controller may operate under this model.
+See diagram below.
+
+<img src="logical_sec_model_fig8.svg" alt="Fig8" style="width: 800px;"/>
+
+## Execution Assumptions
+
+This document makes a few assumptions on how the software is executed.
+Since these assumptions have security implications, they are enumerated here.
+These must remain true no matter how the above model is simplified and how stages / entities are merged.
+
+* Beginning from ROM, through various software stages until the kernel (or other functional equivalent), the execution is **ONE WAY**.
+  This means each software stage completes its task and irreversibly jumps to the next stage.
+  There is no planned ability to jump back to earlier stage software or execute code from earlier stage.
+
+* Once in kernel (or other functional equivalent), the behavior can **OPTIONALLY** become dynamic and no longer one way.
+  Execution can switch between the kernel (controlled by the Silicon Owner) and the Application Provider Software.
+  Since the Silicon Owner software runs in machine mode and the Application Provider in user, this also implies changing privilege levels when the execution switches.
+  As a result, verification and sandboxing of application software will be particularly important if the Silicon Owner and Application Provider are distinct logical entities.
+
+## Firmware Update Considerations
+
+The software stage mapping section describes the various software components supported by the system, without making any assumptions on the physical layout in memory.
+This section discusses some considerations to support A/B firmware update schemes.
+
+Non-volatile memory may be split into two or more banks of equal size and used to store software and application data.
+If the banks are divided into two equal partitions, each would hold an instance of the ROM_ext as well as the boot loader stages, kernel and application software.
+
+In some implementations, the ROM_ext and boot loader stages may be required to have a fixed size to simplify the lookup of components during boot; in other cases,  a fixed lookup table may be implemented at the beginning of each non-volatile partition.
+
+Each boot stage is expected to try to launch the newest available version of the next boot stage upon successful signature verification.
+Newest is defined software with a more recent build timestamp or larger versioning value.
+The following diagram presents a reference software layout with support for A/B updates using two physical flash banks:
+
+<img src="logical_sec_model_fig9.svg" alt="Fig9" style="width: 800px;"/>
+
+## Glossary
+
+| Term            | Definition
+| --------------  | ----------
+| GDSII           | Graphic database systems streaming format.  This refers to the common format used for silicon manufacturing services.
+| OSAT            | Outsourced Silicon Assembly and Test.
+| ROM             | Read only memory.
+| ROM_ext         | Read only memory extension.
+| BL              | Boot loader.
diff --git a/doc/security/logical_security_model/index.md b/doc/security/logical_security_model/index.md
deleted file mode 100644
index 09086f8..0000000
--- a/doc/security/logical_security_model/index.md
+++ /dev/null
@@ -1,247 +0,0 @@
----
-title: "OpenTitan Logical Security Model"
----
-
-## Overview
-
-This document explains the overall OpenTitan logical security model as it relates to secure provisioning and run-time operations.
-Due to the open source nature of OpenTitan, this document articulates fundamental requirements to be considered OpenTitan-compliant.
-
-## Logical Entities
-
-At a high level, the OpenTitan logical security model is composed of 4 entities - Silicon Creator, Silicon Owner, Application Provider and the End User.
-This document focuses on the first 3 entities.
-These entities are directly involved in creating a fully trademark compliant OpenTitan.
-
-Depending on the use case, these entities are not required to be distinct.
-
-### Silicon Creator
-
-The Silicon Creator is the **logical entity**, or **logical collection of entities** that manufactures, packages, tests and provisions the chip with its first identity.
-This **"Creator Identity"** is a cryptographically produced unique seed that proves the chip has been properly manufactured, tested, provisioned and is thus authentic.
-
-The creator may be a collection of different entities.
-For example:
-* Fabless semiconductors companies that design a chip and prepare a GDSII for silicon foundries
-* A silicon foundry which manufactures and wafer-tests the chip
-* An OSAT (see glossary) which packages and performs final tests
-* A provisioning entity that manages the chip's provisioning process
-
-A creator may also be a single, vertically integrated entity that performs all the above functions.
-
-The steps in <span style="color:green">**Green**</span> below represent the stages for which the Silicon Creator is responsible.
-
-<img src="logical_sec_model_fig1.svg" alt="Fig1" style="width: 800px;"/>
-
-### Silicon Owner
-
-The Silicon Owner is the **current logical entity** that has ownership of the chip.
-It is responsible for two primary functions, **ownership assignment** and **functional provisioning**.
-The ownership assignment process provisions a unique and attestable identity separate from the Creator Identity; while the functional provisioning process deploys a functional software stack to the silicon.
-
-The steps in <span style="color:purple">**Purple**</span> below represent stages for which the Silicon Owner is responsible.
-Detailed descriptions are given in the following sections.
-
-<img src="logical_sec_model_fig2.svg" alt="Fig2" style="width: 800px;"/>
-
-#### Owner Assignment
-
-A potential owner validates the chip through a **Transport Creator Certificate** tied to the **"Creator Identity"**.
-After the Creator Identity is validated, the Silicon Owner assigns a second unique identity known as the **"Owner Identity"**.
-This identity is unique to the chip and owner combination,  optionally including the major version of the owner software stack, and is separate from the previous **"Creator Identity"**.
-It is the cryptographically produced unique seed that is owned by a specific owner for a given hardware and owner software configuration.
-
-The two identities (Owner and Creator) form a trusted chain analogous to certificate chaining which can be traced back to the root.
-
-In specific use cases, the owner of the chip is allowed to change in a process known as **Ownership Transfer**.
-During this process, the existing owner relinquishes the provisioned Owner Identity, wipes any stored secrets and transfers the chip to a new owner.
-The new owner will not be able to access the previous identity.
-The new owner must then provision a new **Owner Identity** by following the same owner assignment procedure.
-
-#### Functional Provisioning
-
-In addition to assigning the chip an **Owner Identity**, the Silicon Owner is responsible for supplying a functional software stack.
-This can range from a full kernel / operating system to a bare-metal environment depending on application needs.
-
-In order to support secure boot without including verification keys directly in the silicon design, the owner creates an **"Owner Assignment Blob"** which is endorsed by the creator.
-This blob is loaded into the device as a part of ownership assignment and contains public keys used to verify the owner's initial boot stage.
-
-#### Overlap between Silicon Owner and Silicon Creator
-
-The prior sections describe a case where the Silicon Creator and Silicon Owner are completely distinct.
-The Silicon Creator could be a vertically integrated silicon entity (design & fabrication & manufacturing), while the Silicon Owner could be an entity that builds systems out of such components.
-This relationship follows the conventional silicon supplier / consumer model.
-The supplier creates trusted silicon and the consumer validates this trust and provisions an Owner Identity that is unknown to the creator.
-In this scenario, there are **two distinct identities**, Creator then Owner.
-See the diagram below.
-
-<img src="logical_sec_model_fig5.svg" alt="Fig5" style="width: 800px;"/>
-
-The Silicon Creator and owner however, are not required to be separate entities.
-There can be significant overlap.
-
-In the fully overlapping case, a single entity would be solely responsible for creating the silicon and provisioning its identity and software.
-This is analogous to the TPM model where downstream consumers cannot alter the chip or its software contents.
-In this case there is only a single identity instead of two (Creator and Owner).
-This is represented by the diagram below.
-
-<img src="logical_sec_model_fig3.svg" alt="Fig3" style="width: 800px;"/>
-
-Alternatively,  an overlapping number of legal and logical entities may participate in the Silicon Creator and Silicon Owner processes.
-For example, A SIM card provisioner may sit at both the tail-end of the creator process and function as the owner.
-There is assumed trust between the creator and the owner, making a distinct identity for the owner unnecessary.
-Again there is only one silicon identity, even though the line between the creator and the owner is different than the example above.
-See diagram below.
-
-<img src="logical_sec_model_fig4.svg" alt="Fig4" style="width: 800px;"/>
-
-
-### Application Provider
-
-The Application Provider refers to the **logical entity**, or **logical collection of entities** who supply applications that operate on the Silicon Owner-supplied software stack.
-There may be more than one Application Provider.
-
-Applications can request a unique cryptographic identity derived from the Silicon Owner Identity.
-Identity isolation guarantees are implemented and enforced in the Silicon Owner software stack.
-The Silicon Owner is discouraged from exposing its root identity at the application layer since this could potentially compromise isolation between applications, and isolation between the Silicon Owner and Application Provider.
-
-It is important to note that the application identity or versioning does not impact either the Creator Identity or the Owner Identity.
-An update to an application does not cause the Owner Identity to change, but it can cause the application identity to change.
-
-#### Overlap With Silicon Owner
-The Silicon Owner and Application Provider may not be separate entities.
-In fact there is a gradient of overlap expected.
-
-The owner can supply something similar to a runtime environment, while the Application Providers supply applications that run on top.
-In these scenarios, there may be complete separation between owner and providers.
-
-Alternatively, the system may have only one primary function authored by the owner (such as TPM).
-In these scenarios, it is not necessary to have separation between owner and Application Providers.
-
-### Summary of Logical Entities
-
-An OpenTitan chip may interact with up to 3 distinct logical entities or logical collection of entities over the course of its lifecycle.
-The End User (not discussed in this document), is not expected to impact the identities and stages described in this document.
-The model and underlying hardware are flexible enough to support distinct or overlapping entities.
-It is expected that most use cases will have some overlap between phases; between Silicon Creator and owner, or owner and Application Provider.
-Above all, consumers of OpenTitan have the ability to map the security model to their use case.
-
-## Software Stage Mapping
-
-Since OpenTitan software can contain components authored by up to 3 entities, this section describes the software mapping in the most generic case when all 3 entities are distinct.
-Further examples on how different stages might merge are shown below.
-The overall construction is shown below.
-
-<img src="logical_sec_model_fig6.svg" alt="Fig6" style="width: 800px;"/>
-
-The software stack is assumed to be constructed in stages.
-Each entity "owns" specific stages and thus has a certain amount of trust granted.
-Below, a description is given for each entity and its associated stage.
-Ownership here is defined as control of the signing key associated to a specific stage of software.
-
-### Silicon Creator Software
-
-The Silicon Creator owns the **ROM** and **ROM_ext**.
-**ROM** is **R**ead **O**nly **M**emory and cannot be modified after silicon is manufactured.
-**ROM_ext** is **R**ead **O**nly **M**emory extensions, which represent modifiable code that extends the functionality of the ROM.
-
-The ROM is responsible for initial bootstrap, secure boot and certain security configuration of the silicon.
-It is understood that security configuration during ROM can be risky, so only the most critical security settings are expected to be allocated to ROM.
-
-The ROM_ext has multiple distinct functions
-* Apply patches to correct any fundamental and critical HW issues
-* Apply critical security settings that are too risky to apply in ROM
-* Perform Silicon Creator Identity provisioning
-* Provide Creator Identity attestation functions and assist in ownership assignment and ownership functional provisioning.
-
-As ROM and ROM_ext execute, they shall lock out portions of the chip that are not required by later software stages.
-Lock out is defined here as the process of making a specific silicon resource unmodifiable, unreadable or both.
-Once ROM / ROM_ext are completed, they **SHALL NOT** execute again until the next system reset.
-
-Creator software is always inspectable by the Silicon Owner, and a measurement of the code is used as a component of the Creator Identity.
-This provides strong transparency guarantees during Ownership Transfer.
-Once ownership transfer is complete, the Silicon Owner becomes the entity in charge of managing software updates.
-
-It is assumed that creator software operates in **Machine Mode** and has full visibility into the memory map unless it chooses to lock out certain regions.
-Machine mode is the highest privilege state of a RISC-V processor.
-
-### Silicon Owner Software
-
-The Silicon Owner owns the various boot loader stages (BL{N}) and kernel.
-The boot loader stages continue the secure boot process and lock out any privilege not needed by later stages of software.
-
-The kernel provides central services to downstream applications such as key management, non-volatile storage program / erase and general chip control.
-Unlike the previous stages, owner and subsequent application stages are  expected to jump between owner and downstream user code.
-Therefore privilege lock down at this stage is done dynamically via the physical memory protection function.
-
-The kernel is also responsible for the verification and sandboxing of application software if required.
-
-It is assumed that owner software operates in **Machine Mode** and has full visibility into the memory map except regions locked out by previous boot stages or kernel configuration code.
-Machine mode is the highest privilege state of a RISC-V processor.
-
-### Application Provider Software
-
-Application Provider software is the highest layer and least privileged software.
-It is generally not expected for software at this stage to have direct access to silicon peripherals.
-Instead, it is a pure software implementation that calls into kernel for specific services and functions.
-
-It is assumed that provider's software operates in **User Mode** and has only visibility to unlocked memory granted by the owner and creator software.
-User mode is the lowest privilege state of a RISC-V processor.
-Multiple applications may be resident at once and may have different memory access permissions.
-The owner software shall be responsible for ensuring the isolation between multiple applications.
-
-### Logical Entity Overlap
-
-As discussed [previously](#summary-of-logical-entities), it is expected, based on use case, for the logical entities to have some overlap.
-When stages overlap, the software stages can be simplified.
-For example, in the SIM card provisioning scenario, where parts of the creator flow overlaps with the owner flow, it may be expected for ROM_ext and BL{N} to merge into a single simplified stage.
-
-<img src="logical_sec_model_fig7.svg" alt="Fig7" style="width: 800px;"/>
-
-It may also be possible for the owner and Application Provider to become one entity for chips that support only first party functions.
-In this case, the kernel and application may merge to become one entity.
-For example, a typical laptop embedded controller may operate under this model.
-See diagram below.
-
-<img src="logical_sec_model_fig8.svg" alt="Fig8" style="width: 800px;"/>
-
-## Execution Assumptions
-
-This document makes a few assumptions on how the software is executed.
-Since these assumptions have security implications, they are enumerated here.
-These must remain true no matter how the above model is simplified and how stages / entities are merged.
-
-* Beginning from ROM, through various software stages until the kernel (or other functional equivalent), the execution is **ONE WAY**.
-  This means each software stage completes its task and irreversibly jumps to the next stage.
-  There is no planned ability to jump back to earlier stage software or execute code from earlier stage.
-
-* Once in kernel (or other functional equivalent), the behavior can **OPTIONALLY** become dynamic and no longer one way.
-  Execution can switch between the kernel (controlled by the Silicon Owner) and the Application Provider Software.
-  Since the Silicon Owner software runs in machine mode and the Application Provider in user, this also implies changing privilege levels when the execution switches.
-  As a result, verification and sandboxing of application software will be particularly important if the Silicon Owner and Application Provider are distinct logical entities.
-
-## Firmware Update Considerations
-
-The software stage mapping section describes the various software components supported by the system, without making any assumptions on the physical layout in memory.
-This section discusses some considerations to support A/B firmware update schemes.
-
-Non-volatile memory may be split into two or more banks of equal size and used to store software and application data.
-If the banks are divided into two equal partitions, each would hold an instance of the ROM_ext as well as the boot loader stages, kernel and application software.
-
-In some implementations, the ROM_ext and boot loader stages may be required to have a fixed size to simplify the lookup of components during boot; in other cases,  a fixed lookup table may be implemented at the beginning of each non-volatile partition.
-
-Each boot stage is expected to try to launch the newest available version of the next boot stage upon successful signature verification.
-Newest is defined software with a more recent build timestamp or larger versioning value.
-The following diagram presents a reference software layout with support for A/B updates using two physical flash banks:
-
-<img src="logical_sec_model_fig9.svg" alt="Fig9" style="width: 800px;"/>
-
-## Glossary
-
-| Term            | Definition
-| --------------  | ----------
-| GDSII           | Graphic database systems streaming format.  This refers to the common format used for silicon manufacturing services.
-| OSAT            | Outsourced Silicon Assembly and Test.
-| ROM             | Read only memory.
-| ROM_ext         | Read only memory extension.
-| BL              | Boot loader.
diff --git a/doc/security/specs/README.md b/doc/security/specs/README.md
new file mode 100644
index 0000000..f45f583
--- /dev/null
+++ b/doc/security/specs/README.md
@@ -0,0 +1,83 @@
+# OpenTitan Security Model Specification
+
+The current version of the security model targets discrete silicon
+implementations of OpenTitan. The architecture for an integrated IP solution
+will be covered in a future release of this specification.
+
+The following diagram shows the interaction between the different specification
+components.
+
+<img src="documents.svg" style="width: 900px;">
+
+
+## [Device Life Cycle][device_life_cycle]
+
+OpenTitan supports a set of operational states configured via One Time
+Programmable (OTP) memory, allowing the [Silicon Creator][silicon_creator] to
+manage the state of the device as it is being manufactured and provisioned for
+shipment.
+
+An additional set of life cycle states are also available to encapsulate the
+device ownership state. A device that has been personalized with a unique
+[Creator Identity][creator_identity] can be provisioned with
+[Silicon Owner][silicon_owner] credentials. This enables the
+[Silicon Owner][silicon_owner] to run signed code on the device.
+
+## [Secure Boot][secure_boot]
+
+OpenTitan supports a secure boot implementation anchored in the silicon ROM.
+The ROM contains a set of public keys used to verify the first boot stage
+stored in flash.
+
+Each boot stage is in charge of verifying the signature of the next stage and
+locking out portions of the chip that are not required by later stages. Once the
+boot flow reaches kernel execution, the implementation may opt to implement
+dynamic isolation between applications using the available
+[Physical Memory Protection][pmp] (PMP) unit.
+
+## [Firmware Update][firmware_update]
+
+OpenTitan supports a firmware layout with two flash partitions, supporting
+active and non-active instances of each software component. This enables a
+firmware update implementation in which the active partition flashes the new
+software into the non-active region with minimal downtime. Secure boot ensures
+the integrity and stability of the new software before marking it as active.
+
+## [Identities and Root Keys][identities_and_root_keys]
+
+This document describes the composition of the Silicon Creator and Silicon Owner
+cryptographic identities, as well as the device’s key derivation scheme. This
+scheme is based on a symmetric key manager with support for software binding and
+key versioning.
+
+## [Attestation][attestation]
+
+Covers the mechanism in which software verifies the authenticity and integrity
+of OpenTitan's hardware and software configuration.
+
+## [Ownership Transfer][ownership_transfer]
+
+The owner of the silicon is allowed to change in a process known as Ownership
+Transfer. During this process, a new party can take ownership of the device and
+provision its own identity. This allows the new owner to run its own signed code
+and configure its attestation chain.
+
+## [Provisioning][provisioning]
+
+Describes manufacturing and post-ownership transfer provisioning flows. Secrets
+required to personalize the identity of the device and initialize security
+countermeasures are provisioned at manufacturing time. Silicon owners have the
+option of provisioning additional secrets after taking ownership of the device.
+
+
+[attestation]: ./attestation/README.md
+[creator_identity]: ./identities_and_root_keys/README.md#creator-identity
+[device_life_cycle]: ./device_life_cycle/README.md
+[firmware_update]: ./firmware_update/README.md
+[identities_and_root_keys]: ./identities_and_root_keys/README.md
+[ownership_transfer]: ./ownership_transfer/README.md
+[pmp]: https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html
+[provisioning]: ./device_provisioning/README.md
+[secure_boot]: ./secure_boot/README.md
+[silicon_creator]: ../logical_security_model/README.md#silicon-creator
+[silicon_owner]: ../logical_security_model/README.md#silicon-owner
diff --git a/doc/security/specs/_index.md b/doc/security/specs/_index.md
deleted file mode 100644
index 79a712f..0000000
--- a/doc/security/specs/_index.md
+++ /dev/null
@@ -1,85 +0,0 @@
----
-title: "OpenTitan Security Model Specification"
----
-
-The current version of the security model targets discrete silicon
-implementations of OpenTitan. The architecture for an integrated IP solution
-will be covered in a future release of this specification.
-
-The following diagram shows the interaction between the different specification
-components.
-
-<img src="documents.svg" style="width: 900px;">
-
-
-## [Device Life Cycle][device_life_cycle]
-
-OpenTitan supports a set of operational states configured via One Time
-Programmable (OTP) memory, allowing the [Silicon Creator][silicon_creator] to
-manage the state of the device as it is being manufactured and provisioned for
-shipment.
-
-An additional set of life cycle states are also available to encapsulate the
-device ownership state. A device that has been personalized with a unique
-[Creator Identity][creator_identity] can be provisioned with
-[Silicon Owner][silicon_owner] credentials. This enables the
-[Silicon Owner][silicon_owner] to run signed code on the device.
-
-## [Secure Boot][secure_boot]
-
-OpenTitan supports a secure boot implementation anchored in the silicon mask
-ROM. The mask ROM contains a set of public keys used to verify the first boot
-stage stored in flash.  
-
-Each boot stage is in charge of verifying the signature of the next stage and
-locking out portions of the chip that are not required by later stages. Once the
-boot flow reaches kernel execution, the implementation may opt to implement
-dynamic isolation between applications using the available
-[Physical Memory Protection][pmp] (PMP) unit.
-
-## [Firmware Update][firmware_update]
-
-OpenTitan supports a firmware layout with two flash partitions, supporting
-active and non-active instances of each software component. This enables a
-firmware update implementation in which the active partition flashes the new
-software into the non-active region with minimal downtime. Secure boot ensures
-the integrity and stability of the new software before marking it as active.
-
-## [Identities and Root Keys][identities_and_root_keys]
-
-This document describes the composition of the Silicon Creator and Silicon Owner
-cryptographic identities, as well as the device’s key derivation scheme. This
-scheme is based on a symmetric key manager with support for software binding and
-key versioning.
-
-## [Attestation][attestation]
-
-Covers the mechanism in which software verifies the authenticity and integrity
-of OpenTitan's hardware and software configuration.
-
-## [Ownership Transfer][ownership_transfer]
-
-The owner of the silicon is allowed to change in a process known as Ownership
-Transfer. During this process, a new party can take ownership of the device and
-provision its own identity. This allows the new owner to run its own signed code
-and configure its attestation chain.
-
-## [Provisioning][provisioning]
-
-Describes manufacturing and post-ownership transfer provisioning flows. Secrets
-required to personalize the identity of the device and initialize security
-countermeasures are provisioned at manufacturing time. Silicon owners have the
-option of provisioning additional secrets after taking ownership of the device.
-
-
-[attestation]: {{< relref "doc/security/specs/attestation" >}}
-[creator_identity]: {{< relref "doc/security/specs/identities_and_root_keys#creator-identity" >}}
-[device_life_cycle]: {{< relref "doc/security/specs/device_life_cycle" >}}
-[firmware_update]: {{< relref "doc/security/specs/firmware_update" >}}
-[identities_and_root_keys]: {{< relref "doc/security/specs/identities_and_root_keys" >}}
-[ownership_transfer]: {{< relref "doc/security/specs/ownership_transfer" >}}
-[pmp]: https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html
-[provisioning]: {{< relref "doc/security/specs/device_provisioning" >}}
-[secure_boot]: {{< relref "doc/security/specs/secure_boot" >}}
-[silicon_creator]: {{< relref "doc/security/logical_security_model#silicon-creator" >}}
-[silicon_owner]: {{< relref "doc/security/logical_security_model#silicon-owner" >}}
diff --git a/doc/security/specs/attestation/README.md b/doc/security/specs/attestation/README.md
new file mode 100644
index 0000000..fe6468d
--- /dev/null
+++ b/doc/security/specs/attestation/README.md
@@ -0,0 +1,872 @@
+# Device Attestation
+
+## Overview
+
+Attestation is the mechanism in which software verifies the authenticity and
+integrity of the hardware and software of a device. OpenTitan supports the
+following attestation mechanisms:
+
+_Creator Based Attestation (Creator Identity Certificate)_
+
+It is used to attest the authenticity of the hardware, the configuration of the
+first two firmware boot stages, as well as the unique device key known as the
+Creator Identity. The attestation is captured in a certificate signed with a
+Silicon Creator intermediate Certificate Authority (CA) key, which can be
+validated using Public Key Infrastructure (PKI). Alternatively, the certificate
+can be self-signed and verified against the Creator Identity public key and a
+trustworthy device registry.
+
+_Owned Device Attestation (Owner Identity Certificate)_
+
+It is used to attest ownership of the device, the BL0 configuration, as well as
+the Owner Identity key which is used as a root attestation key managed by the
+Silicon Owner. The attestation certificate is signed with the device's Creator
+Identity key, forming an attestation chain to the Creator Identity certificate.
+
+A Silicon Owner can endorse the Owner Identity Certificate with its own
+intermediate CA key as soon as it takes ownership of the device. This way device
+attestation can be verified against the Silicon Owners' PKI of choice.
+
+## Terminology
+
+Boot stages:
+
+*   `ROM`: Metal ROM, sometimes known as Boot ROM.
+*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
+*   Owner boot stages. This document uses two stages as an example. The Silicon
+    Owner is free to choose other boot configurations.
+    *   `BL0`: Bootloader. Signed by the Silicon Owner.
+    *   `KERNEL`: Signed by the Silicon Owner.
+
+## Attestation Flows
+
+Device attestation is rooted in two asymmetric keys named Creator Identity and
+Owner Identity. The [Asymmetric Keys](#asymmetric-keys) section provides details
+on the cryptographic properties of these keys.
+
+The **[Creator Identity][creator-identity]** is generated at manufacturing time
+and it is endorsed by the Silicon Creator PKI. This key is used to sign
+certificates endorsing the authenticity of the physical device, the ROM and
+`ROM_EXT` configuration (see [Creator custom Extension](#creator-custom-extension)
+for more details), as well as the Owner Identity public key.
+
+The **[Owner Identity][owner-identity]** is generated at ownership transfer time,
+or whenever there is a change to the BL0 software binding tag. The Owner
+Identity is used to attest the owner and BL0 configuration, as well as an
+attestation key used by the Kernel. This key is endorsed by the Creator Identity,
+but can also be endorsed by the Silicon Owner PKI. Endorsement of the Owner
+Identity with the Owner's PKI, is covered in detail in the
+[Owner Personalization](../device_provisioning/README.md#owner_personalization) process
+described in the provisioning specification.
+
+When using a Silicon Owner PKI, the Owner is expected to maintain a device
+registry with Creator Identity to Owner Identity certificate mappings to handle
+certificate revocations issued by the Silicon Creator.
+
+Application specific attestation keys are expected to be endorsed by the Owner
+Identity and managed by the Kernel. The format of the attestation certificate is
+outside the scope of this document.
+
+### Certificate Chains
+
+There are two possible attestation chains. Creator based attestation provides a
+chain to the Creator PKI, which is useful when testing the authenticity of the
+device. After ownership transfer, the Silicon Owner may opt to switch to its own
+PKI by endorsing the Owner Identity. On-Device certificates are stored in a
+flash region accessible to all boot stages including the kernel stage.
+Additional storage is required to store the Owner Identity certificate endorsed
+by the Silicon Owner PKI.
+
+The kernel may opt to extend the attestation chain based on application level
+requirements. See also the [Privacy Considerations](#privacy-considerations)
+section for specific cases in which extending the attestation chain is
+recommended.
+
+<table>
+  <tr>
+    <td>
+      <img src="attestation_fig1.svg" width="" alt="fig1" style="width: 800px;">
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: PKI and Device Certificates</td>
+  </tr>
+</table>
+
+#### Creator Intermediate CA versus Self-Signed Creator Identity Certificates
+
+The Silicon Creator may opt to implement a self-signed Creator Identity
+certificate, which must be verified against a device registry. Self-signed
+certificates can be revoked at a finer level of granularity, and allow for
+in-field certificate rotation as a result of break-glass ROM_EXT updates[^1].
+
+The Silicon Owner only needs to trust the registry during Creator Based
+attestation. This is no different than having to trust the Silicon Creator PKI.
+As a result, the following requirements are imposed on the device registry:
+
+*   The device registry must ensure that only authenticated entities are
+    authorized to insert certificates into the certificate list.
+*   There must be a mechanism for the Silicon Owner to authenticate the device
+    registry and establish a secure connection to query certificates associated
+    with devices transferred to its ownership.
+
+### Attestation Updates
+
+Updates to the attestation chain can be handled via an explicit command during
+the boot sequence, or via an implicit update-after-check mechanism.
+
+#### Attestation command
+
+In this case, attestation updates are handled by a command encoded in a scratch
+register that lives outside of the CPU reset domain. The following table
+describes the sequence of operations required to update the certificate
+attestation chain, which may require generation of new attestation keys.
+
+The attestation command requires a `BOOT_SERVICE_REQUEST` reset to allow the
+`ROM_EXT` component to sign the new attestation chain. This is because
+OpenTitan's isolation model does not provide a way for the `ROM_EXT` to maintain
+its secrets isolated from `BL0` or the kernel layers. Once the `ROM_EXT` is done
+using its secrets, it must wipe them from memory before handing over execution
+to later stages.
+
+<table>
+  <tr>
+    <td><strong>Boot Stage</strong></td>
+    <td><strong>Step</strong></td>
+  </tr>
+  <tr>
+    <td rowspan="2" ><strong>Kernel</strong></td>
+    <td>Queue attestation update command via scratch register write.</td>
+  </tr>
+  <tr>
+    <td>Issue reset with cause `BOOT_SERVICE_REQUEST`.</td>
+  </tr>
+  <tr>
+    <td colspan="2" >System Reset</td>
+  </tr>
+  <tr>
+    <td rowspan="2" ><strong>ROM</strong></td>
+    <td>Verify ROM_EXT and configure key manager ROM stage inputs.</td>
+  </tr>
+  <tr>
+    <td>Jump to ROM_EXT</td>
+  </tr>
+  <tr>
+    <td rowspan="5" ><strong>ROM_EXT</strong></td>
+    <td>Verify BL0 and configure key manager ROM_EXT stage inputs.</td>
+  </tr>
+  <tr>
+    <td>
+Keygen new Owner Identity asymmetric key pair if needed (see
+<a href="#key-generation">Key Generation</a> section).
+
+Note: The Creator Identity key is not derived on regular boot flows. It is only
+derived as a result of the execution of the attestation command.
+    </td>
+  </tr>
+  <tr>
+    <td>
+Issue a new Owner Identity certificate to endorse the Owner Identity key (see
+<a href="#owner-identity-certificate">Owner Identity Certificate</a> section).
+    </td>
+  </tr>
+  <tr>
+    <td>
+The Creator Identity key pair is cleared from memory so that it is not available
+to later boot stages.
+    </td>
+  </tr>
+  <tr>
+    <td>
+Jump to BL0 and promote the attestation update request by updating the scratch
+pad register.
+    </td>
+  </tr>
+  <tr>
+    <td rowspan="5" ><strong>BL0</strong></td>
+    <td>Verify Kernel and configure key manager BL0 stage inputs.</td>
+  </tr>
+  <tr>
+    <td>Keygen new attestation asymmetric key pair.</td>
+  </tr>
+  <tr>
+    <td>
+Issue an attestation certificate to endorse the new attestation key. The
+certificate is signed with the Owner Identity key.
+
+Note: The format of the attestation certificate is controlled by the owner and
+outside of the scope of this specification.
+    </td>
+  </tr>
+  <tr>
+    <td>
+The Owner Identity key pair is cleared from memory so that it is not available
+to later boot stages.
+    </td>
+  </tr>
+  <tr>
+    <td>
+Jump to Kernel and promote the attestation update request by updating the
+scratch pad register.
+    </td>
+  </tr>
+  <tr>
+    <td rowspan="2" ><strong>Kernel</strong></td>
+    <td>Configure key manager state inputs to unwrap attestation key.</td>
+  </tr>
+  <tr>
+    <td>
+Verify attestation chain against attestation key and complete attestation update
+request by clearing the scratch register.
+    </td>
+  </tr>
+</table>
+
+#### Update after check
+
+The `ROM_EXT` may trigger automatic attestation updates by performing an Owner
+Identity attestation check at boot time:
+
+```
+measurement = MAC(KM_VersionedKey,
+  OwnerIdentityCertificate ||
+  OwnerIdentitySeed ||
+  FixedInfo)
+
+update_cert = measurement XOR cached_measurement
+```
+
+The Owner Identity certificate needs to be updated if `update_cert` evaluates to
+a non-zero value. The `measurement` is a MAC operation with the following
+components:
+
+*   `KM_VersionedKey`: Symmetric versioned key extracted from key manager used
+    as MAC key.
+*   `MAC` message is a concatenation of the following data:
+    *   Owner Identity Certificate.
+    *   Owner Identity Seed: Output from the key manager.
+    *   FixedInfo: Data associated with the Owner Identity keygen operation.
+
+### Modes of Operation
+
+OpenTitan supports various modes of operation. The mode of operation is
+encapsulated in the Owner Identity certificate; thus, any given attestation
+chain is only valid for a single mode of operation. Secure boot makes sure that
+the attestation chain is only available if the device state matches the expected
+configuration.
+
+In addition to this, the key manager outputs used to generate the
+[key identifiers](#key-identifiers) depend on system level measurements that
+reflect the mode of operation of the device. The following definitions are
+compatible with the OpenTitan device life cycle definitions.
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Value</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>Not Configured</td>
+    <td>0</td>
+    <td>
+Use when the security mechanisms of the device have not been fully configured.
+    </td>
+  </tr>
+  <tr>
+    <td>Normal</td>
+    <td>1</td>
+    <td>
+Device in PROD lifecycle stage running production software.
+
+*   Secure boot enabled.
+*   Debug functionality in hardware and software are disabled.
+    </td>
+  </tr>
+  <tr>
+    <td>Debug</td>
+    <td>2</td>
+    <td>
+Device in operational state but not fully meeting the criteria of Normal mode of
+operation.
+    </td>
+  </tr>
+</table>
+
+## Provisioning - Key Manager Configuration
+
+The key manager provides identity values which are used to generate both the
+Creator and the Owner identity key pairs. The following sections describe the
+key manager provisioning requirements.
+
+### Creator Secrets and Device Identifier
+
+The Silicon Creator is in charge of provisioning the device root secrets used to
+generate the Creator Identity[^2].
+
+The root secrets are defined as [RootKey][root-key] and
+[DiversificationKey][diversification-key], which are generated by an entropy
+source with a security strength equivalent to the one provided by the key
+manager. Random number generation shall follow the recommendations from
+[NIST 800-90A](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf).
+The number of bits required for each secret is dependent on the key manager
+implementation details.
+
+In addition to this, the Silicon Creator is also responsible for configuring a
+statistically unique non-cryptographic [Device Identifier][device-identifier]
+used to track the device throughout its lifecycle.
+
+#### Manufacturing Flow
+
+Provisioning is performed in a process referred to as personalization, which
+occurs at manufacturing time. Personalization is performed in a secure facility
+that meets the security assurance requirements of the SKU being produced.
+
+There are two alternative methods for personalization described in the sequence
+diagrams below. Implementation details are outside the scope of this document.
+See the [Device Provisioning][device-provisioning] specification for more details.
+
+<table>
+  <tr>
+    <td style="text-align:center">
+      <img src="attestation_fig2.svg" width="" alt="fig2" style="width: 800px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center">
+<strong>Self-Generation</strong>: Secrets are generated inside the device under test and
+never leave the device. The public portion of the Creator Identity is exported
+so that it can be endorsed by the Silicon Creator.
+    </td>
+  </tr>
+</table>
+
+<table>
+  <tr>
+    <td>
+      <img src="attestation_fig3.svg" width="" alt="fig3" style="width: 800px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center">
+<strong>Injection</strong>: This method employs a provisioning appliance, usually built with
+a certified HSM, capable of generating high-quality secrets at a much faster
+rate compared to an OpenTitan device. In this method, the secrets and Creator
+Identity certificate are injected into the device under test.
+    </td>
+  </tr>
+</table>
+
+### Owner Secrets
+
+The ROM Extension is in charge of resetting the
+[OwnerRootSecret](#owner-root-secret) during ownership transfer or updates to
+the BL0 software binding tag. The new value shall be generated by an entropy
+source with a security strength equivalent to the one supported by the key
+manager. The triggering mechanism for updating the value is covered in the
+[Attestation Updates](#attestation-updates) section.
+
+## Asymmetric Keys {#asymmetric-keys}
+
+OpenTitan uses ECDSA attestation keys conformant to
+[FIPS 186-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf). Future
+revisions of this specification may add support for other signature schemes.
+
+### Key Identifiers {#key-identifiers}
+
+Key identifiers are defined as follows, where `SALT_CKI`and `SALT_OKI` are 256b
+values defined in `ROM_EXT` and `BL0` respectively.
+
+```
+CreatorKeyIdentifier = MAC(SALT_CKI, CreatorIdentitySeed)
+OwnerKeyIdentifier = MAC(SALT_OKI, OwnerIdentitySeed)
+```
+
+### Key Generation
+
+The following keygen mechanism employs a DRBG instantiated with a fixed entropy
+pool.
+
+```
+// Seed length compliant with NIST 800-90A section 10.
+// For CTR_DRBG with AES-256 the minimum seed length is 384.
+FIXED_ENTROPY_SEED = least 384b stored in flash
+
+// This mode of operation in hardware is being tracked via a feature
+// request: https://github.com/lowRISC/opentitan/issues/2652
+// The implementation may opt for supporting this in a software
+// based DRBG, as long as performance requirements are met.
+drbg_ctx = DRBG_init(FIXED_ENTROPY_SEED, key_identifier)
+do:
+  c = DRBG_generate(drbg_ctx)
+while c > curve_order - 2
+
+private_key = c + 1
+public_key = private_key * G
+```
+
+Requirements:
+
+*   Key pair generation is based on
+    [FIPS 186-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf)
+    appendix B.4. The example above follows the test candidate key generation
+    method.
+*   The key size shall provide a security strength as recommended by
+    [NIST 800-57](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r4.pdf)
+    part 1, for example:
+
+**Security Strength** | **ECDSA**
+--------------------- | ---------
+128                   | NIST_P256
+196                   | NIST_P384
+256                   | NIST_P521
+
+*   The DRBG shall be initialized with a security strength equivalent to the one
+    supported by the asymmetric key conformant to
+    [NIST 800-133](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r1.pdf)
+    section 4.
+*   `key_identifier` is set to the output of the key manager. See the table
+    below for more details.
+*   `FIXED_ENTROPY_SEED` is extracted from the DRBG and stored in flash. The
+    software shall ensure isolation of this value to enforce the visibility
+    settings described in the table below.
+
+<table>
+  <tr>
+    <td>Key</td>
+    <td>Visibility</td>
+    <td>Key Identifier<br><code>key_identifier</code></td>
+    <td>Entropy Generation Time<br><code>FIXED_ENTROPY_SEED</code></td>
+  </tr>
+  <tr>
+    <td>Creator Identity<br>(UDS)</td>
+    <td>ROM_EXT</td>
+    <td><a href="#key-identifiers">CreatorKeyIdentifier</a></td>
+    <td>personalization (Factory)</td>
+  </tr>
+  <tr>
+    <td>Owner Identity<br>(CDI0)</td>
+    <td>BL0</td>
+    <td><a href="#key-identifiers">OwnerKeyIdentifier</a></td>
+    <td>Ownership Transfer</td>
+  </tr>
+</table>
+
+#### Alternative Key Generation Flow
+
+To avoid the need for a `FIXED_ENTROPY_SEED`, the attestation keys may be
+generated directly from a true entropy source fed into the DRBG. Once the key is
+generated, it has to be wrapped for storage with a key extracted from the key
+manager. In this approach, the `key_identifier parameter` does not need to be
+provided by the key manager.
+
+```
+// Initialize DRBG with True RNG source.
+drbg_ctx = DRBG_init(personalization_string=key_identifier)
+do:
+  c = DRBG_generate(drbg_ctx)
+while c > curve_order - 2
+
+private_key = c + 1
+public_key = private_key * G
+
+// Initialize the key manager to extract a storage key.
+kmgr = keymgr_init()
+
+// Advance to required key manager stage (one-way state change)
+keymgr_advance_state(kmgr)
+
+// Generate versioned key used to wrap the |private_key|. The
+// |wrapping_key| may have to be split into shares before loading
+// into AES. The wrapping key is bound to the key manager software
+// binding and max key version configuration.
+wrapping_key = keymgr_generate_vk(kmgr, key_version, key_id)
+
+// The wrapping key shall be stored with additional metadata to
+// facilitate unwrapping.
+wrapped_key = AES_256_CTR(wrapping_key, iv, private_key)
+```
+
+## Certificate Format
+
+Certificates stored in the device adhere to standard X.509 format, meeting the
+profile specification requirements from
+[RFC 5280](https://tools.ietf.org/html/rfc5280), and the [Open DICE][open-dice] profile
+specification.
+
+### Public Key Identifiers
+
+The following key identifiers are generated using a MAC based one-step key
+derivation function as specified in
+[NIST 800-56C](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr1.pdf).
+The `SALT_ID` parameter is configured by software.
+
+<table>
+  <tr>
+   <td><strong>Label</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>Creator PubKey ID<br>(UDS_ID)</td>
+    <td>
+MAC_KDM(SALT_ID, CreatorIdentityPubKey, outlength=20, info="ID")
+    </td>
+  </tr>
+  <tr>
+    <td>Owner PubKeyID<br>(CDI_ID)</td>
+    <td>
+MAC_KDM(SALT_ID, OwnerIdentityPubKey, outlength=20, info="ID")
+    </td>
+  </tr>
+</table>
+
+### Creator Identity Certificate
+
+The TBSCertificate field contains the subject of the certificate and the CA
+issue information. The following fields are required:
+
+<table>
+  <tr>
+    <td><strong>Field</strong></td>
+    <td><strong>Value</strong></td>
+  </tr>
+  <tr>
+    <td>Version</td>
+    <td>3</td>
+  </tr>
+  <tr>
+    <td>Serial Number</td>
+    <td>
+Creator public key identifier in ASN.1 INTEGER format.
+
+The result value shall be a positive integer upto 20 octets in size.
+    </td>
+  </tr>
+  <tr>
+    <td>Signature</td>
+    <td>
+One of the following identifiers matching the signature and digest format.
+
+
+[rfc5758](https://tools.ietf.org/html/rfc5758#section-3.2):
+
+*   ecdsa-with-SHA256
+*   ecdsa-with-SHA384
+*   ecdsa-with-SHA512
+
+[rfc8692](https://tools.ietf.org/html/rfc8692#section-3):
+
+*   id-ecdsa-with-shake256
+
+Note: There are no identifiers available for SHA3-{256|512}.
+    </td>
+  </tr>
+  <tr>
+    <td>Issuer</td>
+    <td>
+X520SerialNumber: id
+
+Where id is set to the creator public key identifier in hex encoded format.
+    </td>
+  </tr>
+  <tr>
+    <td>Validity</td>
+    <td>
+
+Not Before: personalization time in
+[GeneralizedTime](https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2) format.
+
+Not After : 99991231235959Z from
+[rfc5280](https://tools.ietf.org/html/rfc5280#section-4.1.2.5) (no expiration
+time).
+    </td>
+  </tr>
+  <tr>
+    <td>Subject</td>
+    <td>
+X520SerialNumber: id
+
+Where id is set to the creator public key identifier in hex encoded format.
+    </td>
+  </tr>
+  <tr>
+    <td>Subject Public Key Info</td>
+    <td>
+
+Subject public key information fields associated with ECDSA signature algorithm
+as defined in [rfc5480](https://tools.ietf.org/html/rfc5480#section-2).
+    </td>
+  </tr>
+  <tr>
+    <td>Unique Identifiers</td>
+    <td>These fields are omitted.</td>
+  </tr>
+  <tr>
+    <td>Extensions</td>
+    <td>
+Extensions for compatibility with Open DICE profile:
+
+*   `subjectKeyIdentifier` (non-critical). Used to identify certificates that
+    contain a particular public key. Set to the creator public key identifier
+    value.
+*   `keyUsage`> (critical). Describes the purpose of the public key contained in
+    the certificate. Contains only `keyCertSign`.
+*   `basicConstraints` (critical). Identifies whether the subject of the
+    certificate is a CA and the maximum depth of valid certification paths.
+    *   `cA` is set to TRUE
+    *   `pathLenConstraint` not included.
+
+OpenTitan Creator Identity custom extension:
+
+*   `extnID`: TBD. Need to determine if we need an OID for OpenTitan, or if we
+    can allocate a new ID under the Google certificate extension domain
+    1.3.6.1.4.1.11129.2.1.
+*   `critical`: FALSE
+*   `extnValue`: See [creator custom extension](#creator-custom-extension)
+    section.
+    </td>
+  </tr>
+</table>
+
+#### Creator custom Extension {#creator-custom-extension}
+
+This extension is formatted as an ASN.1 SEQUENCE containing device identifiable
+information. See [Privacy Considerations](#privacy-considerations) for
+additional details on how to handle privacy concerns.
+
+<table>
+  <tr>
+    <td><strong>Seq. No.</strong></td>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td>Operational Mode</td>
+    <td>INTEGER</td>
+    <td>
+
+[Operational mode](#operational-mode) of the device.
+    </td>
+  </tr>
+  <tr>
+    <td>1</td>
+    <td>Device Identifier</td>
+    <td>OCTET STR</td>
+    <td>
+Device Identifier.
+    </td>
+  </tr>
+  <tr>
+    <td>2</td>
+    <td>HASH Type</td>
+    <td>OCTET STR</td>
+    <td>HASH type used in measurements.</td>
+  </tr>
+  <tr>
+    <td>3</td>
+    <td>ROM Hash</td>
+    <td>OCTET STR</td>
+    <td>ROM contents hash.</td>
+  </tr>
+  <tr>
+    <td>4</td>
+    <td>ROM EXT Hash</td>
+    <td>OCTET STR</td>
+    <td>ROM EXT contents hash. Includes code manifest.</td>
+  </tr>
+  <tr>
+    <td>5</td>
+    <td>Code Descriptor</td>
+    <td>OCTET STR</td>
+    <td>ROM/ROM_EXT versioning information.</td>
+  </tr>
+</table>
+
+### Owner Identity Certificate
+
+The TBSCertificate field contains the subject of the certificate and the CA
+issue information. The following fields are required:
+
+<table>
+  <tr>
+    <td><strong>Field</strong></td>
+    <td><strong>Value</strong></td>
+  </tr>
+  <tr>
+    <td>Version</td>
+    <td>3</td>
+  </tr>
+  <tr>
+    <td>Serial Number</td>
+    <td>
+Owner public key identifier in ASN.1 INTEGER format.
+
+The result value shall be a positive integer upto 20 octets in size.
+    </td>
+  </tr>
+  <tr>
+    <td>Signature</td>
+    <td>
+One of the following identifiers matching the signature and digest format.
+
+
+[rfc5758](https://tools.ietf.org/html/rfc5758#section-3.2):
+
+*   ecdsa-with-SHA256
+*   ecdsa-with-SHA384
+*   ecdsa-with-SHA512
+
+[rfc8692](https://tools.ietf.org/html/rfc8692#section-3):
+
+*   id-ecdsa-with-shake256
+    </td>
+  </tr>
+  <tr>
+    <td>Issuer</td>
+    <td>
+X520SerialNumber:  id
+
+Where id is set to the creator public key identifier in hex encoded format.
+    </td>
+  </tr>
+  <tr>
+    <td>Validity</td>
+    <td>
+
+Not Before: personalization time in
+[GeneralizedTime](https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2) format.
+
+Not After : 99991231235959Z from
+[rfc5280](https://tools.ietf.org/html/rfc5280#section-4.1.2.5) (no expiration
+time).
+    </td>
+  </tr>
+  <tr>
+    <td>Subject</td>
+    <td>
+X520SerialNumber:  id
+
+Where id is set to the owner public key identifier in hex encoded format.
+    </td>
+  </tr>
+  <tr>
+    <td>Subject Public Key Info</td>
+    <td>
+
+Subject public key information fields associated with ECDSA signature algorithm
+as defined in [rfc5480](https://tools.ietf.org/html/rfc5480#section-2).
+    </td>
+  </tr>
+  <tr>
+    <td>Unique Identifiers</td>
+    <td>These fields are omitted.</td>
+  </tr>
+  <tr>
+    <td>Extensions</td>
+    <td>
+Extensions for compatibility with Open DICE profile:
+
+*   `authorityKeyIdentifier` (non-critical). Used to identify certificates that
+    contain a particular public key.
+    *   `keyIdentifier` set to the creator public key identifier value. This is
+         the only field needed.
+*   `subjectKeyIdentifier` (non-critical). Used to identify certificates that
+    contain a particular public key. Set to the owner public key identifier
+    value.
+*   `keyUsage` (critical). Describes the purpose of the public key contained in
+    the certificate. Contains only `keyCertSign`.
+*   `basicConstraints` (critical). Identifies whether the subject of the
+    certificate of the is a CA and the maximum depth of valid certification
+    paths.
+    *   `CA` is set to TRUE
+    *   `pathLenConstraint` not included.
+
+OpenTitan Owner Identity custom extension:
+
+*   `extnID`: TBD. Need to determine if we need an OID for OpenTitan, or if we
+    can allocate a new ID under the Google certificate extension domain
+    1.3.6.1.4.1.11129.2.1.
+*   `critical`: FALSE
+*   `extnValue`: See [owner custom extension](#owner-custom-extension) section.
+
+This certificate may also contain the
+[creator custom extension](#creator-custom-extension) if the Owner decides to
+endorse this certificate with its own PKI.
+    </td>
+  </tr>
+</table>
+
+#### Owner Custom Extension
+
+ASN.1 SEQUENCE containing the following fields. The owner can extend this as
+needed.
+
+<table>
+  <tr>
+    <td><strong>Seq. No.</strong></td>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>0</td>
+    <td>Code Descriptor</td>
+    <td>OCTET STR</td>
+    <td>BL0 version and key manager software binding tag information.</td>
+  </tr>
+</table>
+
+## Lightweight Certificate Format
+
+The Silicon Creator may opt to implement attestation signatures over a binary
+manifest containing the endorsed key as well as the measurements described in
+the [creator](#creator-custom-extension) and [owner](#owner-custom-extension)
+custom extensions.
+
+The binary manifest format must be standardized at the OpenTitan project level
+to ensure interoperability between silicon and software layers.
+
+## Privacy Considerations {#privacy-considerations}
+
+The Silicon Owner software must address any privacy concerns associated with the
+use of device identifiable information.
+
+The Silicon Owner certificate, for example, is meant to be used exclusively with
+the Silicon Owner's infrastructure. Use cases requiring multiple identities or
+attestation services for multiple backends are required to derive
+<span style="text-decoration:underline;">application level identities and
+certificates</span>. Such application level identities must fulfill the privacy
+requirements of their use cases.
+
+OpenTitan's key manager provides a mechanism to derive multiple application
+level keys with lineage to the Owner Identity, without making them
+distinguishable from any random key. These properties allow the software
+implementation to bind the OpenTitan attestation to any application level
+attestation flows without precluding the implementation of additional privacy
+measures at the application level.
+
+<!-- Footnotes themselves at the bottom. -->
+## Notes
+
+[^1]: In-field ROM_EXT updates performed by a Silicon Owner may invalidate the
+Creator Identity and its associated certificate. Without a federated device
+registry, new silicon owners are left to trust the Creator Identity certificate
+provided by the current owner to verify the authenticity of the device.
+
+[^2]: The Creator Identity is referred to as the Unique Device Secret (UDS) in
+DICE terminology.
+
+<!-- References -->
+[open-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
+
+[device-identifier]: ../identities_and_root_keys/README.md#device-identifier
+[creator-identity]: ../identities_and_root_keys/README.md#creator-identity
+[owner-identity]: ../identities_and_root_keys/README.md#owner-identity
+[owner-root-secret]: ../identities_and_root_keys/README.md#owner-root-secret
+[root-key]: ../identities_and_root_keys/README.md#root-key
+[diversification-key]:../identities_and_root_keys/README.md#diversification-key
+
+[device-provisioning]: ../device_provisioning/README.md
+[owner-personalization]: ../device_provisioning/README.md#owner_personalization
diff --git a/doc/security/specs/attestation/_index.md b/doc/security/specs/attestation/_index.md
deleted file mode 100644
index 99e1d10..0000000
--- a/doc/security/specs/attestation/_index.md
+++ /dev/null
@@ -1,874 +0,0 @@
----
-title: "Device Attestation"
----
-
-## Overview
-
-Attestation is the mechanism in which software verifies the authenticity and
-integrity of the hardware and software of a device. OpenTitan supports the
-following attestation mechanisms:
-
-_Creator Based Attestation (Creator Identity Certificate)_
-
-It is used to attest the authenticity of the hardware, the configuration of the
-first two firmware boot stages, as well as the unique device key known as the
-Creator Identity. The attestation is captured in a certificate signed with a
-Silicon Creator intermediate Certificate Authority (CA) key, which can be
-validated using Public Key Infrastructure (PKI). Alternatively, the certificate
-can be self-signed and verified against the Creator Identity public key and a
-trustworthy device registry.
-
-_Owned Device Attestation (Owner Identity Certificate)_
-
-It is used to attest ownership of the device, the BL0 configuration, as well as
-the Owner Identity key which is used as a root attestation key managed by the
-Silicon Owner. The attestation certificate is signed with the device's Creator
-Identity key, forming an attestation chain to the Creator Identity certificate.
-
-A Silicon Owner can endorse the Owner Identity Certificate with its own
-intermediate CA key as soon as it takes ownership of the device. This way device
-attestation can be verified against the Silicon Owners' PKI of choice.
-
-## Terminology
-
-Boot stages:
-
-*   `ROM`: Metal mask ROM, sometimes known as Boot ROM.
-*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
-*   Owner boot stages. This document uses two stages as an example. The Silicon
-    Owner is free to choose other boot configurations.
-    *   `BL0`: Bootloader. Signed by the Silicon Owner.
-    *   `KERNEL`: Signed by the Silicon Owner.
-
-## Attestation Flows
-
-Device attestation is rooted in two asymmetric keys named Creator Identity and
-Owner Identity. The [Asymmetric Keys](#asymmetric-keys) section provides details
-on the cryptographic properties of these keys.
-
-The **[Creator Identity][creator-identity]** is generated at manufacturing time
-and it is endorsed by the Silicon Creator PKI. This key is used to sign
-certificates endorsing the authenticity of the physical device, the ROM and
-`ROM_EXT` configuration (see [Creator custom Extension](#creator-custom-extension)
-for more details), as well as the Owner Identity public key.
-
-The **[Owner Identity][owner-identity]** is generated at ownership transfer time,
-or whenever there is a change to the BL0 software binding tag. The Owner
-Identity is used to attest the owner and BL0 configuration, as well as an
-attestation key used by the Kernel. This key is endorsed by the Creator Identity,
-but can also be endorsed by the Silicon Owner PKI. Endorsement of the Owner
-Identity with the Owner's PKI, is covered in detail in the
-[Owner Personalization](owner_personalization) process
-described in the provisioning specification.
-
-When using a Silicon Owner PKI, the Owner is expected to maintain a device
-registry with Creator Identity to Owner Identity certificate mappings to handle
-certificate revocations issued by the Silicon Creator.
-
-Application specific attestation keys are expected to be endorsed by the Owner
-Identity and managed by the Kernel. The format of the attestation certificate is
-outside the scope of this document.
-
-### Certificate Chains
-
-There are two possible attestation chains. Creator based attestation provides a
-chain to the Creator PKI, which is useful when testing the authenticity of the
-device. After ownership transfer, the Silicon Owner may opt to switch to its own
-PKI by endorsing the Owner Identity. On-Device certificates are stored in a
-flash region accessible to all boot stages including the kernel stage.
-Additional storage is required to store the Owner Identity certificate endorsed
-by the Silicon Owner PKI.
-
-The kernel may opt to extend the attestation chain based on application level
-requirements. See also the [Privacy Considerations](#privacy-considerations)
-section for specific cases in which extending the attestation chain is
-recommended.
-
-<table>
-  <tr>
-    <td>
-      <img src="attestation_fig1.svg" width="" alt="fig1" style="width: 800px;">
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: PKI and Device Certificates</td>
-  </tr>
-</table>
-
-#### Creator Intermediate CA versus Self-Signed Creator Identity Certificates
-
-The Silicon Creator may opt to implement a self-signed Creator Identity
-certificate, which must be verified against a device registry. Self-signed
-certificates can be revoked at a finer level of granularity, and allow for
-in-field certificate rotation as a result of break-glass ROM_EXT updates[^1].
-
-The Silicon Owner only needs to trust the registry during Creator Based
-attestation. This is no different than having to trust the Silicon Creator PKI.
-As a result, the following requirements are imposed on the device registry:
-
-*   The device registry must ensure that only authenticated entities are
-    authorized to insert certificates into the certificate list.
-*   There must be a mechanism for the Silicon Owner to authenticate the device
-    registry and establish a secure connection to query certificates associated
-    with devices transferred to its ownership.
-
-### Attestation Updates
-
-Updates to the attestation chain can be handled via an explicit command during
-the boot sequence, or via an implicit update-after-check mechanism.
-
-#### Attestation command
-
-In this case, attestation updates are handled by a command encoded in a scratch
-register that lives outside of the CPU reset domain. The following table
-describes the sequence of operations required to update the certificate
-attestation chain, which may require generation of new attestation keys.
-
-The attestation command requires a `BOOT_SERVICE_REQUEST` reset to allow the
-`ROM_EXT` component to sign the new attestation chain. This is because
-OpenTitan's isolation model does not provide a way for the `ROM_EXT` to maintain
-its secrets isolated from `BL0` or the kernel layers. Once the `ROM_EXT` is done
-using its secrets, it must wipe them from memory before handing over execution
-to later stages.
-
-<table>
-  <tr>
-    <td><strong>Boot Stage</strong></td>
-    <td><strong>Step</strong></td>
-  </tr>
-  <tr>
-    <td rowspan="2" ><strong>Kernel</strong></td>
-    <td>Queue attestation update command via scratch register write.</td>
-  </tr>
-  <tr>
-    <td>Issue reset with cause `BOOT_SERVICE_REQUEST`.</td>
-  </tr>
-  <tr>
-    <td colspan="2" >System Reset</td>
-  </tr>
-  <tr>
-    <td rowspan="2" ><strong>ROM</strong></td>
-    <td>Verify ROM_EXT and configure key manager ROM stage inputs.</td>
-  </tr>
-  <tr>
-    <td>Jump to ROM_EXT</td>
-  </tr>
-  <tr>
-    <td rowspan="5" ><strong>ROM_EXT</strong></td>
-    <td>Verify BL0 and configure key manager ROM_EXT stage inputs.</td>
-  </tr>
-  <tr>
-    <td>
-Keygen new Owner Identity asymmetric key pair if needed (see
-<a href="#key-generation">Key Generation</a> section).
-
-Note: The Creator Identity key is not derived on regular boot flows. It is only
-derived as a result of the execution of the attestation command.
-    </td>
-  </tr>
-  <tr>
-    <td>
-Issue a new Owner Identity certificate to endorse the Owner Identity key (see
-<a href="#owner-identity-certificate">Owner Identity Certificate</a> section).
-    </td>
-  </tr>
-  <tr>
-    <td>
-The Creator Identity key pair is cleared from memory so that it is not available
-to later boot stages.
-    </td>
-  </tr>
-  <tr>
-    <td>
-Jump to BL0 and promote the attestation update request by updating the scratch
-pad register.
-    </td>
-  </tr>
-  <tr>
-    <td rowspan="5" ><strong>BL0</strong></td>
-    <td>Verify Kernel and configure key manager BL0 stage inputs.</td>
-  </tr>
-  <tr>
-    <td>Keygen new attestation asymmetric key pair.</td>
-  </tr>
-  <tr>
-    <td>
-Issue an attestation certificate to endorse the new attestation key. The
-certificate is signed with the Owner Identity key.
-
-Note: The format of the attestation certificate is controlled by the owner and
-outside of the scope of this specification.
-    </td>
-  </tr>
-  <tr>
-    <td>
-The Owner Identity key pair is cleared from memory so that it is not available
-to later boot stages.
-    </td>
-  </tr>
-  <tr>
-    <td>
-Jump to Kernel and promote the attestation update request by updating the
-scratch pad register.
-    </td>
-  </tr>
-  <tr>
-    <td rowspan="2" ><strong>Kernel</strong></td>
-    <td>Configure key manager state inputs to unwrap attestation key.</td>
-  </tr>
-  <tr>
-    <td>
-Verify attestation chain against attestation key and complete attestation update
-request by clearing the scratch register.
-    </td>
-  </tr>
-</table>
-
-#### Update after check
-
-The `ROM_EXT` may trigger automatic attestation updates by performing an Owner
-Identity attestation check at boot time:
-
-```
-measurement = MAC(KM_VersionedKey,
-  OwnerIdentityCertificate ||
-  OwnerIdentitySeed ||
-  FixedInfo)
-
-update_cert = measurement XOR cached_measurement
-```
-
-The Owner Identity certificate needs to be updated if `update_cert` evaluates to
-a non-zero value. The `measurement` is a MAC operation with the following
-components:
-
-*   `KM_VersionedKey`: Symmetric versioned key extracted from key manager used
-    as MAC key.
-*   `MAC` message is a concatenation of the following data:
-    *   Owner Identity Certificate.
-    *   Owner Identity Seed: Output from the key manager.
-    *   FixedInfo: Data associated with the Owner Identity keygen operation.
-
-### Modes of Operation
-
-OpenTitan supports various modes of operation. The mode of operation is
-encapsulated in the Owner Identity certificate; thus, any given attestation
-chain is only valid for a single mode of operation. Secure boot makes sure that
-the attestation chain is only available if the device state matches the expected
-configuration.
-
-In addition to this, the key manager outputs used to generate the
-[key identifiers](#key-identifiers) depend on system level measurements that
-reflect the mode of operation of the device. The following definitions are
-compatible with the OpenTitan device life cycle definitions.
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Value</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>Not Configured</td>
-    <td>0</td>
-    <td>
-Use when the security mechanisms of the device have not been fully configured.
-    </td>
-  </tr>
-  <tr>
-    <td>Normal</td>
-    <td>1</td>
-    <td>
-Device in PROD lifecycle stage running production software.
-
-*   Secure boot enabled.
-*   Debug functionality in hardware and software are disabled.
-    </td>
-  </tr>
-  <tr>
-    <td>Debug</td>
-    <td>2</td>
-    <td>
-Device in operational state but not fully meeting the criteria of Normal mode of
-operation.
-    </td>
-  </tr>
-</table>
-
-## Provisioning - Key Manager Configuration
-
-The key manager provides identity values which are used to generate both the
-Creator and the Owner identity key pairs. The following sections describe the
-key manager provisioning requirements.
-
-### Creator Secrets and Device Identifier
-
-The Silicon Creator is in charge of provisioning the device root secrets used to
-generate the Creator Identity[^2].
-
-The root secrets are defined as [RootKey][root-key] and
-[DiversificationKey][diversification-key], which are generated by an entropy
-source with a security strength equivalent to the one provided by the key
-manager. Random number generation shall follow the recommendations from
-[NIST 800-90A](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf).
-The number of bits required for each secret is dependent on the key manager
-implementation details.
-
-In addition to this, the Silicon Creator is also responsible for configuring a
-statistically unique non-cryptographic [Device Identifier][device-identifier]
-used to track the device throughout its lifecycle.
-
-#### Manufacturing Flow
-
-Provisioning is performed in a process referred to as personalization, which
-occurs at manufacturing time. Personalization is performed in a secure facility
-that meets the security assurance requirements of the SKU being produced.
-
-There are two alternative methods for personalization described in the sequence
-diagrams below. Implementation details are outside the scope of this document.
-See the [Device Provisioning][device-provisioning] specification for more details.
-
-<table>
-  <tr>
-    <td style="text-align:center">
-      <img src="attestation_fig2.svg" width="" alt="fig2" style="width: 800px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center">
-<strong>Self-Generation</strong>: Secrets are generated inside the device under test and
-never leave the device. The public portion of the Creator Identity is exported
-so that it can be endorsed by the Silicon Creator.
-    </td>
-  </tr>
-</table>
-
-<table>
-  <tr>
-    <td>
-      <img src="attestation_fig3.svg" width="" alt="fig3" style="width: 800px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center">
-<strong>Injection</strong>: This method employs a provisioning appliance, usually built with
-a certified HSM, capable of generating high-quality secrets at a much faster
-rate compared to an OpenTitan device. In this method, the secrets and Creator
-Identity certificate are injected into the device under test.
-    </td>
-  </tr>
-</table>
-
-### Owner Secrets
-
-The ROM Extension is in charge of resetting the
-[OwnerRootSecret](#owner-root-secret) during ownership transfer or updates to
-the BL0 software binding tag. The new value shall be generated by an entropy
-source with a security strength equivalent to the one supported by the key
-manager. The triggering mechanism for updating the value is covered in the
-[Attestation Updates](#attestation-updates) section.
-
-## Asymmetric Keys {#asymmetric-keys}
-
-OpenTitan uses ECDSA attestation keys conformant to
-[FIPS 186-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf). Future
-revisions of this specification may add support for other signature schemes.
-
-### Key Identifiers {#key-identifiers}
-
-Key identifiers are defined as follows, where `SALT_CKI`and `SALT_OKI` are 256b
-values defined in `ROM_EXT` and `BL0` respectively.
-
-```
-CreatorKeyIdentifier = MAC(SALT_CKI, CreatorIdentitySeed)
-OwnerKeyIdentifier = MAC(SALT_OKI, OwnerIdentitySeed)
-```
-
-### Key Generation
-
-The following keygen mechanism employs a DRBG instantiated with a fixed entropy
-pool.
-
-```
-// Seed length compliant with NIST 800-90A section 10.
-// For CTR_DRBG with AES-256 the minimum seed length is 384.
-FIXED_ENTROPY_SEED = least 384b stored in flash
-
-// This mode of operation in hardware is being tracked via a feature
-// request: https://github.com/lowRISC/opentitan/issues/2652
-// The implementation may opt for supporting this in a software
-// based DRBG, as long as performance requirements are met.
-drbg_ctx = DRBG_init(FIXED_ENTROPY_SEED, key_identifier)
-do:
-  c = DRBG_generate(drbg_ctx)
-while c > curve_order - 2
-
-private_key = c + 1
-public_key = private_key * G
-```
-
-Requirements:
-
-*   Key pair generation is based on
-    [FIPS 186-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf)
-    appendix B.4. The example above follows the test candidate key generation
-    method.
-*   The key size shall provide a security strength as recommended by
-    [NIST 800-57](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r4.pdf)
-    part 1, for example:
-
-**Security Strength** | **ECDSA**
---------------------- | ---------
-128                   | NIST_P256
-196                   | NIST_P384
-256                   | NIST_P521
-
-*   The DRBG shall be initialized with a security strength equivalent to the one
-    supported by the asymmetric key conformant to
-    [NIST 800-133](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r1.pdf)
-    section 4.
-*   `key_identifier` is set to the output of the key manager. See the table
-    below for more details.
-*   `FIXED_ENTROPY_SEED` is extracted from the DRBG and stored in flash. The
-    software shall ensure isolation of this value to enforce the visibility
-    settings described in the table below.
-
-<table>
-  <tr>
-    <td>Key</td>
-    <td>Visibility</td>
-    <td>Key Identifier<br><code>key_identifier</code></td>
-    <td>Entropy Generation Time<br><code>FIXED_ENTROPY_SEED</code></td>
-  </tr>
-  <tr>
-    <td>Creator Identity<br>(UDS)</td>
-    <td>ROM_EXT</td>
-    <td><a href="#key-identifiers">CreatorKeyIdentifier</a></td>
-    <td>personalization (Factory)</td>
-  </tr>
-  <tr>
-    <td>Owner Identity<br>(CDI0)</td>
-    <td>BL0</td>
-    <td><a href="#key-identifiers">OwnerKeyIdentifier</a></td>
-    <td>Ownership Transfer</td>
-  </tr>
-</table>
-
-#### Alternative Key Generation Flow
-
-To avoid the need for a `FIXED_ENTROPY_SEED`, the attestation keys may be
-generated directly from a true entropy source fed into the DRBG. Once the key is
-generated, it has to be wrapped for storage with a key extracted from the key
-manager. In this approach, the `key_identifier parameter` does not need to be
-provided by the key manager.
-
-```
-// Initialize DRBG with True RNG source.
-drbg_ctx = DRBG_init(personalization_string=key_identifier)
-do:
-  c = DRBG_generate(drbg_ctx)
-while c > curve_order - 2
-
-private_key = c + 1
-public_key = private_key * G
-
-// Initialize the key manager to extract a storage key.
-kmgr = keymgr_init()
-
-// Advance to required key manager stage (one-way state change)
-keymgr_advance_state(kmgr)
-
-// Generate versioned key used to wrap the |private_key|. The
-// |wrapping_key| may have to be split into shares before loading
-// into AES. The wrapping key is bound to the key manager software
-// binding and max key version configuration.
-wrapping_key = keymgr_generate_vk(kmgr, key_version, key_id)
-
-// The wrapping key shall be stored with additional metadata to
-// facilitate unwrapping.
-wrapped_key = AES_256_CTR(wrapping_key, iv, private_key)
-```
-
-## Certificate Format
-
-Certificates stored in the device adhere to standard X.509 format, meeting the
-profile specification requirements from
-[RFC 5280](https://tools.ietf.org/html/rfc5280), and the [Open DICE][open-dice] profile
-specification.
-
-### Public Key Identifiers
-
-The following key identifiers are generated using a MAC based one-step key
-derivation function as specified in
-[NIST 800-56C](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr1.pdf).
-The `SALT_ID` parameter is configured by software.
-
-<table>
-  <tr>
-   <td><strong>Label</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>Creator PubKey ID<br>(UDS_ID)</td>
-    <td>
-MAC_KDM(SALT_ID, CreatorIdentityPubKey, outlength=20, info="ID")
-    </td>
-  </tr>
-  <tr>
-    <td>Owner PubKeyID<br>(CDI_ID)</td>
-    <td>
-MAC_KDM(SALT_ID, OwnerIdentityPubKey, outlength=20, info="ID")
-    </td>
-  </tr>
-</table>
-
-### Creator Identity Certificate
-
-The TBSCertificate field contains the subject of the certificate and the CA
-issue information. The following fields are required:
-
-<table>
-  <tr>
-    <td><strong>Field</strong></td>
-    <td><strong>Value</strong></td>
-  </tr>
-  <tr>
-    <td>Version</td>
-    <td>3</td>
-  </tr>
-  <tr>
-    <td>Serial Number</td>
-    <td>
-Creator public key identifier in ASN.1 INTEGER format.
-
-The result value shall be a positive integer upto 20 octets in size.
-    </td>
-  </tr>
-  <tr>
-    <td>Signature</td>
-    <td>
-One of the following identifiers matching the signature and digest format.
-
-
-[rfc5758](https://tools.ietf.org/html/rfc5758#section-3.2):
-
-*   ecdsa-with-SHA256
-*   ecdsa-with-SHA384
-*   ecdsa-with-SHA512
-
-[rfc8692](https://tools.ietf.org/html/rfc8692#section-3):
-
-*   id-ecdsa-with-shake256
-
-Note: There are no identifiers available for SHA3-{256|512}.
-    </td>
-  </tr>
-  <tr>
-    <td>Issuer</td>
-    <td>
-X520SerialNumber: id
-
-Where id is set to the creator public key identifier in hex encoded format.
-    </td>
-  </tr>
-  <tr>
-    <td>Validity</td>
-    <td>
-
-Not Before: personalization time in
-[GeneralizedTime](https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2) format.
-
-Not After : 99991231235959Z from
-[rfc5280](https://tools.ietf.org/html/rfc5280#section-4.1.2.5) (no expiration
-time).
-    </td>
-  </tr>
-  <tr>
-    <td>Subject</td>
-    <td>
-X520SerialNumber: id
-
-Where id is set to the creator public key identifier in hex encoded format.
-    </td>
-  </tr>
-  <tr>
-    <td>Subject Public Key Info</td>
-    <td>
-
-Subject public key information fields associated with ECDSA signature algorithm
-as defined in [rfc5480](https://tools.ietf.org/html/rfc5480#section-2).
-    </td>
-  </tr>
-  <tr>
-    <td>Unique Identifiers</td>
-    <td>These fields are omitted.</td>
-  </tr>
-  <tr>
-    <td>Extensions</td>
-    <td>
-Extensions for compatibility with Open DICE profile:
-
-*   `subjectKeyIdentifier` (non-critical). Used to identify certificates that
-    contain a particular public key. Set to the creator public key identifier
-    value.
-*   `keyUsage`> (critical). Describes the purpose of the public key contained in
-    the certificate. Contains only `keyCertSign`.
-*   `basicConstraints` (critical). Identifies whether the subject of the
-    certificate is a CA and the maximum depth of valid certification paths.
-    *   `cA` is set to TRUE
-    *   `pathLenConstraint` not included.
-
-OpenTitan Creator Identity custom extension:
-
-*   `extnID`: TBD. Need to determine if we need an OID for OpenTitan, or if we
-    can allocate a new ID under the Google certificate extension domain
-    1.3.6.1.4.1.11129.2.1.
-*   `critical`: FALSE
-*   `extnValue`: See [creator custom extension](#creator-custom-extension)
-    section.
-    </td>
-  </tr>
-</table>
-
-#### Creator custom Extension {#creator-custom-extension}
-
-This extension is formatted as an ASN.1 SEQUENCE containing device identifiable
-information. See [Privacy Considerations](#privacy-considerations) for
-additional details on how to handle privacy concerns.
-
-<table>
-  <tr>
-    <td><strong>Seq. No.</strong></td>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>0</td>
-    <td>Operational Mode</td>
-    <td>INTEGER</td>
-    <td>
-
-[Operational mode](#operational-mode) of the device.
-    </td>
-  </tr>
-  <tr>
-    <td>1</td>
-    <td>Device Identifier</td>
-    <td>OCTET STR</td>
-    <td>
-Device Identifier.
-    </td>
-  </tr>
-  <tr>
-    <td>2</td>
-    <td>HASH Type</td>
-    <td>OCTET STR</td>
-    <td>HASH type used in measurements.</td>
-  </tr>
-  <tr>
-    <td>3</td>
-    <td>ROM Hash</td>
-    <td>OCTET STR</td>
-    <td>ROM contents hash.</td>
-  </tr>
-  <tr>
-    <td>4</td>
-    <td>ROM EXT Hash</td>
-    <td>OCTET STR</td>
-    <td>ROM EXT contents hash. Includes code manifest.</td>
-  </tr>
-  <tr>
-    <td>5</td>
-    <td>Code Descriptor</td>
-    <td>OCTET STR</td>
-    <td>ROM/ROM_EXT versioning information.</td>
-  </tr>
-</table>
-
-### Owner Identity Certificate
-
-The TBSCertificate field contains the subject of the certificate and the CA
-issue information. The following fields are required:
-
-<table>
-  <tr>
-    <td><strong>Field</strong></td>
-    <td><strong>Value</strong></td>
-  </tr>
-  <tr>
-    <td>Version</td>
-    <td>3</td>
-  </tr>
-  <tr>
-    <td>Serial Number</td>
-    <td>
-Owner public key identifier in ASN.1 INTEGER format.
-
-The result value shall be a positive integer upto 20 octets in size.
-    </td>
-  </tr>
-  <tr>
-    <td>Signature</td>
-    <td>
-One of the following identifiers matching the signature and digest format.
-
-
-[rfc5758](https://tools.ietf.org/html/rfc5758#section-3.2):
-
-*   ecdsa-with-SHA256
-*   ecdsa-with-SHA384
-*   ecdsa-with-SHA512
-
-[rfc8692](https://tools.ietf.org/html/rfc8692#section-3):
-
-*   id-ecdsa-with-shake256
-    </td>
-  </tr>
-  <tr>
-    <td>Issuer</td>
-    <td>
-X520SerialNumber:  id
-
-Where id is set to the creator public key identifier in hex encoded format.
-    </td>
-  </tr>
-  <tr>
-    <td>Validity</td>
-    <td>
-
-Not Before: personalization time in
-[GeneralizedTime](https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2) format.
-
-Not After : 99991231235959Z from
-[rfc5280](https://tools.ietf.org/html/rfc5280#section-4.1.2.5) (no expiration
-time).
-    </td>
-  </tr>
-  <tr>
-    <td>Subject</td>
-    <td>
-X520SerialNumber:  id
-
-Where id is set to the owner public key identifier in hex encoded format.
-    </td>
-  </tr>
-  <tr>
-    <td>Subject Public Key Info</td>
-    <td>
-
-Subject public key information fields associated with ECDSA signature algorithm
-as defined in [rfc5480](https://tools.ietf.org/html/rfc5480#section-2).
-    </td>
-  </tr>
-  <tr>
-    <td>Unique Identifiers</td>
-    <td>These fields are omitted.</td>
-  </tr>
-  <tr>
-    <td>Extensions</td>
-    <td>
-Extensions for compatibility with Open DICE profile:
-
-*   `authorityKeyIdentifier` (non-critical). Used to identify certificates that
-    contain a particular public key.
-    *   `keyIdentifier` set to the creator public key identifier value. This is
-         the only field needed.
-*   `subjectKeyIdentifier` (non-critical). Used to identify certificates that
-    contain a particular public key. Set to the owner public key identifier
-    value.
-*   `keyUsage` (critical). Describes the purpose of the public key contained in
-    the certificate. Contains only `keyCertSign`.
-*   `basicConstraints` (critical). Identifies whether the subject of the
-    certificate of the is a CA and the maximum depth of valid certification
-    paths.
-    *   `CA` is set to TRUE
-    *   `pathLenConstraint` not included.
-
-OpenTitan Owner Identity custom extension:
-
-*   `extnID`: TBD. Need to determine if we need an OID for OpenTitan, or if we
-    can allocate a new ID under the Google certificate extension domain
-    1.3.6.1.4.1.11129.2.1.
-*   `critical`: FALSE
-*   `extnValue`: See [owner custom extension](#owner-custom-extension) section.
-
-This certificate may also contain the
-[creator custom extension](#creator-custom-extension) if the Owner decides to
-endorse this certificate with its own PKI.
-    </td>
-  </tr>
-</table>
-
-#### Owner Custom Extension
-
-ASN.1 SEQUENCE containing the following fields. The owner can extend this as
-needed.
-
-<table>
-  <tr>
-    <td><strong>Seq. No.</strong></td>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>0</td>
-    <td>Code Descriptor</td>
-    <td>OCTET STR</td>
-    <td>BL0 version and key manager software binding tag information.</td>
-  </tr>
-</table>
-
-## Lightweight Certificate Format
-
-The Silicon Creator may opt to implement attestation signatures over a binary
-manifest containing the endorsed key as well as the measurements described in
-the [creator](#creator-custom-extension) and [owner](#owner-custom-extension)
-custom extensions.
-
-The binary manifest format must be standardized at the OpenTitan project level
-to ensure interoperability between silicon and software layers.
-
-## Privacy Considerations {#privacy-considerations}
-
-The Silicon Owner software must address any privacy concerns associated with the
-use of device identifiable information.
-
-The Silicon Owner certificate, for example, is meant to be used exclusively with
-the Silicon Owner's infrastructure. Use cases requiring multiple identities or
-attestation services for multiple backends are required to derive
-<span style="text-decoration:underline;">application level identities and
-certificates</span>. Such application level identities must fulfill the privacy
-requirements of their use cases.
-
-OpenTitan's key manager provides a mechanism to derive multiple application
-level keys with lineage to the Owner Identity, without making them
-distinguishable from any random key. These properties allow the software
-implementation to bind the OpenTitan attestation to any application level
-attestation flows without precluding the implementation of additional privacy
-measures at the application level.
-
-<!-- Footnotes themselves at the bottom. -->
-## Notes
-
-[^1]: In-field ROM_EXT updates performed by a Silicon Owner may invalidate the
-Creator Identity and its associated certificate. Without a federated device
-registry, new silicon owners are left to trust the Creator Identity certificate
-provided by the current owner to verify the authenticity of the device.
-
-[^2]: The Creator Identity is referred to as the Unique Device Secret (UDS) in
-DICE terminology.
-
-<!-- References -->
-[open-dice]: https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md
-
-[device-identifier]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#device-identifier
-[creator-identity]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#creator-identity
-[owner-identity]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#owner-identity
-[owner-root-secret]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#owner-root-secret
-[root-key]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#root-key
-[diversification-key]:{{< relref "/doc/security/specs/identities_and_root_keys" >}}#diversification-key
-
-[device-provisioning]: {{< relref "/doc/security/specs/device_provisioning" >}}
-[owner-personalization]: {{< relref "/doc/security/specs/device_provisioning" >}}#owner_personalization
diff --git a/doc/security/specs/device_life_cycle/README.md b/doc/security/specs/device_life_cycle/README.md
new file mode 100644
index 0000000..9634672
--- /dev/null
+++ b/doc/security/specs/device_life_cycle/README.md
@@ -0,0 +1,656 @@
+# Device Life Cycle
+
+## Overview
+
+This document describes the OpenTitan device operational states supported for
+manufacturing, provisioning, regular operation and RMA[^1].
+
+Provisioning refers to the process in which a device creates a unique and
+trusted identity.
+
+Manufacturing refers to the process in which a device is tested and prepared for
+silicon correctness prior to production.
+
+The technical specification of the corresponding life cycle controller IP can be found [here](../../../../hw/ip/lc_ctrl/README.md).
+
+## Background
+
+First see [here](../../logical_security_model/README.md) for
+OpenTitan's logical entity breakdowns.
+
+OpenTitan implements standard device life cycle manufacturing states which are
+configured via OTP[^2] memory. These states allow the **Silicon Creator** to
+manage the state of the device as it is being manufactured and provisioned for
+shipment.
+
+An additional set of life cycle states are used to encode the device ownership
+state. A device that has been personalized with a unique Silicon Creator
+Identity, can be provisioned with a Silicon Owner Identity and Credentials. This
+enables the Silicon Owner to run signed code on the device. The process of
+assigning a Silicon Owner to a device is referred to as Ownership Transfer.
+Depending on the product SKU configuration, a device may support a single and
+permanent ownership transfer or multiple ones. The multiple ownership transfer
+use case has additional state control requirements which are described in more
+detail in later sections.
+
+## Architecture
+
+Life cycle states are defined by a combination of OTP and flash. OTP state
+transitions cannot be reverted, while flash defined states are meant to be
+'flexible', especially when ownership transfer is required. This implies that
+when OTP states are transitioned from A to B, we cannot go back to A from B.
+However, it is still possible to transition from B to C, assuming such a
+transition is legal and allowed by the life cycle definition.
+
+See the table below for more details.
+
+<!--SVG for flow chart below in google docs at
+https://docs.google.com/drawings/d/1kENEhJzrWDkzonu8ZC6SKDRtjETyH6S4SslcO1RpVPM/edit?usp=sharing-->
+<img src="device_life_cycle_fig1.svg" alt="Fig1" style="width: 800px;"/>
+
+In the diagram above,
+
+*   **BLACK** arcs represent unconditional OTP transitions. Specifically, this
+    means that the state will transition as long as it was requested; no
+    additional authorization is necessary.
+*   **ORANGE** arcs represent conditional flash transitions. Specifically, the
+    transition requires either specific value patterns or device specific values
+    to be programmed. Since flash states are managed by software, this is not
+    further covered in this specification.
+*   **RED** arcs represent conditional transitions. These transitions require
+    the user to supply specific device-unique values and meet specific
+    conditions. These transitions cannot be reversed.
+
+Device life cycle is split into two domains.
+
+*   Manufacturing state
+*   Identity & Ownership state
+
+Manufacturing state largely refers to the presence of debug functions and
+liveness of the device. This is closely connected to manufacturing testing
+status as well as desired debug visibility during development and production.
+
+Identity & Ownership state refers to the state of provisioned identities as
+defined by the provisioning flow.
+
+The life cycle management system splits the states into two because there are
+attributes defined by each that are not strictly related most of the time. By
+decoupling a single definition into two, more flexibility is allowed in the
+system.
+
+In addition, while certain states are defined by OTP (thus hard), other states
+are defined by flash. This gives the latter more flexibility in changing values
+to support the provisioning flow ownership transfer model.
+
+For example, transitions into the DEV / PROD / RMA manufacturing states affect
+OTP states and therefore cannot be reversed once completed.
+
+LOCKED_OWNERSHIP / UNLOCKED_OWNERSHIP identity states are defined in flash and thus can be reversed
+under the right circumstances.
+
+Below, each state is described in detail.
+
+### Manufacturing States
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>RAW</td>
+    <td>OTP</td>
+    <td>
+
+This is the default state of the OTP.
+
+During this state, no functions other than transition to TEST_UNLOCKED0 are
+available.
+
+The token authorizing the transition from RAW to TEST_UNLOCKED0 is a value that
+is secret global to all devices.  This is known as the RAW_UNLOCK token.
+    </td>
+  </tr>
+  <tr>
+    <td>TEST_LOCKED{N}</td>
+    <td>OTP</td>
+    <td>
+
+TEST_LOCKED{N} states have identical functionality to RAW state and serve as a
+way for the Silicon Creator to protect devices in transit.
+
+It is not possible to provision OTP root secrets during this state.  This
+is enforced by hardware and is implementation defined.
+
+To progress from a TEST_LOCKED state to another TEST_UNLOCKED state, a TEST_UNLOCK
+token is required.
+    </td>
+  </tr>
+  <tr>
+    <td>TEST_UNLOCKED{N}</td>
+    <td>OTP</td>
+    <td>
+
+Transition from RAW state using OTP write.
+
+This state is used for <strong>manufacturing and production testing.</strong>
+
+During this state:
+
+  *   CPU execution is enabled
+  *   NVM backdoor functions are enabled
+  *   Debug functions are enabled
+  *   DFT functions are enabled
+
+Note: during this state it is not possible to provision specific OTP root
+secrets. This will be enforced by hardware.
+
+It is expected that during TEST_UNLOCKED0 the TEST_UNLOCK and TEST_EXIT tokens will be
+provisioned into OTP.
+
+Once provisioned, these tokens are no longer readable by software.
+    </td>
+  </tr>
+  <tr>
+    <td>PROD</td>
+    <td>OTP</td>
+    <td>
+
+Transition from TEST_UNLOCKED or TEST_LOCKED state via OTP write. PROD is a mutually exclusive state to
+DEV and PROD_END.
+
+To enter this state, a TEST_EXIT token is required.
+
+This state is used both for provisioning and mission mode.
+
+During this state:
+
+  *   CPU execution is enabled
+  *   NVM backdoor functions are disabled
+  *   Debug functions are disabled
+  *   DFT functions are disabled
+    </td>
+  </tr>
+  <tr>
+    <td>PROD_END</td>
+    <td>OTP</td>
+    <td>
+
+This state is identical in functionality to PROD, except the device is never
+allowed to transition to RMA state.
+
+To enter this state, a TEST_EXIT token is required.
+   </td>
+  </tr>
+  <tr>
+   <td>DEV</td>
+   <td>OTP</td>
+   <td>
+
+Transition from TEST_UNLOCKED state via OTP write. This is a mutually exclusive state to
+PROD and PROD_END.
+
+To enter this state, a TEST_EXIT token is required.
+
+This state is used for developing provisioning and mission mode software.
+
+During this state
+
+  *   CPU execution is enabled
+  *   NVM backdoor functions are disabled
+  *   Debug functions are enabled
+  *   DFT functions are disabled
+    </td>
+  </tr>
+  <tr>
+    <td>RMA</td>
+    <td>OTP</td>
+    <td>
+
+Transition from TEST_UNLOCKED / PROD / DEV via OTP write. It is not possible to reach
+this state from PROD_END.
+
+When transitioning from PROD or DEV, an RMA_UNLOCK token is required.
+
+When transitioning from TEST_UNLOCKED, no RMA_UNLOCK token is required.
+
+A hardware-backed mechanism will erase all owner flash content before RMA
+transition is allowed. This includes the isolation owner flash partitions as
+well as any owner code. Once erasure is confirmed and verified, RMA transition
+will proceed.
+
+During this state
+
+  *   CPU execution is enabled
+  *   NVM backdoor is enabled
+  *   Debug functions are enabled
+  *   DFT functions are enabled
+    </td>
+  </tr>
+  <tr>
+    <td>SCRAP</td>
+    <td>OTP</td>
+    <td>
+
+Transition from any manufacturing state via OTP write.
+
+During SCRAP state the device is completely dead.  All functions, including CPU
+execution are disabled.
+
+No owner consent is required to transition to SCRAP.
+
+Note also, SCRAP is meant as an EOL manufacturing state.  Transition to this
+state is always purposeful and persistent, it is NOT part of the device's
+native security countermeasure to transition to this state.
+    </td>
+  </tr>
+  <tr>
+    <td>INVALID</td>
+    <td>OTP</td>
+    <td>
+
+Invalid is any combination of OTP values that do not fall in the categories
+above.  It is the "default" state of life cycle when no other conditions match.
+
+Functionally, INVALID is identical to SCRAP in that no functions are allowed
+and no transitions are allowed.
+
+A user is not able to explicitly transition into INVALID (unlike SCRAP),
+instead, INVALID is meant to cover in-field corruptions, failures or active
+attacks.
+    </td>
+  </tr>
+</table>
+
+The various functionalities controlled by life cycle states can be broken into
+the following categories:
+
+*   DFT Functionality
+    *   Refers to the ability to run any DFT function. (In this context, DFT
+        functions include scan-based manufacturing testing, etc., as opposed to
+        JTAG-based CPU debug.)
+*   NVM backdoor access
+    *   Certain NVM modules can be backdoor accessed from alternate paths
+        *   For example, there may be a functional bit-banging path that
+            bypasses the normal protection controls.
+        *   For example, there may be a pin connected path used to debug the NVM
+            modules meant only for critical debug.
+    *   If these paths are DFT-based, they are absorbed into the category above
+*   Debug
+    *   Refers to both invasive CPU debug (JTAG) and non-invasive system debug
+        (debug output bus or analog test points)
+*   CPU Functionality
+    *   Refers to the ability to run ROM or any custom code
+
+In addition, the life cycle states are mixed into the key manager, ensuring that
+each state will diversify into a different key tree.
+
+The table below summarizes the availability of various functions in each state.
+A `"Y"` mark means the function is directly enabled by hardware during that
+state. A `"grey"` box means a particular function is not available during that
+state.
+
+<table style="text-align:center">
+  <tr>
+    <td style="text-align:left"><strong>Functions</strong></td>
+    <td><strong>DFT_EN</strong></td>
+    <td><strong>NVM_DEBUG_EN</strong></td>
+    <td><strong>HW_DEBUG_EN</strong></td>
+    <td><strong>CPU_EN</strong></td>
+    <td><strong>Change State</strong></td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RAW</td>
+    <td colspan="4" style="background:#dadce0"> </td>
+    <td rowspan="7" >See table below</td>
+  </tr>
+  <tr>
+   <td style="text-align:left">TEST_LOCKED</td>
+   <td colspan="4" style="background:#dadce0"> </td>
+  </tr>
+  <tr>
+    <td style="text-align:left">TEST_UNLOCKED</td>
+    <td>Y</td><td>Y</td><td>Y</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">DEV</td>
+    <td colspan="2" style="background:#dadce0"> </td><td>Y</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">PROD</td>
+    <td colspan="3" style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">PROD_END</td>
+    <td colspan="3" style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RMA</td>
+    <td>Y</td><td>Y</td><td>Y</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">SCRAP</td>
+    <td colspan="5" style="background:#dadce0"> </td>
+  </tr>
+  <tr>
+    <td style="text-align:left">INVALID</td>
+    <td colspan="5" style="background:#dadce0"> </td>
+  </tr>
+</table>
+
+The following table shows the allowed state transitions in each state. INVALID
+state is not an explicit transition destination, therefore it is not listed as
+one of the target states. The `"C"` marks represent conditional transitions
+qualified by the token table that follows.
+
+<table style="text-align:center">
+  <tr>
+    <td style="text-align:left"><strong>States</strong></td>
+    <td><strong>RAW</strong></td>
+    <td><strong>TEST LOCKED</strong></td>
+    <td><strong>TEST_UNLOCKED</strong></td>
+    <td><strong>DEV</strong></td>
+    <td><strong>PROD</strong></td>
+    <td><strong>PROD_END</strong></td>
+    <td><strong>RMA</strong></td>
+    <td><strong>SCRAP</strong></td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RAW</td>
+    <td colspan="2" style="background:#dadce0"> </td><td>C</td><td colspan="4" style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">TEST_LOCKED</td>
+    <td colspan="2" style="background:#dadce0"> </td><td>C</td><td>C</td><td>C</td><td>C</td><td style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">TEST_UNLOCKED</td>
+    <td style="background:#dadce0"> </td><td>Y</td><td style="background:#dadce0"> </td><td>C</td><td>C</td><td>C</td><td>Y</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">DEV</td>
+    <td colspan="6" style="background:#dadce0"> </td><td>C</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">PROD</td>
+    <td colspan="6" style="background:#dadce0"> </td><td>C</td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">PROD_END</td>
+    <td colspan="7" style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RMA</td>
+    <td colspan="7" style="background:#dadce0"> </td><td>Y</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">SCRAP</td>
+    <td colspan="8" style="background:#dadce0"> </td>
+  </tr>
+  <tr>
+    <td style="text-align:left">INVALID</td>
+    <td colspan="8" style="background:#dadce0"> </td>
+  </tr>
+</table>
+
+The following table enumerates the tokens and their properties. The storage
+format is implementation specific.
+
+<table style="text-align:center">
+  <tr>
+    <td><strong style="text-align:left">Token</strong></td>
+    <td><strong>Storage</strong></td>
+    <td><strong>Device Unique?</strong></td>
+    <td><strong>Usage</strong></td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RAW_UNLOCK</td>
+    <td>RTL</td>
+    <td>No</td>
+    <td>From RAW->TEST_UNLOCKED0</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">TEST_UNLOCK</td>
+    <td>OTP</td>
+    <td>Silicon creator choice</td>
+    <td>From TEST_LOCKED{N} to TEST_UNLOCKED{N}</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">TEST_EXIT</td>
+    <td>OTP</td>
+    <td>Silicon creator choice</td>
+    <td>From any TEST_UNLOCKED state to DEV, PROD_END or PROD</td>
+  </tr>
+  <tr>
+    <td style="text-align:left">RMA_UNLOCK</td>
+    <td>OTP</td>
+    <td>Yes</td>
+    <td>From PROD/DEV to RMA</td>
+  </tr>
+</table>
+
+#### RMA Unlock
+
+When in either PROD or DEV state, the device can be transitioned into RMA state
+to re-enable full debug functionality.
+
+A device unique RMA token is required to enter RMA. This RMA token is under the
+control of the Silicon Creator. To prevent abuse of this authority, the current
+manufacturing state is mixed by hardware directly into the key manager. This
+ensures that the Silicon Creator cannot arbitrarily put a device into RMA mode
+and impersonate a victim silicon owner. It also ensures that the Silicon Creator
+cannot accidentally leak any content wrapped by the owner under key manager, as
+the keys can never be re-created.
+
+Further, before transitioning to RMA, a hardware backed mechanism will erase all
+existing owner content in flash. In addition to the key manager mixing above,
+this wipe further limits the Creator's ability to abuse the existing device.
+
+### Identity & Ownership States
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>BLANK</td>
+    <td>OTP</td>
+    <td>
+
+Initial identity and ownership state.  There is no hardware identity or
+ownership provisioned
+    </td>
+  </tr>
+  <tr>
+   <td>CREATOR<br>PERSONALIZED</td>
+   <td>OTP</td>
+   <td>
+
+The hardware relevant information has been provisioned.  This includes:
+
+  *   RMA_UNLOCK Token
+  *   Creator Root Key
+  *   Creator Diversification Key
+
+Once the identity state advances to CREATOR_PERSONALIZED, creator provisioned
+secrets (see Identity flow) are no longer accessible to software and are
+directly consumed by hardware.
+    </td>
+  </tr>
+  <tr>
+    <td>UNLOCKED_OWNERSHIP</td>
+    <td>Flash</td>
+    <td>
+
+This state represents a device which contains a Creator Identity but does not
+yet have a Silicon Owner provisioned.
+
+Since this state is software managed, software is free to define a different
+state.
+    </td>
+  </tr>
+  <tr>
+    <td>LOCKED_OWNERSHIP</td>
+    <td>Flash</td>
+    <td>
+
+This state represents a device which contains both a Creator Identity and a
+Silicon Owner.
+
+A device in the LOCKED_OWNERSHIP state can transition back to UNLOCKED_OWNERSHIP when it is explicitly
+unbound. The unbinding process is described separately.
+
+Since this state is software managed, software is free to define a different
+state.
+    </td>
+  </tr>
+  <tr>
+    <td>EOL</td>
+   <td>Flash</td>
+   <td>
+
+This state represents a device that has reached its identity EOL.
+
+Since this state is software managed, software is free to define a different
+state.
+    </td>
+  </tr>
+</table>
+
+### States and Isolation Properties
+
+The manufacturing state is used as part of the key generation and derivation
+process. (Please see key manager and life cycle implementation for more
+details). By doing this, the device ensures that states with invasive debug
+capabilities, typically used for debug, manufacturing testing and RMA, cannot be
+used to expose device secrets during mission mode.
+
+The identity states are not used as part of the key generation process. Instead
+they are used as metadata to control software accessibility to the OTP
+partitions used to hold device unique secrets provisioned during manufacturing.
+
+*   When identity state is BLANK, software can access the relevant OTP
+    partitions
+*   When identity state is PERSONALIZED, software can no longer access the
+    relevant OTP partitions
+
+Please see life cycle and OTP implementation for more details
+
+### State Conditionals
+
+Entry into RMA should be conditioned upon application of the RMA\_UNLOCK token.
+
+Entry into TERMINATED / SCRAP is unconditional. If the correct command is
+presented, the transition shall not be stopped.
+
+### State Dependencies
+
+There is one manufacturing and identity state dependency.
+
+*   When the manufacturing state is **RAW** or **TEST_UNLOCKED**, the provisioning of
+    creator entropy is disabled and identity state SHALL be **BLANK.**
+*   Once manufacturing state transitions to **DEV / PROD / PROD\_END**,
+    provisioning is then enabled and the identity state is allowed to transition
+    from **BLANK**.
+
+## Functionality not Supported
+
+#### PROD Volatile Debug Unlock
+
+There will be no debug unlock after the device enters production states. To
+properly implement volatile debug unlock, the debug period must be both time
+bound and power cycle bound. This means that in ROM or ROM\_ext, we are required
+to setup functionality in non-volatile storage to track how many times the
+device has been rebooted and a locked timer to check how long the device has
+been alive. Additionally, in order to ensure that the same debug payload is not
+replayed, it must be signed through a challenge-response scheme where the device
+must first provide a nonce and then verify the signed payload.
+
+Lastly, when debug unlock is granted, the device can be hijacked by an external
+entity without the processor being aware; this makes it too dangerous a
+functionality to exist in PROD.
+
+## Compatibility with Standards and Protection Profiles
+
+### Security IC Platform Protection Profile with Augmentation Packages
+
+OpenTitan's Device Life Cycle design may support the PP084 TOE[^3] life cycle
+phase requirements including the composite product life cycle phases.
+
+The list of TOE life cycle phases is available here for reference, See
+[BSI-CC-PP-0084-2014 section 1.2.3](https://www.commoncriteriaportal.org/files/ppfiles/pp0084b_pdf.pdf)
+for more details.
+
+<table>
+  <tr>
+    <td><strong>TOE<br>Manufacturer</strong></td>
+    <td><strong>Composite</strong><br><strong>Manufacturer</strong></td>
+    <td><strong>Life Cycle Phase</strong></td>
+    <td><strong>Entity</strong></td>
+  </tr>
+  <tr>
+    <td> </td>
+    <td style="text-align:center">Y</td>
+    <td>1: Embedded Software Development</td>
+    <td>IC Embedded Software Developer</td>
+  </tr>
+  <tr>
+    <td style="text-align:center">Y</td>
+    <td> </td>
+    <td>2: IC Development</td>
+    <td>IC Developer</td>
+  </tr>
+  <tr>
+    <td style="text-align:center">Y</td>
+    <td> </td>
+    <td>3: IC Manufacturing</td>
+   <td>IC Manufacturer</td>
+  </tr>
+  <tr>
+    <td colspan="2" style="text-align:center">Y</td>
+    <td>4: IC Packaging</td>
+    <td>IC Packaging Manufacturer</td>
+  </tr>
+  <tr>
+    <td> </td>
+    <td style="text-align:center">Y</td>
+    <td>5: Composite Product Integration</td>
+    <td>Composite Product<br>Integrator</td>
+  </tr>
+  <tr>
+    <td> </td>
+    <td style="text-align:center">Y</td>
+    <td>6: Personalisation</td>
+    <td>Personaliser</td>
+  </tr>
+  <tr>
+    <td> </td>
+    <td style="text-align:center">Y</td>
+    <td> </td>
+    <td>Composite Product Issuer</td>
+  </tr>
+  <tr>
+    <td> </td>
+    <td> </td>
+    <td>7: Operational Usage </td>
+    <td>Consumer of Composite<br>Product (End-consumer)</td>
+  </tr>
+</table>
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+[^1]: RMA ("Return Material Authorization") refers to the process in which a
+    device is returned to the Silicon Creator for more triage and debugging.
+    It represents a terminal state of the device, as it cannot be returned to
+    production afterwards.
+
+[^2]: One Time Programmable Memory
+
+[^3]: "Security IC Platform Protection Profile with Augmentation Packages
+    (BSI-CC-PP-0084-2014)"
+    https://www.commoncriteriaportal.org/files/ppfiles/pp0084b_pdf.pdf.
diff --git a/doc/security/specs/device_life_cycle/_index.md b/doc/security/specs/device_life_cycle/_index.md
deleted file mode 100644
index f144d54..0000000
--- a/doc/security/specs/device_life_cycle/_index.md
+++ /dev/null
@@ -1,658 +0,0 @@
----
-title: "Device Life Cycle"
----
-
-## Overview
-
-This document describes the OpenTitan device operational states supported for
-manufacturing, provisioning, regular operation and RMA[^1].
-
-Provisioning refers to the process in which a device creates a unique and
-trusted identity.
-
-Manufacturing refers to the process in which a device is tested and prepared for
-silicon correctness prior to production.
-
-The technical specification of the corresponding life cycle controller IP can be found [here]({{< relref "hw/ip/lc_ctrl/doc" >}}).
-
-## Background
-
-First see [here]({{< relref "doc/security/logical_security_model" >}}) for
-OpenTitan's logical entity breakdowns.
-
-OpenTitan implements standard device life cycle manufacturing states which are
-configured via OTP[^2] memory. These states allow the **Silicon Creator** to
-manage the state of the device as it is being manufactured and provisioned for
-shipment.
-
-An additional set of life cycle states are used to encode the device ownership
-state. A device that has been personalized with a unique Silicon Creator
-Identity, can be provisioned with a Silicon Owner Identity and Credentials. This
-enables the Silicon Owner to run signed code on the device. The process of
-assigning a Silicon Owner to a device is referred to as Ownership Transfer.
-Depending on the product SKU configuration, a device may support a single and
-permanent ownership transfer or multiple ones. The multiple ownership transfer
-use case has additional state control requirements which are described in more
-detail in later sections.
-
-## Architecture
-
-Life cycle states are defined by a combination of OTP and flash. OTP state
-transitions cannot be reverted, while flash defined states are meant to be
-'flexible', especially when ownership transfer is required. This implies that
-when OTP states are transitioned from A to B, we cannot go back to A from B.
-However, it is still possible to transition from B to C, assuming such a
-transition is legal and allowed by the life cycle definition.
-
-See the table below for more details.
-
-<!--SVG for flow chart below in google docs at
-https://docs.google.com/drawings/d/1kENEhJzrWDkzonu8ZC6SKDRtjETyH6S4SslcO1RpVPM/edit?usp=sharing-->
-<img src="device_life_cycle_fig1.svg" alt="Fig1" style="width: 800px;"/>
-
-In the diagram above,
-
-*   **BLACK** arcs represent unconditional OTP transitions. Specifically, this
-    means that the state will transition as long as it was requested; no
-    additional authorization is necessary.
-*   **ORANGE** arcs represent conditional flash transitions. Specifically, the
-    transition requires either specific value patterns or device specific values
-    to be programmed. Since flash states are managed by software, this is not
-    further covered in this specification.
-*   **RED** arcs represent conditional transitions. These transitions require
-    the user to supply specific device-unique values and meet specific
-    conditions. These transitions cannot be reversed.
-
-Device life cycle is split into two domains.
-
-*   Manufacturing state
-*   Identity & Ownership state
-
-Manufacturing state largely refers to the presence of debug functions and
-liveness of the device. This is closely connected to manufacturing testing
-status as well as desired debug visibility during development and production.
-
-Identity & Ownership state refers to the state of provisioned identities as
-defined by the provisioning flow.
-
-The life cycle management system splits the states into two because there are
-attributes defined by each that are not strictly related most of the time. By
-decoupling a single definition into two, more flexibility is allowed in the
-system.
-
-In addition, while certain states are defined by OTP (thus hard), other states
-are defined by flash. This gives the latter more flexibility in changing values
-to support the provisioning flow ownership transfer model.
-
-For example, transitions into the DEV / PROD / RMA manufacturing states affect
-OTP states and therefore cannot be reversed once completed.
-
-LOCKED_OWNERSHIP / UNLOCKED_OWNERSHIP identity states are defined in flash and thus can be reversed
-under the right circumstances.
-
-Below, each state is described in detail.
-
-### Manufacturing States
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>RAW</td>
-    <td>OTP</td>
-    <td>
-
-This is the default state of the OTP.
-
-During this state, no functions other than transition to TEST_UNLOCKED0 are
-available.
-
-The token authorizing the transition from RAW to TEST_UNLOCKED0 is a value that
-is secret global to all devices.  This is known as the RAW_UNLOCK token.
-    </td>
-  </tr>
-  <tr>
-    <td>TEST_LOCKED{N}</td>
-    <td>OTP</td>
-    <td>
-
-TEST_LOCKED{N} states have identical functionality to RAW state and serve as a
-way for the Silicon Creator to protect devices in transit.
-
-It is not possible to provision OTP root secrets during this state.  This
-is enforced by hardware and is implementation defined.
-
-To progress from a TEST_LOCKED state to another TEST_UNLOCKED state, a TEST_UNLOCK
-token is required.
-    </td>
-  </tr>
-  <tr>
-    <td>TEST_UNLOCKED{N}</td>
-    <td>OTP</td>
-    <td>
-
-Transition from RAW state using OTP write.
-
-This state is used for <strong>manufacturing and production testing.</strong>
-
-During this state:
-
-  *   CPU execution is enabled
-  *   NVM backdoor functions are enabled
-  *   Debug functions are enabled
-  *   DFT functions are enabled
-
-Note: during this state it is not possible to provision specific OTP root
-secrets. This will be enforced by hardware.
-
-It is expected that during TEST_UNLOCKED0 the TEST_UNLOCK and TEST_EXIT tokens will be
-provisioned into OTP.
-
-Once provisioned, these tokens are no longer readable by software.
-    </td>
-  </tr>
-  <tr>
-    <td>PROD</td>
-    <td>OTP</td>
-    <td>
-
-Transition from TEST_UNLOCKED state via OTP write. This is a mutually exclusive state to
-DEV and PROD_END.
-
-To enter this state, a TEST_EXIT token is required.
-
-This state is used both for provisioning and mission mode.
-
-During this state:
-
-  *   CPU execution is enabled
-  *   NVM backdoor functions are disabled
-  *   Debug functions are disabled
-  *   DFT functions are disabled
-    </td>
-  </tr>
-  <tr>
-    <td>PROD_END</td>
-    <td>OTP</td>
-    <td>
-
-This state is identical in functionality to PROD, except the device is never
-allowed to transition to RMA state.
-
-To enter this state, a TEST_EXIT token is required.
-   </td>
-  </tr>
-  <tr>
-   <td>DEV</td>
-   <td>OTP</td>
-   <td>
-
-Transition from TEST_UNLOCKED state via OTP write. This is a mutually exclusive state to
-PROD and PROD_END.
-
-To enter this state, a TEST_EXIT token is required.
-
-This state is used for developing provisioning and mission mode software.
-
-During this state
-
-  *   CPU execution is enabled
-  *   NVM backdoor functions are disabled
-  *   Debug functions are enabled
-  *   DFT functions are disabled
-    </td>
-  </tr>
-  <tr>
-    <td>RMA</td>
-    <td>OTP</td>
-    <td>
-
-Transition from TEST_UNLOCKED / PROD / DEV via OTP write. It is not possible to reach
-this state from PROD_END.
-
-When transitioning from PROD or DEV, an RMA_UNLOCK token is required.
-
-When transitioning from TEST_UNLOCKED, no RMA_UNLOCK token is required.
-
-A hardware-backed mechanism will erase all owner flash content before RMA
-transition is allowed. This includes the isolation owner flash partitions as
-well as any owner code. Once erasure is confirmed and verified, RMA transition
-will proceed.
-
-During this state
-
-  *   CPU execution is enabled
-  *   NVM backdoor is enabled
-  *   Debug functions are enabled
-  *   DFT functions are enabled
-    </td>
-  </tr>
-  <tr>
-    <td>SCRAP</td>
-    <td>OTP</td>
-    <td>
-
-Transition from any manufacturing state via OTP write.
-
-During SCRAP state the device is completely dead.  All functions, including CPU
-execution are disabled.
-
-No owner consent is required to transition to SCRAP.
-
-Note also, SCRAP is meant as an EOL manufacturing state.  Transition to this
-state is always purposeful and persistent, it is NOT part of the device's
-native security countermeasure to transition to this state.
-    </td>
-  </tr>
-  <tr>
-    <td>INVALID</td>
-    <td>OTP</td>
-    <td>
-
-Invalid is any combination of OTP values that do not fall in the categories
-above.  It is the "default" state of life cycle when no other conditions match.
-
-Functionally, INVALID is identical to SCRAP in that no functions are allowed
-and no transitions are allowed.
-
-A user is not able to explicitly transition into INVALID (unlike SCRAP),
-instead, INVALID is meant to cover in-field corruptions, failures or active
-attacks.
-    </td>
-  </tr>
-</table>
-
-The various functionalities controlled by life cycle states can be broken into
-the following categories:
-
-*   DFT Functionality
-    *   Refers to the ability to run any DFT function. (In this context, DFT
-        functions include scan-based manufacturing testing, etc., as opposed to
-        JTAG-based CPU debug.)
-*   NVM backdoor access
-    *   Certain NVM modules can be backdoor accessed from alternate paths
-        *   For example, there may be a functional bit-banging path that
-            bypasses the normal protection controls.
-        *   For example, there may be a pin connected path used to debug the NVM
-            modules meant only for critical debug.
-    *   If these paths are DFT-based, they are absorbed into the category above
-*   Debug
-    *   Refers to both invasive CPU debug (JTAG) and non-invasive system debug
-        (debug output bus or analog test points)
-*   CPU Functionality
-    *   Refers to the ability to run ROM or any custom code
-
-In addition, the life cycle states are mixed into the key manager, ensuring that
-each state will diversify into a different key tree.
-
-The table below summarizes the availability of various functions in each state.
-A `"Y"` mark means the function is directly enabled by hardware during that
-state. A `"grey"` box means a particular function is not available during that
-state.
-
-<table style="text-align:center">
-  <tr>
-    <td style="text-align:left"><strong>Functions</strong></td>
-    <td><strong>DFT_EN</strong></td>
-    <td><strong>NVM_DEBUG_EN</strong></td>
-    <td><strong>HW_DEBUG_EN</strong></td>
-    <td><strong>CPU_EN</strong></td>
-    <td><strong>Change State</strong></td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RAW</td>
-    <td colspan="4" style="background:#dadce0"> </td>
-    <td rowspan="7" >See table below</td>
-  </tr>
-  <tr>
-   <td style="text-align:left">TEST_LOCKED</td>
-   <td colspan="4" style="background:#dadce0"> </td>
-  </tr>
-  <tr>
-    <td style="text-align:left">TEST_UNLOCKED</td>
-    <td>Y</td><td>Y</td><td>Y</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">DEV</td>
-    <td colspan="2" style="background:#dadce0"> </td><td>Y</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">PROD</td>
-    <td colspan="3" style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">PROD_END</td>
-    <td colspan="3" style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RMA</td>
-    <td>Y</td><td>Y</td><td>Y</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">SCRAP</td>
-    <td colspan="5" style="background:#dadce0"> </td>
-  </tr>
-  <tr>
-    <td style="text-align:left">INVALID</td>
-    <td colspan="5" style="background:#dadce0"> </td>
-  </tr>
-</table>
-
-The following table shows the allowed state transitions in each state. INVALID
-state is not an explicit transition destination, therefore it is not listed as
-one of the target states. The `"C"` marks represent conditional transitions
-qualified by the token table that follows.
-
-<table style="text-align:center">
-  <tr>
-    <td style="text-align:left"><strong>States</strong></td>
-    <td><strong>RAW</strong></td>
-    <td><strong>TEST LOCKED</strong></td>
-    <td><strong>TEST_UNLOCKED</strong></td>
-    <td><strong>DEV</strong></td>
-    <td><strong>PROD</strong></td>
-    <td><strong>PROD_END</strong></td>
-    <td><strong>RMA</strong></td>
-    <td><strong>SCRAP</strong></td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RAW</td>
-    <td colspan="2" style="background:#dadce0"> </td><td>C</td><td colspan="4" style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">TEST_LOCKED</td>
-    <td colspan="2" style="background:#dadce0"> </td><td>C</td><td>C</td><td>C</td><td>C</td><td style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">TEST_UNLOCKED</td>
-    <td style="background:#dadce0"> </td><td>Y</td><td style="background:#dadce0"> </td><td>C</td><td>C</td><td>C</td><td>Y</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">DEV</td>
-    <td colspan="6" style="background:#dadce0"> </td><td>C</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">PROD</td>
-    <td colspan="6" style="background:#dadce0"> </td><td>C</td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">PROD_END</td>
-    <td colspan="7" style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RMA</td>
-    <td colspan="7" style="background:#dadce0"> </td><td>Y</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">SCRAP</td>
-    <td colspan="8" style="background:#dadce0"> </td>
-  </tr>
-  <tr>
-    <td style="text-align:left">INVALID</td>
-    <td colspan="8" style="background:#dadce0"> </td>
-  </tr>
-</table>
-
-The following table enumerates the tokens and their properties. The storage
-format is implementation specific.
-
-<table style="text-align:center">
-  <tr>
-    <td><strong style="text-align:left">Token</strong></td>
-    <td><strong>Storage</strong></td>
-    <td><strong>Device Unique?</strong></td>
-    <td><strong>Usage</strong></td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RAW_UNLOCK</td>
-    <td>RTL</td>
-    <td>No</td>
-    <td>From RAW->TEST_UNLOCKED0</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">TEST_UNLOCK</td>
-    <td>OTP</td>
-    <td>Silicon creator choice</td>
-    <td>From TEST_LOCKED{N} to TEST_UNLOCKED{N}</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">TEST_EXIT</td>
-    <td>OTP</td>
-    <td>Silicon creator choice</td>
-    <td>From any TEST_UNLOCKED state to DEV, PROD_END or PROD</td>
-  </tr>
-  <tr>
-    <td style="text-align:left">RMA_UNLOCK</td>
-    <td>OTP</td>
-    <td>Yes</td>
-    <td>From PROD/DEV to RMA</td>
-  </tr>
-</table>
-
-#### RMA Unlock
-
-When in either PROD or DEV state, the device can be transitioned into RMA state
-to re-enable full debug functionality.
-
-A device unique RMA token is required to enter RMA. This RMA token is under the
-control of the Silicon Creator. To prevent abuse of this authority, the current
-manufacturing state is mixed by hardware directly into the key manager. This
-ensures that the Silicon Creator cannot arbitrarily put a device into RMA mode
-and impersonate a victim silicon owner. It also ensures that the Silicon Creator
-cannot accidentally leak any content wrapped by the owner under key manager, as
-the keys can never be re-created.
-
-Further, before transitioning to RMA, a hardware backed mechanism will erase all
-existing owner content in flash. In addition to the key manager mixing above,
-this wipe further limits the Creator's ability to abuse the existing device.
-
-### Identity & Ownership States
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>BLANK</td>
-    <td>OTP</td>
-    <td>
-
-Initial identity and ownership state.  There is no hardware identity or
-ownership provisioned
-    </td>
-  </tr>
-  <tr>
-   <td>CREATOR<br>PERSONALIZED</td>
-   <td>OTP</td>
-   <td>
-
-The hardware relevant information has been provisioned.  This includes:
-
-  *   RMA_UNLOCK Token
-  *   Creator Root Key
-  *   Creator Diversification Key
-
-Once the identity state advances to CREATOR_PERSONALIZED, creator provisioned
-secrets (see Identity flow) are no longer accessible to software and are
-directly consumed by hardware.
-    </td>
-  </tr>
-  <tr>
-    <td>UNLOCKED_OWNERSHIP</td>
-    <td>Flash</td>
-    <td>
-
-This state represents a device which contains a Creator Identity but does not
-yet have a Silicon Owner provisioned.
-
-Since this state is software managed, software is free to define a different
-state.
-    </td>
-  </tr>
-  <tr>
-    <td>LOCKED_OWNERSHIP</td>
-    <td>Flash</td>
-    <td>
-
-This state represents a device which contains both a Creator Identity and a
-Silicon Owner.
-
-A device in the LOCKED_OWNERSHIP state can transition back to UNLOCKED_OWNERSHIP when it is explicitly
-unbound. The unbinding process is described separately.
-
-Since this state is software managed, software is free to define a different
-state.
-    </td>
-  </tr>
-  <tr>
-    <td>EOL</td>
-   <td>Flash</td>
-   <td>
-
-This state represents a device that has reached its identity EOL.
-
-Since this state is software managed, software is free to define a different
-state.
-    </td>
-  </tr>
-</table>
-
-### States and Isolation Properties
-
-The manufacturing state is used as part of the key generation and derivation
-process. (Please see key manager and life cycle implementation for more
-details). By doing this, the device ensures that states with invasive debug
-capabilities, typically used for debug, manufacturing testing and RMA, cannot be
-used to expose device secrets during mission mode.
-
-The identity states are not used as part of the key generation process. Instead
-they are used as metadata to control software accessibility to the OTP
-partitions used to hold device unique secrets provisioned during manufacturing.
-
-*   When identity state is BLANK, software can access the relevant OTP
-    partitions
-*   When identity state is PERSONALIZED, software can no longer access the
-    relevant OTP partitions
-
-Please see life cycle and OTP implementation for more details
-
-### State Conditionals
-
-Entry into RMA should be conditioned upon application of the RMA\_UNLOCK token.
-
-Entry into TERMINATED / SCRAP is unconditional. If the correct command is
-presented, the transition shall not be stopped.
-
-### State Dependencies
-
-There is one manufacturing and identity state dependency.
-
-*   When the manufacturing state is **RAW** or **TEST_UNLOCKED**, the provisioning of
-    creator entropy is disabled and identity state SHALL be **BLANK.**
-*   Once manufacturing state transitions to **DEV / PROD / PROD\_END**,
-    provisioning is then enabled and the identity state is allowed to transition
-    from **BLANK**.
-
-## Functionality not Supported
-
-#### PROD Volatile Debug Unlock
-
-There will be no debug unlock after the device enters production states. To
-properly implement volatile debug unlock, the debug period must be both time
-bound and power cycle bound. This means that in ROM or ROM\_ext, we are required
-to setup functionality in non-volatile storage to track how many times the
-device has been rebooted and a locked timer to check how long the device has
-been alive. Additionally, in order to ensure that the same debug payload is not
-replayed, it must be signed through a challenge-response scheme where the device
-must first provide a nonce and then verify the signed payload.
-
-Lastly, when debug unlock is granted, the device can be hijacked by an external
-entity without the processor being aware; this makes it too dangerous a
-functionality to exist in PROD.
-
-## Compatibility with Standards and Protection Profiles
-
-### Security IC Platform Protection Profile with Augmentation Packages
-
-OpenTitan's Device Life Cycle design may support the PP084 TOE[^3] life cycle
-phase requirements including the composite product life cycle phases.
-
-The list of TOE life cycle phases is available here for reference, See
-[BSI-CC-PP-0084-2014 section 1.2.3](https://www.commoncriteriaportal.org/files/ppfiles/pp0084b_pdf.pdf)
-for more details.
-
-<table>
-  <tr>
-    <td><strong>TOE<br>Manufacturer</strong></td>
-    <td><strong>Composite</strong><br><strong>Manufacturer</strong></td>
-    <td><strong>Life Cycle Phase</strong></td>
-    <td><strong>Entity</strong></td>
-  </tr>
-  <tr>
-    <td> </td>
-    <td style="text-align:center">Y</td>
-    <td>1: Embedded Software Development</td>
-    <td>IC Embedded Software Developer</td>
-  </tr>
-  <tr>
-    <td style="text-align:center">Y</td>
-    <td> </td>
-    <td>2: IC Development</td>
-    <td>IC Developer</td>
-  </tr>
-  <tr>
-    <td style="text-align:center">Y</td>
-    <td> </td>
-    <td>3: IC Manufacturing</td>
-   <td>IC Manufacturer</td>
-  </tr>
-  <tr>
-    <td colspan="2" style="text-align:center">Y</td>
-    <td>4: IC Packaging</td>
-    <td>IC Packaging Manufacturer</td>
-  </tr>
-  <tr>
-    <td> </td>
-    <td style="text-align:center">Y</td>
-    <td>5: Composite Product Integration</td>
-    <td>Composite Product<br>Integrator</td>
-  </tr>
-  <tr>
-    <td> </td>
-    <td style="text-align:center">Y</td>
-    <td>6: Personalisation</td>
-    <td>Personaliser</td>
-  </tr>
-  <tr>
-    <td> </td>
-    <td style="text-align:center">Y</td>
-    <td> </td>
-    <td>Composite Product Issuer</td>
-  </tr>
-  <tr>
-    <td> </td>
-    <td> </td>
-    <td>7: Operational Usage </td>
-    <td>Consumer of Composite<br>Product (End-consumer)</td>
-  </tr>
-</table>
-
-<!-- Footnotes themselves at the bottom. -->
-
-## Notes
-
-[^1]: RMA ("Return Material Authorization") refers to the process in which a
-    device is returned to the Silicon Creator for more triage and debugging.
-    It represents a terminal state of the device, as it cannot be returned to
-    production afterwards.
-
-[^2]: One Time Programmable Memory
-
-[^3]: "Security IC Platform Protection Profile with Augmentation Packages
-    (BSI-CC-PP-0084-2014)"
-    https://www.commoncriteriaportal.org/files/ppfiles/pp0084b_pdf.pdf.
diff --git a/doc/security/specs/device_provisioning/README.md b/doc/security/specs/device_provisioning/README.md
new file mode 100644
index 0000000..a84e856
--- /dev/null
+++ b/doc/security/specs/device_provisioning/README.md
@@ -0,0 +1,961 @@
+# Device Provisioning
+
+<p style="color: red; text-align: right;">
+  Status: Pre-RFC
+</p>
+
+
+## Overview
+
+This document describes the OpenTitan provisioning flow which is divided into
+two stages:
+
+*   [Personalization](#personalization): Covers initialization of the device
+    with an unique cryptographic identity known as the
+    [Creator Identity][creator_identity], its endorsement certificate, as well
+    as additional secrets required to configure defensive mechanisms. This
+    occurs only at manufacturing time.
+*   [Owner Personalization](#owner_personalization): Covers provisioning of
+    owner secrets and endorsement of the Owner Identity by the Silicon Owner.
+    This may occur either at manufacturing time or as part of a later in-field
+    ownership transfer.
+
+## Security Scope
+
+The security scope is derived from the threats against the assets handled by the
+provisioning infrastructure.
+
+### Assets
+
+The wafer foundry and OSAT are untrusted to maintain the secrecy of the
+following assets:
+
+*   Device secrets
+*   Provisioning appliance secrets (e.g. certificate endorsement signing keys).
+
+### Security Model
+
+The security model of the provisioning infrastructure is based on the following
+requirements:
+
+*   There is a provisioning appliance connected to an HSM in the manufacturing
+    floor. This device is the only component trusted with secrets other than
+    the Device Under Test (DUT).
+*   There is a pre-personalization device authentication function that can be
+    implemented by trusted components.
+*   The wafer foundry does not collude with the OSAT to attack the device
+    authentication function, although some mitigations are considered.
+
+## Device Lifecycle and Personalization Stages
+
+### Unlock Tokens {#unlock_tokens}
+
+OpenTitan provides a set of lock/unlock tokens to control the state of the
+device in early manufacturing stages. See
+[device lifecycle specification][device_lifecycle] for more details.
+
+*   `RAW_UNLOCK`
+*   `TEST_UNLOCK`
+*   `TEST_EXIT`
+*   `RMA_UNLOCK`
+
+Test unlock tokens are used as part of the process to logically disable the
+devices so that they can be safely transported between manufacturing stages.
+Unlock operations do not require CPU intervention. A test exit token is used to
+gate the device transition from TEST state into one of the final operational
+states (`DEV`, `PROD_END`, `PROD`).
+
+### Pre-Personalization
+
+The following steps illustrate the set of operations required prior to
+personalization.
+
+#### Wafer Test - Electrical Die Sorting (EDS)
+
+*   `RAW_UNLOCK`: Unlock TEST mode by providing unlock token via TAP interface.
+*   `ANALOG TRIM & TEST (EDS)`: Regular analog test, scan, calibration, trimming
+    and functional testing (EDS stage).
+*   `SET DEVICE_ID`: Program the device identifier fuses. `DEVICE_ID` export may
+    be required depending on the provisioning flow.
+*   `LOCK DEVICE`: (Optional) Program the TEST_UNLOCK (optional) and/or
+    TEST_EXIT tokens depending on the test flow configuration.
+
+    Optional: If required by the manufacturing flow, lock the device for safe
+    transport into the next test stage.
+
+#### Package Test (TEST MODE) - Automated Test Equipment (ATE)
+
+*   `TEST_UNLOCK`: Unlock TEST mode by providing TEST_UNLOCK token via TAP
+    interface.
+*   `ANALOG TRIM & TEST (OSAT)`: Regular analog test, scan, calibration,
+    trimming and functional testing (EDS stage).
+*   `LOG DEVICE_ID`: Record DEVICE_ID and test results.
+*   `LOCK DEVICE`: Program the TEST_UNLOCK (optional) and/or TEST_EXIT tokens
+    depending on test configuration.
+
+    Optional: If required by the manufacturing flow, lock the device for safe
+    transport into the next test stage.
+
+#### Package Test (DEV or PROD MODE) - Automated Test Equipment (ATE)
+
+*   `TEST_EXIT`: Unlock device and transition from TEST to one of the following
+    operational states: DEV, PROD_END, or PROD.
+*   `SKU SETTINGS`: SKU dependent fuse and info flash configuration.
+*   `PERSONALIZATION`: See [Personalization](#personalization) section for more
+*   `LOAD FW IMAGE`: Load factory image payload.
+
+## Personalization {#personalization}
+
+### Test Setup
+
+The following diagram shows the physical connections between test components.
+
+<table>
+  <tr>
+     <td>
+        <img src="img_ate_setup.svg" style="width: 800px;">
+     </td>
+  </tr>
+  <tr><td style="text-align:center;">Figure: FT Connectivity</td></tr>
+</table>
+
+#### Components
+
+*   **Device Under Test**: An OpenTitan device being tested as part of its
+    manufacturing process.
+*   **ATE**: Automatic Test Equipment (ATE), used to perform tests on the
+    devices as part of the manufacturing process.
+*   **Provisioning Appliance**: A network connected local server with an
+    attached HSM. The server implements an OpenTitan compliant secure boot
+    implementation, and runs signed applications used to communicate with ATE
+    and cloud services.
+*   **Provisioning Service**: Cloud service used to authenticate and initialize
+    provisioning appliances.
+
+    The provisioning service is used to provide initialization data to the
+    provisioning appliance once it has been authenticated.
+
+*   **Registry Service**: A cloud service used to host a device registry
+    containing certificates and Certificate Revocations Lists (CRLs).
+
+#### Connectivity
+
+ATE - OpenTitan (DUT)
+
+*   `RST_N`: OpenTitan reset pin.
+*   `STRAPS`: Pins used to control hardware and software test functionality. On
+    the hardware side, strap pins are used to configure TEST modes and select
+    TAP interfaces. On the software side, straps used to enable the SPI flash
+    bootstrap mode in ROM, as well as manufacturing test modes. Some strap
+    functionality is only available before entering DEV or PROD states. There is
+    also additional functionality that is disabled when the device reaches the
+    end of manufacturing.
+*   `SPI`: Bidirectional interface used to exchange payloads and status with the
+    device.
+
+Provisioning Appliance - ATE
+
+*   Local network interface.
+
+Provisioning/Registry Services - Provisioning Appliance
+
+*   External network interface. Connectivity between endpoints is authenticated
+    and encrypted. The Provisioning appliance uses service accounts to identify
+    itself. The Provisioning and Registration Services are authenticated via
+    TLS. The interface has to support unstable connections. The Provisioning
+    Appliance has to be able to buffer messages to be able to recover from
+    network disruptions.
+
+### Overview
+
+The following diagram captures the life of a device throughout the manufacturing
+flow with emphasis on the personalization process.
+
+<table>
+  <tr>
+    <td>
+      <img src="img_provisioning_overview.svg" style="width: 900px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center;">Figure: Device personalization (high level)</td>
+  </tr>
+</table>
+
+Steps:
+
+1.  [Device identifiers][device_id] (`device_id`) and
+    [unlock tokens](#unlock_tokens) are programmed into the device's One Time
+    Programmable (OTP) memory. The unlock tokens are delivered in cleartext form
+    to each OpenTitan die.
+2.  (Optional) A provisioning appliance collects all device identifiers and
+    unlock tokens and sends them in encrypted form to a provisioning service
+    hosted in the cloud.
+3.  Wafers are transported from wafer test (EDS) to the package test (ATE)
+    location. Devices are transported in `TEST_LOCKED` lifecycle state.
+4.  The provisioning service authenticates the provisioning appliance(s) and
+    sends `appliance_secrets, and identifiers (device_ids)` in encrypted form.
+5.  Devices are tested and switched to PROD or DEV mode before personalization.
+    The ATE test setup actuates the personalization flow in collaboration with
+    the provisioning appliance.
+    1.  There are two personalization approaches supported by OpenTitan.
+        1.  [Injection Process](#injection_process): The provisioning appliance
+            generates `device_secrets` (in the injection case) and endorsement
+            certificates. `appliance_secrets` are used to enable signing on
+            endorsement certificates with Silicon Creator intermediate CA keys.
+        2.  [Self-Generated Process](#self_generated_process): The device
+            generates its own `device_secrets` and the provisioning appliance is
+            used to sign the endorsement certificate.
+6.  At the end of a successful provisioning flow, the provisioning appliance
+    sends the device certificates to a device registry. Silicon Owners can use
+    the registry as part of identity ingestion flows.
+7.  The devices are shipped with a factory image and a Silicon Creator
+    endorsement certificate. The Silicon Creator may also provide
+    [Owner Personalization](#owner_personalization) services. All shipped
+    devices have secure boot enabled, which provides a logical locking mechanism
+    to restrict the use of open samples.
+8.  The Silicon Creator may issue a Certificate Revocation List (CRL) to the
+    device registry. The registry is in charge of serving the CRL to downstream
+    consumers (Silicon Owners).
+
+### Secure Boot
+
+Secure boot is always enforced by the ROM and cannot be disabled.
+Personalization and factory software payloads are signed, and boot verification
+is used to anchor the mechanism in which the device authenticates the
+provisioning appliance during personalization.
+
+### Injection Process
+
+This section describes the personalization injection mode in detail.
+
+<img src="img_provisioning_injection.svg" style="width: 900px;">
+
+**P0. Get device identifier**
+
+The DEVICE is initially in LOCKED state before the start of production test. The
+TESTER gets the Device ID via TAP interface. The provisioning appliance returns
+unlock tokens associated with the Device ID.
+
+Note 1: The Device ID read requirement applies only if unlock tokens are unique
+per device.
+
+Note 2: The manufacturer must document the unlock token usage plan, including
+the proposed rotation plan.
+
+**P1. Unlock device and load personalization software**
+
+P1.1. The TESTER unlocks the DEVICE by sending the `TEST_UNLOCK` and `TEST_EXIT`
+tokens via TAP interface.
+
+P1.2. The TESTER loads personalization software on the DEVICE using the SPI
+bootstrap interface. The DEVICE verifies the personalization software
+signature against one of the public keys stored in the ROM via secure boot.
+
+**P2. Device authentication and key exchange**
+
+P2.1 Authentication function
+
+The authentication function relies on the following assumptions:
+
+*   The provisioning appliance (HSM) is the only component trusted with secrets
+    other than the Device Under Test (DUT) in the manufacturing floor.
+*   The authentication function can only be implemented by trusted components.
+*   The wafer manufacturer does not collude with the OSAT to attack the
+    authentication function, although some mitigations are considered.
+
+```
+# Shared secret key between provisioning appliance and the DEVICE.
+# The parameters of the authentication function are outside the
+# classification scope of this document.
+key_auth = authentication_function()
+```
+
+P2.2. DEVICE sends authentication data to TESTER
+
+**The DEVICE will fail to generate an authentication payload if the device is
+not in `PROD`, `PROD_END` or `DEV` state.**
+
+The DEVICE sends an authentication payload to the TESTER including:
+
+*   Device ID (`device_id`).
+*   Static key (`receiver_pub_key`) used to derive session keys[^1]; and,
+*   MAC tag covering the authentication payload.
+
+The DEVICE sends the payload to the TESTER via SPI, repeating the message
+continuously to simplify timing adjustments in the test sequence.
+
+Payload generation:
+
+```
+// ECC key pair generation compliant to FIPS 186-4 appendix B.4.
+// Curves under consideration: NIST_P256 and NIST_P386.
+receiver_priv_key, receiver_pub_key = ECC_KDF(DRBG_context)
+
+key_auth = authentication_function()
+
+// data_size includes the size of the data + tag.
+data = "OTAU" || receiver_pub_key || device_id || data_size
+tag = MAC(key_auth, data)
+
+// Continuously broadcast via SPI device port.
+return data || tag
+```
+
+P2.3 TESTER verifies authentication data
+
+The TESTER calls the provisioning appliance to verify the tag attached to the
+DEVICE authentication payload using the authentication key function from P2.1.
+The TESTER aborts personalization on failure.
+
+Verification function:
+
+```
+key_auth = authentication_function()
+
+data || tag = authentication_data
+calculated_tag = MAC(key_auth, data)
+RETURN_ERROR_IF(calculated_tag != tag)
+```
+
+**P3: Inject secrets**
+
+P3.1 Get wrapped personalization payload from provisioning appliance
+
+The TESTER gets a personalization payload from the provisioning appliance. The
+provisioning appliance is in charge of generating the device secrets. The
+payload is associated with the Device ID and is wrapped with a key derived from
+the `receiver_pub_key`extracted in step P2.
+
+Transport wrapping. See [ECIES Encryption](#ecies_encryption) for more details
+on the `ECIES_ENC` input and output parameters. Some parameters are omitted for
+simplicity.
+
+```
+// OPEN: ctx_id in this case is set to a portion of the device_id.
+// The manufacturer shall incorporate a monotonically incrementing counter
+// into ctx_id.
+ephemeral_pub_key || tag || ctx_id || sender_pub_key ||
+  data_size || data_enc =
+  ECIES_ENC(key=sender_priv_key, receiver_pub_key,
+            data=(device_secrets || creator_certificate))
+
+personalization_payload =  "OTPL" || tag || ctx_id ||
+   sender_pub_key || data_size || data_enc
+```
+
+P3.2 TESTER sends personalization payload to DEVICE
+
+The personalization payload is sent to the device via SPI in 1KB frame chunks.
+The payload is transferred from the DEVICE SPI RX FIFO into SRAM.
+
+**P4. DEVICE unwrap and install secrets**
+
+P4.1 DEVICE unwraps secrets and creator certificate
+
+Unwrap process. See [ECIES Decryption](#ecies_decryption) for more details on
+the `ECIES_DEC` input and output parameters. Some parameters are omitted for
+simplicity.
+
+```
+// The following operation will verify the integrity of the encrypted
+// blob. The sender_pub_key is verified against a whitelist stored
+// in the personalization software.
+device_secrets, creator_cerficate =
+    ECIES_DEC(key=receiver_priv_key,
+              ephemeral_pub_key, sender_key_pub, data=enc_payload)
+```
+
+P4.2 Install secrets
+
+Secrets are installed in OTP and in Flash. See Device Secrets for a breakdown of
+Silicon Creator level secrets.
+
+P4.3 Report install status
+
+Report install status to tester via SPI interface. The status code is repeated
+continuously on the SPI interface to simplify the test implementation.
+
+**P5. Install factory image**
+
+The factory image is the software loaded on the device before shipping. It is
+not expected to change frequently once the manufacturing flow is deployed for a
+given SKU configuration. At a minimum, the factory image will contain a ROM
+Extension (`ROM_EXT`) component.
+
+The TESTER programs the factory image on the DEVICE via SPI bootstrap interface
+The hash of the `ROM_EXT` must match the value used to calculate the Creator
+Identity as described in the
+[Identities and Root Keys][identities_and_root_keys] document.
+
+**P6. Provisioning result**
+
+The DEVICE boots in identity UNOWNED state and in manufacturing mode. The
+conditions used to trigger manufacturing mode are TBD.
+
+P6.1 Test Creator Identity
+
+The `ROM_EXT` uses the key manager to obtain the Creator Identity key. See the
+[Asymmetric Keys section in the Attestation specification][attestation_keys] for
+more details.
+
+The public portion of the Creator Identity is tested against the Creator
+Certificate. The result is reported to the TESTER via SPI.
+
+### Self-Generated Process {#self_generated_process}
+
+This section describes the personalization self-generate mode in detail. In this
+mode, The device generates its own device secrets and the provisioning appliance
+is used to sign the endorsement certificate.
+
+<img src="img_provisioning_self_gen.svg" style="width: 900px;">
+
+**PS0. Get device identifier**
+
+The DEVICE is initially in LOCKED state. The TESTER gets the Device ID via TAP
+interface. The provisioning appliance returns unlock tokens associated with the
+Device ID.
+
+**PS1. Unlock device and load perso firmware**
+
+PS1.1. The TESTER unlocks the DEVICE by sending the `TEST_UNLOCK` and
+`TEST_EXIT` tokens via TAP interface.
+
+PS1.2. The TESTER loads personalization software on the DEVICE using the SPI
+bootstrap interface. The DEVICE verifies the personalization software signature
+against one of the public keys stored in the ROM via secure boot.
+
+**PS2. Generate and install secrets**
+
+The device uses the available on-device entropy source to generate all the
+device secrets.
+
+Secrets are installed in OTP and in Flash. Fuse locks are set as described in
+the OTP specification.
+
+**PS3. Export public key**
+
+The device exports the Creator Identity public key via SPI. The authentication
+function from P2.1 is used to generate an integrity digest over the exported
+data.
+
+Payload with integrity tag:
+
+```
+// Shared between device and provisioning appliance.
+key_auth = authentication_function()
+
+// data_size includes the size of the data + tag.
+data = "OTAU" || data_size || device_id || creator_identity_pub_key
+tag = MAC(key_auth, data)
+
+// Continuously broadcast via SPI device port.
+return data || tag
+```
+
+**PS4. Generate certificate**
+
+PS4.1. Verify received data
+
+The provisioning appliance verifies the integrity of the received payload. The
+verification of the payload is used to authenticate the device, since the key
+used to calculate the digest tag is only known to the device and the
+provisioning appliance.
+
+Verification function:
+
+```
+key_auth = authentication_function()
+
+data || tag = authentication_data
+calculated_tag = MAC(key_auth, data)
+RETURN_ERROR_IF(calculated_tag != tag)
+```
+
+PS4.2. Generate certificate
+
+The provisioning appliance generates an endorsement certificate as defined in
+the [Certificate Format][attestation_certificate] section of the Attestation
+specification.
+
+PS4.3. Send certificate
+
+The certificate is sent to the device via SPI in 1KB frames. An integrity tag is
+added to the message using the same mechanism as in PS3.
+
+Provisioning service to device certificate payload.
+
+```
+// Shared between device and provisioning appliance.
+key_auth = authentication_function()
+
+// data_size includes the size of the data + tag.
+data = "OTCI" || data_size || device_id || creator_identity_cert
+tag = MAC(key_auth, data)
+
+// Continuously broadcast via SPI device port.
+return data || tag
+```
+
+**PS5. Install certificate**
+
+The device verifies the certificate payload sent by the provisioning appliance
+and installs it in a flash block.
+
+**PS6. Install factory image**
+
+The factory image is the software loaded on the device before shipping. It is
+not expected to change frequently once the manufacturing flow is deployed for a
+given SKU configuration. At a minimum, the factory image will contain a ROM
+Extension (`ROM_EXT`) component.
+
+The TESTER programs the factory image on the DEVICE via SPI bootstrap interface.
+The hash of the `ROM_EXT` must match the value used to calculate the Creator
+Identity as described in the
+[Identities and Root Keys][identities_and_root_keys] document.
+
+**PS7. Provisioning result.**
+
+The `ROM_EXT` uses the key manager to obtain the Creator Identity key. See the
+[Asymmetric Keys section in the Attestation specification][attestation_keys] for
+more details.
+
+The public portion of the Creator Identity is tested against the Creator
+Certificate. The result is reported to the TESTER via SPI.
+
+## Owner Personalization {#owner_personalization}
+
+OpenTitan provides a mechanism to enable provisioning of Silicon Owner secrets
+and endorsement certificates in manufacturing and post-manufacturing stages.
+Owners are encouraged to create an implementation plan to perform
+post-manufacturing provisioning to take full advantage of ownership transfer.
+
+Provisioning post-ownership transfer assumes that the OpenTitan device is
+integrated into a system, and there is a HOST capable of communicating
+synchronously or asynchronously with the DEVICE (OpenTitan) and a remote
+registry and provisioning service.
+
+The physical transport layer between the DEVICE and the HOST is use-case
+specific and managed at the OpenTitan SKU configuration level (e.g. different
+`ROM_EXT` implementation per SKU).
+
+### Overview
+
+The following diagram captures the Silicon Owner provisioning flow. Device
+attestation requires the device to have a valid Creator Identity endorsed by the
+Silicon Creator as described in the [Personalization](#personalization) section.
+
+The process can be implemented during manufacturing or post-manufacturing. If
+implemented during manufacturing, the ATE and provisioning appliance fulfill the
+role of the HOST.
+
+<table>
+  <tr>
+    <td>
+      <img src="img_provisioning_owner.svg" style="width: 900px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center;">Figure: Owner personalization</td>
+  </tr>
+</table>
+
+Steps:
+
+1.  [Device identifiers][device_id] (`device_id)` and Silicon [Creator
+    Certificates][attestation_certificate] are imported into the Silicon Owners
+    internal device registry.
+2.  Ownership transfer is performed on the device to provision code verification
+    keys used as part of secure boot. Software signed by the Silicon Owner is
+    programmed on the device.
+3.  The host verifies the attestation chain provided by the device and requests
+    a provisioning payload from the Silicon Owner's provisioning service. Data
+    in transit is encrypted with a key negotiated between the OpenTitan device
+    and the provisioning service. Provisioning data is divided into:
+    1.  Owner Certificate endorsed by the Silicon Owner. The owner can also
+        implement endorsement of additional certificates as part of the process.
+    2.  Additional Owner secrets required to provision application level
+        secrets.
+4.  The host sends the provisioning payload to the device. The device unwraps
+    the secrets and installs them.
+
+### Injection Process
+
+<table>
+  <tr>
+    <td>
+      <img src="img_provisioning_owner_injection.svg" style="width: 900px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center;">Figure: Provisioning post-ownership transfer</td>
+  </tr>
+</table>
+
+**RP0. Ownership transfer**
+
+The DEVICE is initially in UNOWNED state. The Silicon Owner triggers ownership
+transfer and loads new software. Ownership transfer details are covered in a
+separate document.
+
+See [Ownership Transfer][ownership_transfer] document for more details.
+
+**RP1. Get attestation certificates**
+
+The HOST requests attestation certificates from the DEVICE. A device that has
+been transferred to a new owner has its attestation chain rooted in the Creator
+Identity. See the [Attestation][attestation] documentation for more information.
+
+**RP2. Get `receiver_pub_key`**
+
+The DEVICE generates a `receiver` key pair and shares the `receiver_pub_key`
+with the HOST.
+
+Additional data required to generate a new owner certificate and/or device
+secrets is added to the payload. The payload is signed with the
+`owner_identity_key` which can be verified against the DEVICE attestation chain
+obtained in RP1.
+
+```
+// ECC key pair generation compliant to FIPS 186-4 appendix B.4.
+// Curves under consideration: NIST_P256 and NIST_P386.
+receiver_priv_key, receiver_pub_key = ECC_KDF(DRBG_context)
+
+data = any additional data required to generate the owner cert.
+hdr = ctx_id || receiver_pub_key || data
+
+// owner_identity_key is an ECC key as described in the Asymmetric
+// Keys section of the Attestation document.
+signature = ASYM_SIGN(key=owner_identity_priv_key, data=hdr)
+
+payload = hdr || signature
+```
+
+**RP3. Get wrapped secrets and owner certificate**
+
+RP3.1. The provisioning service verifies the `receiver_pub_key` against the
+attestation chain obtained in RP1 and the internal device registry.
+
+The provisioning service then generates a new `owner_certificate` with the
+additional data provided by the DEVICE. The certificate is signed with a key
+managed by the Silicon Owner (e.g. `owner_intermediate_ca_key`). The new
+`owner_certificate` will be used as the root certificate in Attestation flows as
+described in the [Attestation][attestation] document.
+
+RP3.2. The provisioning service performs ECIES encryption using the following
+parameters:
+
+*   `sender_priv_key`: Provisioning service private key. The public portion is
+    known to the DEVICE firmware.
+*   `receiver_pub_key`: DEVICE public key obtained in step RP2.
+*   `ctx_id`: Context ID provided by the device in step RP2.
+*   `owner_certificate`: New owner certificate generated in step RP3.1.
+*   `device_secrets`: Additional secrets bound to the device. May be generated
+    offline or as part of step RP3.1.
+
+```
+payload =
+ECIES_ENC(key=sender_priv_key, pub_key=receiver_pub_key,
+            data=(device_secrets | ctx_id))
+| owner_certificate
+```
+
+RP3.3. The wrapped payload is sent to the DEVICE.
+
+**RP4. Unwrap and install secrets and owner certificate**
+
+Device performs ECIES decryption to unwrap the payload and install the new
+`owner_certificate` and `device_secrets`. The owner has access to info flash
+pages for storage of secrets.
+
+The `server_key_pub` and `ephemeral_pub_key` values are sent with the wrapped
+payload. The public server key (`server_key_pub`) is also known to the software
+running in the DEVICE.
+
+```
+device_secrets || owner_certificate =
+    ECIES_DEC(key=receiver_priv_key, server_key_pub,
+              ephemeral_pub_key, data=enc_payload)
+```
+
+**RP5: Get attestation certificates**
+
+The HOST issues a new attestation command to the DEVICE. The DEVICE responds
+with an attestation chain rooted on the new `owner_certificate`. An additional
+test command can be supported to test any functionality associated with the
+`device_secrets`.
+
+## ECIES Operations
+
+**Key Establishment**
+
+Key exchange is based on [NIST 800-56Ar3][nist_800_56r3] 6.2.1.2 Cofactor
+One-Pass Unified Model Scheme, employing one ephemeral key, two secret keys and
+ECC CDH. Both sender and receiver authentication are performed via public
+crypto, thus one secret key is associated with the receiver, while the other one
+is associated with the sender.
+
+**AEAD Modes of Operation**
+
+The following authenticated encryption schemes are supported by this
+architecture:
+
+*   AES-CTR-HMAC-12B-IV-32B-Tag (FIPS OK)
+*   AES-CTR-HMAC-16B-IV-32B-Tag (FIPS OK)
+*   AES-GCM (FIPS OK)
+
+### Encryption
+
+<table>
+  <tr>
+    <td>
+      <img src="img_ecies_encryption.svg" style="width: 900px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center;">Figure: ECIES Encryption, simplified diagram</td>
+  </tr>
+</table>
+
+Inputs:
+
+*   `receiver_pub_key`: ECC public key provided by the receiver point (e.g.
+    NIST_P256, NIST_P386
+*   `sender_priv_key`: Sender ECC private key.
+*   `ctx_id`: Context identifier provided by the device. Used to control forward
+    secrecy of the protocol. For personalization this value is set to a portion
+    of the device identifier (`device_id`).
+*   `data`, `data_size`: Data to be encrypted and its size
+*   `ecies_key_length`: Target ECIES key length. Used as a configuration
+    parameter.
+
+Outputs:
+
+*   `ephemeral_pub_key`: Ephemeral ECC shared key, required to perform
+    decryption.
+*   `tag`: MAC over payload.
+*   `ctx_id`: Context identifier provided by the device.
+*   `sender_pub_key`: Sender ECC public key.
+*   `data_enc`, `data_size`: Encrypted data and its size.
+
+Algorithm:
+
+```
+// NIST 800-56Ar3 6.2.1.2 One-Pass Unified Model. This requires
+// the creation of two shared secrets: shared_ephemeral and
+// shared_static.
+ephemeral_priv_key = GenerateEphemeralKey()
+shared_ephemeral =
+    ECDH_compute_key(key_len,
+                     EC_POINT(receiver_pub_key), ephemeral_priv_key)
+shared_static =
+    ECDH_compute_key(key_len,
+                     EC_POINT(receiver_pub_key), sender_priv_key)
+
+// Key derivation function used to calculate K and IV.
+K, IV = key_and_iv_generaration(
+   ctx_id, data_size, shared_ephemeral, shared_static,
+   receiver_pub_key, sender_pub_key)
+
+// The following authenticated encryption approach follows encrypt
+// then MAC approach.
+// See also Alternative Authenticated Encryption Scheme
+
+// Ke length should be one of 128, 192, 256.
+// Km length should be one of 256, 384, 512.
+// K should have an entropy with a security strength equivalent to
+// the one provided by Ke and Km when used with AES_CTR and MAC
+// respectively.
+[Ke || Km] = K
+
+data_enc = AES_CTR_ENC(Ke, IV, data)
+tag = MAC(Km, ctx_id || sender_pub_key || data_size || data_enc)
+
+return [
+  ephemeral_pub_key || tag || ctx_id || sender_pub_key ||
+  data_size || data_enc]
+```
+
+#### Alternative Authenticated Encryption Scheme
+
+The following implementation uses AES in GCM mode to obtain the ciphertext and
+integrity tag.
+
+```
+// The following authenticated encryption approach removes the need
+// for separately calling a MAC function. In this case there is no
+// need to split the key K into Ke and Km components.
+
+// All the parameters are taken from the main encryption pseudo-code
+// block above. The following call replaces the AES_CTR and MAC
+// calls.
+data_enc, tag =
+   AES_GCM_ENC(K, IV, ctx_id || sender_pub_key || data_size || data)
+```
+
+### Decryption
+
+<table>
+  <tr>
+    <td>
+      <img src="img_ecies_decryption.svg" style="width: 900px;">
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center;">Figure: ECIES Decryption, simplified diagram</td>
+  </tr>
+</table>
+
+Inputs:
+
+*   `receiver_priv_key`: Receiver ECC private key. Generated by the device and
+    associated with the context identifier (`ctx_id`).
+*   `ephemeral_pub_key`: Ephemeral ECC shared key generated by the sender.
+*   `sender_pub_key`: Sender ECC public key. Embedded in payload sent to the
+    device.
+*   `ctx_id`: Context identifier provided by the device.
+*   `data`, `data_size`: Encrypted data and its size.
+*   `ecies_key_length`: Target ECIES key length. Used as a configuration
+    parameter.
+
+Outputs:
+
+The algorithm implementation currently includes authentication and integrity
+checks, thus plaintext is the only documented output parameter.
+
+*   `plaintext`, `size`: Plaintext data and its size.
+
+Algorithm:
+
+```
+[ephemeral_pub_key || tag || ctx_id ||
+ sender_pub_key || data_size || ciphertext ] = data
+
+// Check sender_pub_key against whitelist stored in device software.
+check_sender_key(sender_pub_key)
+
+// NIST 800-56Ar3 6.2.1.2 One-Pass Unified Model. This requires
+// the creation of two shared secrets: shared_ephemeral and
+// shared_static.
+ephemeral_key = GenerateEphemeralKey()
+shared_ephemeral =
+    ECDH_compute_key(key_len,
+                     EC_POINT(ephemeral_pub_key), ephemeral_priv_key)
+shared_static =
+    ECDH_compute_key(key_len,
+                     EC_POINT(sender_pub_key), ephemeral_priv_key)
+
+// Key derivation function used to calculate K and IV.
+K, IV = key_and_iv_generaration(
+   ctx_id, data_size, shared_ephemeral, shared_static,
+   receiver_pub_key, sender_pub_key)
+
+// The following authenticated encryption approach follows encrypt
+// then MAC approach.
+// See also
+
+// Ke length should be one of 128, 192, 256.
+// Km length should be one of 256, 384, 512.
+// K should have an entropy with a security strength equivalent to
+// the one provided by Ke and Km when used with AES_CTR and MAC
+// respectively.
+Ke || Km = K
+
+tag_expected =
+   MAC(Ke, ctx_id || sender_pub_key || data_size || data_enc)
+
+RETURN_ERROR_IF(tag != tag_expected)
+RETURN_ERROR_IF(ctx_id != ctx_id_expected)
+
+plaintext = AES_CTR_DEC(Km, IV, data=ciphertext)
+
+return [plaintext]
+```
+
+### Key and IV Derivation
+
+Inputs:
+
+*   `shared_ephemeral`: Derived ephemeral key. See Encryption and Decryption
+    algorithms for more details.
+*   `shared_static`: Derived static key.
+*   `ctx_id`: Context identifier provided by the device.
+*   `data_size`: Size of input data.
+*   `ecies_key_length`: Target ECIES key length. Used as a configuration
+    parameter.
+
+Outputs:
+
+*   `K`: ECIES key of size equal to `ecies_key_length`.
+*   `IV`: `IV` parameter derived to use as AES decrypt parameter.
+
+Algorithm:
+
+```
+shared_secret = shared_ephemeral || shared_static
+salt = "shared_tag" || ctx_id || 00's padding
+
+// KDF based on NIST 800-56C 2-step key derivation. The extract and
+// expand operations can be implemented via HMAC-SHA2. For SHA3 see
+// Alternative KDF 1-step KMAC.
+KDK = HKDF_extract(salt=salt, secret=shared_secret)
+
+L = ecies_key_length
+label = "ot_encrypt"
+context = receiver_pub_key || sender_pub_key
+fixed_info = label || context || STRING(L)
+
+K = HKDF_expand(KDK, L, fixed_info)
+
+# IV generation using additional HKDF_expand
+fixed_info_iv = "ot_iv" || context || STRING(12)
+IV = HKDF_expand(KDK, 12, fixed_info_iv)
+
+return K, IV
+```
+
+##### Alternative KDF 1-step KMAC
+
+The following implementation uses a one-step key derivation function based on a
+KMAC implementation (e.g. KMAC256).
+
+```
+// KDF based on NIST 800-56C section 4.1 1-step key derivation.
+// Using KMAC256 as specified in section 4.1 option 3.
+L = ecies_key_length
+label = "ot_encrypt"
+context = receiver_pub_key || sender_pub_key
+fixed_info = label || context || STRING(L)
+
+salt =
+   "shared_tag" || ctx_id || 00's padding up to key size
+
+other_input = salt || fixed_info
+K = HKDF(secret=shared_secret, L, other_intput)
+
+
+fixed_info_iv = "ot_iv" || context || STRING(12)
+IV = HKDF(secret=shared_secret, 12,
+          other_input=salt || fixed_info_iv)
+
+return K, IV
+```
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+
+[^1]: This is a static key within the context of a personalization run. The key
+    is erased after personalization is complete.
+
+
+[attestation]: ../attestation/README.md
+[attestation_keys]: ../attestation/README.md#asymmetric-keys
+[attestation_certificate]: ../attestation/README.md#certificate-format
+[creator_identity]: ../identities_and_root_keys/README.md#creator-identity
+[device_lifecycle]: ../device_life_cycle/README.md
+[device_id]: ../identities_and_root_keys/README.md#device-identifier
+[identities_and_root_keys]: ../identities_and_root_keys/README.md
+[nist_800_56r3]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar3.pdf
+[ownership_transfer]: ../ownership_transfer/README.md
diff --git a/doc/security/specs/device_provisioning/index.md b/doc/security/specs/device_provisioning/index.md
deleted file mode 100644
index 55b5d2e..0000000
--- a/doc/security/specs/device_provisioning/index.md
+++ /dev/null
@@ -1,963 +0,0 @@
----
-title: "Device Provisioning"
----
-
-<p style="color: red; text-align: right;">
-  Status: Pre-RFC
-</p>
-
-
-## Overview
-
-This document describes the OpenTitan provisioning flow which is divided into
-two stages:
-
-*   [Personalization](#personalization): Covers initialization of the device
-    with an unique cryptographic identity known as the
-    [Creator Identity][creator_identity], its endorsement certificate, as well
-    as additional secrets required to configure defensive mechanisms. This
-    occurs only at manufacturing time.
-*   [Owner Personalization](#owner_personalization): Covers provisioning of
-    owner secrets and endorsement of the Owner Identity by the Silicon Owner.
-    This may occur either at manufacturing time or as part of a later in-field
-    ownership transfer.
-
-## Security Scope
-
-The security scope is derived from the threats against the assets handled by the
-provisioning infrastructure.
-
-### Assets
-
-The wafer foundry and OSAT are untrusted to maintain the secrecy of the
-following assets:
-
-*   Device secrets
-*   Provisioning appliance secrets (e.g. certificate endorsement signing keys).
-
-### Security Model
-
-The security model of the provisioning infrastructure is based on the following
-requirements:
-
-*   There is a provisioning appliance connected to an HSM in the manufacturing
-    floor. This device is the only component trusted with secrets other than
-    the Device Under Test (DUT).
-*   There is a pre-personalization device authentication function that can be
-    implemented by trusted components.
-*   The wafer foundry does not collude with the OSAT to attack the device
-    authentication function, although some mitigations are considered.
-
-## Device Lifecycle and Personalization Stages
-
-### Unlock Tokens {#unlock_tokens}
-
-OpenTitan provides a set of lock/unlock tokens to control the state of the
-device in early manufacturing stages. See
-[device lifecycle specification][device_lifecycle] for more details.
-
-*   `RAW_UNLOCK`
-*   `TEST_UNLOCK`
-*   `TEST_EXIT`
-*   `RMA_UNLOCK`
-
-Test unlock tokens are used as part of the process to logically disable the
-devices so that they can be safely transported between manufacturing stages.
-Unlock operations do not require CPU intervention. A test exit token is used to
-gate the device transition from TEST state into one of the final operational
-states (`DEV`, `PROD_END`, `PROD`).
-
-### Pre-Personalization
-
-The following steps illustrate the set of operations required prior to
-personalization.
-
-#### Wafer Test - Electrical Die Sorting (EDS)
-
-*   `RAW_UNLOCK`: Unlock TEST mode by providing unlock token via TAP interface.
-*   `ANALOG TRIM & TEST (EDS)`: Regular analog test, scan, calibration, trimming
-    and functional testing (EDS stage).
-*   `SET DEVICE_ID`: Program the device identifier fuses. `DEVICE_ID` export may
-    be required depending on the provisioning flow.
-*   `LOCK DEVICE`: (Optional) Program the TEST_UNLOCK (optional) and/or
-    TEST_EXIT tokens depending on the test flow configuration.
-
-    Optional: If required by the manufacturing flow, lock the device for safe
-    transport into the next test stage.
-
-#### Package Test (TEST MODE) - Automated Test Equipment (ATE)
-
-*   `TEST_UNLOCK`: Unlock TEST mode by providing TEST_UNLOCK token via TAP
-    interface.
-*   `ANALOG TRIM & TEST (OSAT)`: Regular analog test, scan, calibration,
-    trimming and functional testing (EDS stage).
-*   `LOG DEVICE_ID`: Record DEVICE_ID and test results.
-*   `LOCK DEVICE`: Program the TEST_UNLOCK (optional) and/or TEST_EXIT tokens
-    depending on test configuration.
-
-    Optional: If required by the manufacturing flow, lock the device for safe
-    transport into the next test stage.
-
-#### Package Test (DEV or PROD MODE) - Automated Test Equipment (ATE)
-
-*   `TEST_EXIT`: Unlock device and transition from TEST to one of the following
-    operational states: DEV, PROD_END, or PROD.
-*   `SKU SETTINGS`: SKU dependent fuse and info flash configuration.
-*   `PERSONALIZATION`: See [Personalization](#personalization) section for more
-*   `LOAD FW IMAGE`: Load factory image payload.
-
-## Personalization {#personalization}
-
-### Test Setup
-
-The following diagram shows the physical connections between test components.
-
-<table>
-  <tr>
-     <td>
-        <img src="img_ate_setup.svg" style="width: 800px;">
-     </td>
-  </tr>
-  <tr><td style="text-align:center;">Figure: FT Connectivity</td></tr>
-</table>
-
-#### Components
-
-*   **Device Under Test**: An OpenTitan device being tested as part of its
-    manufacturing process.
-*   **ATE**: Automatic Test Equipment (ATE), used to perform tests on the
-    devices as part of the manufacturing process.
-*   **Provisioning Appliance**: A network connected local server with an
-    attached HSM. The server implements an OpenTitan compliant secure boot
-    implementation, and runs signed applications used to communicate with ATE
-    and cloud services.
-*   **Provisioning Service**: Cloud service used to authenticate and initialize
-    provisioning appliances.
-
-    The provisioning service is used to provide initialization data to the
-    provisioning appliance once it has been authenticated.
-
-*   **Registry Service**: A cloud service used to host a device registry
-    containing certificates and Certificate Revocations Lists (CRLs).
-
-#### Connectivity
-
-ATE - OpenTitan (DUT)
-
-*   `RST_N`: OpenTitan reset pin.
-*   `STRAPS`: Pins used to control hardware and software test functionality. On
-    the hardware side, strap pins are used to configure TEST modes and select
-    TAP interfaces. On the software side, straps used to enable the SPI flash
-    bootstrap mode in ROM, as well as manufacturing test modes. Some strap
-    functionality is only available before entering DEV or PROD states. There is
-    also additional functionality that is disabled when the device reaches the
-    end of manufacturing.
-*   `SPI`: Bidirectional interface used to exchange payloads and status with the
-    device.
-
-Provisioning Appliance - ATE
-
-*   Local network interface.
-
-Provisioning/Registry Services - Provisioning Appliance
-
-*   External network interface. Connectivity between endpoints is authenticated
-    and encrypted. The Provisioning appliance uses service accounts to identify
-    itself. The Provisioning and Registration Services are authenticated via
-    TLS. The interface has to support unstable connections. The Provisioning
-    Appliance has to be able to buffer messages to be able to recover from
-    network disruptions.
-
-### Overview
-
-The following diagram captures the life of a device throughout the manufacturing
-flow with emphasis on the personalization process.
-
-<table>
-  <tr>
-    <td>
-      <img src="img_provisioning_overview.svg" style="width: 900px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center;">Figure: Device personalization (high level)</td>
-  </tr>
-</table>
-
-Steps:
-
-1.  [Device identifiers][device_id] (`device_id`) and
-    [unlock tokens](#unlock_tokens) are programmed into the device's One Time
-    Programmable (OTP) memory. The unlock tokens are delivered in cleartext form
-    to each OpenTitan die.
-2.  (Optional) A provisioning appliance collects all device identifiers and
-    unlock tokens and sends them in encrypted form to a provisioning service
-    hosted in the cloud.
-3.  Wafers are transported from wafer test (EDS) to the package test (ATE)
-    location. Devices are transported in `TEST_LOCKED` lifecycle state.
-4.  The provisioning service authenticates the provisioning appliance(s) and
-    sends `appliance_secrets, and identifiers (device_ids)` in encrypted form.
-5.  Devices are tested and switched to PROD or DEV mode before personalization.
-    The ATE test setup actuates the personalization flow in collaboration with
-    the provisioning appliance.
-    1.  There are two personalization approaches supported by OpenTitan.
-        1.  [Injection Process](#injection_process): The provisioning appliance
-            generates `device_secrets` (in the injection case) and endorsement
-            certificates. `appliance_secrets` are used to enable signing on
-            endorsement certificates with Silicon Creator intermediate CA keys.
-        2.  [Self-Generated Process](#self_generated_process): The device
-            generates its own `device_secrets` and the provisioning appliance is
-            used to sign the endorsement certificate.
-6.  At the end of a successful provisioning flow, the provisioning appliance
-    sends the device certificates to a device registry. Silicon Owners can use
-    the registry as part of identity ingestion flows.
-7.  The devices are shipped with a factory image and a Silicon Creator
-    endorsement certificate. The Silicon Creator may also provide
-    [Owner Personalization](#owner_personalization) services. All shipped
-    devices have secure boot enabled, which provides a logical locking mechanism
-    to restrict the use of open samples.
-8.  The Silicon Creator may issue a Certificate Revocation List (CRL) to the
-    device registry. The registry is in charge of serving the CRL to downstream
-    consumers (Silicon Owners).
-
-### Secure Boot
-
-Secure boot is always enforced by the mask ROM and cannot be disabled.
-Personalization and factory software payloads are signed, and boot verification
-is used to anchor the mechanism in which the device authenticates the
-provisioning appliance during personalization.
-
-### Injection Process
-
-This section describes the personalization injection mode in detail.
-
-<img src="img_provisioning_injection.svg" style="width: 900px;">
-
-**P0. Get device identifier**
-
-The DEVICE is initially in LOCKED state before the start of production test. The
-TESTER gets the Device ID via TAP interface. The provisioning appliance returns
-unlock tokens associated with the Device ID.
-
-Note 1: The Device ID read requirement applies only if unlock tokens are unique
-per device.
-
-Note 2: The manufacturer must document the unlock token usage plan, including
-the proposed rotation plan.
-
-**P1. Unlock device and load personalization software**
-
-P1.1. The TESTER unlocks the DEVICE by sending the `TEST_UNLOCK` and `TEST_EXIT`
-tokens via TAP interface.
-
-P1.2. The TESTER loads personalization software on the DEVICE using the SPI
-bootstrap interface. The DEVICE verifies the personalization software
-signature against one of the public keys stored in the ROM via secure boot.
-
-**P2. Device authentication and key exchange**
-
-P2.1 Authentication function
-
-The authentication function relies on the following assumptions:
-
-*   The provisioning appliance (HSM) is the only component trusted with secrets
-    other than the Device Under Test (DUT) in the manufacturing floor.
-*   The authentication function can only be implemented by trusted components.
-*   The wafer manufacturer does not collude with the OSAT to attack the
-    authentication function, although some mitigations are considered.
-
-```
-# Shared secret key between provisioning appliance and the DEVICE.
-# The parameters of the authentication function are outside the
-# classification scope of this document.
-key_auth = authentication_function()
-```
-
-P2.2. DEVICE sends authentication data to TESTER
-
-**The DEVICE will fail to generate an authentication payload if the device is
-not in `PROD`, `PROD_END` or `DEV` state.**
-
-The DEVICE sends an authentication payload to the TESTER including:
-
-*   Device ID (`device_id`).
-*   Static key (`receiver_pub_key`) used to derive session keys[^1]; and,
-*   MAC tag covering the authentication payload.
-
-The DEVICE sends the payload to the TESTER via SPI, repeating the message
-continuously to simplify timing adjustments in the test sequence.
-
-Payload generation:
-
-```
-// ECC key pair generation compliant to FIPS 186-4 appendix B.4.
-// Curves under consideration: NIST_P256 and NIST_P386.
-receiver_priv_key, receiver_pub_key = ECC_KDF(DRBG_context)
-
-key_auth = authentication_function()
-
-// data_size includes the size of the data + tag.
-data = "OTAU" || receiver_pub_key || device_id || data_size
-tag = MAC(key_auth, data)
-
-// Continuously broadcast via SPI device port.
-return data || tag
-```
-
-P2.3 TESTER verifies authentication data
-
-The TESTER calls the provisioning appliance to verify the tag attached to the
-DEVICE authentication payload using the authentication key function from P2.1.
-The TESTER aborts personalization on failure.
-
-Verification function:
-
-```
-key_auth = authentication_function()
-
-data || tag = authentication_data
-calculated_tag = MAC(key_auth, data)
-RETURN_ERROR_IF(calculated_tag != tag)
-```
-
-**P3: Inject secrets**
-
-P3.1 Get wrapped personalization payload from provisioning appliance
-
-The TESTER gets a personalization payload from the provisioning appliance. The
-provisioning appliance is in charge of generating the device secrets. The
-payload is associated with the Device ID and is wrapped with a key derived from
-the `receiver_pub_key`extracted in step P2.
-
-Transport wrapping. See [ECIES Encryption](#ecies_encryption) for more details
-on the `ECIES_ENC` input and output parameters. Some parameters are omitted for
-simplicity.
-
-```
-// OPEN: ctx_id in this case is set to a portion of the device_id.
-// The manufacturer shall incorporate a monotonically incrementing counter
-// into ctx_id.
-ephemeral_pub_key || tag || ctx_id || sender_pub_key ||
-  data_size || data_enc =
-  ECIES_ENC(key=sender_priv_key, receiver_pub_key,
-            data=(device_secrets || creator_certificate))
-
-personalization_payload =  "OTPL" || tag || ctx_id ||
-   sender_pub_key || data_size || data_enc
-```
-
-P3.2 TESTER sends personalization payload to DEVICE
-
-The personalization payload is sent to the device via SPI in 1KB frame chunks.
-The payload is transferred from the DEVICE SPI RX FIFO into SRAM.
-
-**P4. DEVICE unwrap and install secrets**
-
-P4.1 DEVICE unwraps secrets and creator certificate
-
-Unwrap process. See [ECIES Decryption](#ecies_decryption) for more details on
-the `ECIES_DEC` input and output parameters. Some parameters are omitted for
-simplicity.
-
-```
-// The following operation will verify the integrity of the encrypted
-// blob. The sender_pub_key is verified against a whitelist stored
-// in the personalization software.
-device_secrets, creator_cerficate =
-    ECIES_DEC(key=receiver_priv_key,
-              ephemeral_pub_key, sender_key_pub, data=enc_payload)
-```
-
-P4.2 Install secrets
-
-Secrets are installed in OTP and in Flash. See Device Secrets for a breakdown of
-Silicon Creator level secrets.
-
-P4.3 Report install status
-
-Report install status to tester via SPI interface. The status code is repeated
-continuously on the SPI interface to simplify the test implementation.
-
-**P5. Install factory image**
-
-The factory image is the software loaded on the device before shipping. It is
-not expected to change frequently once the manufacturing flow is deployed for a
-given SKU configuration. At a minimum, the factory image will contain a ROM
-Extension (`ROM_EXT`) component.
-
-The TESTER programs the factory image on the DEVICE via SPI bootstrap interface
-The hash of the `ROM_EXT` must match the value used to calculate the Creator
-Identity as described in the
-[Identities and Root Keys][identities_and_root_keys] document.
-
-**P6. Provisioning result**
-
-The DEVICE boots in identity UNOWNED state and in manufacturing mode. The
-conditions used to trigger manufacturing mode are TBD.
-
-P6.1 Test Creator Identity
-
-The `ROM_EXT` uses the key manager to obtain the Creator Identity key. See the
-[Asymmetric Keys section in the Attestation specification][attestation_keys] for
-more details.
-
-The public portion of the Creator Identity is tested against the Creator
-Certificate. The result is reported to the TESTER via SPI.
-
-### Self-Generated Process {#self_generated_process}
-
-This section describes the personalization self-generate mode in detail. In this
-mode, The device generates its own device secrets and the provisioning appliance
-is used to sign the endorsement certificate.
-
-<img src="img_provisioning_self_gen.svg" style="width: 900px;">
-
-**PS0. Get device identifier**
-
-The DEVICE is initially in LOCKED state. The TESTER gets the Device ID via TAP
-interface. The provisioning appliance returns unlock tokens associated with the
-Device ID.
-
-**PS1. Unlock device and load perso firmware**
-
-PS1.1. The TESTER unlocks the DEVICE by sending the `TEST_UNLOCK` and
-`TEST_EXIT` tokens via TAP interface.
-
-PS1.2. The TESTER loads personalization software on the DEVICE using the SPI
-bootstrap interface. The DEVICE verifies the personalization software signature
-against one of the public keys stored in the ROM via secure boot.
-
-**PS2. Generate and install secrets**
-
-The device uses the available on-device entropy source to generate all the
-device secrets.
-
-Secrets are installed in OTP and in Flash. Fuse locks are set as described in
-the OTP specification.
-
-**PS3. Export public key**
-
-The device exports the Creator Identity public key via SPI. The authentication
-function from P2.1 is used to generate an integrity digest over the exported
-data.
-
-Payload with integrity tag:
-
-```
-// Shared between device and provisioning appliance.
-key_auth = authentication_function()
-
-// data_size includes the size of the data + tag.
-data = "OTAU" || data_size || device_id || creator_identity_pub_key
-tag = MAC(key_auth, data)
-
-// Continuously broadcast via SPI device port.
-return data || tag
-```
-
-**PS4. Generate certificate**
-
-PS4.1. Verify received data
-
-The provisioning appliance verifies the integrity of the received payload. The
-verification of the payload is used to authenticate the device, since the key
-used to calculate the digest tag is only known to the device and the
-provisioning appliance.
-
-Verification function:
-
-```
-key_auth = authentication_function()
-
-data || tag = authentication_data
-calculated_tag = MAC(key_auth, data)
-RETURN_ERROR_IF(calculated_tag != tag)
-```
-
-PS4.2. Generate certificate
-
-The provisioning appliance generates an endorsement certificate as defined in
-the [Certificate Format][attestation_certificate] section of the Attestation
-specification.
-
-PS4.3. Send certificate
-
-The certificate is sent to the device via SPI in 1KB frames. An integrity tag is
-added to the message using the same mechanism as in PS3.
-
-Provisioning service to device certificate payload.
-
-```
-// Shared between device and provisioning appliance.
-key_auth = authentication_function()
-
-// data_size includes the size of the data + tag.
-data = "OTCI" || data_size || device_id || creator_identity_cert
-tag = MAC(key_auth, data)
-
-// Continuously broadcast via SPI device port.
-return data || tag
-```
-
-**PS5. Install certificate**
-
-The device verifies the certificate payload sent by the provisioning appliance
-and installs it in a flash block.
-
-**PS6. Install factory image**
-
-The factory image is the software loaded on the device before shipping. It is
-not expected to change frequently once the manufacturing flow is deployed for a
-given SKU configuration. At a minimum, the factory image will contain a ROM
-Extension (`ROM_EXT`) component.
-
-The TESTER programs the factory image on the DEVICE via SPI bootstrap interface.
-The hash of the `ROM_EXT` must match the value used to calculate the Creator
-Identity as described in the
-[Identities and Root Keys][identities_and_root_keys] document.
-
-**PS7. Provisioning result.**
-
-The `ROM_EXT` uses the key manager to obtain the Creator Identity key. See the
-[Asymmetric Keys section in the Attestation specification][attestation_keys] for
-more details.
-
-The public portion of the Creator Identity is tested against the Creator
-Certificate. The result is reported to the TESTER via SPI.
-
-## Owner Personalization {#owner_personalization}
-
-OpenTitan provides a mechanism to enable provisioning of Silicon Owner secrets
-and endorsement certificates in manufacturing and post-manufacturing stages.
-Owners are encouraged to create an implementation plan to perform
-post-manufacturing provisioning to take full advantage of ownership transfer.
-
-Provisioning post-ownership transfer assumes that the OpenTitan device is
-integrated into a system, and there is a HOST capable of communicating
-synchronously or asynchronously with the DEVICE (OpenTitan) and a remote
-registry and provisioning service.
-
-The physical transport layer between the DEVICE and the HOST is use-case
-specific and managed at the OpenTitan SKU configuration level (e.g. different
-`ROM_EXT` implementation per SKU).
-
-### Overview
-
-The following diagram captures the Silicon Owner provisioning flow. Device
-attestation requires the device to have a valid Creator Identity endorsed by the
-Silicon Creator as described in the [Personalization](#personalization) section.
-
-The process can be implemented during manufacturing or post-manufacturing. If
-implemented during manufacturing, the ATE and provisioning appliance fulfill the
-role of the HOST.
-
-<table>
-  <tr>
-    <td>
-      <img src="img_provisioning_owner.svg" style="width: 900px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center;">Figure: Owner personalization</td>
-  </tr>
-</table>
-
-Steps:
-
-1.  [Device identifiers][device_id] (`device_id)` and Silicon [Creator
-    Certificates][attestation_certificate] are imported into the Silicon Owners
-    internal device registry.
-2.  Ownership transfer is performed on the device to provision code verification
-    keys used as part of secure boot. Software signed by the Silicon Owner is
-    programmed on the device.
-3.  The host verifies the attestation chain provided by the device and requests
-    a provisioning payload from the Silicon Owner's provisioning service. Data
-    in transit is encrypted with a key negotiated between the OpenTitan device
-    and the provisioning service. Provisioning data is divided into:
-    1.  Owner Certificate endorsed by the Silicon Owner. The owner can also
-        implement endorsement of additional certificates as part of the process.
-    2.  Additional Owner secrets required to provision application level
-        secrets.
-4.  The host sends the provisioning payload to the device. The device unwraps
-    the secrets and installs them.
-
-### Injection Process
-
-<table>
-  <tr>
-    <td>
-      <img src="img_provisioning_owner_injection.svg" style="width: 900px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center;">Figure: Provisioning post-ownership transfer</td>
-  </tr>
-</table>
-
-**RP0. Ownership transfer**
-
-The DEVICE is initially in UNOWNED state. The Silicon Owner triggers ownership
-transfer and loads new software. Ownership transfer details are covered in a
-separate document.
-
-See [Ownership Transfer][ownership_transfer] document for more details.
-
-**RP1. Get attestation certificates**
-
-The HOST requests attestation certificates from the DEVICE. A device that has
-been transferred to a new owner has its attestation chain rooted in the Creator
-Identity. See the [Attestation][attestation] documentation for more information.
-
-**RP2. Get `receiver_pub_key`**
-
-The DEVICE generates a `receiver` key pair and shares the `receiver_pub_key`
-with the HOST.
-
-Additional data required to generate a new owner certificate and/or device
-secrets is added to the payload. The payload is signed with the
-`owner_identity_key` which can be verified against the DEVICE attestation chain
-obtained in RP1.
-
-```
-// ECC key pair generation compliant to FIPS 186-4 appendix B.4.
-// Curves under consideration: NIST_P256 and NIST_P386.
-receiver_priv_key, receiver_pub_key = ECC_KDF(DRBG_context)
-
-data = any additional data required to generate the owner cert.
-hdr = ctx_id || receiver_pub_key || data
-
-// owner_identity_key is an ECC key as described in the Asymmetric
-// Keys section of the Attestation document.
-signature = ASYM_SIGN(key=owner_identity_priv_key, data=hdr)
-
-payload = hdr || signature
-```
-
-**RP3. Get wrapped secrets and owner certificate**
-
-RP3.1. The provisioning service verifies the `receiver_pub_key` against the
-attestation chain obtained in RP1 and the internal device registry.
-
-The provisioning service then generates a new `owner_certificate` with the
-additional data provided by the DEVICE. The certificate is signed with a key
-managed by the Silicon Owner (e.g. `owner_intermediate_ca_key`). The new
-`owner_certificate` will be used as the root certificate in Attestation flows as
-described in the [Attestation][attestation] document.
-
-RP3.2. The provisioning service performs ECIES encryption using the following
-parameters:
-
-*   `sender_priv_key`: Provisioning service private key. The public portion is
-    known to the DEVICE firmware.
-*   `receiver_pub_key`: DEVICE public key obtained in step RP2.
-*   `ctx_id`: Context ID provided by the device in step RP2.
-*   `owner_certificate`: New owner certificate generated in step RP3.1.
-*   `device_secrets`: Additional secrets bound to the device. May be generated
-    offline or as part of step RP3.1.
-
-```
-payload =
-ECIES_ENC(key=sender_priv_key, pub_key=receiver_pub_key,
-            data=(device_secrets | ctx_id))
-| owner_certificate
-```
-
-RP3.3. The wrapped payload is sent to the DEVICE.
-
-**RP4. Unwrap and install secrets and owner certificate**
-
-Device performs ECIES decryption to unwrap the payload and install the new
-`owner_certificate` and `device_secrets`. The owner has access to info flash
-pages for storage of secrets.
-
-The `server_key_pub` and `ephemeral_pub_key` values are sent with the wrapped
-payload. The public server key (`server_key_pub`) is also known to the software
-running in the DEVICE.
-
-```
-device_secrets || owner_certificate =
-    ECIES_DEC(key=receiver_priv_key, server_key_pub,
-              ephemeral_pub_key, data=enc_payload)
-```
-
-**RP5: Get attestation certificates**
-
-The HOST issues a new attestation command to the DEVICE. The DEVICE responds
-with an attestation chain rooted on the new `owner_certificate`. An additional
-test command can be supported to test any functionality associated with the
-`device_secrets`.
-
-## ECIES Operations
-
-**Key Establishment**
-
-Key exchange is based on [NIST 800-56Ar3][nist_800_56r3] 6.2.1.2 Cofactor
-One-Pass Unified Model Scheme, employing one ephemeral key, two secret keys and
-ECC CDH. Both sender and receiver authentication are performed via public
-crypto, thus one secret key is associated with the receiver, while the other one
-is associated with the sender.
-
-**AEAD Modes of Operation**
-
-The following authenticated encryption schemes are supported by this
-architecture:
-
-*   AES-CTR-HMAC-12B-IV-32B-Tag (FIPS OK)
-*   AES-CTR-HMAC-16B-IV-32B-Tag (FIPS OK)
-*   AES-GCM (FIPS OK)
-
-### Encryption
-
-<table>
-  <tr>
-    <td>
-      <img src="img_ecies_encryption.svg" style="width: 900px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center;">Figure: ECIES Encryption, simplified diagram</td>
-  </tr>
-</table>
-
-Inputs:
-
-*   `receiver_pub_key`: ECC public key provided by the receiver point (e.g.
-    NIST_P256, NIST_P386
-*   `sender_priv_key`: Sender ECC private key.
-*   `ctx_id`: Context identifier provided by the device. Used to control forward
-    secrecy of the protocol. For personalization this value is set to a portion
-    of the device identifier (`device_id`).
-*   `data`, `data_size`: Data to be encrypted and its size
-*   `ecies_key_length`: Target ECIES key length. Used as a configuration
-    parameter.
-
-Outputs:
-
-*   `ephemeral_pub_key`: Ephemeral ECC shared key, required to perform
-    decryption.
-*   `tag`: MAC over payload.
-*   `ctx_id`: Context identifier provided by the device.
-*   `sender_pub_key`: Sender ECC public key.
-*   `data_enc`, `data_size`: Encrypted data and its size.
-
-Algorithm:
-
-```
-// NIST 800-56Ar3 6.2.1.2 One-Pass Unified Model. This requires
-// the creation of two shared secrets: shared_ephemeral and
-// shared_static.
-ephemeral_priv_key = GenerateEphemeralKey()
-shared_ephemeral =
-    ECDH_compute_key(key_len,
-                     EC_POINT(receiver_pub_key), ephemeral_priv_key)
-shared_static =
-    ECDH_compute_key(key_len,
-                     EC_POINT(receiver_pub_key), sender_priv_key)
-
-// Key derivation function used to calculate K and IV.
-K, IV = key_and_iv_generaration(
-   ctx_id, data_size, shared_ephemeral, shared_static,
-   receiver_pub_key, sender_pub_key)
-
-// The following authenticated encryption approach follows encrypt
-// then MAC approach.
-// See also Alternative Authenticated Encryption Scheme
-
-// Ke length should be one of 128, 192, 256.
-// Km length should be one of 256, 384, 512.
-// K should have an entropy with a security strength equivalent to
-// the one provided by Ke and Km when used with AES_CTR and MAC
-// respectively.
-[Ke || Km] = K
-
-data_enc = AES_CTR_ENC(Ke, IV, data)
-tag = MAC(Km, ctx_id || sender_pub_key || data_size || data_enc)
-
-return [
-  ephemeral_pub_key || tag || ctx_id || sender_pub_key ||
-  data_size || data_enc]
-```
-
-#### Alternative Authenticated Encryption Scheme
-
-The following implementation uses AES in GCM mode to obtain the ciphertext and
-integrity tag.
-
-```
-// The following authenticated encryption approach removes the need
-// for separately calling a MAC function. In this case there is no
-// need to split the key K into Ke and Km components.
-
-// All the parameters are taken from the main encryption pseudo-code
-// block above. The following call replaces the AES_CTR and MAC
-// calls.
-data_enc, tag =
-   AES_GCM_ENC(K, IV, ctx_id || sender_pub_key || data_size || data)
-```
-
-### Decryption
-
-<table>
-  <tr>
-    <td>
-      <img src="img_ecies_decryption.svg" style="width: 900px;">
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center;">Figure: ECIES Decryption, simplified diagram</td>
-  </tr>
-</table>
-
-Inputs:
-
-*   `receiver_priv_key`: Receiver ECC private key. Generated by the device and
-    associated with the context identifier (`ctx_id`).
-*   `ephemeral_pub_key`: Ephemeral ECC shared key generated by the sender.
-*   `sender_pub_key`: Sender ECC public key. Embedded in payload sent to the
-    device.
-*   `ctx_id`: Context identifier provided by the device.
-*   `data`, `data_size`: Encrypted data and its size.
-*   `ecies_key_length`: Target ECIES key length. Used as a configuration
-    parameter.
-
-Outputs:
-
-The algorithm implementation currently includes authentication and integrity
-checks, thus plaintext is the only documented output parameter.
-
-*   `plaintext`, `size`: Plaintext data and its size.
-
-Algorithm:
-
-```
-[ephemeral_pub_key || tag || ctx_id ||
- sender_pub_key || data_size || ciphertext ] = data
-
-// Check sender_pub_key against whitelist stored in device software.
-check_sender_key(sender_pub_key)
-
-// NIST 800-56Ar3 6.2.1.2 One-Pass Unified Model. This requires
-// the creation of two shared secrets: shared_ephemeral and
-// shared_static.
-ephemeral_key = GenerateEphemeralKey()
-shared_ephemeral =
-    ECDH_compute_key(key_len,
-                     EC_POINT(ephemeral_pub_key), ephemeral_priv_key)
-shared_static =
-    ECDH_compute_key(key_len,
-                     EC_POINT(sender_pub_key), ephemeral_priv_key)
-
-// Key derivation function used to calculate K and IV.
-K, IV = key_and_iv_generaration(
-   ctx_id, data_size, shared_ephemeral, shared_static,
-   receiver_pub_key, sender_pub_key)
-
-// The following authenticated encryption approach follows encrypt
-// then MAC approach.
-// See also
-
-// Ke length should be one of 128, 192, 256.
-// Km length should be one of 256, 384, 512.
-// K should have an entropy with a security strength equivalent to
-// the one provided by Ke and Km when used with AES_CTR and MAC
-// respectively.
-Ke || Km = K
-
-tag_expected =
-   MAC(Ke, ctx_id || sender_pub_key || data_size || data_enc)
-
-RETURN_ERROR_IF(tag != tag_expected)
-RETURN_ERROR_IF(ctx_id != ctx_id_expected)
-
-plaintext = AES_CTR_DEC(Km, IV, data=ciphertext)
-
-return [plaintext]
-```
-
-### Key and IV Derivation
-
-Inputs:
-
-*   `shared_ephemeral`: Derived ephemeral key. See Encryption and Decryption
-    algorithms for more details.
-*   `shared_static`: Derived static key.
-*   `ctx_id`: Context identifier provided by the device.
-*   `data_size`: Size of input data.
-*   `ecies_key_length`: Target ECIES key length. Used as a configuration
-    parameter.
-
-Outputs:
-
-*   `K`: ECIES key of size equal to `ecies_key_length`.
-*   `IV`: `IV` parameter derived to use as AES decrypt parameter.
-
-Algorithm:
-
-```
-shared_secret = shared_ephemeral || shared_static
-salt = "shared_tag" || ctx_id || 00's padding
-
-// KDF based on NIST 800-56C 2-step key derivation. The extract and
-// expand operations can be implemented via HMAC-SHA2. For SHA3 see
-// Alternative KDF 1-step KMAC.
-KDK = HKDF_extract(salt=salt, secret=shared_secret)
-
-L = ecies_key_length
-label = "ot_encrypt"
-context = receiver_pub_key || sender_pub_key
-fixed_info = label || context || STRING(L)
-
-K = HKDF_expand(KDK, L, fixed_info)
-
-# IV generation using additional HKDF_expand
-fixed_info_iv = "ot_iv" || context || STRING(12)
-IV = HKDF_expand(KDK, 12, fixed_info_iv)
-
-return K, IV
-```
-
-##### Alternative KDF 1-step KMAC
-
-The following implementation uses a one-step key derivation function based on a
-KMAC implementation (e.g. KMAC256).
-
-```
-// KDF based on NIST 800-56C section 4.1 1-step key derivation.
-// Using KMAC256 as specified in section 4.1 option 3.
-L = ecies_key_length
-label = "ot_encrypt"
-context = receiver_pub_key || sender_pub_key
-fixed_info = label || context || STRING(L)
-
-salt =
-   "shared_tag" || ctx_id || 00's padding up to key size
-
-other_input = salt || fixed_info
-K = HKDF(secret=shared_secret, L, other_intput)
-
-
-fixed_info_iv = "ot_iv" || context || STRING(12)
-IV = HKDF(secret=shared_secret, 12,
-          other_input=salt || fixed_info_iv)
-
-return K, IV
-```
-
-<!-- Footnotes themselves at the bottom. -->
-
-## Notes
-
-
-[^1]: This is a static key within the context of a personalization run. The key
-    is erased after personalization is complete.
-
-
-[attestation]: {{< relref "/doc/security/specs/attestation" >}}
-[attestation_keys]: {{< relref "/doc/security/specs/attestation" >}}#asymmetric-keys
-[attestation_certificate]: {{< relref "/doc/security/specs/attestation" >}}#certificate-format
-[creator_identity]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#creator-identity
-[device_lifecycle]: {{< relref "/doc/security/specs/device_life_cycle" >}}
-[device_id]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}#device-identifier
-[identities_and_root_keys]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}
-[nist_800_56r3]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar3.pdf
-[ownership_transfer]: {{< relref "/doc/security/specs/ownership_transfer" >}}
diff --git a/doc/security/specs/firmware_update/README.md b/doc/security/specs/firmware_update/README.md
new file mode 100644
index 0000000..80dbc29
--- /dev/null
+++ b/doc/security/specs/firmware_update/README.md
@@ -0,0 +1,48 @@
+# Firmware Update
+
+<p style="color: red; text-align: right;">
+  Status: Pre-RFC
+</p>
+
+# Overview
+
+The purpose of this document is to capture the process by which Tock will
+perform a complete in-place B-slot update of the running firmware and request
+that the `ROM/ROM_EXT` code boot into it.
+
+*Note*: For certification reasons this document does not discuss detailed
+guidance for encrypted updates. Additional implementation details on both
+regular and encrypted updates to follow when possible.
+
+# Update Process
+
+1. A firmware update is initiated by a process external to the device.
+    1. The system negotiates the parameters of the update:
+        1. The system's availability for an update.
+        2. Which slot the update must be built for, if the update requires a
+           specific memory location (such as non relocatable monoliths).
+        3. Which signature the update must be signed with in order to boot.
+        4. The size of the update blocks. These should ideally be the same size
+          as native flash pages.
+        5. The total size of the update payload, in order to set boundaries on
+           which the update can be performed.
+2. The system unlocks the flash protection for the bounds of the B-slot.
+3. For each block of the pending firmware update:
+    1. The system erases the flash page at the destination address for the
+       block.
+    2. The system receives the block and writes it immediately to its
+       destination address in flash. Blocks will need to be buffered given the
+       speed of a flash write.
+    3. Once written, the block is hashed and compared against the hash of the
+       buffered block. This ensures that every flash write is complete and
+       successful.
+        1. If the hash doesn't match the hash of the buffered block, repeat the
+           flash erase and write cycle.
+        2. If the erase/flash cycle fails three times declare complete failure.
+4. Issue a warning to the running applications that the system will be
+   resetting.
+5. Write a Boot Services request block into persistent RAM:
+    1. Indicate the new firmware version slot to be booted.
+    2. Indicate the retry policy to use when booting it.
+6. Restart the system with a Boot Services cause set.
+7. System follows the normal Secure Boot path into the new firmware image.
diff --git a/doc/security/specs/firmware_update/index.md b/doc/security/specs/firmware_update/index.md
deleted file mode 100644
index 562e84b..0000000
--- a/doc/security/specs/firmware_update/index.md
+++ /dev/null
@@ -1,50 +0,0 @@
----
-title: "Firmware Update"
----
-
-<p style="color: red; text-align: right;">
-  Status: Pre-RFC
-</p>
-
-# Overview
-
-The purpose of this document is to capture the process by which Tock will
-perform a complete in-place B-slot update of the running firmware and request
-that the `ROM/ROM_EXT` code boot into it.
-
-*Note*: For certification reasons this document does not discuss detailed
-guidance for encrypted updates. Additional implementation details on both
-regular and encrypted updates to follow when possible.
-
-# Update Process
-
-1. A firmware update is initiated by a process external to the device.
-    1. The system negotiates the parameters of the update:
-        1. The system's availability for an update.
-        2. Which slot the update must be built for, if the update requires a
-           specific memory location (such as non relocatable monoliths).
-        3. Which signature the update must be signed with in order to boot.
-        4. The size of the update blocks. These should ideally be the same size
-          as native flash pages.
-        5. The total size of the update payload, in order to set boundaries on
-           which the update can be performed.
-2. The system unlocks the flash protection for the bounds of the B-slot.
-3. For each block of the pending firmware update:
-    1. The system erases the flash page at the destination address for the
-       block.
-    2. The system receives the block and writes it immediately to its
-       destination address in flash. Blocks will need to be buffered given the
-       speed of a flash write.
-    3. Once written, the block is hashed and compared against the hash of the
-       buffered block. This ensures that every flash write is complete and
-       successful.
-        1. If the hash doesn't match the hash of the buffered block, repeat the
-           flash erase and write cycle.
-        2. If the erase/flash cycle fails three times declare complete failure.
-4. Issue a warning to the running applications that the system will be
-   resetting.
-5. Write a Boot Services request block into persistent RAM:
-    1. Indicate the new firmware version slot to be booted.
-    2. Indicate the retry policy to use when booting it.
-6. Restart the system with a Boot Services cause set.
-7. System follows the normal Secure Boot path into the new firmware image.
diff --git a/doc/security/specs/identities_and_root_keys/README.md b/doc/security/specs/identities_and_root_keys/README.md
new file mode 100644
index 0000000..054dec9
--- /dev/null
+++ b/doc/security/specs/identities_and_root_keys/README.md
@@ -0,0 +1,818 @@
+# Identities and Root Keys
+
+## Overview
+
+This document describes the composition of the Silicon Creator and Silicon Owner
+cryptographic identities and the Silicon Owner root key derivation scheme. This
+scheme is based on a symmetric key manager with support for software binding and
+key versioning.
+
+This document also defines a non-cryptographic **Device Identifier** to
+facilitate silicon tracking during manufacturing flows. The Device Identifier is
+also mixed into the Creator Identity.
+
+Further, this scheme is compatible with the [Open DICE profile](https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md).
+
+Overall identity flow:
+<img src="identities_and_root_keys_fig1.svg" alt="Fig1" style="width: 1000px;"/>
+
+DICE compatible identity flow:
+<img src="identities_and_root_keys_DICE_fig1b.svg" alt="Fig1b" style="width: 1000px;"/>
+
+## Terminology
+
+Boot stages:
+
+*   `ROM`: Metal ROM, sometimes known as Boot ROM.
+*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon
+    Creator[^1].
+*   `BL0`: Bootloader. Signed by the Silicon Owner.
+*   `Kernel`: Signed by the Silicon Owner.
+
+Key manager operations:
+
+*   `KM_DERIVE`: Key manager one-way function used to derive a new symmetric
+    key.
+
+Memory state operations:
+
+*   `CLEAR_BEFORE_NEXT_BOOT_STAGE`: Clear key material before moving to the next
+    boot stage.
+*   `CLEAR_AFTER_USE`: Clear immediately after use.
+
+## Device Identifier
+
+The device identifier is a globally unique 256b value provisioned on each
+device's OTP memory in early manufacturing stages (e.g. wafer test). It is used
+to facilitate device tracking during manufacturing and provisioning. This value
+is also used as a component in the generation of the device's Silicon Creator
+Identity, a cryptographically unique identity.
+
+The 256b value is split into two halves. The first contains hardware origin
+information following a global standard format, while the second one is defined
+by the device SKU provisioning requirements.
+
+**128b Hardware origin information**
+
+<table>
+  <tr>
+    <td><strong>No. Bits</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>16</td>
+    <td>Silicon Creator identifier. Assigned by the OpenTitan project.</td>
+  </tr>
+  <tr>
+    <td>16</td>
+    <td>
+Product identifier. Assigned by the Silicon Creator. Used to identify a class of
+devices.
+    </td>
+  </tr>
+  <tr>
+    <td>64</td>
+    <td>
+Individual device identification number. Assigned by the Silicon Creator. For
+example, the wafer lot number and die's X,Y coordinates may be encoded here to
+simplify manufacturing tracking. Another option is to use a non cryptographic
+hash function with collision checks to guarantee global uniqueness.
+    </td>
+  </tr>
+  <tr>
+    <td>32</td>
+    <td>CRC-32 IEEE 802.3. covering the previous bytes.</td>
+  </tr>
+</table>
+
+**128b SKU specific device information**
+
+The device provisioner information varies for each provisioning use case. Each
+use case must have a specification defining the allocation of these bits. See
+the
+[UICC EID Specification](https://www.gsma.com/newsroom/wp-content/uploads/SGP.02-v4.0.pdf)
+as an example.
+
+## Creator Root Key (`CreatorRootKey`) {#creator-root-key}
+
+The following sequence describes the creation of the `CreatorRootKey`. All
+inputs into the key manager can be locked down during ROM execution.
+
+The size of the inputs is dependent on the security strength and masking
+configuration of the implementation. Depending on the KM\_DERIVE intrinsic
+function, one of the following two mixing operations is acceptable:
+
+Cascading:
+
+```
+Key0 = KM_DERIVE(RootKey, DiversificationKey)
+Key1 = KM_DERIVE(Key0, HealthStateMeasurement)
+Key2 = KM_DERIVE(Key1, DeviceIdentifier)
+Key3 = KM_DERIVE(Key2, ROMExtSecurityDescriptor)
+
+CreatorRootKey = KM_DERIVE(Key3, HardwareRevisionSecret)
+```
+
+Collapsed:
+
+The concatenation function must be injective. This can be achieved by fixing the
+width of all the operands.
+
+```
+CreatorRootKey = KM_DERIVE(RootKey,
+    DiversificationKey | HealthStateMeasurement | DeviceIdentifier |
+    ROMExtSecurityDescriptor | HardwareRevisionSecret)
+```
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td id="root-key">RootKey</td>
+    <td>OTP</td>
+    <td>
+Device root key. Provisioned at manufacturing time by the Silicon Creator.
+
+Hidden from software once personalization is complete.
+    </td>
+  </tr>
+  <tr>
+    <td id="diversification-key">DiversificationKey</td>
+    <td>Flash</td>
+    <td>
+Additional diversification key stored in flash. Provisioned at
+manufacturing time by the Silicon Creator.
+
+Hidden from software once provisioned.
+    </td>
+  </tr>
+  <tr>
+    <td>ROMExtSecurityDescriptor</td>
+    <td>SW register</td>
+    <td>
+The implementation may choose one of the following options:
+
+1. Hash of the ROM extension. Changes in the ROM extension code will trigger an
+   update of the Creator Identity.
+2. Use a software binding tag stored in the ROM_EXT manifest. This is to
+   retain the Creator Identity across validated updates of the ROM_EXT.
+   The implementation may opt to use the software binding interface
+   described in later sections to fulfill this property.
+    </td>
+  </tr>
+  <tr>
+    <td>DeviceIdentifier</td>
+    <td>OTP</td>
+    <td>
+Provisioned at manufacturing time. Readable from software and JTAG interface.
+    </td>
+  </tr>
+  <tr>
+    <td>HardwareRevisionSecret</td>
+    <td>Gates</td>
+    <td>
+Encoded in gates. Provisioned by Silicon Creator before tapeout. Hidden from
+software.
+    </td>
+  </tr>
+  <tr>
+    <td>Health State Measurement</td>
+    <td>Register (ROM stage)</td>
+    <td>
+Comprises the following measurements:
+
+* Device life cycle state.
+* Debug mode state.
+* ROM Hash.
+
+Some values are read from the device life cycle controller. The device life
+cycle state should be consumed by the ROM stage.
+
+The debug mode shall be used as well if there are multiple debug configurations
+supported by a single life cycle state.
+    </td>
+  </tr>
+</table>
+
+The CreatorRootKey can be used to generate the Creator Identity key and the
+OwnerIntermediateKey described in the following sections.
+
+## Creator Identity
+
+The Creator Identity is an asymmetric key derived from the `CreatorRootKey`. It
+is used as a cryptographic identifier bound to the device and the Silicon
+Creator. It is used to attest to the authenticity of the physical device and the
+ROM and ROM\_EXT configuration.
+
+The Creator Identity is generated as follows:
+
+```
+CreatorIdentitySeed =
+  KM_DERIVE(CreatorRootKey, IdentityDiversificationConstant)
+
+// ASYM_KDF is a KDF function compliant to the Asymmetric Key
+// requirements defined in the Attestation specification document.
+CreatorIdentity_Private = ASYM_KDF(CreatorIdentitySeed)
+CLEAR_BEFORE_NEXT_BOOT_STAGE(CreatorIdentitySeed, CreatorIdentity_Private)
+```
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong>
+    </td>
+  </tr>
+  <tr>
+    <td>IdentityDiversificationConstant</td>
+    <td>Gates</td>
+    <td>
+A constant defined in gates. Used to derive the CreatorIdentitySeed from the
+CreatorRootKey.
+
+Hidden from software.
+    </td>
+  </tr>
+  <tr>
+    <td>CreatorIdentitySeed</td>
+    <td>SW Register Output</td>
+    <td>Seed used to generate the CreatorIdentity asymmetric key.</td>
+  </tr>
+</table>
+
+The `CreatorIdentitySeed` and the private portion of the Creator Identity shall
+be cleared before the ROM Extension hands over execution to the Silicon Owner
+first boot stage.
+
+## OwnerIntermediateKey {#owner-intermediate-key}
+
+The `OwnerIntermediateKey` is used as a root component of the Silicon Owner key
+hierarchy. It is used to establish a cryptographic link to the root secrets
+provisioned at manufacturing time.
+
+**Visibility**
+
+The `OwnerIntermediateKey` shall be kept hidden from software to mitigate owner
+impersonation attacks.
+
+The `OwnerIntermediateKey` is generated as follows:
+
+```
+OwnerIntermediateKey =
+   KM_DERIVE(CreatorRootKey, OwnerRootSecret | SoftwareBindingValue)
+```
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td id="owner-root-secret">OwnerRootSecret</td>
+    <td>Flash</td>
+    <td>
+Used as a diversification constant with acceptable entropy. Provisioned at
+Ownership Transfer time by the Silicon Creator.
+
+The OwnerRootSecret has different visibility options depending on the level of
+isolation provided in hardware:
+
+*   The value should be hidden from software after provisioning.
+*   The value is visible to ROM and ROM Extension, but hidden from all Silicon
+    Owner software. The ROM Extension implements this property.
+    </td>
+  </tr>
+  <tr>
+    <td>SoftwareBindingValue</td>
+    <td>SW Register<br>Lockable Input</td>
+    <td>
+Software binding value configured during secure boot. See
+<a href="#software-binding">Software Binding</a> for more details.
+    </td>
+  </tr>
+</table>
+
+## Owner Identity
+
+The Owner Identity is used as a cryptographic identifier bound to the device and
+the Silicon Owner. It is used in Attestation flows. The Owner Identity is not
+expected to change during the lifetime of the device ownership assignment.
+
+```
+OwnerIdentitySeed =
+  KM_DERIVE(OwnerIntermediateKey, OwnerRootIdentityKey)
+
+// ASYM_KDF is a KDF function compliant to the Asymmetric Key
+// requirements defined in the Attestation specification document.
+OwnerIdentity_Private = ASYM_KDF(OwnerIdentitySeed)
+CLEAR_BEFORE_NEXT_BOOT_STAGE(OwnerRootSeed, OwnerIdentity_Private)
+```
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>Owner Root Identity Key</td>
+    <td>Gates</td>
+    <td>
+The OwnerRootIdentityKey is a diversification constant with acceptable entropy
+provisioned in gates.
+
+Visibility: Hidden from software.
+    </td>
+  </tr>
+  <tr>
+    <td>Owner Identity Seed</td>
+    <td>SW Register Output</td>
+    <td>Seed used to generate the OwnerIdentity asymmetric key.</td>
+  </tr>
+</table>
+
+The `OwnerIdentitySeed` and the private portion of the Owner Identity shall be
+cleared before the bootloader (BL0) hands over execution to the kernel.
+
+## Owner Root Key and Versioned Keys {#owner-root-key}
+
+The key manager supports the generation of versioned keys with lineage to the
+`OwnerRootKey` for software consumption and sideload operations.
+
+```
+OwnerRootKey =
+   KM_DERIVE(OwnerIntermediateKey, SoftwareBindingValue)
+
+Key0 = KM_DERIVE(OwnerRootKey, KeyVersion)
+Key1 = KM_DERIVE(Key0, KeyID)
+Key2 = KM_DERIVE(Key1, Salt)
+VersionedKey = KM_DERIVE(Key2, SoftwareExportConstant)
+
+CLEAR_AFTER_USE(VersionedKey)
+```
+
+If the implementation allows it, the generation of the version key can be
+collapsed as follows:
+
+```
+OwnerRootKey =
+   KM_DERIVE(OwnerIntermediateKey, SoftwareBindingValue)
+
+VersionedKey = KM_DERIVE(OwnerRootKey,
+    KeyVersion | KeyID | Salt | SoftwareExportConstant)
+```
+
+The concatenation function must be injective. This can be achieved by fixing the
+width of all the operands.
+
+<table>
+  <tr>
+    <td><strong>Name</strong></td>
+    <td><strong>Encoding</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>OwnerRootKey</td>
+    <td>Internal RAM</td>
+    <td>
+Owner Root Key bound to the software stack.
+
+Visibility: Hidden from software.
+    </td>
+  </tr>
+  <tr>
+    <td>SoftwareBindingValue</td>
+    <td>SW Register<br>Lockable Input</td>
+    <td>
+Software binding value configured during secure boot. See
+<a href="#software-binding">Software Binding</a> for more details.
+    </td>
+  </tr>
+  <tr>
+    <td>KeyVersion</td>
+    <td>SW Register Input</td>
+    <td>
+Key version. The value provided by software may be mixed with a gate constant
+before key derivation steps.
+
+The value should also pass the version comparison criteria configured during
+secure boot. See
+<a href="#key-versioning">Key Versioning</a> for more details.
+    </td>
+  </tr>
+  <tr>
+    <td>KeyID</td>
+    <td>SW Register Input</td>
+    <td>
+Key identifier. Used to derive a VersionedKey from VersionedRootKey. Processing
+of this field should provide countermeasures against key recovery attacks.
+    </td>
+  </tr>
+  <tr>
+    <td>SoftwareExportConstant</td>
+    <td>Gates</td>
+    <td>
+The SoftwareExportConstant is a diversification constant with acceptable entropy
+provisioned in gates. It is used to mitigate key recovery attacks on software
+inputs.
+
+Visibility: Hidden from software.
+    </td>
+  </tr>
+  <tr>
+    <td>Salt</td>
+    <td>SW Register Input</td>
+    <td>Salt input controlled by software.</td>
+  </tr>
+  <tr>
+    <td>VersionedKey</td>
+    <td>SW Register Output</td>
+    <td>
+Output key derived from VersionedRootKey and KeyID. Support for sideloading may
+require additional support in the key manager, otherwise the software will be in
+charge of enforcing isolation.
+    </td>
+  </tr>
+</table>
+
+### Software Binding {#software-binding}
+
+Software binding is used to ensure that the key derivation scheme is only
+reproducible for a trusted software configuration. This is achieved by having
+the secure boot implementation configure runtime-irrevocable binding tags in the
+key derivation scheme. Such tags are usually delivered inside the signed
+manifest of each code partition.
+
+OpenTitan shall support software binding for at least two Silicon Owner code
+stages (_e.g._ bootloader and kernel). It is expected that the kernel will
+implement binding with the application layer exclusively in software.
+
+Each key manager binding stage shall provide at least 128b of data.
+
+### Key Versioning {#key-versioning}
+
+Key versioning is the mechanism by which software implements key rotation
+triggered by security updates. Since there may be more than one updateable code
+partition in the system, the key versioning scheme has to implement at least 8
+32b version comparators with lockable controls, which will be configured as part
+of the secure boot process.
+
+#### Configuration
+
+The next figure shows an example on how to configure the maximum key version
+constraints in the key manager. The ROM\_EXT software verifies the BL0 manifest,
+and configures one of the maximum key version registers with the maximum
+allowable version stored in the BL0 manifest. In the same way, the BL0 software
+verifies the Kernel manifest and configures a separate key version register. The
+software implementation is free to allocate more than one maximum key version
+register per boot stage.
+
+<table>
+  <tr>
+    <td>
+      <img src="identities_and_root_keys_fig2.svg" alt="Fig2" style="width: 800px;"/>
+    </td>
+  </tr>
+  <tr>
+    <td style="text-align:center">
+Figure: Maximum allowable versions configured as part of the secure boot
+
+Note: The diagram is overly simplified and does not take into account security
+hardening.
+    </td>
+  </tr>
+</table>
+
+#### Key Consumption
+
+Secrets wrapped with versioned keys shall have additional metadata including Key
+Version, Key ID and salt information.
+
+The versioned key generation is gated on the version comparison check enforced
+by the key manager implementation. The following set of operations will only
+succeed if the key version set by software is valid.
+
+<table>
+  <tr>
+    <td>
+      <img src="identities_and_root_keys_fig3.svg" alt="Fig3" style="width: 800px;"/>
+    </td>
+  </tr>
+  <tr>
+    <td>
+Figure: Key version is set during key configuration
+
+Note: The diagram is overly simplified and does not take into account security
+hardening.
+    </td>
+  </tr>
+</table>
+
+## Recommendations for Programming Model Abstraction
+
+### High Level Key Manager States {#high-level-key-manager-states}
+
+The hardware may opt to implement a software interface with higher level one-way
+step functions to advance the internal state of the key manager. The following
+are the minimum set of steps required:
+
+1   [CreatorRootKey](#creator-root-key)
+2   [OwnerIntermediateKey](#owner-intermediate-key)
+3   [OwnerRootKey](#owner-root-key)
+
+<table>
+  <tr>
+    <td>
+      <img src="identities_and_root_keys_fig4.svg" alt="Fig4" style="width: 800px;"/>
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: Minimum set of high level one-way step functions.</td>
+  </tr>
+</table>
+
+Instantiations of the key manager can be conditioned to start at the current
+internal state of the key manager, for example, kernel-level instantiations may
+always start at the `OwnerRootKey` level, assuming the previous boot stages
+advanced the state of the key manager.
+
+The following code block presents a simplified version of an API implemented on
+hardware with support for high level step functions.
+
+```
+typedef enum kmgr_state {
+  kKMgrUninitialized = 0,
+  kKMgrCreatorRootKey,
+  kKMgrOwnerIntermediateKey,
+  kKMgrOwnerRootKey,
+  kKMgrDisabled,
+} kmgr_state_t;
+
+/**
+ * Initialise an instance of the Key Manager
+ *
+ * @param base_addr Base address of an instance of the Key Manager
+ * @param kmgr Key Manager state data.
+ * @return true if the function was successful, false otherwise
+ */
+bool keymgr_init(mmio_region_t base_addr, kmgr_t* kmgr);
+
+/**
+ * Advance Key Manager state
+ *
+ * Advances internal state of Key Manager. All state transitions
+ * persist until the next system reset.
+ *
+ * The hardware supports the following transitions:
+ * Uninitialized --> CreatorRootKey -->
+ * OwnerIntermediateKey --> OwnerRootKey
+ *
+ * Defensive measures may trigger a state transition to Disabled.
+ *
+ * @param kmgr Key Manager state data.
+ * @return true if the function was successful, false otherwise.
+ */
+bool keymgr_advance_state(const kmgr_t* kmgr);
+
+/**
+ * @return Current Key Manager state associated with |kmgr|
+ * instance
+ */
+kmgr_state_t keymgr_get_state(const kmgr_t* kmgr);
+```
+
+### Versioned Keys
+
+The following high level software interface may be supported by hardware to
+generate versioned keys. The hardware may opt to implement versioned key
+functionality at each of the
+[high level key manager states](#high-level-key-manager-states).
+
+
+```
+/**
+ * Generate versioned key for a given |key_id|.
+ *
+ * Generates a versioned key rooted in the current state of the
+ * key manager. Requires the key manager to be in one of the
+ * following states:
+ *    CreatorRootKey, OwnerIntermediateKey, OwnerRootKey
+ *
+ * @param kmgr Key Manager state data.
+ * @param key_version Key version. Each version 32b word shall
+ *  be less than its associated max version value. Requires the
+ *  maximum version registers to be configured before calling this
+ *  function
+ * @param key_id Key identifier.
+ * @param versioned_key Key output.
+ * @return true if the function was successful, false otherwise.
+ */
+bool keymgr_generate_vk(const kmgr_t *kmgr,
+                        const uint32_t key_version[8],
+                        const uint32_t key_id[8]
+                        uint32_t *versioned_key[8]);
+```
+
+## Alternatives Considered
+
+### Collapse Creator and Owner Identities
+
+The Silicon Creator and Silicon Owner identities may be collapsed, leaving the
+Silicon Creator identity as the sole identity supported by the platform. This
+would require the Ownership Transfer flow to support a Certificate Signing
+Request (CSR) command to be able to endorse the identity by the owner PKI.
+
+The current approach enforces a separate
+[OwnerRootSecret](#owner-root-secret) provisioned at Ownership Transfer
+time to provide isolation between device owners.
+
+### Support Identities Outside of the Key Manager
+
+The identities can be generated outside the key manager and be completely
+managed by software. The key manager in this case can be used to generate
+storage wrapping keys for the identity seeds.
+
+<img src="identities_and_root_keys_fig5.svg" alt="Fig1" style="width: 800px;"/>
+
+The current design includes support for identity states which forces the mixing
+of class level constants (i.e. `IdentityDiversificationConstant`,
+`OwnerRootIdentityKey`) for each identity seed. This ensures lineage to the
+RootKey and the Device Identifier. Additional provisioning requirements would
+have to be considered if the Identity Seeds are not derived from the Root Key.
+
+### Alternatives Considered for Software Binding
+
+The hardware may be required to support more than 256b of software binding data.
+Additional bits may be added in 256b increments to support more complex software
+binding schemes.
+
+## Requirements
+
+_KM\_DERIVE function and security strength claims_
+
+Key Manager derive functions should support at least 256b of security strength.
+
+Note on standards: The key derivation function (`KM_DERIVE`), when instantiated
+with a Pseudorandom Function (PRF), shall be compatible with
+[NIST SP 800-133r2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r2.pdf)[^2]
+_section 6.3: Symmetric Keys Produced by Combining (Multiple) Keys and Other
+Data_. This imposes provisioning requirements for the root keys, which are
+covered in the Provisioning specification.
+
+Security strength for the `KM_DERIVE` function based on a Pseudorandom Function
+(PRF)[^3]:
+
+<table>
+  <tr>
+    <td><strong>PRF</strong></td>
+    <td><strong>Security Strength</strong></td>
+    <td><strong>Notes</strong></td>
+  </tr>
+  <tr>
+    <td>CMAC-AES-256</td>
+    <td>128</td>
+    <td>
+Security strength limited to AES block size.
+
+SCA countermeasures for AES are more widely available in literature.
+    </td>
+  </tr>
+  <tr>
+    <td>HMAC-SHA2-256</td>
+    <td>256</td>
+    <td>
+There are no plans for hardening the OpenTitan HMAC hardware implementation due
+to complexity.
+
+No planned support for HMAC-SHA3.
+    </td>
+  </tr>
+  <tr>
+    <td>KMAC256</td>
+    <td>256</td>
+    <td>
+Security hardening is under consideration for the OpenTitan KMAC hardware
+implementation.
+
+Need to verify with a lab that the claim for 800-133r2 section 6.3 compliance
+holds.
+
+Certification using a KMAC construction is challenged by the following issues.
+
+Common Criteria:
+
+1.   Even though SHA-3 is an approved SOG-IS algorithm, KMAC is not[^4].
+
+FIPS - NIST specs:
+
+1.   NIST 800-56Cr1 lists KMAC as an approved one-step KDF, although the spec is
+     focused on key establishment applications; but,
+2.   NIST 800-108, which focuses on PRF key derivation functions, does not list
+     KMAC as a primitive. Note however that the document is pretty old.
+    </td>
+  </tr>
+</table>
+
+Security strength for the KM\_DERIVE function based on a Deterministic RNG
+(DRNG):
+
+<table>
+  <tr>
+    <td><strong>PRF</strong></td>
+    <td><strong>Security Strength</strong></td>
+    <td><strong>Notes</strong></td>
+  </tr>
+  <tr>
+    <td>CTR-DRBG</td>
+    <td>256</td>
+    <td>
+Additional requirements:
+
+1.   Factory time provisioned entropy (seed).
+2.   Hash function required to compress additional data into DRBG
+     (additional_data or perso_string).
+3.   Global counter to keep track of key derivations (requires device lifetime
+     key manager keygen counter, which can be implemented in software? This can
+     be very difficult since the key derivation paths change per ownership
+     transfer).
+
+Compliant to NIST 800-133r2 section 4 (Need to verify claim with a lab).
+    </td>
+  </tr>
+</table>
+
+_Key recovery attacks on user inputs_
+
+The hardware shall support countermeasures against key recovery attacks for all
+software controlled inputs.
+
+_Version comparison registers_
+
+The hardware shall support at least 8 32b write-lockable version comparison
+registers to provide key versioning functionality.
+
+_Software-Hardware binding registers_
+
+The hardware shall support at least 256b of software write-lockable registers to
+implement software-hardware key manager binding as part of the secure boot
+implementation.
+
+_Support for key IDs (key handles or salt value)_
+
+The hardware shall support [versioned key](#versioned-key) derivations for
+software provided key IDs. A key ID is defined as a 256b value used as a key
+handle.
+
+_Root secrets isolation from software_
+
+The hardware shall isolate the `RootKey` and other provisioned secrets from
+software after completion of personalization at manufacturing time.
+
+_Isolation between boot stages_
+
+Later boot stages shall have no access to secrets maintained by previous boot
+stages.
+
+_Lockable inputs_
+
+The software shall configure runtime lockable inputs as part of the secure boot
+implementation to fix the construction of identities and root keys in the key
+manager.
+
+_Integrity and confidentiality of secret values_
+
+Hardware secrets stored in OTP and flash shall be scrambled to increase the
+difficulty of physical attacks.
+
+_Silicon Creator identity invalidation (optional)_
+
+ROM Extension updates may invalidate the Silicon Owner and Silicon Creator
+identities as well as the root keys.
+
+_Fallback support_
+
+The implementation should consider a software based backup mechanism to mitigate
+security and/or certification issues with the main implementation. The backup
+mechanism shall not rely on secrets from the main implementation.
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+[^1]: The Silicon Creator is the logical entity, or logical collection of
+    entities that manufactures, packages, tests and provisions the chip with
+    its first identity.
+
+[^2]: [Recommendation for Cryptographic Key Generation](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r2.pdf)
+    (NIST SP 800-133r2).
+
+[^3]: Security strengths for PRF functions as documented in:
+    [Recommendation for Key-Derivation Methods in Key-Establishment Schemes](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr1.pdf)
+    (NIST 800-56Cr1)
+
+[^4]: <a href="https://www.sogis.eu/documents/cc/crypto/SOGIS-Agreed-Cryptographic-Mechanisms-1.1.pdf">SOG-IS
+    Crypto Working Group SOG-IS Crypto Evaluation Scheme Agreed Cryptographic Mechanisms</a>
diff --git a/doc/security/specs/identities_and_root_keys/_index.md b/doc/security/specs/identities_and_root_keys/_index.md
deleted file mode 100644
index 547ba07..0000000
--- a/doc/security/specs/identities_and_root_keys/_index.md
+++ /dev/null
@@ -1,820 +0,0 @@
----
-title: "Identities and Root Keys"
----
-
-## Overview
-
-This document describes the composition of the Silicon Creator and Silicon Owner
-cryptographic identities and the Silicon Owner root key derivation scheme. This
-scheme is based on a symmetric key manager with support for software binding and
-key versioning.
-
-This document also defines a non-cryptographic **Device Identifier** to
-facilitate silicon tracking during manufacturing flows. The Device Identifier is
-also mixed into the Creator Identity.
-
-Further, this scheme is compatible with the [Open DICE profile](https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/specification.md).
-
-Overall identity flow:
-<img src="identities_and_root_keys_fig1.svg" alt="Fig1" style="width: 1000px;"/>
-
-DICE compatible identity flow:
-<img src="identities_and_root_keys_DICE_fig1b.svg" alt="Fig1b" style="width: 1000px;"/>
-
-## Terminology
-
-Boot stages:
-
-*   `ROM`: Metal mask ROM, sometimes known as Boot ROM.
-*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon
-    Creator[^1].
-*   `BL0`: Bootloader. Signed by the Silicon Owner.
-*   `Kernel`: Signed by the Silicon Owner.
-
-Key manager operations:
-
-*   `KM_DERIVE`: Key manager one-way function used to derive a new symmetric
-    key.
-
-Memory state operations:
-
-*   `CLEAR_BEFORE_NEXT_BOOT_STAGE`: Clear key material before moving to the next
-    boot stage.
-*   `CLEAR_AFTER_USE`: Clear immediately after use.
-
-## Device Identifier
-
-The device identifier is a globally unique 256b value provisioned on each
-device's OTP memory in early manufacturing stages (e.g. wafer test). It is used
-to facilitate device tracking during manufacturing and provisioning. This value
-is also used as a component in the generation of the device's Silicon Creator
-Identity, a cryptographically unique identity.
-
-The 256b value is split into two halves. The first contains hardware origin
-information following a global standard format, while the second one is defined
-by the device SKU provisioning requirements.
-
-**128b Hardware origin information**
-
-<table>
-  <tr>
-    <td><strong>No. Bits</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>16</td>
-    <td>Silicon Creator identifier. Assigned by the OpenTitan project.</td>
-  </tr>
-  <tr>
-    <td>16</td>
-    <td>
-Product identifier. Assigned by the Silicon Creator. Used to identify a class of
-devices.
-    </td>
-  </tr>
-  <tr>
-    <td>64</td>
-    <td>
-Individual device identification number. Assigned by the Silicon Creator. For
-example, the wafer lot number and die's X,Y coordinates may be encoded here to
-simplify manufacturing tracking. Another option is to use a non cryptographic
-hash function with collision checks to guarantee global uniqueness.
-    </td>
-  </tr>
-  <tr>
-    <td>32</td>
-    <td>CRC-32 IEEE 802.3. covering the previous bytes.</td>
-  </tr>
-</table>
-
-**128b SKU specific device information**
-
-The device provisioner information varies for each provisioning use case. Each
-use case must have a specification defining the allocation of these bits. See
-the
-[UICC EID Specification](https://www.gsma.com/newsroom/wp-content/uploads/SGP.02-v4.0.pdf)
-as an example.
-
-## Creator Root Key (`CreatorRootKey`) {#creator-root-key}
-
-The following sequence describes the creation of the `CreatorRootKey`. All
-inputs into the key manager can be locked down during mask ROM execution.
-
-The size of the inputs is dependent on the security strength and masking
-configuration of the implementation. Depending on the KM\_DERIVE intrinsic
-function, one of the following two mixing operations is acceptable:
-
-Cascading:
-
-```
-Key0 = KM_DERIVE(RootKey, DiversificationKey)
-Key1 = KM_DERIVE(Key0, HealthStateMeasurement)
-Key2 = KM_DERIVE(Key1, DeviceIdentifier)
-Key3 = KM_DERIVE(Key2, ROMExtSecurityDescriptor)
-
-CreatorRootKey = KM_DERIVE(Key3, HardwareRevisionSecret)
-```
-
-Collapsed:
-
-The concatenation function must be injective. This can be achieved by fixing the
-width of all the operands.
-
-```
-CreatorRootKey = KM_DERIVE(RootKey,
-    DiversificationKey | HealthStateMeasurement | DeviceIdentifier |
-    ROMExtSecurityDescriptor | HardwareRevisionSecret)
-```
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td id="root-key">RootKey</td>
-    <td>OTP</td>
-    <td>
-Device root key. Provisioned at manufacturing time by the Silicon Creator.
-
-Hidden from software once personalization is complete.
-    </td>
-  </tr>
-  <tr>
-    <td id="diversification-key">DiversificationKey</td>
-    <td>Flash</td>
-    <td>
-Additional diversification key stored in flash. Provisioned at
-manufacturing time by the Silicon Creator.
-
-Hidden from software once provisioned.
-    </td>
-  </tr>
-  <tr>
-    <td>ROMExtSecurityDescriptor</td>
-    <td>SW register</td>
-    <td>
-The implementation may choose one of the following options:
-
-1. Hash of the ROM extension. Changes in the ROM extension code will trigger an
-   update of the Creator Identity.
-2. Use a software binding tag stored in the ROM_EXT manifest. This is to
-   retain the Creator Identity across validated updates of the ROM_EXT.
-   The implementation may opt to use the software binding interface
-   described in later sections to fulfill this property.
-    </td>
-  </tr>
-  <tr>
-    <td>DeviceIdentifier</td>
-    <td>OTP</td>
-    <td>
-Provisioned at manufacturing time. Readable from software and JTAG interface.
-    </td>
-  </tr>
-  <tr>
-    <td>HardwareRevisionSecret</td>
-    <td>Gates</td>
-    <td>
-Encoded in gates. Provisioned by Silicon Creator before tapeout. Hidden from
-software.
-    </td>
-  </tr>
-  <tr>
-    <td>Health State Measurement</td>
-    <td>Register (ROM stage)</td>
-    <td>
-Comprises the following measurements:
-
-* Device life cycle state.
-* Debug mode state.
-* ROM Hash.
-
-Some values are read from the device life cycle controller. The device life
-cycle state should be consumed by the ROM stage.
-
-The debug mode shall be used as well if there are multiple debug configurations
-supported by a single life cycle state.
-    </td>
-  </tr>
-</table>
-
-The CreatorRootKey can be used to generate the Creator Identity key and the
-OwnerIntermediateKey described in the following sections.
-
-## Creator Identity
-
-The Creator Identity is an asymmetric key derived from the `CreatorRootKey`. It
-is used as a cryptographic identifier bound to the device and the Silicon
-Creator. It is used to attest to the authenticity of the physical device and the
-ROM and ROM\_EXT configuration.
-
-The Creator Identity is generated as follows:
-
-```
-CreatorIdentitySeed =
-  KM_DERIVE(CreatorRootKey, IdentityDiversificationConstant)
-
-// ASYM_KDF is a KDF function compliant to the Asymmetric Key
-// requirements defined in the Attestation specification document.
-CreatorIdentity_Private = ASYM_KDF(CreatorIdentitySeed)
-CLEAR_BEFORE_NEXT_BOOT_STAGE(CreatorIdentitySeed, CreatorIdentity_Private)
-```
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong>
-    </td>
-  </tr>
-  <tr>
-    <td>IdentityDiversificationConstant</td>
-    <td>Gates</td>
-    <td>
-A constant defined in gates. Used to derive the CreatorIdentitySeed from the
-CreatorRootKey.
-
-Hidden from software.
-    </td>
-  </tr>
-  <tr>
-    <td>CreatorIdentitySeed</td>
-    <td>SW Register Output</td>
-    <td>Seed used to generate the CreatorIdentity asymmetric key.</td>
-  </tr>
-</table>
-
-The `CreatorIdentitySeed` and the private portion of the Creator Identity shall
-be cleared before the ROM Extension hands over execution to the Silicon Owner
-first boot stage.
-
-## OwnerIntermediateKey {#owner-intermediate-key}
-
-The `OwnerIntermediateKey` is used as a root component of the Silicon Owner key
-hierarchy. It is used to establish a cryptographic link to the root secrets
-provisioned at manufacturing time.
-
-**Visibility**
-
-The `OwnerIntermediateKey` shall be kept hidden from software to mitigate owner
-impersonation attacks.
-
-The `OwnerIntermediateKey` is generated as follows:
-
-```
-OwnerIntermediateKey =
-   KM_DERIVE(CreatorRootKey, OwnerRootSecret | SoftwareBindingValue)
-```
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td id="owner-root-secret">OwnerRootSecret</td>
-    <td>Flash</td>
-    <td>
-Used as a diversification constant with acceptable entropy. Provisioned at
-Ownership Transfer time by the Silicon Creator.
-
-The OwnerRootSecret has different visibility options depending on the level of
-isolation provided in hardware:
-
-*   The value should be hidden from software after provisioning.
-*   The value is visible to ROM and ROM Extension, but hidden from all Silicon
-    Owner software. The ROM Extension implements this property.
-    </td>
-  </tr>
-  <tr>
-    <td>SoftwareBindingValue</td>
-    <td>SW Register<br>Lockable Input</td>
-    <td>
-Software binding value configured during secure boot. See
-<a href="#software-binding">Software Binding</a> for more details.
-    </td>
-  </tr>
-</table>
-
-## Owner Identity
-
-The Owner Identity is used as a cryptographic identifier bound to the device and
-the Silicon Owner. It is used in Attestation flows. The Owner Identity is not
-expected to change during the lifetime of the device ownership assignment.
-
-```
-OwnerIdentitySeed =
-  KM_DERIVE(OwnerIntermediateKey, OwnerRootIdentityKey)
-
-// ASYM_KDF is a KDF function compliant to the Asymmetric Key
-// requirements defined in the Attestation specification document.
-OwnerIdentity_Private = ASYM_KDF(OwnerIdentitySeed)
-CLEAR_BEFORE_NEXT_BOOT_STAGE(OwnerRootSeed, OwnerIdentity_Private)
-```
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>Owner Root Identity Key</td>
-    <td>Gates</td>
-    <td>
-The OwnerRootIdentityKey is a diversification constant with acceptable entropy
-provisioned in gates.
-
-Visibility: Hidden from software.
-    </td>
-  </tr>
-  <tr>
-    <td>Owner Identity Seed</td>
-    <td>SW Register Output</td>
-    <td>Seed used to generate the OwnerIdentity asymmetric key.</td>
-  </tr>
-</table>
-
-The `OwnerIdentitySeed` and the private portion of the Owner Identity shall be
-cleared before the bootloader (BL0) hands over execution to the kernel.
-
-## Owner Root Key and Versioned Keys {#owner-root-key}
-
-The key manager supports the generation of versioned keys with lineage to the
-`OwnerRootKey` for software consumption and sideload operations.
-
-```
-OwnerRootKey =
-   KM_DERIVE(OwnerIntermediateKey, SoftwareBindingValue)
-
-Key0 = KM_DERIVE(OwnerRootKey, KeyVersion)
-Key1 = KM_DERIVE(Key0, KeyID)
-Key2 = KM_DERIVE(Key1, Salt)
-VersionedKey = KM_DERIVE(Key2, SoftwareExportConstant)
-
-CLEAR_AFTER_USE(VersionedKey)
-```
-
-If the implementation allows it, the generation of the version key can be
-collapsed as follows:
-
-```
-OwnerRootKey =
-   KM_DERIVE(OwnerIntermediateKey, SoftwareBindingValue)
-
-VersionedKey = KM_DERIVE(OwnerRootKey,
-    KeyVersion | KeyID | Salt | SoftwareExportConstant)
-```
-
-The concatenation function must be injective. This can be achieved by fixing the
-width of all the operands.
-
-<table>
-  <tr>
-    <td><strong>Name</strong></td>
-    <td><strong>Encoding</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>OwnerRootKey</td>
-    <td>Internal RAM</td>
-    <td>
-Owner Root Key bound to the software stack.
-
-Visibility: Hidden from software.
-    </td>
-  </tr>
-  <tr>
-    <td>SoftwareBindingValue</td>
-    <td>SW Register<br>Lockable Input</td>
-    <td>
-Software binding value configured during secure boot. See
-<a href="#software-binding">Software Binding</a> for more details.
-    </td>
-  </tr>
-  <tr>
-    <td>KeyVersion</td>
-    <td>SW Register Input</td>
-    <td>
-Key version. The value provided by software may be mixed with a gate constant
-before key derivation steps.
-
-The value should also pass the version comparison criteria configured during
-secure boot. See
-<a href="#key-versioning">Key Versioning</a> for more details.
-    </td>
-  </tr>
-  <tr>
-    <td>KeyID</td>
-    <td>SW Register Input</td>
-    <td>
-Key identifier. Used to derive a VersionedKey from VersionedRootKey. Processing
-of this field should provide countermeasures against key recovery attacks.
-    </td>
-  </tr>
-  <tr>
-    <td>SoftwareExportConstant</td>
-    <td>Gates</td>
-    <td>
-The SoftwareExportConstant is a diversification constant with acceptable entropy
-provisioned in gates. It is used to mitigate key recovery attacks on software
-inputs.
-
-Visibility: Hidden from software.
-    </td>
-  </tr>
-  <tr>
-    <td>Salt</td>
-    <td>SW Register Input</td>
-    <td>Salt input controlled by software.</td>
-  </tr>
-  <tr>
-    <td>VersionedKey</td>
-    <td>SW Register Output</td>
-    <td>
-Output key derived from VersionedRootKey and KeyID. Support for sideloading may
-require additional support in the key manager, otherwise the software will be in
-charge of enforcing isolation.
-    </td>
-  </tr>
-</table>
-
-### Software Binding {#software-binding}
-
-Software binding is used to ensure that the key derivation scheme is only
-reproducible for a trusted software configuration. This is achieved by having
-the secure boot implementation configure runtime-irrevocable binding tags in the
-key derivation scheme. Such tags are usually delivered inside the signed
-manifest of each code partition.
-
-OpenTitan shall support software binding for at least two Silicon Owner code
-stages (_e.g._ bootloader and kernel). It is expected that the kernel will
-implement binding with the application layer exclusively in software.
-
-Each key manager binding stage shall provide at least 128b of data.
-
-### Key Versioning {#key-versioning}
-
-Key versioning is the mechanism by which software implements key rotation
-triggered by security updates. Since there may be more than one updatable code
-partition in the system, the key versioning scheme has to implement at least 8
-32b version comparators with lockable controls, which will be configured as part
-of the secure boot process.
-
-#### Configuration
-
-The next figure shows an example on how to configure the maximum key version
-constraints in the key manager. The ROM\_EXT software verifies the BL0 manifest,
-and configures one of the maximum key version registers with the maximum
-allowable version stored in the BL0 manifest. In the same way, the BL0 software
-verifies the Kernel manifest and configures a separate key version register. The
-software implementation is free to allocate more than one maximum key version
-register per boot stage.
-
-<table>
-  <tr>
-    <td>
-      <img src="identities_and_root_keys_fig2.svg" alt="Fig2" style="width: 800px;"/>
-    </td>
-  </tr>
-  <tr>
-    <td style="text-align:center">
-Figure: Maximum allowable versions configured as part of the secure boot
-
-Note: The diagram is overly simplified and does not take into account security
-hardening.
-    </td>
-  </tr>
-</table>
-
-#### Key Consumption
-
-Secrets wrapped with versioned keys shall have additional metadata including Key
-Version, Key ID and salt information.
-
-The versioned key generation is gated on the version comparison check enforced
-by the key manager implementation. The following set of operations will only
-succeed if the key version set by software is valid.
-
-<table>
-  <tr>
-    <td>
-      <img src="identities_and_root_keys_fig3.svg" alt="Fig3" style="width: 800px;"/>
-    </td>
-  </tr>
-  <tr>
-    <td>
-Figure: Key version is set during key configuration
-
-Note: The diagram is overly simplified and does not take into account security
-hardening.
-    </td>
-  </tr>
-</table>
-
-## Recommendations for Programming Model Abstraction
-
-### High Level Key Manager States {#high-level-key-manager-states}
-
-The hardware may opt to implement a software interface with higher level one-way
-step functions to advance the internal state of the key manager. The following
-are the minimum set of steps required:
-
-1   [CreatorRootKey](#creator-root-key)
-2   [OwnerIntermediateKey](#owner-intermediate-key)
-3   [OwnerRootKey](#owner-root-key)
-
-<table>
-  <tr>
-    <td>
-      <img src="identities_and_root_keys_fig4.svg" alt="Fig4" style="width: 800px;"/>
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: Minimum set of high level one-way step functions.</td>
-  </tr>
-</table>
-
-Instantiations of the key manager can be conditioned to start at the current
-internal state of the key manager, for example, kernel-level instantiations may
-always start at the `OwnerRootKey` level, assuming the previous boot stages
-advanced the state of the key manager.
-
-The following code block presents a simplified version of an API implemented on
-hardware with support for high level step functions.
-
-```
-typedef enum kmgr_state {
-  kKMgrUninitialized = 0,
-  kKMgrCreatorRootKey,
-  kKMgrOwnerIntermediateKey,
-  kKMgrOwnerRootKey,
-  kKMgrDisabled,
-} kmgr_state_t;
-
-/**
- * Initialise an instance of the Key Manager
- *
- * @param base_addr Base address of an instance of the Key Manager
- * @param kmgr Key Manager state data.
- * @return true if the function was successful, false otherwise
- */
-bool keymgr_init(mmio_region_t base_addr, kmgr_t* kmgr);
-
-/**
- * Advance Key Manager state
- *
- * Advances internal state of Key Manager. All state transitions
- * persist until the next system reset.
- *
- * The hardware supports the following transitions:
- * Uninitialized --> CreatorRootKey -->
- * OwnerIntermediateKey --> OwnerRootKey
- *
- * Defensive measures may trigger a state transition to Disabled.
- *
- * @param kmgr Key Manager state data.
- * @return true if the function was successful, false otherwise.
- */
-bool keymgr_advance_state(const kmgr_t* kmgr);
-
-/**
- * @return Current Key Manager state associated with |kmgr|
- * instance
- */
-kmgr_state_t keymgr_get_state(const kmgr_t* kmgr);
-```
-
-### Versioned Keys
-
-The following high level software interface may be supported by hardware to
-generate versioned keys. The hardware may opt to implement versioned key
-functionality at each of the
-[high level key manager states](#high-level-key-manager-states).
-
-
-```
-/**
- * Generate versioned key for a given |key_id|.
- *
- * Generates a versioned key rooted in the current state of the
- * key manager. Requires the key manager to be in one of the
- * following states:
- *    CreatorRootKey, OwnerIntermediateKey, OwnerRootKey
- *
- * @param kmgr Key Manager state data.
- * @param key_version Key version. Each version 32b word shall
- *  be less than its associated max version value. Requires the
- *  maximum version registers to be configured before calling this
- *  function
- * @param key_id Key identifier.
- * @param versioned_key Key output.
- * @return true if the function was successful, false otherwise.
- */
-bool keymgr_generate_vk(const kmgr_t *kmgr,
-                        const uint32_t key_version[8],
-                        const uint32_t key_id[8]
-                        uint32_t *versioned_key[8]);
-```
-
-## Alternatives Considered
-
-### Collapse Creator and Owner Identities
-
-The Silicon Creator and Silicon Owner identities may be collapsed, leaving the
-Silicon Creator identity as the sole identity supported by the platform. This
-would require the Ownership Transfer flow to support a Certificate Signing
-Request (CSR) command to be able to endorse the identity by the owner PKI.
-
-The current approach enforces a separate
-[OwnerRootSecret](#owner-root-secret) provisioned at Ownership Transfer
-time to provide isolation between device owners.
-
-### Support Identities Outside of the Key Manager
-
-The identities can be generated outside the key manager and be completely
-managed by software. The key manager in this case can be used to generate
-storage wrapping keys for the identity seeds.
-
-<img src="identities_and_root_keys_fig5.svg" alt="Fig1" style="width: 800px;"/>
-
-The current design includes support for identity states which forces the mixing
-of class level constants (i.e. `IdentityDiversificationConstant`,
-`OwnerRootIdentityKey`) for each identity seed. This ensures lineage to the
-RootKey and the Device Identifier. Additional provisioning requirements would
-have to be considered if the Identity Seeds are not derived from the Root Key.
-
-### Alternatives Considered for Software Binding
-
-The hardware may be required to support more than 256b of software binding data.
-Additional bits may be added in 256b increments to support more complex software
-binding schemes.
-
-## Requirements
-
-_KM\_DERIVE function and security strength claims_
-
-Key Manager derive functions should support at least 256b of security strength.
-
-Note on standards: The key derivation function (`KM_DERIVE`), when instantiated
-with a Pseudorandom Function (PRF), shall be compatible with
-[NIST SP 800-133r2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r2.pdf)[^2]
-_section 6.3: Symmetric Keys Produced by Combining (Multiple) Keys and Other
-Data_. This imposes provisioning requirements for the root keys, which are
-covered in the Provisioning specification.
-
-Security strength for the `KM_DERIVE` function based on a Pseudorandom Function
-(PRF)[^3]:
-
-<table>
-  <tr>
-    <td><strong>PRF</strong></td>
-    <td><strong>Security Strength</strong></td>
-    <td><strong>Notes</strong></td>
-  </tr>
-  <tr>
-    <td>CMAC-AES-256</td>
-    <td>128</td>
-    <td>
-Security strength limited to AES block size.
-
-SCA countermeasures for AES are more widely available in literature.
-    </td>
-  </tr>
-  <tr>
-    <td>HMAC-SHA2-256</td>
-    <td>256</td>
-    <td>
-There are no plans for hardening the OpenTitan HMAC hardware implementation due
-to complexity.
-
-No planned support for HMAC-SHA3.
-    </td>
-  </tr>
-  <tr>
-    <td>KMAC256</td>
-    <td>256</td>
-    <td>
-Security hardening is under consideration for the OpenTitan KMAC hardware
-implementation.
-
-Need to verify with a lab that the claim for 800-133r2 section 6.3 compliance
-holds.
-
-Certification using a KMAC construction is challenged by the following issues.
-
-Common Criteria:
-
-1.   Even though SHA-3 is an approved SOG-IS algorithm, KMAC is not[^4].
-
-FIPS - NIST specs:
-
-1.   NIST 800-56Cr1 lists KMAC as an approved one-step KDF, although the spec is
-     focused on key establishment applications; but,
-2.   NIST 800-108, which focuses on PRF key derivation functions, does not list
-     KMAC as a primitive. Note however that the document is pretty old.
-    </td>
-  </tr>
-</table>
-
-Security strength for the KM\_DERIVE function based on a Deterministic RNG
-(DRNG):
-
-<table>
-  <tr>
-    <td><strong>PRF</strong></td>
-    <td><strong>Security Strength</strong></td>
-    <td><strong>Notes</strong></td>
-  </tr>
-  <tr>
-    <td>CTR-DRBG</td>
-    <td>256</td>
-    <td>
-Additional requirements:
-
-1.   Factory time provisioned entropy (seed).
-2.   Hash function required to compress additional data into DRBG
-     (additional_data or perso_string).
-3.   Global counter to keep track of key derivations (requires device lifetime
-     key manager keygen counter, which can be implemented in software? This can
-     be very difficult since the key derivation paths change per ownership
-     transfer).
-
-Compliant to NIST 800-133r2 section 4 (Need to verify claim with a lab).
-    </td>
-  </tr>
-</table>
-
-_Key recovery attacks on user inputs_
-
-The hardware shall support countermeasures against key recovery attacks for all
-software controlled inputs.
-
-_Version comparison registers_
-
-The hardware shall support at least 8 32b write-lockable version comparison
-registers to provide key versioning functionality.
-
-_Software-Hardware binding registers_
-
-The hardware shall support at least 256b of software write-lockable registers to
-implement software-hardware key manager binding as part of the secure boot
-implementation.
-
-_Support for key IDs (key handles or salt value)_
-
-The hardware shall support [versioned key](#versioned-key) derivations for
-software provided key IDs. A key ID is defined as a 256b value used as a key
-handle.
-
-_Root secrets isolation from software_
-
-The hardware shall isolate the `RootKey` and other provisioned secrets from
-software after completion of personalization at manufacturing time.
-
-_Isolation between boot stages_
-
-Later boot stages shall have no access to secrets maintained by previous boot
-stages.
-
-_Lockable inputs_
-
-The software shall configure runtime lockable inputs as part of the secure boot
-implementation to fix the construction of identities and root keys in the key
-manager.
-
-_Integrity and confidentiality of secret values_
-
-Hardware secrets stored in OTP and flash shall be scrambled to increase the
-difficulty of physical attacks.
-
-_Silicon Creator identity invalidation (optional)_
-
-ROM Extension updates may invalidate the Silicon Owner and Silicon Creator
-identities as well as the root keys.
-
-_Fallback support_
-
-The implementation should consider a software based backup mechanism to mitigate
-security and/or certification issues with the main implementation. The backup
-mechanism shall not rely on secrets from the main implementation.
-
-<!-- Footnotes themselves at the bottom. -->
-
-## Notes
-
-[^1]: The Silicon Creator is the logical entity, or logical collection of
-    entities that manufactures, packages, tests and provisions the chip with
-    its first identity.
-
-[^2]: [Recommendation for Cryptographic Key Generation](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-133r2.pdf)
-    (NIST SP 800-133r2).
-
-[^3]: Security strengths for PRF functions as documented in:
-    [Recommendation for Key-Derivation Methods in Key-Establishment Schemes](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr1.pdf)
-    (NIST 800-56Cr1)
-
-[^4]: <a href="https://www.sogis.eu/documents/cc/crypto/SOGIS-Agreed-Cryptographic-Mechanisms-1.1.pdf">SOG-IS
-    Crypto Working Group SOG-IS Crypto Evaluation Scheme Agreed Cryptographic Mechanisms</a>
diff --git a/doc/security/specs/ownership_transfer/README.md b/doc/security/specs/ownership_transfer/README.md
new file mode 100644
index 0000000..9f33e57
--- /dev/null
+++ b/doc/security/specs/ownership_transfer/README.md
@@ -0,0 +1,602 @@
+# Ownership Transfer
+
+## Overview
+
+The Silicon Owner is defined as a logical entity or entities allowed to sign
+code for execution, as well as to sign ownership management commands[^1].
+OpenTitan supports the following device life cycle states to manage the
+ownership state of the device:
+
+*   _UNLOCKED_OWNERSHIP_: The device is ready to be assigned to a new owner.
+*   _LOCKED_OWNERSHIP_: The device is assigned to an owner and in operational mode.
+
+The following steps describe the ownership transfer process in detail.
+
+1. The current owner of the device wipes any secret information they may have on it, and boots the device with an "unlock ownership" boot service request and a signature authenticating the request.
+At this point the device switches from a `LOCKED_OWNERSHIP` to an `UNLOCKED_OWNERSHIP` state, and the current owner will now be referred to as the "previous owner".
+    *  The device attestation process will specify that the device is in an unlocked state rather than attesting to any specific owner.
+Key derivations will also correspond to the unlocked state rather than the previous owner.
+    * The previous owner's public keys are still stored after ownership is unlocked.
+1. The new owner creates a set of keys by any means they wish (doesn't have to be on an OpenTitan device).
+These keys must include an RSA-3072 key pair for secure boot, an ECDSA-P256 key pair for unlocking ownership, and another ECDSA-P256 key pair for endorsing the next owner (see [Owner Keys](#owner-keys)).
+    * The number of redundant keys stored is configurable up to around 2kiB of total key material.
+The new owner may choose, for example, to generate several RSA public/private key pairs.
+1. The new owner gives their public keys to the previous owner (which may be the manufacturer), who endorses them with a cryptographic signature.
+The Silicon Creator can also endorse new owners, although this will not happen often in practice.
+The endorser then creates an "ownership transfer payload", which includes the new owner’s public keys and their own ECDSA-P256 signature to authenticate the keys.
+1. The device should now be booted with a "transfer ownership" boot service request and with the ownership transfer payload to a special region of memory.
+During boot (specifically during the `ROM_EXT` stage), the device will check the signature on the payload against the endorser's public key.
+    * If the endorser is the previous owner, the device checks against the inactive public keys in the Silicon Owner slot.
+    * If the endorser is the Silicon Creator, the device checks against public keys which have been injected during manufacturing.
+1. If the signature is valid, then `ROM_EXT` will set up the new owner as the current owner by randomly generating a new Silicon Owner "root secret" (which is used to derive the Silicon Owner identity key for attestation) and writing the new owner's public keys to one of the two slots described in [Key Provisioning](#key-provisioning).
+    * The previous owner's public keys are still on the device and it is still in `UNLOCKED_OWNERSHIP` until the new owner's code successfully boots.
+This is a protection in case there is a problem with the new owner's keys or they have been tampered with in transit.
+1. At this point, the device should be restarted with the new owner's code.
+The device will attempt to verify the code's cryptographic signature with the new owner's stored keys.
+If successful, the device will disable the previous owner's keys and move to the `LOCKED_OWNERSHIP` state.
+It will now boot any code signed by the new owner, and generate attestation certificates associated with the new owner's identity.
+
+### Examples
+
+By design, ownership transfer has a lot of flexibility.
+To help explain the process, here are a few concrete examples of how ownership transfer could work in practice.
+This is just for illustration, and is by no means an exhaustive list.
+
+**Scenario 1:** Immediately after manufacturing the device, the Silicon Creator assigns ownership to a known first Silicon Owner who has commissioned the device and prepared an ownership transfer payload in advance.
+
+**Scenario 2:** A Silicon Owner wants to sell their device to someone else.
+The buyer sends the seller an ownership transfer payload with their public keys.
+The seller signs the payload and sends the buyer a) their signature and b) the physical device in an `UNLOCKED_OWNERSHIP` state.
+The buyer can now complete the ownership transfer procedure to become the new owner.
+
+**Scenario 3:** A Silicon Owner wants to send their device back to the Silicon Creator for resale.
+They send it back in an `UNLOCKED_OWNERSHIP` state.
+(This unlocking step is vital, although the Silicon Creator can endorse a new owner, they cannot unlock ownership for a device that is in a `LOCKED_OWNERSHIP` state.)
+The Silicon Creator can later endorse the ownership transfer payload of whoever buys the device, without further involvement from the previous owner.
+
+## Owner Keys {#owner-keys}
+
+The following keys are provisioned as part of this flow.
+
+### CODE_SIGN
+
+*   **Description**: Used to verify the Silicon Owner first bootloader stage.
+*   **Key Type**: RSA-3072 public key with exponent F4.
+
+### UNLOCK
+
+*   **Description**: Used to authenticate the Unlock Ownership payload.
+*   **Key Type**: ECC NIST-P256 Curve.
+
+### NEXT_OWNER
+
+*   **Description**: Used to authenticate the Ownership Transfer payload.
+*   **Key Type**: ECC NIST-P256 Curve.
+
+The `UNLOCK` and `NEXT_OWNER` keys are required to ensure ownership state
+transitions are only triggered by authenticated and authorized commands.
+Authorization is implemented via key identification (`UNLOCK` versus
+`NEXT_OWNER`).
+
+<!-- TODO: Add link to Identities and Root Keys doc -->
+
+Transition into `LOCKED_OWNERSHIP` stage results in a new device [Owner Identity](#), used
+in attestation and post-ownership-transfer application provisioning flows.
+
+There are three modes of ownership transfer supported:
+
+*   **Silicon Creator endorses Next Owner**: The Silicon Creator signs the set
+    of public keys associated with the next owner as part of the ownership
+    transfer flow.
+*   **Current Owner endorses Next Owner**: The Current Owner signs the set of
+    keys associated with the next owner as part of the ownership transfer flow.
+*   **Fixed Owner**: In this case a single owner is provisioned on the device
+    and ownership transfer is disabled via manufacturing SKU configuration.
+
+## Terminology
+
+Boot stages:
+
+*   `ROM`: Metal ROM, sometimes known as Boot ROM.
+*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
+*   Owner boot stages. This document uses two stages as an example. The Silicon
+    Owner is free to choose other boot configurations.
+    *   `BL0`: Bootloader. Signed by the Silicon Owner.
+    *   `KERNEL`: Signed by the Silicon Owner.
+
+## Key Provisioning {#key-provisioning}
+
+As part of the Ownership Transfer flow, the Silicon Owner keys are endorsed
+either by the Silicon Creator or by the Current Owner of the device. This is
+done to ensure that only authenticated and authorized entities are able to take
+ownership of the device.
+
+### Key Endorsement Format
+
+Key endorsement is implemented as a signed manifest. The rest of the document
+refers to this as the Key Endorsement Manifest, or manifest for simplicity. The
+following fields must be supported by the manifest implementation:
+
+#### signature
+
+Signature covering the manifest.
+
+#### signature_algorithm
+
+Signature algorithm. For this version of the specification it is always set
+to `ECDSA-sha256`.
+
+#### public_key
+
+Public key to verify the signature with. The number of bytes depends on the
+`signature_algorithm` field. Depending on the ownership transfer model, the
+public key must match one of the following requirements:
+
+ Endorsing Entity | Public Key Requirement
+ ---------------- | --------------------------------------------------------
+ Silicon Creator  | The public key must be stored in the ROM_EXT and  integrity protected by the ROM_EXT signature.
+ Previous Owner   | The public key must be stored in the previous owner slot and labeled as the `NEXT_OWNER` in the policy field. See `owner_keys` for more details.
+
+#### owner_keys (array)
+
+List of public keys endorsed by the manifest. See [Owner Keys](#owner-keys) for
+more details.
+
+#### Optional Parameters
+
+The following parameters are required in the secure boot manifest
+implementation, but are left as optional in the key endorsement manifest. The
+Silicon Creator or previous Silicon Owner may want to implement these parameters
+to restrict the deployment of the endorsed keys.
+
+Note: If implemented, the restrictions imposed by these fields cannot be revoked
+by ownership transfer once provisioned. This is to simplify the implementation
+of an open samples policy.
+
+##### otp_settings
+
+Minimum list of fuses that must match the device configuration before committing
+the endorsed keys to flash. The hash of the list of targeted fuse values is
+hashed with the endorsement manifest before signing.
+
+Note: The device identifier fuses can be added to the `otp_settings` to restrict
+the keys to be used with a single device. This mode of operation is referred to
+as node-locked secure boot configuration.
+
+### Device Key Provisioning Flow
+
+The following figure shows the sequence of operations to commit the new set of
+keys once the key endorsement manifest has been verified.
+
+Definitions:
+
+*   `slot`: Owner slot. This implementation assumes that there are only 2 owner
+    slots, so the only valid values are 0 and 1.
+*   `id`: The owner assignment identifier. It is implemented as a monotonically
+    increasing counter. The new owner id is equivalent to N + 1 for a previous
+    owner id of N.
+*   `pub_keys`: List of keys associated with the owner slot. Includes key policy
+    information.
+*   `digest`: Integrity of the owner slot record calculated as `MAC(Kn,
+    slot|id|pub_keys)`. The key (`Kn`) requirements are described later in more
+    detail.
+
+<table>
+  <tr>
+    <td>
+    <!--
+    svgs are made in google docs, and can be found here:
+    https://docs.google.com/drawings/d/1hnC2EgkYpkxhVJ6I0prxdoQbglstqfwxvBYh_JIt8TA/edit?usp=sharing
+    -->
+      <img src="ownership_transfer_fig1.svg" width="900" alt="fig1">
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: Provisioning sequence versus owner slots.</td>
+  </tr>
+</table>
+
+Detailed description:
+
+**Step 1 (S1) Initial State**
+
+The current owner is stored in slot 0 and the device is in unlocked ownership
+state. A key stored in the current owner slot (0) is used to validate the key
+endorsement manifest for the next owner.
+
+**Step 2 (S2) Intermediate State**
+
+The new owner keys are written into the available owner slot - in this case slot
+one. The `pub_keys` and `digest` parameters are written before the `id`. Once the
+`id`is written into flash, the new owner slot entry is considered to be valid.
+
+The `id` parameter must be strictly greater than the previous owner `id`.
+
+**Step 3 (S3) Final State**
+
+An additional integrity check over the new owner slot is performed before
+completing provisioning of the new owner keys. Upon successful verification, the
+new owner is marked as the current owner by deleting the `id` of the previous
+owner. The `pub_keys` of the previous owner may be deleted as well as part of
+this step.
+
+#### Integrity Key (Kn, Kn+1)
+
+Integrity keys are used to implement integrity checks for each owner slot. The
+integrity key has the following requirements:
+
+*   IK_REQ1: The key shall be unique per owner slot and ownership configuration.
+*   IK_REQ2: The key can only be computed by a trusted `ROM_EXT` boot
+    configuration.
+*   IK_REQ3: The key only available to the `ROM`/`ROM_EXT`.
+
+These requirements can be achieved by a combination of physical security and
+cryptographic guarantees. The following example demonstrates how to derive the
+Integrity Key from a symmetric key stored in OTP and only available to
+`ROM`/`ROM_EXT` software.
+
+##### Key Step Function - MAC
+
+This approach relies on a symmetric secret (`K`) managed by the `ROM`/`ROM_EXT`
+software. It is intended to mitigate boot time issues associated with consuming
+`K` directly from the key manager.
+
+Parameters:
+
+*   `K`: Device integrity secret provisioned at manufacturing time. Only visible
+    to ROM and ROM_EXT software. Stored in OTP.
+*   `slot`: Owner slot. This implementation assumes that there are only 2 owner
+    slots, so the only valid values are 0 and 1. This parameter is used to make
+    sure the key cannot be reused to verify the other slot.
+*   `n`: The owner assignment identifier. It is implemented as a monotonically
+    increasing counter. It is used here to bind the key to the owner identifier.
+*   `prev_owner_digest`: (Under consideration) Digest of the previous owner
+    (e.g. `id = n - 1`). Used to bind the key to the custody chain (chain of
+    owners).
+
+Function: `MAC` is an OpenTitan approved MAC function.
+
+```
+Kn = MAC(K, "OwnerSlot" | slot | n | prev_owner_digest)
+```
+
+The slot and n values are used to fulfill the IK_REQ1 requirement. The
+availability of `K` is enforced by software to fulfill IK_REQ2 and IK_REQ3.
+`prev_owner_digest` is under consideration to bind the key to the custody chain
+(chain of ownership).
+
+#### Additional Requirements
+
+**Key Manager Availability**
+
+The `ROM_EXT` is required to disable the key manager before handing over
+execution to the next boot stage when the device is in device
+`UNLOCKED_OWNERSHIP`
+ownership state.
+
+**Manufacturing Requirements**
+
+Determine if the `prev_owner_digest` field must be initialized with non-zero
+value at manufacturing time.
+
+## OpenTitan Device Mode
+
+A host can send unlock ownership and ownership transfer commands to OpenTitan
+via any physical interface supported by the `ROM_EXT`. The details of the
+command transport layer protocol, as well as the list of supported physical
+devices are left to the reference software implementation.
+
+However, there must be at least one mechanism available to perform ownership
+transfer at manufacturing time using an implementation compatible with ATE[^2]
+infrastructure.
+
+### Unlock Ownership {#unlock-ownership}
+
+This flow implements transition from `LOCKED_OWNERSHIP` to `UNLOCKED_OWNERSHIP` states. It
+is used by the Silicon Owner to relinquish ownership of the device and enable
+ownership transfer functionality. The device must be in `UNLOCKED_OWNERSHIP` state before
+it can be assigned to a new owner.
+
+The unlock operation is implemented as a signed command sent from the
+Kernel/Application layer to the `ROM_EXT`. The signature is required to allow
+the current owner to only allow authenticated and authorized users access to the
+unlock ownership flow.
+
+The following fields must be supported by the command:
+
+#### signature
+
+Signature covering the command structure. The signature is verified using the
+`UNLOCK` key stored in the active owner slot.
+
+#### unlock_nonce
+
+Ownership-unlock nonce value, which is generated at the time the current owner
+first took ownership of the device.
+
+*   The nonce is expected to be unique per device and ownership assignment.
+*   The nonce is stored to make the unlock command re-triable (and fault
+    tolerant).
+*   The nonce may be readable from any boot stage to simplify the unlock
+    operation.
+
+#### flags
+
+Additional flags passed to the `ROM_EXT` to configure unlock flow settings:
+
+*   `WIPE_FLASH`: Erase owner flash contents on successful unlock operation.
+
+<strong>Example</strong>
+
+The following sequence diagram shows a reference implementation of the unlock
+flow. Error handling is omitted for simplicity.
+
+<table>
+  <tr>
+    <td>
+      <img src="ownership_transfer_fig2.svg" width="900" alt="fig2" >
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: Device unlock</td>
+  </tr>
+</table>
+
+Detailed description:
+
+**Step 1 (S1) Get unlock nonce and device id**: The Host queries the device
+identifier and unlock nonce from the device.
+
+**Steps 2-4 (S2, S3, S4) Request unlock command signature**: The Host requests
+the Device Registry service to sign the unlock ownership command for the device
+id with provided nonce. The Device Registry requests a cloud key manager service
+to sign the command with a key associated with the device identifier. The
+signature must be verifiable with the `UNLOCK` key stored in the active owner
+slot.
+
+**Step 5 (S5) Request device unlock**: The Host sends the unlock ownership
+command to the Device. The command is first handled by the Kernel/APP layer.
+
+**Step 6 (S6) Pre-unlock steps**: The Kernel/APP layer may verify the unlock
+command and execute any pre-unlock steps, including erasing owner level secrets.
+
+**Step 7 (S7) Request device unlock**: The Kernel copies the unlock command to a
+boot services memory region shared with the `ROM_EXT`, and performs a reset to
+trigger the unlock operation on the next boot.
+
+**Step 8 (S8) Unlock steps**: The `ROM_EXT` verifies the unlock command and
+updates the device state to `UNLOCKED_OWNERSHIP` state. The Device proceeds with the boot
+flow and reports the unlock result to the kernel via shared memory.
+
+**Step 9 (S9) Unlock result**: The unlock result is first propagated to the
+Device Kernel/APP layer. The Kernel may opt to execute any post-unlock steps
+before propagating the result to the Host. The Host propagates the unlock result
+to the Device Registry. The Device Registry may opt to remove the device from
+its allow-list.
+
+### Ownership Transfer {#ownership-transfer-device}
+
+An ownership transfer command sent by a host to OpenTitan, is serviced by the
+ROM extension (`ROM_EXT`) allowing the Silicon Owner to take ownership of the
+device at silicon manufacture, Contract Manufacturing (CM) stage or in the
+field.
+
+<table>
+  <tr>
+    <td>
+      <img src="ownership_transfer_fig3.svg" width="300" alt="fig3" >
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: Ownership Transfer supported by ROM_EXT</td>
+  </tr>
+</table>
+
+<table>
+  <tr>
+    <td><strong>Step</strong></td>
+    <td><strong>Description</strong></td>
+  </tr>
+  <tr>
+    <td>UNLOCKED_OWNERSHIP state</td>
+    <td>
+
+Entry into the ownership transfer flow is conditional to the device being in
+`UNLOCKED_OWNERSHIP` state. See [Unlock Flow](#unlock-ownership) for more details on how
+to transition from LOCKED_OWNERSHIP to UNLOCKED_OWNERSHIP states.
+    </td>
+  </tr>
+  <tr>
+    <td>Verify payload header</td>
+    <td>
+
+The ownership transfer payload header including the key endorsement manifest is
+verified by the ROM extension. The *header shall fit within available SRAM* and
+be signed by an approved key as described in the
+[Key Provisioning](#key-provisioning) section.
+    </td>
+  </tr>
+  <tr>
+    <td>Flash erase</td>
+    <td>
+
+Code, data and info pages available to the Silicon Owner are erased. Erase
+checks are performed.
+   </td>
+  </tr>
+  <tr>
+    <td>Reset secrets</td>
+    <td>
+    <!-- TODO: Link to Identities and Root Keys document -->
+
+The [OwnerRootSecret](#) is reset with a value extracted from a DRBG configured
+with a security strength equivalent to one supported by the key manager.
+    </td>
+  </tr>
+  <tr>
+    <td>Rotate unlock nonce</td>
+    <td>
+
+A 64bit random value is extracted from the DRBG to be used as an unlock nonce.
+See [Unlock Flow](#unlock-flow) for more details.
+    </td>
+  </tr>
+  <tr>
+    <td>Provision owner keys</td>
+    <td>
+
+Owner keys are provisioned into the device as defined in the
+[Key Provisioning](#key-provisioning) section.
+    </td>
+  </tr>
+  <tr>
+    <td>Flash image</td>
+    <td>Owner software is written into one of the flash partitions.</td>
+  </tr>
+  <tr>
+    <td>Activate owner</td>
+    <td>
+
+Owner software sends a command to the `ROM_EXT` to complete ownership transfer,
+which effectively sets the new owner as the current owner.
+    </td>
+  </tr>
+</table>
+
+## OpenTitan Host Mode
+
+Some of the OpenTitan use cases require support for self updates in which
+OpenTitan is used in host mode to scan an external device interface for update
+payloads. This section describes Ownership Transfer layered on top of such self
+update mechanism.
+
+An OpenTitan implementation may support this ownership transfer model at the SKU
+level.
+
+### Unlock Ownership
+
+The Device is initially in `LOCKED_OWNERSHIP` state and configured with a stack signed by
+the current owner. The following steps must be implemented in a fault tolerant
+way:
+
+1.  The Device is updated to a stack able to support the ownership transfer
+    implementation as described in the next section. The owner may opt for
+    clearing any device secrets as part of this step.
+2.  Ownership unlock is performed as described in the OpenTitan device mode
+    [Unlock Flow](#unlock-flow) section.
+
+### Ownership Transfer
+
+In this section, SPI EEPROM is used as the target device. However, the
+implementation may opt for supporting other targets.
+
+The Device is initially in `UNLOCKED_OWNERSHIP` state and configured with a stack (Kernel +
+APPs) able to scan an external SPI EEPROM and trigger the ownership transfer
+flow. The following procedure also assumes that the Device storage follows the
+internal [flash layout](#flash-layout) guidelines.
+
+The process must be implemented in a fault tolerant manner to be able to restart
+the process to recover from a failed attempt.
+
+Detailed steps:
+
+1.  The current owner endorses the next owner keys as described in the
+    [Key Endorsement](#key-endorsement) section.
+2.  The next owner writes the key endorsement manifest to the external EEPROM
+    followed by an update payload. The update payload includes all the silicon
+    owner boot stages.
+3.  The Device kernel module or application in charge of performing ownership
+    transfer, referred to as _the application_, is activated upon detecting the
+    `UNLOCKED_OWNERSHIP` state at Device boot time.
+4.  The application scans the external EEPROM for a valid key endorsement
+    manifest and update payload. The key endorsement manifest is validated with
+    the current owner’s `NEXT_OWNER` key. The update payload is validated with
+    one of the `CODE_SIGN` keys embedded in the key endorsement manifest.
+5.  The application writes the inactive embedded flash code partitions with the
+    ones included in the update payload.
+    *   Note: In order to avoid a BL0 (bootloader) fixed flash size allocation
+        across all owners, the implementation may opt to support the following:
+        1.  The boot flow will give preference to the next boot stage residing
+            in the same flash bank if both A and B versions are the same.
+        2.  The A/B flash layout may contain two identical copies of the stack
+            at the start of the process to make sure it is possible to boot from
+            a single flash bank.
+6.  The application loads the key endorsement manifest into the boot services
+    shared memory region and triggers a reset to perform ownership transfer on
+    the next boot cycle.
+7.  The `ROM_EXT` executes the ownership transfer flow described in the
+    [Ownership Transfer](#ownership-transfer-device) section with the following
+    differences:
+    1.  Flash erase and flash image stages are not executed.
+    2.  The activate owner stage may be delayed and executed later depending on
+        the implementation.
+8.  The `ROM_EXT` attempts to boot the new owner image with the new owner
+    configuration.
+9.  On the first boot, the new owner image queues an activate owner command,
+    which is then executed by the `ROM_EXT` on the next boot. The new owner
+    becomes the current owner.
+10. The previous owner code partitions can be erased at this point.
+11. Device attestation can be performed after this point.
+
+## Ownership Transfer Disabled
+
+Ownership Transfer can be disabled at the SKU level. In this case secure boot is
+implemented by storing the Silicon Owner BL0 verification keys in the `ROM_EXT`.
+The `ROM_EXT` is thus not required to implement ownership transfer in this
+configuration.
+
+## Flash Layout {#flash-layout}
+
+To simplify the implementation, the flash layout implements fixed offset and
+size allocations for the `ROM_EXT` and the certificate storage regions. This
+allows the flash erase and write operations to be performed at deterministic
+address ranges.
+
+The implementation may opt to store the certificates in info regions to save
+data partition space.
+
+<table>
+  <tr>
+    <td>
+      <img src="ownership_transfer_fig4.svg" width="900" alt="fig4" >
+    </td>
+  </tr>
+  <tr>
+    <td>Figure: Flash layout</td>
+  </tr>
+</table>
+
+`owner_slot_0` and `owner_slot_1` are used to store the Silicon Owner keys as
+described in the [Key Provisioning](#key-provisioning) section.
+
+## Attestation Update
+
+<!-- TODO: Link to Attestation specification document -->
+
+Regular attestation updates as described in the [Attestation](#) specification
+are available when the device has an active owner. Devices in
+`UNLOCKED_OWNERSHIP` state may have restricted attestation capabilities, for example,
+restricted to only end-to-end attestation.
+
+## Ownership Transfer During Manufacturing
+
+Manufacturing shall not preclude the implementation of the following default
+stack configurations:
+
+<!-- TODO: Update links to device life cycle specification doc.  -->
+
+*   [`LOCKED_OWNERSHIP`](#) state with default factory image.
+*   [`UNLOCKED_OWNERSHIP`](#) state with default factory image.
+*   [`LOCKED_OWNERSHIP`](#) state with default factory image and Ownership Transfer
+    disabled.
+
+Factory software may be used to configure the ownership slots before injecting
+the factory image.
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+[^1]: https://docs.opentitan.org/doc/security/logical_security_model/#silicon-owner
+
+[^2]: Automatic Test Equipment used at package level testing.
diff --git a/doc/security/specs/ownership_transfer/_index.md b/doc/security/specs/ownership_transfer/_index.md
deleted file mode 100644
index 6b7a6d5..0000000
--- a/doc/security/specs/ownership_transfer/_index.md
+++ /dev/null
@@ -1,573 +0,0 @@
----
-title: "Ownership Transfer"
----
-
-## Overview
-
-The Silicon Owner is defined as a logical entity or entities allowed to sign
-code for execution, as well as to sign ownership management commands[^1].
-OpenTitan supports the following device life cycle states to manage the
-ownership state of the device:
-
-*   _UNLOCKED_OWNERSHIP_: The device is ready to be assigned to a new owner.
-*   _LOCKED_OWNERSHIP_: The device is assigned to an owner and in operational mode.
-
-This document defines the ownership management functions that control the
-transitions between ownership states:
-
-### Unlock Ownership {#unlock-ownership-top}
-
-Implements transition from `LOCKED_OWNERSHIP` to `UNLOCKED_OWNERSHIP` state. The device must be in
-`UNLOCKED_OWNERSHIP` state before it can be assigned to a new owner.
-
-### Ownership Transfer (or Ownership Assignment) {#ownership-transfer-top}
-
-Implements transition from `UNLOCKED_OWNERSHIP` to `LOCKED_OWNERSHIP` stage. The rest of this document
-refers to this functionality as Ownership Transfer.
-
-## Owner Keys {#owner-keys}
-
-The following keys are provisioned as part of this flow.
-
-### CODE_SIGN
-
-*   **Description**: Used to verify the Silicon Owner first bootloader stage.
-*   **Key Type**: RSA-3072 public key with exponent F4.
-
-### UNLOCK
-
-*   **Description**: Used to authenticate the Unlock Ownership payload.
-*   **Key Type**: ECC NIST-P256 Curve.
-
-### NEXT_OWNER
-
-*   **Description**: Used to authenticate the Ownership Transfer payload.
-*   **Key Type**: ECC NIST-P256 Curve.
-
-The `UNLOCK` and `NEXT_OWNER` keys are required to ensure ownership state
-transitions are only triggered by authenticated and authorized commands.
-Authorization is implemented via key identification (`UNLOCK` versus
-`NEXT_OWNER`).
-
-<!-- TODO: Add link to Identities and Root Keys doc -->
-
-Transition into `LOCKED_OWNERSHIP` stage results in a new device [Owner Identity](#), used
-in attestation and post-ownership-transfer application provisioning flows.
-
-There are three modes of ownership transfer supported:
-
-*   **Silicon Creator endorses Next Owner**: The Silicon Creator signs the set
-    of public keys associated with the next owner as part of the ownership
-    transfer flow.
-*   **Current Owner endorses Next Owner**: The Current Owner signs the set of
-    keys associated with the next owner as part of the ownership transfer flow.
-*   **Fixed Owner**: In this case a single owner is provisioned on the device
-    and ownership transfer is disabled via manufacturing SKU configuration.
-
-## Terminology
-
-Boot stages:
-
-*   `ROM`: Metal mask ROM, sometimes known as Boot ROM.
-*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
-*   Owner boot stages. This document uses two stages as an example. The Silicon
-    Owner is free to choose other boot configurations.
-    *   `BL0`: Bootloader. Signed by the Silicon Owner.
-    *   `KERNEL`: Signed by the Silicon Owner.
-
-## Key Provisioning {#key-provisioning}
-
-As part of the Ownership Transfer flow, the Silicon Owner keys are endorsed
-either by the Silicon Creator or by the Current Owner of the device. This is
-done to ensure that only authenticated and authorized entities are able to take
-ownership of the device.
-
-### Key Endorsement Format
-
-Key endorsement is implemented as a signed manifest. The rest of the document
-refers to this as the Key Endorsement Manifest, or manifest for simplicity. The
-following fields must be supported by the manifest implementation:
-
-#### signature
-
-Signature covering the manifest.
-
-#### signature_algorithm
-
-Signature algorithm. For this version of the specification it is always set
-to `ECDSA-sha256`.
-
-#### public_key
-
-Public key to verify the signature with. The number of bytes depends on the
-`signature_algorithm` field. Depending on the ownership transfer model, the
-public key must match one of the following requirements:
-
- Endorsing Entity | Public Key Requirement
- ---------------- | --------------------------------------------------------
- Silicon Creator  | The public key must be stored in the ROM_EXT and  integrity protected by the ROM_EXT signature.
- Previous Owner   | The public key must be stored in the previous owner slot and labeled as the `NEXT_OWNER` in the policy field. See `owner_keys` for more details.
-
-#### owner_keys (array)
-
-List of public keys endorsed by the manifest. See [Owner Keys](#owner-keys) for
-more details.
-
-#### Optional Parameters
-
-The following parameters are required in the secure boot manifest
-implementation, but are left as optional in the key endorsement manifest. The
-Silicon Creator or previous Silicon Owner may want to implement these parameters
-to restrict the deployment of the endorsed keys.
-
-Note: If implemented, the restrictions imposed by these fields cannot be revoked
-by ownership transfer once provisioned. This is to simplify the implementation
-of an open samples policy.
-
-##### otp_settings
-
-Minimum list of fuses that must match the device configuration before committing
-the endorsed keys to flash. The hash of the list of targeted fuse values is
-hashed with the endorsement manifest before signing.
-
-Note: The device identifier fuses can be added to the `otp_settings` to restrict
-the keys to be used with a single device. This mode of operation is referred to
-as node-locked secure boot configuration.
-
-### Device Key Provisioning Flow
-
-The following figure shows the sequence of operations to commit the new set of
-keys once the key endorsement manifest has been verified.
-
-Definitions:
-
-*   `slot`: Owner slot. This implementation assumes that there are only 2 owner
-    slots, so the only valid values are 0 and 1.
-*   `id`: The owner assignment identifier. It is implemented as a monotonically
-    increasing counter. The new owner id is equivalent to N + 1 for a previous
-    owner id of N.
-*   `pub_keys`: List of keys associated with the owner slot. Includes key policy
-    information.
-*   `digest`: Integrity of the owner slot record calculated as `MAC(Kn,
-    slot|id|pub_keys)`. The key (`Kn`) requirements are described later in more
-    detail.
-
-<table>
-  <tr>
-    <td>
-    <!--
-    svgs are made in google docs, and can be found here: 
-    https://docs.google.com/drawings/d/1hnC2EgkYpkxhVJ6I0prxdoQbglstqfwxvBYh_JIt8TA/edit?usp=sharing
-    -->
-      <img src="ownership_transfer_fig1.svg" width="900" alt="fig1">
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: Provisioning sequence versus owner slots.</td>
-  </tr>
-</table>
-
-Detailed description:
-
-**Step 1 (S1) Initial State**
-
-The current owner is stored in slot 0 and the device is in unlocked ownership
-state. A key stored in the current owner slot (0) is used to validate the key
-endorsement manifest for the next owner.
-
-**Step 2 (S2) Intermediate State**
-
-The new owner keys are written into the available owner slot - in this case slot
-one. The `pub_keys` and `digest` parameters are written before the `id`. Once the
-`id`is written into flash, the new owner slot entry is considered to be valid.
-
-The `id` parameter must be strictly greater than the previous owner `id`.
-
-**Step 3 (S3) Final State**
-
-An additional integrity check over the new owner slot is performed before
-completing provisioning of the new owner keys. Upon successful verification, the
-new owner is marked as the current owner by deleting the `id` of the previous
-owner. The `pub_keys` of the previous owner may be deleted as well as part of
-this step.
-
-#### Integrity Key (Kn, Kn+1)
-
-Integrity keys are used to implement integrity checks for each owner slot. The
-integrity key has the following requirements:
-
-*   IK_REQ1: The key shall be unique per owner slot and ownership configuration.
-*   IK_REQ2: The key can only be computed by a trusted `ROM_EXT` boot
-    configuration.
-*   IK_REQ3: The key only available to the `ROM`/`ROM_EXT`.
-
-These requirements can be achieved by a combination of physical security and
-cryptographic guarantees. The following example demonstrates how to derive the
-Integrity Key from a symmetric key stored in OTP and only available to
-`ROM`/`ROM_EXT` software.
-
-##### Key Step Function - MAC
-
-This approach relies on a symmetric secret (`K`) managed by the `ROM`/`ROM_EXT`
-software. It is intended to mitigate boot time issues associated with consuming
-`K` directly from the key manager.
-
-Parameters:
-
-*   `K`: Device integrity secret provisioned at manufacturing time. Only visible
-    to ROM and ROM_EXT software. Stored in OTP.
-*   `slot`: Owner slot. This implementation assumes that there are only 2 owner
-    slots, so the only valid values are 0 and 1. This parameter is used to make
-    sure the key cannot be reused to verify the other slot.
-*   `n`: The owner assignment identifier. It is implemented as a monotonically
-    increasing counter. It is used here to bind the key to the owner identifier.
-*   `prev_owner_digest`: (Under consideration) Digest of the previous owner
-    (e.g. `id = n - 1`). Used to bind the key to the custody chain (chain of
-    owners).
-
-Function: `MAC` is an OpenTitan approved MAC function.
-
-```
-Kn = MAC(K, "OwnerSlot" | slot | n | prev_owner_digest)
-```
-
-The slot and n values are used to fulfill the IK_REQ1 requirement. The
-availability of `K` is enforced by software to fulfill IK_REQ2 and IK_REQ3.
-`prev_owner_digest` is under consideration to bind the key to the custody chain
-(chain of ownership).
-
-#### Additional Requirements
-
-**Key Manager Availability**
-
-The `ROM_EXT` is required to disable the key manager before handing over
-execution to the next boot stage when the device is in device
-`UNLOCKED_OWNERSHIP`
-ownership state.
-
-**Manufacturing Requirements**
-
-Determine if the `prev_owner_digest` field must be initialized with non-zero
-value at manufacturing time.
-
-## OpenTitan Device Mode
-
-A host can send unlock ownership and ownership transfer commands to OpenTitan
-via any physical interface supported by the `ROM_EXT`. The details of the
-command transport layer protocol, as well as the list of supported physical
-devices are left to the reference software implementation.
-
-However, there must be at least one mechanism available to perform ownership
-transfer at manufacturing time using an implementation compatible with ATE[^2]
-infrastructure.
-
-### Unlock Ownership {#unlock-ownership}
-
-This flow implements transition from `LOCKED_OWNERSHIP` to `UNLOCKED_OWNERSHIP` states. It
-is used by the Silicon Owner to relinquish ownership of the device and enable
-ownership transfer functionality. The device must be in `UNLOCKED_OWNERSHIP` state before
-it can be assigned to a new owner.
-
-The unlock operation is implemented as a signed command sent from the
-Kernel/Application layer to the `ROM_EXT`. The signature is required to allow
-the current owner to only allow authenticated and authorized users access to the
-unlock ownership flow.
-
-The following fields must be supported by the command:
-
-#### signature
-
-Signature covering the command structure. The signature is verified using the
-`UNLOCK` key stored in the active owner slot.
-
-#### unlock_nonce
-
-Ownership-unlock nonce value, which is generated at the time the current owner
-first took ownership of the device.
-
-*   The nonce is expected to be unique per device and ownership assignment.
-*   The nonce is stored to make the unlock command re-triable (and fault
-    tolerant).
-*   The nonce may be readable from any boot stage to simplify the unlock
-    operation.
-
-#### flags
-
-Additional flags passed to the `ROM_EXT` to configure unlock flow settings:
-
-*   `WIPE_FLASH`: Erase owner flash contents on successful unlock operation.
-
-<strong>Example</strong>
-
-The following sequence diagram shows a reference implementation of the unlock
-flow. Error handling is omitted for simplicity.
-
-<table>
-  <tr>
-    <td>
-      <img src="ownership_transfer_fig2.svg" width="900" alt="fig2" >
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: Device unlock</td>
-  </tr>
-</table>
-
-Detailed description:
-
-**Step 1 (S1) Get unlock nonce and device id**: The Host queries the device
-identifier and unlock nonce from the device.
-
-**Steps 2-4 (S2, S3, S4) Request unlock command signature**: The Host requests
-the Device Registry service to sign the unlock ownership command for the device
-id with provided nonce. The Device Registry requests a cloud key manager service
-to sign the command with a key associated with the device identifier. The
-signature must be verifiable with the `UNLOCK` key stored in the active owner
-slot.
-
-**Step 5 (S5) Request device unlock**: The Host sends the unlock ownership
-command to the Device. The command is first handled by the Kernel/APP layer.
-
-**Step 6 (S6) Pre-unlock steps**: The Kernel/APP layer may verify the unlock
-command and execute any pre-unlock steps, including erasing owner level secrets.
-
-**Step 7 (S7) Request device unlock**: The Kernel copies the unlock command to a
-boot services memory region shared with the `ROM_EXT`, and performs a reset to
-trigger the unlock operation on the next boot.
-
-**Step 8 (S8) Unlock steps**: The `ROM_EXT` verifies the unlock command and
-updates the device state to `UNLOCKED_OWNERSHIP` state. The Device proceeds with the boot
-flow and reports the unlock result to the kernel via shared memory.
-
-**Step 9 (S9) Unlock result**: The unlock result is first propagated to the
-Device Kernel/APP layer. The Kernel may opt to execute any post-unlock steps
-before propagating the result to the Host. The Host propagates the unlock result
-to the Device Registry. The Device Registry may opt to remove the device from
-its allow-list.
-
-### Ownership Transfer {#ownership-transfer-device}
-
-An ownership transfer command sent by a host to OpenTitan, is serviced by the
-ROM extension (`ROM_EXT`) allowing the Silicon Owner to take ownership of the
-device at silicon manufacture, Contract Manufacturing (CM) stage or in the
-field.
-
-<table>
-  <tr>
-    <td>
-      <img src="ownership_transfer_fig3.svg" width="300" alt="fig3" >
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: Ownership Transfer supported by ROM_EXT</td>
-  </tr>
-</table>
-
-<table>
-  <tr>
-    <td><strong>Step</strong></td>
-    <td><strong>Description</strong></td>
-  </tr>
-  <tr>
-    <td>UNLOCKED_OWNERSHIP state</td>
-    <td>
-
-Entry into the ownership transfer flow is conditional to the device being in
-`UNLOCKED_OWNERSHIP` state. See [Unlock Flow](#unlock-ownership) for more details on how
-to transition from LOCKED_OWNERSHIP to UNLOCKED_OWNERSHIP states.
-    </td>
-  </tr>
-  <tr>
-    <td>Verify payload header</td>
-    <td>
-
-The ownership transfer payload header including the key endorsement manifest is
-verified by the ROM extension. The *header shall fit within available SRAM* and
-be signed by an approved key as described in the
-[Key Provisioning](#key-provisioning) section.
-    </td>
-  </tr>
-  <tr>
-    <td>Flash erase</td>
-    <td>
-
-Code, data and info pages available to the Silicon Owner are erased. Erase
-checks are performed.
-   </td>
-  </tr>
-  <tr>
-    <td>Reset secrets</td>
-    <td>
-    <!-- TODO: Link to Identities and Root Keys document -->
-
-The [OwnerRootSecret](#) is reset with a value extracted from a DRBG configured
-with a security strength equivalent to one supported by the key manager.
-    </td>
-  </tr>
-  <tr>
-    <td>Rotate unlock nonce</td>
-    <td>
-
-A 64bit random value is extracted from the DRBG to be used as an unlock nonce.
-See [Unlock Flow](#unlock-flow) for more details.
-    </td>
-  </tr>
-  <tr>
-    <td>Provision owner keys</td>
-    <td>
-
-Owner keys are provisioned into the device as defined in the
-[Key Provisioning](#key-provisioning) section.
-    </td>
-  </tr>
-  <tr>
-    <td>Flash image</td>
-    <td>Owner software is written into one of the flash partitions.</td>
-  </tr>
-  <tr>
-    <td>Activate owner</td>
-    <td>
-
-Owner software sends a command to the `ROM_EXT` to complete ownership transfer,
-which effectively sets the new owner as the current owner.
-    </td>
-  </tr>
-</table>
-
-## OpenTitan Host Mode
-
-Some of the OpenTitan use cases require support for self updates in which
-OpenTitan is used in host mode to scan an external device interface for update
-payloads. This section describes Ownership Transfer layered on top of such self
-update mechanism.
-
-An OpenTitan implementation may support this ownership transfer model at the SKU
-level.
-
-### Unlock Ownership
-
-The Device is initially in `LOCKED_OWNERSHIP` state and configured with a stack signed by
-the current owner. The following steps must be implemented in a fault tolerant
-way:
-
-1.  The Device is updated to a stack able to support the ownership transfer
-    implementation as described in the next section. The owner may opt for
-    clearing any device secrets as part of this step.
-2.  Ownership unlock is performed as described in the OpenTitan device mode
-    [Unlock Flow](#unlock-flow) section.
-
-### Ownership Transfer
-
-In this section, SPI EEPROM is used as the target device. However, the
-implementation may opt for supporting other targets.
-
-The Device is initially in `UNLOCKED_OWNERSHIP` state and configured with a stack (Kernel +
-APPs) able to scan an external SPI EEPROM and trigger the ownership transfer
-flow. The following procedure also assumes that the Device storage follows the
-internal [flash layout](#flash-layout) guidelines.
-
-The process must be implemented in a fault tolerant manner to be able to restart
-the process to recover from a failed attempt.
-
-Detailed steps:
-
-1.  The current owner endorses the next owner keys as described in the
-    [Key Endorsement](#key-endorsement) section.
-2.  The next owner writes the key endorsement manifest to the external EEPROM
-    followed by an update payload. The update payload includes all the silicon
-    owner boot stages.
-3.  The Device kernel module or application in charge of performing ownership
-    transfer, referred to as _the application_, is activated upon detecting the
-    `UNLOCKED_OWNERSHIP` state at Device boot time.
-4.  The application scans the external EEPROM for a valid key endorsement
-    manifest and update payload. The key endorsement manifest is validated with
-    the current owner’s `NEXT_OWNER` key. The update payload is validated with
-    one of the `CODE_SIGN` keys embedded in the key endorsement manifest.
-5.  The application writes the inactive embedded flash code partitions with the
-    ones included in the update payload.
-    *   Note: In order to avoid a BL0 (bootloader) fixed flash size allocation
-        across all owners, the implementation may opt to support the following:
-        1.  The boot flow will give preference to the next boot stage residing
-            in the same flash bank if both A and B versions are the same.
-        2.  The A/B flash layout may contain two identical copies of the stack
-            at the start of the process to make sure it is possible to boot from
-            a single flash bank.
-6.  The application loads the key endorsement manifest into the boot services
-    shared memory region and triggers a reset to perform ownership transfer on
-    the next boot cycle.
-7.  The `ROM_EXT` executes the ownership transfer flow described in the
-    [Ownership Transfer](#ownership-transfer-device) section with the following
-    differences:
-    1.  Flash erase and flash image stages are not executed.
-    2.  The activate owner stage may be delayed and executed later depending on
-        the implementation.
-8.  The `ROM_EXT` attempts to boot the new owner image with the new owner
-    configuration.
-9.  On the first boot, the new owner image queues an activate owner command,
-    which is then executed by the `ROM_EXT` on the next boot. The new owner
-    becomes the current owner.
-10. The previous owner code partitions can be erased at this point.
-11. Device attestation can be performed after this point.
-
-## Ownership Transfer Disabled
-
-Ownership Transfer can be disabled at the SKU level. In this case secure boot is
-implemented by storing the Silicon Owner BL0 verification keys in the `ROM_EXT`.
-The `ROM_EXT` is thus not required to implement ownership transfer in this
-configuration.
-
-## Flash Layout {#flash-layout}
-
-To simplify the implementation, the flash layout implements fixed offset and
-size allocations for the `ROM_EXT` and the certificate storage regions. This
-allows the flash erase and write operations to be performed at deterministic
-address ranges.
-
-The implementation may opt to store the certificates in info regions to save
-data partition space.
-
-<table>
-  <tr>
-    <td>
-      <img src="ownership_transfer_fig4.svg" width="900" alt="fig4" >
-    </td>
-  </tr>
-  <tr>
-    <td>Figure: Flash layout</td>
-  </tr>
-</table>
-
-`owner_slot_0` and `owner_slot_1` are used to store the Silicon Owner keys as
-described in the [Key Provisioning](#key-provisioning) section.
-
-## Attestation Update
-
-<!-- TODO: Link to Attestation specification document -->
-
-Regular attestation updates as described in the [Attestation](#) specification
-are available when the device has an active owner. Devices in
-`UNLOCKED_OWNERSHIP` state may have restricted attestation capabilities, for example,
-restricted to only end-to-end attestation.
-
-## Ownership Transfer During Manufacturing
-
-Manufacturing shall not preclude the implementation of the following default
-stack configurations:
-
-<!-- TODO: Update links to device life cycle specification doc.  -->
-
-*   [`LOCKED_OWNERSHIP`](#) state with default factory image.
-*   [`UNLOCKED_OWNERSHIP`](#) state with default factory image.
-*   [`LOCKED_OWNERSHIP`](#) state with default factory image and Ownership Transfer
-    disabled.
-
-Factory software may be used to configure the ownership slots before injecting
-the factory image.
-
-<!-- Footnotes themselves at the bottom. -->
-
-## Notes
-
-[^1]: https://docs.opentitan.org/doc/security/logical_security_model/#silicon-owner
-
-[^2]: Automatic Test Equipment used at package level testing.
diff --git a/doc/security/specs/secure_boot/README.md b/doc/security/specs/secure_boot/README.md
new file mode 100644
index 0000000..cdd54b0
--- /dev/null
+++ b/doc/security/specs/secure_boot/README.md
@@ -0,0 +1,218 @@
+# Secure Boot
+
+<p style="color: red; text-align: right;">
+  Status: Pre-RFC
+</p>
+
+The following overview gives a brief, high-level explanation of OpenTitan's secure boot process.
+The basic guarantee of secure boot is that *no unauthorized code will be executed before the boot process reaches the device owner's code*.
+All executed code must be cryptographically signed by either the owner of the OpenTitan device or the (trusted) entity that originally set up the device at manufacturing time (the "Silicon Creator").
+
+Additionally, the secure boot procedure restricts certain stages to the Silicon Creator, so that even the current device owner can't change them.
+Therefore, if the device changes owners, the new owner only has to trust the Silicon Creator, *not* the previous owner(s).
+
+The diagram below summarizes the specific steps involved in the secure boot process:
+
+<img src="secure_boot_flow.svg" style="width: 800px;">
+
+## ROM {#rom}
+
+The first stage of secure boot is called "ROM".
+ROM is a region of read-only memory that cannot be updated at all after an OpenTitan device is manufactured.
+For that reason, the ROM is kept as simple as possible; it does some minimal setup, authenticates the next stage (`ROM_EXT`), and jumps there.
+
+The ROM contains non-updateable public keys, which it uses to authenticate the `ROM_EXT` signature.
+These public keys correspond to what OpenTitan calls the "Silicon Creator": the entity who was initially involved in the manufacturing of the device.
+It's important to distinguish between the unchanging Silicon Creator and the changeable "Silicon Owner", the entity that owns the device at a given time.
+
+On startup, hardware settings use a feature called ["enhanced Physical Memory Protection" (ePMP)][ibex-epmp] to ensure that only the ROM code itself is executable.
+For details about how the ROM configures ePMP, see [Memory Protection Module][rom-epmp].
+Flash and MMIO memory regions are unlocked for read/write (for signature verification and configuring peripherals) but are not executable at this stage.
+
+On boot, the ROM code does the following:
+1. Initialize any additional ePMP settings beyond what is set by hardware at startup, plus initialize the C runtime and hardware peripherals (e.g. pinmux, UART).
+2. Load the "manifest" for `ROM_EXT`, which includes the start and end address of `ROM_EXT` code, a cryptographic signature, a public key modulus, and "selector bits" for hardware information.
+    * The manifest's modulus field identifies which of the stored [Silicon Creator keys][silicon-creator-keys] should be used for signature verification. If there is no matching stored key, or the matching key is not suitable for the device's lifecycle state, then the boot fails.
+    * There are two slots for `ROM_EXT` implementations; the ROM will first try the one with the newest security version. If the signature verification for that slot fails, the ROM will attempt to boot from the other slot.
+3. Read usage constraints from hardware according to the selector bits from the manifest.
+    * The selector bits can choose all or part of the device ID, and/or information about the state of the device. Because the selected constraints are included in the digest, this means the signer of a `ROM_EXT` image can restrict their signature to only certain devices/states.
+4. Compute the SHA2-256 digest of the selected usage constraints concatenated with the contents of memory between the `ROM_EXT` start and end address given in the manifest.
+5. Check the signature from the manifest against the digest and the selected Silicon Creator public key.
+    * Unlock flash execution, configure ePMP so that the `ROM_EXT` region is executable, and jump to the start of `ROM_EXT`.
+
+## `ROM_EXT` {#rom-ext}
+
+The `ROM_EXT` ("ROM extension") stage is another region of read-only memory that is controlled by the Silicon Creator.
+However, unlike the ROM, it *can* be updated after the device is manufactured, as long as the new version is signed by the Silicon Creator.
+
+Like the ROM, the `ROM_EXT` must check the signature of the next boot stage (this time against the Silicon Owner's keys, rather than the Silicon Creator’s).
+In addition, the `ROM_EXT` initializes Silicon Creator and then Silicon Owner keys using the key manager and performs “boot services”, which are a small set of specific operations that require access to the Silicon Creator's keys (for instance, providing an [attestation of device state][attestation] or [transferring the device to a new owner][ownership-transfer]).
+These services must happen during boot and not afterwards, because after boot we don't have access to the Silicon Creator keys.
+
+The general procedure for the `ROM_EXT` looks something like this:
+1. Load the manifest for BL0 (which has the same format as the `ROM_EXT` manifest described in the ROM procedure).
+2. Read usage constraints from hardware according to the selector bits from the manifest.
+3. Compute the SHA2-256 digest of the selected usage constraints concatenated with the contents of BL0, as ROM does for `ROM_EXT`.
+4. Check the signature from the manifest against the digest and a public code-signing key from the current Silicon Owner (RSA-3072 with the F4 exponent).
+    * Like the Silicon Creator, the Silicon Owner may have multiple public code-signing keys. How many keys exactly is configurable by the Owner.
+    * The number of retries in case verification fails might differ from ROM, and can differ between `ROM_EXT` implementations.
+5. Step the key manager device in order to obtain the Silicon Creator "root key".
+    * The root key mixes secret values provisioned by the Silicon Creator at manufacturing time with information about the specific device, the device health state, and `ROM_EXT`. It will differ between devices and between `ROM_EXT` images.
+    * The actual value of the root key is locked inside the [key manager][key-manager] and hidden from software. The key manager starts with the Silicon Creator's secret values (which are also hidden from all software) and advances its state to a new value with each additional piece of information.
+    * See the [identities and root keys][identities-keys] page for more details about the intermediate, identity, and root keys.
+6. From the Silicon Creator root key, use the key manager interface to derive the Silicon Creator "identity" key (an ECDSA-P256 key used for [attestation][attestation]) and the Silicon Owner "intermediate key" (a new key manager state which can later be used to derive the Silicon Owner's "identity" key).
+    * To create the intermediate key, the key manager starts with the Silicon Creator root key from the ROM stage, and software steps the key manager state by mixing in owner secrets (provisioned by the owner at the time they take ownership of the device) and certain BL0 configuration information.
+    * In between getting the Silicon Creator identity key and the Silicon Owner intermediate key, check if the boot purpose was "attestation"; if so, generate an attestation certificate for the software/hardware state, sign it with the Silicon Creator identity key, and write it to a special region of memory.
+7. Unlock flash execution, configure ePMP so that the Silicon Creator controlled regions of memory are not writable and the BL0 region is executable, and then jump to the start of BL0.
+
+Once the code has jumped into the Silicon Owner code at BL0, secure boot in its simplest form is complete.
+The Silicon Owner may choose to extend the secure boot process with multiple boot stages of their own; this will differ between device owners, while the stages described here are guaranteed by the Silicon Creator and will be shared by all OpenTitan implementations.
+If any signature verification in the above process fails, or there is any kind of unexpected error, the device will fail to boot.
+
+# Silicon Creator Keys {#silicon-creator-keys}
+
+The Silicon Creator has multiple public keys.
+This redundancy partially protects against the scenario in which one of the keys is compromised; any OpenTitan devices produced after the key is known to be compromised can mark the compromised key invalid, without requiring a full new ROM implementation.
+Devices produced before the key is known to be compromised are not protected by this strategy.
+
+Additionally, each key is restricted to one of three "roles", which determine in which device states the key can be used.
+The roles are:
+* `dev`: development, only for devices in the `DEV` lifecycle state.
+* `test`: manufacturing and testing, only for devices in the `TEST_UNLOCK` and `RMA` lifecycle states.
+* `prod`: production, intended for `PROD` or `PROD_END` lifecycle states but can be used in all states.
+
+The following table summarizes which role can be used in which lifecycle state:
+
+| Key Type | LC_TEST | LC_RMA | LC_DEV | LC_PROD |
+|----------|:-------:|:------:|:------:|:-------:|
+| `test`   | X       | X      |        |         |
+| `dev`    |         |        | X      |         |
+| `prod`   | X       | X      | X      | X       |
+
+If the key indicated in the manifest has a role that doesn't match the lifecycle state of the device, the boot fails.
+All of these keys are 3072-bit RSA public keys with exponent e=65537 (the “F4 exponent”).
+
+The `ROM` has `N` key slots (the exact number depends on the `ROM`) numbered from `0` to `N-1`.
+The `CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN` item in the [OTP](otp-mmap) can be used to invalidate a key at
+manufacturing time. This item consists of several little-endian 32-bit words. Each word contains four 8-bit hardened booleans
+(see `hardened_byte_bool_t` in `hardened.h`) that specifies whether the key is valid (`kHardenedByteBoolTrue`)
+or invalid (any value other that `kHardenedByteBoolTrue`). In order to verify that the key slot `i` contains
+a valid key, the `ROM` will:
+
+* read the `floor(i / 4)`-th word in `CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN`,
+* extract the `(i % 4)`-th boolean from that word (the lower 8 bits correspond to the 0-th boolean and so on),
+* compare it to `kHardenedByteBoolTrue`.
+
+# Terminology Quick Reference
+
+## OpenTitan Logical Entities
+
+*   **Silicon Creator:** The owner of the public keys injected into the OpenTitan device at manufacturing time.
+    Signs the `ROM` and `ROM_EXT` boot stages.
+*   **Silicon Owner:** The individual or group who has taken ownership of the OpenTitan device by adding their public keys during an ownership transfer.
+    Signs all boot stages after `ROM_EXT`.
+
+The Silicon Creator and the Silicon Owner may be the same individual or group, but are not necessarily so.
+The Silicon Owner can change during the lifetime of the device, but the Silicon Creator cannot.
+
+## RISC-V Concepts:
+
+*   **ePMP:** Enhanced Physical Memory Protection unit:
+    [Ibex Physical Memory Protection][ibex-epmp].
+    Can be configured to allow or prevent read (R), write (W) and/or execute (X)
+    access to regions of memory.
+
+## Boot stages:
+
+*   `ROM`: Metal ROM, sometimes known as ROM or Boot ROM.
+*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
+*   `BL0`: Bootloader. Signed by the Silicon Owner.
+*   `Kernel`: Post-bootloader code. Signed by the Silicon Owner.
+
+# Boot Policy {#boot-policy}
+
+In order to provide a flexible boot mechanism the Boot Info page will store a
+structure called the Boot Policy. The boot policy dictates the boot flow,
+including storing boot attempts and successes for a given `ROM_EXT`, allowing
+the ROM code to decide when to mark a `ROM_EXT` good or bad. The boot policy
+also contains directions to `ROM_EXT` about which slot it loads silicon owner
+code from. TODO(gdk): Expand on policy.
+
+# Memory Layout {#memory-layout}
+
+<img src="flash_layout.svg" style="width: 800px;">
+
+Memory on OpenTitan can be considered as split into three separate regions: ROM,
+Flash Info, and addressable flash. The addressable flash is further divided into
+two equally-sized regions called Flash Bank 0 and Flash Bank 1. The beginning
+addresses for Flash Bank 0 and Flash Bank 1 are the only fixed points of
+reference that the system is opinionated about, as they correspond to the
+beginning of each physical flash bank. It is expected that a Silicon Owner might
+arbitrarily reserve space at the end of each flash bank to use as additional
+storage.
+
+# Boot Services {#boot-services}
+
+Boot Services refers to the functionality stored inside of `ROM`/`ROM_EXT` that
+can be controlled via specific messages passed between from Silicon Owner code
+in retention SRAM. Since ROM/`ROM_EXT` is responsible for the boot process and
+is the only software on the device which can manipulate identities belonging to
+the Silicon Owner, these services are required to perform actions such as
+attestation of device identity and firmware update.
+
+## Entering Boot Services
+
+Boot services are invoked by placing a request structure at the beginning of
+retention SRAM and resetting the system with the cause `BOOT_SERVICE_REQUEST`.
+
+## Commands
+
+*   `UNLOCK_OWNERSHIP`: As per [Ownership Transfer][ownership-transfer]'s
+    [Unlock Flow][ot-unlock-flow], relinquish ownership of the device.
+*   `TRANSFER_OWNERSHIP`: Initiate processing of an ownership transfer blob.
+*   `REFRESH_ATTESTATION`: See [Attestation][attestation]. Causes the `ROM_EXT`
+    to regenerate the attestation chain as per the
+    [attestation command][attestation-command] section.
+*   `UPDATE_FIRMWARE`: Instructs the active `ROM_EXT` to begin the firmware
+    update process. This process allows for attempting to boot from a new
+    `ROM_EXT` or silicon owner code with a programmable attempt count that must
+    be satisfied before committing to the new code. This is done by updating the
+    boot policy block. When a kernel wishes to update the other slot in the
+    device it writes the firmware there and then issues an `UPDATE_FIRMWARE`
+    command to instruct the next boot of `ROM_EXT` to attempt to load from that
+    slot.
+
+# Manifest Requirements
+
+This document does not prescribe an exact data structure for the `ROM_EXT` manifest as seen by the ROM.
+For that information, see the [manifest format page][manifest-format].
+However, these are the requirements that the manifest format is required to support:
+
+*   **Hash scheme selection.** The manifest must specify the hashing scheme that
+    covers the `ROM_EXT` slot.
+*   **Signature scheme selection**. The manifest must specify the signature
+    scheme that covers the `ROM_EXT` slot.
+*   **Key derivation constants**. As specified in the
+    [Identities and Root Keys][identities-keys] document, the manifest header
+    must include constants used to derive the next key generation.
+*   **Header version.** The version of the `ROM_EXT`. This version field is used
+    as part of the measurements for key derivations, and can be used as a
+    constraint for the boot policy.
+*   **Rollback protection.** A generation marker that is used by `ROM_EXT` to
+    determine if this version is bootable or is considered a rollback.
+*   **Entrypoint.** The executable entrypoint for this `ROM_EXT` slot.
+
+<!-- TODO: Update with published documents when available. -->
+[attestation]: ../attestation/README.md
+[attestation-command]: ../attestation/README.md#attestation-command
+[ibex-epmp]: https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html
+[identities-keys]: ../identities_and_root_keys/README.md
+[key-manager]: ../../../../hw/ip/keymgr/README.md
+[manifest-format]: ../../../../sw/device/silicon_creator/rom_ext/doc/manifest.md
+[rom-epmp]: ../../../../sw/device/silicon_creator/rom/doc/memory_protection.md
+[otp-mmap]: ../../../../hw/ip/otp_ctrl/README.md#direct-access-memory-map
+[ot-flash]: #
+[ot-unlock-flow]: #
+[ownership-transfer]: ../ownership_transfer/README.md
+[rv-isa-priv]: https://riscv.org/technical/specifications/
+[silicon-creator-keys]: #silicon-creator-keys
diff --git a/doc/security/specs/secure_boot/index.md b/doc/security/specs/secure_boot/index.md
deleted file mode 100644
index a149e55..0000000
--- a/doc/security/specs/secure_boot/index.md
+++ /dev/null
@@ -1,200 +0,0 @@
----
-title: "Secure Boot"
----
-
-<p style="color: red; text-align: right;">
-  Status: Pre-RFC
-</p>
-
-The following overview gives a brief, high-level explanation of OpenTitan's secure boot process.
-The basic guarantee of secure boot is that *no unauthorized code will be executed before the boot process reaches the device owner's code*.
-All executed code must be cryptographically signed by either the owner of the OpenTitan device or the (trusted) entity that originally set up the device at manufacturing time (the "Silicon Creator").
-
-Additionally, the secure boot procedure restricts certain stages to the Silicon Creator, so that even the current device owner can't change them.
-Therefore, if the device changes owners, the new owner only has to trust the Silicon Creator, *not* the previous owner(s).
-
-The diagram below summarizes the specific steps involved in the secure boot process:
-
-<img src="secure_boot_flow.svg" style="width: 800px;">
-
-## Mask ROM {#mask-rom}
-
-The first stage of secure boot is called "Mask ROM".
-Mask ROM is a region of read-only memory that cannot be updated at all after an OpenTitan device is manufactured.
-For that reason, the Mask ROM is kept as simple as possible; it does some minimal setup, authenticates the next stage (`ROM_EXT`), and jumps there.
-
-The Mask ROM contains non-updateable public keys, which it uses to authenticate the `ROM_EXT` signature.
-These public keys correspond to what OpenTitan calls the "Silicon Creator": the entity who was initially involved in the manufacturing of the device.
-It's important to distinguish between the unchanging Silicon Creator and the changeable "Silicon Owner", the entity that owns the device at a given time.
-
-On startup, hardware settings use a feature called ["enhanced Physical Memory Protection" (ePMP)][ibex-epmp] to ensure that only the Mask ROM code itself is executable.
-For details about how the Mask ROM configures ePMP, see [Memory Protection Module][mask-rom-epmp].
-Flash and MMIO memory regions are unlocked for read/write (for signature verification and configuring peripherals) but are not executable at this stage.
-
-On boot, the Mask ROM code does the following:
-1. Initialize any additional ePMP settings beyond what is set by hardware at startup, plus initialize the C runtime and hardware peripherals (e.g. pinmux, UART).
-2. Load the "manifest" for `ROM_EXT`, which includes the start and end address of `ROM_EXT` code, a cryptographic signature, a public key modulus, and "selector bits" for hardware information.
-    * The manifest's modulus field identifies which of the stored [Silicon Creator keys][silicon-creator-keys] should be used for signature verification. If there is no matching stored key, or the matching key is not suitable for the device's lifecycle state, then the boot fails.
-    * There are two slots for `ROM_EXT` implementations; the Mask ROM will first try the one with the newest security version. If the signature verification for that slot fails, the Mask ROM will attempt to boot from the other slot.
-3. Read usage constraints from hardware according to the selector bits from the manifest.
-    * The selector bits can choose all or part of the device ID, and/or information about the state of the device. Because the selected constraints are included in the digest, this means the signer of a `ROM_EXT` image can restrict their signature to only certain devices/states.
-4. Compute the SHA2-256 digest of the selected usage constraints concatenated with the contents of memory between the `ROM_EXT` start and end address given in the manifest.
-5. Check the signature from the manifest against the digest and the selected Silicon Creator public key.
-    * Unlock flash execution, configure ePMP so that the `ROM_EXT` region is executable, and jump to the start of `ROM_EXT`.
-
-## `ROM_EXT` {#rom-ext}
-
-The `ROM_EXT` ("ROM extension") stage is another region of read-only memory that is controlled by the Silicon Creator.
-However, unlike the Mask ROM, it *can* be updated after the device is manufactured, as long as the new version is signed by the Silicon Creator.
-
-Like the Mask ROM, the `ROM_EXT` must check the signature of the next boot stage (this time against the Silicon Owner's keys, rather than the Silicon Creator’s).
-In addition, the `ROM_EXT` initializes Silicon Creator and then Silicon Owner keys using the key manager and performs “boot services”, which are a small set of specific operations that require access to the Silicon Creator's keys (for instance, providing an [attestation of device state][attestation] or [transferring the device to a new owner][ownership-transfer]).
-These services must happen during boot and not afterwards, because after boot we don't have access to the Silicon Creator keys.
-
-The general procedure for the `ROM_EXT` looks something like this:
-1. Load the manifest for BL0 (which has the same format as the `ROM_EXT` manifest described in the Mask ROM procedure).
-2. Read usage constraints from hardware according to the selector bits from the manifest.
-3. Compute the SHA2-256 digest of the selected usage constraints concatenated with the contents of BL0, as Mask ROM does for `ROM_EXT`.
-4. Check the signature from the manifest against the digest and a public code-signing key from the current Silicon Owner (RSA-3072 with the F4 exponent).
-    * Like the Silicon Creator, the Silicon Owner may have multiple public code-signing keys. How many keys exactly is configurable by the Owner.
-    * The number of retries in case verification fails might differ from Mask ROM, and can differ between `ROM_EXT` implementations.
-5. Step the key manager device in order to obtain the Silicon Creator "root key".
-    * The root key mixes secret values provisioned by the Silicon Creator at manufacturing time with information about the specific device, the device health state, and `ROM_EXT`. It will differ between devices and between `ROM_EXT` images.
-    * The actual value of the root key is locked inside the [key manager][key-manager] and hidden from software. The key manager starts with the Silicon Creator's secret values (which are also hidden from all software) and advances its state to a new value with each additional piece of information.
-    * See the [identities and root keys][identities-keys] page for more details about the intermediate, identity, and root keys.
-6. From the Silicon Creator root key, use the key manager interface to derive the Silicon Creator "identity" key (an ECDSA-P256 key used for [attestation][attestation]) and the Silicon Owner "intermediate key" (a new key manager state which can later be used to derive the Silicon Owner's "identity" key).
-    * To create the intermediate key, the key manager starts with the Silicon Creator root key from the Mask ROM stage, and software steps the key manager state by mixing in owner secrets (provisioned by the owner at the time they take ownership of the device) and certain BL0 configuration information.
-    * In between getting the Silicon Creator identity key and the Silicon Owner intermediate key, check if the boot purpose was "attestation"; if so, generate an attestation certificate for the software/hardware state, sign it with the Silicon Creator identity key, and write it to a special region of memory.
-7. Unlock flash execution, configure ePMP so that the Silicon Creator controlled regions of memory are not writable and the BL0 region is executable, and then jump to the start of BL0.
-
-Once the code has jumped into the Silicon Owner code at BL0, secure boot in its simplest form is complete.
-The Silicon Owner may choose to extend the secure boot process with multiple boot stages of their own; this will differ between device owners, while the stages described here are guaranteed by the Silicon Creator and will be shared by all OpenTitan implementations.
-If any signature verification in the above process fails, or there is any kind of unexpected error, the device will fail to boot.
-
-# Silicon Creator Keys {#silicon-creator-keys}
-
-The Silicon Creator has multiple public keys.
-This redundancy partially protects against the scenario in which one of the keys is compromised; any OpenTitan devices produced after the key is known to be compromised can mark the compromised key invalid, without requiring a full new Mask ROM implementation.
-Devices produced before the key is known to be compromised are not protected by this strategy.
-
-Additionally, each key is restricted to one of three "roles", which determine in which device states the key can be used.
-The roles are:
-* dev (development, only for devices in the `DEV` lifecycle state)
-* test (manufacturing and testing, only for devices in the `TEST_UNLOCK` lifecycle state)
-* prod (production, only for devices in the `PROD` or `PROD_END` lifecycle states)
-
-If the key indicated in the manifest has a role that doesn't match the lifecycle state of the device, the boot fails.
-All of these keys are 3072-bit RSA public keys with exponent e=65537 (the “F4 exponent”).
-
-# Terminology Quick Reference
-
-## OpenTitan Logical Entitites
-
-*   **Silicon Creator:** The owner of the public keys injected into the OpenTitan device at manufacturing time.
-    Signs the `Mask ROM` and `ROM_EXT` boot stages.
-*   **Silicon Owner:** The individual or group who has taken ownership of the OpenTitan device by adding their public keys during an ownership transfer.
-    Signs all boot stages after `ROM_EXT`.
-
-The Silicon Creator and the Silicon Owner may be the same individual or group, but are not necessarily so.
-The Silicon Owner can change during the lifetime of the device, but the Silicon Creator cannot.
-
-## RISC-V Concepts:
-
-*   **ePMP:** Enhanced Physical Memory Protection unit:
-    [Ibex Physical Memory Protection][ibex-epmp].
-    Can be configured to allow or prevent read (R), write (W) and/or execute (X)
-    access to regions of memory.
-
-## Boot stages:
-
-*   `Mask ROM`: Metal mask ROM, sometimes known as ROM or Boot ROM.
-*   `ROM_EXT`: ROM Extension. Stored in flash and signed by the Silicon Creator.
-*   `BL0`: Bootloader. Signed by the Silicon Owner.
-*   `Kernel`: Post-bootloader code. Signed by the Silicon Owner.
-
-# Boot Policy {#boot-policy}
-
-In order to provide a flexible boot mechanism the Boot Info page will store a
-structure called the Boot Policy. The boot policy dictates the boot flow,
-including storing boot attempts and successes for a given `ROM_EXT`, allowing
-the ROM code to decide when to mark a `ROM_EXT` good or bad. The boot policy
-also contains directions to `ROM_EXT` about which slot it loads silicon owner
-code from. TODO(gdk): Expand on policy.
-
-# Memory Layout {#memory-layout}
-
-<img src="flash_layout.svg" style="width: 800px;">
-
-Memory on OpenTitan can be considered as split into three separate regions: ROM,
-Flash Info, and addressable flash. The addressable flash is further divided into
-two equally-sized regions called Flash Bank 0 and Flash Bank 1. The beginning
-addresses for Flash Bank 0 and Flash Bank 1 are the only fixed points of
-reference that the system is opinionated about, as they correspond to the
-beginning of each physical flash bank. It is expected that a Silicon Owner might
-arbitrarily reserve space at the end of each flash bank to use as additional
-storage.
-
-# Boot Services {#boot-services}
-
-Boot Services refers to the functionality stored inside of `ROM`/`ROM_EXT` that
-can be controlled via specific messages passed between from Silicon Owner code
-in retention SRAM. Since ROM/`ROM_EXT` is responsible for the boot process and
-is the only software on the device which can manipulate identities belonging to
-the Silicon Owner, these services are required to perform actions such as
-attestation of device identity and firmware update.
-
-## Entering Boot Services
-
-Boot services are invoked by placing a request structure at the beginning of
-retention SRAM and resetting the system with the cause `BOOT_SERVICE_REQUEST`.
-
-## Commands
-
-*   `UNLOCK_OWNERSHIP`: As per [Ownership Transfer][ownership-transfer]'s
-    [Unlock Flow][ot-unlock-flow], relinquish ownership of the device.
-*   `TRANSFER_OWNERSHIP`: Initiate processing of an ownership transfer blob.
-*   `REFRESH_ATTESTATION`: See [Attestation][attestation]. Causes the `ROM_EXT`
-    to regenerate the attestation chain as per the
-    [attestation command][attestation-command] section.
-*   `UPDATE_FIRMWARE`: Instructs the active `ROM_EXT` to begin the firmware
-    update process. This process allows for attempting to boot from a new
-    `ROM_EXT` or silicon owner code with a programmable attempt count that must
-    be satisfied before committing to the new code. This is done by updating the
-    boot policy block. When a kernel wishes to update the other slot in the
-    device it writes the firmware there and then issues an `UPDATE_FIRMWARE`
-    command to instruct the next boot of `ROM_EXT` to attempt to load from that
-    slot.
-
-# Manifest Requirements
-
-This document does not prescribe an exact data structure for the `ROM_EXT` manifest as seen by the Mask ROM.
-For that information, see the [manifest format page][manifest-format].
-However, these are the requirements that the manifest format is required to support:
-
-*   **Hash scheme selection.** The manifest must specify the hashing scheme that
-    covers the `ROM_EXT` slot.
-*   **Signature scheme selection**. The manifest must specify the signature
-    scheme that covers the `ROM_EXT` slot.
-*   **Key derivation constants**. As specified in the
-    [Identities and Root Keys][identities-keys] document, the manifest header
-    must include constants used to derive the next key generation.
-*   **Header version.** The version of the `ROM_EXT`. This version field is used
-    as part of the measurements for key derivations, and can be used as a
-    constraint for the boot policy.
-*   **Rollback protection.** A generation marker that is used by `ROM_EXT` to
-    determine if this version is bootable or is considered a rollback.
-*   **Entrypoint.** The executable entrypoint for this `ROM_EXT` slot.
-
-<!-- TODO: Update with published documents when available. -->
-[attestation]: {{< relref "/doc/security/specs/attestation" >}}
-[attestation-command]: {{< relref "/doc/security/specs/attestation" >}}#attestation-command
-[ibex-epmp]: https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html
-[identities-keys]: {{< relref "/doc/security/specs/identities_and_root_keys" >}}
-[key-manager]: {{< relref "/hw/ip/keymgr/doc" >}}
-[manifest-format]: {{< relref "/sw/device/silicon_creator/rom_ext/docs/manifest" >}}
-[mask-rom-epmp]: {{< relref "/sw/device/silicon_creator/mask_rom/docs/memory_protection" >}}
-[ot-flash]: #
-[ot-unlock-flow]: #
-[ownership-transfer]: {{< relref "/doc/security/specs/ownership_transfer" >}}
-[rv-isa-priv]: https://riscv.org/technical/specifications/
-[silicon-creator-keys]: {{< relref "#silicon-creator-keys" >}}
diff --git a/doc/security/threat_model/README.md b/doc/security/threat_model/README.md
new file mode 100644
index 0000000..d4dbc27
--- /dev/null
+++ b/doc/security/threat_model/README.md
@@ -0,0 +1,92 @@
+# Lightweight Threat Model
+
+This threat model aims to identify in-scope design assets, attacker profiles,
+attack methods and attack surfaces. This is used to produce security
+requirements and implementation guidelines that cover hardware, software and the
+design process.
+
+The threat model is considered for discrete and integrated instances of
+OpenTitan which may include external non-volatile memory.
+
+**Assets**
+
+*   Secrets and configuration parameters stored in the device or on external
+    memory:
+    *   Private asymmetric keys (integrity and confidentiality)
+    *   Symmetric keys (integrity and confidentiality)
+    *   Anti-rollback and other counters (integrity)
+    *   Unique identifiers and data (integrity, authenticity and partly
+        confidentiality)
+    *   Data requiring integrity and/or confidentiality
+*   Secrets stored outside the device (e.g. in a backend system), but that are
+    used to support critical functionality or lifecycle operations
+*   Integrity and authorization of cryptographic operations
+*   Integrity and authenticity of the code running on the device
+*   Integrity and authenticity of stored data
+*   Confidentiality of selected code and data
+*   Quality of the entropy source used in cryptographic operations and that used
+    in countermeasures
+*   Correct operation and confidentiality, integrity, authenticity of
+    cryptographic services.
+
+**Attacker Profile**
+
+*   A bad actor with physical access either during manufacturing or active
+    deployment
+*   Malicious device owners: *e.g.* attacking one device to develop template
+    attacks
+*   Malicious actors with remote device access: *e.g.* performing remote
+    measurements of side channels that expose assets
+
+**Attack Surface**
+
+*   Chip surface
+    *   Electrical stimulation and/or measurements
+    *   Energy and particle exposure
+    *   Inspection and reverse engineering
+    *   Physical manipulation
+*   Operational environment
+    *   Temperature
+    *   Power
+*   Peripheral interfaces
+*   Protocols and APIs at the boundary of the device
+*   Test and debug interfaces
+*   Clock, power, reset lines
+*   Documentation and design data
+
+**Attack Methods**
+
+*Logical Attacks*
+
+*   Software vulnerabilities (e.g. bugs) exercised through interfaces or code
+    that can be run by the attacker on the device
+*   Compromise of secure boot
+*   Insider compromise of the personalization process
+*   Cryptanalysis
+*   Silicon Creator or Silicon Owner impersonation
+*   Compromise through misuse of test or debug functionality
+
+*Non-Volatile Memory Attacks*
+
+*   Firmware downgrade
+*   Data rollback
+*   Data/Command playback
+
+*Physical Access Attacks*
+
+*   Side Channel Analysis (SCA): Passive measurement attacks where the device is
+    operated normally. The attacker extracts the assets (*e.g.* secrets) via
+    observation of side channels, such as: 1) execution time, 2) power
+    consumption, 3) electromagnetic radiation.
+*   Fault Injection (FI): Fault Injection is a class of active attacks where the
+    device’s input and/or operation conditions are manipulated to make the
+    device operate outside its specification. An attacker modifies device
+    behavior (data and/or operations) by manipulating the internal electrical
+    state of the device (voltage, current, charge) through disturbances such as
+    voltage/current, EM, lasers, or body-bias injection.
+*   Any combination of passive and active physical attacks.
+
+## Read More
+
+*   [Use Cases](../../use_cases/README.md)
+*   [OpenTitan Security](../README.md)
diff --git a/doc/security/use_cases/index.md b/doc/security/use_cases/index.md
deleted file mode 100644
index 143246e..0000000
--- a/doc/security/use_cases/index.md
+++ /dev/null
@@ -1,239 +0,0 @@
----
-title: "OpenTitan Use Cases"
----
-
-## Overview
-
-OpenTitan's mission is to raise the security bar industry wide by implementing a
-transparent, logically secure hardware root of trust with wide application.
-
-This document describes some of those use cases for OpenTitan. These range from
-data center integrations, to embedded security applications such as security
-keys and smart cards. References to relevant specifications and certification
-targets are noted where use cases are backed by industry standards.
-
-## Platform Integrity Module
-
-An OpenTitan IC used as a Platform Integrity Module interposes between a
-platform's boot flash and its main boot devices such as the Baseboard Management
-Controller (BMC), the Platform Controller Hub (PCH) and the CPU.
-
-<img src="use_cases_fig1.svg" alt="Fig1" style="width: 300px;"/>
-
-Figure 1: Platform Integrity Module
-
-The Platform Integrity Module use case implements the following security
-properties:
-
-*   Measure integrity of first boot firmware stages before bringing the boot
-    devices out of reset accessing boot flash via SPI or similar interface.
-*   Monitor resets and heartbeat of downstream boot devices. Monitoring tasks
-    are handled by OpenTitan as Interrupt Service Routines (ISRs), and are not
-    expected to operate under real time constraints.
-*   Enforce runtime boot device access policies, and manage A/B firmware updates
-    for software stored in boot flash. The OpenTitan to boot device interface is
-    implemented on SPI or a similar interface.
-*   Provides root key store and attestation flows as part of the platform
-    integrity secure boot implementation.
-
-### Minimum Crypto Algorithm Requirements
-
-The current target for all crypto is at least 128-bit security strength. This is
-subject to change based on the implementation timeline of any given
-instantiation of OpenTitan. It is expected that a future implementation may be
-required to target a minimum of 192-bit or 256-bit security strength.
-
-*   TRNG: NIST SP 800-90B compliant entropy source.
-*   DRBG: NIST SP 800-90A compliant DRBG.
-*   Hash Algorithms:
-    *   SHA256: An approved hash algorithm with approximately the same security
-        strength as its strongest asymmetric algorithm.
-*   Asymmetric Key Algorithms:
-    *   RSA-3072: Secure boot signature verification.
-    *   ECDSA P-256: Signature and verification for identity and attestation
-        keys.
-*   Symmetric Key Algorithms:
-    *   HMAC-SHA256: NIST FIPS 180-4 compliant. Used in integrity measurements
-        for storage and in transit data as well as secure boot.
-    *   AES: AES-CTR NIST 800-38A. Used to wrap keys and encrypt data stored in
-        internal flash.
-
-### Provisioning Requirements
-
-Provisioning an OpenTitan device is performed in two steps:
-
-*   Device Personalization: The device is initialized with a unique
-    cryptographic identity endorsed by a Transit PKI which is only used to
-    support initial Ownership Transfer.
-*   Ownership Transfer: Ownership is assigned to a user that has the ability to
-    run software on the device. As Silicon Owner, the user can generate a
-    cryptographic identity strongly associated to the hardware and the software
-    version running on the device.
-
-OpenTitan used as a Platform Integrity Module has the following provisioning
-requirements:
-
-*   Unique Global Identifier: Non-Cryptographic big integer value (up to 256b)
-    used to facilitate tracking of the devices throughout their life cycle. The
-    identifier is stored in One Time Programmable (OTP) storage during
-    manufacturing.
-*   Hardware Cryptographic Identity: Symmetric and asymmetric keys associated
-    with the hardware, used to attest the authenticity of the chip and also as a
-    component of the Owner Cryptographic Identity. These keys are generated
-    inside the device by the secure manufacturing process.
-*   Hardware Transport Certificate: Used to endorse the asymmetric hardware
-    identity with a transit PKI trusted by the Silicon Owner at Ownership
-    Transfer time.
-*   Factory Firmware: Baseline image with support for firmware update and
-    Ownership Transfer. Firmware update may be actuated by writing an OpenTitan
-    update payload to boot flash. Upon reset, OpenTitan scans the boot flash
-    device for valid updates. The factory image may not be owned by the Silicon
-    Owner and its main purpose is to assist Ownership Transfer.
-*   Owner Cryptographic Identity: The Silicon Owner is required to generate an
-    identity as part of the Ownership transfer flow. Owner identities are bound
-    to the Hardware Identity and the software version running on the device.
-    Owner identities are used in Silicon Ownership attestation flows and as a
-    root component of Application keys.
-*   Application Keys: Keys bound to the owner identity and the application
-    version running on the device. Application keys are provisioned in most
-    cases when the application runs for the first time. The purpose of each key
-    is configured at the application layer and enforced by the kernel.
-
-### Performance Requirements
-
-Performance requirements are derived from integration-specific requirements. The
-following performance requirements are presented for illustration purposes:
-
-*   Boot Time: Maximum time allowed to get to device kernel serving state from
-    cold reset.
-*   Resume Time: Maximum time allowed to get to device kernel serving state from
-    sleep.
-*   External Flash Verification Time: Maximum time allowed for verification of
-    external flash as part of platform boot verified boot implementation.
-    Defined in milliseconds for a given flash partition size.
-
-### Packaging Constraints
-
-*   Non-HDI packaging is required.
-*   (Proposed) Device packaging QR code with device ID linkable to manufacturing
-    data.
-
-### Additional Requirements
-
-#### Memory Requirements
-
-*   At least 512KB of flash storage with 2 partitions, 4KB page size, 100K
-    endurance cycles. 1MB flash would be ideal to allow for future code size
-    growth.
-*   At least 16KB of isolated flash storage for manufacturing and device life
-    cycle operations.
-*   At least 8KB of OTP for manufacturing and device life cycle operations.
-*   At least 64KB of SRAM. 128KB would be ideal for future growth.
-
-#### External Peripherals
-
-The following list of peripheral requirements is speculative at the moment and
-subject to change based on platform integration requirements:
-
-*   SPI Host/Device:
-    *   Dual support. Quad support needs to be evaluated.
-    *   Required features for EEPROM mode:
-        -   Passthrough boot flash interface with support for EEPROM command
-            handling/filtering.
-        -   Access to on-die ram and flash memory regions.
-        -   Mailbox interface with support for custom opcode commands.
-*   UART: Debug console interface. May be disabled by production firmware.
-*   GPIO: Reset control and monitoring. Status signals.
-*   I2C interface compatible with SMBus interfaces.
-
-## Universal 2nd-Factor Security Key
-
-When used as a security key, OpenTitan implements the Universal 2nd Factor (U2F)
-authentication standard, using a Universal Serial Bus (USB) 1.1 interface to
-communicate with host devices. U2F requires the implementation of a
-challenge-response authentication protocol based on public key cryptography. The
-security key is provisioned with a unique identity in the form of an asymmetric
-key, which may be self-endorsed by a certificate issued at manufacturing time.
-
-When used as a security key, OpenTitan shall meet the FIDO Authenticator
-security goals and measures described in the [FIDO Security Reference v1.2][1]
-specification. See [Universal 2nd Factor (U2F) Overview v1.2][2] for more
-details on the functional requirements of this use case.
-
-### Certification Requirements
-
-*   [BSI-PP-CC-0096-V3-2018][3] FIDO Universal Second Factor (U2F)
-    Authenticator. The minimum assurance level for this Protection Profile (PP)
-    is EAL4 augmented. This PP supports composite certification on top of the
-    Security IC Platform Protection Profile with Augmentation Packages,
-    BSI-CC-PP-0084-2014 (referred to as PP84).
-*   [FIPS 140-2 L1 + L3 physical][4] certification is required for some use
-    cases.
-
-### Minimum Crypto Algorithm Requirements
-
-The current target for all crypto is at least 128-bit security strength. This is
-subject to change based on the implementation timeline of any given
-instantiation of OpenTitan. It is expected that a future implementation may be
-required to target a minimum of 192-bit or 256-bit security strength.
-
-*   TRNG:
-    *   Entropy source for ECDSA keypair generation (seed and nonce).
-    *   (optional) Symmetric MAC key generation.
-*   Asymmetric Key Algorithms:
-    *   ECDSA: Signature and verification on NIST P-256 curve for identity and
-        attestation keys.
-    *   RSA-3072: Secure boot signature verification. Used to verify the
-        signature of the device's firmware.
-*   Symmetric Key Algorithms:
-    *   AES-CTR:
-        -   (optional) Used to wrap a user private key in a key handle.
-            Implementation dependent.
-    *   HMAC-SHA256:
-        -   For application key handle generation.
-*   Hash Algorithms:
-    *   SHA-256:
-        -   Code and hardware measurements used in internal secure boot
-            implementation.
-        -   (optional) For key handle generation. Implementation dependent.
-        -   (optional) Attestation cert generation, if generated on the fly.
-
-### Provisioning Requirements
-
-OpenTitan used as a security key has the following provisioning requirements:
-
-*   Unique Global Identifier: Non-Cryptographic big integer value (up to 256b)
-    used to facilitate tracking of the devices throughout their life cycle. The
-    identifier is stored in One Time Programmable (OTP) storage during
-    manufacturing.
-*   Attestation Key: Unique cryptographic identity used for attestation
-    purposes.
-*   Self-Signed Attestation Certificate: Self signed certificate and extracted
-    at manufacturing time for registration purposes. U2F backend servers can
-    create an allow-list of certificates reported by the secure key
-    manufacturer, and use them to perform authenticity checks as part of the
-    registration flow.
-*   Factory Firmware: Baseline image with support for firmware update via USB,
-    and the USB HID U2F command spec.
-
-### Additional Requirements
-
-*   Physical Presence GPIO: U2F requires physical user presence checks for
-    registration and authentication flows. This is implemented either via a push
-    button or capacitive touch sensor connected to an input GPIO pin.
-    *   At least 2 PWM peripherals can facilitate implementation of capacitive
-        touch sensor IO operations.
-*   Status LEDs GPIO: The security key may use LEDs to provide feedback to the
-    user. This requires up to 4 additional output GPIO pins.
-*   USB HID U2F Stack: The security key communicates with host devices via a USB
-    HID protocol. OpenTitan shall meet the USB 1.1 connectivity and protocol
-    requirements to interface with the host.
-
-### Relevant specs
-
-https://fidoalliance.org/specifications/download/
-
-[1]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-security-ref-v1.2-ps-20170411.html
-[2]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html
-[3]: https://www.commoncriteriaportal.org/files/ppfiles/pp0096V3b_pdf.pdf
-[4]: https://en.wikipedia.org/wiki/FIPS_140-2#Security_levels
\ No newline at end of file
diff --git a/doc/ug/_index.md b/doc/ug/_index.md
deleted file mode 100644
index cfffe77..0000000
--- a/doc/ug/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "User Guides"
----
-
-{{% sectionContent %}}
diff --git a/doc/ug/design.md b/doc/ug/design.md
deleted file mode 100644
index 61495e5..0000000
--- a/doc/ug/design.md
+++ /dev/null
@@ -1,255 +0,0 @@
----
-title: "Design Methodology within OpenTitan"
----
-
-The design methodology within OpenTitan combines the challenges of industry-strength design methodologies with open source ambitions.
-When in conflict, quality must win, and thus we aim to create a final design product that is equal to the quality required from a full production silicon chip tapeout.
-
-## Language and Tool Selection
-
-Starting with the language, the strategy is to use the SystemVerilog language, restricted to a feature set described by the lowRISC
-[Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
-All IP should be developed and delivered under the feature set described by this style guide.
-Inconsistencies or lack of clarity within the style guide should be solved by filing and helping close an issue on the style guide in the
-[lowrisc/style-guides GitHub repo](https://github.com/lowRISC/style-guides).
-Note that when developing OpenTitan security IP, designers should additionally follow the [Secure Hardware Design Guidelines]({{< relref "doc/security/implementation_guidelines/hardware" >}}).
-
-For professional tooling, the team has chosen several industry-grade tools for its design signoff process.
-Wherever possible we attempt to remain tool-agnostic, but we must choose a selection of tools as our ground truth for our own confidence of signoff-level assurances.
-As a project we promote other open source methodologies and work towards a future where these are signoff-grade as well.
-The discussions on how the design tools are used and which ones are chosen are given below in separate sections.
-
-## 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]({{< 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.
-
-TODO: briefly discuss key architectural decisions, and how we came to the conclusion, with pointers to more thorough documentation. Some candidates:
-*   Processor/RISC-V strategy
-*   Bus strategy
-*   Reset strategy
-
-## Defining Design Complete: stages and tracking
-
-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]({{< relref "doc/project/development_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
-
-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 [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.
-To automate the process a script [build_docs.py]({{< relref "doc/ug/documentation" >}}) is provided for generating the documentation.
-
-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 used by `build_docs.py` to 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]({{< 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]({{< 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]({{< relref "doc/rm/comportability_specification" >}}).
-
-## Linting Methodology
-
-Linting is a productivity tool for designers to quickly find typos and bugs at the time when the RTL is written.
-Capturing fast and efficient feedback on syntactic and semantic (as well as style) content early in the process proves to be useful for high quality as well as consistent usage of the language.
-Running lint is especially useful with SystemVerilog, a weakly-typed language, unlike more modern hardware description languages.
-Running lint is faster than running a simulation.
-
-### Semantic Linting using Verilator (Open Source)
-
-The Verilator tool is open source, thus enabling all project contributors to conveniently download, install and run the tool locally as described [in the installation instructions]({{< relref "doc/getting_started/setup_verilator" >}}), without the need for buying a lint tool license.
-
-For developers of design IP, the recommendation is thus to set up the Verilator lint flow for their IP as described in the [Lint Flow README]({{< relref "hw/lint/doc" >}}).
-Developers should run their code through the Verilator lint tool before creating a design pull request.
-Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
-These waivers have to be reviewed as part of the pull request review process.
-
-Note that a pull request cannot be merged if it is not lint-clean, since the continuous integration infrastructure will run Verilator lint on each pull request.
-
-### Style Linting using Verible (Open Source)
-
-To complement the Verilator lint explained above, we also leverage the Verible style linter, which captures different aspects of the code and detects style elements that are in violation of our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
-
-The tool is open source and freely available on the [Verible GitHub page](https://github.com/google/verible/).
-Hence, we recommend IP designers install the tool as described [here]({{< relref "doc/getting_started#step-6a-install-verible-optional" >}}) and in the [Lint Flow README]({{< relref "hw/lint/doc" >}}), and use the flow locally to close the errors and warnings.
-
-Developers should run their code through the Verible style lint tool before creating a design pull request.
-Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
-These waivers have to be reviewed as part of the pull request review process.
-
-Note that a pull request cannot be merged if it is not lint-clean, since the continuous integration infrastructure will run Verible lint on each pull request.
-
-### Semantic Linting using AscentLint (Sign-Off)
-
-The rule set and capabilities of commercial tools currently still go beyond what open-source tools can provide.
-Hence, we have standardized on the [AscentLint](https://www.realintent.com/rtl-linting-ascent-lint/) tool from RealIntent for sign-off.
-This tool exhibits fast run-times and a comprehensive set of rules that provide concise error and warning messages.
-
-The sign-off lint flow leverages a new lint rule policy named _"lowRISC Lint Rules"_ that has been tailored towards our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md).
-The lint flow run scripts and waiver files are available in the GitHub repository of this project but, due to the proprietary nature of the lint rules and their configuration, the _"lowRISC Lint Rules"_ lint policy file can not be publicly provided.
-However, the _"lowRISC Lint Rules"_ are available as part of the default policies in AscentLint release 2019.A.p3 or newer (as `LRLR-v1.0.policy`).
-This allows designers with access to this tool to run the lint flow locally on their premises.
-
-If developers of design IP have access to AscentLint, we recommend to set up the AscentLint flow for their IP as described in the [Lint Flow README]({{< relref "hw/lint/doc" >}}), and use the flow locally to close the errors and warnings.
-Linting errors and warnings can be closed by fixing the code in question (preferred), or waiving the error.
-These waivers have to be reviewed as part of the pull request review process.
-
-Note that our continuous integration infrastructure does not currently run AscentLint on each pull request as it does with Verilator lint.
-However, all designs with enabled AscentLint targets on the master branch will be run through the tool in eight-hour intervals and the results are published as part of the tool dashboards on the [hardware IP overview page](https://docs.opentitan.org/hw), enabling designers to close the lint errors and warnings even if they cannot run the sign-off tool locally.
-
-Goals for sign-off linting closure per design milestone are given in the [OpenTitan Development Stages]({{< relref "doc/project/development_stages" >}}) document.
-
-Note that cases may occur where the open-source and the sign-off lint tools both output a warning/error that is difficult to fix in RTL in a way that satisfies both tools at the same time.
-In those cases, priority shall be given to the RTL fix that satisfies the sign-off lint tool, and the open-source tool message shall be waived.
-
-## Assertion Methodology
-
-The creation and maintenance of assertions within RTL design code is an essential way to get feedback if a design is being used improperly.
-Common examples include asserting that a full FIFO should never be written to, a state machine doesn't receive an input while in a particular state, or two signals should remain mutually exclusive.
-Often these will eventually result in a downstream error (incorrect data, bus collisions, etc.), but early feedback at the first point of inconsistency gives designers and verifiers alike fast access to easier debug.
-
-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]({{< relref "hw/formal/doc" >}}).
-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.
-
-## CDC Methodology
-
-Logic designs that have signals that cross from one clock domain to another unrelated clock domain are notorious for introducing hard to debug problems.
-The reason is that design verification, with its constant and idealized timing relationships on signals, does not represent the variability and uncertainty of real world systems.
-For this reason, maintaining a robust Clock Domain Crossing verification strategy ("CDC methodology") is critical to the success of any multi-clock design.
-
-Our general strategy is threefold:
-maintain a list of proven domain crossing submodules;
-enforce the usage of these submodules;
-use a production-worthy tool to check all signals within the design conform to correct crossing rules.
-The *CDC Methodology document* (TODO:Coming Soon) gives details on the submodules and explains more rationale for the designs chosen.
-
-The tool chosen for this program is not finalized.
-We will choose a sign-off-grade CDC checking tool that provides the features needed for CDC assurance.
-It is understandable that not all partner members will have access to the tool.
-Once chosen, the project will use it as its sign-off tool, and results will be shared in some form (TODO: final decision).
-CDC checking errors can be closed by fixing the code in question (preferred), or waiving the error.
-CDC waivers should be reviewed as part of the pull request review process.
-Details on how to run the tool will be provided once the decision has been finalized.
-
-The team will standardize on a suite of clock-crossing modules that can be used for most multi-clock designs.
-Many of those will be documented in the `hw/ip/prim/doc` directory.
-
-Similar to the linting tool, due to the proprietary nature of the CDC tool, it is possible that not all content towards running the tool will be checked in in the open source repository.
-For those items, we will work with the tool provider to allow other partners to also use the tool.
-When this methodology is finalized the details will be given here. (TODO)
-
-## DFT
-
-Design For Testability is another critical part of any design methodology.
-It is the preparation of a design for a successful manufacturing test regime.
-This includes, but is not limited to, the ability to use scan chains for testing digital logic;
-the optimization of design logic to allow maximum access of test logic for fault coverage;
-the ability to observe and control memory cells and other storage macros;
-the control of analog designs and other items that are often outside the reach of test logic;
-built in self test (BIST) insertion for logic and memories.
-In this context, our primary concern at this stage is what impact does this have on the RTL that makes up the IP in our library.
-
-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 later in this 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.
-
-At this time the DFT methodology for OpenTitan is not finalized.
-The expectation is that the RTL collateral will undergo a DFT introduction -
-likely with the propagation of such signals as `testmode`, `scanmode`, `bistmode`, etc -
-at a stage before final project completion.
-At this point there are a few references to such signals but they are not yet built into a coherent whole.
-At that future time the DFT considerations will be fully documented and carried out throughout all IP.
-
-## Generated Code
-
-The OpenTitan project contains a lot of generated code through a variety of methods.
-Most modern SystemVerilog-based projects work around the weaknesses in the language in such a way.
-But our first goal is to take full advantage of the language as much as possible, and only resort to generated code where necessary.
-
-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 an over-arching 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]({{< relref "doc/rm/register_tool" >}}))
-are checked in.
-There is an over-arching 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.
-
-## Automatic SV Code Formatting using Verible (Open Source)
-
-The open source Verible tool used for [style linting]({{< relref "#style-linting-using-verible-open-source" >}}) also supports an automatic code formatting mode for SystemVerilog.
-The formatter follows our [Verilog Style Guide](https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md) and helps reducing manual code alignment steps.
-
-Note that this formatter is still under development and not entirely production ready yet due to some remaining formatting bugs and discrepancies - hence automatic code formatting is not enforced in CI at this point.
-However, the tool is mature enough for manual use on individual files (i.e., certain edits may have to be manually amended after using it).
-
-The tool is open source and freely available on the [Verible GitHub page](https://github.com/google/verible/).
-Hence, we encourage IP designers to install the tool as described [here]({{< relref "doc/getting_started#step-6a-install-verible-optional" >}}), and run their code through the formatter tool before creating a design pull request.
-
-The tool can be invoked on specific SystemVerilog files with the following command:
-```shell
-util/verible-format.py --inplace --files <path to SV files>
-
-```
-This is an in-place operation, hence it is recommended to commit all changes before invoking the formatter.
-
-Note that the formatter only edits whitespace.
-The tool performs an equivalency check before emitting the reformatted code to ensure that no errors are introduced.
-
-## Getting Started Designing Hardware
-
-The process for getting started with a design involves many steps, including getting clarity on its purpose, its feature set, authorship, documentation, etc.
-These are discussed in the [Getting Started Designing Hardware]({{< relref "hw_design.md" >}}) document.
-
-## FPGA vs Silicon
-
-One output of the OpenTitan project will be silicon instantiations of hardware functionality described in this open source repository.
-The RTL repository defines design functionality at a level satisfactory to prove the hardware and software functionality in an FPGA (see [user guides]({{< relref "doc/ug" >}})).
-That level is so-called "tapeout ready".
-Once the project reaches that milestone, the team will work with a vendor or vendors to ensure a trustworthy, industry-quality, fully functional OpenTitan chip is manufactured.
-
-It is important that any IP that *can* be open *is* open to ensure maximal trustworthiness and transparency of the final devices.
-
-To that end, OpenTitan will define compliance collateral that ensures correctness - that the FPGA and the eventual silicon work the same.
-Due to fundamental economic and technical limitations, there may, and likely will, be differences between these incarnations.
-Some examples include the following:
-
-* Silicon versions by definition use different technologies for fundamental vendor collateral, including memories, analog designs, pads, and standard cells.
-* Some of the silicon collateral is beyond the so-called "foundry boundry" and not available for open sourcing.
-
-Some IP blocks will undergo hardening of designs to protect them against physical attack to meet security and certification requirements.
-Some of this hardening, for instance in fuses, may be of necessity proprietary.
-These changes will not impact the functionality of the design, but are described in processes unique to an ASIC flow vs. the emulated flow of an FPGA.
-
-Even with these differences, the overriding objective is compliance equivalence between the FPGA and silicon versions.
-This may require instantiation-specific differences in the software implementation of the compliance suite.
-
-Consider the embedded flash macro.
-This design is highly dependent upon the silicon technology node.
-In the open source repository, the embedded flash macro is emulated by a model that approximates the timing one would typically find in silicon.
-It lacks the myriad timing knobs and configuration points required to control the final flash block.
-This necessitates that the compliance suite will have initialization sections for flash that differ between FPGA and silicon.
-
-We consider this demonstration of "security equivalence" to be an open, unsolved problem and are committed to clearly delimiting any differences in the compliance suite implementation.
diff --git a/doc/ug/directory_structure.md b/doc/ug/directory_structure.md
deleted file mode 100644
index cf59203..0000000
--- a/doc/ug/directory_structure.md
+++ /dev/null
@@ -1,142 +0,0 @@
----
-title: "Directory Structure"
----
-
-This document provides an overview of the opentitan repository directory structure.
-The hierarchy underneath the root is fairly self explanatory, containing the following:
-* `doc`: High level documentatation, user guides and reference manuals
-* `util`: Helper scripts and generator tools
-* `hw`: Design and DV sources
-* `sw`: All software sources used in the project
-
-We will focus on the directory structure underneath `hw` and `sw` below.
-
-## Directory structure underneath `hw`
-```
-hw
-├──dv             => Common / shared resources for SV/UVM as well as
-│                    Verilator based testbenches
-│
-├──formal         => Scripts to build and run formal property verification
-│                    (FPV) for all applicable IPs to ensure protocol
-│                    compliance
-│
-├──ip             => Standalone or generic / parameterized implementations
-│                    of comportable IPs designed towards building SoCs
-│
-├──lint           => Scripts to run the `lint` tool on all RTL sources
-│
-├──top_earlgrey   => An implementation of OpenTitan SoC built using above
-│                    IPs as well as third-party 'vendored' IPs
-│
-├──vendor         => Vendored-in open source IPs from external repositories
-```
-
-### `hw/ip`
-```
-   ip
-   ├──uart        => UART IP root dir
-   │  │
-   │  ├──data     => Configuration data sources for design, DV and project
-   │  │              status tracking
-   │  │
-   │  ├──doc      => All documentation sources including design specification
-   │  │              and DV document
-   │  │
-   │  ├──dv       => SV/UVM testbench sources
-   │  │
-   │  ├──fpv      => Testbench sources used in FPV (if applicable)
-   │  │
-   │  ├──model    => Reference 'model' implementation in C (if applicable)
-   │  │
-   │  ├──pre_dv   => This is a work area for experimental DV work that is worthy of
-   │  │              maintaining, but not intended to apply towards code coverage.
-   │  │
-   │  ├──rtl      => RTL design sources
-   │  │
-   │  ├──util     => IP-specfic automation scripts (if applicable)
-   │  │
-   │  ├──...      => Additional sub-directories could exist for specific IPs
-   │                 based on need
-   │
-   ├──...         => More such Comportable IPs...
-```
-
-### `hw/top_earlgrey`
-```
-   top_earlgrey         => Chip root dir
-   │
-   ├──data              => Configuration data sources for design, DV and
-   │                       project status tracking
-   │
-   ├──doc               => All documentation sources including chip
-   │                       specification and DV document
-   │
-   ├──dv                => Chip level SV/UVM testbench sources
-   │  └──autogen        => auto-generated chip DV sources
-   │
-   ├──ip                => IPs tailor-made for top_earlgrey
-   │  │
-   │  ├──xbar           => XBAR implementation for top_earlgrey
-   │  │  ├──dv          => DV sources
-   │  │  │  └──autogen  => auto-generated XBAR DV sources
-   │  │  └──rtl         => RTL sources
-   │  │     └──autogen  => Auto-generated XBAR RTL sources
-   │  │
-   │  ├──...            => More such IPs tailored for top_earlgrey...
-   │
-   ├──rtl               => Chip level RTL sources
-   │  └──autogen        => auto-generated chip RTL sources
-   │
-   ├──sw                => Auto-generated chip-specific headers for SW
-   │
-   └──util              => Chip-specfic automation scripts
-```
-
-### Auto-generated sources: checked-in
-In cases where we rely on automatic generation of RTL, DV, or software sources we currently check those files in to the repository.
-This is primarily motivated by a desire to make it easy for engineers to rapidly test spot-fixes.
-This is a decision that might be revisited in the future.
-
-#### Mitigating issues
-Auto-generated sources can get out-of-sync if the underlying tools or templates are updated.
-Also, users may accidently make modifications to those files by hand, which will cease to exist the next time the tools are run.
-We employ the following methods to mitigate these risks:
-* Add a CI check when a pull request is made to merge new changes to ensure that the checked-in file and the generator output are enquivalent and not out-of-sync
-* Put auto-generated sources under a dedicated `autogen/` directory
-* Add a warning message banner as a comment clearly indicating that the file has been auto-generated with the complete command
-
-## Directory structure underneath `sw`
-```
-sw
-├──device                 => Sources compiled for the OpenTitan chip,
-│  │                         including tests run on FPGA and simulations
-│  │ 
-│  ├──benchmarks          => Standard benchmarks and instructions for running
-│  │   └──coremark           them
-│  │ 
-│  ├──examples            => Example programs to demonstrate basic
-│  │   ├──hello_world        functionality
-│  │   ├──...
-│  │ 
-│  ├──exts                => Sources that are specific to the intended target
-│  │   │                     (FPGA, Verilator, DV, firmware)
-│  │   └──common          => Common sources for all SW tests including the CRT
-│  │                         source and the linker script
-│  │ 
-│  ├──lib                 => SW libraries, including device interface functions
-│  │                         (DIFs), testutils, test boot ROM (test_rom), the
-│  │                         OTTF, and other base APIs for controlling the
-│  │                         hardware
-│  │ 
-│  └──tests               => SW tests implemented on FPGA/Verilator/DV targets
-│     ├──flash_ctrl
-│     ├──...
-│   
-├──host                   => Sources compiled for the host communicating with
-│                            our OpenTitan chip
-│
-└──vendor                 => Vendored-in open source software sources from
-    │                        external repositories
-    └── cryptoc
-```
diff --git a/doc/ug/documentation.md b/doc/ug/documentation.md
deleted file mode 100644
index e5d3fe1..0000000
--- a/doc/ug/documentation.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-title: "Building documentation"
----
-
-The documentation for OpenTitan is available [online](https://docs.opentitan.org).
-The creation of documentation is mainly based around the conversion from Markdown to HTML files with [Hugo](https://gohugo.io/).
-Rules for how to write correct Markdown files can be found in the [reference manual]({{< relref "doc/rm/markdown_usage_style.md" >}}).
-
-## Building locally
-
-Before Hugo is executed a few project specific processing steps are necessary.
-These steps require the installation of the dependencies outlined in the following section.
-All processing steps as well as the invocation to Hugo are combined in the script `util/build_docs.py`.
-
-### Running the server
-
-In order to run a local instance of the documentation server run the following command from the root of the project repository.
-
-```console
-$ ./util/build_docs.py --preview
-```
-
-This will execute the preprocessing, fetch the correct Hugo version, build the documentation and finally start a local server.
-The output will indicate at which address the local instance can be accessed.
-The default is [http://127.0.0.1:1313](http://127.0.0.1:1313).
diff --git a/doc/ug/dv_methodology/index.md b/doc/ug/dv_methodology/index.md
deleted file mode 100644
index f33bba8..0000000
--- a/doc/ug/dv_methodology/index.md
+++ /dev/null
@@ -1,611 +0,0 @@
----
-title: "Design Verification Methodology within OpenTitan"
----
-
-Verification within OpenTitan combines the challenges of industry-strength verification methodologies with open source ambitions.
-When in conflict, quality must win, and thus we aim to create a verification product that is equal to the quality required from a full production silicon chip tapeout.
-
-For the purpose of this document, each design (IPs or the full chip) verified within the OpenTitan project will be referred to as the 'design under test' or 'DUT' ('RTL' or 'design' may be used interchangeably as well), and the design verification as 'DV'.
-
-## Language and Tool Selection
-
-The following are the key techniques used to perform design verification within OpenTitan:
-
-*  Dynamic simulations of the design with functional tests
-*  Formal Property Verification (FPV)
-
-For running dynamic simulations, the strategy is to use the [UVM1.2 methodology](https://www.accellera.org/downloads/standards/uvm) on top of a foundation of SystemVerilog based verification to develop constrained-random functional tests.
-Each DUT will include within the repository, a UVM testbench, a [testplan]({{< relref "doc/ug/dv_methodology/index.md#testplan" >}}), overall [DV document]({{< relref "doc/ug/dv_methodology/index.md#dv-document" >}}), a suite of tests, and a method to build, run tests and report the current status.
-For FPV, some DUTs may also include an SV testbench along with design properties captured in the SystemVerilog Assertions (SVA) language.
-As the project is still in development, the current status will not be completed for all IP, but that is the ultimate goal.
-See discussion below on tracking progress.
-
-For professional tooling, the team has chosen [Synopsys' VCS](https://www.synopsys.com/verification/simulation/vcs.html) as the simulator of choice with respect to the tracking of verification completeness and [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.html) for FPV.
-Wherever possible we attempt to remain tool-agnostic, but we must choose a simulator as our ground truth for our own confidence of signoff-level assurances.
-Likewise, for FPV, [Synopsys VC Formal](https://www.synopsys.com/verification/static-and-formal-verification/vc-formal.html) is also supported within the same flow, and can be used by those with access to VC Formal licenses.
-At this time there is also some support for Cadence's Xcelium, for those organizations which have few Synopsys VCS licenses.
-However support is not as mature as for VCS, which remains the tool for signoff.
-Furthermore, as a project we promote other open source verification methodologies - Verilator, Yosys, cocoTB, etc - and work towards a future where these are signoff-grade.
-The discussions on how those are used within the program are carried out in a different user guide.
-
-## Defining Verification Complete: Stages and Checklists
-
-Verification within the OpenTitan project comes in a variety of completion status levels.
-Some designs are "tapeout ready" while others are still a work in progress.
-Understanding the status of verification is important to gauge the confidence in the design's advertised feature set.
-To that end, we've designated a spectrum of design and verification stages in the  [OpenTitan Hardware Development Stages]({{< relref "doc/project/development_stages.md" >}}) document.
-It defines the verification stages and references where one can find the current verification status of each of the designs in the repository.
-Splitting the effort in such a way enables the team to pace the development effort and allows the progress to be in lock-step with the design stages.
-The list of tasks that are required to be completed to enable the effort to transition from one stage to the next is defined in the [checklists]({{< relref "doc/project/checklist" >}}) document.
-Verification is said to be complete when the checklist items for all stages are marked as done.
-We will explain some of the key items in those checklists in the remainder of this document.
-
-## Documentation
-
-DV effort needs to be well documented to not only provide a detailed description of what tests are being planned and functionality covered, but also how the overall effort is strategized and implemented.
-The first is provided by the **testplan** document and the second, by the **DV document**.
-The [**project status**]({{< relref "doc/project/development_stages.md#indicating-stages-and-making-transitions" >}}) document tracks to progression of the effort through the stages.
-
-In addition to these documents, a nightly **regression dashboard** tabulating the test and coverage results will provide ability to track progress towards completion of the verification stages.
-
-To effectively document all these pieces, there are some key tooling components which are also discussed briefly below.
-
-### Testplan
-
-The testplan consist of two parts:
-- A testplan that captures at a high level, a list of tests that are being planned to verify all design features listed in the design specification.
-- A functional coverage plan that captures at high level a list of functional coverage points and coverage crosses needed to verify that the features listed in the design specification is tested by the list of tests.
-
-The testplan is written in Hjson format and is made available in the corresponding `data` directory of each DUT.
-
-The Hjson schema enables this information to be human-writable and machine-parsable, which facilitates an automated and documentation-driven DV effort.
-The testplan is parsed into a data structure that serves the following purposes:
-
-*  Provide the ability to insert the testplan and coverage plan as tables into the DV document itself, so that all of the required information is in one place
-*  Annotate the nightly regression results to allow us to track our progress towards executing the testplan and coverage collection
-
-The [testplanner]({{< relref "util/dvsim/doc/testplanner.md" >}}) tool provides some additional information on the Hjson testplan anatomy and some of the features and constructs supported.
-The [build_docs]({{< relref "doc/ug/documentation" >}}) tool works in conjunction with the `testplanner` tool to enable its insertion into the DV document as a table.
-
-### DV document
-
-The DV document expands the testplan inline, in addition to capturing the overall strategy, intent, the testbench block diagram, a list of interfaces / agents, VIPs, reference models, the functional coverage model, assertions and checkers. It also covers FPV goals, if applicable.
-This is written in [Markdown]({{< relref "doc/rm/markdown_usage_style" >}}) and is made available in the corresponding `doc` directory of each DUT.
-
-A [template]({{< relref "hw/dv/doc/dv_doc_template" >}}) for the DV documentation as well as the testbench block diagram in the OpenTitan team drive  (under the 'design verification' directory) are available to help get started.
-
-### Regression Dashboard
-
-The DV document provides a link to the latest [nightly](#nightly) regression and coverage results dashboard uploaded to the web server.
-This dashboard contains information in a tabulated format mapping the written tests to planned tests (in the testplan) to provide ability to track progress towards executing the testplan.
-
-## Automation
-
-We rely on automation wherever possible, to avoid doing repetitive tasks manually.
-With this in mind, there are three key areas where the whole testbench, or part of it is auto-generated.
-These are described below.
-
-### Initial UVM Testbench Generation
-
-As is the case with design, we strive for conformity in our verification efforts as well.
-The motivation for this is not just aesthetics, but also to reap the advantages of [code reuse](#code-reuse), which we rely heavily on.
-To help achieve this, we provide a verification starter tool-kit called [uvmdvgen]({{< relref "util/uvmdvgen/doc" >}}).
-It can be used to completely auto-generate the complete initial DV enviroment for a new DUT, including the [documentation](#documentation) pieces (testplan as well as DV document), the complete UVM environment including the testbench, to the collaterals for building and running tests along with some common tests.
-This significantly helps reduce the development time.
-It can also be used to auto-generate the initial skeleton source code for building a new reusable verification component for an interface (a complete UVM agent).
-
-### UVM Register Abstraction Layer (RAL) Model
-
-The UVM RAL model for DUTs containing CSRs is auto-generated using the [reggen]({{< relref "util/reggen/doc" >}}) tool.
-The specification for capturing the CSRs in the Hjson format can be found in the [Register Tool]({{< relref "doc/rm/register_tool" >}}) documentation.
-We currently check-in the auto-generated UVM RAL model along with our UVM testbench code and rely on CI checks for consistency.
-In the future we may move to a flow where it is not checked into the repository, but auto-generated on-the-fly as a part of the simulation.
-
-### Testbench Automation
-
-For a parameterized DUT that may possibly have multiple flavors instantiated in the chip, it would be prohibitively difficult to manually maintain the DV testbenches for all those flavors.
-To cater to this, we develop a generic UVM testbench and rely on custom tooling to auto-generate the specific parameter sets that are required to undergo the full verification till signoff.
-<!-- TODO: have this point to TLUL DV document -->
-An effort of this sort is planned for verifying the [TileLink XBAR]({{< relref "hw/ip/tlul/doc" >}}).
-
-## Code Reuse
-
-SystemVerilog / UVM is structured to make code highly reusable across different benches.
-To that end, several commonly used verification infrastructure pieces are provided to aid the testbench development, which are discussed below.
-
-### DV Base Library
-
-We provide an elementary scaffolding / base layer for constructing UVM testbenches via a [DV base library]({{< relref "hw/dv/sv/dv_lib/doc" >}}) of classes to help us get off the ground quickly.
-Most, if not all, testbenches in OpenTitan (whether developed for a comportable IP or not) extend from this library, which provides a common set of features.
-A UVM testbench feature (stimulus / sequence, checking logic or functional coverage element) that is generic enough to be applicable for use in all testbenches is a valid candidate to be added to the DV base library.
-By doing so, we improve synergies across our testbenches and reduce the overall development effort & time to market.
-The features are discussed in more detail in the document referenced above.
-The actual UVM testbenches for some of the IPs extend from this library as the final layer.
-
-### Comportable IP DV Library
-
-Defining a common ground to develop all OpenTitan IPs as described in the [Comportable IP specification]({{< relref "doc/rm/comportability_specification" >}}) provides us an excellent path to maximize code reuse and shorten the testbench development time even further.
-In view of that, we provide the [Comportable IP DV library]({{< relref "hw/dv/sv/cip_lib/doc" >}}) of classes, which themselves extend from DV base library to form the second layer.
-These provide a common set of DV features that are specific to Comportable IPs.
-The actual UVM testbenches for the Comportable IPs extend from this library as the third and the final layer.
-
-### Common Verification Components
-
-In addition to the above library of classes, there are several common plug-and-play verification compoments (a.k.a. universal verification components or UVCs) provided for use in testbenches at `hw/dv/sv` location.
-A few examples of these are as follows:
-
-*  [Common interfaces]({{< relref "hw/dv/sv/common_ifs" >}})
-*  [DV utilities]({{< relref "hw/dv/sv/dv_utils/doc" >}})
-*  [CSR utilities]({{< relref "hw/dv/sv/csr_utils/doc" >}})
-*  [Device memory model]({{< relref "hw/dv/sv/mem_model/doc" >}})
-*  Interface agents
-  *  [TileLink agent]({{< relref "hw/dv/sv/tl_agent/doc" >}})
-  *  [UART agent]({{< relref "hw/dv/sv/usb20_agent/doc" >}})
-
-This is not an exhaustive list since we are still actively developing and adding more such components as we speak.
-Please navigate to the above code location to find more sure UVCs.
-
-## DV Efforts in OpenTitan
-
-The overall OpenTitan DV effort is spread across 3 tiers:
-
-*  IP level DV
-*  Core (Ibex) level DV
-*  Chip level DV
-
-### IP Level DV
-
-IP level DV testbenches are small and provide fine grained control of stimulus and corner case generation.
-Tests at this level run relatively quickly and development cycles are shorter.
-Coverage closure is more intensive since there are typically no pre-verified sub-modules.
-To achieve our coverage goals, we take a constrained random approach to generate the stimulus.
-The DV environment models the behavior of the DUT more closely to perform checks (typically within the scoreboard) independently of the stimulus.
-In some IPs (specifically the ones that provide cryptographic functions), we also use open source third party C libraries as reference models to check the behavior of the DUT through DPI-C calls.
-
-Each of the IP level DV environments are described in further detail within their own [DV document](#dv-document).
-To find all of them, please navigate to this [landing page]({{< relref "hw" >}}).
-The [UART DV document]({{< relref "hw/ip/uart/doc/dv" >}}) documentation which can be found there can be used as an example / reference.
-
-### Core Ibex Level DV
-
-The RISC-V CPU core Ibex used in OpenTitan has its own DV testbench and it is verified to full coverage closure.
-Please see the [Ibex DV documentation](https://github.com/lowRISC/opentitan/blob/master/hw/vendor/lowrisc_ibex/doc/verification.rst) for more details.
-
-### Chip Level DV
-
-The chip level DV effort is aimed at ensuring that all of the IPs are integrated correctly into the chip.
-For IPs that are pre-verified sub-modules, we perform [integration testing](#integration-testing).
-These are simple functional tests written in C which are cross-compiled and run natively on the Ibex core.
-The software compilation flow to enable this is explained in further detail in the [Building Software]({{< relref "doc/getting_started/build_sw" >}}) document.
-Further, there is a mechanism for the C test running on the CPU to signal the SystemVerilog testbench the test pass or fail indication based on the observed DUT behavior.
-We also provide an environment knob to 'stub' the CPU and use a TL agent to drive the traffic via the CPU's data channel instead, in cases where more intensive testing is needed.
-<!-- TODO: add link to chip DV document -->
-The chip DV document, which is currently under active development will explain these methodologies and flows in further detail.
-
-## Key Test Focus Areas
-
-When progressing through the verification stages, there are key focus areas or testing activities that are perhaps common across all DUTs.
-These are as follows.
-
-### Progressing towards [V1]({{< relref "doc/project/development_stages#hardware-verification-stages" >}})
-
-These set of tests (not exhaustive) provide the confidence that the design is ready for vertical integration.
-
-#### Basic Smoke Test
-
-At this stage, just a skeleton testbench environment is available and most components lack functionality.
-A basic sanity test drives the clock, brings the DUT out of reset, checks if all outputs are legal values (not unknown) and exercise a major datapath with simple set of checks.
-This paves the way for more complex testing.
-During the testplan and the DV document review, the key stake holders at the higher level who consume the DUT as an IP (for example, design and DV engineers working at the chip level into which the IP is integrated) may drive the requirements for the level of testing to be done.
-This test (or set of tests) is also included as a part of the sanity regression to maintain the code health.
-
-#### CSR Suite of Tests
-
-The very first set of real tests validate the SW interface laid out using the regtool.
-These prove that the SW interface is solid and all assumptions in CSRs in terms of field descriptions and their accessibility are correctly captured and there are no address decode bugs.
-
-### Progressing towards [V2]({{< relref "doc/project/development_stages#hardware-verification-stages" >}})
-
-Bulk of testing in this stage focus on functionally testing the DUT.
-There however are certain categories of tests that may need additional attention.
-These categories are fairly generic and apply to most DUTs.
-
-#### Power Tests
-
-It is vital to be able to predict the power consumption of our SoC in early development stages and refactor the design as needed to optimize the RTL.
-Typically, DV tests that mimic idle and high power consumption scenarios are written and FSDB generated from those tests are used for analysis.
-
-This is perhaps applicable when an actual ASIC will be built out of our SoC.
-At that time, we could have lower power requirements in terms of being able to put parts of the SoC in different power 'islands' in retention voltage or be power collapsed.
-If and when such requirements are fully specified for the product, we need to ensure that power-aware tests have been added to verify this.
-
-#### Performance Tests
-
-ASICs vary widely in terms of their target applications.
-For some, performance testing may take the center stage, and for some, it may not be as important.
-No matter what the target application is, there is almost always some kind of requirement in terms of sustained bandwidth on a particular interface captured in the DUT specification.
-These set of tests ensure that those requirements are indeed met.
-
-#### Security & Error Tests
-
-Error tests focus on generating stimulus that may not be considered legal and ensure that the DUT can detect, react and behave accordingly, instead of locking up or worse, exposing sensitive information.
-These types of tests are particularly important for a security chip such as ours.
-These are typically handled via directed tests since it can be prohibitively time consuming to develop complex scoreboards that can handle the error-checking when running completely unconstrained random sequences.
-A classic example of this is the illegal / error access tests via the TileLink interface, which are run for all DUTs.
-Here, we constrain a random sequence to generate TL accesses that are considered illegal and ensure that the DUT responds with an error when appropriate.
-Another example is testing RAMs that support ECC / error correction.
-
-While some of the examples listed above pertain to concrete features in the design, we are actively also exploring alternative ways of finding and covering security holes that may not be uncovered via traditional DV efforts.
-
-#### Debug Tests
-
-This mainly applies to DUTs that contain a processing element (CPU).
-These focus on verifying the debug features supported by the CPU at the chip level based on the [RISCV debug specification](https://riscv.org/specifications/debug-specification).
-
-#### Stress Tests
-
-Stress tests lean heavily on constrained random techniques, and exercise multiple interfaces and / or design features simultaneously.
-This is done by forking off multiple individual test sequences in parallel (or sequentially if the pieces of hardware exercised by the tests sequences overlap).
-Stress tests are hard to debug due to lot of things happening in parallel and the scoreboard may not be written as robustly initially to handle those scenarios.
-To mitigate that, they are constructed with knobs to control the level of constraints applied to the randomization (of individual sequences), so that the scoreboard can be made more robust incrementally to handle the corner cases.
-The level of constraints are then slowly eased to allow deeper state space exploration, until all areas of the DUT are satisfactorily stressed.
-Stress tests are ideal for bug hunting and closing coverage.
-
-### Progressing towards [V3]({{< relref "doc/project/development_stages#hardware-verification-stages" >}})
-
-The main focus of testing at this stage is to meet our [regression](#nightly) and [coverage](#coverage-collection) goals.
-Apart from that, there are cleanup activities to resolve all pending TODO items in the DV code base and fix all compile and run time warnings (if any) thrown by the simulator tools.
-
-## Assertions
-
-In DV, we follow the same assertion methodology as indicated in the [design methodology]({{< relref "./design.md#assertion-methodology" >}}).
-Wherever possible, the assertion monitors developed for FPV are reused in UVM testbenches when running dynamic simulations.
-An example of this is the [TLUL Protocol Checker]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}).
-
-## Regressions
-
-There are 2 main types of regressions suites - 'sanity' and 'nightly'.
-
-### Sanity
-
-Due to heavy code reuse, breakages happen quite often.
-It is necessary for each DUT testbench to provide a set of simple sanity test that accesses a major datapath in the DUT.
-Categorizing such tests into a sanity suite provides a quick path for users who touch common / shared piece of code to run those tests for all DUTs and ensure no breakages occur.
-If the DUT testbench has more than one compile-time configuration, there needs to be at least 1 sanity test per configuration.
-
-Ideally, the sanity regression is run as a part of the CI check whenever a PR is submitted. Due to use of proprietary tools for running DV simulations, this cannot be accomplished. Instead, we run a daily cron job locally on the up-to-date `master` branch to identify such breakages and deploy fixes quickly.
-
-### Nightly
-
-While development mostly happens during the work day, nighttime and weekends are better utilized to run all of our simulations (a.k.a "regression").
-Achieving 100% pass rate in our nightly regressions consistently is a key to asserting 'verification complete'.
-The main goals (for all DUTs) are:
-
-*  Run each constrained random test with a sufficiently large number of seeds (arbitrarily chosen to be 100)
-   * Pass completely random seed values to the simulator when running the tests
-*  Spawn jobs via LSF to leverage compute resources at one's disposal
-*  Run resource intensive simulations without impacting daytime development
-*  Collect and merge coverage
-*  Publish the testplan-annotated regression and coverage results in the regression dashboard
-
-One of the key requirements of nightly regressions is to complete overnight, so that the results are available for analysis and triage the next morning.
-If test runtimes are longer, we could define a weekly regression based on need.
-In general, it is a good practice to periodically profile the simulation to identify bottlenecks in terms of simulation performance (which often is a result of specific coding style choices).
-
-## Coverage Collection
-
-Collecting, analyzing, and reporting coverage with waivers is a requirement to assert 'verification complete'.
-Any gaps in our measured coverage need to be understood and either waived (no need to cover) or closed by additional testing.
-The end goal is to achieve 100% coverage across all applicable coverage metrics.
-This process is known as "coverage closure", and is done in close collaboration with the designer(s).
-Coverage collected from all tests run as a part of the regression is merged into a database for analysis.
-Our primary tool of choice for our coverage closure needs is Synopsys VCS & Verdi.
-However, the use of other simulators is welcome.
-
-**Why do we need coverage?**
-
-The common answer is to flush out bugs in the design.
-This is not accurate enough.
-Making sure there are no bugs in a design, while it is important it is not sufficient.
-One must also make sure the design works as intended.
-That is, it must provide all the functionality specified in the design specification.
-So a more precise answer for why we need coverage is to flush out flaws in the design.
-These flaws can be either design bugs or deficiencies in the design with respect to the specification.
-
-Another reason for why we need coverage is to answer the seemingly simple but important question:
-**When are we done testing?**
-Do we need 1, 10, or 100 tests and should they run 10, 100, or 1000 regressions?
-Only coverage can answer this question for you.
-
-There are two key types of coverage metrics: code coverage and functional coverage.
-Both are important and are covered in more detail below.
-For this topic, we define 'pre-verified sub-modules' as IPs within the DUT that have already been (or are planned to be) verified to complete sign-off within individual test benches.
-
-### Code Coverage
-
-Commercial simulators typically provide a way to extract coverage statistics from our regressions.
-Tools automatically analyze the design to extract key structures such as lines, branches, FSMs, conditions, and IO toggle and provide them as different metrics to measure coverage against (to what extent is our stimulus through constrained random tests covering these structures).
-These metrics are explained briefly below:
-
-* **Line Coverage**: This metric measures which lines of SystemVerilog RTL code were executed during the simulation.
-  This is probably the most intuitive metric to use.
-  Note that `assign` statements are always listed as covered using this metric.
-* **Toggle Coverage**: This metric measures every logic bit to see if it transitions from 1 &rarr; 0 and 0 &rarr; 1.
-  It is very difficult, and not particularly useful to achieve 100% toggle coverage across a design.
-  Instead, we focus on closing toggle coverage only on the IO ports of the DUT and IO ports of pre-verified IPs within the DUT.
-* **FSM state Coverage**: This metric measures which finite state machine states were executed during a simulation.
-* **FSM transition Coverage**: This metric measures which arcs were simulated for each finite state machine in the design.
-* **Conditional Coverage**: This metric tracks all combinations of conditional expressions simulated.
-* **Branch Coverage**: This metric is similar to line coverage, but not quite the same.
-  It tracks the flow of simulation (e.g. if/else blocks) as well as conditional expressions.
-  Note that un-hit FSM state/transition coverage almost always shows up as un-hit branch coverage as well.
-
-Code coverage is sometimes referred to as implicit coverage as it is generated based on the code and takes no additional effort to implement.
-
-### Functional Coverage
-
-Unlike code coverage, functional coverage requires the designer and/or DV engineer to write additional cover points and covergroups.
-For this reason functional coverage is sometimes referred to as explicit coverage.
-Cover points and covergroups are more complex constructs that capture whether signals (that reflect the current state of the design) have met an interesting set or a sequence of values (often called corner cases).
-These constructs also allow us to capture whether multiple scenarios have occurred simultaneously through crosses.
-This is also often referred to as cross coverage.
-These constructs are typically encapsulated in a class and are sometimes referred to as the 'functional coverage model'.
-They are sampled in 'reactive' components of the testbench, such as monitors and/or the scoreboards.
-They can also be embedded within the RTL to sample interesting scenarios through DV stimulus.
-
-Here are the metrics used with a brief explanation:
-
-* **Covergroup Coverage**: Observes values on buses, registers and so on.
-  This can verify that a specific value or range of values was seen on a bus and that no illegal values were seen.
-  Simulators can be set to throw an error if an illegal value is seen.
-  One use of a covergroup is to define something called cross coverage.
-  This asserts that several coverage points are hit at once. For example, we might want to see a FIFO's full and write enable signals be asserted at the same time.
-  This is called a coverage cross.
-* **Cover Property coverage**:
-   * **Assertion coverage using SVA**
-   * **procedural code**
-
-Most often property coverage is implemented using SystemVerilog Assertions (SVA).
-This observes events or series of events.
-As an example think of the TL UL register bus used in OpenTitan.
-A cover property cover point could be the handshaking between valid and ready.
-SVA also allows the design engineer to add cover for procedures and variables not visible on output pins.
-Note, an assertion precondition counts as a cover point.
-
-
-#### Do we need both types of coverage
-
-Reaching a 100% code coverage does not tell you the whole story.
-Think of the simple example of an AND gate with inputs A, B, and output O.
-To get to 100% code coverage only two different input combinations are needed: 00 and 11, these two will produce all possible outcomes of O.
-
-![single AND gate](dv_method_single_gate.svg)
-
-
-The coverage will indicate that all code was exercised.
-But we do not know that our design works as intended.
-All we know is that A, B, and O have been observed to take on both logic 0 and 1.
-We could not say for certain that the design was in fact an AND gate: it could just as easily be an OR gate.
-So we need functional coverage to tell us this.
-The first thing functional coverage will tell us is if we observed all possible values on the inputs.
-And by adding a cross between the inputs and the output it will tell us which gate we are looking at.
-
-
-Reaching 100% functional coverage is not enough either.
-Remember functional coverage requires the designer to manually add coverage point into the design.
-Going back to our AND gate, let us say we take two of these and OR the outputs of the two.
-
-![multi gate](dv_method_multi_gate.svg)
-
-
-If we only add the cover points from our original example, these will still exercise the new output of our system and therefore result in reaching 100% functional coverage, but half of the design was not exercised.
-This is called a coverage hole and code coverage would have indicated that a part of the code was never exercised.
-
-While functional coverage will tell you if your design is working correctly, code coverage will highlight if your testplan is incomplete, or if there are any uncovered/unreachable features in the design.
-Coverage holes can be addressed accordingly through either updating the design specification and augmenting the testplan / written tests, or by optimizing the design to remove unreachable logic if possible.
-There may be features that cannot be tested and cannot be removed from the design either.
-These would have to be analyzed and excluded from the coverage collection as a waiver.
-This is an exercise the DV and the designer typically perform together.
-This is discussed in more detail below.
-
-### Exclusions
-
-Post-simulation coverage analysis typically yields items that may need to be waived off for various reasons.
-This is documented via exclusions, which are generated by the simulator tools.
-The following are some of the best practices when adding exclusions:
-
-*  Designers are required to sign-off on exclusions in a PR review.
-*  Provide annotations for ALL exclusions to explain the reasoning for the waiver.
-*  Annotate exclusions with a standardized prefix (this makes writing exclusions and reviewing them easier).
-   Exclusions almost always fall under a set of categories that can be standardized.
-   Annotation can be prefixed with a category tag reflecting one of those categories, like this:
-
-   `[CATEGORY-TAG] <additional explanation if required>`
-
-   These categories are as follows:
-
-   *  **UNR**: Unreachable code due to design constraints, or module inputs being tied off in a certain way will result in specific coverage items being unreachable.
-      Additional explanation is optional.
-   *  **NON_RTL**: Simulation constructs in RTL that can be safely excluded in structural coverage collection.
-      These include tasks and functions, initial / final blocks that are specifically used for simulation such as backdoor write and read functions for memory elements.
-      Additional explanation is optional.
-   *  **UNSUPPORTED**: Item being excluded is a part of design feature that is not supported.
-      Additional explanation is optional.
-      *  IP designed by some other team / third party is incorporated, but only a subset of the features are in use. Remaining ones are not supported.
-      *  Features that are added into the design but are not made a part of the design specification for the current generation / chip being taped out.
-      *  UVC / agent with detailed coverage items where certain crosses are not supported by the design (ex: TL agent with fcov on full spectrum of burst with all sizes and lengths, but only a subset of it actually being supported).
-      Additional explanation is **mandatory**.
-   *  **EXTERNAL**: Items that are already covered in another bench.
-      Additional explanation is **mandatory**.
-   *  **LOW_RISK**: Items that are prohibitively hard to hit, given the resource constraints and are deemed to be of low risk and low value.
-      Features that are added to the design AND are described adequately in the design spec AND a collective upfront decision has been made in agreement with SW/architecture/design/DV to not verify it due to resource constraints.
-      Additional explanation is **mandatory**.
-
-### Integration Testing
-
-For a DUT containing pre-verified sub-modules, the DV effort can be slightly eased.
-From the code coverage collection perspective, such sub-modules can be 'black boxed' where we turn off all other metrics within their hierarchies and only collect the toggle coverage on their IOs.
-This eases our effort by allowing us to develop less complex tests and verification logic (pertaining to those pre-verified modules) since our criteria for closure reduces to only functionally exercising the IOs and their interactions with the rest of the DUT to prove that sub-modules are properly connected.
-
-Of course, the rest of the sub-modules and glue-logic within the DUT that are not pre-verified do need to undergo the full spectrum of coverage closure.
-We achieve this by patterning the compile-time code coverage model in a particular way (this is a simulator tool-specific capability; for VCS, this is same as the coverage hierarchy file that is written and passed to the simulator with the `-cm_hier` option).
-
-### Coverage Collection Guidelines
-
-Coverage closure is perhaps the most time-consuming part of the whole DV effort, often with low return.
-Conservatively collecting coverage on everything might result in poor ROI of DV user's time.
-Also, excessive coverage collection slows down the simulation.
-This section aims to capture some of the best practices related to coverage closure.
-
-It is recommended to follow these guidelines when collecting coverage:
-
-*  Shape the coverage collection at compile time if possible by only enabling coverage collection on DUT/sub-modules and nodes, and/or metrics of interest.
-*  Collect toggle coverage only on IOs of DUT and all of its sub-modules.
-  *  If this is not worthwhile, then collect toggle coverage on top-level DUT IOs and IOs of pre-verified sub-modules.
-*  Collect all coverage metrics (except toggle based on above bullet) on the DUT and all of its non-pre-verified sub-modules.
-*  Treat pre-verified sub-modules as ['black-box'](#integration-testing) in terms of coverage collection.
-
-### Unreachable Coverage Analysis
-
-Instead of manually reviewing coverage reports to find unreachable code, we use VCS UNR to generate a UNR exclusion file which lists all the unreachable codes.
-VCS UNR (Unreachability) is a formal solution that determines the unreachable coverage objects automatically from simulation.
-The same coverage hierarchy file and the exclusion files used for the simulation can be supplied to VCS UNR.
-
-Follow these steps to run and submit the exclusion file.
-1. Generate the VCS coverage database for the block by running full regression with `--cov` switch.
-2. Launch the VCS UNR flow:
-```
-util/dvsim/dvsim.py path/to/<dut>_sim_cfg.hjson --cov-unr
-```
-3. If no exclusion file is generated, there is no unreachable code in RTL.
-   If there is an exclusion file generated, the output should be reviewed by both designer and verification engineer.
-   When the unreachable code is sensible and we decide to exclude it in coverage report, create a PR to add to ['common_cov_excl.cfg'](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/vcs/common_cov_excl.cfg) or block specific exclusion file, such as ['uart_cov_excl.el'](https://github.com/lowRISC/opentitan/blob/master/hw/ip/uart/dv/cov/uart_cov_excl.el).
-
-Here are some guidelines for using UNR and checking in generating exclusion.
-1. It's encouraged that designers run UNR to check the design in the early design stage (D1/D2), but adding exclusions for unreachable coverage should be done between the D2 and V3 stage when the design is frozen (no feature update is allowed, except bug fix).
-Getting to 90% coverage via functional tests is easy.
-Over 90% is the hard part as there may be a big chunk of unreachable codes.
-It is cumbersome to go through a coverage report to manually add exclusions, but the VCS UNR flow provides a path to weed out all of the unreachable ones.
-However, it is not the right thing to add a coverage exclusion file to reach the 80% needed for V2 since the design isn't stable at that period.
-2. If any RTL changes happen to the design after the coverage exclusion file has been created, it needs to be redone and re-reviewed.
-3. All coverage exclusion files and coverage configuration file (if it's not using default [cover.cfg](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/vcs/cover.cfg)) should be checked during sign-off.
-4. Keep the VCS generated `CHECKSUM` along with exclusions, which is served as a crosscheck to ensure that the exclusion isn't applied when there is some change on the corresponding codes and the exclusion is outdated.
-We should not use `--exclude-bypass-checks` to disable the check, otherwise, it's needed to have additional review to make sure exclusions match to the design.
-5. For IP verified in IP-level test bench, UNR should be run in IP-level to generate exclusion.
-For IP verified in top-level, UNR should be run in top-level.
-There is no reuse of exclusions from IP to top, since they are independently closed for coverage.
-
-Note: VCS UNR doesn't support assertion or functional coverage.
-
-## X-Propagation (Xprop)
-
-Standard RTL simulations (RTL-sim) ignore the uncertainty of X-valued control signals and assign predictable output values.
-As a result, classic RTL-sim often fail to detect design problems related to the lack of Xprop, which actually can be detected in gate-level simulations (gate-sim).
-With Xprop in RTL-sim, we can detect these problems without having to run gate-sim.
-
-Synopsys VCS and Cadence Xcelium both provide the following 2 modes for Xprop.
-  * **Optimistic Mode**: Closer to actual hardware behavior and is the more commonly used mode.
-  * **Pessimistic Mode**: More pessimistic than a standard gate-sim.
-
-Example:
-```systemverilog
-always @(posedge clk) begin
-  if (cond) out <= a;
-  else      out <= b;
-end
-```
-
-In the above example, results of 'out' are shown as following.
-
-a | b | cond | Classic RTL-sim | Gate-sim | Actual Hardware | Xprop Optimistic | Xprop Pessimistic |
---|---|------|-----------------|----------|-----------------|------------------|-------------------|
-0 | 0 |  X   |        0        |     0    |       0         |         0        |         X         |
-0 | 1 |  X   |        1        |     X    |       0/1       |         X        |         X         |
-1 | 0 |  X   |        0        |     X    |       0/1       |         X        |         X         |
-1 | 1 |  X   |        1        |     X    |       1         |         1        |         X         |
-
-We choose **Pessimistic Mode** as we want to avoid using X value in the condition.
-Xprop is enabled by default when running simulations for all of our DUTs due to the acceptable level of overhead it adds in terms of wall-clock time (less than 10%).
-
-It's mandatory to enable Xprop when running regression for coverage closure.
-To test Xprop more effectively, the address / data / control signals are required to be driven to Xs when invalid (valid bit is not set).
-For example, when a_valid is 0 in the TLUL interface, we drive data, address and control signals to unknown values.
-```systemverilog
-  function void invalidate_a_channel();
-    vif.host_cb.h2d.a_opcode  <= tlul_pkg::tl_a_op_e'('x);
-    vif.host_cb.h2d.a_param   <= '{default:'x};
-    vif.host_cb.h2d.a_size    <= '{default:'x};
-    vif.host_cb.h2d.a_source  <= '{default:'x};
-    vif.host_cb.h2d.a_address <= '{default:'x};
-    vif.host_cb.h2d.a_mask    <= '{default:'x};
-    vif.host_cb.h2d.a_data    <= '{default:'x};
-    vif.host_cb.h2d.a_user    <= '{default:'x};
-    vif.host_cb.h2d.a_valid   <= 1'b0;
-  endfunction : invalidate_a_channel
-```
-
-## FPV
-
-This section is TBD.
-
-## Security Verification
-
-Security verification is one of the critical challenges in OpenTitan.
-Each design IP contains certain security countermeasures.
-There are several common countermeasures that are widely used in the blocks.
-Therefore, a common verification framework is built up in the DV base libraries.
-The following common countermeasures can be either automatically or semi-automatically verified by this framework.
-
-1. Countermeasures using common primitives can be verified by the [Security Countermeasure Verification Framework]({{< relref "sec_cm_dv_framework" >}}).
-2. The following common countermeasures can be verified by cip_lib.
-The steps to enabling them is described in cip_lib [document]({{< relref "hw/dv/sv/cip_lib/doc#security-verification-in-cip_lib" >}}).
-  - Bus integrity
-  - Shadowed CSRs
-  - REGWEN CSRs
-  - MUBI type CSRs/ports
-
-For custom countermeasures, they have to be handled in a case-by-case manner.
-
-## Reviews
-
-One of the best ways to convince ourselves that we have done our job right is by seeking from, as well as providing feedback to, our contributors.
-We have the following types of reviews for DV.
-
-### Code Reviews
-
-Whenever a pull-request is made with DV updates, at least one approval is required by a peer and / or the original code developer to enable the changes to be submitted.
-DV updates are scrutinized in sufficient detail to enforce coding style, identify areas of optimizations / improvements and promote code-reuse.
-
-### Sign-off Reviews
-
-In the initial work stage of verification, the DV document and the completed testplan documents are reviewed face-to-face with the following individuals:
-
-*  Designer(s)
-*  DV peers
-*  Leads
-*  Chip level (or higher level) designers and DV engineers within which the DUT is integrated
-*  Software team
-*  Product architect
-
-The goal of this review is to achieve utmost clarity in the planning of the DV effort and resolve any queries or assumptions.
-The feedback in this review flows both ways - the language in the design specification could be made more precise, or missing items in both, the design specification as well as the testplan can be identified and added.
-This enables the development stage to progress smoothly.
-
-Subsequently, the intermediate transitions within the verification stages are reviewed within the GitHub pull-request made for updating the checklist and the [project status]({{< relref "doc/project/development_stages.md#indicating-stages-and-making-transitions" >}}).
-
-Finally, after the verification effort is complete, there is a final sign-off review to ensure all checklist items are completed satisfactorily without any major exceptions or open issues.
-
-## Filing Issues
-
-We use the [OpenTitan GitHub Issue tracker](https://github.com/lowRISC/opentitan/issues) for filing possible bugs not just in the design, but also in the DV code base or in any associated tools or processes that may hinder progress.
-
-## Getting Started with DV
-
-The process for getting started with DV involves many steps, including getting clarity on its purpose, setting up the testbench, documentation, etc.
-These are discussed in the [Getting Started with DV]({{< relref "doc/getting_started/setup_dv.md" >}}) document.
-
-## Pending Work Items
-
-These capabilities are currently under active development and will be available sometime in the near future:
-
-*  Provide ability to run regressions.
diff --git a/doc/ug/fpga_boards.md b/doc/ug/fpga_boards.md
deleted file mode 100644
index d7e05a6..0000000
--- a/doc/ug/fpga_boards.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: "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.
-The following sections give details of how to obtain our supported FPGA boards.
-
-## ChipWhisperer CW310
-
-The ChipWhisperer CW310 board is produced by [NewAE Technology](https://www.newae.com/).
-It is available with a Xilinx Kintex 7 XC7K160T or XC7K410T FPGA.
-At the moment, the version with the smaller Xilinx Kintex 7 XC7K160T FPGA is not supported by OpenTitan.
-The ChipWhisperer CW310 board with the bigger Xilinx Kintex 7 XC7K410T FPGA is the main development FPGA board for OpenTitan.
-
-### Ordering
-
-You can get the ChipWhisperer CW310 board from the [NewAE Technology web store](https://store.newae.com/cw310-bergen-board-large-fpga-k410t-for-full-emulation/).
-
-In the future, the board might also be listed on [Mouser](https://eu.mouser.com/manufacturer/newae-technology/).
-
-### Notes
-
-* By default the board ships with a US and European power plug.
-  Optionally, a UK power plug can be selected in the NewAE Technology web store.
-* The board is available in two options.
-  The normal option is suitable for e.g. regular hardware and software development.
-  If you also plan to do Side Channel Analysis (SCA), you should make sure to order the SCA option which misses some decoupling capacitors.
diff --git a/doc/ug/github_notes.md b/doc/ug/github_notes.md
deleted file mode 100644
index 3854006..0000000
--- a/doc/ug/github_notes.md
+++ /dev/null
@@ -1,509 +0,0 @@
----
-title: GitHub Notes
----
-
-The OpenTitan source tree is maintained on GitHub in a [monolithic repository](https://github.com/lowRISC/opentitan) called opentitan.
-
-These notes are for people who intend to contribute back to OpenTitan (based on
-notes taken by a relatively inexperienced git user). If you don't intend to do
-this you can simply clone the main repository, instructions are in [install
-instructions]({{< relref "doc/getting_started" >}}) and this document can be
-ignored.There is much more to using git, a possible next step is to reference
-[Resources to learn Git](https://try.github.io/).
-
-## Getting a working repository
-
-To develop in the repository you will need to get a copy on your local
-machine. To allow contributions to be made back to the main repo
-(through a process called a Pull Request) you need to first make your
-own copy of the repository on GitHub then transfer that to your local
-machine.
-
-You will need to log in to GitHub, go to the [opentitan repository](https://github.com/lowRISC/opentitan) and click on
-"Fork". This will generate a copy in your GitHub area that you can
-use.
-
-Then setup git on your local machine and set some standard parameters
-to ensure your name and email are correctly inserted. These commands
-set them globally for all your use of git on the local machine so you
-may have done this step already, there is no need to repeat it. (See
-below if you want to use a different email address for this repo.)
-
-Check the parameters:
-```console
-$ git config -l
-```
-
-And if they do not exist then set them:
-
-```console
-$ git config --global user.name "My Name"
-$ git config --global user.email "my_name@email.com"
-```
-
-`git` will take care of prompting for your GitHub user name and
-password when it is required, but it can be useful to allow it to
-cache these credentials (set here to an hour using the timeout in
-seconds) so you don't have to enter every time:
-
-```console
-$ git config --global credential.helper 'cache --timeout=3600'
-```
-
-Now make a local copy of your GitHub copy of the repository and let git know
-that it is derived from the **upstream** lowRISC repo:
-
-```console
-$ cd <where the repository should go>
-$ git clone https://github.com/$GITHUB_USER/opentitan.git
-$ cd opentitan
-$ git remote add upstream https://github.com/lowRISC/opentitan.git
-$ git fetch upstream
-$ git remote -v
-```
-
-The `git remote -v` should give your GitHub copy as **origin** and the
-lowRISC one as **upstream**. Making this link will allow you to keep your
-local and GitHub repos up to date with the lowRISC one.
-
-If you want a different email address (or name) for the lowRISC repository then
-you can set it locally in the repository (similar to above but without the
---global flag). This command must be executed from a directory inside
-the local copy of the repo. (There is no need for the first `cd` if
-you are following the previous step.)
-
-
-```console
-$ cd opentitan
-$ git config user.email "my_name@lowrisc.org"
-```
-
-## Working in your local repo
-
-The repository that you have created locally will initially be on the
-`master` branch. In general you should not make changes on this
-branch, just use it to track your GitHub repository and synchronize with the
-main lowRISC repository.
-
-The typical workflow is to make your own branch which it is
-conventional to name based on the change you are making:
-
-```console
-$ git checkout -b forchange
-$ git status
-```
-
-The status will initially indicate there are no changes, but as you
-add, delete or edit files it will let you know the state of things.
-
-Once you are happy with your changes, commit them to the local repository by adding the files to the changes (if things are clean you can add using `git commit -a -s` instead of a number of `add` commands):
-
-```console
-$ git add...
-$ git commit -s
-```
-
-The commit will make you add a message. The first line of this is a
-short summary of the change. It should be prefixed with a word in
-square brackets indicating the area being changed, typically the IP or
-Tool name. For example:
-
-```console
-[doc/um] Add notes on using GitHub and the repo
-```
-
-After this there should be a blank line and the main description of
-the change. If you are fixing an issue then add a line at the end of
-the message `Fixes #nn` where `nn` is the issue number. This will link
-the fix and close out the issue when it is added to the lowRISC repo.
-If the change is an attempted fix that has not yet had confirmation from
-verification engineers, it should not close the related issue. In this case,
-write `Related to #nn` in the commit message rather than `Fixes #nn`.
-
-The commit message will end with a "Signed-off-by" line inserted by `git` due to using the `-s` option to `git commit`.
-Adding this line certifies you agree to the statement in [CONTRIBUTING.md](https://github.com/lowRISC/opentitan/blob/master/CONTRIBUTING.md), indicating your contribution is made under our Contributor License Agreement.
-
-When you have finished everything locally (it is good practice to do a
-status check to ensure things are clean) you can push your branch (eg
-forchange) to **your** GitHub repository (the **origin**):
-
-```console
-$ git status
-$ git push origin forchange
-```
-
-Then you need to go to your repository in GitHub and either select branch
-from the pulldown or often there is a status message that you can
-click on, review the changes and make a Pull Request. You can add
-reviewers and get your change reviewed.
-
-If you need to make changes to satisfy the reviews then you do that in
-your local repository on the same branch. You will need to `add` files and
-commit again. It is normally best to squash your changes into a single
-commit by doing it with `--amend` which will give you a chance to edit
-the message. If you do this you need to force `-f` the push back to
-your repo.
-
-```console
-$ git add...
-$ git commit --amend
-$ git status
-$ git push -f origin forchange
-```
-
-Once the reviewers are happy you can "Rebase and Merge" the Pull
-Request on GitHub, delete the branch there (it offers to do this when
-you do the merge). You can delete the branch in your local repository with:
-
-```console
-$ git checkout master
-$ git branch -D forchange
-```
-
-When a Pull Request contain multiple commits, those commits should be logically
-independent. Interactive rebase can be used to manipulate commits in a pull
-request to achieve that goal. Commonly, it might be used to squash commits that
-fix up issues reported during review back into the relevant commit.
-
-```console
-$ git rebase -i `git merge-base {current_branch} master`
-```
-
-Then, an editor will open. Follow the instructions given there, to reorder and
-combine commits, or to change the commit message. Then update the PR branch in
-the GitHub remote repository.
-
-```console
-$ git push -f origin HEAD
-```
-
-## Automatically adding a Signed-off-by line for your commits
-
-One option to avoid having to remember to add `-s` to `git commit` is to configure `git` to append it to your commit messages for you.
-This can be done using an executable hook.
-An appropriate hook can be installed by executing this from the root of your repository checkout:
-
-```console
-$ cat <<"EOF" > .git/hooks/prepare-commit-msg
-#!/bin/sh
-
-# Add a Signed-off-by line to the commit message if not already present.
-git interpret-trailers --if-exists doNothing --trailer \
-  "Signed-off-by: $(git config user.name) <$(git config user.email)>" \
-  --in-place "$1"
-EOF
-$ chmod +x .git/hooks/prepare-commit-msg
-```
-
-## Update your repository with changes in the lowRISC repo
-
-There is a little work to do to keep everything in sync. Normally you
-want to first get your local repository `master` branch up to date with the
-lowRISC repository (**upstream**) and then you use that to update your GitHub
-copy (**origin**).
-
-```console
-$ git checkout master
-$ git pull --ff-only upstream master
-$ git push origin
-```
-
-If you do this while you have changes on some other branch then before
-a Pull Request will work you need to be sure your branch merges
-cleanly into the new lowRISC repo. Assuming you got the local `master`
-branch up to date with the procedure above you can now **rebase** your
-changes on the new `master`. Assuming you have your changes on the local
-branch `forchange`:
-
-```console
-$ git checkout forchange
-$ git rebase master
-```
-
-If you are lucky this will just work, it unwinds your changes, gets
-the updated `master` and replays your changes. If there are conflicts
-then you need a big pot of coffee and patience (see next section).
-
-Once everything has rebased properly you can do
-
-
-```console
-$ git log
-```
-
-And see that the changes you commited on the branch are at the top of
-the log followed by the latest changes on the `master` branch.
-
-Note: be mindful about which branch you are working on.
-If you have added new commits to your local `master` branch, aligning it with `upstream` using `git pull --ff-only` may fail.
-An alternative is to use `git pull --rebase` in that case, which directly rebases your local `master` branch onto `upstream/master`.
-Do not use plain `git pull` or `git merge` commands, as they do not preserve a linear commit history.
-See also [this tutorial](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) to learn more about `git rebase` vs `git merge`.
-
-## Dealing with conflicts after a rebase
-
-If a rebase fails because of conflicts between your changes and the
-code you are rebasing to then git will leave your working directories
-in a bit of a mess and expect you to fix it. Often the conflict is
-simple (eg you and someone else added a new routine at the same place
-in the file) and resolution is simple (have both in the new
-output). Sometimes there is more to untangle if different changes were
-done to the same routine. In either case git has marked that you are
-in a conflict state and work is needed before you can go back to using
-your local git tree as usual.
-
-The git output actually describes what to do (once you are used to how
-to read it). For example:
-
-```console
-$ git rebase master
-First, rewinding head to replay your work on top of it...
-Applying: [util][pystyle] Clean Python style in single file tools
-Using index info to reconstruct a base tree...
-M       util/diff_generated_util_output.py
-M       util/build_docs.py
-Falling back to patching base and 3-way merge...
-Auto-merging util/build_docs.py
-CONFLICT (content): Merge conflict in util/build_docs.py
-Auto-merging util/diff_generated_util_output.py
-error: Failed to merge in the changes.
-Patch failed at 0001 [util][pystyle] Clean Python style in single file tools
-Use 'git am --show-current-patch' to see the failed patch
-
-Resolve all conflicts manually, mark them as resolved with
-"git add/rm <conflicted_files>", then run "git rebase --continue".
-You can instead skip this commit: run "git rebase --skip".
-To abort and get back to the state before "git rebase", run "git rebase --abort".
-```
-
-The last line of this gives the ultimate out. You can abort the rebase
-and figure some other way to proceed. As it says, this is done with:
-
-```console
-$ git rebase --abort
-```
-
-After executing this command you are back to a clean tree with your
-changes intact, but they are still based on whatever the earlier state
-of the repository was. Normally you will have to resolve the conflict
-sometime, but the escape hatch can be useful if you don't have time
-immediately!
-
-In the normal case, read the output to find the file with the
-problem. In this case `Merge conflict in util/build_docs.py` (The merge
-of `util/diff_generated_util_output.py` was successful even though it
-is mentioned in the middle of what looks like error output.)
-
-If the file is opened with an editor the points at which there are
-conflicts will have diff-style change information embedded in to them. For example:
-
-```console
-<<<<<<< HEAD
-import livereload
-
-import docgen.generate
-=======
-import docgen
-import livereload
->>>>>>> [util][pystyle] Clean Python style in single file tools
-
-```
-
-In this case, the upstream repository's copy of `util/build_docs.py`
-was modified to import `docgen.generate` rather than just `docgen`.
-Because git couldn't automatically merge that change with the one
-we made, it gave up. The code between `<<<<<<< HEAD` and `=======`
-represents the change in the upstream repository and the code between
-`=======` and `>>>>>>>` represents the change in our copy.
-
-These lines have to be edited to get the correct merged
-result and the diff markers removed. There may be multiple points in
-the file where fixes are needed. Once all conflicts have been
-addressed the file can be `git add`ed and once all files addressed the
-rebase continued.
-
-
-After the fix a status report will remind you where you are.
-
-```console
-$ git status
-rebase in progress; onto cb85dc4
-You are currently rebasing branch 'sastyle' on 'cb85dc4'.
-  (all conflicts fixed: run "git rebase --continue")
-
-Changes to be committed:
-  (use "git reset HEAD <file>..." to unstage)
-
-        modified:   diff_generated_util_output.py
-        modified:   build_docs.py
-
-Changes not staged for commit:
-  (use "git add <file>..." to update what will be committed)
-  (use "git checkout -- <file>..." to discard changes in working directory)
-
-        modified:   build_docs.py
-
-```
-
-This gives the same instructions as the original merge failure message
-and gives the comfort that all conflicts were fixed. To finish up you
-need to follow the instructions.
-
-```console
-$ git add build_docs.py
-$ git rebase --continue
-Applying: [util][pystyle] Clean Python style in single file tools
-```
-
-If there were more than one patch outstanding (which isn't usual if
-you use the `commit --amend` flow) then you may get subsequent
-conflicts following the `rebase --continue` as other patches are
-replayed.
-
-You can check the rebase worked as expected by looking at the log to
-see your branch is one commit (or more if there were more) ahead of
-the `master` branch.
-
-```console
-$ git log
-
-commit dd8721d2b1529c575c4aef988219fbf2ecd3fd1b (HEAD -> sastyle)
-Author: Mark Hayter <mark.hayter@gmail.com>
-Date:   Thu Jan 10 09:41:20 2019 +0000
-
-    [util][pystyle] Clean Python style in single file tools
-
-    Result of lintpy.py --fix on the diff and build_docs tools
-
-    Tested with  ./diff_generated_util_output.py master
-
-commit cb85dc42199e925ad09c45d33f6483a14764b93e (upstream/master, origin/master, origin/HEAD, master)
-
-```
-
-This shows the new commit (`HEAD` of the branch `sastyle`) and the
-preceding commit is at the `master` branch (and at the same point as
-`master` on both `origin` and `upstream` so everything is in sync at
-`master`).
-
-At this point the conflicts have been cleared and the local repository can
-be used as expected.
-
-You may find it useful to change the default for the way git reports conflicts in a file. See [Take the pain out of git conflict resolution: use diff3](https://blog.nilbus.com/take-the-pain-out-of-git-conflict-resolution-use-diff3/)
-
-## Downloading a pull request to our local repo
-
-With the commands below, you can checkout a pull request from the upstream repository to
-your local repo.
-
-```console
-$ git fetch upstream pull/{ID}/head:{BRANCH_NAME}
-$ # e.g. git fetch upstream pull/5/head:docgen_review
-$ git checkout {BRANCH_NAME}
-```
-
-### Applying the pull request to the local commit
-
-In some cases, you might need to apply a pull request on top of your local commits.
-For instance, if one user prepares a verification test case and finds a bug.
-Another user fixed it and created a pull request. The first user needs
-to test if the fix works on the new verification environment.
-
-In this case, it is recommanded to create a new branch on top of the local
-commit and `rebase`, `cherry-pick`, or `merge` the pull request. Assume you have a branch
-name 'br_localfix' which has an update for the verification environment. If the
-other user created a pull request with ID #345, which has a fix for the
-design bug, then you can apply the pull request into new branch
-'br_localandremote' with the following three methods:
-
-* The `rebase` method:
-
-    ```console
-    $ git checkout -b br_localandremote br_localfix
-    $ git fetch upstream pull/345/head:pr_345
-    $ git rebase pr_345
-    ```
-
-* The `cherry-pick` method:
-
-    ```console
-    $ git checkout -b br_localandremote br_localfix
-    $ git fetch upstream pull/345/head:pr_345
-    $ # find the commit ID from pr_345 that you want to merge: b345232342ff
-    $ git cherry-pick b345232342ff
-    ```
-* The `merge` method:
-
-    ```console
-    $ git fetch upstream pull/345/head:pr_345
-    $ git checkout -b br_localandremote br_localfix
-    $ git merge pr_345
-    ```
-
-The `rebase` method is more convenient than `cherry-pick` if you have more
-than one commit ID to merge. The `merge` method can only be used for local
-testing as the upstream lowRISC repository does not allow merge commits.
-Sometimes, applying other contributor's pull request can result in conflicts
-if both commits have the same change. In that case, you should resolve the conflicts
-and continue the merge. Section [Dealing with conflicts after a rebase](#dealing-with-conflicts-after-a-rebase)
-has detailed guidance on how to solve conflicts.
-
-## Creating updates on top of a pending pull request
-
-In some cases, you might want to directly change other contributor's pull
-request. The process is quite complicated so please follow the instruction below
-step-by-step with caution:
-
-* Step 1: Checkout the other pull request branch
-
-    ```console
-    $ git fetch upstream pull/{ID}/head:{BRANCH_NAME}
-    $ git checkout {BRANCH_NAME}
-    ```
-
-* Step 2: Make necessary changes
-
-    ```console
-    $ git add...
-    $ git commit -m "Add CFG examples to UART specification"
-    ```
-
-* Step 3: Create your GitHub branch for the pull request
-
-    ```console
-    $ git push -u origin {BRANCH_NAME}:<remote_branch_name>
-    ```
-
-    You can use any branch name for the pull request.
-    If you want to the created branch name same as local branch name, this can
-    simply be:
-
-    ```console
-    $ git push -u origin HEAD
-    ```
-
-* Step 4: Create a pull request into the other contributor's forked repository
-
-    To create a pull request in other's forked repository, you can follow the same method
-    as creating a pull request as section [Working in your local
-    repo](#working-in-your-local-repo) explained in details.
-    Once the other contributor's pull request is merged into the upstream
-    repository, you will need to create a pull request in that upstream repository
-    instead.
-
-These are all the steps needed. Once your pull request is reviewed and merged, you will
-be able to see that the commit is also visible in the original pull request.
-
-## Dealing with email notifications
-In your profile settings, under notifications, you can set up whether you get email notifications and what your default email address is.
-Under the "Custom routing" you can choose to get emails from lowRISC repositories to a different email address in case you're active in multiple organizations.
-You can also filter GitHub emails based on the To and CC fields.
-For example, notifications related to the OpenTitan repository will be sent to `opentitan@noreply.github.com`.
-The CC field can include any of the following depending on the type of notification:
-- `assign@noreply.github.com`
-- `author@noreply.github.com`
-- `ci-activity@noreply.github.com`
-- `comment@noreply.github.com`
-- `mention@noreply.github.com`
-- `subscribed@noreply.github.com`
-These can help you set up automatically sorting emails into specific folders.
diff --git a/doc/ug/hw_design.md b/doc/ug/hw_design.md
deleted file mode 100644
index ee79a8b..0000000
--- a/doc/ug/hw_design.md
+++ /dev/null
@@ -1,137 +0,0 @@
----
-title: "Designing Hardware"
----
-
-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]({{< 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.
-There are times when a more regimented approach is warranted, and those will be highlighted in this document.
-Feedback and improvements are welcome.
-
-
-## Stages of a Design
-
-The life stages of a design within the OpenTitan are described in the [Hardware Development Stages]({{< relref "doc/project/development_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.
-
-
-## Concept and RFC
-
-The concept of a design might come from a variety of inspirations: a known required module already slated for future product need; a new design needed for a proposed product feature; an inspiration worthy of being discussed more broadly; perhaps even a new approach to an existing design (higher performance; more secure; etc).
-Regardless of the inspiration, the concept should be codified into a brief proposal with basic features.
-This is as opposed to minor modification proposals to an existing design, which can be handled as a GitHub pull request or issue filing associated with the existing design.
-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)]({{< relref "../project/rfc_process.md" >}}) process, which handles all such proposals.
-If the RFC potentially contains information that could be certification-sensitive (guidance to be shared), send a note to security@opentitan.org first for feedback.
-The OpenTitan Technical Committee may be able to suggest other collaborators to help with early stage review.
-
-An example of a canonical RFC will be provided *here* (TODO).
-
-
-## 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]({{< 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.
-
-An example of a canonical detailed specification is the pinmux specification which can be found in the TeamDrive under TechnicalSpecifications --> deprecated, for those that have access to that resource.
-(Google Docs that have been converted into Markdown on GitHub are archived here).
-
-Note that when developing OpenTitan security IP, designers should follow the [Secure Hardware Design Guidelines]({{< relref "doc/security/implementation_guidelines/hardware" >}}).
-
-## Specification Publication
-
-Once the specification is published to the wider team, the author(s) need to address input, comments, questions, etc., continuing to hone the proposal.
-Other team members should also feel empowered to help address these questions and feedback.
-It is acceptable to keep an unanswered-questions section of the document and move forward where there is agreement.
-
-
-## Initial Skeleton Design
-
-In parallel with publication and review of the specification, the initial skeleton design can commence.
-There are many ways to get past this "big bang" of necessary implementation and get it through eventual code review.
-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]({{< 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]({{< 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) for the AES module.
-
-As part of the GitHub filing process, the Google Doc specification must be converted into a Markdown specification.
-(Tip: there are Google Doc add-ons that can convert the specification into Markdown format).
-Once this is completed, any specifications on the Team Drive should be moved to the deprecated section of the drive, with a link at the top indicating that the document is for historical purposes only.
-This applies only for those specifications that originated on the Drive.
-
-
-## Full Design
-
-As the design develops within the OpenTitan repository, it transitions into "D0", "D1", etc., [design stages]({{< relref "doc/project/development_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
-
-Once an IP block is ready, it can be included in the top-level design.
-To do so, follow the steps below.
-
-* Get an agreement on if and how the IP block should be integrated.
-* Ensure that the IP block is of acceptable quality.
-* Ensure that the top level simulation is not adversely affected.
-* Open a Pull Request with the necessary code changes.
-
-If it is not clear on how to proceed, feel free to file an issue requesting assistance.
-
-## Change of Interrupts
-
-Changing the interrupts of an IP block needs some extra work in addition to the RTL change.
-1. If a DV environment exists, its testbench (`tb.sv`) will need the ports connected.
-1. If the IP block has been instantiated in a top-level, that top-level will need re-generating to include the new ports.
-1. The auto-generated DIFs will need re-generating.
-1. The PLIC test should also be updated.
-
-Item 1 is reasonably self-explanatory.
-The sections below show how to do items 2-4.
-
-### Update TOP
-
-```console
-$ make -C hw top
-```
-
-After you revise the IP `.hjson`, IP top module, IP register interface, and DV testbench `tb.sv`, you can re-generate top-levels with the command above.
-This reads the `.hjson` file and updates the interrupt signal and re-generates the PLIC module if needed.
-
-### New DIF
-
-Unfortunately, re-generating TOP does not resolve everything.
-If the interrupt names are changed or new interrupts are introduced, the DIF for the IP block should be re-generated.
-
-```console
-$ ./util/make_new_dif.py --mode=regen --only=autogen
-```
-
-The command above updates the DIF (auto-generated part) under `sw/device/lib/dif/`.
-
-If DIF for the IP block already uses the interrupt enums inside, you need to manually revise the reference.
-In most cases, the interrupt name is `kDif`, followed by the IP name in PascalCase (e.g `SpiDevice`), then `Irq`, then the interrupt name in PascalCase (e.g. `RxFull`).
-For example, `kDifSpiDeviceIrqRxFull`.
-To refer to an interrupt in the top-level PLIC enum, the format is `kTopEarlgreyPlicIrqId`, followed by the IP name in PascalCase, then the interrupt name in PascalCase.
-For example, `kTopEarlgreyPlicIrqIdSpiDeviceRxFull`.
-
-### PLIC Unittest
-
-If the number of interrupts has changed, you need to revise the SW unit test manually.
-
-Open `sw/device/lib/dif/dif_rv_plic_unittest.cc` and change the `RV_PLIC_PARAM_NUM_SRC` macro to the current interrupt number.
-Then, find `RV_PLIC_IE0_*_REG_OFFSET`, `RV_PLIC_IP0_*_REG_OFFSET` and revise the Bit value or add lines below if new `IE`, `IP` registers are added.
diff --git a/doc/ug/otbn_sw.md b/doc/ug/otbn_sw.md
deleted file mode 100644
index 4eb70ec..0000000
--- a/doc/ug/otbn_sw.md
+++ /dev/null
@@ -1,70 +0,0 @@
----
-title: "Writing and building software for OTBN"
----
-
-OTBN is the OpenTitan Big Number accelerator and this document describes how to write and build software for it.
-The [OTBN reference manual]({{< relref "hw/ip/otbn/doc" >}}) describes the hardware and associated ISA.
-
-Since OTBN is designed for cryptographic offload, not general computation, it has no C compiler.
-However, it does have the usual binutils programs: an assembler, linker and disassembler.
-The core of OTBN's instruction set is based on RV32I, the RISC-V base integer instruction set.
-As such, we implement the toolchain as a thin wrapper around RISC-V binutils.
-
-## Assembly format and examples
-
-The OTBN ISA and programmer model are described in the [OTBN reference manual]({{< relref "hw/ip/otbn/doc" >}}).
-In particular, note that the register `x1` has special stack-like behaviour.
-There are some example programs at `sw/otbn/code-snippets`.
-These range from simple examples of how to use pseudo-operations up to example cryptographic primitives.
-
-For specific formatting and secure coding guidelines, see the [OTBN style guide]({{< relref "doc/rm/otbn_style_guide" >}}).
-
-## The tools
-
-### Assembler
-
-The OTBN assembler is called `otbn_as.py` and can be found at `hw/ip/otbn/util/otbn_as.py`.
-This has the same command line interface as `riscv32-unknown-elf-as` (indeed, it's a wrapper around that program).
-The only difference in default flags is that `otbn_as.py` passes `-mno-relax`, telling the assembler not to request linker relaxation.
-This is needed because one of these relaxations generates GP-relative loads, which assume `x3` is treated as a global pointer (not true for OTBN code).
-
-To assemble some code in `foo.s` to an ELF object called `foo.o`, run:
-```shell
-hw/ip/otbn/util/otbn_as.py -o foo.o foo.s
-```
-
-### Linker
-
-The OTBN linker is called `otbn_ld.py` and can be found at `hw/ip/otbn/util/otbn_ld.py`.
-This is a thin wrapper around `riscv32-unknown-elf-ld`, but supplies a default linker script that matches the OTBN memory layout.
-This linker script creates `.start`, `.text` and `.data` output sections.
-The `.start` and `.text` sections go to IMEM, with `.start` coming first.
-The `.data` section goes to DMEM.
-Since OTBN has a strict Harvard architecture with IMEM and DMEM both starting at address zero, the `.start` and the `.data` sections will both start at VMA zero.
-The instruction and data segments have distinct LMAs (for addresses, see the IMEM and DMEM windows at `hw/ip/otbn/data/otbn.hjson`).
-
-Since the entry point for OTBN is always address zero, the entry vector should be the one and only thing in the `.start` section.
-To achieve that, put your entry point (and nothing else) in the `.text.start` input section like this:
-```asm
-.section .text.start
-  jal x0, main
-
-.text
-  ...
-```
-This ensure that, even if there are multiple objects being linked together, the intended entry point will appear in the right place.
-
-To link ELF object files to an OTBN ELF binary, run
-```shell
-hw/ip/otbn/util/otbn_ld.py -o foo foo0.o foo1.o foo2.o
-```
-
-### Objdump
-
-To disassemble OTBN code, use `otbn_objdump.py`, which can be found at `hw/ip/otbn/util/otbn_objdump.py`.
-This wraps `riscv32-unknown-elf-objdump`, but correctly disassembles OTBN instructions when run with the `-d` flag.
-
-To disassemble the ELF binary linked in the previous section, run
-```shell
-hw/ip/otbn/util/otbn_objdump.py -d foo
-```
diff --git a/doc/ug/rust_for_c.md b/doc/ug/rust_for_c.md
deleted file mode 100644
index c8c5acd..0000000
--- a/doc/ug/rust_for_c.md
+++ /dev/null
@@ -1,2346 +0,0 @@
----
-title: "Rust for Embedded C Programmers"
----
-
-## Foreword
-
-This document is intended as an introduction to Rust, targeted at engineers with deep exposure to embedded systems C, and little to no experience with C++ and no knowledge of Rust.
-This document will:
-
-*   Provide Rust analogues of everything in the embedded C toolkit.
-*   Discuss how some of those analogues might be importantly different from C's.
-*   Point out where Rust's memory and execution models materially differ from C.
-*   Introduce Rust-specific features that are either key to using Rust at all or otherwise extremely useful (references, lifetimes, generics, traits).
-
-While this document takes a casual approach to Rust, language-lawyer-ey notes are included in footnotes.
-These are not required to get the most out of the document.
-
-Playing with the compiler, and seeing what compiles, is a great way to learn Rust.
-`rustc`, the standard Rust compiler, has excellent error messages.
-Matt Godbolt's [Compiler Explorer](https://rust.godbolt.org/) is useful for getting a feel for the assembly that Rust produces.
-The [Rust Playground](https://play.rust-lang.org/) can also be used to see what Rust code does when _executed_, though it is even more limited.
-
-This document is written with a view towards embedded development.
-We will not be discussing any non-embedded Rust features: see [https://rust-embedded.github.io/book/intro/no-std.html](https://rust-embedded.github.io/book/intro/no-std.html)[^1].
-
-C++ users are cautioned: Rust shares a lot of terminology and concepts (ownership, lifetimes, destructors, polymorphism) with it, but Rust's realization of them tends to have significantly different semantics.
-Experience in C++ should not be expected to translate cleanly.
-
-
-## What is Rust? {#what-is-rust}
-
-Rust is a general-purpose programming language with a focus on maximum programmer control and zero runtime overhead, while eliminating the sharp edges traditionally associated with such languages.
-It is also sometimes called a "systems language".
-
-Syntactically and philosophically, Rust most resembles C++ and ML (a family of functional programming languages), though semantically it is very different from both.
-Rust is the first popular, well-supported language that provides absolute memory safety[^2] without the use of automatic reference counting or a garbage collector.
-This is primarily achieved through a technology called the _borrow checker_, which uses source code annotations to statically prove that memory is not accessed after it is no longer valid, with no runtime checking.
-
-Rust compiles to native code and rivals C and C++ for memory and compute performance, and can seamlessly integrate with anything using a C calling convention.
-It also statically eliminates a large class of memory bugs associated with security vulnerabilities.
-The Rust toolchain is built on LLVM, so all work towards LLVM performance is surfaced in Rust.
-
-Rust also contains a special dialect called Unsafe Rust, which disables some static checking for the rare times when it is necessary to perform low-level operations.
-We will make reference to this dialect throughout the document.
-
-
-## The Rust Toolchain {#the-rust-toolchain}
-
-A complete Rust toolchain consists of a few major parts:
-
-*   `rustc`, the Rust compiler[^3].
-*   `rustup`, the Rust toolchain installer[^4].
-*   `cargo`, a build system for Rust (though rustc can be invoked directly or by other tools).
-*   `std` and `core`, the standard libraries.
-
-The Rust toolchain is on a six-week release cycle, similar to Chrome's: every six weeks, a release branch is cut as the next beta, and after six weeks it becomes the next stable.
-Nightly Rust is cut from `master` every day; it is on nightly that unstable features can be enabled.
-Some unstable features[^5] are very useful for embedded, so it is not uncommon for embedded Rust projects to use a nightly compiler.
-
-`rustup` is used for managing Rust installations.
-This is mostly necessary because Rust releases too frequently for operating system package managers to keep up, and projects can pin specific versions of Rust.
-When the Rust toolchain is installed through rustup, components such as rustc and cargo will be aware of it; `rustc +nightly-2020-03-22` will defer to rustup to download and execute the `rustc` nightly built on March 22.
-A file named `rust-toolchain` in a project directory can be used to the same effect.[^6]
-
-Cargo is a build system/package manager for Rust.
-It can build projects (that is, directories with a `Cargo.toml` file in them) and their dependencies automatically.
-Individual units of compilation in Rust are called "crates", which are either static libraries[^7] (i.e., comparable to a `.a` file) or fully linked native binaries.
-This is in contrast to C, where each `.c` file generates a separate object file.[^8] Rust also does not have headers, though it provides a module system for intra-crate code organization that will be discussed later on.
-Tock boards are a good example of what a more-complex cargo file looks like: [https://github.com/tock/tock/blob/master/boards/opentitan/Cargo.toml](https://github.com/tock/tock/blob/master/boards/opentitan/Cargo.toml)
-
-Some useful `cargo` subcommands include:
-*   `cargo check` runs rustc's checks, but stops before it starts emitting code and optimizing it.
-    This is useful for checking for errors during development.
-*   `cargo build` builds a library or binary, depending on the crate type.
-*   `cargo clippy` runs the Rust linter, Clippy.
-*   `cargo doc --open` builds crate documentation and then opens it in the browser.
-*   `cargo fmt` runs the Rust formatter[^9].
-
-Also, the contents of the `RUSTFLAGS` environment variable are passed to `rustc`, as a mechanism for injecting flags.
-
-The Rust standard library, like libc, is smaller in embedded environments.
-The standard library consists of three crates: `core`[^10], `alloc`, and `std`.
-`core`, sometimes called libcore, is all of the fundamental definitions, which don't rely on operating system support.
-Nothing in `core` can heap-allocate.
-`alloc` does not require OS support, but does require `malloc` and `free` symbols.
-`std` is `core` + `alloc`, as well as OS APIs like file and thread support.
-The `#[no_std]` pragma disables `std` and `alloc`, leaving behind `core`.
-Throughout this document, we will only use `core` types, though we may refer to them through the `std` namespace (they're aliases).
-That is, we may refer to `std::mem::drop`, though in `#[no_std]` code it must be named `core::mem::drop`.
-
-rustc has a number of flags. The most salient of these are:
-
-
-
-*   `--emit asm` and `--emit llvm-ir`, useful for inspecting compiler output.
-*   `--target`, which sets the cross-compilation target.
-     It has a similar role to Clang's `-target`, `-march`, and `-mabi` flags.
-     It accepts a target definition (which in many cases resembles an LLVM target triple) that defines the platform.
-     For example, OpenTitan software uses the `riscv32imc-unknown-none-elf` target.
-     Using a target that isn't the host target (e.g.,`x86_64-unknown-linux-musl`) requires installing the corresponding standard library build with `rustup component install rust-std-<target>`.
-     See `rustc --print targets`.
-     `--target` is also accepted directly by Cargo, unlike most rustc flags.
-*   `-C link-arg`, equivalent to Clang's `-T`.
-*   `-C opt-level`, equivalent to Clang's `-O` (we mostly use `-C opt-level=z` for embedded).
-*   `-C lto`, equivalent to Clang's `-flto`.
-*   `-C force-frame-pointers`, equivalent to Clang's `-fno-omit-frame-pointer`.
-*   `-D warnings` is roughly equivalent to `-Werror`.
-
-Other interesting flags can be found under `rustc -C help` and, on nightly, under `rustc -Z help`.
-
-
-## Part I: Rewriting your C in Rust {#part-i-rewriting-your-c-in-rust}
-
-Before diving into Rust's specific features, we will begin by exploring how C concepts map onto Rust, as well as Unsafe Rust, a dialect of Rust that is free of many of Rust's restrictions, but also most of its safety guarantees.
-
-
-### Types {#types}
-
-Rust and C have roughly the same approach to types, though Rust has few implicit conversions (for example, it lacks integer promotion like C).
-In this section, we will discuss how to translate C types into Rust types.
-
-
-#### Integers {#integers}
-
-Rust lacks C's `int`, `long`, `unsigned`, and other types with an implementation-defined size.
-Instead, Rust's primitive integer types are exact-size types: `i8`, `i16`, `i32`, `i64`, and `i128` are signed integers of 8, 16, 32, 64, and 128 bits, respectively, while `u8`, `u16`, `u32`, `u64`, and `u128` are their unsigned variants.
-Rust also provides `isize` and `usize`, which correspond to `intptr_t` and `uintptr_t`[^11].
-Alignment requirements are exactly the same as in C.
-
-Rust supports all of the usual binary operations on all of its integer types[^12][^13], though you can't mix different types when doing arithmetic, and, unlike in C, there is no integer promotion.
-Overflow in Rust is different from C[^14]: it is implementation-defined, and must either crash the program[^15] or wrap around[^16].
-Casting is done with the `as` keyword, and behaves exactly the same way as in C: `(uint8_t) x` is written `x as u8`. Integer types never implicitly convert between each other, even between signed and unsigned variants.
-
-Rust has the usual integer literals: `123` for decimal, `0xdead` for hex, `0b1010` for binary, and `0o777` for octal.
-Underscores may be arbitrarily interspersed throughout an integer literal to separate groups of digits: `0xdead_beef`, `1_000_000`.
-They may also be suffixed with the name of a primitive integer type to force their type: `0u8`, `0o777i16`, `12_34_usize`; they will otherwise default to whatever type inference (more on this later) chooses, or `i32` if unconstrained.
-
-Rust also has a dedicated `bool` type.
-It is not implicitly convertible with integers, and is otherwise a `u8` that is guaranteed to have either the value `0x00` or `0x01`[^17], with respective literals `false` and `true`.
-`bool` supports all of the bitwise operations, and is the only type compatible with short-circuiting `&&` and `||`.
-It is also the only type that can be used in `if` and `while` conditions.
-
-Integers have an extensive set of built-in operations for bit-twiddling, exposed as methods, such as `x.count_zeros()` and `x.next_power_of_two()`[^18].
-See [https://doc.rust-lang.org/std/primitive.u32.html](https://doc.rust-lang.org/std/primitive.u32.html) for examples.
-
-#### Structs and Tuples {#structs-and-tuples}
-
-Structs are declared almost like in C:
-```rust
-struct MyStruct {
-  pub foo: i32,
-  pub bar: u8,
-}
-```
-Rust has per-field visibility modifiers (`pub`); we'll give visibility a more thorough treatment later.
-
-Struct values can be created using an analogue of C's designated initialization[^19] syntax:
-```rust
-MyStruct { foo: -42, bar: 0xf, }
-```
-
-Rust structs are not laid out like C structs, though; in fact, Rust does not specify the layout of its structs[^20].
-A C struct can be specified in Rust using the `#[repr(C)]` attribute:
-```rust
-#[repr(C)]
-struct MyCStruct {
-  a: u8,
-  b: u32,
-  c: u8,
-}
-```
-This is guaranteed to lay out fields in declaration order, adding padding for alignment.
-`#[repr(Rust)]` is the implicit default.
-`#[repr(packed)]` is analogous to `__attribute__((packed))`, and will not produce any padding[^21].
-The alignment of the whole struct can be forced to a larger value using `#[repr(align(N))]`, similar to `_Alignas`.
-
-Fields can be accessed using the same dot syntax as C: `my_struct.foo`, `my_struct.bar = 5;`.
-
-Rust also provides "tuple-like structs", which are structs with numbered, rather than named, fields:
-```rust
-struct MyTuple(pub u32, pub u8);
-```
-
-Fields are accessed with a similar dot syntax: `tuple.0`, `tuple.1`, and are constructed with function-call-like syntax: `MyTuple(1, 2)`.
-Other than syntax, they are indistinguishable from normal structs. The fields on a tuple-like struct may be omitted to declare a zero-byte[^22] struct:
-```rust
-struct MyEmpty;
-```
-
-Anonymous versions of tuples are also available: `(u32, u8)`.
-These are essentially anonymous structs with unnamed fields.
-The empty tuple type, `()`, is called "unit", and serves as Rust's `void` type (unlike `void`, `()` has exactly one value, also named `()`, which is zero-sized).
-Rust has another void-like type, `!`, which we'll discuss when we talk about functions.
-
-If every field of a struct can be compared with `==`, the compiler can generate an equality function for your struct:
-```rust
-#[derive(PartialEq, Eq)]
-struct MyStruct {
-  a: u32,
-  b: u8,
-}
-```
-
-This makes it possible to use `==` on `MyStruct` values, which will just perform field-wise equality.
-The same can be done for ordering operations like `<` and `>=`: `#[derive(PartialOrd, Ord)]` will define comparison functions that compare structs lexicographically.
-
-#### Enums and Unions
-
-Much like C, Rust has enumerations for describing a type with a fixed set of values:
-```rust
-enum MyEnum {
-    Banana, Apple, Pineapple,
-}
-```
-
-Unlike C, though, `MyEnum` is a real type, and not just an alias for an integer type.
-Also unlike C, the enum's variants are not dumped into the global namespace, and must instead be accessed through the enum type: `MyEnum::Banana`.
-Note that, unlike structs, the variants of an enum are automatically public.
-
-While Rust does represent enum values with integers (these integers are called _discriminants_), the way they're laid out is unspecified.
-To get an enum whose discriminants are allocated like in C, we can employ a `repr` attribute:
-```rust
-#[repr(C)]
-enum MyCEnum {
-  Banana = 0,
-  Apple = 5,
-  Pineapple = 7,
-}
-```
-Unlike C, though, Rust will only guarantee discriminant values that are explicitly written down.
-Such enums may be safely cast into integer types (like `MyCEnum::Apple as u32`), but not back: the compiler always assumes that the underlying value of a `MyCEnum` is `0`, `5`, or `7`, and violating this constraint is Undefined Behavior (UB)[^23].
-If we want to require that an enum is an exact integer width, we can use `#[repr(T)]`, where `T` is an integral type like `u16` or `i8`.
-
-Unions in Rust are a fairly recent feature, and are generally not used for much in normal code.
-They are declared much like structs:
-```rust
-union MyUnion {
-  pub foo: i32,
-  pub bar: u8,
-}
-```
-and created much like structs:
-```rust
-MyUnion { bar: 0xa, }  // `bar` is the active variant.
-```
-
-Assigning to union variants is the same as in structs, but reading a variant requires Unsafe Rust, since the compiler can't prove that you're not reading uninitialized or invalid data[^24], so you'd need to write
-```rust
-unsafe { my_union.bar }  // I assert that bar is the active variant.
-```
-Unions also have restrictions on what types can be used as variants, due to concerns about destructors.
-
-Since unions are so useful in C, but utterly unsafe, Rust provides built-in _tagged unions_[^25], which are accessed through the `enum` syntax:
-```rust
-enum MyEnum {
-  FooVariant { foo: i32 },
-  BarVariant(u8),
-}
-```
-
-Tagged-union enum variants use the same syntax as Rust structs; enum consists of a tag value (the discriminant) big enough to distinguish between all the variants, and a compiler-tracked union of variants.
-However, effective use of such enums requires _pattern matching_, which requires its own section to explain.
-We'll see these enums again when we discuss patterns.
-
-Just like with structs, `#[derive]` can be used on enums to define comparison operators, which are defined analogously to the struct case.
-
-#### Arrays {#arrays}
-
-Rust arrays are just C arrays: inline storage of a compile-time-known number of values.
-`T[N]` in C is spelled `[T; N]` in Rust.
-Arrays are created with `[a, b, c]` syntax, and an array with lots of copies of the same value can be created using `[0x55u8; 1024]`[^26].
-A multi-dimensional array can be declared as an array of arrays: `[[T; N]; M]`.
-
-Array elements can be accessed with `x[index]`, exactly like in C.
-Note, however, that Rust automatically inserts bounds checks around every array access[^27]; failing the bounds check will trigger a panic in the program.
-Unsafe Rust can be used to cheat the bounds check when it is known (to the programmer, not rustc!) to be unnecessary to perform it, but when it is performance critical to elide it.
-
-Rust arrays are "real" types, unlike in C. They can be passed by value into functions, and returned by value from functions.
-They also don't decay into pointers when passed into functions.
-
-#### Pointers {#pointers}
-
-Like every other embedded language, Rust has pointers.
-These are usually referred to as _raw pointers_, to distinguish them from the myriad of smart pointer types.
-Rust spells `T*` and `const T*` as `*mut T` and `*const T`.
-Unlike in C, pointers do not need to be aligned to their pointee type[^28] until they are dereferenced (like C, Rust assumes that all pointer reads/writes are well-aligned).
-
-Note that C's type-based strict aliasing does not exist in Rust[^29].
-As we'll learn later, Rust has different aliasing rules for references that are both more powerful and which the compiler can check automatically.
-
-`usize`, `isize`, and all pointer types may be freely cast back and forth.[^30] Null pointers may be created using the `std::ptr::null()` and `std::ptr::null_mut()` functions[^31].
-Rust pointers do not support arithmetic operators; instead, a method fills this role: instead of `ptr + 4`, write `ptr.offset(4)`.
-Equality among pointers is simply address equality.
-
-Pointers can be dereferenced with the `*ptr` syntax[^32], though this is Unsafe Rust and requires uttering `unsafe`.
-When pointers are dereferenced, they must be well-aligned and point to valid memory, like in C; failure to do so is UB.
-Unlike in C, the address-of operator, `&x`, produces a reference[^33], rather than a pointer.
-`&x as *const T` will create a pointer, instead.
-
-Pointer dereference is still subject to move semantics, like in normal Rust[^34].
-the `read()` and `write()` methods on pointers can be used to ignore these rules[^35].
-`read_unaligned()`[^36] and `write_unaligned()`[^37] can be used to perform safe[^38] unaligned access, and `copy_to()`[^39] and `copy_nonoverlapping_to()`[^40] are analogous to `memmove()` and `memcpy()`, respectively.
-See [https://doc.rust-lang.org/std/primitive.pointer.html](https://doc.rust-lang.org/std/primitive.pointer.html) for other useful pointer methods.
-Volatile operations are also performed using pointer methods, which are discussed separately later on.
-
-Since all of these operations dereference a pointer, they naturally are restricted to Unsafe Rust.
-
-As we will discover later, Rust has many other pointer types beyond raw pointers.
-In general, raw pointers are only used in Rust to point to potentially uninitialized memory[^41] and generally denote _addresses_, rather than references to actual memory.
-For that, we use _references_, which are discussed much later.
-
-We will touch on function pointers when we encounter functions.
-
-
-### Items {#items}
-
-Like C, Rust has globals and functions.
-These, along with the type definitions above, are called _items_ in the grammar, to avoid confusion with C's declaration/definition distinction.
-Unlike C, Rust does not have forward declaration or declaration-order semantics; everything is visible to the entire file.
-Items are imported through dedicated import statements, rather than textual inclusion; more on this later.
-
-
-#### Constants and Globals {#constants-and-globals}
-
-Rust has dedicated syntax for compile-time constants, which serve the same purpose as `#define`d constants do in C.
-Their syntax is
-```rust
-const MY_CONSTANT: u32 = 0x42;
-```
-The type is required here, and the right-hand side must be a _constant expression_, which is roughly any combination of literals, numeric operators, and `const fn`s (more on those in a moment).
-
-Constants do not exist at runtime.
-They can be thought of best as fixed expressions that get copy+pasted into wherever they get used, similar to `#define`s and `enum` declarators in C.
-It is possible to take the address of a constant, but it is not guaranteed to be consistent.[^42]
-
-Globals look like constants, but with the keyword `static`[^43]:
-```rust
-static MY_GLOBAL: u8 = 0x00;
-static mut MY_MUTABLE_GLOBAL: Foo = Foo::new();
-```
-Globals are guaranteed to live in `.rodata`, `.data`, or `.bss`, depending on their mutability and initialization.
-Unlike constants, they have unique addresses, but as with constants, they must be initialized with constant expressions.[^44]
-
-Mutable globals are particularly dangerous, since they can be a source of data races in multicore systems.
-Mutable globals can also be a source other racy behavior due to IRQ control flow.
-As such, reading or writing to a mutable global, or creating a reference to one, requires Unsafe Rust.
-
-
-#### Functions {#functions}
-
-In C and Rust, functions are the most important syntactic construct. Rust declares functions like so:
-```rust
-fn my_function(x: u32, y: *mut u32) -> bool {
-  // Function body.
-}
-```
-The return type, which follows the `->` token, can be omitted when it is `()` ("unit", the empty tuple), which serves as Rust's equivalent of the `void` type.
-Functions are called with the usual `foo(a, b, c)` syntax.
-
-The body of a function consists of a list of statements, potentially ending in an expression; that expression is the function's return value (no `return` keyword needed).
-If the expression is missing, then `()` is assumed as the return type. Items can be mixed in with the statements, which are local to their current scope but visible in all of it.
-
-Rust functions can be marked as `unsafe fn`.
-This means the function cannot be called normally, and must instead be called using Unsafe Rust.
-The body of an `unsafe fn` behaves like an `unsafe` block; we'll go more into this when we discuss Unsafe Rust in detail.
-
-Rust functions have an unspecified calling convention[^45].
-To declare a function with a different calling convention, the function is declared `extern "ABI" fn foo()`, where ABI is a supported ABI.
-`"C"` is the only one we really care about, which switches the calling convention to the system's C ABI. The default, implicit calling convention is `extern "Rust"`[^46].
-
-Marking functions as `extern` does not disable name mangling[^47]; this must be done by adding the `#[no_mangle]` attribute to the function.
-Unmangled functions can then be called by C, allowing a Rust library to have a C interface.
-A mangled, `extern "C"` function's main use is to be turned into a function pointer to pass to C.
-
-Function pointer types look like functions with all the variable names taken out: `fn(u32, *mut u32) -> bool`.
-Function pointers cannot be null, and must always point to a valid function with the correct ABI.
-Function pointers can be created by implicitly converting a function into one (no `&` operator).
-Function pointers also include the `unsafe`-ness and `extern`-ness of a function: `unsafe extern "C" fn() -> u32`.
-
-Functions that never return have a special return type `!`, called the "never type".
-This is analogous to the `noreturn` annotation in C.
-However, using an expression of type `!` is necessarily dead code, and, as such, `!` will implicitly coerce to all types (this simplifies type-checking, and is perfectly fine since this all occurs in provably-dead code).
-
-Functions can also be marked as `const`.
-This makes the function available for constant evaluation, but greatly restricts the operations available.
-The syntax available in `const` functions increases in every release, though.
-Most standard library functions that can be `const` are already `const`.
-
-
-#### Macros {#macros}
-
-Rust, like C, has macros.
-Rust macros are much more powerful than C macros, and operate on Rust syntax trees, rather than by string replacement.
-Macro calls are differentiated from function calls with a `!` following the macro name.
-For example, `file!()` expands to a string literal with the file name.
-To learn more about macros, see [https://danielkeep.github.io/tlborm/book/index.html](https://danielkeep.github.io/tlborm/book/index.html).
-
-
-#### Aliases {#aliases}
-
-Rust has `type`, which works exactly like `typedef` in `C`.
-Its syntax is
-```rust
-type MyAlias = u32;
-```
-
-### Expressions and Statements {#expressions-and-statements}
-
-Very much unlike C, Rust has virtually no statements in its grammar: almost everything is an expression of some kind, and can be used in expression context.
-Roughly speaking, the only statement in the language is creating a binding:
-```rust
-let x: u32 = foo();
-```
-The type after the `:` is optional, and if missing, the compiler will use all information in the current scope, both before and after the `let`, to infer one[^48].
-
-An expression followed by a semicolon simply evaluates the expression for side-effects, like in other languages[^49].
-Some expressions, like `if`s, `whiles`, and `for`s, don't need to be followed by a semicolon.
-If they are not used in an expression, they will be executed for side-effects.
-
-`let` bindings are immutable by default, but `let mut x = /* ... */;` will make it mutable.
-
-Like in C, reassignment is an expression, but unlike in C, it evaluates to `()` rather than to the assigned value.
-
-Like in almost all other languages, literals, operators, function calls, variable references, and so on are all standard expressions, which we've already seen how Rust spells already.
-Let's dive into some of Rust's other expressions.
-
-
-#### Blocks {#blocks}
-
-Blocks in Rust are like better versions of C's blocks; in Rust, every block is an expression.
-A block is delimited by `{ }`, consists of a set of a list of statements and items, and potentially an ending expression, much like a function.
-The block will then evaluate to the final expression. For example:
-```rust
-let foo = {
-  let bar = 5;
-  bar ^ 2
-};
-```
-Blocks are like local functions that execute immediately, and can be useful for constraining the scope of variables.
-
-If a block does not end in an expression (that is, every statement within ends in a semicolon), it will implicitly return `()`, just like a function does.
-This automatic `()` is important when dealing with constructs like `if` and `match` expressions that need to unify the types of multiple branches of execution into one.
-
-#### Conditionals: `if` and `match` {#conditionals-if-and-match}
-
-Rust's `if` expression is, syntactically, similar to C's. The full syntax is
-```rust
-if cond1 {
-  // ...
-} else if cond2 {
-  // ...
-} else {
-  // ...
-}
-```
-
-Conditions must be expressions that evaluate to `bool`.
-A conditional may have zero or more `else if` clauses, and the `else` clause is optional.
-Because of this, Rust does not need (and, thus, does not have) a ternary operator:
-```rust
-let x = if c { a } else { b };
-```
-
-Braces are required on `if` expressions in Rust.
-
-`if` expressions evaluate to the value of the block that winds up getting executed.
-As a consequence, all blocks must have the same type[^50].
-For example, the following won't compile, because an errant semicolon causes type checking to fail:
-```rust
-if cond() {
-  my_int(4)   // Type is i32.
-} else {
-  my_int(7);  // Type is (), due to the ;
-}
-```
-`i32` and `()` are different types, so the compiler can't unify them into an overall type for the whole `if`.
-
-In general, it is a good idea to end all final expressions in `if` clauses with a semicolon, unless its value is needed.
-
-Like C, Rust has a `switch`-like construct called `match`. You can match integers:
-```rust
-let y = match x {
-  0 => 0x00,       // Match 0.
-  1..=10 => 0x0f,  // Match all integers from 1 to 10, inclusive.
-  _ => 0xf0,       // Match anything, like a `default:` case.
-};
-```
-Like `if` expressions, `match` expressions produce a value.
-The syntax `case val: stuff; break;` roughly translates into `val => stuff,` in Rust.
-Rust calls these case clauses "match arms"[^51].
-
-`match` statements do not have fallthrough, unlike C.
-In particular, only one arm is ever executed.
-Rust, however, allows a single match arm to match multiple values:
-```rust
-match x {
-  0 | 2 | 4 => /* ... */,
-  _ => /* ... */,
-}
-```
-
-Rust will statically check that every possible case is covered.
-This is especially useful when matching on an enum:
-```rust
-enum Color { Red, Green, Blue, }
-let c: Color = /* ... */;
-match c {
-  Color::Red =>   /* ... */,
-  Color::Green => /* ... */,
-  Color::Blue =>  /* ... */,
-}
-```
-No `_ =>` case is required, like a `default:` would in C, since Rust statically knows that all cases are covered (because enums cannot take on values not listed as a variant).
-If this behavior is not desired in an enum (because more variants will be added in the future), the `#[non_exhaustive]` attribute can be applied to an enum definition to require a default arm.
-
-We will see later that pattern matching makes `match` far more powerful than C's `switch`.
-
-
-#### Loops: `loop` and `while` {#loops-loop-and-while}
-
-Rust has three kinds of loops: `loop`, `while`, and `for`.
-`for` is not a C-style `for`, so we'll discuss it later.
-`while` is simply the standard C `while` loop, with slightly different syntax:
-```rust
-while loop_condition { /* Stuff. */ }
-```
-It can be used as an expression, but it always has type `()`; this is most notable when it is the last expression in a block.
-
-`loop` is unique to Rust; it is simply an infinite loop[^52]:
-```rust
-loop { /* Stuff. */ }
-```
-Because infinite loops can never end, the type of the `loop` expression (without a `break` in it!) is `!`, since any code after the loop is dead.
-Having an unconditional infinite loop allows Rust to perform better type and lifetime analysis on loops.
-Under the hood, all of Rust's control flow is implemented in terms of `loop`, `match`, and `break`.
-
-
-#### Control Flow {#control-flow}
-
-Rust has `return`, `break`, and `continue`, which have their usual meanings from C.
-They are also expressions, and, much like `loop {}`, have type `!` because all code that follows them is never executed (since they yank control flow).
-
-`return x` exits a function early with the value `x`.
-`return` is just syntax for `return ()`.
-`break` and `continue` do the usual things to loops.
-
-All kinds of loops may be annotated with labels (the only place where Rust allows labels):
-```rust
-'a: loop {
-  // ...
-}
-```
-`break` and `continue` may be used with those labels (e.g. `break 'a`), which will break or continue the enclosing loop with that label (rather than the closest enclosing loop).
-While C lacks this feature, most languages without `goto` have it.
-
-It is also possible to `break`-with-value out of an infinite loop, which will cause the `loop` expression to evaluate to that value, rather than `!`[^53].
-```rust
-let value = loop {
-  let attempt = get();
-  if successful(attempt) {
-    break attempt;
-  }
-};
-```
-
-
-### Talking to C {#talking-to-c}
-
-One of Rust's great benefits is mostly-seamless interop with existing C libraries.
-Because Rust essentially has no runtime, Rust types that correspond to C types can be trivially shared, and Rust can call C functions with almost no overhead[^54].
-The names of external symbols can be "forward-declared" using an `extern` block, which allows Rust to name, and later link with, those symbols:
-``` rust
-extern "C" {
-  fn malloc(bytes: usize) -> *mut u8;
-  static mut errno: i32;
-}
-```
-When the ABI specified is `"C"`, it can be left off; `extern {}` is implicitly `extern "C" {}`.
-
-It is the linker's responsibility to make sure those symbols wind up existing.
-Also, some care must be taken with what types are sent over the boundary.
-See [https://doc.rust-lang.org/reference/items/external-blocks.html](https://doc.rust-lang.org/reference/items/external-blocks.html) for more details.
-
-
-### Analogues of other functionality {#analogues-of-other-functionality}
-
-#### Volatile {#volatile}
-
-Rust does not have a `volatile` qualifier.
-Instead, volatile reads can be performed using the `read_volatile()`[^55] and `write_volatile()`[^56] methods on pointers, which behave exactly like volatile pointer dereference in C.
-
-Note that these methods work on types wider than the architecture's volatile loads and stores, which will expand into a series of volatile accesses, so beware.
-The same caveat applies in C: `volatile uint64_t` will emit multiple accesses on a 32-bit machine.
-
-
-#### Inline Assembly {#inline-assembly}
-
-Rust does not quite support inline assembly yet.
-Clang's inline assembly syntax is available behind the unstable macro `llvm_asm!()`, which will eventually be replaced with a Rust-specific syntax that better integrates with the language.
-`global_asm!()` is the same, but usable in global scope, for defining whole functions.
-Naked functions can be created using `#[naked]`. See [https://doc.rust-lang.org/1.8.0/book/inline-assembly.html](https://doc.rust-lang.org/1.8.0/book/inline-assembly.html).
-
-[Note that this syntax is currently in the process of being redesigned and stabilized.](https://blog.rust-lang.org/inside-rust/2020/06/08/new-inline-asm.html)
-
-#### Bit Casting {#bit-casting}
-
-Rust provides a type system trap-door for bitcasting any type to any other type of the same size:
-```rust
-let x = /* ... */;
-let y = std::mem::transmute<A, B>(x);
-```
-This trap-door is extremely dangerous, and should only be used when `as` casts are insufficient.
-The primary embedded-relevant use-case is for summoning function pointers from the aether, since Rust does not allow casting raw pointers into function pointers (since the latter are assumed valid).
-
-[https://doc.rust-lang.org/std/mem/fn.transmute.html](https://doc.rust-lang.org/std/mem/fn.transmute.html) has a list of uses, many of which do not actually require transmute.
-
-
-#### Linker Shenanigans and Other Attributes {#linker-shenanigans-and-other-attributes}
-
-Below are miscellaneous attributes relevant to embedded programming.
-Many of these subtly affect linker/optimizer behavior, and are very much in the "you probably don't need to worry about it" category.
-
-*   `#[link_section = ".my_section"]` is the rust spelling of `__attribute__((section(".my_section")))`, which will stick a symbol in the given ELF (or equivalent) section.
-*   `#[used]` can be used to force a symbol to be kept by the linker[^57] (this is often done in C by marking the symbol as `volatile`). The usual caveats for `__attribute__((used))`, and other linker hints, apply here.
-*   `#[inline]` is analogous to C's `inline`, and is merely a hint; `#[inline(always)]` and `#[inline(never)]` will always[^58] or never be inlined, respectively.
-*   `#[cold]` can also be used to pessimize inlining for functions that are unlikely to ever be called.
-
-
-## Part II: Rust-Specific Features {#part-ii-rust-specific-features}
-
-The previous part established enough vocabulary to take roughly any embedded C program and manually translate it into Rust.
-However, those Rust programs are probably about as safe and ergonomic as the C they came from.
-This section will focus on introducing features that make Rust safer and easier to write.
-
-
-### Ownership {#ownership}
-
-Double-free or, generally, double-use, are a large class of insidious bugs in C, which don't look obviously wrong at a glance:
-```c
-// `handle` is a managed resource to a peripheral, that should be
-// destroyed to signal to the hardware that the resource is not in use.
-my_handle_t handle = new_handle(0x40000);
-use_for_scheduling(handle);  // Does something with `handle` and destroys it.
-// ... 200 lines of scheduler code later ...
-use_for_scheduling(handle);  // Oops double free.
-```
-Double-free and use-after-free are a common source of crashes and security vulnerabilities in C code.
-Let's see what happens if we were to try this in Rust.
-
-Consider the equivalent code written in Rust:
-```rust
-let handle = new_handle(0x40000);
-use_for_scheduling(handle);
-// ...
-use_for_scheduling(handle);
-```
-
-If you then attempt to compile this code, you get an error:
-```rust
-error[E0382]: use of moved value: `handle`
-  --> src/main.rs:10:24
-   |
-7  |     let handle = new_handle(0x40000);
-   |         ------ move occurs because `handle` has type `Handle`,
-   |                which does not implement the `Copy` trait
-8  |     use_for_scheduling(handle);
-   |                        ------ value moved here
-9  |     // ...
-10 |     use_for_scheduling(handle);
-   |                        ^^^^^^ value used here after move
-```
-
-Use-after-free errors (and double-free errors) are impossible in Safe Rust.
-This particular class of errors (which don't directly involve pointers) are prevented by _move semantics_.
-As the error above illustrates, using a variable marks it as having been "moved from": the variable is now an empty slot of uninitialized memory.
-The compiler tracks this statically, and compilation fails if you try to move out again.
-The variable in which a value is currently stored is said to be its "owner"[^59]; an owner is entitled to hand over ownership to another variable[^60], but may only do so once.
-
-The error also notes that "`Handle` does not implement the `Copy` trait."
-Traits proper are a topic for later; all this means right now is that `Handle` has move semantics (the default for new types).
-Types which _do_ implement `Copy` have _copy semantics_; this is how all types passed-by-value in C behave: in C, passing a struct-by-value always copies the whole struct, while passing a struct-by-reference merely makes a copy of the pointer to the struct.
-This is why moves are not relevant when handling integers and raw pointers: they're all `Copy` types.
-
-Note that any structs and enums you define won't be `Copy` by default, even if all of their fields are.
-If you want a struct whose fields are all `Copy` to also be `Copy`, you can use the following special syntax:
-```rust
-#[derive(Clone, Copy)]
-struct MyPodType {
-  // ...
-}
-```
-
-Of course, the copy/move distinction is something of a misnomer: reassignment due to copy and move semantics compiles to the same `memcpy` or register move code.
-The distinction is purely for static analysis.
-
-
-### References and Lifetimes {#references-and-lifetimes}
-
-Another class of use-after-free involves stack variables after the stack is destroyed.
-Consider the following C:
-```c
-const int* alloc_int(void) {
-  int x = 0;
-  return &x;
-}
-```
-This function is obviously wrong, but such bugs, where a pointer outlives the data it points to, are as insidious as they are common in C.
-
-Rust's primary pointer type, _references_, make this impossible.
-References are like raw pointers, except they are always well-aligned, non-null, and point to valid memory; they also have stronger aliasing restrictions than C pointers.
-Let's explore how Rust achieves this last guarantee.
-
-Consider the following Rust program:
-```rust
-fn alloc_int() -> &i32 {
-  let x = 0i32;
-  &x
-}
-```
-This program will fail to compile with a cryptic error: `missing lifetime specifier`.
-Clearly, we're missing something, but at least the compiler didn't let this obviously wrong program through.
-
-A lifetime, denoted by a symbol like `'a`[^61] (the apostrophe is often pronounced "tick"), labels a region of source code[^62].
-Every reference in Rust has a lifetime attached, representing the region in which a reference is known to point to valid memory: this is specified by the syntax `&'a i32`: reference to `i32` during `'a`.
-Lifetimes, much like types, do not exist at runtime; they only exist for the compiler to perform _borrow checking_, in which the compiler ensures that references only exist within their respective lifetimes.
-A special lifetime, `'static` represents the entire program.
-It is the lifetime of constants and global variables.
-
-Consider the following Rust code:
-```rust
-let x: i32 = 42;
-let y: &'a i32 = &x;  // Start of 'a.
-use_reference(y);
-use_value(x);  // End of 'a, because x has been moved.
-use_reference(y);  // Error: use of y outside of 'a.
-```
-Reference lifetimes start when the reference is taken, and end either when the lifetime goes out of scope or when the value referenced is moved.
-Trying to use the reference outside of the lifetime is an error, since it is now a dangling pointer.
-
-Rust often refers to references as _borrows_: a reference can borrow a value from its owner for a limited time (the lifetime), but must return it before the owner gives the value up to someone else.
-It is also possible for a reference to be a borrow of a borrow, or a _reborrow_: it is always possible to create a reference with a shorter lifetime but with the same value as another one.
-Reborrowing is usually performed implicitly by the compiler, usually around call sites, but can be performed explicitly by writing `&*x`.
-
-Lifetimes can be elided in most places they are used:
-```rust
-fn get_field(m: &MyStruct) -> &u32 {
-  &m.field  // For references, unlike for raw pointers, . acts the same way -> does in C.
-}
-```
-Here, the compiler assumes that the lifetime of the return type should be the same as the lifetime of the `m`[^63].
-However, we can write this out explicitly:
-```rust
-fn get_field<'a>(m: &'a MyStruct) -> &'a u32 { /* ... */ }
-```
-The `<'a>` syntax introduces a new named lifetime for use in the function signature, so that we can explicitly tell the compiler "these two references have the same lifetime".
-This is especially useful for specifying many lifetimes, when the compiler can't make any assumptions:
-```rust
-fn get_fields<'a, 'b>(m1: &'a MyStruct, m2: &'b MyStruct) -> (&'a u32, &'b u32) {
-  (&m1.field, &m2.field)
-}
-```
-Now we can try to fix our erroneous stack-returning function.
-We need to introduce a new lifetime for this function, since there's no function arguments to get a lifetime from:
-```rust
-fn alloc_int<'a>() -> &'a i32 {
-  let x = 0i32;
-  &x
-}
-```
-This now gives us a straightforward error, showing the borrow-checking prevents erroneous stack returns:
-```rust
-error[E0515]: cannot return reference to local variable `x`
- --> src/lib.rs:9:3
-  |
-9 |   &x
-  |   ^^ returns a reference to data owned by the current function
-```
-
-This `<'a>` syntax can also be applied to items like structs: If you're creating a type containing a reference, the `<'a>` is required:
-```rust
-struct MyRef<'a> {
-  meta: MyMetadata,
-  ptr: &'a u32,  // Lifetime elision not allowed here.
-}
-```
-
-Rust's references come in two flavors: shared and unique.
-A shared reference, `&T`, provides immutable access to a value of type `T`, and can be freely duplicated: `&T` is `Copy`.
-A unique reference, `&mut T`, provides mutable access to a value of type `T`, but is subject to Rust's aliasing rules, which are far more restrictive than C's strict aliasing, and can't be turned off.
-
-There can only be one `&mut T` active for a given value at a time.
-This means that no other references may be created within the lifetime of the unique reference.
-However, a `&mut T` may be reborrowed, usually for passing to a function.
-During the reborrow lifetime, the original reference cannot be used.
-This means the following code works fine:
-```rust
-fn do_mut(p: &mut Handle) { /* ... */ }
-
-let handle: &mut Handle = /* ... */;
-do_mut(handle);  // Reborrow of handle for the duration of do_mut.
-// handle is valid again.
-do_mut(handle);  // Reborrow again.
-```
-In other words, Rust does not have a safe equivalent to `int*`; it only has equivalents for `const int*` and `int* restrict` ... plus casting away `const` is instant Undefined Behavior[^64].
-Rust will aggressively collapse loads and stores, and assume that no mutable references alias for the purpose of its alias analysis.
-This means more optimization opportunities, without safe code needing to do anything.[^65]
-
-Finally, it should go without saying that references are only useful for main memory; Rust is entitled to generate spurious loads and stores for (possibly unused!) references[^66], so MMIO should be performed exclusively through raw pointers.
-
-[https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html](https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html) elaborates further on the various lifetime rules.
-
-
-#### Operations on References {#operations-on-references}
-
-Rust references behave much more like scalar values than like pointers (borrow-checking aside).
-Because it is statically known that every reference, at all times, points to a valid, initialized value of type `T`, explicit dereferencing is elided most of the time (though, when necessary, they can be dereferenced: `*x` is an lvalue that can be assigned to).
-
-Rust does not have a `->` operator, but, for `x: &T`, the dot operator behaves as if `x` were a `T`.
-If `field` is a field of `T`, for example, `x.field` is the lvalue of `field` (which would be spelled `x->field` in C).
-This applies even for heavily nested references: the dot operator on `&&&T` will trigger three memory lookups.
-This is called the "auto-deref" behavior.
-
-Equality of references is defined as equality of underlying values: `x == y`, for `x: &T` and `y: &T`, becomes `*x == *y`.
-Pointer equality is still possible with `std::ptr::eq(x, y)`.
-References can be coerced into raw pointers: `x as *const T`, and compared directly.
-
-
-### Methods {#methods}
-
-While Rust is not an object-oriented language, it does provide a mechanism for namespacing functions under types: `impl` (for implementation) blocks.
-These also allow you to make use of Rust's visibility annotations, to make implementation details and helpers inaccessible to outside users.
-
-Here's an example of a type with methods.
-```rust
-pub struct Counter(u64);  // Non-public field!
-impl Counter {
-  /// Creates a new `Counter`.
-  pub fn new() -> Self {
-    Counter(0)
-  }
-
-  /// Private helper.
-  fn add(&mut self, x: u64) {
-    self.0 += x;
-  }
-
-  /// Get the current counter value.
-  pub fn get(&self) -> u64 {
-    self.0
-  }
-
-  /// Increment the counter and return the previous value.
-  pub fn inc(&mut self) -> u64 {
-    let prev = self.get();
-    self.add(1);
-    prev
-  }
-
-  /// Consumes the counter, returning its final value.
-  pub fn consume(self) -> u64 {
-    self.get()
-  }
-}
-```
-Outside modules cannot access anything not marked as `pub`, allowing us to enforce an invariant on `Counter`: it is monotonic.
-Let's unpack the syntax.
-
-Functions in an `impl` block are called "inherent functions" or "methods", depending on whether they take a `self` parameter.
-Inherent functions don't take a `self`, and are called like `Counter::new()`[^67].
-
-A `self` parameter is a parameter named `self` (which is a keyword) and having a type involving `Self`[^68] (another keyword, which is a type alias for the type the `impl` block is for), such as `&Self`[^69].
-The syntax `self`, `&self`, `mut self`, and `&mut self` are sugar for the common forms `self: Self`, `self: &Self`, `mut self: Self` and `self: &mut Self`, representing self-by-value, self-by-reference, self-by-mut-value, and self-by-mut-reference.
-
-Methods can, thus, be called like so: `my_counter.inc()`.
-Methods are really just normal functions: you could also have called this like `Counter::inc(&mut my_counter)`.
-Note that calling a function that takes `&self` or `&mut self` triggers a borrow of the receiving type; if a `&self` function is called on a non-reference value, the value will have its address taken, which gets passed into the method.
-
-`impl` blocks, like other items, can be parameterized by lifetimes.
-In order to add methods to a struct with a reference in it, the following syntax can be used:
-```rust
-impl<'a> MyStruct<'a> { /* ... */ }
-```
-If `'a` is never actually used inside the `impl` block, this can be written using a _placeholder lifetime_:
-```rust
-impl MyStruct<'_> { /* ... */ }
-```
-As we've already seen, many primitive types have methods, too; these are defined in special `impl` blocks in the standard library.
-
-
-### Slices and `for` {#slices-and-for}
-
-References also do not allow for pointer arithmetic, so a `&u32` cannot be used to point to a buffer of words.
-Static buffers can be passed around as arrays, like `&[u32; 1024]`, but often we want to pass a pointer to contiguous memory of a runtime-known value.
-_Slices_ are Rust's solution to pointer+length.
-
-A slice of `T` is the type `[T]`; this type is most like a "flexible array member" in C:
-```rust
-struct slice {
-  size_t len;
-  T values[];
-}
-```
-Then, a `slice*` would point to a length followed by that many `T`s; it can't reasonably exist except behind a pointer.
-Similarly, `[T]` is what Rust calls a dynamically sized type[^70], which needs to exist behind a reference: it is much more common to see `&[T]` and `&mut [T]`.
-
-However, Rust still differs from the C version: `&[T]` is a _fat pointer_, being two words wide.
-It essentially looks like this:
-```rust
-struct Slice {
-  len: usize,
-  values: *const T,
-}
-```
-A reference to a slice otherwise works like an array reference: `&x[n]` extracts a reference to the `n`th element in the slice (with bounds checking), `x[n] = y` assigns to it.
-The length of a slice can also be extracted with the `len` method: `x.len()`.
-
-`str`[^71] is a slice-like type that is guaranteed to contain UTF-8 string data.
-
-Slices can be created from arrays and other slices using a "ranged index operation": `&x[a..b]`[^72]`.` This takes the array or slice `x` and creates a slice with the elements from index `a` to index `b` (inclusive of `a`, exclusive of `b`), of length `b - a`.
-`&x[a..]` is the suffix starting at `a`, `&x[..b]` is the prefix ending at `b`, and `&x[..]` is the whole slice, useful for converting arrays into slices.
-Inclusive ranges are also available, with the syntax `a..=b`.
-
-Slices can be iterated over, using `for` loops:
-```rust
-let slice: &[u32] = /* ... */;
-for x in slice {
-  // x is a reference to the nth element in slice.
-}
-```
-
-If an index is desired, it is possible to iterate over a range directly:
-```rust
-for i in 0..slice.len() {
-  let x = &slice[i];
-  // ...
-}
-```
-
-This can be combined with the `_` pattern to simply repeat an operation `n` times:
-```rust
-for _ in 0..n {
-  // ...
-}
-```
-
-One important note with slices, as pertains to borrowing, is unique references.
-If we have a unique reference to a slice, it's not possible to take unique references to multiple elements at once:
-```
-let slice: &mut [u32] = /* ... */;
-let x = &mut slice[0];
-let y = &mut slice[1];  // Error: slice is already borrowed.
-```
-The method `split_at_mut()`[^73] can be used to split a unique slice reference into two non-overlapping unique slice references:
-```rust
-let slice: &mut [u32] = /* ... */;
-let (slice1, slice2) = slice.split_at_mut(1);
-let x = &mut slice1[0];  // slice[0]
-let y = &mut slice2[0];  // slice[1]
-```
-It is usually possible to structure code in such a way that avoids it, but this escape hatch exists for when necessary.
-Slices can also be decomposed into their pointer and length parts, using the `as_ptr()` and `len()` functions, and reassembled with `std::slice::from_raw_parts()`[^74].
-This operation is unsafe, but useful for bridging C and Rust, or Rust and Rust across a syscall or IPC boundary.
-
-More slice operations can be found at [https://doc.rust-lang.org/std/slice/index.html](https://doc.rust-lang.org/std/slice/index.html) and [https://doc.rust-lang.org/std/primitive.slice.html](https://doc.rust-lang.org/std/primitive.slice.html).
-
-
-#### String Literals {#string-literals}
-
-Rust string literals are much like C string literals: `"abcd..."`.
-Arbitrary ASCII-range bytes can be inserted with `\xNN`, and supports most of the usual escape sequences.
-However, all Rust strings are UTF-8 encoded byte slices: `&str` is a wrapper type around `&[u8]` that guarantees that the bytes inside are valid UTF-8.
-The type of all string literals is `&'static str`.
-
-Rust string literals can contain arbitrary newlines in them, which can be escaped:
-```rust
-// Equivalent to "foo\n  bar".
-let s = "foo
-  bar";
-// Equivalent to "foo  bar".
-let s = "foo\
-  bar";
-```
-
-Raw strings disable escape sequences, and are delimited by an arbitrary, matching number of pound signs:
-```rust
-let s = r"...";
-let s = r#" ..."#;
-let s = r#####"..."#####;
-```
-If instead a byte string with no encoding is required, byte strings can be used: `b"..."`.
-Their contents must be ASCII (or escaped bytes), and their type is `&[u8]`.
-Raw strings can also be byte strings: `br#"..."#`.
-
-Rust also has character literals in the form of `'z'`[^75], though their type is `char`, a 32-bit Unicode code-point.
-To get an ASCII byte of type `u8`, instead, use `b'z'`.
-
-### Destructors and RAII {#destructors-and-raii}
-
-Destructors are special functions that perform cleanup logic when a value has become unreachable (i.e., both the `let` that originally declared it can no longer be named, and the last reference to it expired).
-After the destructor is run, each of the value's fields, if it's a struct or enum, are also destroyed (or "dropped").
-
-Destructors are declared with a special kind of `impl` block (we'll see more like this, later):
-```rust
-impl Drop for MyType {
-  fn drop(&mut self) {
-    // Dtor code.
-  }
-}
-```
-If several values go out of scope simultaneously, they are dropped in reverse order of declaration.
-
-The `drop` method can't be called manually; however, the standard library function `std::mem::drop()` [^76] can be used to give up ownership of a value and immediately destroy it.
-Unions[^77] and types with copy semantics cannot have destructors.
-
-Destructors enable the _resource acquisition is initialization_ (RAII) idiom.
-A type that holds some kind of temporary resource, like a handle to a peripheral, can have a destructor to automatically free that resource.
-The resource is cleaned up as soon as the handle goes out of scope.
-
-The classic example of RAII is dynamic memory management: you allocate memory with `malloc`, stash the returned pointer in a struct, and then that struct's destructor calls `free` on that pointer.
-Since the struct has gone out of scope when `free` is called, UAF is impossible.
-Thanks to Rust's move semantics, this struct can't be duplicated, so the destructor can't be called twice.
-Thus, double-free is also impossible[^78].
-
-In some situations, calling a destructor might be undesirable (for example, during certain Unsafe Rust operations).
-The standard library provides the special function `std::mem::forget()`[^79], which consumes a value without calling its destructor.
-The `std::mem::ManuallyDrop<T>`[^80] type is a smart pointer[^81] that holds a `T`, while inhibiting its destructor.
-For this reason, there is no expectation that a destructor actually runs.
-
-The function `std::mem::needs_drop()`[^82] can be used to discover if a type needs to be dropped; even if it doesn't have a `drop` method, it may recursively have a field which does.
-`std::ptr::drop_in_place()`[^83] can be used to run the destructor in the value behind a raw pointer, without technically giving up access to it.
-
-
-### Pattern Matching {#pattern-matching}
-
-References cannot be null, but it turns out that a null value is sometimes useful.
-`Option<T>` is a standard library type representing a "possibly absent `T`"[^84].
-It is implemented as an enum:
-```rust
-enum Option<T> {
-  Some(T),
-  None,
-}
-```
-
-The `<T>` is similar to the lifetime syntax we saw before; it means that `Option<T>` is a _generic type_; we'll dig into those soon.
-
-If we have a value of type `Option<T>` (or, any other enum, really), we can write code conditioned on the value's discriminant using _pattern matching_, which is accessed through the `match` expression:
-```rust
-let x: Option<u32> = /* ... */;
-let y = match x {
-  Some(val) => val,  // If `x` is a `Some`, bind the value inside to `val`.
-  None => 42,  // If `x` is a `None`, do this instead.
-};
-```
-
-The key thing pattern matching gives us is the ability to inspect the union within an enum safely: the tag check is enforced by the compiler.
-
-Patterns are like expressions, forming a mini-language.
-If expressions build up a value by combining existing values, patterns do the opposite: they build up values by deconstructing values.
-In particular, a pattern, applied to an expression, performs the following operations:
-*   Checks that the expression's value actually matches that pattern.
-    (Note that type-checking doesn't go into this; patterns can't be used for conditioning on the type of an expression.)
-*   Optionally binds that expression's value to a name.
-*   Optionally recurs into subpatterns.
-
-Here's a few examples of patterns.
-Keep in mind the matching, binding, and recurrence properties of each.
-In general, patterns look like the value of the expression they match.
-
-*   `_`, an underscore[^85] pattern.
-    The match always succeeds, but it throws away the matched value.
-    This is the `_` in the equivalent of a `default:` case.
-*   `foo`, an identifier pattern.
-    This pattern is exactly like `_`, but it binds the matched value to its name.
-    This is the `val` in the `Some(val)` above.
-    This can also be used by itself as a default case that wants to do something with the matched-on value.
-    The binding can be made mutable by writing `Some(mut val)` instead.
-*   Any numeric literal, for a literal pattern.
-    This match compares the matched value against the literal value, and doesn't match anything.
-    These can also be inclusive ranges: `5..=16`[^86].
-*   `(pat1, pat2, /* etc */)`, a tuple pattern.
-    This match operates on tuple types, and always succeeds: it extracts the individual elements of a tuple, and applies them to the pattern's subpatterns.
-    In particular, the `()` pattern matches the unit value `()`.
-```rust
-let x: (u32, u32) = /* ... */;
-match x {
-  (5, u) => /* ... */,  // Check that first element is five,
-                        // bind the second element to `u`.
-  (u, _) => /* ... */,  // Bind the first element to `u`,
-                        // discard the second element.
-}
-
-let y: (u32, (u32, u32)) = /* ... */;
-match y {
-  // All patterns can nest arbitrarily, like expressions.
-  (42, (u, _)) =>  /* ... */,
-  // `..` can be used to match either a head or a tail of tuple.
-  (.., u) => /* ... */,
-  (u, ..) => /* ... */,
-  (..) =>    /* ... */,  // Synonymous with _.
-}
-```
-*   Struct patterns are analogous to tuple patterns.
-    For a tuple-like struct, they have the exact same syntax, but start with the name of the struct: `MyTuple(a, b, _)`.
-    Regular structs are much more interesting syntax-wise:
-```rust
-struct MyStruct { a: i32, b: u32 }
-match my_struct {
-  MyStruct { a, b } => /* ... */,  // Bind the fields `a` and `b` to
-                                   // names `a` and `b`, respectively.
-  MyStruct { a: foo, b: _ } => /* ... */,  // Bind the field `a` to the name
-                                           // `foo`, and discard the field `b`.
-  MyStruct { a: -5, .. } => /* ... */  // Check that `a` is -5, and ignore
-                                       // other fields.
-}
-```
-*   Enum patterns are probably the most important kind of pattern, and are what we saw in the `match` statement with `Option` above.
-    They're very similar to struct patterns, except that instead of always succeeding, they check that the enum discriminant is the one named in the pattern.
-```rust
-enum MyEnum { A, B{u32), C { a: i32, b: i32 }, }
-match my_enum {
-  MyEnum::A =>    /* ... */,  // Match for variant `A`.
-  MyEnum::B(7) => /* ... */,  // Match for variant `B`, with 7 as the value inside.
-  MyEnum::B(x) => /* ... */,  // Match for variant `B`, binding the value inside to
-                              // `x`.
-
-  MyEnum::C { a: 7, .. } => /* ... */,  // Match for variant `C`, with 7 as the
-                                        // value in `a` and all other fields ignored.
-
-  MyEnum::C { b, .. } => /* ... */,  // Match for variant `C`, binding b to b.
-}
-```
-A complete treatment of the pattern syntax can be found at [https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html).
-
-A `match` expression will evaluate each pattern against a value until one matches, in order; the compiler will warn about patterns which are unreachable[^87].
-The compiler will also ensure that every value will get matched with one of the match arms, either because every case is covered (e.g., every enum variant is present) or an _irrefutable pattern_ is present (i.e., a pattern which matches all values). `_`, `foo`, `(a, _)`, and `MyStruct { a, .. }` are all examples of irrefutable patterns.
-
-If the value being matched on is a reference of some kind, bound names will be references, too.
-
-For example:
-```rust
-match &my_struct {
-  MyStruct { a, .. } => {
-    // Here, `a` is a `&i32`, which is a reference to the `a` field in my_struct.
-  },
-}
-```
-This feature is sometimes called _match ergonomics_, since before it was added, explicit dereference and special `ref` pattern qualifiers had to be added to matches on references.
-
-In addition, `match` statements support two additional features on top of the pattern syntax discussed above:
-*   _Multi-match arms_ can allow a match arm to match on one of multiple patterns: `a | b | c => /* ... */,`.
-    If any of the patterns match, the arm executes.
-*   _Match guards_ give you a shorthand for conditioning an arm on some expression: `Some(foo) if foo.has_condition() => /* ... */,`.
-
-Also, the standard library provides the `matches!()` macro as a shorthand for the following common match expression:
-```rust
-match expr {
-  <some_complex_match_arm> => true,
-  _ => false,
-}
-// ... can be replaced with ...
-matches!(expr, some_complex_match_arm)
-```
-`matches!` supports multi-match and match guards as well.
-
-Irrefutable patterns can be used with normal variable declaration.
-The syntax `let x = /* ... */;` actually uses a pattern: `x` is a pattern.
-When we write `let mut x = /* ... */;`, we are using a `mut x` pattern instead.
-Other irrefutable patterns can be used there:
-```rust
-// Destructure a tuple, rather than using clunky `.0` and `.1` field names.
-let (a, b) = /* ... */;
-
-// Destructure a struct, to access its fields directly.
-let Foo { foo, bar, baz } = /* ... */;
-
-// Syntactically valid but not allowed: `42` is not an irrefutable pattern.
-let 42 = /* ... */;
-```
-
-Special variants of `if` and `while` exist to take advantage of patterns, too:
-```rust
-if let Some(x) = my_option {
-  // If the pattern succeeds, the body will be executed, and `x` will be bound
-  // to the value inside the Option.
-  do_thing(x);
-} else {
-  // Else block is optional; `x` is undefined here.
-  // do_thing(x);  // Error.
-}
-
-while let Some(x) = some_func() {
-  // Loop terminates once the pattern match fails. Again, `x` is bound
-  // to the value inside the Option.
-}
-```
-Unlike normal `let` statements, `if let` and `while let` expressions are meant to be used with refutable patterns.
-
-In general, almost every place where a value is bound can be an irrefutable pattern, such as function parameters and `for` loop variables:
-```rust
-fn get_first((x, _): (u32, u32)) -> u32 { x }
-
-for (k, v) in my_key_values {
-  // ...
-}
-```
-
-### Traits {#traits}
-
-Traits are Rust's core code-reuse abstraction.
-Rust traits are like interfaces in other languages: a list of methods that a type must implement.
-Traits themselves, however, are _not_ types.
-
-A very simple trait from the standard library is `Clone`[^88]:
-```rust
-trait Clone {
-  fn clone(&self) -> Self;
-}
-```
-
-A type satisfying `Clone`'s interface (in Rust parlance, "implements `Clone`") has a `clone` method with the given signature, which returns a duplicate of `self`.
-To implement a trait, you use a slightly funny `impl` syntax:
-```rust
-impl Clone for MyType {
-  fn clone(&self) -> Self { /* implementation */ }
-}
-```
-
-This gives us a consistent way to spell "I want a duplicate of this value".
-The standard library provides traits for a number of similar operations, such as `Default`[^89], for providing a default value, `PartialEq`[^90] and `Eq`, for equality, `PartialOrd`[^91] and `Ord`, for ordering, and `Hash`[^92], for non-cryptographic hashing.
-
-The above traits are special in that they have trivial implementations for a struct or enum, assuming that all fields of that struct or enum implement it.
-The `#[derive()]` syntax described in the "Ownership" section can be used with any of these traits to automatically implement them for a type.
-It is not uncommon for Plain Old Data (POD) types to look like this:
-```rust
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
-struct MyPod {
-  pub a: u8,
-  pub b: u8,
-  // The following line wouldn't compile, because `derive(Eq)` requires
-  // all fields to be `Eq`.
-  // c: NonEq,
-}
-```
-
-Traits can also provide built-in methods implemented in terms of other methods, to provide a default implementation (which can be overridden if a more efficient one is available for a particular type).
-The full `Clone` trait actually looks like this:
-```rust
-pub trait Clone {
-  fn clone(&self) -> Self;
-  fn clone_from(&mut self, source: &Self) {
-    *self = source.clone();
-  }
-}
-```
-
-Implementers don't need to provide `clone_from`, but are allowed to do so if the default implementation isn't good enough.
-
-Traits, and types that implement them, can be defined in different modules, so long as the implementing module defines either the trait or the type.
-This means that trait methods aren't really part of the type, but rather part of the trait plus the type.
-Thus, in order to call trait methods on a particular type, that trait has to be in scope, too.
-When unambiguous, trait functions can be called as either `foo.trait_fn()`, `Foo::trait_fn(foo)`, or `Trait::trait_fn(foo)`.
-However, since names can sometimes be ambiguous, there is a fully unambiguous syntax[^93]: `<Foo as Trait>::trait_fn(foo)`.
-This last syntax is also useful in generic contexts, or for being precise about the exact function being referred to.
-
-Traits are also the vehicle for operator overloading: these traits are found in the std::ops[^94] module of the standard library.
-
-
-#### Trait Objects {#trait-objects}
-
-Traits can be used for dynamic dispatch (also known as virtual polymorphism) through a mechanism called _trait objects_.
-Given a trait `Trait`, and a type `T` that implements it, we can `as`-cast a reference `&T` into a dynamic trait object: `&dyn Trait`.
-For example:
-```rust
-trait Id {
-    fn get_id(&self) -> usize;
-}
-impl Id for Device {
-  // ...
-}
-
-let device: Device = /* ... */;
-let dyn_id = &device as &dyn Id;  // Create a vtable.
-let id = dyn_id.get_id();  // Indirect procedure call.
-```
-
-`dyn Trait` is a dynamically-sized type, much like slices, and can only exist behind a pointer.
-The reference `&dyn Trait` looks something like this:
-```rust
-struct TraitObject {
-  value: *mut (),
-  vtable: *mut Vtable,
-}
-
-struct Vtable {
-  size: usize,
-  align: usize,
-  dtor: fn(&mut T),
-  // Other trait methods.
-}
-```
-Thus, the dynamic function call to `get_id` would compile to something like the following:
-```rust
-let device: Device = /* ... */;
-let dyn_id = &device as IdTraitObject;
-let id = (dyn_id.vtable.get_id)(dyn_id.value);
-```
-
-There are some limitations on what traits can be made into trait objects: namely, functions cannot take or return functions of type `Self`; only `&Self` or `&mut Self`[^95].
-In other words, all of the functions must treat `Self` as if it were not sized and only accessible through a pointer.[^96] A trait that can be made into a trait object is called _object safe_.
-The type `dyn Trait` always behaves as if it implemented `Trait`, which is relevant for generics, discussed below.
-
-
-#### Unsafe Traits {#unsafe-traits}
-
-It is possible to mark a trait as `unsafe` by writing `unsafe trait MyTrait { /* ... */ }`; the only difference with normal traits is that it requires `unsafe impl` to be implemented.
-Unsafe traits typically enforce some kind of additional constraint in addition to their methods; in fact, unsafe traits frequently don't have methods at all.
-For example, the standard library trait `Sync` is implemented by all types which synchronize access[^97].
-Because the invariant this trait asserts is beyond what the compiler can check, it is an unsafe trait.
-
-Trait methods may separately be marked as `unsafe`.
-This is usually done to indicate that not only does care need to be taken in implementing the trait, but calling the function also requires care (and uttering `unsafe`).
-This is separate from marking a trait as `unsafe`, and it is not necessary to mark a trait as `unsafe` for it to have `unsafe` methods.
-
-
-#### Auto Traits
-
-Auto traits are a compiler mechanism for automatically implementing certain traits; in the standard library's source code, this shows up as `auto trait Foo {}` (though this syntax is unavailable for normal libraries).
-Auto traits are implemented automatically for a struct or enum type if all of its fields also implement that trait, and are used for exposing transitive properties to the trait system.
-For example, `Send` and `Sync` are auto traits; a number of other marker traits[^98] are also auto traits.
-
-Auto traits are always markers that you don't really want to opt out of.
-They're like the opposite of `derive()` traits, which you need to opt into, since they meaningfully affect the API of your type in a way that it is important to be able to control.
-
-
-### Generics {#generics}
-
-_Generic programming_ is writing source code that can be compiled for many types.
-Generics are one of Rust's core features, which enable polymorphic[^99] static dispatch.
-
-Functions can be made generic by introducing _type parameters_, using a syntax similar to explicit lifetimes[^100]:
-```rust
-fn identity<T>(x: T) -> T {
-  x
-}
-```
-This function accepts a value of any type and immediately returns it.
-It can then be called like this: `identity::<i32>(42)`[^101].
-Using a generic function with all of its type parameters filled in causes it to be _instantiated_ (or _monomorphized_), resulting in code being generated for it.
-This process essentially consists of replacing each occurrence of `T` with its concrete value.
-
-Each distinct instantiation is a separate function at runtime, with a separate address, though for functions which generate identical code, like `identity::<i32>` and `identity::<u32>`, the linker may deduplicate them.
-Overzealous use of generic code can lead to binary bloat.
-
-Most of the time, the `::<>` bit (referred to by some reference materials as the "turbofish") is unnecessary, since Rust type deduction can infer it: `let x: u64 = identity(42);` will infer that `T = u64`.
-It can, however, be useful to include when otherwise unnecessary to help with readability.
-
-Types can also be generic, like the  `Option<T>` type from before[^102]:
-```rust
-struct MyWrapper<T> {
-  foo: usize,
-  bar: T,
-}
-```
-The concrete type `MyWrapper<i32>` consists of replacing all occurrences of `T` in the definition with `i32`, which we can otherwise use as a normal type:
-```
-fn get_foo(mw: MyWrapper<i32>) -> usize {
-  mw.foo
-}
-```
-Note that `MyWrapper` on its own is not a type.
-
-Note that different generic instantiations are different types, with different layouts and sizes, which cannot be converted between each other in general.[^103]
-
-Unsurprisingly, we can combine generic functions with generic types.
-In this case, we don't really need to know that `T = i32`, so we factor it out.
-```rust
-fn get_foo<T>(mw: MyWrapper<T>) -> usize {
-  mw.foo
-}
-```
-We can also build a generic function to extract the generic field:
-```rust
-fn get_bar<T>(mw: MyWrapper<T>) -> T {
-  mw.bar
-}
-```
-
-Just like with lifetimes, `impl` blocks need to introduce type parameters before they are used:
-```rust
-impl<T> MyWrapper<T> {
-  // ...
-}
-```
-
-
-#### Generic Bounds {#generic-bounds}
-
-However, generics alone have one limitation: the function is only type and borrow checked once, in its generic form, rather than per instantiation; this means that generic code can't just call inherent methods of `T` and expect the lookup to succeed[^104].
-For example, this code won't compile:
-```rust
-fn generic_add<T>(x: T, y: T) -> T {
-  x + y
-}
-```
-
-The error looks like this:
-```rust
-error[E0369]: cannot add `T` to `T`
- --> src/lib.rs:2:6
-  |
-2 |     x+y
-  |     -^- T
-  |     |
-  |     T
-  |
-  = note: T might need a bound for std::ops::Add
-```
-The compiler helpfully suggests that we need a "bound" of some sort.
-Generic bounds are where traits really shine.
-
-`Add` is a standard library trait, that looks something like the following:
-```rust
-trait Add<Rhs> {
-  type Output;
-  fn add(self, other: Rhs) -> Self::Output;
-}
-```
-Not only is this trait generic, but it also defines an _associated type_, which allows implementations to choose the return type of the addition operation[^105].
-Thus, for any types `T` and `U`, we can add them together if `T` implements `Add<U>`; the return type of the operation is the type `<T as Add<U>>::Output`[^106].
-
-Thus, our `generic_add` function should be rewritten into
-```rust
-fn generic_add<T: Add<T>>(x: T, y: T) -> T::Output {
-  x + y
-}
-```
-The `T: Add<T>` part is a generic bound, asserting that this function can only compile when the chosen `T` implements `Add<T>`.
-
-If we want to ensure we return a T, we can change the bound to require that `Output` be `T`:
-```rust
-fn generic_add<T>(x: T, y: T) -> T
-  where T: Add<T, Output=T>
-{
-  // ...
-}
-```
-Note that this bound is included in a `where` clause, after the return type.
-This is identical to placing it in the angle brackets, but is recommended for complicated bounds to keep them out of the way.
-In-bracket bounds and `where` clauses are available for all other items that can have generic bounds, such as traits, impls, structs, and enums[^107].
-
-Bound generics can be used to emulate all kinds of other behavior.
-For example, the `From` and `Into` traits represent lossless conversions, so a function that wants any value that can be converted into `MyType` might look like
-```rust
-fn foo<T: Into<MyType>>(x: T) {
-  // ...
-}
-```
-You could then implement `From<T>` on `MyType` for all `T` that can be converted into `MyType`.
-When `U` implements `From<T>`, a generic `impl` in the standard library causes `T` to implement `Into<U>`.
-At the call-site, this looks like an overloaded function[^108].
-
-Bound generics can also be used to pass in constants. Imagine that we define a trait like
-```rust
-trait DriverId {
-  const VALUE: u8;
-}
-```
-This trait could then be implemented by various zero-sized types that exist only to be passed in as type parameters:
-```rust
-struct GpioDriverId;
-impl DriverId for GpioDriverId {
-  const VALUE: u8 = 0x4a;
-}
-```
-Then, functions that need to accept a constant id for a driver can be written and called like this:
-```rust
-fn get_device_addr<Id: DriverId>() -> usize {
-  // use Id::VALUE somehow ...
-}
-// ...
-get_device_addr::<GpioDriverId>()
-```
-Types can also be bound by lifetimes.
-The bound `T: 'a` says that every reference in `T` is longer than `'a`; this kind of bound will be implicitly inserted whenever a generic `&'a T` is passed around.
-Bounds may be combined: `T: Clone + Default` and `T: Clone + 'a` are both valid bounds.
-Finally, lifetimes may be bound by other lifetimes: `'a: 'b` means that the lifetime `'a` is longer than `'b`.
-
-
-#### Phantom Data
-
-The following is an error in Rust:
-```rust
-error[E0392]: parameter `T` is never used
- --> src/lib.rs:2:12
-  |
-2 | struct Foo<T>;
-  |            ^ unused parameter
-  |
-  = help: consider removing `T`, referring to it in a field,
-    or using a marker such as `std::marker::PhantomData`
-```
-
-Rust requires that all lifetime and type parameters be used, since generating code to call destructors requires knowing if a particular type owns a `T`.
-This is not always ideal, since it's sometimes useful to expose a `T` in your type even though you don't own one; we can work around this using the compiler's suggestion: `PhantomData`.
-For more information on how to use it, refer to the type documentation[^109] or the relevant Rustonomicon entry[^110].
-
-
-### Smart Pointers {#smart-pointers}
-
-In Rust, a "smart pointer"[^1112] is any type that implements `std::ops::Deref`[^112], the dereference operator[^113].
-`Deref` is defined like this:
-```rust
-trait Deref {
-  type Target;
-  fn deref(&self) -> &Self::Target;
-}
-```
-Types which implement `Deref` can also implement the mutable variant:
-```rust
-trait DerefMut: Deref {
-  fn deref_mut(&mut self) -> &mut Self::Target;
-}
-```
-
-Implementing the `Deref` trait gives a type `T` two features:
-*   It can be dereferenced: `*x` becomes syntax sugar for `*(x.deref())` or `*(x.deref_mut())`, depending on whether the resulting lvalue is assigned to.
-*   It gains auto-deref: if `x.foo` is not a field or method of `T`, then it expands into `x.deref().foo` or `x.deref_mut().foo`, again depending on use.
-
-Furthermore, `deref` and `deref_mut` are called by doing an explicit reborrow: `&*x` and `&mut *x`.
-
-One example of a smart pointer is `ManuallyDrop<T>`.
-Even though this type contains a `T` directly (rather than through a reference), it's still called a "smart pointer", because it can be dereferenced to obtain the `T` inside, and methods of `T` can be called on it.
-As we will see later, the `RefCell<T>` type also produces smart pointers.
-It is not uncommon for generic wrapper types, which restrict access to a value, to be smart pointers.
-
-Note that, because `Target` is an associated type, a type can only dereference to one other type.
-
-While not quite as relevant to smart pointers, the `Index` and `IndexMut` traits are analogous to the `Deref` and `DerefMut` traits, which enable the `x[foo]` subscript syntax.
-`Index` looks like this:
-```rust
-trait Index<Idx> {
-  type Output;
-  fn index(&self, index: Idx) -> &Self::Output;
-}
-```
-
-An indexing operation, much like a dereference operation, expands from `x[idx]` into `*(x.index(idx))`.
-Note that indexing _can_ be overloaded, and is a useful example of how this overloading through traits can be useful.
-For example, `<[u8] as Index<usize>>::Output` is `u8`, while `<[u8] as Index<Range>>::Output` is `[u8]`.
-Indexing with a single index produces a single byte, while indexing with a range produces another slice.
-
-
-### Closures {#closures}
-
-Closures (sometimes called "lambda expressions" in other languages) are function literals that capture some portion of their environment, which can be passed into other functions to customize behavior.
-
-Closures are not mere function pointers, because of this captured state.
-The closest analogue to this in C is a function that takes a function pointer and some "context".
-For example, the Linux `pthread_create()` function takes a `void* (*start_routine)(void*)` argument and a `void* arg` argument.
-`arg` represents state needed by `start_routine` to execute.
-In a similar way, Rust closures need extra state to execute, except `arg` becomes part of the `start_routine` value.
-Not only that, Rust will synthesize a bespoke context struct for `arg`, where normally the programmer would need to do this manually.
-Rust makes this idiom much easier to use, and, as such, much more common.
-
-As we'll see, Rust has a number of different ABIs for closures, some of which closely resemble what `pthread_create` does; in some cases, the function pointer and its context can even be inlined.
-
-In Rust, the syntax for creating a closure is `|arg1, arg2| expr`.
-They can be very simple, like `|(k, _)| k` (which uses pattern-matching to extract the first element of a tuple) or complex, using a block expression to create a longer function: `|foo| { /* ... */ }`.
-The types of arguments can be optionally specified as `|foo: Foo| { /* ... */ }`, and the return type as `|foo| -> Bar { /* ... */ }`, though in almost all cases type inference can figure them out correctly.
-A closure that takes no arguments can be written as `|| /* ... */`.
-
-Closures capture their environment by reference; the mutable-ness of that reference is inferred from use.
-For example:
-```rust
-let x = /* ... */;
-let y = /* ... */;
-let f = |arg| {
-  x.do_thing(arg);  // Takes &self, so this implicitly produces a shared reference.
-  y.do_mut_thing(arg);  // Takes &mut self, so it takes a unique reference instead.
-};
-// Note: f holds a unique borrow of y.
-let z = &mut y;  // Error!
-```
-
-Above, `f` captures `x` by shared reference and `y` by unique reference.
-The actual closure value `f` is a synthetic struct containing the captures:
-```rust
-struct MyClosure<'a> {
-  x: &'a X,
-  y: &'a mut Y,
-}
-```
-Calling a closure, like `f()`, calls a synthetic function that takes `MyClosure` as its first argument.
-It's possible to instead capture by moving into the closure; this can be done with the `move |arg| { /* ... */ }` syntax.
-If it were applied to `f`, `MyClosure` would become
-```rust
-struct MyClosure<'a> {
-  x: X,
-  y: Y,
-}
-```
-
-Rust does not cleanly support mixing capture-by-move and capture-by-reference, but it is possible to mix them by capturing references by move:
-```rust
-let x = /* ... */;
-let y = /* ... */;
-let x_ref = &x;
-let f = move |arg| {
-  x_ref.do_thing(arg);  // Capture x_ref by move, aka capture x by shared ref.
-  y.do_mut_thing(arg);  // Capture y by move.
-};
-```
-The distinction between capture-by-move and capture-by-ref is mostly irrelevant for `Copy` types.
-
-To be polymorphic over different closure types, we use the special `Fn`, `FnMut`, and `FnOnce` traits.
-These represent functions that can be called by shared reference, unique reference, or by move.
-Closures that only capture shared references implement all three; closures that capture by unique reference implement only the latter two, and closures that capture by move implement only the last one[^114].
-Function pointers, function items[^115], and closures that don't capture also implement all three, and can all be converted to function pointers.
-
-These traits use special syntax similar to function pointers[^116].
-For example, `Fn(i32) -> i32` represents taking an `i32` argument and returning another `i32`.
-Closures also implement `Copy` and `Clone` if all of their captures do, too.
-
-
-#### Closures as Function Arguments {#closures-as-function-arguments}
-
-There are roughly two ways of writing a function that accepts a closure argument: through dynamic dispatch, or through static dispatch, which have a performance and a size penalty, respectively.
-
-`Fn` and `FnMut` closures can be accepted using trait objects:
-```rust
-fn my_do_thing(func: &dyn Fn(i32) -> i32) -> i32 {
-  func(MY_CONST)
-}
-```
-This is completely identical to the C approach: the synthetic function lives in the trait object vtable, while the captures are behind the actual trait object pointer itself.
-In other words:
-```rust
-struct DynClosure {
-  vtable: *mut Vtable,
-  captures: *mut Captures,
-}
-```
-Of course, the vtable call has a performance penalty, but avoids the code size overhead of generic instantiation.
-
-Using generics allows passing in closures implementing `Fn`, `FnMut`, or `FnOnce`, by specializing the called function for each function type:
-```rust
-fn my_do_thing<F: Fn(i32) -> i32>(func: F) -> i32 {
-  func(MY_CONST)
-}
-```
-This will translate to a direct call to the synthetic closure function with no overhead, but will duplicate the function for each closure passed in, which can result in a big size hit if used on large functions.
-
-It is possible to use a shorthand for declaring this type of function, that avoids having to declare a type parameter:
-```rust
-fn my_do_thing(func: impl Fn(i32) -> i32) -> i32 { /* ... */ }
-```
-
-The pseudotype `impl Trait` can be used in function argument position to say "this parameter can be of any type that implements `Trait`", which effectively declares an anonymous generic parameter.
-Note that `Trait` can technically be any generic bound involving at least one trait: `impl Clone + Default` and `impl Clone + 'a` are valid.
-
-
-#### Closures as Function Returns {#closures-as-function-returns}
-
-Closure types are generally unnameable. The canonical way to return closures is with `impl Trait` in return position:
-```rust
-fn new_fn() -> impl Fn(i32) -> i32 {
-  |x| x * x
-}
-```
-
-Return-position `impl Trait` means "this function returns some unspecified type that implements `Trait`".
-Callers of the function are not able to use the actual type, only functions provided through `Trait`.
-`impl Trait` can also be used to hide implementation details, when a returned value only exists to implement some trait.
-
-Return-position `impl Trait` has a major caveat: it cannot return multiple types that implement the trait.
-For example, the following code is a type error:
-```rust
-fn new_fn(flag: bool) -> impl Fn(i32) -> i32 {
-  if flag {
-    |_| 0
-  } else {
-    |x| x * x
-  }
-}
-```
-The resulting compiler error looks like this:
-```rust
-  = note: expected type `[closure@src/lib.rs:3:5: 3:10]`
-          found closure `[closure@src/lib.rs:5:5: 5:14]`
-  = note: no two closures, even if identical, have the same type
-  = help: consider boxing your closure and/or using it as a trait object
-```
-In a non-embedded context, the solution (as suggested by the compiler) would be to allocate the closures on the heap, and use trait objects.
-However, allocation is limited in embedded contexts, so this solution is unavailable.
-
-If none of the closures capture, returning a function pointer may be an acceptable solution:
-```rust
-fn new_fn(flag: bool) -> fn(i32) -> i32 {
-  if flag {
-    |_| 0
-  } else {
-    |x| x * x
-  }
-}
-```
-#### Closures as Struct Fields {#closures-as-struct-fields}
-
-Using closures as struct fields is fairly limited without being able to easily allocate.
-The two options are to either make the trait generic on the closure type (which needs to be propagated through everything that uses the struct), or to require that closures not capture, and use function pointers instead:
-```rust
-struct MyStruct<F>
-  where F: Fn(i32) -> i32 {
-  val: usize,
-  func: F,
-}
-// Vs.
-struct MyStruct {
-  val: usize,
-  func: fn(i32) -> i32,
-}
-```
-In general, function pointers are easiest, and the requirement of no captures is not particularly onerous.
-The generic variant tends to be more useful for short-lived types, like combinators.
-
-Short-lived structs can also try to use trait objects, but the lifetime requirement can be fairly limiting:
-```rust
-struct MyStruct<'a> {
-  val: usize,
-  func: &'a dyn Fn(i32) -> i32,
-}
-```
-
-
-### `Option` and `Result`: Error Handling in Rust {#option-and-result-error-handling-in-rust}
-
-As we saw above, `Option` is a type that lets us specify a "potentially uninitialized" value.
-While it is common to work with `Option` using `match` expressions, it also has a number of convenience functions that shorten common sequences of code.
-`is_none()` can be used to check that an `Option` is empty; `map` can be used to convert the value inside an Option:
-```rust
-opt.map(|t| t + 1)  // Increments the value inside, if there is one.
-```
-
-`unwrap_or()` can be used to extract the value inside, with a default:
-```rust
-opt.unwrap_or(42)  // Get the value inside, or the value 42 if the former is unavailable.
-```
-
-The documentation for `Option` describes a number of other potential uses and operations on `Option`: [https://doc.rust-lang.org/std/option](https://doc.rust-lang.org/std/option/).
-The type documentation itself has a full list of all of the convenience functions defined for `Option`: [https://doc.rust-lang.org/std/option/enum.Option.html](https://doc.rust-lang.org/std/option/enum.Option.html).
-
-One key application of `Option` is safe nullable references: `Option<&T>`.
-The Rust language guarantees that `Option<&T>` is identical to a nullable pointer at the ABI layer[^117], so it can be safely passed that way into C code.
-This optimization is also available for structs which contain at least one reference: the `is_none()` bit will be compressed into one of the struct's reference fields.
-Some other types are also eligible for memory layout optimization, such as `NonZeroI32`[^118].
-
-`Result<T, E>` is similar to `Option<T>`, but rather than having an "empty" state, it has an "error" state:
-```rust
-enum Result<T, E> {
-  Ok(T),
-  Err(E),
-}
-```
-
-A `Result<T, E>` represents a completed computation of a value of type `T` that might have gone wrong.
-The "gone wrong" part is represented by some user-specified type `E`.
-`E` is usually some kind of enum type, since Rust does not have a single error type for all situations.
-```rust
-enum MyError {
-  DeadlineExceeded,
-  BufferExhausted(usize),
-  OtherError(ErrorCode),
-}
-```
-It is fairly common to define custom `Result` types using a common error enum for your code:
-```rust
-type Result<T> = std::result::Result<T, MyError>;
-```
-
-In a way, `Option<T>` is just a `Result<T, ()>`, where the error type is just the trivial unit tuple.
-Rust provides a number of functions for converting between them:
-```rust
-opt.ok_or(error)  // Converts Option<T> into Result<T, E>, using the provided error if
-// the Option is empty.
-res.ok()  // Discards the error portion and returns an Option<T>.
-res.err()  // Discards the ok portion and returns an Option<E>.
-```
-Computations that are executed for their side-effects which can fail, such as a write, tend to return `Result<(), E>`.
-This is unlike `C`, where the handling of functions that can fail is inconsistent when they return `void`, because `void` is not a real type.
-
-Sometimes, it is necessary to produce a `Result`, due to some trait's interface, for an operation that cannot fail.
-Current practice is to use the type `Result<T, std::convert::Infallible>`[^119], which can be matched on as follows:
-```rust
-let res: Result<T, Infallible> = /* ... */;
-match res {
-  Ok(t) => { /* ... */ },
-  Err(x) => match x {},
-}
-```
-
-`Result` supports a special early-return syntax.
-When in a function returning `Result<T, E>`, and you have a value `res` of type `Result<U, E>`, the expression `res?` will unwrap the `Result`, and get the "ok" value inside if it's present, or immediately return with the error if it isn't.
-That is, `res?` is translated to
-```result
-match res {
-  Ok(x) => x,
-  Err(e) => return Err(e),
-}
-```
-This question mark operator can be chained with methods, so that straightforward code that early-returns on the first error can be written without explicit control flow:
-```
-let x = my_thing.foo()?.bar()?.baz()?;
-```
-See [https://doc.rust-lang.org/std/result/index.html](https://doc.rust-lang.org/std/result/index.html) for more `Result` operations.
-
-
-### `for` Revisited: Iterators {#for-revisited-iterators}
-
-An _iterator_ is any type that implements the `Iterator` trait, which looks like this:
-```rust
-trait Iterator {
-  type Item;
-  fn next(&mut self) -> Option<Self::Item>;
-}
-```
-
-An iterator produces a sequence of `Option<Item>` values; the `next()` method allows an iterator to advance some internal state and produce the next value in the sequence.
-
-For example, a very simple iterator simply produces every nonnegative integer value, in sequence:
-```rust
-struct Counter { state: u64 }
-impl Iterator for Counter {
-  type Item = u64;
-  fn next(&mut self) -> Option<u64> {
-    let current = self.state;
-    self.state += 1;
-    Some(current)
-  }
-}
-```
-
-This iterator will produce values forever: it always returns `Some`.
-An iterator that eventually produces `None`, and then forever more returns `None`, is called "fused"[^120].
-Some iterators may start returning `Some` again after returning `None`, but most Rust constructs treat all iterators as if they are fused.
-
-A related trait is the `IntoIter` trait:
-
-```rust
-trait IntoIter {
-  type Iter: Iterator;
-  fn into_iter(self) -> Self::Iter;
-}
-```
-
-An `IntoIter` type can be converted into an iterator.
-This type is used to drive the `for` loops we saw for iterating slices:
-```rust
-for pattern in expr {
-  // ...
-}
-// is syntactic sugar for
-let iter = expr.into_iter();
-while let Some(pattern) = iter.next() {
-  // ...
-}
-```
-In the slice example, `&'a [T]` implements `IntoIter`, which produces an iterator that produces each element of the slice, in sequence; the `Range<i32>` type (which is what the syntax `0..32` produces) also implements `IntoIter`.
-
-All this machinery allows users to build their own iterators for use with `for` loops for their own types, or using existing iterators and _iterator combinators_.
-The `Iterator` trait defines dozens of provided methods, which can be used to build more complex iterators.
-Here are a few examples of particularly useful combinators:
-
-*   `iter.chain(iter2)`.
-    Chains two iterators with the same `Item` type together.
-     The second iterator starts when `iter` produces `None`.
-*   `iter.peekable()`.
-    Converts the iterator into an iterator with a `.peek()` function, which returns a reference to the next value in the sequence (but does not advance it).
-*   `iter.enumerate()`.
-    Changes the `Item` type from `T` into `(usize, T)`, tracking the current index in the sequence along with the value.
-*   `iter.step_by(n)`.
-    Changes the iterator to return every `n`th element.
-*   `iter.take(n)`.
-    Shortens the iterator to return `n` elements before fusing.
-*   `iter.map(|x| /* ... */)`.
-     Applies a closure to each element lazily.
-*   `iter.filter(|x| /* ... */)`.
-     Applies a predicate to each element; if the predicate returns `false`, it is skipped by `next()`.
-
-A number of other traits can enhance the properties of an iterator, enabling further methods: `ExactSizeIterator` iterators produce a known, fixed number of values; `DoubleEndedIterators` can have elements pulled from both the front, and the back of the sequence.
-While many of the operations above have naive implementations in terms of `next`, standard library iterators will override them when a more efficient algorithm is available.
-In general, iterators can produce very efficient code similar to that emitted by `while` loops, but care should be taken when using especially complex chains of combinators.
-
-See [https://doc.rust-lang.org/std/iter/trait.Iterator.html](https://doc.rust-lang.org/std/iter/trait.Iterator.html) and [https://doc.rust-lang.org/std/iter](https://doc.rust-lang.org/std/iter/) for full details on available operations.
-
-
-### Modules and Crate Layout {#modules-and-crate-layout}
-
-Each Rust crate is (from the compiler's perspective) given a unique, single-identifier name.
-This name is used to namespace a crate's symbols[^121].
-`core` and `std` are crates.
-
-Each crate is rooted at a `lib.rs` or `main.rs` file, depending on whether it is a library or a binary.
-This file can declare _modules_, which are sub-namespaces of a crate:
-```rust
-// Declares a public module named `devices`. Its definition is found in
-// either `devices.rs` or `devices/mod.rs`, relative to the current file.
-pub mod devices;
-
-// Declares a private module named `tests`. Its definition is found
-// within the curly braces.
-mod tests {
-  // ...
-}
-
-// Declares a private module named `generated`. Its definition is found
-// in the given path.
-#[path = "relative/path/to/file.rs"]
-mod generated;
-```
-Modules can nest arbitrarily: a module can declare more modules.
-
-Symbols in a module can be referred to by paths: `std::mem::drop` refers to the symbol `drop` in the module `std::mem` of crate `std`.
-`crate::devices::gpio::Gpio` refers to the symbol `Gpio` in the module `devices::gpio` of the current crate.
-
-`use` items can be used to create symbol aliases in the current scope:
-```rust
-// Pull in std::mem::drop, aliased to `drop`.
-use std::mem::drop;
-
-// Pull in the module std::mem, so we can now write `mem::drop` for `std::mem::drop`.
-use std::mem;
-
-// Pull in both size_of and drop:
-use std::mem::{size_of, drop};
-
-// Pull in all symbols in `std::mem`, including `drop`. Should typically be best
-// avoided, for readability.
-use std::mem::*;
-
-// Pull in all symbols from the parent module:
-use super::*;
-
-// Pull in a symbol from a submodule (synonymous with using the full
-// path starting with `crate`).
-use self::devices::Gpio;
-
-// Pull in a name, but rename it.
-use std::io::Result as IoResult;
-
-// Pull in a trait to enable its methods, but without pulling its name
-// into scope.
-use std::io::Write as _;
-```
-
-Note that this is subject to visibility restrictions.
-By default, all symbols are "private", only visible in the current module and its child modules.
-This can be spelled explicitly as `pub(self)`.
-A symbol can be restricted to the parent module and child modules with `pub(super)`, and to the current crate with `pub(crate)`.
-Finally, a symbol can be restricted to a particular path using `pub(in that::path)`.
-`pub` simply means "public everywhere".
-
-Pretty much all items can be marked with visibilities, except `impl`s.
-Marking a module with visibility restricts the visibility of all items within.
-A `pub` symbol in a `pub(crate)` module is `pub(crate)`.
-`use` statements can also be marked with visibility: this will cause the imported symbols to become part of the module.
-For example, `std` is full of instances of `pub use core::Symbol;`, to enable `core` symbols to be imported through `std`.
-
-Even `use` items can be marked with visibility:
-```rust
-// mod my_mod
-pub use std::mem::size_of;
-```
-This means that other modules can now access the symbol `size_of` through `my_mod::size_of`, effectively re-exporting the symbol.
-This is how many fundamental `core` types are accessible through the `std` crate as well.
-
-Rust does not have headers, or declaration order constraints; modules within a crate can freely form cyclic dependencies, since they are not units of compilation, merely namespaces.
-Rust crate namespaces are closed: after a crate is fully compiled, no other symbols can be added to it.
-
-
-### Interior Mutability {#interior-mutability}
-
-_Interior mutability_ is a borrow check escape hatch for working around Rust's aliasing rules.
-
-Normally, Rust requires that you prove statically that you have unique access to a value before you mutate it.
-`UnsafeCell<T>`[^122] is a special, compiler-blessed[^123] type which contains a single `T`, and has a method `fn get(&self) -> *mut T`.
-When you can guarantee at runtime that a shared reference to an `UnsafeCell` is actually unique, the raw pointer returned by `get()` can be converted into a unique reference.
-This makes it possible to safely mutate code that is known, at runtime, to be uniquely owned.
-Of course, it is very unsafe to use `UnsafeCell` directly, and exists to form the basis of other abstractions.
-
-There are two common strategies for exposing interior mutability safely: the `Cell`[^124] approach and the `RefCell`[^125] approach.
-
-The `Cell` approach simply never creates a unique reference at all: instead, it holds a valid `T` at all times, and provides a `swap` primitive for taking out the `T` and leaving behind another one.
-This way, no aliasing rules need to be enforced, since no reference actually points to that `T`.
-
-The `RefCell` approach instead does basic borrow checking at runtime.
-In addition to holding a `T`, a `RefCell` holds a counter of the number of outstanding shared references (or a sentinel value for an outstanding unique reference).
-The `try_borrow()` and `try_borrow_mut()` methods dynamically check if such a borrow is valid (no outstanding unique references, or no outstanding references at all, respectively), and return a `Result` to indicate success or failure.
-On success, the returned value is a smart pointer wrapping a reference, whose destructor will decrement the shared/unique reference count in the original `RefCell`.
-In other words, a `RefCell` is like a single-threaded read-write mutex, without the cost of atomicity.
-
-Other abstractions can be built on top of `UnsafeCell` that maintain the aliasing invariants with other strategies, but they will ultimately be analogous to one of `Cell` or `RefCell`.
-
-Interior mutability is also one of the main differences between a constant and a static:
-```rust
-static S: MyCell<u32> = MyCell::new(0);
-const C: MyCell<u32> = MyCell::new(0);
-
-S.set(1);
-S.get();  // value = 1, because `set` modified the memory location.
-C.set(1);
-C.get()  // value = 0, because `set` modified an inlined copy.
-```
-Note that the memory behind `S` changed, so it must be allocated in a `.data` or `.bss` section.
-This illustrates another property of `UnsafeCell`: it causes data that is otherwise declared as immutable to be allocated as mutable.
-
-See [https://doc.rust-lang.org/std/cell/index.html](https://doc.rust-lang.org/std/cell/index.html) for more details; as with all aliasing-related topics, it requires careful attention t detail, and this section is far from exhaustive.
-
-
-### Unsafe Rust {#unsafe-rust}
-
-Unsafe Rust is a dialect of Rust that is denoted by the keyword `unsafe`: `unsafe` blocks, unsafe functions, unsafe traits.
-Importantly, all of these actions require uttering the keyword `unsafe`, so that they can be easily detected in code review.
-Code inside unsafe blocks says to the  reader that the programmer has checked subtle safety guarantees that the compiler cannot on its own.
-
-Unsafe Rust fundamentally works by "turning off" certain checks the compiler normally enforces, whenever inside a `unsafe { /* ... */ }` block expression or the body of an `unsafe fn`.
-The things that Unsafe Rust can do that Safe Rust cannot are:
-*   Call `unsafe` functions.
-*   Dereference raw pointers.
-*   Mutate global state through a mutable `static`.
-*   Read union fields.
-*   Call the `asm!` macro.
-
-Additionally, `unsafe impl`s may implement `unsafe trait`s, but don't need to be inside an `unsafe` block.
-
-The canonical reference is the Rustonomicon[^126], a non-normative document describing common uses for Unsafe Rust.
-It is required reading (mostly the first half) for embedded programming.
-It contains detailed examples of correct and incorrect Unsafe Rust usage, and guidance on when to invoke Unsafe Rust.
-
-Throughout this document, references to Unsafe Rust are made, mostly around calling unsafe functions and referencing raw pointers, which are roughly all that Unsafe Rust can do that normal Rust can't.
-With these powers comes responsibility: Unsafe Rust is not safe from Undefined Behavior, and can leave the machine in a state where actions allowed in normal, safe Rust would trigger Undefined Behavior.
-In general, a few rules of thumb are useful:
-*   Every `unsafe fn` should declare, in documentation, what invariants it assumes that the caller will uphold, and what state it will leave the machine in.
-    For example, `<[T]>::get_unchecked(n)` elides the bounds check for the indexing operation, and it is up to the caller to uphold it instead.
-*   Every time `unsafe` code calls into a non-`unsafe` function, it must ensure that no violated invariants, which could trigger Undefined Behavior, are observable in that safe code.
-    For example, if we have a type that maintains the invariant that `len > 0`, and we temporarily set it to `0` during an `unsafe` block, it must be restored to `> 0` before any safe methods can be called on that type.
-*   Unsafe code should be kept to the absolute minimum, and wrapped in safe interfaces that assert invariants, either through static type-system guarantees or through runtime checks.
-    Every line of unsafe code is a place where the engineering cost of Rust's guarantees are wasted.
-
-In other words, Safe Rust is able to freely assume that Rust's guarantees on aliasing, ownership, and representation of values hold at all times.
-This assumption is pervasive: not only does the compiler use it aggressively optimize code for speed and size, but other library code, such as the destructors of wrapper types, assume it, too.
-Unsafe Rust is responsible for upholding this core guarantee.
-In a way, Unsafe Rust is responsible for protecting Safe Rust.
-
-## Footnotes
-
-[^1]: The "embedded Rust book" is a useful resource for existing rust programmers.
-This document assumes no knowledge of Rust or C++.
-
-[^2]: In particular, use-after-frees, double frees, null dereferences, and data races are all impossible in Rust without using the keyword `unsafe`; this applies for most other things traditionally considered Undefined Behavior in C.
-However, Unsafe Rust has Undefined Behavior, and, due to its stronger invariants, Rust is generally more punishing than C compilers when Undefined Behavior occurs.
-
-[^3]: Alternative implementations exist, though `rustc` is the reference implementation.
-Rust is still in the process of being specified, so, for now, `rustc`'s behavior is mostly normative.
-
-[^4]: Not every project uses rustup, but it's frequently necessary for "nightly" features.
-
-[^5]: Inline assembly is the most salient of these.
-Nightly also provides support for the sanitizers, such as ASAN and TSAN.
-
-[^6]: Example from Tock: [https://github.com/tock/tock/blob/master/rust-toolchain](https://github.com/tock/tock/blob/master/rust-toolchain)
-
-[^7]: Rust libraries use their own special "rlib" format, which carries extra metadata past what a normal .a file would.
-
-[^8]: While each crate is essentially a giant object file, Rust will split up large crates into smaller translation units to aid compilation time.
-
-[^9]: The formatter is kind of a new component, so may require separate installation through `rustup`.
-See [https://github.com/rust-lang/rustfmt#on-the-stable-toolchain](https://github.com/rust-lang/rustfmt#on-the-stable-toolchain).
-
-[^10]: [https://doc.rust-lang.org/core/index.html](https://doc.rust-lang.org/core/index.html)
-
-[^11]: They are also used analogously to `ptrdiff_t` and `size_t`.
-In practice, these types are all the same width on modern systems, though C draws a distinction for portability reasons.
-
-[^12]: Note that Rust spells bitwise negation on integers as `!x` rather than `~x`.
-This is because Rust has a separate boolean type, for which `!x` is logical not.
-
-[^13]: Rust has slightly less absurd precedence rules around bitwise operators; `x ^ y == z` is `(x ^ y) == z`.
-
-[^14]: In C, signed overflow is UB, and unsigned overflow always wraps around.
-
-[^15]: Really, this causes a "panic", which triggers unwinding of the stack.
-In embedded contexts, unwinding is disabled, so this is just a jump to the panic handler, which performs a platform-specific abort.
-
-[^16]: rustc performs the former in debug builds and the latter in release builds.
-
-[^17]: Creating `bool`s with any other representation, using Unsafe Rust, is instant UB.
-This is sometimes called the "validity invariant", and applies to other restricted-range types, such as padding bytes in structs.
-["What The Hardware Does" is not What Your Program Does: Uninitialized Memory](https://www.ralfj.de/blog/2019/07/14/uninit.html) provides a short explanation about why this is important.
-
-[^18]: These functions are frequently entry-points for LLVM intrinsics, similar to GCC/Clangs `__builtin_clz()` and similar.
-
-[^19]: This syntax: `(my_struct_t) { .a = 0, .b = 1 }`.
-
-[^20]: Rust allows trailing commas basically everywhere.
-
-[^21]: Currently, the compiler will reorder struct fields to minimize alignment padding, though there is ongoing discussion to add a "struct field layout randomization" flag as an ASLR-like hardening.
-
-[^22]: Unlike C, Rust has true zero-sized types (ZSTs).
-They are completely elided from the program at runtime, but can be useful for creating abstractions.
-
-[^23]: This is similar to how a `bool` must always be `0` or `1`.
-One could even imagine that `bool` is defined as `enum bool { false = 0, true = 1 }`.
-
-[^24]: Without uttering `unsafe`, it is impossible to witness uninitialized or invalid data.
-Doing so even with `unsafe` is Undefined Behavior.
-
-[^25]: This feature is also sometimes known as "algebraic data types".
-
-[^26]: This syntax requires that the array component type be "copyable", which we will get to later.
-
-[^27]: These accesses are often elided.
-LLVM is very good at optimizing them away, but not perfect.
-
-[^28]: In C, pointers must _always_ be well-aligned.
-Rust only requires this when dereferencing the pointer.
-
-[^29]: In other words, Rust's aliasing model for pointers is the same as that of `-fno-strict-aliasing`.
-Rust allows `*mut u32` and `*mut u64`to alias, for example.
-
-[^30]: In C, it is technically UB to forge raw pointers (such as creating a pointer to an MMIO register) and then dereference them, although all embedded programs need to do this anyway.
-While Rust has not yet formalized what this means, it seems unlikely that they will make forging raw pointers to MMIO regions UB.
-
-[^31]: Or by casting zero.
-
-[^32]: Rust currently does not have an equivalent of the `->` operator, though it may get one some day.
-As such, it is not possible to get pointers to fields of a pointer-to-struct without creating a reference along the way.
-
-[^33]: We will get into references later.
-
-[^34]: We will learn more about "move semantics" when we discuss ownership.
-
-[^35]: However, great care should be taken when using these methods on types that track ownership of a resource, since this can result in a double-free when the pointee is freed.
-
-[^36]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.read_unaligned](https://doc.rust-lang.org/std/primitive.pointer.html#method.read_unaligned)
-
-[^37]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.write_unaligned](https://doc.rust-lang.org/std/primitive.pointer.html#method.write_unaligned)
-
-[^38]: These are effectively memcpys: they will behave as if they read each byte individually with no respect for alignment.
-
-[^39]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to](https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to)
-
-[^40]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to_nonoverlapping](https://doc.rust-lang.org/std/primitive.pointer.html#method.copy_to_nonoverlapping)
-
-[^41]: Rust also provides an abstraction for dealing with uninitialized memory that has somewhat fewer sharp edges: [https://doc.rust-lang.org/std/mem/union.MaybeUninit.html](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html).
-
-[^42]: Rust also allows taking the addresses of rvalues, which simply shoves them onto `.rodata` or the stack, depending on mutation.
-For example, `&0` is valid, and produces an address in `.rodata`, while `&mut 0` will push into the stack. The mechanism that converts `&0` into a reference to a constant is called *rvalue promotion*, since `0`, an rvalue of limited lifetime, is promoted into a constant, which has `'static` lifetime.
-
-[^43]: Completely unrelated to the meaning of this keyword in C, where it specifies a symbol's linkage.
-
-[^44]: Immutable statics seem pretty useless: why not make them constants, since you can't mutate them?
-This distinction will arise again when we discuss _interior mutability_ towards the end of the document.
-Also, like globals in `C`, these static variables can be made visible to other libraries.
-
-[^45]: In practice, this is just the C calling convention, except that `#[repr(Rust)]` structs and enums are aggressively broken up into words so they can be passed in registers.
-On 32-bit RISC-V, `(u64, u32, i32)` is passed in four registers.
-Rust functions also do some handling of "panics" in their prologues that is mostly irrelevant in an embedded context.
-
-[^46]: Supported calling conventions: [https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions](https://doc.rust-lang.org/nomicon/ffi.html#foreign-calling-conventions)
-
-[^47]: For those familiar with C++, extern blocks *do* disable name mangling there.
-
-[^48]: Rust's type inference is very powerful, which is part of its ML heritage.
-Readable Rust code should include type annotations where necessary to ensure that the type of every `let` can be deduced without having to look at distant context; this is analogous to similar advice for `auto` in C++.
-
-[^49]: It is also possible to leave off the expression in a `let` binding: `let x;`.
-The variable cannot be used until Rust can prove that, in all branches of the program, the variable has been assigned to (and, of course, all the assignments must produce the same type).
-
-[^50]: A missing else block implicitly gets replaced by `else {}`, so the type of all blocks needs to be `()`.
-
-[^51]: The value being matched on is called the "scrutinee" (as in scrutinize) in some compiler errors.
-
-[^52]: Rust has a long-standing miscompilation where `loop {}` triggers UB; this is an issue with LLVM that is actively being worked on, and which in practice is rarely an issue for users.
-
-[^53]: Needless to say, all `break`s must have the same type.
-
-[^54]: Rust's execution model is far more constrained than C, so C function calls are basically black boxes that inhibit optimization.
-However, there is no runtime cost compared to cross-library-calls in C.
-
-[^55]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.read_volatile](https://doc.rust-lang.org/std/primitive.pointer.html#method.read_volatile)
-
-[^56]: [https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile](https://doc.rust-lang.org/std/primitive.pointer.html#method.write_volatile)
-
-[^57]: Of course, due to an LLVM bug, this is not guaranteed to work...
-
-[^58]: Caveats apply as with `__attribute__((inline_always))`.
-
-[^59]: This coincides with the similar notion in C++: the _owner_ of a value is whomever is responsible for destroying it. We'll cover Rust destructors later on.
-
-[^60]: "Owners" don't need to be stack variables: they can be heap allocations, global variables, function parameters for a function call, or an element of an array.
-
-[^61]: Suggested pronunciations include: "tick a", "apostrophe a", and "lifetime a".
-
-[^62]: These days, it's a subgraph of the control flow graph.
-
-[^63]: The compiler does so using strict "elision rules", and does not actually peek into the function body to do so.
-
-[^64]: Rust is so strict about this that it will mark code as unreachable and emit illegal instructions if it notices that this is happening.
-
-[^65]: Also, note that strict aliasing does not apply to shared references, either.
-`&u32` and `&u64` may alias, but `&mut u32` and `&mut u64` cannot, because a `&mut T` can never alias with any other active reference.
-
-[^66]: To put it in perspective, all references are marked as "dereferenceable" at the LLVM layer, which gives them the same optimization semantics as C++ references.
-Merely materializing an invalid reference is Undefined Behavior, because LLVM will treat it as a pointer to valid memory that it can cache reads from and combine writes to.
-
-[^67]: It is common convention in Rust to name the default function for creating a new value `new`; it is not a reserved word.
-
-[^68]: Note the capital S.
-
-[^69]: This is currently fairly restricted; for our purposes, only `Self`, `&Self`, and `&mut Self` are allowed.
-
-[^70]: There's only a couple of dynamically sized types built into the language; user-defined DSTs exist, but they're a very advanced topic.
-
-[^71]: https://doc.rust-lang.org/std/str/index.html
-
-[^72]: It should be noted that `a..b` is itself an expression, which creates a `Range<T>` of the chosen numeric type.
-
-[^73]: [https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut)
-
-[^74]: [https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html)
-
-[^75]: The second quote disambiguates them from lifetime names.
-
-[^76]: [https://doc.rust-lang.org/std/mem/fn.drop.html](https://doc.rust-lang.org/std/mem/fn.drop.html)
-
-[^77]: Unions also cannot contain types with destructors in them.
-
-[^78]: While not available in embedded environments, `Box<T>` in the standard library is an implementation of this idea: [https://doc.rust-lang.org/std/boxed/index.html](https://doc.rust-lang.org/std/boxed/index.html).
-
-[^79]: [https://doc.rust-lang.org/std/mem/fn.forget.html](https://doc.rust-lang.org/std/mem/fn.forget.html)
-
-[^80]: [https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html](https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html)
-
-[^81]: We'll get to these later.
-
-[^82]: [https://doc.rust-lang.org/std/mem/fn.needs_drop.html](https://doc.rust-lang.org/std/mem/fn.needs_drop.html)
-
-[^83]:
-[https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html](https://doc.rust-lang.org/std/ptr/fn.drop_in_place.html)
-
-[^84]: Though naively, it might seem like this would make `Option<T>` bigger than `T` (twice as big, for an integer type, where alignment == size), the compiler can optimize the size down.
-For example, as we'll see later, `Option::<&T>::None` is represented as a null pointer, since `&T` can never be null.
-
-[^85]: The single underscore is a keyword in Rust.
-
-[^86]: Inclusive ranges are currently an unstable feature.
-
-[^87]: Of course, the compiler has no problem optimizing match expressions into C switch statements when possible.
-
-[^88]: [https://doc.rust-lang.org/std/clone/trait.Clone.html](https://doc.rust-lang.org/std/clone/trait.Clone.html)
-
-[^89]: [https://doc.rust-lang.org/std/default/trait.Default.html](https://doc.rust-lang.org/std/default/trait.Default.html)
-
-[^90]: [https://doc.rust-lang.org/std/cmp/trait.PartialEq.html](https://doc.rust-lang.org/std/cmp/trait.PartialEq.html)
-
-[^91]: [https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html)
-
-[^92]: [https://doc.rust-lang.org/std/hash/trait.Hash.html](https://doc.rust-lang.org/std/hash/trait.Hash.html)
-
-[^93]: The so-called Universal Function Call Syntax.
-
-[^94]: [https://doc.rust-lang.org/std/ops/index.html](https://doc.rust-lang.org/std/ops/index.html)
-
-[^95]: Associated types and constants will also disqualify a trait.
-
-[^96]: The trait `Sized` is implemented by all types with a compile-time known size.
-In the language of generic bounds, trait object methods cannot rely on the fact that `Self: Sized`.
-
-[^97]: This is sometimes called being "thread compatible".
-
-[^98]: [https://doc.rust-lang.org/std/marker/index.html](https://doc.rust-lang.org/std/marker/index.html)
-
-[^99]: I.e., write once, use for many types.
-
-[^100]: When mixing lifetime and type parameters, lifetimes go first: `<'a, T>`.
-
-[^101]: This syntax is necessary to disambiguate a generic call from the `<` operator, while keeping the grammar simple.
-It is also required when naming generic types in expression position, like `Option::<u32>::Some(42)`.
-
-[^102]: The `::<>` syntax is not used in type position, because it is not necessary for disambiguation.
-
-[^103]: The symbol `MyWrapper`, without angle brackets, is sometimes called a _type constructor_.
-Empty angle brackets are also allowed to follow non-generic symbols, mostly for syntactic completeness.
-`i32<>` is equivalent to `i32`.
-
-[^104]: Contrast this to C++: C++ template metaprogramming is similar to Rust generics, but significantly harder to use, because callsites can trigger compiler errors in the definition.
-Rust side-steps this issue completely.
-
-[^105]: A note on generic parameters vs associated types.
-These are similar, but distinct ways of attaching type information to a trait.
-While it is completely possible for a single type `T` to implement `Add<U1>` and `Add<U2>`, it is not possible to implement `Add<U1, Output=u32>` and `Add<U1, Output=u64>` at the same time.
-
-[^106]: Note the Universal Function Call Syntax to refer to a particular trait implementation.
-
-[^107]: `where` clauses can also be empty, which looks silly but is useful for macro authors: `fn foo() -> u32 where { /* ... */ }`.
-
-[^108]: While not present in C (except in C11 via the `_Generic` keyword), function overloading is a popular feature in many other languages.
-
-[^109]: [https://doc.rust-lang.org/std/marker/struct.PhantomData.html](https://doc.rust-lang.org/std/marker/struct.PhantomData.html)
-
-[^110]: [https://doc.rust-lang.org/nomicon/phantom-data.html](https://doc.rust-lang.org/nomicon/phantom-data.html)
-
-[^111]: C++ also has "smart pointers", though it is not as strict about what that means as Rust (none of the smart pointers used in embedded programming in Rust allocate, for example).
-
-[^112]: [https://doc.rust-lang.org/stable/std/ops/trait.Deref.html](https://doc.rust-lang.org/stable/std/ops/trait.Deref.html)
-
-[^113]: Even though raw pointers can be dereferenced with `*ptr`, they do _not_ implement `Deref`.
-Similarly, although references implement `Deref`, they are not typically called smart pointers.
-
-[^114]: These traits actually have an inheritance relation: calling by `&self` means you can obviously call by `&mut self`, by reborrowing the unique reference as a shared reference; similarly, if you can call by `&mut self`, you can call by `self`, by taking a unique reference to `self`.
-In practice, the compiler is pretty good at inlining away the extra calls and references.
-
-[^115]: Functions defined like `fn foo()` each have a unique, closure-like type that coerces to a function pointer when necessary.
-
-[^116]: This is currently magic, but is likely to become less magic in future versions.
-
-[^117]: See [https://doc.rust-lang.org/std/option/index.html#options-and-pointers-nullable-pointers](https://doc.rust-lang.org/std/option/index.html#options-and-pointers-nullable-pointers)
-
-[^118]: See [https://doc.rust-lang.org/std/num/struct.NonZeroI32.html](https://doc.rust-lang.org/std/num/struct.NonZeroI32.html)
-
-[^119]: See [https://doc.rust-lang.org/std/convert/enum.Infallible.html](https://doc.rust-lang.org/std/convert/enum.Infallible.html)
-
-[^120]: As in an electrical fuse: eventually, the fuse goes off and stops working.
-
-[^121]: These being Rust source-code symbols, _not_ linker symbols.
-
-[^122]: [https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html](https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html)
-
-[^123]: `UnsafeCell` (or, the effect it has on data, at any rate) is well-known to the optimizer and the code generator.
-As we'll see below, the presence of `UnsafeCell` can radically change how a value is laid out in memory, even though it logically only contains a `T`.
-
-[^124]: [https://doc.rust-lang.org/std/cell/struct.Cell.html](https://doc.rust-lang.org/std/cell/struct.Cell.html)
-
-[^125]: [https://doc.rust-lang.org/std/cell/struct.RefCell.html](https://doc.rust-lang.org/std/cell/struct.RefCell.html)
-
-[^126]: [https://doc.rust-lang.org/stable/nomicon/](https://doc.rust-lang.org/stable/nomicon/)
diff --git a/doc/ug/sec_cm_dv_framework/index.md b/doc/ug/sec_cm_dv_framework/index.md
deleted file mode 100644
index f065b92..0000000
--- a/doc/ug/sec_cm_dv_framework/index.md
+++ /dev/null
@@ -1,158 +0,0 @@
----
-title: "Security Countermeasure Verification Framework"
----
-
-## Purpose
-In a security chip, there are many hardening security countermeasures implemented in security blocks.
-Hardened security countermeasures are used to detect attacks by embedding integrity checks.
-Usually it can’t be triggered by software or toggling the pins on the chip.
-So, this may need to be tested one by one via forcing internal design signals on the security countermeasure related logic, which mimics attackers to flip the circuit.
-This kind of verification is needed but the effort could be tremendous.
-The work can be significantly reduced by using a unified verification framework.
-
-## Standardized Design Countermeasure Primitive
-Each countermeasure is implemented as a primitive - an RTL design building block written in SystemVerilog.
-The standardization of how these building blocks are designed paves the way for automated verification.
-
-There are 2 requirements that are needed in this primitive.
-1. Create a standard primitive for each kind of countermeasure, which can be reused across all blocks in the chip.
-Each primitive has an error indicator as an IO (or an internal signal if other blocks don’t need to connect with this error indicator).
-Once the design detects an attack, the error will be set.
-
-![diagram_of_sec_cm_prim](diagram_of_sec_cm_prim.svg)
-
-2. Once the error is set, the block should report a fatal alert or trigger unmasked interrupt to the system.
-As shown in the diagram below, there could be some additional logics which are triggered when error occurs but not unified across the blocks.
-For example, the block may contain custom logics which cancelling the operation when error occurs.
-![diagram_of_block_that_contains_sec_cm_primitive](diagram_of_block_that_contains_sec_cm_primitive.svg)
-
-## Embedded Common Checks In The Design
-At the top of each IP, an assertion check should be added to ensure that the fatal alert will be triggered once error is set.
-In the countermeasure primitive, there is another check to ensure that each instance of the countermeasure primitive must contain this assertion check.
-These 2 things can be accomplished by using the SystemVerilog macro.
-
-Firstly, define a macro in the countermeasure primitive and declare a logic “assert_connected” which will be assigned when the macro is invoked correctly.
-If the macro isn’t used for any instance of countermeasure primitive, the assertion will fail.
-
-```systemverilog
-`define ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(NAME_, PRIM_HIER_, ALERT_, MAX_CYCLES_ = 5) \
-  NAME_: assert property (@(posedge clk)) disable iff(!rst_n) \
-                ($rose(PRIM_HIER_.err_o) |-> ##[1:MAX_CYCLES_] $rose(ALERT_.alert_p)) \
-  `ifdef SIM_OR_FPV \
-  assign PRIM_HIER_.unused_assert_connected = 1'b1; \
-  `endif
-
-module prim_count();
-  ...
-  // This logic that will be assign to one, when user adds macro
-  // ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT to check the error with alert, in case that prim_count
-  // is used in design without adding this assertion check.
-  `ifdef SIM_OR_FPV \
-  logic assert_connected;
-
-  initial #0 assert(assert_connected === 1'b1);
-  `endif
-endmodule
-```
-
-Secondly, invoke the assertion macro for each instance of the countermeasure primitive in the IP top.
-
-```systemverilog
-module a_ip_top();
-  ...
-  // Invoke the assertion macro for each instance of the countermeasure primitive
-  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CtrlCntAlertCheck_A, u_ctrl.u_cnt, alert_tx_o[0])
-
-  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FsmCntAlertCheck_A, u_fsm.u_cnt, alert_tx_o[0])
-
-endmodule
-
-```
-
-### Special Handling of Sparse FSM Primitive
-
-Sparse FSMs in OpenTitan security IPs are implemented with the `prim_sparse_fsm_flop` countermeasure primitive to ensure that the state encoding cannot be altered by synthesis tools.
-This primititive also implements the embedded common checks mentioned above.
-
-However, simulation tools like Xcelium and VCS are at this time not able to correctly infer FSMs and report FSM coverage when the state registers reside in a different hierarchy (such as `prim_sparse_fsm_flop`) than the next-state logic of the FSMs.
-
-In order to work around this issue, the wrapper macro `PRIM_FLOP_SPARSE_FSM` should be used instead of directly instantiating the `prim_sparse_fsm_flop` primitive.
-The `PRIM_FLOP_SPARSE_FSM` macro instantiates a behavioral state register in addition to the `prim_sparse_fsm_flop` primitive when the design is built with `SIMULATION` defined.
-This enables simulation tools to correctly infer FSMs and report coverage accordingly.
-For other build targets that do not define `SIMULATION` this macro only instantiates the `prim_sparse_fsm_flop` primitive.
-
-An example of how the macro should be used is shown below:
-
-```systemverilog
-// u_state_flops: instance name of the prim_sparse_fsm_flop primitive
-// state_d: FSM next state
-// state_q: FSM current state
-// state_e: FSM state enum type
-// ResetSt: FSM reset state
-// clk_i: Clock of the design (defaults to clk_i)
-// rst_ni: Reset of the design (defaults to rst_ni)
-// SvaEn: A value of 1 enables the embedded assertion (defaults to 1)
-`PRIM_FLOP_SPARSE_FSM(u_state_flops, state_d, state__q, state_e, ResetSt, clk_i, rst_ni, SvaEn)
-```
-
-In order to generate a complete template for sparsely encoded FSMs, please refer to the [the sparse-fsm-encode.py script](https://github.com/lowRISC/opentitan/blob/master/util/design/sparse-fsm-encode.py).
-
-## Verification Framework For The Standardized Design Countermeasures
-
-This verification framework involves three different steps, which are all needed in order to acheive the completed verification for countermeasures.
-
-### Primitive-Level Verification
-Use FPV or a simple simulation based testbench to verify the correctness of the error indicator as well as other functionality in the primitive.
-This unit-level testing eases the verification work at the higher level.
-Coverage closure for this primitive at higher-level can be eliminated.
-
-### FPV Proves The Embedded Assertion
-Use FPV to prove that the embedded assertion is true (fatal alert is triggered once error is set).
-This mathematically proves that an error unconditionally leads to the fatal alert.
-
-### Block-Level Verification With A Unified Semi-Automated Framework.
-Triggering the fatal alert is usually not the only outcome of detecting an attack.
-Some other behavior may occur as well, such as wiping all the internal secrets or invaliding new commands, after detecting a fault.
-However, there are usually many security primitives that are embedded in different places of the security chip.
-It could take tremendous effort to manually test them one by one.
-The following steps are the key implementation of this framework, which uses some automation to reduce the effort.
-
-1. Create a SystemVerilog interface with a proxy class (refer to this [article](https://blog.verificationgentleman.com/2015/08/31/sv-if-polymorphism-extendability.html) for implementation) for each countermeasure primitive and bind the interface to the primitive module.
-All the proxy classes extend from the same base class.
-
-2. In the interface, implement an “initial” block that stores the handle of the proxy class to a queue in a package.
-This queue is declared as the base type of the proxy class. All the handles of various proxy classes are automatically stored at time 0 in the simulation.
-
-```systemverilog
-interface prim_count_if;
-
-  // this class has the access to any signals in the interface and interface can connect to the signals in the primitive
-  class prim_count_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy; // allow extendability
-    virtual task inject_fault();
-       // use uvm_hdl_force/deposit to do fault injection
-    endtask
-  endclass
-
-  prim_count_if_proxy if_proxy;
-  initial begin
-    if_proxy = new("if_proxy");
-    if_proxy.sec_cm_type = sec_cm_pkg::SecCmPrimCount;
-    if_proxy.prim_path = $sformatf("%m");
-    sec_cm_pkg::sec_cm_if_proxy_q.push_back(if_proxy);
-  end
-```
-
-3. In the base sequence, create a loop to test each instance of the countermeasure primitive. In each loop, “inject_fault” task from the proxy class will be invoked, which is followed by a “check_resp” task. The block owner can extend this sequence and add additional checks for non-standardized behavior based on the proxy_class info such as the hierarchy of the primitive instance.
-
-```systemverilog
-foreach (sec_cm_pkg::sec_cm_if_proxy_q[i]) begin
-    sec_cm_pkg::sec_cm_base_if_proxy if_proxy = sec_cm_pkg::sec_cm_if_proxy_q[i];
-    inject_fault(if_proxy));  // call if_proxy.inject_fault
-    check_resp(if_proxy);
-end
-```
-This automation essentially relies on standardizing design countermeasure primitives.
-It uses the primitive types to anchor all the instances of countermeasure primitives and store all the handles that associate to the interface of the countermeasure primitive in a global location.
-The framework loops over all the handles and provides a common sequence to check common behavior as well as callback functions that allow users to test any non-standard behavior.
-
-Refer to cip_lib [document]({{< relref "hw/dv/sv/cip_lib/doc#security-verification-in-cip_lib" >}}) at the section - "Security Verification for common countermeasure primitives" for the steps to enable this test in block-level testbench.
diff --git a/doc/ug/system_list.md b/doc/ug/system_list.md
deleted file mode 100644
index 5d7b1e3..0000000
--- a/doc/ug/system_list.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: "List of Top-Level Designs"
----
-
-This page lists all top-level designs and their targets that are contained within this repository.
-Click on the design name to get more information about the design.
-
-| Design | Internal Name | Simulation Targets | FPGA Targets | ASIC Targets | Description |
-|--------|---------------|--------------------|--------------|--------------|-------------|
-| [Earl Grey]({{< relref "hw/top_earlgrey/doc" >}}) | `top_earlgrey` | Verilator | ChipWhisperer CW310\* | *None yet.* | 0.1 release |
-
-`*` There exists a modified version of the Earl Grey top-level design that can be implemented on the Nexys Video FPGA board usable with a free EDA tool license.
diff --git a/doc/ug/vendor_hw.md b/doc/ug/vendor_hw.md
deleted file mode 100644
index 8e33215..0000000
--- a/doc/ug/vendor_hw.md
+++ /dev/null
@@ -1,405 +0,0 @@
----
-title: Work with hardware code in external repositories
----
-
-OpenTitan is not a closed ecosystem: we incorporate code from third parties, and we split out pieces of our code to reach a wider audience.
-In both cases, we need to import and use code from external repositories in our OpenTitan code base.
-Read on for step-by-step instructions for common tasks, and for background information on the topic.
-
-## Summary
-
-Code in subdirectories of `hw/vendor` is imported (copied in) from external repositories (which may be provided by lowRISC or other sources).
-The external repository is called "upstream".
-Any development on imported in `hw/vendor` code should happen upstream when possible.
-Files ending with `.vendor.hjson` indicate where the upstream repository is located.
-
-In particular, this means:
-
-- If you find a bug in imported code or want to enhance it, report it upstream.
-- Follow the rules and style guides of the upstream project.
-   They might differ from our own rules.
-- Use the upstream mechanisms to do code changes. In many cases, upstream uses GitHub just like we do with Pull Requests.
-- Work with upstream reviewers to get your changes merged into their code base.
-- Once the change is part of the upstream repository, the `util/vendor` tool can be used to copy the upstream code back into our OpenTitan repository.
-
-Read on for the longer version of these guidelines.
-
-Pushing changes upstream first isn't always possible or desirable: upstream might not accept changes, or be slow to respond.
-In some cases, code changes are needed which are irrelevant for upstream and need to be maintained by us.
-Our vendoring infrastructure is able to handle such cases, read on for more information on how to do it.
-
-## Background
-
-OpenTitan is developed in a "monorepo", a single repository containing all its source code.
-This approach is beneficial for many reasons, ranging from an easier workflow to better reproducibility of the results, and that's why large companies like [Google](https://ai.google/research/pubs/pub45424) and Facebook are using monorepos.
-Monorepos are even more compelling for hardware development, which cannot make use of a standardized language-specific package manager like npm or pip.
-
-At the same time, open source is all about sharing and a free flow of code between projects.
-We want to take in code from others, but also to give back and grow a wider ecosystem around our output.
-To be able to do that, code repositories should be sufficiently modular and self-contained.
-For example, if a CPU core is buried deep in a repository containing a full SoC design, people will have a hard time using this CPU core for their designs and contributing to it.
-
-Our approach to this challenge: develop reusable parts of our code base in an external repository, and copy the source code back into our monorepo in an automated way.
-The process of copying in external code is commonly called "vendoring".
-
-Vendoring code is a good thing.
-We continue to maintain a single code base which is easy to fork, tag and generally work with, as all the normal Git tooling works.
-By explicitly importing code we also ensure that no unreviewed code sneaks into our code base, and a "always buildable" configuration is maintained.
-
-But what happens if the imported code needs to be modified?
-Ideally, all code changes are submitted upstream, integrated into the upstream code base, and then re-imported into our code base.
-This development methodology is called "upstream first".
-History has shown repeatedly that an upstream first policy can help significantly with the long-term maintenance of code.
-
-However, strictly following an upstream first policy isn't great either.
-Some changes might not be useful for the upstream community, others might be not acceptable upstream or only applied after a long delay.
-In these situations it must be possible to modify the code downstream, i.e. in our repository, as well.
-Our setup includes multiple options to achieve this goal.
-In many cases, applying patches on top of the imported code is the most sustainable option.
-
-To ease the pain points of vendoring code we have developed tooling and continue to do so.
-Please open an issue ticket if you see areas where the tooling could be improved.
-
-## Basic concepts
-
-This section gives a quick overview how we include code from other repositories into our repository.
-
-All imported ("vendored") hardware code is by convention put into the `hw/vendor` directory.
-(We have more conventions for file and directory names which are discussed below when the import of new code is described.)
-To interact with code in this directory a tool called `util/vendor.py` is used.
-A "vendor description file" controls the vendoring process and serves as input to the `util/vendor` tool.
-
-In the simple, yet typical, case, the vendor description file is only a couple of lines of human-readable JSON:
-
-```command
-$ cat hw/vendor/lowrisc_ibex.vendor.hjson
-{
-  name: "lowrisc_ibex",
-  target_dir: "lowrisc_ibex",
-
-  upstream: {
-    url: "https://github.com/lowRISC/ibex.git",
-    rev: "master",
-  },
-}
-```
-
-This description file essentially says:
-We vendor a component called "lowrisc_ibex" and place the code into the "lowrisc_ibex" directory (relative to the description file).
-The code comes from the `master` branch of the Git repository found at https://github.com/lowRISC/ibex.git.
-
-With this description file written, the `util/vendor` tool can do its job.
-
-```command
-$ cd $REPO_TOP
-$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson --verbose --update
-INFO: Cloning upstream repository https://github.com/lowRISC/ibex.git @ master
-INFO: Cloned at revision 7728b7b6f2318fb4078945570a55af31ee77537a
-INFO: Copying upstream sources to /home/philipp/src/opentitan/hw/vendor/lowrisc_ibex
-INFO: Changes since the last import:
-* Typo fix in muldiv: Reminder->Remainder (Stefan Wallentowitz)
-INFO: Wrote lock file /home/philipp/src/opentitan/hw/vendor/lowrisc_ibex.lock.hjson
-INFO: Import finished
-```
-
-Looking at the output, you might wonder: how did the `util/vendor` tool know what changed since the last import?
-It knows because it records the commit hash of the last import in a file called the "lock file".
-This file can be found along the `.vendor.hjson` file, it's named `.lock.hjson`.
-
-In the example above, it looks roughly like this:
-
-```command
-$ cat hw/vendor/lowrisc_ibex.lock.hjson
-{
-  upstream:
-  {
-    url: https://github.com/lowRISC/ibex.git
-    rev: 7728b7b6f2318fb4078945570a55af31ee77537a
-  }
-}
-```
-
-The lock file should be committed together with the code itself to make the import step reproducible at any time.
-This import step can be reproduced by running the `util/vendor` tool without the `--update` flag.
-
-After running `util/vendor`, the code in your local working copy is updated to the latest upstream version.
-Next is testing: run simulations, syntheses, or other tests to ensure that the new code works as expected.
-Once you're confident that the new code is good to be committed, do so using the normal Git commands.
-
-```command
-$ cd $REPO_TOP
-
-$ # Stage all files in the vendored directory
-$ git add -A hw/vendor/lowrisc_ibex
-
-$ # Stage the lock file as well
-$ git add hw/vendor/lowrisc_ibex.lock.hjson
-
-$ # Now commit everything. Don't forget to write a useful commit message!
-$ git commit
-```
-
-Instead of running `util/vendor` first, and then manually creating a Git commit, you can also use the `--commit` flag.
-
-```command
-$ cd $REPO_TOP
-$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson \
-    --verbose --update --commit
-```
-
-This command updates the "lowrisc_ibex" code, and creates a Git commit from it.
-
-Read on for a complete example how to efficiently update a vendored dependency, and how to make changes to such code.
-
-## Update vendored code in our repository
-
-A complete example to update a vendored dependency, commit its changes, and create a pull request from it, is given below.
-
-```command
-$ cd $REPO_TOP
-$ # Ensure a clean working directory
-$ git stash
-$ # Create a new branch for the pull request
-$ git checkout -b update-ibex-code upstream/master
-$ # Update lowrisc_ibex and create a commit
-$ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson \
-    --verbose --update --commit
-$ # Push the new branch to your fork
-$ git push origin update-ibex-code
-$ # Restore changes in working directory (if anything was stashed before)
-$ git stash pop
-```
-
-Now go to the GitHub web interface to open a Pull Request for the `update-ibex-code` branch.
-
-## How to modify vendored code (fix a bug, improve it)
-
-### Step 1: Get the vendored repository
-
-1. Open the vendor description file (`.vendor.hjson`) of the dependency you want to update and take note of the `url` and the `branch` in the `upstream` section.
-
-2. Clone the upstream repository and switch to the used branch:
-
-   ```command
-   $ # Go to your source directory (can be anywhere)
-   $ cd ~/src
-   $ # Clone the repository and switch the branch. Below is an example for ibex.
-   $ git clone https://github.com/lowRISC/ibex.git
-   $ cd ibex
-   $ git checkout master
-   ```
-
-After this step you're ready to make your modifications.
-You can do so *either* directly in the upstream repository, *or* start in the OpenTitan repository.
-
-### Step 2a: Make modifications in the upstream repository
-
-The easiest option is to modify the upstream repository directly as usual.
-
-### Step 2b: Make modifications in the OpenTitan repository
-
-Most changes to external code are motivated by our own needs.
-Modifying the external code directly in the `hw/vendor` directory is therefore a sensible starting point.
-
-1. Make your changes in the OpenTitan repository. Do not commit them.
-
-2. Create a patch with your changes. The example below uses `lowrisc_ibex`.
-
-   ```command
-   $ cd hw/vendor/lowrisc_ibex
-   $ git diff --relative . > changes.patch
-   ```
-
-3. Take note of the revision of the imported repository from the lock file.
-   ```command
-   $ cat hw/vendor/lowrisc_ibex.lock.hjson | grep rev
-    rev: 7728b7b6f2318fb4078945570a55af31ee77537a
-   ```
-
-4. Switch to the checked out upstream repository and bring it into the same state as the imported repository.
-   Again, the example below uses ibex, adjust as needed.
-
-   ```command
-   # Change to the upstream repository
-   $ cd ~/src/ibex
-
-   $ # Create a new branch for your patch
-   $ # Use the revision you determined in the previous step!
-   $ git checkout -b modify-ibex-somehow 7728b7b6f2318fb4078945570a55af31ee77537a
-   $ git apply -p1 < $REPO_BASE/hw/vendor/lowrisc_ibex/changes.patch
-
-   $ # Add and commit your changes as usual
-   $ # You can create multiple commits with git add -p and committing
-   $ # multiple times.
-   $ git add -u
-   $ git commit
-   ```
-
-### Step 3: Get your changes accepted upstream
-
-You have now created a commit in the upstream repository.
-Before submitting your changes upstream, rebase them on top of the upstream development branch, typically `master`, and ensure that all tests pass.
-Now you need to follow the upstream guidelines on how to get the change accepted.
-In many cases their workflow is similar to ours: push your changes to a repository fork on your namespace, create a pull request, work through review comments, and update it until the change is accepted and merged.
-
-### Step 4: Update the vendored copy of the external dependency
-
-After your change is accepted upstream, you can update our copy of the code using the `util/vendor` tool as described before.
-
-## How to vendor new code
-
-Vendoring external code is done by creating a vendor description file, and then running the `util/vendor` tool.
-
-1. Create a vendor description file for the new dependency.
-   1. Make note of the Git repository and the branch you want to vendor in.
-   2. Choose a name for the external dependency.
-      It is recommended to use the format `<vendor>_<name>`.
-      Typically `<vendor>` is the lower-cased user or organization name on GitHub, and `<name>` is the lower-cased project name.
-   3. Choose a target directory.
-      It is recommended use the dependency name as directory name.
-   4. Create the vendor description file in `hw/vendor/<vendor>_<name>.vendor.hjson` with the following contents (adjust as needed):
-
-      ```
-      // Copyright lowRISC contributors.
-      // Licensed under the Apache License, Version 2.0, see LICENSE for details.
-      // SPDX-License-Identifier: Apache-2.0
-      {
-        name: "lowrisc_ibex",
-        target_dir: "lowrisc_ibex",
-
-        upstream: {
-          url: "https://github.com/lowRISC/ibex.git",
-          rev: "master",
-        },
-      }
-      ```
-
-2. Create a new branch for a subsequent pull request
-
-   ```command
-   $ git checkout -b vendor-something upstream/master
-   ```
-
-3. Commit the vendor description file
-
-   ```command
-   $ git add hw/vendor/<vendor>_<name>.vendor.hjson
-   $ git commit
-   ```
-
-4. Run the `util/vendor` tool for the newly vendored code.
-
-   ```command
-   $ cd $REPO_TOP
-   $ ./util/vendor.py hw/vendor/lowrisc_ibex.vendor.hjson --verbose --commit
-   ```
-
-5. Push the branch to your fork for review (assuming `origin` is the remote name of your fork).
-
-   ```command
-   $ git push -u origin vendor-something
-   ```
-
-   Now go the GitHub web interface to create a Pull Request for the newly created branch.
-
-## How to exclude some files from the upstream repository
-
-You can exclude files from the upstream code base by listing them in the vendor description file under `exclude_from_upstream`.
-Glob-style wildcards are supported (`*`, `?`, etc.), as known from shells.
-
-Example:
-
-```
-// section of a .vendor.hjson file
-exclude_from_upstream: [
-  // exclude all *.h files in the src directory
-  "src/*.h",
-  // exclude the src_files.yml file
-  "src_files.yml",
-  // exclude some_directory and all files below it
-  "some_directory",
-]
-```
-
-If you want to add more files to `exclude_from_upstream`, just update this section of the `.vendor.hjson` file and re-run the vendor tool without `--update`.
-The repository will be re-cloned without pulling in upstream updates, and the file exclusions and patches specified in the vendor file will be applied.
-
-## How to add patches on top of the imported code
-
-In some cases the upstream code must be modified before it can be used.
-For this purpose, the `util/vendor` tool can apply patches on top of imported code.
-The patches are kept as separate files in our repository, making it easy to understand the differences to the upstream code, and to switch the upstream code to a newer version.
-
-To apply patches on top of vendored code, do the following:
-
-1. Extend the `.vendor.hjson` file of the dependency and add a `patch_dir` line pointing to a directory of patch files.
-   It is recommended to place patches into the `patches/<vendor>_<name>` directory.
-
-    ```
-    patch_dir: "patches/lowrisc_ibex",
-    ```
-
-2. Place patch files with a `.patch` suffix in the `patch_dir`.
-
-3. When running `util/vendor`, patches are applied on top of the imported code according to the following rules.
-
-   - Patches are applied alphabetical order according to the filename.
-     Name patches like `0001-do-someting.patch` to apply them in a deterministic order.
-   - Patches are applied relative to the base directory of the imported code.
-   - The first directory component of the filename in a patch is stripped, i.e. they are applied with the `-p1` argument of `patch`.
-   - Patches are applied with `git apply`, making all extended features of Git patches available (e.g. renames).
-
-If you want to add more patches and re-apply them without updating the upstream repository, add them to the patches directory and re-run the vendor tool without `--update`.
-
-## How to manage patches in a Git repository
-
-Managing patch series on top of code can be challenging.
-As the underlying code changes, the patches need to be refreshed to continue to apply.
-Adding new patches is a very manual process.
-And so on.
-
-Fortunately, Git can be used to simplify this task.
-The idea:
-
-- Create a forked Git repository of the upstream code
-- Create a new branch in this fork.
-- Commit all your changes on top of the upstream code into this branch.
-- Convert all commits into patch files and store them where the `util/vendor` tool can find and apply them.
-
-The last step is automated by the `util/vendor` tool through its `--refresh-patches` argument.
-
-1. Modify the vendor description file to add a `patch_repo` section.
-   - The `url` parameter specifies the URL to the fork of the upstream repository containing all modifications.
-   - The `rev_base` is the base revision, typically the `master` branch.
-   - The `rev_patched` is the patched revision, typically the name of the branch with your changes.
-
-    ```
-    patch_repo: {
-      url: "https://github.com/lowRISC/riscv-dbg.git",
-      rev_base: "master",
-      rev_patched: "changes",
-    },
-    ```
-
-2. Create commit and push to the forked repository.
-   Make sure to push both branches to the fork: `rev_base` **and** `rev_patched`.
-   In the example above, this would be (with `REMOTE_NAME_FORK` being the remote name of the fork):
-
-   ```command
-   git push REMOTE_NAME_FORK master changes
-   ```
-
-3. Run the `util/vendor` tool with the `--refresh-patches` argument.
-   It will first check out the patch repository and convert all commits which are in the `rev_patched` branch and not in the `rev_base` branch into patch files.
-   These patch files are then stored in the patch directory.
-   After that, the vendoring process continues as usual: changes from the upstream repository are downloaded if `--update` passed, all patches are applied, and if instructed by the `--commit` flag, a commit is created.
-   This commit now also includes the updated patch files.
-
-To update the patches you can use all the usual Git tools in the forked repository.
-
-- Use `git rebase` to refresh them on top of changes in the upstream repository.
-- Add new patches with commits to the `rev_patched` fork.
-- Remove patches or reorder them with Git interactive rebase (`git rebase -i`).
-
-It is important to update and push *both* branches in the forked repository: the `rev_base` branch and the `rev_patched` branch.
-Use `git log rev_base..rev_patched` (replace `rev_base` and `rev_patched` as needed) to show all commits which will be turned into patches.
diff --git a/doc/use_cases/README.md b/doc/use_cases/README.md
new file mode 100644
index 0000000..7995087
--- /dev/null
+++ b/doc/use_cases/README.md
@@ -0,0 +1,17 @@
+# Use Cases
+
+## Overview
+
+OpenTitan's mission is to raise the security bar industry-wide by implementing a
+transparent, logically secure hardware root of trust with wide application.
+
+This document describes some of those use cases for OpenTitan. These range from
+data center integrations, to embedded security applications such as security
+keys and smart cards. References to relevant specifications and certification
+targets are noted where use cases are backed by industry standards.
+
+## Discrete Use Cases
+
+* [Platform Integrity Module](./platform_integrity_module/README.md)
+* [Trusted Platform Module](./tpm/README.md)
+* [Universal 2nd-Factor Security Key](./u2f/README.md)
diff --git a/doc/use_cases/platform_integrity_module/README.md b/doc/use_cases/platform_integrity_module/README.md
new file mode 100644
index 0000000..385076b
--- /dev/null
+++ b/doc/use_cases/platform_integrity_module/README.md
@@ -0,0 +1,133 @@
+# Platform Integrity Module
+
+An OpenTitan IC used as a Platform Integrity Module interposes between a
+platform's boot flash and its main boot devices such as the Baseboard Management
+Controller (BMC), the Platform Controller Hub (PCH) and the CPU.
+
+<img src="use_cases_fig1.svg" alt="Fig1" style="width: 300px;"/>
+
+Figure 1: Platform Integrity Module
+
+The Platform Integrity Module use case implements the following security
+properties:
+
+*   Measure integrity of first boot firmware stages before bringing the boot
+    devices out of reset accessing boot flash via SPI or similar interface.
+*   Monitor resets and heartbeat of downstream boot devices. Monitoring tasks
+    are handled by OpenTitan as Interrupt Service Routines (ISRs), and are not
+    expected to operate under real time constraints.
+*   Enforce runtime boot device access policies, and manage A/B firmware updates
+    for software stored in boot flash. The OpenTitan to boot device interface is
+    implemented on SPI or a similar interface.
+*   Provides root key store and attestation flows as part of the platform
+    integrity secure boot implementation.
+
+### Minimum Crypto Algorithm Requirements
+
+The current target for all crypto is at least 128-bit security strength. This is
+subject to change based on the implementation timeline of any given
+instantiation of OpenTitan. It is expected that a future implementation may be
+required to target a minimum of 192-bit or 256-bit security strength.
+
+*   TRNG: NIST SP 800-90B compliant entropy source.
+*   DRBG: NIST SP 800-90A compliant DRBG.
+*   Hash Algorithms:
+    *   SHA256: An approved hash algorithm with approximately the same security
+        strength as its strongest asymmetric algorithm.
+*   Asymmetric Key Algorithms:
+    *   RSA-3072: Secure boot signature verification.
+    *   ECDSA P-256: Signature and verification for identity and attestation
+        keys.
+*   Symmetric Key Algorithms:
+    *   HMAC-SHA256: NIST FIPS 180-4 compliant. Used in integrity measurements
+        for storage and in transit data as well as secure boot.
+    *   AES: AES-CTR NIST 800-38A. Used to wrap keys and encrypt data stored in
+        internal flash.
+
+### Provisioning Requirements
+
+Provisioning an OpenTitan device is performed in two steps:
+
+*   Device Personalization: The device is initialized with a unique
+    cryptographic identity endorsed by a Transit PKI which is only used to
+    support initial Ownership Transfer.
+*   Ownership Transfer: Ownership is assigned to a user that has the ability to
+    run software on the device. As Silicon Owner, the user can generate a
+    cryptographic identity strongly associated to the hardware and the software
+    version running on the device.
+
+OpenTitan used as a Platform Integrity Module has the following provisioning
+requirements:
+
+*   Unique Global Identifier: Non-Cryptographic big integer value (up to 256b)
+    used to facilitate tracking of the devices throughout their life cycle. The
+    identifier is stored in One Time Programmable (OTP) storage during
+    manufacturing.
+*   Hardware Cryptographic Identity: Symmetric and asymmetric keys associated
+    with the hardware, used to attest the authenticity of the chip and also as a
+    component of the Owner Cryptographic Identity. These keys are generated
+    inside the device by the secure manufacturing process.
+*   Hardware Transport Certificate: Used to endorse the asymmetric hardware
+    identity with a transit PKI trusted by the Silicon Owner at Ownership
+    Transfer time.
+*   Factory Firmware: Baseline image with support for firmware update and
+    Ownership Transfer. Firmware update may be actuated by writing an OpenTitan
+    update payload to boot flash. Upon reset, OpenTitan scans the boot flash
+    device for valid updates. The factory image may not be owned by the Silicon
+    Owner and its main purpose is to assist Ownership Transfer.
+*   Owner Cryptographic Identity: The Silicon Owner is required to generate an
+    identity as part of the Ownership transfer flow. Owner identities are bound
+    to the Hardware Identity and the software version running on the device.
+    Owner identities are used in Silicon Ownership attestation flows and as a
+    root component of Application keys.
+*   Application Keys: Keys bound to the owner identity and the application
+    version running on the device. Application keys are provisioned in most
+    cases when the application runs for the first time. The purpose of each key
+    is configured at the application layer and enforced by the kernel.
+
+### Performance Requirements
+
+Performance requirements are derived from integration-specific requirements. The
+following performance requirements are presented for illustration purposes:
+
+*   Boot Time: Maximum time allowed to get to device kernel serving state from
+    cold reset.
+*   Resume Time: Maximum time allowed to get to device kernel serving state from
+    sleep.
+*   External Flash Verification Time: Maximum time allowed for verification of
+    external flash as part of platform boot verified boot implementation.
+    Defined in milliseconds for a given flash partition size.
+
+### Packaging Constraints
+
+*   Non-HDI packaging is required.
+*   (Proposed) Device packaging QR code with device ID linkable to manufacturing
+    data.
+
+### Additional Requirements
+
+#### Memory Requirements
+
+*   At least 512KB of flash storage with 2 partitions, 4KB page size, 100K
+    endurance cycles. 1MB flash would be ideal to allow for future code size
+    growth.
+*   At least 16KB of isolated flash storage for manufacturing and device life
+    cycle operations.
+*   At least 8KB of OTP for manufacturing and device life cycle operations.
+*   At least 64KB of SRAM. 128KB would be ideal for future growth.
+
+#### External Peripherals
+
+The following list of peripheral requirements is speculative at the moment and
+subject to change based on platform integration requirements:
+
+*   SPI Host/Device:
+    *   Dual support. Quad support needs to be evaluated.
+    *   Required features for EEPROM mode:
+        -   Passthrough boot flash interface with support for EEPROM command
+            handling/filtering.
+        -   Access to on-die ram and flash memory regions.
+        -   Mailbox interface with support for custom opcode commands.
+*   UART: Debug console interface. May be disabled by production firmware.
+*   GPIO: Reset control and monitoring. Status signals.
+*   I2C interface compatible with SMBus interfaces.
diff --git a/doc/security/use_cases/use_cases_fig1.svg b/doc/use_cases/platform_integrity_module/use_cases_fig1.svg
similarity index 100%
rename from doc/security/use_cases/use_cases_fig1.svg
rename to doc/use_cases/platform_integrity_module/use_cases_fig1.svg
diff --git a/doc/use_cases/tpm/README.md b/doc/use_cases/tpm/README.md
new file mode 100644
index 0000000..ada6a78
--- /dev/null
+++ b/doc/use_cases/tpm/README.md
@@ -0,0 +1,118 @@
+# Trusted Platform Module - TPM
+
+## Overview
+
+OpenTitan can be used to implement the full Trusted Platform Module (TPM) 2.0
+specification to meet client and server platform use cases. When used as a TPM,
+OpenTitan is provisioned with an endorsement seed and RSA and ECDSA endorsement
+certificates (EK). TPM commands are served over either SPI or I2C device
+peripherals.
+
+## Certification Requirements
+
+*   [ANSSI-CC-PP-2018/03](https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PP_1p3_for_Library_1p59_pub_29sept2021.pdf)
+    Protection Profile Client Specific TPM[^1]. The minimum assurance level for
+    this Protection Profile (PP) is EAL 4 augmented with ALC\_FLR.1 and
+    AVA\_VAN.4.
+
+    *   ALC\_FLR.1: Basic flaw remediation. The developer provides flaw
+        remediation procedures to the Target of Evaluation (TOE) developers.
+    *   AVA\_VAN.4: Methodical vulnerability analysis. Methodical vulnerability
+        analysis is performed by the evaluator to identify the presence of
+        potential vulnerabilities. Penetration testing is performed by the
+        evaluator with a _moderate _attack potential.
+
+## Minimum Crypto Algorithm Requirements
+
+*   TRNG: At least one internal entropy source is required. The entropy source
+    and collector should provide entropy to the state register in a manner that
+    is not visible to an outside process. The entropy collector should regularly
+    update the state register with additional, unbiased entropy.
+*   Hash Algorithms:
+    *   An approved hash algorithm with approximately the same security strength
+        as its strongest asymmetric algorithm. For OpenTitan the target is
+        SHA2-256, SHA2-384.
+    *   A TPM should support the extend function to make incremental updates to
+        a digest value.
+*   Symmetric Key Algorithms:
+    *   HMAC as described in ISO/IEC 9797-2. XOR obfuscation for use in a hash
+        based stream cipher.
+    *   A symmetric block cipher in CFB mode. For OpenTitan the target is
+        AES-CFB 128/192/256-bit.
+*   Asymmetric key algorithm:
+    *   At least one of:
+        *   RSA:
+            *   Sign and verify support for 3072-bit or larger key sizes.
+            *   Verify support for 3072-bit key size as part of secure boot
+                implementation.
+        *   ECDSA
+            *   For OpenTitan, the minimum requirement is to support signature
+                and verification on NIST P-256 and P-384 curves.
+*   Key derivation function:
+    *   Counter mode use of SP800-108, with HMAC as the PRF.
+
+## Provisioning Requirements
+
+OpenTitan used as a TPM has the following provisioning requirements:
+
+*   **Unique Global Identifier**: Big integer value (up to 256b) used to
+    facilitate tracking of the devices throughout their life cycle. The
+    identifier is stored in One Time Programmable (OTP) storage during
+    manufacturing.
+*   **Endorsement Seed**: Generation of endorsement seed for RSA and ECC
+    asymmetric operations. The seed is stored in encrypted or masked form with a
+    key bound to the device's key manager.
+*   **EK Certificate**: One EK Certificate for each asymmetric key type. Stored
+    in the device. Additional requirements which may be fulfilled by an
+    implementation relying on Ownership Transfer:
+    *   The intermediate root certificate may be cross-signed by the Silicon
+        Owner.
+    *   The intermediate root certificate may only be used for a class of
+        devices managed by the Silicon Owner.
+    *   The intermediate root certificate must be chained to a well known root
+        CA.
+*   **Factory Firmware**: Baseline image with support for firmware update via
+    SPI or I2C, and TPM 2.0 full or subset of commands required by the target
+    platform.
+
+## Packaging Constraints
+
+*   Non-HDI packaging is required.
+*   (Optional) TPM-spec compatible packaging.
+
+## Additional Requirements
+
+The requirements listed below are extracted from the
+[TPM Profile (PTP) Specification version 1.03 revision 22](https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/),
+referred to as the PTP spec in the following sections.
+
+### Storage Requirements
+
+*   Size requirements as specified in section 3.6.1 of the PTP spec:
+    *   Minimum of 8KB bytes of NV storage.
+    *   Follow the storage guidance for pre-provisioned EK Certificates if these
+        are available.
+
+### External Peripherals
+
+*   SPI device with support for TPM flow control protocol as specified in
+    section 6.4.5 of the PTP spec. It is preferred to implement flow control in
+    hardware.
+*   I2C interface as specified in section 7.1 of the PTP doc.
+*   GPIO: Additional pins used to implement platform security flows for a set of
+    integration use cases.
+
+## Relevant specs
+
+*   https://trustedcomputinggroup.org/resource/tpm-library-specification/
+
+*   https://trustedcomputinggroup.org/work-groups/trusted-platform-module/
+
+<!-- Footnotes themselves at the bottom. -->
+
+## Notes
+
+[^1]: TCG requires membership in order to obtain TPM certification. There are
+    additional compliance testing requirements. See TCG's certification portal
+    for more details:
+    https://trustedcomputinggroup.org/membership/certification/.
diff --git a/doc/use_cases/u2f/README.md b/doc/use_cases/u2f/README.md
new file mode 100644
index 0000000..ad471ed
--- /dev/null
+++ b/doc/use_cases/u2f/README.md
@@ -0,0 +1,91 @@
+# Universal 2nd-Factor Security Key
+
+When used as a security key, OpenTitan implements the Universal 2nd Factor (U2F)
+authentication standard, using a Universal Serial Bus (USB) 1.1 interface to
+communicate with host devices. U2F requires the implementation of a
+challenge-response authentication protocol based on public key cryptography. The
+security key is provisioned with a unique identity in the form of an asymmetric
+key, which may be self-endorsed by a certificate issued at manufacturing time.
+
+When used as a security key, OpenTitan shall meet the FIDO Authenticator
+security goals and measures described in the [FIDO Security Reference v1.2][1]
+specification. See [Universal 2nd Factor (U2F) Overview v1.2][2] for more
+details on the functional requirements of this use case.
+
+### Certification Requirements
+
+*   [BSI-PP-CC-0096-V3-2018][3] FIDO Universal Second Factor (U2F)
+    Authenticator. The minimum assurance level for this Protection Profile (PP)
+    is EAL4 augmented. This PP supports composite certification on top of the
+    Security IC Platform Protection Profile with Augmentation Packages,
+    BSI-CC-PP-0084-2014 (referred to as PP84).
+*   [FIPS 140-2 L1 + L3 physical][4] certification is required for some use
+    cases.
+
+### Minimum Crypto Algorithm Requirements
+
+The current target for all crypto is at least 128-bit security strength. This is
+subject to change based on the implementation timeline of any given
+instantiation of OpenTitan. It is expected that a future implementation may be
+required to target a minimum of 192-bit or 256-bit security strength.
+
+*   TRNG:
+    *   Entropy source for ECDSA keypair generation (seed and nonce).
+    *   (optional) Symmetric MAC key generation.
+*   Asymmetric Key Algorithms:
+    *   ECDSA: Signature and verification on NIST P-256 curve for identity and
+        attestation keys.
+    *   RSA-3072: Secure boot signature verification. Used to verify the
+        signature of the device's firmware.
+*   Symmetric Key Algorithms:
+    *   AES-CTR:
+        -   (optional) Used to wrap a user private key in a key handle.
+            Implementation dependent.
+    *   HMAC-SHA256:
+        -   For application key handle generation.
+*   Hash Algorithms:
+    *   SHA-256:
+        -   Code and hardware measurements used in internal secure boot
+            implementation.
+        -   (optional) For key handle generation. Implementation dependent.
+        -   (optional) Attestation cert generation, if generated on the fly.
+
+### Provisioning Requirements
+
+OpenTitan used as a security key has the following provisioning requirements:
+
+*   Unique Global Identifier: Non-Cryptographic big integer value (up to 256b)
+    used to facilitate tracking of the devices throughout their life cycle. The
+    identifier is stored in One Time Programmable (OTP) storage during
+    manufacturing.
+*   Attestation Key: Unique cryptographic identity used for attestation
+    purposes.
+*   Self-Signed Attestation Certificate: Self signed certificate and extracted
+    at manufacturing time for registration purposes. U2F backend servers can
+    create an allow-list of certificates reported by the secure key
+    manufacturer, and use them to perform authenticity checks as part of the
+    registration flow.
+*   Factory Firmware: Baseline image with support for firmware update via USB,
+    and the USB HID U2F command spec.
+
+### Additional Requirements
+
+*   Physical Presence GPIO: U2F requires physical user presence checks for
+    registration and authentication flows. This is implemented either via a push
+    button or capacitive touch sensor connected to an input GPIO pin.
+    *   At least 2 PWM peripherals can facilitate implementation of capacitive
+        touch sensor IO operations.
+*   Status LEDs GPIO: The security key may use LEDs to provide feedback to the
+    user. This requires up to 4 additional output GPIO pins.
+*   USB HID U2F Stack: The security key communicates with host devices via a USB
+    HID protocol. OpenTitan shall meet the USB 1.1 connectivity and protocol
+    requirements to interface with the host.
+
+### Relevant specs
+
+https://fidoalliance.org/specifications/download/
+
+[1]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-security-ref-v1.2-ps-20170411.html
+[2]: https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html
+[3]: https://www.commoncriteriaportal.org/files/ppfiles/pp0096V3b_pdf.pdf
+[4]: https://en.wikipedia.org/wiki/FIPS_140-2#Security_levels
diff --git a/hw/BUILD b/hw/BUILD
index 46af4e2..5e7b9a1 100644
--- a/hw/BUILD
+++ b/hw/BUILD
@@ -2,13 +2,13 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
+load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
 load("//rules:fusesoc.bzl", "fusesoc_build")
 
 # This configuration exposes fusesoc's "verilator_options" option to the
 # command line. This is intended to allow CI to specifically build a single
-# -threaded Verilator model since the build environment there is more resource
-# -constrained.
-# By default and in all other cases, the Verilator model should be built to
+# -threaded Verilated model to suit it's resource constraints.
+# By default, the Verilated model should be built to
 # run with 4 threads.
 load("@bazel_skylib//rules:common_settings.bzl", "string_list_flag")
 
@@ -20,6 +20,18 @@
     ],
 )
 
+# This configuration exposes fusesoc's "make_options" to enable parallel
+# compilation of the verilated model. Compilation takes about 30m of cpu time
+# and 5m of time that isn't parallelized by this option, so this should reduce
+# the total runtime to ~12m.
+string_list_flag(
+    name = "make_options",
+    build_setting_default = [
+        "-j",
+        "4",
+    ],
+)
+
 fusesoc_build(
     name = "verilator_real",
     srcs = [
@@ -29,19 +41,29 @@
         "//:cores",
     ],
     data = ["//hw/ip/otbn:all_files"],
-    flags = ["--verilator_options=--threads 1"],
+    make_options = ":make_options",
+    output_groups = {
+        "binary": ["sim-verilator/Vchip_sim_tb"],
+    },
     systems = ["lowrisc:dv:chip_verilator_sim"],
-    tags = ["verilator"],
+    tags = [
+        "manual",
+        "verilator",
+    ],
     target = "sim",
     verilator_options = ":verilator_options",
 )
 
+filegroup(
+    name = "verilator_bin",
+    srcs = [":verilator_real"],
+    output_group = "binary",
+)
+
 # This is used in CI steps that do not want to run Verilator tests, and thus
-# do not want to accidentally build the Verilator model. This causes the
-# //hw:verilator target to not emit any files, which will break any tests that
-# actually rely on this; builds will succeed, tests will fail.
-#
-# FIXME(lowRISC/opentitan#12259): This is a temporary workaround.
+# do not want to build the Verilated model. This causes the //hw:verilator
+# target to not emit any files, which will break any tests that rely on this;
+# builds will succeed, tests will fail.
 config_setting(
     name = "disable_verilator_build",
     values = {"define": "DISABLE_VERILATOR_BUILD=true"},
@@ -49,23 +71,22 @@
 
 genrule(
     name = "verilator_stub",
-    outs = ["verilator-stub"],
+    outs = ["Vfake_sim_tb"],
     cmd = """
-        mkdir -p $@/sim-verilator
-        script=$@/sim-verilator/Vchip_sim_tb
+        script=$@
         echo '#!/bin/bash' > $$script
         echo 'echo "ERROR: sim_verilator tests cannot be run when --define DISABLE_VERILATOR_BUILD=true is set!"' >> $$script
         echo 'echo "This indicates an error in your Bazel invokation"' >> $$script
         echo 'exit 1' >> $$script
-        chmod +x $@/sim-verilator/Vchip_sim_tb
+        chmod +x $@
     """,
 )
 
 alias(
     name = "verilator",
     actual = select({
-        ":disable_verilator_build": ":verilator-stub",
-        "//conditions:default": ":verilator_real",
+        ":disable_verilator_build": ":verilator_stub",
+        "//conditions:default": ":verilator_bin",
     }),
     tags = ["verilator"],
     visibility = ["//visibility:public"],
@@ -85,9 +106,20 @@
 # relationships between verilog components.
 filegroup(
     name = "all_files",
-    srcs = glob(["**"]) + [
+    srcs = glob(
+        ["**"],
+        # TODO(lowRISC/opentitan#15882): make Verilator work with foundry repo present.
+        exclude = ["foundry/**"],
+    ) + [
         "//hw/ip:all_files",
         "//hw/top_earlgrey:all_files",
     ],
     visibility = ["//visibility:public"],
 )
+
+pkg_files(
+    name = "package",
+    srcs = ["verilator_bin"],
+    prefix = "earlgrey/verilator",
+    visibility = ["//visibility:public"],
+)
diff --git a/hw/README.md b/hw/README.md
new file mode 100644
index 0000000..58a0851
--- /dev/null
+++ b/hw/README.md
@@ -0,0 +1,57 @@
+# Hardware
+
+This page serves as the landing spot for all hardware development within the OpenTitan project.
+
+We start off by providing links to the [results of various tool-flows](#results-of-toolflows) run on all of our [Comportable](../doc/contributing/hw/comportability/README.md) IPs.
+This includes DV simulations, FPV and lint, all of which are run with the `dvsim` tool which serves as the common frontend.
+
+The [Comportable IPs](#comportable-ips) following it provides links to their design specifications and DV documents, and tracks their current stage of development.
+See the [Hardware Development Stages](../doc/project_governance/development_stages.md) for description of the hardware stages and how they are determined.
+
+Next, we focus on all available [processor cores](#processor-cores) and provide links to their design specifications, DV documents and the DV simulation results.
+
+Finally, we provide the same set of information for all available [top level designs](#top-level-designs).
+
+## Results of tool-flows
+
+* [DV simulation summary results, with coverage (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/summary/latest/report.html)
+* [FPV sec_cm results (weekly)](https://reports.opentitan.org/hw/top_earlgrey/formal/sec_cm/summary/latest/report.html)
+* [FPV ip results (weekly)](https://reports.opentitan.org/hw/top_earlgrey/formal/ip/summary/latest/report.html)
+* [FPV prim results (weekly)](https://reports.opentitan.org/hw/top_earlgrey/formal/prim/summary/latest/report.html)
+* [AscentLint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/ascentlint/summary/latest/report.html)
+* [Verilator lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/verilator/summary/latest/report.html)
+* [Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/veriblelint/summary/latest/report.html)
+* [DV Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/lint/veriblelint/summary/latest/report.html)
+* [FPV Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/fpv/lint/veriblelint/summary/latest/report.html)
+
+## Comportable IPs
+
+{{#dashboard comportable }}
+
+## Processor cores
+
+* `core_ibex`
+  * [User manual](https://ibex-core.readthedocs.io/en/latest)
+  * [DV document](https://ibex-core.readthedocs.io/en/latest/03_reference/verification.html)
+  * DV simulation results, with coverage (nightly) (TBD)
+
+## Earl Grey chip-level results
+
+* [Datasheet](./top_earlgrey/doc/specification.md)
+* [Specification](./top_earlgrey/doc/design/README.md)
+* [DV Document](./top_earlgrey/dv/README.md)
+* [DV simulation results, with coverage (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/latest/report.html)
+* [Connectivity results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/conn/jaspergold/latest/report.html)
+* [AscentLint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/ascentlint/latest/report.html)
+* [Verilator lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/verilator/latest/report.html)
+* [Style lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/veriblelint/latest/report.html)
+* [DV Style lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/lint/veriblelint/latest/report.html)
+* [CDC results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/cdc/latest/report.html)
+
+### Earl Grey-specific comportable IPs
+
+{{#dashboard top_earlgrey }}
+
+## Hardware documentation overview
+
+{{% sectionContent %}}
diff --git a/hw/_index.md b/hw/_index.md
deleted file mode 100644
index 728b5d1..0000000
--- a/hw/_index.md
+++ /dev/null
@@ -1,59 +0,0 @@
----
-title: "Hardware Dashboard"
-aliases: [/doc/project/hw_dashboard/]
----
-
-This page serves as the landing spot for all hardware development within the OpenTitan project.
-
-We start off by providing links to the [results of various tool-flows](#results-of-toolflows) run on all of our [Comportable]({{< relref "doc/rm/comportability_specification" >}}) IPs.
-This includes DV simulations, FPV and lint, all of which are run with the `dvsim` tool which serves as the common frontend.
-
-The [Comportable IPs](#comportable-ips) following it provides links to their design specifications and DV documents, and tracks their current stage of development.
-See the [Hardware Development Stages]({{< relref "/doc/project/development_stages.md" >}}) for description of the hardware stages and how they are determined.
-
-Next, we focus on all available [processor cores](#processor-cores) and provide links to their design specifications, DV documents and the DV simulation results.
-
-Finally, we provide the same set of information for all available [top level designs](#top-level-designs), including an additional dashboard with preliminary synthesis results for some of these designs.
-
-
-## Results of tool-flows
-
-* [DV simulation summary results, with coverage (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/summary.html)
-* [FPV summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/formal/summary.html)
-* [AscentLint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/ascentlint/summary.html)
-* [Verilator lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/verilator/summary.html)
-* [Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/veriblelint/summary.html)
-* [DV Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/lint/veriblelint/summary.html)
-* [FPV Style lint summary results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/fpv/lint/veriblelint/summary.html)
-
-## Comportable IPs
-
-{{< dashboard "comportable" >}}
-
-## Processor cores
-
-* `core_ibex`
-  * [User manual](https://ibex-core.readthedocs.io/en/latest)
-  * [DV document](https://ibex-core.readthedocs.io/en/latest/03_reference/verification.html)
-  * DV simulation results, with coverage (nightly) (TBD)
-
-## Earl Grey toplevel design
-
-* [Design specification]({{< relref "hw/top_earlgrey/doc" >}})
-* [DV document]({{< relref "hw/top_earlgrey/doc/dv" >}})
-* [DV simulation results, with coverage (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/latest/results.html)
-* [Connectivity results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/conn/jaspergold/latest/results.html)
-* [AscentLint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/ascentlint/latest/results.html)
-* [Verilator lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/verilator/latest/results.html)
-* [Style lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/lint/veriblelint/latest/results.html)
-* [DV Style lint results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/dv/lint/veriblelint/latest/results.html)
-* [Synthesis results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/syn/latest/results.html)
-* [CDC results (nightly)](https://reports.opentitan.org/hw/top_earlgrey/cdc/latest/results.html)
-
-### Earl Grey-specific comportable IPs
-
-{{< dashboard "top_earlgrey" >}}
-
-## Hardware documentation overview
-
-{{% sectionContent %}}
diff --git a/hw/bitstream/BUILD b/hw/bitstream/BUILD
index 16d9ee9..ce5d1da 100644
--- a/hw/bitstream/BUILD
+++ b/hw/bitstream/BUILD
@@ -2,27 +2,36 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
+load("//rules:splice.bzl", "bitstream_splice")
+load("//rules:otp.bzl", "get_otp_images")
+
 package(default_visibility = ["//visibility:public"])
 
-# Use a bitstream from the GCP bucket (this is also the default condition).
-# You can control the GCP bitstream selection via the BITSTREAM environment
-# variable.  See //rules/bitstreams.bzl for more information.
+# By default, targets in this file will use cached artifacts from the GCP bucket
+# instead of building them from scratch.
 #
-# Example:
-#   bazel test //sw/device/silicon_creator/lib/drivers:hmac_functest_fpga_cw310 --define bitstream=gcp
-config_setting(
-    name = "bitstream_gcp",
-    define_values = {
-        "bitstream": "gcp",
-    },
-)
+# You can control GCP bitstream selection with the BITSTREAM environment
+# variable. See //rules:bitstreams.bzl for more information.
+#
+# Alternatively, you can disable or alter this caching behavior with the
+# "bitstream" config setting.
+#
+# * `--define bitstream=skip` skips loading a bitstream into the FPGA. This is
+#   useful if you already have a bitstream loaded into the FPGA and you don't
+#   want the GCP cache manager to do anything unexpected.
+#
+# * `--define bitstream=vivado` causes these targets to build from scratch with
+#   Vivado. You'll need to have Xilinx Vivado installed and have properly
+#   configured access to a license or license server.
+#
+# * `--define bitstream=gcp_splice` causes these targets to use a cached
+#   bitstream, but splice in a locally-built ROM image or OTP.
+#
+# Downstream targets will see the effects of this caching logic. For example,
+# specifying `--define bitstream=vivado` when testing
+# //sw/device/silicon_creator/lib/drivers:hmac_functest_fpga_cw310 will turn
+# `:test_rom` into a full Vivado bitstream build.
 
-# Skip loading a bitstream in to the FPGA.  This is useful if you already
-# have a bitstream loaded into the FPGA and you don't want the GCP cache
-# manager to do anything unexpected.
-#
-# Example:
-#   bazel test //sw/device/silicon_creator/lib/drivers:hmac_functest_fpga_cw310 --define bitstream=skip
 config_setting(
     name = "bitstream_skip",
     define_values = {
@@ -30,12 +39,6 @@
     },
 )
 
-# Use a bitstream built by Vivado.  You'll need to have Xilinx Vivado
-# installed and have properly configured access to a license or license
-# server.
-#
-# Example:
-#   bazel test //sw/device/silicon_creator/lib/drivers:hmac_functest_fpga_cw310 --define bitstream=vivado
 config_setting(
     name = "bitstream_vivado",
     define_values = {
@@ -43,31 +46,109 @@
     },
 )
 
+config_setting(
+    name = "bitstream_gcp_splice",
+    define_values = {
+        "bitstream": "gcp_splice",
+    },
+)
+
 filegroup(
     name = "test_rom",
+    testonly = True,
     srcs = select({
         "bitstream_skip": ["skip.bit"],
         "bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_test_rom"],
-        "bitstream_gcp": ["@bitstreams//:bitstream_test_rom"],
-        "//conditions:default": ["@bitstreams//:bitstream_test_rom"],
+        "bitstream_gcp_splice": [":gcp_spliced_test_rom"],
+        "//conditions:default": [":gcp_spliced_test_rom"],
     }),
+    tags = ["manual"],
 )
 
 filegroup(
-    name = "mask_rom",
+    name = "rom",
+    testonly = True,
     srcs = select({
         "bitstream_skip": ["skip.bit"],
-        "bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_mask_rom"],
-        "bitstream_gcp": ["@bitstreams//:bitstream_mask_rom"],
-        "//conditions:default": ["@bitstreams//:bitstream_mask_rom"],
+        "bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_rom"],
+        "bitstream_gcp_splice": [":gcp_spliced_rom"],
+        "//conditions:default": [":gcp_spliced_rom"],
     }),
+    tags = ["manual"],
 )
 
-# TODO(lowRISC/opentitan#13603): Use `select` once we're uploading these
-# artifacts to GCP.
 filegroup(
-    name = "mask_rom_otp_dev",
-    srcs = [
-        "//hw/bitstream/vivado:fpga_cw310_mask_rom_otp_dev",
-    ],
+    name = "rom_mmi",
+    testonly = True,
+    srcs = select({
+        "bitstream_skip": ["skip.bit"],
+        "bitstream_vivado": ["//hw/bitstream/vivado:rom_mmi"],
+        "//conditions:default": ["@bitstreams//:chip_earlgrey_cw310_rom_mmi"],
+    }),
+    tags = ["manual"],
 )
+
+filegroup(
+    name = "otp_mmi",
+    testonly = True,
+    srcs = select({
+        "bitstream_skip": ["skip.bit"],
+        "bitstream_vivado": ["//hw/bitstream/vivado:otp_mmi"],
+        "//conditions:default": ["@bitstreams//:chip_earlgrey_cw310_otp_mmi"],
+    }),
+    tags = ["manual"],
+)
+
+[
+    filegroup(
+        name = "rom_otp_" + otp_name,
+        testonly = True,
+        srcs = select({
+            "bitstream_skip": ["skip.bit"],
+            "bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_rom_otp_" + otp_name],
+            "bitstream_gcp_splice": [":gcp_spliced_rom_otp_" + otp_name],
+            "//conditions:default": [":gcp_spliced_rom_otp_" + otp_name],
+        }),
+        tags = ["manual"],
+    )
+    for (otp_name, _) in get_otp_images()
+]
+
+# Build the Test ROM and splice it into a cached bitstream.
+bitstream_splice(
+    name = "gcp_spliced_test_rom",
+    testonly = True,
+    src = "@bitstreams//:chip_earlgrey_cw310_bitstream",
+    data = "//sw/device/lib/testing/test_rom:test_rom_fpga_cw310_scr_vmem",
+    meminfo = ":rom_mmi",
+    tags = ["manual"],
+    update_usr_access = True,
+    visibility = ["//visibility:private"],
+)
+
+# Build the ROM and splice it into a cached bitstream.
+bitstream_splice(
+    name = "gcp_spliced_rom",
+    testonly = True,
+    src = "@bitstreams//:chip_earlgrey_cw310_bitstream",
+    data = "//sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310_scr_vmem",
+    meminfo = ":rom_mmi",
+    tags = ["manual"],
+    update_usr_access = True,
+    visibility = ["//visibility:private"],
+)
+
+# Splice OTP images into the locally-spliced ROM bitstream.
+[
+    bitstream_splice(
+        name = "gcp_spliced_rom_otp_" + otp_name,
+        testonly = True,
+        src = ":gcp_spliced_rom",
+        data = img_target,
+        meminfo = ":otp_mmi",
+        tags = ["manual"],
+        update_usr_access = True,
+        visibility = ["//visibility:private"],
+    )
+    for (otp_name, img_target) in get_otp_images()
+]
diff --git a/hw/bitstream/hyperdebug/BUILD b/hw/bitstream/hyperdebug/BUILD
new file mode 100644
index 0000000..8d76dbc
--- /dev/null
+++ b/hw/bitstream/hyperdebug/BUILD
@@ -0,0 +1,108 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+load("//rules:splice.bzl", "bitstream_splice")
+load("//rules:otp.bzl", "get_otp_images")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "test_rom",
+    testonly = True,
+    srcs = select({
+        "//hw/bitstream:bitstream_skip": ["//hw/bitstream:skip.bit"],
+        "//hw/bitstream:bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_test_rom"],
+        "//hw/bitstream:bitstream_gcp_splice": [":gcp_spliced_test_rom"],
+        "//conditions:default": [":gcp_spliced_test_rom"],
+    }),
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "rom",
+    testonly = True,
+    srcs = select({
+        "//hw/bitstream:bitstream_skip": ["skip.bit"],
+        "//hw/bitstream:bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_rom"],
+        "//hw/bitstream:bitstream_gcp_splice": [":gcp_spliced_rom"],
+        "//conditions:default": [":gcp_spliced_rom"],
+    }),
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "rom_mmi",
+    testonly = True,
+    srcs = select({
+        "//hw/bitstream:bitstream_skip": ["skip.bit"],
+        "//hw/bitstream:bitstream_vivado": ["//hw/bitstream/vivado:rom_mmi"],
+        "//conditions:default": ["@bitstreams//:chip_earlgrey_cw310_hyperdebug_rom_mmi"],
+    }),
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "otp_mmi",
+    testonly = True,
+    srcs = select({
+        "//hw/bitstream:bitstream_skip": ["skip.bit"],
+        "//hw/bitstream:bitstream_vivado": ["//hw/bitstream/vivado:otp_mmi"],
+        "//conditions:default": ["@bitstreams//:chip_earlgrey_cw310_hyperdebug_otp_mmi"],
+    }),
+    tags = ["manual"],
+)
+
+[
+    filegroup(
+        name = "rom_otp_" + otp_name,
+        testonly = True,
+        srcs = select({
+            "//hw/bitstream:bitstream_skip": ["skip.bit"],
+            "//hw/bitstream:bitstream_vivado": ["//hw/bitstream/vivado:fpga_cw310_rom_otp_" + otp_name],
+            "//hw/bitstream:bitstream_gcp_splice": [":gcp_spliced_rom_otp_" + otp_name],
+            "//conditions:default": [":gcp_spliced_rom_otp_" + otp_name],
+        }),
+        tags = ["manual"],
+    )
+    for (otp_name, _) in get_otp_images()
+]
+
+# Build the Test ROM and splice it into a cached bitstream.
+bitstream_splice(
+    name = "gcp_spliced_test_rom",
+    testonly = True,
+    src = "@bitstreams//:chip_earlgrey_cw310_hyperdebug_bitstream",
+    data = "//sw/device/lib/testing/test_rom:test_rom_fpga_cw310_scr_vmem",
+    meminfo = ":rom_mmi",
+    tags = ["manual"],
+    update_usr_access = True,
+    visibility = ["//visibility:private"],
+)
+
+# Build the ROM and splice it into a cached bitstream.
+bitstream_splice(
+    name = "gcp_spliced_rom",
+    testonly = True,
+    src = "@bitstreams//:chip_earlgrey_cw310_hyperdebug_bitstream",
+    data = "//sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310_scr_vmem",
+    meminfo = ":rom_mmi",
+    tags = ["manual"],
+    update_usr_access = True,
+    visibility = ["//visibility:private"],
+)
+
+# Splice OTP images into the locally-spliced ROM bitstream.
+[
+    bitstream_splice(
+        name = "gcp_spliced_rom_otp_" + otp_name,
+        testonly = True,
+        src = ":gcp_spliced_rom",
+        data = img_target,
+        meminfo = ":otp_mmi",
+        tags = ["manual"],
+        update_usr_access = True,
+        visibility = ["//visibility:private"],
+    )
+    for (otp_name, img_target) in get_otp_images()
+]
diff --git a/hw/bitstream/vivado/BUILD b/hw/bitstream/vivado/BUILD
index a1a7b23..4a6f52c 100644
--- a/hw/bitstream/vivado/BUILD
+++ b/hw/bitstream/vivado/BUILD
@@ -2,7 +2,9 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
+load("@rules_pkg//pkg:mappings.bzl", "pkg_filegroup", "pkg_files")
 load("//rules:fusesoc.bzl", "fusesoc_build")
+load("//rules:otp.bzl", "get_otp_images")
 load("//rules:splice.bzl", "bitstream_splice")
 
 package(default_visibility = ["//visibility:public"])
@@ -29,6 +31,7 @@
 # a functest.
 fusesoc_build(
     name = "fpga_cw310",
+    testonly = True,
     srcs = [
         "//hw:all_files",
         _FPGA_CW310_TESTROM,
@@ -52,6 +55,7 @@
 
 filegroup(
     name = "fpga_cw310_test_rom",
+    testonly = True,
     srcs = [":fpga_cw310"],
     output_group = "bitstream",
     tags = ["manual"],
@@ -59,6 +63,7 @@
 
 filegroup(
     name = "rom_mmi",
+    testonly = True,
     srcs = [":fpga_cw310"],
     output_group = "rom_mmi",
     tags = ["manual"],
@@ -66,24 +71,118 @@
 
 filegroup(
     name = "otp_mmi",
+    testonly = True,
     srcs = [":fpga_cw310"],
     output_group = "otp_mmi",
     tags = ["manual"],
 )
 
 bitstream_splice(
-    name = "fpga_cw310_mask_rom",
+    name = "fpga_cw310_rom",
+    testonly = True,
     src = ":fpga_cw310_test_rom",
-    data = "//sw/device/silicon_creator/mask_rom:mask_rom_fpga_cw310_scr_vmem",
+    data = "//sw/device/silicon_creator/rom:rom_with_fake_keys_fpga_cw310_scr_vmem",
     meminfo = ":rom_mmi",
     tags = ["manual"],
 )
 
-# Splice in the dev OTP image, replacing the RMA image.
-bitstream_splice(
-    name = "fpga_cw310_mask_rom_otp_dev",
-    src = ":fpga_cw310_mask_rom",
-    data = "//hw/ip/otp_ctrl/data:img_dev",
-    meminfo = ":otp_mmi",
+# Splice OTP images into the ROM bitstream.
+[
+    bitstream_splice(
+        name = "fpga_cw310_rom_otp_" + otp_name,
+        testonly = True,
+        src = ":fpga_cw310_rom",
+        data = img_target,
+        meminfo = ":otp_mmi",
+        tags = ["manual"],
+    )
+    for (otp_name, img_target) in get_otp_images()
+]
+
+# Standalone CW310 image for use with hyperdebug.
+fusesoc_build(
+    name = "fpga_cw310_hyperdebug",
+    testonly = True,
+    srcs = [
+        "//hw:all_files",
+        _FPGA_CW310_TESTROM,
+        _FPGA_CW310_OTP_RMA,
+    ],
+    cores = ["//:cores"],
+    data = ["//hw/ip/otbn:all_files"],
+    flags = [
+        "--BootRomInitFile=" + _FPGA_CW310_TESTROM_PATH,
+        "--OtpCtrlMemInitFile=" + _FPGA_CW310_OTP_RMA_PATH,
+    ],
+    output_groups = {
+        "bitstream": ["synth-vivado/lowrisc_systems_chip_earlgrey_cw310_hyperdebug_0.1.bit"],
+        "rom_mmi": ["synth-vivado/rom.mmi"],
+        "otp_mmi": ["synth-vivado/otp.mmi"],
+    },
+    systems = ["lowrisc:systems:chip_earlgrey_cw310_hyperdebug"],
+    tags = ["manual"],
+    target = "synth",
+)
+
+filegroup(
+    name = "fpga_cw310_test_rom_hyp",
+    testonly = True,
+    srcs = [":fpga_cw310_hyperdebug"],
+    output_group = "bitstream",
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "rom_mmi_hyp",
+    testonly = True,
+    srcs = [":fpga_cw310_hyperdebug"],
+    output_group = "rom_mmi",
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "otp_mmi_hyp",
+    testonly = True,
+    srcs = [":fpga_cw310_hyperdebug"],
+    output_group = "otp_mmi",
+    tags = ["manual"],
+)
+
+# Packaging rules for bitstreams
+pkg_files(
+    name = "standard",
+    testonly = True,
+    srcs = [
+        ":fpga_cw310_rom",
+        ":fpga_cw310_test_rom",
+        ":otp_mmi",
+        ":rom_mmi",
+    ] + [
+        ":fpga_cw310_rom_otp_" + otp_name
+        for (otp_name, _) in get_otp_images()
+    ],
+    prefix = "earlgrey/fpga_cw310/standard",
+    tags = ["manual"],
+)
+
+pkg_files(
+    name = "hyperdebug",
+    testonly = True,
+    srcs = [
+        ":fpga_cw310_test_rom_hyp",
+        ":otp_mmi_hyp",
+        ":rom_mmi_hyp",
+    ],
+    prefix = "earlgrey/fpga_cw310/hyperdebug",
+    tags = ["manual"],
+)
+
+pkg_filegroup(
+    name = "package",
+    testonly = True,
+    srcs = [
+        ":hyperdebug",
+        ":standard",
+    ],
     tags = ["manual"],
 )
diff --git a/hw/cdc/tools/dvsim/common_cdc_cfg.hjson b/hw/cdc/tools/dvsim/common_cdc_cfg.hjson
index 119ac89..979af6d 100644
--- a/hw/cdc/tools/dvsim/common_cdc_cfg.hjson
+++ b/hw/cdc/tools/dvsim/common_cdc_cfg.hjson
@@ -58,9 +58,6 @@
   // Restrict the maximum message count in each bucket
   max_msg_count: 1000
 
-  // TODO(#9079): Need to align with new parser mechanism.
-  build_fail_patterns: []
-
   exports: [
     { CDC_ROOT:     "{cdc_root}" },
     { FOUNDRY_ROOT: "{foundry_root}" },
diff --git a/hw/cdc/tools/dvsim/verixcdc.hjson b/hw/cdc/tools/dvsim/verixcdc.hjson
index 0f702b7..9bb2266 100644
--- a/hw/cdc/tools/dvsim/verixcdc.hjson
+++ b/hw/cdc/tools/dvsim/verixcdc.hjson
@@ -6,8 +6,10 @@
   exports: [
     { CONSTRAINT:         "{sdc_file}" },
     { FOUNDRY_CONSTRAINT: "{foundry_sdc_file}" },
-    { CDC_WAIVER_FILE:    "{cdc_waiver_file}" }
-    { ENV_FILE:           "{env_file}" }
+    { CDC_WAIVER_FILE:    "{cdc_waiver_file}" },
+    { ENV_FILE:           "{env_file}" },
+    { USER_ENV_FILE:      "{cdc_user_env}" },
+    { AST_LIB:            "{ast_lib}" },
   ]
 
   // Tool invocation
diff --git a/hw/cdc/tools/verixcdc/run-cdc.tcl b/hw/cdc/tools/verixcdc/run-cdc.tcl
index c453a7e..7244e2f 100644
--- a/hw/cdc/tools/verixcdc/run-cdc.tcl
+++ b/hw/cdc/tools/verixcdc/run-cdc.tcl
@@ -27,6 +27,8 @@
 set CDC_WAIVER_FILE    [get_env_var "CDC_WAIVER_FILE"]
 set CDC_WAIVER_DIR     [file dirname $CDC_WAIVER_FILE]
 set ENV_FILE           [get_env_var "ENV_FILE"]
+set USER_ENV_FILE      [get_env_var "USER_ENV_FILE"]
+set AST_LIB            [get_env_var "AST_LIB"]
 
 # Used to disable some SDC constructs that are not needed by CDC.
 set IS_CDC_RUN 1
@@ -56,17 +58,23 @@
 set ri_create_outputs_in_create_env true
 set ri_print_module_nand2_counts true
 # enable analysis of large arrays
-set ri_max_total_range_bits 100000
+set ri_max_total_range_bits 20
+set ri_prefer_liberty true
+
+############################
+## Apply Liberty File ##
+
+read_liberty $AST_LIB -db_dir ./RI_compiled_libs/verix_libs
+############################
 
 #########################
 ## Analyze & Elaborate ##
 #########################
 
-# TODO(#11492): Fix the issue of CDC delay
 if {$DEFINE != ""} {
-  analyze -sverilog +define+${DEFINE} +define+AST_BYPASS_CLK -f ${SV_FLIST}
+  analyze -sverilog +define+${DEFINE}  -f ${SV_FLIST}
 } else {
-  analyze -sverilog  +define+AST_BYPASS_CLK -f ${SV_FLIST}
+  analyze -sverilog   -f ${SV_FLIST}
 }
 
 # TODO(#13197): two flops are accessing the same memory cells
@@ -102,31 +110,32 @@
 
 # Req/Ack synchronizer
 
-
-# TODO: These should not be hardcoded here, instead, we need to create another variable called CDC_CONSTRAINT
-# where a top could specifically suppli this
-#
-# The following paths ignore data integrity errors that are directly generated on the async fifo data
-# The path is as follows: asycn_fifo.rdata_o -> data integrity check -> a_valid
-# There are two such paths: One path is a the error pin directly into ibex, and the other is ibex's internal check
-set async_fifo_data [get_pins -of_objects [get_cells -hier * -filter {ref_name == prim_fifo_async}] -filter {name =~ rdata_o*}]
-set_ignore_cdc_paths -name async_fifo_to_ibex_data_err -through_signal $async_fifo_data -through_signal [get_pins top_earlgrey/u_rv_core_ibex/u_core/data_err_i]
-set_ignore_cdc_paths -name async_fifo_to_ibex_ecc_err -through_signal $async_fifo_data -through_signal [get_pins top_earlgrey/u_rv_core_ibex/u_core/u_ibex_core/load_store_unit_i/load_intg_err_o]
-
-# The following paths ignore valid qualification using the response data
-# In the socketm1 module, the returned ID is used to pick which of the original source made the request
-# CDC sees this as rdata directly affecting the control, but the control signal should already dictate whether the rdata is safe to use.
-set_ignore_cdc_paths -name async_fifo_to_ibex_ivalid -through_signal $async_fifo_data -through_signal [get_pins top_earlgrey/u_rv_core_ibex/u_core/instr_rvalid_i]
-set_ignore_cdc_paths -name async_fifo_to_ibex_dvalid -through_signal $async_fifo_data -through_signal [get_pins top_earlgrey/u_rv_core_ibex/u_core/data_rvalid_i]
-
-# CDC between tlul_fifo_async and regs : ignored
-set tlul_async_data [get_pins -of_objects [get_cells -hier * -filter {ref_name == tlul_fifo_async}] -filter {name =~ tl_d_o*}]
-set_ignore_cdc_paths -name tlul_async_fifo_err -through_signal $tlul_async_data -through_signal [get_pins {top_earlgrey/*/u_reg/u_io_*meas_ctrl_shadowed_cdc/src_we_i}]
 #########################
 ## Apply Constraints   ##
 #########################
 
-read_sdc $CONSTRAINT
+#read_sdc $CONSTRAINT
+create_scenario {sdc env}
+current_scenario sdc
+read_sdc  $CONSTRAINT  -output_env constraints.sdc.env
+current_scenario env
+read_env constraints.sdc.env
+touch $USER_ENV_FILE
+read_env $USER_ENV_FILE
+
+# RI's recommended approach to filter out unnecessary errors on async_fifo
+catch {rm tmp.env}
+set outfile [open tmp.env  w]
+puts $outfile "set_data_clock_domain rdata_o -module prim_fifo_async -derived_from rvalid_o"
+# TODO: These should not be hardcoded here, instead, we need to create another variable called CDC_CONSTRAINT
+# where a top could specifically suppli this
+puts $outfile "set_stable {{top_earlgrey.u_pinmux_aon.mio_oe_retreg_q[46:0]}}"
+puts $outfile "set_stable {{top_earlgrey.u_pinmux_aon.dio_out_retreg_q[15:0]}}"
+puts $outfile "set_clock_sense -stop_propagation -clocks { AST_EXT_CLK } { u_ast.clk_ast_ext_i }"
+
+close $outfile
+read_env tmp.env
+
 if {$FOUNDRY_CONSTRAINT != ""} {
   read_sdc $FOUNDRY_CONSTRAINT
 }
@@ -143,7 +152,9 @@
 ## Run CDC             ##
 #########################
 
-analyze_intent
+#analyze_intent
+analyze_intent -disable_auto_intent_generation
+report_policy {ALL} -output setup_checks.rpt -compat -verbose
 
 verify_cdc
 
diff --git a/hw/data/common_project_cfg.hjson b/hw/data/common_project_cfg.hjson
index 59660b8..6d0edb4 100644
--- a/hw/data/common_project_cfg.hjson
+++ b/hw/data/common_project_cfg.hjson
@@ -11,6 +11,12 @@
   scratch_base_path:  "{scratch_root}/{branch}"
   scratch_path:       "{scratch_base_path}/{dut}-{flow}-{tool}"
 
+  // Common data structure
+  build_pass_patterns: []
+  // TODO: Add back FuseSoC fail pattern after
+  // https://github.com/lowRISC/opentitan/issues/7348 is resolved.
+  build_fail_patterns: []
+
   exports: [
     { SCRATCH_PATH: "{scratch_path}" },
     { proj_root: "{proj_root}" }
@@ -19,23 +25,14 @@
   // Results server stuff - indicate what command to use to copy over the results.
   // Workaround for gsutil to fall back to using python2.7.
   results_server_prefix:      "gs://"
-  results_server_url_prefix:  "https://"
   results_server_cmd:         "/usr/bin/gsutil"
-
-  results_server_path: "{results_server_prefix}{results_server}/{rel_path}"
-  results_server_dir:  "{results_server_path}/latest"
-
-  results_server_html: "results.html"
-  results_server_page: "{results_server_dir}/{results_server_html}"
-
-  results_summary_server_html: "summary.html"
-  results_summary_server_page: "{results_server_path}/{results_summary_server_html}"
+  results_html_name:          "report.html"
 
   // If defined, this is printed into the results md files
   revision: '''{eval_cmd}
             COMMIT_L=`git rev-parse HEAD`; \
             COMMIT_S=`git rev-parse --short HEAD`; \
-            REV_STR="GitHub Revision: [\`$COMMIT_S\`]({results_server_url_prefix}{repo_server}/tree/$COMMIT_L)"; \
+            REV_STR="GitHub Revision: [\`$COMMIT_S\`](https://{repo_server}/tree/$COMMIT_L)"; \
             printf "$REV_STR"; \
             if [ -d "{proj_root}/hw/foundry" ]; then \
               COMMIT_FOUNDRY_S=`git -C {proj_root}/hw/foundry rev-parse --short HEAD`; \
diff --git a/hw/doc/cores.md b/hw/doc/cores.md
new file mode 100644
index 0000000..51eabc0
--- /dev/null
+++ b/hw/doc/cores.md
@@ -0,0 +1,4 @@
+# Cores
+
+* [Ibex](../ip/rv_core_ibex/README.md)
+* [OTBN](../ip/otbn/README.md)
diff --git a/hw/dv/README.md b/hw/dv/README.md
new file mode 100644
index 0000000..10af998
--- /dev/null
+++ b/hw/dv/README.md
@@ -0,0 +1,4 @@
+# Common Design Verification Collateral
+The documentation is split into SystemVerilog reference and tools.
+
+{{% sectionContent type="section" depth="1" %}}
diff --git a/hw/dv/_index.md b/hw/dv/_index.md
deleted file mode 100644
index 9df4256..0000000
--- a/hw/dv/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Design Verification"
----
-
-{{% sectionContent %}}
diff --git a/hw/dv/doc/dv_doc_template.md b/hw/dv/doc/dv_doc_template.md
index b478204..b60609d 100644
--- a/hw/dv/doc/dv_doc_template.md
+++ b/hw/dv/doc/dv_doc_template.md
@@ -1,6 +1,4 @@
----
-title: "FOO DV document"
----
+# FOO DV document
 
 <!-- Copy this file to hw/ip/foo/doc/index.md and make changes as needed.
 For convenience 'foo' in the document can be searched and replaced easily with the
@@ -9,7 +7,10 @@
 as a starting point and modify it to reflect your foo testbench and save it
 to hw/ip/foo/doc/tb.svg. It should get linked and rendered under the block
 diagram section below. Please update / modify / remove sections below as
-applicable. Once done, remove this comment before making a PR. -->
+applicable. Once done, remove this comment before making a PR.
+
+Remove "draft: true" before submission.
+-->
 
 ## Goals
 * **DV**
@@ -19,16 +20,16 @@
   * Verify TileLink device protocol compliance with an SVA based testbench
 
 ## Current status
-* [Design & verification stage]({{< relref "hw" >}})
-  * [HW development stages]({{< relref "doc/project/development_stages.md" >}})
-* [Simulation results](https://reports.opentitan.org/hw/ip/foo/dv/latest/results.html)
+* [Design & verification stage](../../README.md)
+  * [HW development stages](../../../doc/project_governance/development_stages.md)
+* [Simulation results](https://reports.opentitan.org/hw/ip/foo/dv/latest/report.html)
 
 ## Design features
 <!-- TODO: uncomment link to the spec below -->
 For detailed information on FOO design features, please see the [FOO  HWIP technical specification]({{</* relref "hw/ip/foo/doc" */>}}).
 
 ## Testbench architecture
-FOO testbench has been constructed based on the [CIP testbench architecture]({{< relref "hw/dv/sv/cip_lib/doc" >}}).
+FOO testbench has been constructed based on the [CIP testbench architecture](../sv/cip_lib/README.md).
 
 ### Block diagram
 ![Block diagram](tb.svg)
@@ -36,17 +37,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]({{< relref "hw/dv/sv/common_ifs" >}})
-* [TileLink host interface]({{< relref "hw/dv/sv/tl_agent/doc" >}})
+* [Clock and reset interface](../sv/common_ifs/README.md)
+* [TileLink host interface](../sv/tl_agent/README.md)
 * FOO IOs
-* Interrupts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}}))
-* Alerts ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}}))
-* Devmode ([`pins_if`]({{< relref "hw/dv/sv/common_ifs" >}}))
+* Interrupts ([`pins_if`](../sv/common_ifs/README.md))
+* Alerts ([`pins_if`](../sv/common_ifs/README.md))
+* Devmode ([`pins_if`](../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]({{< relref "hw/dv/sv/dv_utils/doc" >}})
-* [csr_utils_pkg]({{< relref "hw/dv/sv/csr_utils/doc" >}})
+* [dv_utils_pkg](../sv/dv_utils/README.md)
+* [csr_utils_pkg](../sv/csr_utils/README.md)
 
 ### Compile-time configurations
 [list compile time configurations, if any and what are they used for]
@@ -58,7 +59,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]({{< relref "hw/dv/sv/tl_agent/doc" >}})
+FOO testbench instantiates (already handled in CIP base env) [tl_agent](../sv/tl_agent/README.md)
 which provides the ability to drive and independently monitor random traffic via
 TL host interface into FOO device.
 
@@ -69,7 +70,7 @@
 [Describe here or add link to its README]
 
 ### UVM RAL Model
-The FOO RAL model is created with the [`ralgen`]({{< relref "hw/dv/tools/ralgen/doc" >}}) FuseSoC generator script automatically when the simulation is at the build stage.
+The FOO RAL model is created with the [`ralgen`](../tools/ralgen/README.md) FuseSoC generator script automatically when the simulation is at the build stage.
 
 It can be created manually (separately) by running `make` in the the `hw/` area.
 
@@ -101,13 +102,13 @@
 <!-- explain inputs monitored, flow of data and outputs checked -->
 
 #### Assertions
-* 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.
+* TLUL assertions: The `tb/foo_bind.sv` binds the `tlul_assert` [assertions](../../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]({{< relref "hw/dv/tools/doc" >}}) for building and running our tests and regressions.
+We are using our in-house developed [regression tool](../../../util/dvsim/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 smoke test:
diff --git a/hw/dv/dpi/common/tcp_server/tcp_server.c b/hw/dv/dpi/common/tcp_server/tcp_server.c
index 1caf2f8..3dfa95d 100644
--- a/hw/dv/dpi/common/tcp_server/tcp_server.c
+++ b/hw/dv/dpi/common/tcp_server/tcp_server.c
@@ -21,7 +21,7 @@
 /**
  * Simple buffer for passing data between TCP sockets and DPI modules
  */
-const int BUFSIZE_BYTE = 256;
+#define BUFSIZE_BYTE 256
 
 struct tcp_buf {
   unsigned int rptr;
@@ -38,8 +38,8 @@
   uint16_t listen_port;
   volatile bool socket_run;
   // Writeable by the server thread
-  tcp_buf *buf_in;
-  tcp_buf *buf_out;
+  struct tcp_buf *buf_in;
+  struct tcp_buf *buf_out;
   int sfd;  // socket fd
   int cfd;  // client fd
   pthread_t sock_thread;
@@ -378,7 +378,8 @@
 }
 
 // Abstract interface functions
-tcp_server_ctx *tcp_server_create(const char *display_name, int listen_port) {
+struct tcp_server_ctx *tcp_server_create(const char *display_name,
+                                         int listen_port) {
   struct tcp_server_ctx *ctx =
       (struct tcp_server_ctx *)calloc(1, sizeof(struct tcp_server_ctx));
   assert(ctx);
diff --git a/hw/dv/dpi/common/tcp_server/tcp_server.h b/hw/dv/dpi/common/tcp_server/tcp_server.h
index f76ff0e..5226322 100644
--- a/hw/dv/dpi/common/tcp_server/tcp_server.h
+++ b/hw/dv/dpi/common/tcp_server/tcp_server.h
@@ -16,6 +16,7 @@
 extern "C" {
 #endif
 
+#include <stdbool.h>
 #include <stdint.h>
 
 struct tcp_server_ctx;
@@ -47,7 +48,8 @@
  * @param listen_port On which port the server should listen
  * @return A pointer to the created context struct
  */
-tcp_server_ctx *tcp_server_create(const char *display_name, int listen_port);
+struct tcp_server_ctx *tcp_server_create(const char *display_name,
+                                         int listen_port);
 
 /**
  * Shut down the server and free all reserved memory
diff --git a/hw/dv/dpi/dmidpi/dmidpi.c b/hw/dv/dpi/dmidpi/dmidpi.c
index f9df5b3..c44bbac 100644
--- a/hw/dv/dpi/dmidpi/dmidpi.c
+++ b/hw/dv/dpi/dmidpi/dmidpi.c
@@ -31,7 +31,7 @@
 // [3:0]    0x1  - Protocol version (0.13)
 const int DTMCSRVAL = 0x00000071;
 
-enum jtag_state_t : uint8_t {
+enum jtag_state_t {
   TestLogicReset,
   RunTestIdle,
   SelectDrScan,
@@ -50,7 +50,7 @@
   UpdateIr
 };
 
-enum jtag_reg_t : uint8_t {
+enum jtag_reg_t {
   Bypass0 = 0x0,
   IdCode = 0x1,
   DTMCSR = 0x10,
@@ -65,7 +65,7 @@
   uint64_t dr_captured;
   uint8_t dr_length;
   uint8_t jtag_tdo;
-  jtag_state_t jtag_state;
+  enum jtag_state_t jtag_state;
   uint8_t dmi_outstanding;
 };
 
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.c b/hw/dv/dpi/gpiodpi/gpiodpi.c
index 4b326f5..5652ecd 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.c
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.c
@@ -21,6 +21,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+// The number of ticks of host_to_device_tick between making syscalls.
+#define TICKS_PER_SYSCALL 2048
+
 // This file does a lot of bit setting and getting; these macros are intended to
 // make that a little more readable.
 #define GET_BIT(word, bit_idx) (((word) >> (bit_idx)) & 1)
@@ -33,6 +36,11 @@
 
   // The last known value of the pins, in little-endian order.
   uint32_t driven_pin_values;
+  // Whether or not the pin is being driven weakly or strongly.
+  uint32_t weak_pins;
+  // A counter of calls into the host_to_device_tick function; used to
+  // avoid excessive `read` syscalls to the pipe fd.
+  uint32_t counter;
 
   // File descriptors and paths for the device-to-host and host-to-device
   // FIFOs.
@@ -90,6 +98,8 @@
   printf("GPIO: To drive the pins, run a command like\n");
   printf("$ echo 'h09 l31' > %s  # Pull the pin 9 high, and pin 31 low.\n",
          wfifo);
+  printf("$ echo 'wh10' > %s  # Pull pin 10 high through a weak pull-up.\n",
+         wfifo);
 }
 
 void *gpiodpi_create(const char *name, int n_bits) {
@@ -103,6 +113,8 @@
   ctx->n_bits = n_bits;
 
   ctx->driven_pin_values = 0;
+  ctx->weak_pins = 0;
+  ctx->counter = 0;
 
   char cwd_buf[PATH_MAX];
   char *cwd = getcwd(cwd_buf, sizeof(cwd_buf));
@@ -186,53 +198,88 @@
   return value;
 }
 
-uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe) {
+static void set_bit_val(uint32_t *word, uint32_t idx, bool val) {
+  if (val) {
+    SET_BIT(*word, idx);
+  } else {
+    CLR_BIT(*word, idx);
+  }
+}
+
+uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe,
+                                     svBitVecVal *gpio_pull_en,
+                                     svBitVecVal *gpio_pull_sel) {
   struct gpiodpi_ctx *ctx = (struct gpiodpi_ctx *)ctx_void;
   assert(ctx);
 
-  char gpio_str[32 + 2];
-  ssize_t read_len = read(ctx->host_to_dev_fifo, gpio_str, 32 + 1);
-  if (read_len < 0) {
-    return ctx->driven_pin_values;
-  }
-  gpio_str[read_len] = '\0';
+  if (ctx->counter % TICKS_PER_SYSCALL == 0) {
+    char gpio_str[256];
+    ssize_t read_len =
+        read(ctx->host_to_dev_fifo, gpio_str, sizeof(gpio_str) - 1);
+    if (read_len > 0) {
+      gpio_str[read_len] = '\0';
 
-  char *gpio_text = gpio_str;
-  for (; *gpio_text != '\0'; ++gpio_text) {
-    switch (*gpio_text) {
-      case '\n':
-      case '\r':
-      case '\0':
-        goto parse_loop_end;
-      case 'l':
-      case 'L': {
-        ++gpio_text;
-        int idx = parse_dec(&gpio_text);
-        if (!GET_BIT(gpio_oe[0], idx)) {
-          fprintf(stderr,
-                  "GPIO: Host tried to pull disabled pin low: pin %2d\n", idx);
+      bool weak = false;
+      char *gpio_text = gpio_str;
+      for (; *gpio_text != '\0'; ++gpio_text) {
+        switch (*gpio_text) {
+          case '\0':
+            goto parse_loop_end;
+          case 'w':
+          case 'W': {
+            weak = true;
+            break;
+          }
+          case 'l':
+          case 'L': {
+            ++gpio_text;
+            int idx = parse_dec(&gpio_text);
+            if (!GET_BIT(gpio_oe[0], idx)) {
+              fprintf(stderr,
+                      "GPIO: Host tried to pull disabled pin low: pin %2d\n",
+                      idx);
+            }
+            CLR_BIT(ctx->driven_pin_values, idx);
+            set_bit_val(&ctx->weak_pins, idx, weak);
+            weak = false;
+            break;
+          }
+          case 'h':
+          case 'H': {
+            ++gpio_text;
+            int idx = parse_dec(&gpio_text);
+            if (!GET_BIT(gpio_oe[0], idx)) {
+              fprintf(stderr,
+                      "GPIO: Host tried to pull disabled pin high: pin %2d\n",
+                      idx);
+            }
+            SET_BIT(ctx->driven_pin_values, idx);
+            set_bit_val(&ctx->weak_pins, idx, weak);
+            weak = false;
+            break;
+          }
+          default:
+            break;
         }
-        CLR_BIT(ctx->driven_pin_values, idx);
-        break;
       }
-      case 'h':
-      case 'H': {
-        ++gpio_text;
-        int idx = parse_dec(&gpio_text);
-        if (!GET_BIT(gpio_oe[0], idx)) {
-          fprintf(stderr,
-                  "GPIO: Host tried to pull disabled pin high: pin %2d\n", idx);
-        }
-        SET_BIT(ctx->driven_pin_values, idx);
-        break;
-      }
-      default:
-        break;
     }
   }
 
 parse_loop_end:
-  return ctx->driven_pin_values;
+  ctx->counter += 1;
+  // The verilated module simulates logic, but the weak/strong inputs result
+  // from the properties of the IO pads and the selection of external pull
+  // resistors. Since the verilated model doesn't model the analog properties
+  // of the IO pads, we fake it in the DPI interface.
+  //
+  // Candidates for reporting a value other than the driven value are those pins
+  // which are being weak and the pinmux has the pull up/down resistor enabled.
+  // On weak pins, the pinmux pull up/down wins over the driven value of the
+  // pin.  On strong pins, the driven value always wins.
+  uint32_t candidates = ctx->weak_pins & gpio_pull_en[0];
+  uint32_t pull = candidates & gpio_pull_sel[0];
+  uint32_t result = (ctx->driven_pin_values & ~candidates) | pull;
+  return result;
 }
 
 void gpiodpi_close(void *ctx_void) {
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.h b/hw/dv/dpi/gpiodpi/gpiodpi.h
index 80ee70e..80fef2d 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.h
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.h
@@ -38,7 +38,9 @@
  * Intended to be called from SystemVerilog.
  * @return the values to pull the GPIO pins to.
  */
-uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe);
+uint32_t gpiodpi_host_to_device_tick(void *ctx_void, svBitVecVal *gpio_oe,
+                                     svBitVecVal *gpio_pull_en,
+                                     svBitVecVal *gpio_pull_sel);
 
 /**
  * Relinquish resources held by a GPIO DPI interface.
diff --git a/hw/dv/dpi/gpiodpi/gpiodpi.sv b/hw/dv/dpi/gpiodpi/gpiodpi.sv
index 492616d..5d5ce31 100644
--- a/hw/dv/dpi/gpiodpi/gpiodpi.sv
+++ b/hw/dv/dpi/gpiodpi/gpiodpi.sv
@@ -12,7 +12,9 @@
 
   output logic [N_GPIO-1:0] gpio_p2d,
   input  logic [N_GPIO-1:0] gpio_d2p,
-  input  logic [N_GPIO-1:0] gpio_en_d2p
+  input  logic [N_GPIO-1:0] gpio_en_d2p,
+  input  logic [N_GPIO-1:0] gpio_pull_en,
+  input  logic [N_GPIO-1:0] gpio_pull_sel
 );
    import "DPI-C" function
      chandle gpiodpi_create(input string name, input int n_bits);
@@ -26,7 +28,9 @@
 
    import "DPI-C" function
      int gpiodpi_host_to_device_tick(input chandle ctx,
-                                     input [N_GPIO-1:0] gpio_en_d2p);
+                                     input [N_GPIO-1:0] gpio_en_d2p,
+                                     input [N_GPIO-1:0] gpio_pull_en,
+                                     input [N_GPIO-1:0] gpio_pull_sel);
 
    chandle ctx;
 
@@ -51,26 +55,8 @@
    always_ff @(posedge clk_i or negedge rst_ni) begin
      if (!rst_ni) begin
        gpio_p2d <= '0; // default value
-     end else if (gpio_write_pulse) begin
-       gpio_p2d <= gpiodpi_host_to_device_tick(ctx, gpio_en_d2p);
-     end
-   end
-
-   // gpiodpio_host_to_device_tick() will be called every MAX_COUNT
-   // clock posedges; this should be kept reasonably high, since each
-   // tick call will perform at least one syscall.
-   localparam MAX_COUNT = 2048;
-   logic [$clog2(MAX_COUNT)-1:0] counter;
-
-   assign gpio_write_pulse = counter == MAX_COUNT -1;
-
-   always_ff @(posedge clk_i or negedge rst_ni) begin
-     if (!rst_ni) begin
-       counter <= '0;
-     end else if (gpio_write_pulse) begin
-       counter <= '0;
      end else begin
-       counter <= counter + 1'b1;
+       gpio_p2d <= gpiodpi_host_to_device_tick(ctx, gpio_en_d2p, gpio_pull_en, gpio_pull_sel);
      end
    end
 
diff --git a/hw/dv/dpi/usbdpi/monitor_usb.c b/hw/dv/dpi/usbdpi/monitor_usb.c
deleted file mode 100644
index 7063918..0000000
--- a/hw/dv/dpi/usbdpi/monitor_usb.c
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-#include <assert.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "usbdpi.h"
-
-const char *decode_pid[] = {
-    "Rsvd",     //         0000b
-    "OUT",      //         0001b
-    "ACK",      //         0010b
-    "DATA0",    //         0011b
-    "PING",     //         0100b
-    "SOF",      //         0101b
-    "NYET",     //         0110b
-    "DATA2",    //         0111b
-    "SPLIT",    //         1000b
-    "IN",       //         1001b
-    "NAK",      //         1010b
-    "DATA1",    //         1011b
-    "PRE/ERR",  //         1100b
-    "SETUP",    //         1101b
-    "STALL",    //         1110b
-    "MDATA"     //         1111b
-};
-
-#define MS_IDLE 0
-#define MS_GET_PID 1
-#define MS_GET_BYTES 2
-
-#define M_NONE 0
-#define M_HOST 1
-#define M_DEVICE 2
-
-#define SE0 0
-#define DK 1
-#define DJ 2
-
-#define MON_BYTES_SIZE 1024
-
-struct mon_ctx {
-  int state;
-  int driver;
-  int pu;
-  int line;
-  int rawbits;
-  int bits;
-  int needbits;
-  int byte;
-  int sopAt;
-  int lastpid;
-  unsigned char bytes[MON_BYTES_SIZE + 2];
-};
-
-void *monitor_usb_init(void) {
-  struct mon_ctx *mon = (struct mon_ctx *)calloc(1, sizeof(struct mon_ctx));
-  assert(mon);
-
-  mon->state = MS_IDLE;
-  mon->driver = M_NONE;
-  mon->pu = 0;
-  mon->line = 0;
-  return (void *)mon;
-}
-
-#define DR_SIZE 128
-static char dr[DR_SIZE];
-char *pid_2data(int pid, unsigned char d0, unsigned char d1) {
-  int comp_crc = CRC5((d1 & 7) << 8 | d0, 11);
-  const char *crcok = (comp_crc == d1 >> 3) ? "OK" : "BAD";
-  if ((pid == USB_PID_IN) || (pid == USB_PID_OUT) || (pid == USB_PID_SETUP)) {
-    snprintf(dr, DR_SIZE, "%s %d.%d (CRC5 %02x %s)", decode_pid[pid & 0xf],
-             d0 & 0x7f, (d1 & 7) << 1 | d0 >> 7, d1 >> 3, crcok);
-  } else if (pid == USB_PID_SOF) {
-    snprintf(dr, DR_SIZE, "SOF %03x (CRC5 %02x %s)", (d1 & 7) << 8 | d0,
-             d1 >> 3, crcok);
-  } else if ((pid == USB_PID_DATA0) || (pid == USB_PID_DATA1)) {
-    snprintf(dr, DR_SIZE, "%s %02x, %02x (%s)", decode_pid[pid & 0xf], d0, d1,
-             (d0 | d1) ? "CRC16 BAD" : "NULL");
-  } else {
-    snprintf(dr, DR_SIZE, "%s %02x, %02x (CRC5 %s)", decode_pid[pid & 0xf], d0,
-             d1, crcok);
-  }
-  return dr;
-}
-
-void monitor_usb(void *mon_void, FILE *mon_file, int loglevel, int tick,
-                 int hdrive, int p2d, int d2p, int *lastpid) {
-  struct mon_ctx *mon = (struct mon_ctx *)mon_void;
-  assert(mon);
-  int dp, dn;
-  int log, compact;
-  log = (loglevel & 0x2);
-  compact = (loglevel & 0x1);
-
-  if ((d2p & D2P_DP_EN) || (d2p & D2P_DN_EN) || (d2p & D2P_D_EN)) {
-    if (hdrive) {
-      fprintf(mon_file, "mon: %8d: Bus clash\n", tick);
-    }
-    if (d2p & D2P_TX_USE_D_SE0) {
-      if ((d2p & D2P_SE0) || !(d2p & D2P_D_EN)) {
-        dp = 0;
-        dn = 0;
-      } else {
-        dp = (d2p & D2P_D) ? 1 : 0;
-        dn = (d2p & D2P_D) ? 0 : 1;
-      }
-    } else {
-      dp = ((d2p & D2P_DP_EN) && (d2p & D2P_DP)) ? 1 : 0;
-      dn = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) ? 1 : 0;
-    }
-    mon->driver = M_DEVICE;
-  } else if (hdrive) {
-    if (d2p & D2P_RX_ENABLE) {
-      if (p2d & (P2D_DP | P2D_DN)) {
-        dp = (p2d & P2D_D) ? 1 : 0;
-        dn = (p2d & P2D_D) ? 0 : 1;
-      } else {
-        dp = 0;
-        dn = 0;
-      }
-    } else {
-      dp = (p2d & P2D_DP) ? 1 : 0;
-      dn = (p2d & P2D_DN) ? 1 : 0;
-    }
-    mon->driver = M_HOST;
-  } else {
-    if ((mon->driver != M_NONE) || (mon->pu != (d2p & D2P_PU))) {
-      if (log) {
-        if (d2p & D2P_PU) {
-          fprintf(mon_file, "mon: %8d: Idle, FS resistor (d2p 0x%x)\n", tick,
-                  d2p);
-        } else {
-          fprintf(mon_file, "mon: %8d: Idle, SE0\n", tick);
-        }
-      }
-      mon->driver = M_NONE;
-      mon->pu = (d2p & D2P_PU);
-    }
-    mon->line = 0;
-    return;
-  }
-  // If the DN pullup is there then swap
-  if (d2p & D2P_DNPU) {
-    int tmp = dp;
-    dp = dn;
-    dn = tmp;
-  }
-  mon->line = (mon->line << 2) | dp << 1 | dn;
-
-  if (mon->state == MS_IDLE) {
-    if ((mon->line & 0xfff) == ((DK << 10) | (DJ << 8) | (DK << 6) | (DJ << 4) |
-                                (DK << 2) | (DK << 0))) {
-      if (log) {
-        fprintf(mon_file, "mon: %8d: (%c) SOP\n", tick,
-                mon->driver == M_HOST ? 'H' : 'D');
-      }
-      mon->sopAt = tick;
-      mon->state = MS_GET_PID;
-      mon->needbits = 8;
-    }
-    return;
-  }
-  if ((mon->line & 0x3f) == ((SE0 << 4) | (SE0 << 2) | (DJ << 0))) {
-    if ((log || compact) && (mon->state == MS_GET_BYTES) && (mon->byte > 0)) {
-      int i;
-      int text = 1;
-      uint32_t pkt_crc16, comp_crc16;
-
-      if (compact && mon->byte == 2) {
-        fprintf(mon_file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
-                mon->sopAt, tick, mon->driver == M_HOST ? 'H' : 'D',
-                pid_2data(mon->lastpid, mon->bytes[0], mon->bytes[1]));
-      } else if (compact && mon->byte == 1) {
-        fprintf(mon_file, "mon: %8d -- %8d: (%c) SOP, PID %s %02x EOP\n",
-                mon->sopAt, tick, mon->driver == M_HOST ? 'H' : 'D',
-                decode_pid[mon->lastpid & 0xf], mon->bytes[0]);
-      } else {
-        if (compact) {
-          fprintf(mon_file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
-                  mon->sopAt, tick, mon->driver == M_HOST ? 'H' : 'D',
-                  decode_pid[mon->lastpid & 0xf]);
-        }
-        fprintf(mon_file,
-                "mon:     %s: ", mon->driver == M_HOST ? "h->d" : "d->h");
-        comp_crc16 = CRC16(mon->bytes, mon->byte - 2);
-        pkt_crc16 = mon->bytes[mon->byte - 2] | mon->bytes[mon->byte - 1] << 8;
-        for (i = 0; i < mon->byte; i++) {
-          fprintf(mon_file, "%02x%s", mon->bytes[i],
-                  ((i & 0xf) == 0xf)       ? "\nmon:           "
-                  : ((i + 1) == mon->byte) ? ""
-                                           : ", ");
-          if ((mon->bytes[i] == 0x0d) || (mon->bytes[i] == 0x0a)) {
-            mon->bytes[i] = '_';
-          }
-          if (mon->bytes[i] == 0) {
-            mon->bytes[i] = '?';
-          }
-          if (i >= (mon->byte - 2)) {
-            mon->bytes[i] = 0;
-          } else if ((mon->bytes[i] < 32) || (mon->bytes[i] > 127)) {
-            text = 0;
-          }
-        }
-        if (comp_crc16 == pkt_crc16) {
-          fprintf(mon_file, "%s CRCOK\n",
-                  (mon->byte == MON_BYTES_SIZE) ? "..." : "");
-        } else {
-          fprintf(mon_file, "%s\nmon:           CRC16 %04x BAD expected %04x\n",
-                  (mon->byte == MON_BYTES_SIZE) ? "..." : "", pkt_crc16,
-                  comp_crc16);
-        }
-        if (text && mon->byte > 2) {
-          fprintf(mon_file, "mon:          %s\n", mon->bytes);
-        }
-      }
-    } else if (compact) {
-      fprintf(mon_file, "mon: %8d -- %8d: (%c) SOP, PID %s EOP\n", mon->sopAt,
-              tick, mon->driver == M_HOST ? 'H' : 'D',
-              decode_pid[mon->lastpid & 0xf]);
-    }
-    if (log) {
-      fprintf(mon_file, "mon: %8d: (%c) EOP\n", tick,
-              mon->driver == M_HOST ? 'H' : 'D');
-    }
-    mon->state = MS_IDLE;
-    return;
-  }
-  int newbit = (((mon->line & 0xc) >> 2) == (mon->line & 0x3)) ? 1 : 0;
-  mon->rawbits = (mon->rawbits << 1) | newbit;
-  if ((mon->rawbits & 0x7e) == 0x7e) {
-    if (newbit == 1) {
-      fprintf(mon_file, "mon: %8d: (%c) Bitstuff error, got 1 after 0x%x\n",
-              tick, mon->driver == M_HOST ? 'H' : 'D', mon->rawbits);
-    }
-    /* Ignore bit stuff bit */
-    return;
-  }
-  mon->bits = (mon->bits >> 1) | (newbit << 7);
-  mon->needbits--;
-  if (mon->needbits) {
-    return;
-  }
-  switch (mon->state) {
-    case MS_GET_PID:
-      if (((mon->bits & 0xf0) >> 4) ^ (mon->bits & 0x0f)) {
-        *lastpid = mon->bits;
-        mon->lastpid = mon->bits;
-        if (log) {
-          fprintf(mon_file, "mon: %8d: (%c) PID %s (0x%x)\n", tick,
-                  mon->driver == M_HOST ? 'H' : 'D',
-                  decode_pid[mon->bits & 0xf], mon->bits);
-        }
-      } else if (log) {
-        fprintf(mon_file, "mon: %8d: (%c) BAD PID 0x%x\n", tick,
-                mon->driver == M_HOST ? 'H' : 'D', mon->bits);
-      }
-      mon->state = MS_GET_BYTES;
-      mon->needbits = 8;
-      mon->byte = 0;
-      break;
-
-    case MS_GET_BYTES:
-      mon->bytes[mon->byte] = mon->bits & 0xff;
-      mon->needbits = 8;
-      if (mon->byte < MON_BYTES_SIZE) {
-        mon->byte++;
-      }
-      break;
-  }
-}
diff --git a/hw/dv/dpi/usbdpi/usb_crc.c b/hw/dv/dpi/usbdpi/usb_crc.c
index 98b969b..c5fac78 100644
--- a/hw/dv/dpi/usbdpi/usb_crc.c
+++ b/hw/dv/dpi/usbdpi/usb_crc.c
@@ -88,7 +88,7 @@
 }  // CRC5()
 
 // Added mdhayter
-uint32_t CRC16(uint8_t *data, int bytes) {
+uint32_t CRC16(const uint8_t *data, int bytes) {
   const uint32_t poly16 = 0xA001;
   uint32_t crc16 = 0xffff;
   int i;
diff --git a/hw/dv/dpi/usbdpi/usb_monitor.c b/hw/dv/dpi/usbdpi/usb_monitor.c
new file mode 100644
index 0000000..aea1c77
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_monitor.c
@@ -0,0 +1,394 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "usb_utils.h"
+#include "usbdpi.h"
+
+// USB monitor state
+typedef enum { MS_IDLE = 0, MS_GET_PID, MS_GET_BYTES } usb_monitor_state_t;
+
+// Current driver of the USB
+typedef enum { M_NONE = 0, M_HOST, M_DEVICE } usbmon_driver_t;
+
+#define SE0 0
+#define DK 1
+#define DJ 2
+
+#define MON_BYTES_SIZE 1024
+
+// Number of bytes in max output buffer line
+#define MAX_OBUF 80
+
+/**
+ * USB monitor context
+ */
+struct usb_monitor_ctx {
+  /**
+   * Log file
+   */
+  FILE *file;
+  /**
+   * Monitor state, reflecting the current state of the USB
+   */
+  usb_monitor_state_t state;
+  /**
+   * Current bus driver
+   */
+  usbmon_driver_t driver;
+  uint32_t pu;
+  int line;
+  int rawbits;
+  int bits;
+  int needbits;
+  int sopAt;
+  uint8_t lastpid;
+  /**
+   * USB data callback
+   */
+  usb_monitor_data_callback_t data_callback;
+  /**
+   * Context for data callback
+   */
+  void *data_ctx;
+  /**
+   * Byte offset of the next byte to be collected in the data buffer
+   */
+  uint8_t byte;
+  /**
+   * Buffer of collected bytes
+   */
+  uint8_t bytes[MON_BYTES_SIZE + 2];
+};
+
+// Invoke the USB data callback function, if registered
+static inline void data_callback(usb_monitor_ctx_t *mon,
+                                 usbmon_data_type_t type, uint8_t d) {
+  if (mon->data_callback) {
+    mon->data_callback(mon->data_ctx, type, d);
+  }
+}
+
+/**
+ * Create and initialize a USB monitor instance
+ */
+usb_monitor_ctx_t *usb_monitor_init(const char *filename,
+                                    usb_monitor_data_callback_t data_cb,
+                                    void *data_ctx) {
+  usb_monitor_ctx_t *mon =
+      (usb_monitor_ctx_t *)calloc(1, sizeof(usb_monitor_ctx_t));
+  assert(mon);
+
+  mon->state = MS_IDLE;
+  mon->driver = M_NONE;
+  mon->pu = 0;
+  mon->line = 0;
+
+  // Retain details of the device reception callback
+  mon->data_callback = data_cb;
+  mon->data_ctx = data_ctx;
+
+  mon->file = fopen(filename, "w");
+  if (!mon->file) {
+    fprintf(stderr, "USBDPI: Unable to open monitor file at %s: %s\n", filename,
+            strerror(errno));
+    free(mon);
+    return NULL;
+  }
+
+  // more useful for tail -f
+  setlinebuf(mon->file);
+  printf(
+      "\nUSBDPI: Monitor output file created at %s. Works well with tail:\n"
+      "$ tail -f %s\n",
+      filename, filename);
+
+  return mon;
+}
+
+/**
+ * Finalize a USB monitor
+ */
+void usb_monitor_fin(usb_monitor_ctx_t *mon) {
+  fclose(mon->file);
+  free(mon);
+}
+
+/**
+ * Append a formatted message to the USB monitor log file
+ */
+void usb_monitor_log(usb_monitor_ctx_t *ctx, const char *fmt, ...) {
+  char obuf[MAX_OBUF];
+  va_list ap;
+  va_start(ap, fmt);
+  int n = vsnprintf(obuf, MAX_OBUF, fmt, ap);
+  va_end(ap);
+  size_t written = fwrite(obuf, sizeof(char), (size_t)n, ctx->file);
+  assert(written == (size_t)n);
+}
+
+#define DR_SIZE 128
+static char dr[DR_SIZE];
+char *pid_2data(int pid, unsigned char d0, unsigned char d1) {
+  int comp_crc = CRC5((d1 & 7) << 8 | d0, 11);
+  const char *crcok = (comp_crc == d1 >> 3) ? "OK" : "BAD";
+
+  switch (pid) {
+    case USB_PID_SOF:
+      snprintf(dr, DR_SIZE, "SOF %03x (CRC5 %02x %s)", (d1 & 7) << 8 | d0,
+               d1 >> 3, crcok);
+      break;
+
+    case USB_PID_SETUP:
+    case USB_PID_OUT:
+    case USB_PID_IN:
+      snprintf(dr, DR_SIZE, "%s %d.%d (CRC5 %02x %s)", decode_pid(pid),
+               d0 & 0x7f, (d1 & 7) << 1 | d0 >> 7, d1 >> 3, crcok);
+      break;
+
+    case USB_PID_DATA0:
+    case USB_PID_DATA1:
+      snprintf(dr, DR_SIZE, "%s %02x, %02x (%s)", decode_pid(pid), d0, d1,
+               (d0 | d1) ? "CRC16 BAD" : "NULL");
+      break;
+
+    // Validate and log other PIDs
+    default:
+      if (((pid >> 4) ^ 0xf) == (pid & 0xf)) {
+        snprintf(dr, DR_SIZE, "%s %02x, %02x (CRC5 %s)", decode_pid(pid), d0,
+                 d1, crcok);
+      } else {
+        snprintf(dr, DR_SIZE, "BAD PID %s %02x, %02x (CRC5 %s)",
+                 decode_pid(pid), d0, d1, crcok);
+      }
+      break;
+  }
+  return dr;
+}
+
+/**
+ * Per-cycle monitoring of the USB
+ */
+void usb_monitor(usb_monitor_ctx_t *mon, int loglevel, uint32_t tick_bits,
+                 bool hdrive, uint32_t p2d, uint32_t d2p, uint8_t *lastpid) {
+  bool log = ((loglevel & 0x2) != 0);
+  bool compact = ((loglevel & 0x1) != 0);
+
+  assert(mon);
+
+  // Ascertain state of D+/D- pair; these may have been swapped in some use
+  // cases, but we can ascertain this by looking at the pull-up enables.
+  // The DUT is a full speed device so the pull up should be on D+
+  int dp, dn;
+  if ((d2p & D2P_DP_EN) || (d2p & D2P_DN_EN) || (d2p & D2P_D_EN)) {
+    if (hdrive) {
+      fprintf(mon->file, "mon: %8d: Bus clash\n", tick_bits);
+    }
+    if (d2p & D2P_TX_USE_D_SE0) {
+      // Single-ended mode uses D and SE0
+      if ((d2p & D2P_SE0) || !(d2p & D2P_D_EN)) {
+        // SE0 state, both D+ and D- are low
+        dp = 0;
+        dn = 0;
+      } else {
+        // Normal single-ended data transmission
+        dp = (d2p & D2P_D) ? 1 : 0;
+        dn = (d2p & D2P_D) ? 0 : 1;
+      }
+    } else {
+      dp = ((d2p & D2P_DP_EN) && (d2p & D2P_DP)) ? 1 : 0;
+      dn = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) ? 1 : 0;
+    }
+    mon->driver = M_DEVICE;
+  } else if (hdrive) {
+    if (d2p & D2P_RX_ENABLE) {
+      if (p2d & (P2D_DP | P2D_DN)) {
+        dp = (p2d & P2D_D) ? 1 : 0;
+        dn = (p2d & P2D_D) ? 0 : 1;
+      } else {
+        dp = 0;
+        dn = 0;
+      }
+    } else {
+      dp = (p2d & P2D_DP) ? 1 : 0;
+      dn = (p2d & P2D_DN) ? 1 : 0;
+    }
+    mon->driver = M_HOST;
+  } else {
+    if ((mon->driver != M_NONE) || (mon->pu != (d2p & D2P_PU))) {
+      if (log) {
+        if (d2p & D2P_PU) {
+          fprintf(mon->file, "mon: %8d: Idle, FS resistor (d2p 0x%x)\n",
+                  tick_bits, d2p);
+        } else {
+          fprintf(mon->file, "mon: %8d: Idle, SE0\n", tick_bits);
+        }
+      }
+      mon->driver = M_NONE;
+      mon->pu = (d2p & D2P_PU);
+    }
+    mon->line = 0;
+    return;
+  }
+  // If the DN pullup is there then swap
+  if (d2p & D2P_DNPU) {
+    int tmp = dp;
+    dp = dn;
+    dn = tmp;
+  }
+  // Collect D+/D- state
+  mon->line = (mon->line << 2) | dp << 1 | dn;
+
+  // SYNC at start of packet
+  if (mon->state == MS_IDLE) {
+    if ((mon->line & 0xfff) == ((DK << 10) | (DJ << 8) | (DK << 6) | (DJ << 4) |
+                                (DK << 2) | (DK << 0))) {
+      if (log) {
+        fprintf(mon->file, "mon: %8d: (%c) SOP\n", tick_bits,
+                mon->driver == M_HOST ? 'H' : 'D');
+      }
+      mon->sopAt = tick_bits;
+      mon->state = MS_GET_PID;
+      mon->needbits = 8;
+      data_callback(mon, UsbMon_DataType_Sync, 0U);
+    }
+    return;
+  }
+
+  // EOP detection, calculate and check the CRC16 on any data field
+  if ((mon->line & 0x3f) == ((SE0 << 4) | (SE0 << 2) | (DJ << 0))) {
+    if ((log || compact) && (mon->state == MS_GET_BYTES) && (mon->byte > 0)) {
+      uint32_t pkt_crc16, comp_crc16;
+
+      if (compact && mon->byte == 2) {
+        fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
+                mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
+                pid_2data(mon->lastpid, mon->bytes[0], mon->bytes[1]));
+      } else if (compact && mon->byte == 1) {
+        fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s %02x EOP\n",
+                mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
+                decode_pid(mon->lastpid), mon->bytes[0]);
+      } else {
+        if (compact) {
+          fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s, EOP\n",
+                  mon->sopAt, tick_bits, mon->driver == M_HOST ? 'H' : 'D',
+                  decode_pid(mon->lastpid));
+        }
+        fprintf(mon->file, "mon:     %s:\n",
+                mon->driver == M_HOST ? "h->d" : "d->h");
+        comp_crc16 = CRC16(mon->bytes, mon->byte - 2);
+        pkt_crc16 =
+            mon->bytes[mon->byte - 2] | (mon->bytes[mon->byte - 1] << 8);
+
+        dump_bytes(mon->file, "mon:          ", mon->bytes, mon->byte - 2, 0u);
+
+        // Display the received CRC16 value
+        fprintf(mon->file, "\nmon:          (CRC16 %02x %02x",
+                mon->bytes[mon->byte - 2], mon->bytes[mon->byte - 1]);
+        if (comp_crc16 == pkt_crc16) {
+          fprintf(mon->file, "%s OK)\n",
+                  (mon->byte == MON_BYTES_SIZE) ? "..." : "");
+        } else {
+          fprintf(mon->file,
+                  "%s BAD)\nmon:           CRC16 %04x BAD expected %04x\n",
+                  (mon->byte == MON_BYTES_SIZE) ? "..." : "", pkt_crc16,
+                  comp_crc16);
+        }
+      }
+    } else if (compact) {
+      fprintf(mon->file, "mon: %8d -- %8d: (%c) SOP, PID %s EOP\n", mon->sopAt,
+              tick_bits, mon->driver == M_HOST ? 'H' : 'D',
+              decode_pid(mon->lastpid));
+    }
+    if (log) {
+      fprintf(mon->file, "mon: %8d: (%c) EOP\n", tick_bits,
+              mon->driver == M_HOST ? 'H' : 'D');
+    }
+    mon->state = MS_IDLE;
+    data_callback(mon, UsbMon_DataType_EOP, 0U);
+    return;
+  }
+
+  int newbit = (((mon->line & 0xc) >> 2) == (mon->line & 0x3)) ? 1 : 0;
+  mon->rawbits = (mon->rawbits << 1) | newbit;
+  if ((mon->rawbits & 0x7e) == 0x7e) {
+    if (newbit == 1) {
+      fprintf(mon->file, "mon: %8d: (%c) Bitstuff error, got 1 after 0x%x\n",
+              tick_bits, mon->driver == M_HOST ? 'H' : 'D', mon->rawbits);
+    }
+    /* Ignore bit stuff bit */
+    return;
+  }
+  mon->bits = (mon->bits >> 1) | (newbit << 7);
+  mon->needbits--;
+  if (mon->needbits) {
+    return;
+  }
+
+  // Complete byte received
+  switch (mon->state) {
+    case MS_GET_PID: {
+      // Any byte for which the upper nibble is not the exact complement
+      // of the lower nibble is invalid
+      uint8_t pid = (uint8_t)mon->bits;
+      if (((pid ^ 0xf0) >> 4) ^ (pid & 0x0f)) {
+        if (log) {
+          fprintf(mon->file, "mon: %8d: (%c) BAD PID 0x%x\n", tick_bits,
+                  mon->driver == M_HOST ? 'H' : 'D', pid);
+        }
+      } else {
+        *lastpid = pid;
+        mon->lastpid = pid;
+        if (log) {
+          fprintf(mon->file, "mon: %8d: (%c) PID %s (0x%x)\n", tick_bits,
+                  mon->driver == M_HOST ? 'H' : 'D', decode_pid(pid), pid);
+        }
+      }
+      mon->state = MS_GET_BYTES;
+      mon->needbits = 8;
+      mon->byte = 0;
+      data_callback(mon, UsbMon_DataType_PID, pid);
+    } break;
+
+    case MS_GET_BYTES: {
+      uint8_t d = (uint8_t)mon->bits;
+      mon->bytes[mon->byte] = d;
+      mon->needbits = 8;
+      if (mon->byte < MON_BYTES_SIZE) {
+        mon->byte++;
+      }
+      data_callback(mon, UsbMon_DataType_Byte, d);
+    } break;
+
+    case MS_IDLE:
+      break;
+
+    default:
+      assert(!"Unknown/undefined USB monitor state");
+      break;
+  }
+}
+
+// Export some internal diagnostic state for visibility in waveforms
+uint32_t usb_monitor_diags(usb_monitor_ctx_t *mon) {
+  // Show the PID most recently detected
+  uint32_t diags = mon->lastpid;
+
+  // Show the byte most recently received
+  if (mon->byte > 0 && mon->byte <= MON_BYTES_SIZE)
+    diags |= (mon->bytes[mon->byte - 1]) << 8;
+
+  // Show the down counting of bits required
+  diags |= (mon->needbits << 16);
+
+  // Monitor state number
+  diags |= mon->state << 20;
+
+  return diags;
+}
diff --git a/hw/dv/dpi/usbdpi/usb_monitor.h b/hw/dv/dpi/usbdpi/usb_monitor.h
new file mode 100644
index 0000000..ca33acf
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_monitor.h
@@ -0,0 +1,78 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_DPI_USBDPI_USB_MONITOR_H_
+#define OPENTITAN_HW_DV_DPI_USBDPI_USB_MONITOR_H_
+#include <stdbool.h>
+#include <stdint.h>
+
+/**
+ * USB monitor context
+ */
+typedef struct usb_monitor_ctx usb_monitor_ctx_t;
+
+// USB data callback types
+typedef enum {
+  UsbMon_DataType_Sync,
+  UsbMon_DataType_PID,
+  UsbMon_DataType_Byte,
+  UsbMon_DataType_EOP
+} usbmon_data_type_t;
+
+/**
+ * Callback function for USB data
+ */
+typedef void (*usb_monitor_data_callback_t)(void *ctx_v,
+                                            usbmon_data_type_t type, uint8_t d);
+
+/**
+ * Create and initialize a USB monitor instance
+ *
+ * @param  filename  Filename to be used for log file
+ * @param  data_cb   USB data callback function
+ * @param  data_ctx  Context for data callback
+ * @return           USB monitor context
+ */
+usb_monitor_ctx_t *usb_monitor_init(const char *filename,
+                                    usb_monitor_data_callback_t data_cb,
+                                    void *data_ctx);
+
+/**
+ * Finalize a USB monitor
+ *
+ * @param mon        USB monitor context
+ */
+void usb_monitor_fin(usb_monitor_ctx_t *mon);
+
+/**
+ * Append a formatted message to the USB monitor log file
+ *
+ * @param mon        USB monitor context
+ * @param fmt        Format specifier for message
+ */
+void usb_monitor_log(usb_monitor_ctx_t *mon, const char *fmt, ...);
+
+/**
+ * Per-cycle monitoring of the USB
+ *
+ * @param mon        USB monitor context
+ * @param loglevel   Level of logging information required
+ * @param tick_bits  Elapsed simulation time in USB bit intervals (12Mbps)
+ * @param hdrive     Indicates whether the host is driving the bus
+ * @param d2p        Signals from USBDEV to DPI model
+ * @param p2d        Signals from DPI model to USBDEV
+ * @param lastpid    Receives the most-recently decoded PID (either direction)
+ */
+void usb_monitor(usb_monitor_ctx_t *mon, int log, uint32_t tick_bits,
+                 bool hdrive, uint32_t p2d, uint32_t d2p, uint8_t *lastpid);
+
+/**
+ * Export diagnostic state for waveform viewing
+ *
+ * @param mon        USB monitor context
+ * @return           Diagnostic state information (see usbdpi.sv)
+ */
+uint32_t usb_monitor_diags(usb_monitor_ctx_t *mon);
+
+#endif  // OPENTITAN_HW_DV_DPI_USBDPI_USB_MONITOR_H_
diff --git a/hw/dv/dpi/usbdpi/usb_transfer.c b/hw/dv/dpi/usbdpi/usb_transfer.c
new file mode 100644
index 0000000..6025201
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_transfer.c
@@ -0,0 +1,214 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <assert.h>
+#include <string.h>
+
+#include "usb_utils.h"
+#include "usbdpi.h"
+
+// Set up all of the available transfer descriptors
+void usb_transfer_setup(usbdpi_ctx_t *ctx) {
+  usbdpi_transfer_t *next = NULL;
+  int idx = (int)USBDPI_MAX_TRANSFERS;
+  while (--idx >= 0) {
+    ctx->transfer_pool[idx].next = next;
+    next = &ctx->transfer_pool[idx];
+  }
+  ctx->free = next;
+}
+
+// Allocate and initialize a transfer descriptor
+usbdpi_transfer_t *transfer_alloc(usbdpi_ctx_t *ctx) {
+  usbdpi_transfer_t *transfer = ctx->free;
+  if (transfer) {
+    ctx->free = transfer->next;
+    transfer_init(transfer);
+  }
+  return transfer;
+}
+
+// Free a transfer descriptor
+void transfer_release(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer) {
+  // Prepend this transfer descriptor to the list of free descriptors
+  transfer->next = ctx->free;
+  ctx->free = transfer;
+}
+
+// Initialize a transfer descriptor
+void transfer_init(usbdpi_transfer_t *transfer) {
+  // Not within a linked list
+  transfer->next = NULL;
+
+  // Initially content free
+  transfer->num_bytes = 0U;
+  transfer->data_start = USBDPI_NO_DATA_STAGE;
+}
+
+// Prepare and send a 'Start Of Frame' transfer descriptor
+void transfer_frame_start(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                          unsigned frame) {
+  // 11 bits of the frame number are used, and they are encoded in the same
+  // manner as the device address and endpoint number of other packets
+  transfer_token(transfer, USB_PID_SOF, frame & 0x7FU, (frame >> 7) & 15);
+  transfer_send(ctx, transfer);
+}
+
+// Construct and send the setup stage of a control transfer
+void transfer_setup(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                    uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue,
+                    uint16_t wIndex, uint16_t wLength) {
+  transfer_token(transfer, USB_PID_SETUP, ctx->dev_address, ENDPOINT_ZERO);
+
+  uint8_t *dp = transfer_data_start(transfer, USB_PID_DATA0, 8U);
+  dp[0] = bmRequestType;
+  dp[1] = bRequest;
+  dp[2] = (uint8_t)wValue;
+  dp[3] = (uint8_t)(wValue >> 8);
+  dp[4] = (uint8_t)wIndex;
+  dp[5] = (uint8_t)(wIndex >> 8);
+  dp[6] = (uint8_t)wLength;
+  dp[7] = (uint8_t)(wLength >> 8);
+  transfer_data_end(transfer, dp + 8);
+
+  // Send the transfer descriptor
+  transfer_send(ctx, transfer);
+  ctx->bus_state = kUsbControlSetup;
+  ctx->wait = USBDPI_TIMEOUT(ctx, USBDPI_INTERVAL_SETUP_STAGE);
+  ctx->hostSt = HS_WAITACK;
+}
+
+// Append a token packet to a transfer descriptor that is under construction
+void transfer_token(usbdpi_transfer_t *transfer, uint8_t pid, uint8_t device,
+                    uint8_t endpoint) {
+  assert(device < 0x80U);
+  assert(endpoint < 0x10U);
+
+  transfer_init(transfer);
+
+  assert(transfer->num_bytes <= sizeof(transfer->data) - 3);
+  uint8_t *dp = &transfer->data[transfer->num_bytes];
+
+  dp[0] = pid;
+  dp[1] = (uint8_t)(device | (endpoint << 7));
+  dp[2] =
+      (uint8_t)((endpoint >> 1) | (CRC5((endpoint << 7) | device, 11) << 3));
+
+  transfer->num_bytes += 3U;
+}
+
+// Append a data stage to the given transfer descriptor, using the specified PID
+//   (DATA0|DATA1)  and checking that there is sufficient space available
+//   (est_size)
+uint8_t *transfer_data_start(usbdpi_transfer_t *transfer, uint8_t pid,
+                             unsigned est_size) {
+  // TODO - we should at this point check that we are properly compliant with
+  //        DATA0|DATA1 signalling too!
+  //        unless of course we are deliberately being non-compliant
+  assert(pid == USB_PID_DATA0 || pid == USB_PID_DATA1);
+
+  // If an estimated size has been speciifed, check whether there's sufficient
+  // space
+  unsigned data_start = transfer->num_bytes;
+  if (est_size > sizeof(transfer->data) - 1U - data_start) {
+    assert(!"transfer_data_start space check failed");
+    return NULL;
+  }
+
+  // Retain the offset of the PID (not the data field itself) for the
+  // calculation of the CRC16 and for the signalling transition (EOP) when we
+  // come to send this...
+  transfer->data_start = data_start;
+  uint8_t *dp = &transfer->data[data_start];
+  *dp++ = pid;
+  transfer->num_bytes++;
+
+  // We return a pointer to where the data stage shall be constructed, and the
+  // caller shall pass the advanced pointer to tranfer_data_end() to ensure
+  // correct completion of the data stage
+  return dp;
+}
+
+// Conclude a data stage within a transfer, calculating and appending the CRC16
+// of the data field
+void transfer_data_end(usbdpi_transfer_t *transfer, uint8_t *dp) {
+  assert(transfer->data_start != USBDPI_NO_DATA_STAGE);
+  assert(dp >= transfer->data);
+
+  // Check that we still have space to append the CRC16
+  assert(dp < transfer->data + sizeof(transfer->data) - 2);
+
+  // Note: historically the datastart field has pointed to the PID rather than
+  //       the data field itself
+  const uint8_t *data_field = &transfer->data[transfer->data_start + 1U];
+  uint32_t crc = CRC16(data_field, dp - data_field);
+  dp[0] = (uint8_t)crc;
+  dp[1] = (uint8_t)(crc >> 8);
+
+  // Update the count of bytes in the transfer
+  transfer->num_bytes = (dp + 2) - transfer->data;
+}
+
+// Append some data to a transfer description
+bool transfer_append(usbdpi_transfer_t *transfer, const uint8_t *dp, size_t n) {
+  unsigned num_bytes = transfer->num_bytes;
+
+  // Check that we still have space to append this data
+  if (sizeof(transfer->data) - num_bytes < n) {
+    return false;
+  }
+  memcpy(&transfer->data[num_bytes], dp, n);
+  transfer->num_bytes = num_bytes + n;
+  return true;
+}
+
+// Prepare a transfer for transmission to the device, and get ready to transmit
+void transfer_send(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer) {
+  // Validate the transfer properties
+  assert(transfer->num_bytes > 0U &&
+         transfer->num_bytes <= sizeof(transfer->data));
+
+  // Set this as the current in-progress transfer
+  ctx->sending = transfer;
+
+  // Prepare for transmission of the first byte, following the implicit SYNC
+  ctx->state = ST_SYNC;
+  ctx->byte = 0;
+  ctx->bit = 1;
+}
+
+// Construct and prepare to send a Status response;
+// Note: there is no requirement to call either transfer_init() or
+//       transfer_send() when using transfer_status()
+void transfer_status(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                     uint8_t pid) {
+  // The single byte Status stage carries just the PID
+  transfer->num_bytes = 1U;
+  transfer->data[0] = pid;
+
+  transfer->data_start = USBDPI_NO_DATA_STAGE;
+
+  // Set this as the current in-progress transfer
+  ctx->sending = transfer;
+
+  // Prepare for transmission of the first byte, following the implicit SYNC
+  ctx->state = ST_SYNC;
+  ctx->byte = 0;
+  ctx->bit = 1;
+}
+
+// Diagnostic utility function to dump out the contents of a transfer descriptor
+void transfer_dump(usbdpi_transfer_t *transfer, FILE *out) {
+  fprintf(out, "[usbdpi] Transfer descriptor at %p\n", transfer);
+  fprintf(out, "[usbdpi] num_bytes 0x%x data_start 0x%x\n", transfer->num_bytes,
+          transfer->data_start);
+  dump_bytes(out, "[usbdpi] ", transfer->data, transfer->num_bytes, 0U);
+}
+
+// `extern` declarations to give the inline functions in the
+// corresponding header a link location.
+
+extern uint8_t *transfer_data_field(usbdpi_transfer_t *transfer);
+extern uint8_t transfer_data_pid(usbdpi_transfer_t *transfer);
+extern uint32_t transfer_length(const usbdpi_transfer_t *transfer);
diff --git a/hw/dv/dpi/usbdpi/usb_transfer.h b/hw/dv/dpi/usbdpi/usb_transfer.h
new file mode 100644
index 0000000..895c978
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_transfer.h
@@ -0,0 +1,223 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_DPI_USBDPI_USB_TRANSFER_H_
+#define OPENTITAN_HW_DV_DPI_USBDPI_USB_TRANSFER_H_
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+// USBDEV IP block supports up to 64-bytes/packet
+#ifndef USBDEV_MAX_PACKET_SIZE
+#define USBDEV_MAX_PACKET_SIZE 64U
+#endif
+
+// Maximal length of a byte sequence to be transmitted/receive without
+// interruption or pause (Start Of Frame, Setup packet, OUT Data packet
+// and End Of Frame all back-to-back?)
+#define USBDPI_MAX_DATA (3U + 3U + 1U + USBDEV_MAX_PACKET_SIZE + 2U + 1U)
+
+// Special value that denotes that this transfer does not include a data stage
+#define USBDPI_NO_DATA_STAGE ((uint8_t)~0U)
+
+/**
+ * Forwards reference to usbpdi state context
+ */
+typedef struct usbdpi_ctx usbdpi_ctx_t;
+
+/**
+ * Description of a transfer over the USB
+ *
+ * A control transfer
+ *    Setup stage - [ Data stage ] - Status stage
+ * A data transfer
+ *    Setup stage - Data stage - [ Status stage ]
+ */
+typedef struct usbdpi_transfer usbdpi_transfer_t;
+
+struct usbdpi_transfer {
+  /**
+   * Received transfers are linked together in the order of receipt
+   */
+  usbdpi_transfer_t *next;
+  /**
+   * Number of bytes to be transmitted/received
+   */
+  uint8_t num_bytes;
+  /**
+   * Offset of the PID of the data stage (or USBDPI_NO_DATA_STAGE if none)
+   */
+  uint8_t data_start;
+  /**
+   * Bytes being transferred (Note: this includes PID and CRCs; it is _not_ just
+   * the data field)
+   */
+  uint8_t data[USBDPI_MAX_DATA];
+};
+
+/**
+ * Set up all of the available transfer descriptors in a USB DPI context
+ *
+ * @param  ctx       USB DPI context
+ */
+void usb_transfer_setup(usbdpi_ctx_t *ctx);
+
+/**
+ * Allocate and initialize a transfer descriptor
+ *
+ * @param  ctx       USB DPI context
+ */
+usbdpi_transfer_t *transfer_alloc(usbdpi_ctx_t *ctx);
+
+/**
+ * Free a transfer descriptor and return it to the pool
+ *
+ * @param  ctx       USB DPI context
+ * @param  transfer  Transfer descriptor to be released
+ */
+void transfer_release(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer);
+
+/**
+ * Initialize a transfer descriptor for use
+ *
+ * @param  transfer  Transfer descriptor to be initialized
+ */
+void transfer_init(usbdpi_transfer_t *transfer);
+
+/**
+ * Prepare and send a 'Start Of Frame' transfer descriptor
+ *
+ * @param  ctx       USB DPI context
+ * @param  transfer  Transfer descriptor
+ * @param  frame     Frame number to be sent
+ */
+void transfer_frame_start(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                          unsigned frame);
+
+/**
+ * Construct and send the setup stage of a control transfer
+ *
+ * @param  ctx           USB DPI context
+ * @param  transfer      Transfer descriptor
+ * @param  bmRequestType Characteristics of USB device request
+ * @param  bRequest      Specific device request
+ * @param  wValue        Word-sized field that varies according to the request
+ * @param  WIndex        Typically used to pass an index or offset
+ * @param  wLength       Number of bytes to transfer if there is a Data stage
+ */
+void transfer_setup(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                    uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue,
+                    uint16_t wIndex, uint16_t wLength);
+
+/**
+ * Append a token packet to a transfer descriptor that is under construction
+ *
+ * @param  transfer  Transfer descriptor
+ * @param  pid       Packet IDentifier of token packet
+ * @param  device    Device address of recipient
+ * @param  endpoint  Endpoint number of recipient
+ */
+void transfer_token(usbdpi_transfer_t *transfer, uint8_t pid, uint8_t device,
+                    uint8_t endpoint);
+
+/**
+ * Append a data stage to the given transfer descriptor, using the specified PID
+ * (DATA0|DATA1) and checking that there is sufficient space available
+ * (est_size)
+ *
+ * @param  transfer  Transfer descriptor under construction
+ * @param  pid       Packet IDentifier of data packet
+ * @param  est_size  Estimated size of data if known (or 0)
+ */
+uint8_t *transfer_data_start(usbdpi_transfer_t *transfer, uint8_t pid,
+                             unsigned est_size);
+
+/**
+ * Conclude a data stage within a transfer, calculating and appending the CRC16
+ * of the data field
+ *
+ * @param  transfer  Transfer descriptor under construction
+ * @param  dp        Pointer beyond the data field within the transfer
+ */
+void transfer_data_end(usbdpi_transfer_t *transfer, uint8_t *dp);
+
+/**
+ * Return access to the data field of a transfer descriptor
+ *
+ * @param  transfer  Transfer descriptor
+ * @return           Pointer to the start of the data field or NULL
+ */
+inline uint8_t *transfer_data_field(usbdpi_transfer_t *transfer) {
+  assert(transfer);
+  if (transfer->data_start != USBDPI_NO_DATA_STAGE &&
+      transfer->data_start + 1U < transfer->num_bytes) {
+    return &transfer->data[transfer->data_start + 1U];
+  }
+  return NULL;
+}
+
+/**
+ * Return DATAx PID of the data field of a transfer descriptor
+ *
+ * @param  transfer  Transfer descriptor
+ * @return           USB_PID_DATA0|1
+ */
+inline uint8_t transfer_data_pid(usbdpi_transfer_t *transfer) {
+  assert(transfer);
+  assert(transfer->data_start != USBDPI_NO_DATA_STAGE &&
+         transfer->data_start + 1U < transfer->num_bytes);
+  return transfer->data[transfer->data_start];
+}
+
+/**
+ * Append some data to a transfer description
+ *
+ * @param  transfer  Transfer descriptor under construction
+ * @param  dp        Pointer to data bytes to be appended
+ * @param  n         Number of bytes
+ * @return           true iff appended successfully
+ */
+bool transfer_append(usbdpi_transfer_t *transfer, const uint8_t *dp, size_t n);
+
+/**
+ * Prepare a transfer for transmission to the device, and get ready to transmit
+ *
+ * @param  ctx       USB DPI context
+ * @param  transfer  Transfer descriptor to be sent to device
+ */
+void transfer_send(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer);
+
+/**
+ * Construct and prepare to send a Status response;
+ *
+ * Note: there is no requirement to call either transfer_init() or
+ *       transfer_send() when using transfer_status()
+ *
+ * @param  ctx       USB DPI context
+ * @param  transfer  Transfer descriptor to be completed and sent
+ * @param  pid       Packet IDentifier of status packet
+ */
+void transfer_status(usbdpi_ctx_t *ctx, usbdpi_transfer_t *transfer,
+                     uint8_t pid);
+
+/**
+ * Returns the total number of bytes to be transferred
+ *
+ * @param  transfer  Transfer descriptor
+ * @return           Number of bytes
+ */
+
+inline uint32_t transfer_length(const usbdpi_transfer_t *transfer) {
+  return transfer->num_bytes;
+}
+
+/**
+ * Diagnostic utility function to dump out the contents of a transfer descriptor
+ *
+ * @param  transfer  Transfer descriptor
+ * @param  out       Stream to receive diagnostic output
+ */
+void transfer_dump(usbdpi_transfer_t *transfer, FILE *out);
+
+#endif  // OPENTITAN_HW_DV_DPI_USBDPI_USB_TRANSFER_H_
diff --git a/hw/dv/dpi/usbdpi/usb_utils.c b/hw/dv/dpi/usbdpi/usb_utils.c
new file mode 100644
index 0000000..0b69de1
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_utils.c
@@ -0,0 +1,82 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "usb_utils.h"
+
+#include <stdio.h>
+
+static const char *pid_names[] = {
+    "Rsvd",     //         0000b
+    "OUT",      //         0001b
+    "ACK",      //         0010b
+    "DATA0",    //         0011b
+    "PING",     //         0100b
+    "SOF",      //         0101b
+    "NYET",     //         0110b
+    "DATA2",    //         0111b
+    "SPLIT",    //         1000b
+    "IN",       //         1001b
+    "NAK",      //         1010b
+    "DATA1",    //         1011b
+    "PRE/ERR",  //         1100b
+    "SETUP",    //         1101b
+    "STALL",    //         1110b
+    "MDATA"     //         1111b
+};
+
+// Decode a PID and return its textual name, iff valid
+const char *decode_pid(uint8_t pid) {
+  // Valid PIDs are formed from two complementary nibbles
+  return (((pid ^ 0xf0u) >> 4) ^ (pid & 0xfu)) ? "???" : pid_names[pid & 0xfu];
+}
+
+// Dump a sequence of bytes as hexadecimal and AsCII for diagnostic purposes
+void dump_bytes(FILE *out, const char *prefix, const uint8_t *data, size_t n,
+                uint32_t flags) {
+  static const char hex_digits[] = "0123456789abcdef";
+  const unsigned ncols = 0x10u;
+  char buf[ncols * 4u + 2u];
+
+  if (!prefix) {
+    prefix = "";
+  }
+
+  // Note: we have no generic printf functionality and must use LOG_INFO()
+  while (n > 0u) {
+    const unsigned chunk = (n > ncols) ? ncols : (unsigned)n;
+    const uint8_t *row = data;
+    unsigned idx = 0u;
+    char *dp = buf;
+
+    // Columns of hexadecimal bytes
+    while (idx < chunk) {
+      dp[0] = hex_digits[row[idx] >> 4];
+      dp[1] = hex_digits[row[idx++] & 0xfu];
+      dp[2] = ' ';
+      dp += 3;
+    }
+    while (idx++ < ncols) {
+      dp[2] = dp[1] = dp[0] = ' ';
+      dp += 3;
+    }
+
+    // Printable ASCII characters
+    for (unsigned idx = 0u; idx < chunk; idx++) {
+      char ch = row[idx];
+      *dp++ = (ch < ' ' || ch >= 0x7f) ? '.' : ch;
+    }
+    *dp = '\0';
+    fprintf(out, "%s%s\n", prefix, buf);
+    data += chunk;
+    n -= chunk;
+  }
+}
+
+// `extern` declarations to give the inline functions in the
+// corresponding header a link location.
+
+extern uint16_t get_le16(const uint8_t *dp);
+extern uint32_t get_le32(const uint8_t *dp);
+extern uint8_t *set_le16(uint8_t *dp, uint16_t n);
+extern uint8_t *set_le32(uint8_t *dp, uint32_t n);
diff --git a/hw/dv/dpi/usbdpi/usb_utils.h b/hw/dv/dpi/usbdpi/usb_utils.h
new file mode 100644
index 0000000..c2b4cf7
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usb_utils.h
@@ -0,0 +1,81 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_DPI_USBDPI_USB_UTILS_H_
+#define OPENTITAN_HW_DV_DPI_USBDPI_USB_UTILS_H_
+#include <stdint.h>
+#include <stdio.h>
+
+/**
+ * Decode a PID and return its textual name, iff valid
+ *
+ * @param pid        Packet IDentifier to be decoded
+ * @return           Textual name of PID (or "???")
+ */
+const char *decode_pid(uint8_t pid);
+
+/**
+ * Dump a sequence of bytes as hexadecimal and AsCII for diagnostic purposes
+ *
+ * @param file       Handle of output file
+ * @param prefix     Optional prefix for each output line
+ * @param data       First byte of data to be output
+ * @param n          Number of bytes
+ * @param flags      Formatting flags (reserved, SBZ)
+ */
+void dump_bytes(FILE *out, const char *prefix, const uint8_t *data, size_t n,
+                uint32_t flags);
+
+/**
+ * Utility function that reads a 16-bit value from a byte stream in little
+ * endian order, as per USB convention
+ *
+ * @param  dp        Pointer to first byte in stream
+ * @return           Value read
+ */
+inline uint16_t get_le16(const uint8_t *dp) { return dp[0] | (dp[1] << 8); }
+
+/**
+ * Utility function that reads a 32-bit value from a byte stream in little
+ * endian order, as per USB convention
+ *
+ * @param  dp        Pointer to first byte in stream
+ * @return           Value read
+ */
+inline uint32_t get_le32(const uint8_t *dp) {
+  return dp[0] | ((uint32_t)dp[1] << 8) | ((uint32_t)dp[2] << 16) |
+         ((uint32_t)dp[3] << 24);
+}
+
+/**
+ * Utility function that writes a 16-bit value into a byte stream in little
+ * endian order, as per USB convention
+ *
+ * @param  dp        Pointer into stream to receive first byte
+ * @param  n         Value to be written
+ * @return           Pointer beyond the bytes written
+ */
+inline uint8_t *set_le16(uint8_t *dp, uint16_t n) {
+  dp[0] = (uint8_t)n;
+  dp[1] = (uint8_t)(n >> 8);
+  return dp + 2;
+}
+
+/**
+ * Utility function that writes a 32-bit value into a byte stream in little
+ * endian order, as per USB convention
+ *
+ * @param  dp        Pointer into stream to receive first byte
+ * @param  n         Value to be written
+ * @return           Pointer beyond the bytes written
+ */
+inline uint8_t *set_le32(uint8_t *dp, uint32_t n) {
+  dp[0] = (uint8_t)n;
+  dp[1] = (uint8_t)(n >> 8);
+  dp[2] = (uint8_t)(n >> 16);
+  dp[3] = (uint8_t)(n >> 24);
+  return dp + 4;
+}
+
+#endif  // OPENTITAN_HW_DV_DPI_USBDPI_USB_UTILS_H_
diff --git a/hw/dv/dpi/usbdpi/usbdpi.c b/hw/dv/dpi/usbdpi/usbdpi.c
index 984bbbf..3ccfcb3 100644
--- a/hw/dv/dpi/usbdpi/usbdpi.c
+++ b/hw/dv/dpi/usbdpi/usbdpi.c
@@ -4,16 +4,11 @@
 
 #include "usbdpi.h"
 
-#ifdef __linux__
-#include <pty.h>
-#elif __APPLE__
-#include <util.h>
-#endif
-
 #include <assert.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -21,43 +16,101 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-// Historically the simulation started too fast to connect to all
-// the fifos and terminals without loss of output. So a delay was added.
-// Today the startup is slow enough this does not seem to be needed.
-// In case things change again Im going to leave this behind a define
-// for now, but if this continues not to be needed the code can be deleted.
-// Uncomment next line if you need the delay
-// #define NEED_SLEEP
+#include "usb_utils.h"
+#include "usbdpi_test.h"
 
+// Indexed directly by ctx->state (ST_)
 static const char *st_states[] = {"ST_IDLE 0", "ST_SEND 1", "ST_GET 2",
                                   "ST_SYNC 3", "ST_EOP 4",  "ST_EOP0 5"};
 
+// Indexed directly by ct-x>hostSt (HS_)
 static const char *hs_states[] = {
     "HS_STARTFRAME 0", "HS_WAITACK 1",   "HS_SET_DATASTAGE 2", "HS_DS_RXDATA 3",
     "HS_DS_SENDACK 4", "HS_DONEDADR 5",  "HS_REQDATA 6",       "HS_WAITDATA 7",
     "HS_SENDACK 8",    "HS_WAIT_PKT 9",  "HS_ACKIFDATA 10",    "HS_SENDHI 11",
-    "HS_EMPTYDATA 12", "HS_WAITACK2 13", "HS_NEXTFRAME 14"};
+    "HS_EMPTYDATA 12", "HS_WAITACK2 13", "HS_STREAMOUT 14",    "HS_STREAMIN 15",
+    "HS_NEXTFRAME 16"};
 
+static const char *decode_usb[] = {"SE0", "0-K", "1-J", "SE1"};
+
+// Optionally invert the signals the host is driving, according to bus
+// configuration
+static uint32_t inv_driving(usbdpi_ctx_t *ctx, uint32_t d2p);
+
+// Request IN transfer. Get back NAK or DATA0/DATA1.
+static void pollRX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool send_hi,
+                   bool nak_data);
+// Get Baud (Vendor-specific)
+static void readBaud(usbdpi_ctx_t *ctx, uint8_t endpoint);
+
+// Get Test Configuration (Vendor-specific)
+static void getTestConfig(usbdpi_ctx_t *ctx, uint16_t desc_len);
+
+// Get Descriptor
+static void getDescriptor(usbdpi_ctx_t *ctx, uint8_t desc_type,
+                          uint8_t desc_idx, uint16_t desc_len);
+// Set Baud (Vendor-specific)
+static void setBaud(usbdpi_ctx_t *ctx, uint8_t endpoint);
+
+// Set device address (with null data stage)
+static void setDeviceAddress(usbdpi_ctx_t *ctx, uint8_t dev_addr);
+
+// Set device configuration
+static void setDeviceConfiguration(usbdpi_ctx_t *ctx, uint8_t config);
+
+// Set test status, reporting progress/success/failure (Vendor-specific)
+static void setTestStatus(usbdpi_ctx_t *ctx, uint32_t status, const char *msg);
+
+// Change DP and DN outputs from host
+static uint32_t set_driving(usbdpi_ctx_t *ctx, uint32_t d2p, uint32_t newval);
+
+// Try to send OUT transfer. Optionally expect Status packet (eg. ACK|NAK) in
+// response
+static void tryTX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool expect_status);
+
+// Test the ischronous transfers (without ACKs)
+// static void testIso(usbdpi_ctx_t *ctx);
+#define testIso(ctx) tryTX((ctx), ENDPOINT_ISOCHRONOUS, false)
+
+// Callback for USB data detection
+static void usbdpi_data_callback(void *ctx_v, usbmon_data_type_t type,
+                                 uint8_t d);
+
+/**
+ * Create a USB DPI instance, returning a 'chandle' for later use
+ */
 void *usbdpi_create(const char *name, int loglevel) {
-  struct usbdpi_ctx *ctx =
-      (struct usbdpi_ctx *)calloc(1, sizeof(struct usbdpi_ctx));
+  // Use calloc for zero-initialisation
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)calloc(1, sizeof(usbdpi_ctx_t));
   assert(ctx);
 
-  ctx->state = kUsbIdle;
-  ctx->tick = 0;
-  ctx->frame = 0;
-  ctx->framepend = 0;
-  ctx->lastframe = 0;
-  ctx->last_pu = 0;
-  ctx->inframe = 4;
+  // Note: calloc has initialized most of the fields for us
+  // ctx->tick = 0;
+  // ctx->tick_bits = 0;
+  // ctx->frame = 0;
+  // ctx->framepend = 0;
+  // ctx->frame_start = 0;
+  // ctx->last_pu = 0;
+  // ctx->driving = 0;
+  // ctx->baudrate_set_successfully = 0;
+
+  // Initialize state for each endpoint and direction
+  for (unsigned ep = 0U; ep < USBDPI_MAX_ENDPOINTS; ep++) {
+    // First DATAx received shall be DATA0
+    ctx->ep_in[ep].next_data = USB_PID_DATA0;
+
+    // First DATAx transmitted shall be DATA0 because it must follow a SETUP
+    // transaction
+    ctx->ep_out[ep].next_data = USB_PID_DATA0;
+  }
+
   ctx->state = ST_IDLE;
-  ctx->driving = 0;
   ctx->hostSt = HS_NEXTFRAME;
   ctx->loglevel = loglevel;
-  ctx->mon = monitor_usb_init();
-  ctx->baudrate_set_successfully = 0;
 
-  char cwd[PATH_MAX];
+  ctx->step = STEP_BUS_RESET;
+
+  char cwd[FILENAME_MAX];
   char *cwd_rv;
   const char *test_out_dir = getenv("TEST_UNDECLARED_OUTPUTS_DIR");
   if (test_out_dir) {
@@ -67,72 +120,26 @@
   }
   assert(cwd_rv != NULL);
 
-  int rv;
-
   // Monitor log file
-  rv = snprintf(ctx->mon_pathname, PATH_MAX, "%s/%s.log", cwd, name);
-  assert(rv <= PATH_MAX && rv > 0);
-  ctx->mon_file = fopen(ctx->mon_pathname, "w");
-  if (ctx->mon_file == NULL) {
-    fprintf(stderr, "USB: Unable to open monitor file at %s: %s\n",
-            ctx->mon_pathname, strerror(errno));
-    return NULL;
-  }
-  // more useful for tail -f
-  setlinebuf(ctx->mon_file);
-  printf(
-      "\nUSB: Monitor output file created at %s. Works well with tail:\n"
-      "$ tail -f %s\n",
-      ctx->mon_pathname, ctx->mon_pathname);
+  int rv = snprintf(ctx->mon_pathname, FILENAME_MAX, "%s/%s.log", cwd, name);
+  assert(rv <= FILENAME_MAX && rv > 0);
+
+  ctx->mon = usb_monitor_init(ctx->mon_pathname, usbdpi_data_callback, ctx);
+
+  // Prepare the transfer descriptors for use
+  usb_transfer_setup(ctx);
 
   return (void *)ctx;
 }
 
-const char *decode_usb[] = {"SE0", "0-K", "1-J", "SE1"};
-
 void usbdpi_device_to_host(void *ctx_void, const svBitVecVal *usb_d2p) {
-  struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void;
   assert(ctx);
-  int d2p = usb_d2p[0];
-  int dp, dn;
-  int n;
-  char obuf[MAX_OBUF];
 
-  char raw_str[D2P_BITS + 1];
-  {
-    int i;
-    for (i = 0; i < D2P_BITS; i++) {
-      raw_str[D2P_BITS - i - 1] = d2p & (1 << i) ? '1' : '0';
-    }
-  }
-  raw_str[D2P_BITS] = 0;
-
-  if (d2p & (D2P_DP_EN | D2P_DN_EN | D2P_D_EN)) {
-    if (ctx->state == ST_SEND) {
-      printf("USB: %4x %8d error state %s hs %s and device drives\n",
-             ctx->frame, ctx->tick, st_states[ctx->state],
-             hs_states[ctx->hostSt]);
-    }
-    ctx->state = ST_GET;
-  } else {
-    if (ctx->state == ST_GET) {
-      ctx->state = ST_IDLE;
-    }
-  }
-
-  if ((d2p & D2P_DNPU) && (d2p & D2P_DPPU)) {
-    printf("USB: %4x %8d error both pullups are driven\n", ctx->frame,
-           ctx->tick);
-  }
-  if ((d2p & D2P_PU) != ctx->last_pu) {
-    n = snprintf(obuf, MAX_OBUF, "%4x %8d Pullup change to %s%s%s\n",
-                 ctx->frame, ctx->tick, (d2p & D2P_DPPU) ? "DP Pulled up " : "",
-                 (d2p & D2P_DNPU) ? "DN Pulled up " : "",
-                 (d2p & D2P_TX_USE_D_SE0) ? "SingleEnded" : "Differential");
-    ssize_t written = fwrite(obuf, sizeof(char), (size_t)n, ctx->mon_file);
-    assert(written == n);
-    ctx->last_pu = d2p & D2P_PU;
-  }
+  // Ascertain the state of the D+/D- signals from the device
+  // TODO - migrate to a simple function
+  uint32_t d2p = usb_d2p[0];
+  unsigned dp, dn;
   if (d2p & D2P_TX_USE_D_SE0) {
     // Single-ended mode uses D and SE0
     if (d2p & D2P_D_EN) {
@@ -151,7 +158,7 @@
   } else {
     // Normal D+/D- mode
     if (d2p & D2P_DNPU) {
-      // DN pullup would say DP and DN are swapped
+      // Assertion of DN pullup suggests DP and DN are swapped
       dp = ((d2p & D2P_DN_EN) && (d2p & D2P_DN)) ||
            (!(d2p & D2P_DN_EN) && (d2p & D2P_DNPU));
       dn = (d2p & D2P_DP_EN) && (d2p & D2P_DP);
@@ -163,20 +170,84 @@
     }
   }
 
+  // TODO - check the timing of the device responses to ensure compliance with
+  // the specification; the response time of acknowledgements, for example, has
+  // a hard real time requirement that is specified in terms of bit intervals
+  if (d2p & (D2P_DP_EN | D2P_DN_EN | D2P_D_EN)) {
+    switch (ctx->state) {
+      // Host to device transmission
+      case ST_SYNC:
+      case ST_SEND:
+      case ST_EOP:
+      case ST_EOP0:
+        printf(
+            "[usbdpi] frame 0x%x tick_bits 0x%x error state %s hs %s and "
+            "device "
+            "drives\n",
+            ctx->frame, ctx->tick_bits, st_states[ctx->state],
+            hs_states[ctx->hostSt]);
+        break;
+
+      // Device to host transmission; collect the bits
+      case ST_GET:
+        // TODO - perform bit-level decoding and packet construction here rather
+        // than relying upon usb_monitor to do that
+        // TODO - synchronize with the device transmission and check that the
+        // signals remain stable across all 4 cycles of the bit interval
+        break;
+
+      case ST_IDLE:
+        // Nothing to do
+        break;
+
+      default:
+        assert(!"Invalid/unknown state");
+        break;
+    }
+
+    ctx->state = ST_GET;
+  } else {
+    if (ctx->state == ST_GET) {
+      ctx->state = ST_IDLE;
+    }
+  }
+
+  if ((d2p & D2P_DNPU) && (d2p & D2P_DPPU)) {
+    printf("[usbdpi] frame 0x%x tick_bits 0x%x error both pullups are driven\n",
+           ctx->frame, ctx->tick_bits);
+  }
+  if ((d2p & D2P_PU) != ctx->last_pu) {
+    usb_monitor_log(ctx->mon, "0x%-3x 0x%-8x Pullup change to %s%s%s\n",
+                    ctx->frame, ctx->tick_bits,
+                    (d2p & D2P_DPPU) ? "DP Pulled up " : "",
+                    (d2p & D2P_DNPU) ? "DN Pulled up " : "",
+                    (d2p & D2P_TX_USE_D_SE0) ? "SingleEnded" : "Differential");
+
+    ctx->last_pu = d2p & D2P_PU;
+  }
+
+  // TODO - prime candidate for a function
   if (ctx->loglevel & LOG_BIT) {
+    char raw_str[D2P_BITS + 1];
+    {
+      int i;
+      for (i = 0; i < D2P_BITS; i++) {
+        raw_str[D2P_BITS - i - 1] = d2p & (1 << i) ? '1' : '0';
+      }
+    }
+    raw_str[D2P_BITS] = 0;
+
     const char *pullup = (d2p & D2P_PU) ? "PU" : "  ";
     const char *state =
         (ctx->state == ST_GET) ? decode_usb[dp << 1 | dn] : "ZZ ";
-
-    n = snprintf(obuf, MAX_OBUF, "%4x %8d %s %s %s %x\n", ctx->frame, ctx->tick,
-                 raw_str, pullup, state, d2p);
-    ssize_t written = fwrite(obuf, sizeof(char), (size_t)n, ctx->mon_file);
-    assert(written == n);
+    usb_monitor_log(ctx->mon, "0x%-3x 0x%-8x %s %s %s %x\n", ctx->frame,
+                    ctx->tick_bits, raw_str, pullup, state, d2p);
   }
 
   // Device-to-Host EOP
-  if (ctx->state == ST_GET && dp == 0 && dp == 0) {
+  if (ctx->state == ST_GET && dp == 0 && dn == 0) {
     switch (ctx->bus_state) {
+      // Control Transfers
       case kUsbControlSetup:
         ctx->bus_state = kUsbControlSetupAck;
         break;
@@ -192,58 +263,117 @@
       case kUsbControlStatusOut:
         ctx->bus_state = kUsbControlStatusOutAck;
         break;
+
+      // Bulk Transfers
       case kUsbBulkOut:
         ctx->bus_state = kUsbBulkOutAck;
         break;
       case kUsbBulkInToken:
         ctx->bus_state = kUsbBulkInData;
-      default:;
+        break;
+
+      default:
+        break;
     }
   }
 }
 
-// Note: start points to the PID which is not in the CRC
-void add_crc16(uint8_t *dp, int start, int pos) {
-  uint32_t crc = CRC16(dp + start + 1, pos - start - 1);
-  dp[pos] = crc & 0xff;
-  dp[pos + 1] = crc >> 8;
+// Callback for USB data detection
+// - the DPI host model presently does not duplicate the bit-level decoding and
+//   packet construction of the usb_monitor, so we piggyback on its decoding and
+//   trust it to be neutral.
+//
+// Note: this is invoked for any byte transferred over the USB; both
+// host-to-device and device-to-host traffic
+void usbdpi_data_callback(void *ctx_v, usbmon_data_type_t type, uint8_t d) {
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_v;
+  assert(ctx);
+  if (!ctx->recving) {
+    ctx->recving = transfer_alloc(ctx);
+  }
+
+  // We are interested only in the packets from device to host
+  if (ctx->state != ST_GET) {
+    return;
+  }
+
+  usbdpi_transfer_t *tr = ctx->recving;
+  // TODO - commute to run time error indicating buffer exhaustion
+  assert(tr);
+  if (tr) {
+    if (false) {  // ctx->loglevel & LOG_MON) {
+      printf("[usbdpi] data type %u d 0x%02x\n", type, d);
+    }
+
+    bool ok = false;
+    switch (type) {
+      case UsbMon_DataType_Sync:
+        // Initialize/rewind the received transfer
+        transfer_init(tr);
+        ok = true;
+        break;
+      case UsbMon_DataType_EOP:
+        ok = true;
+        break;
+      // Collect the PID and any subsequent data bytes
+      case UsbMon_DataType_PID:
+        switch (d) {
+          case USB_PID_DATA0:
+          case USB_PID_DATA1: {
+            // TODO - this records the start of the data field with the
+            // current transfer descriptors
+            uint8_t *dp = transfer_data_start(tr, d, 0U);
+            ok = (dp != NULL);
+          } break;
+          default:
+            ok = transfer_append(tr, &d, 1);
+            break;
+        }
+        break;
+      // Collect data field
+      case UsbMon_DataType_Byte:
+        ok = transfer_append(tr, &d, 1);
+        break;
+      default:
+        assert(!"Unknown/unhandled rx type from monitor");
+        break;
+    }
+
+    // TODO - commute to run time error indicating excessive packet length
+    assert(ok);
+  }
 }
 
 // Set device address (with null data stage)
-void setDeviceAddress(struct usbdpi_ctx *ctx) {
+void setDeviceAddress(usbdpi_ctx_t *ctx, uint8_t dev_addr) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
-      ctx->bus_state = kUsbControlSetup;
-      ctx->state = ST_SYNC;
-      ctx->bytes = 14;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      // Setup PID and data to set device 2
-      ctx->data[0] = USB_PID_SETUP;
-      ctx->data[1] = 0;
-      ctx->data[2] = 0 | CRC5(0, 11) << 3;
-      ctx->data[3] = USB_PID_DATA0;
+      // Setting device address, uses address 0 initially
+      transfer_token(tr, USB_PID_SETUP, 0, ENDPOINT_ZERO);
+
+      dp = transfer_data_start(tr, USB_PID_DATA0, 8);
       if (INSERT_ERR_PID) {
-        ctx->data[3] = 0xc4;
+        *(dp - 1) = 0xc4U;
       }
-      ctx->data[4] = 0;  // h2d, std, device
-      ctx->data[5] = 5;  // set address
-      ctx->data[6] = 2;  // device address
-      ctx->data[7] = 0;
+      dp[0] = 0;  // h2d, std, device
+      dp[1] = USB_REQ_SET_ADDRESS;
+      dp[2] = dev_addr;  // device address
+      dp[3] = 0;
       // Trigger bitstuffing, technically the device
-      // behaviour is unspecified with wIndex != 0
-      ctx->data[8] = 0xFF;  // wIndex = 0xFF00
-      ctx->data[9] = 0;
-      ctx->data[10] = 0;  // wLength = 0
-      ctx->data[11] = 0;
-      add_crc16(ctx->data, ctx->datastart, 12);
-      // ctx->data[12] = 0xEB; // pre-computed CRC16
-      // ctx->data[13] = 0x16;
+      // behavior is unspecified with wIndex != 0
+      dp[4] = 0xFF;  // wIndex = 0xFF00
+      dp[5] = 0;
+      dp[6] = 0;  // wLength = 0
+      dp[7] = 0;
+      transfer_data_end(tr, dp + 8);
       if (INSERT_ERR_CRC) {
         // Flip the last CRC bit to emulate a CRC error
-        ctx->data[13] = ctx->data[13] ^ 0x01;
+        dp[9] ^= 0x01u;
       }
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbControlSetup;
       ctx->hostSt = HS_WAITACK;
       break;
     case HS_WAITACK:
@@ -253,15 +383,9 @@
     case HS_SET_DATASTAGE:
       if (ctx->bus_state == kUsbControlSetupAck &&
           ctx->tick_bits >= ctx->wait) {
+        transfer_token(tr, USB_PID_IN, 0, 0);
+        transfer_send(ctx, tr);
         ctx->bus_state = kUsbControlStatusInToken;
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_IN;
-        ctx->data[1] = 0;
-        ctx->data[2] = 0 | CRC5(0, 11) << 3;
         ctx->hostSt = HS_DS_RXDATA;
       }
       break;
@@ -272,14 +396,13 @@
     case HS_DS_SENDACK:
       if (ctx->bus_state == kUsbControlStatusInData ||
           ctx->tick_bits >= ctx->wait) {
+        transfer_status(ctx, tr, USB_PID_ACK);
         ctx->bus_state = kUsbIdle;
-        ctx->state = ST_SYNC;
-        ctx->bytes = 1;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_ACK;
         ctx->hostSt = HS_NEXTFRAME;
+
+        // Remember the assigned device address
+        ctx->dev_address = dev_addr;
+
         printf("[usbdpi] setDeviceAddress done\n");
       }
       break;
@@ -288,49 +411,86 @@
   }
 }
 
-// Get Descriptor
-void readDescriptor(struct usbdpi_ctx *ctx) {
+// Set device configuration
+void setDeviceConfiguration(usbdpi_ctx_t *ctx, uint8_t config) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
+      transfer_token(tr, USB_PID_SETUP, ctx->dev_address, ENDPOINT_ZERO);
+
+      dp = transfer_data_start(tr, USB_PID_DATA0, 8);
+      dp[0] = 0;  // h2d, std, device
+      dp[1] = USB_REQ_SET_CONFIGURATION;
+      dp[2] = config;
+      dp[3] = 0;
+      dp[4] = 0;  // wIndex = 0
+      dp[5] = 0;
+      dp[6] = 0;  // wLength = 0
+      dp[7] = 0;
+      transfer_data_end(tr, dp + 8);
+
+      transfer_send(ctx, tr);
       ctx->bus_state = kUsbControlSetup;
-      ctx->state = ST_SYNC;
-      ctx->bytes = 14;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_SETUP;
-      ctx->data[1] = 2;
-      ctx->data[2] = 0 | CRC5(2, 11) << 3;
-      ctx->data[3] = USB_PID_DATA0;
-      ctx->data[4] = 0x80;  // d2h, std, device
-      ctx->data[5] = 6;     // get descr
-      ctx->data[6] = 0;     // index 0
-      ctx->data[7] = 1;     // type device
-      ctx->data[8] = 0;     // wIndex = 0
-      ctx->data[9] = 0;
-      ctx->data[10] = 0x12;  // wLength = 18
-      ctx->data[11] = 0;
-      add_crc16(ctx->data, ctx->datastart, 12);
-      // ctx->data[12] = 0xE0; // pre-computed CRC32
-      // ctx->data[13] = 0xF4;
       ctx->hostSt = HS_WAITACK;
       break;
     case HS_WAITACK:
       ctx->wait = ctx->tick_bits + 532;  // HACK
+      ctx->hostSt = HS_SET_DATASTAGE;
+      break;
+    case HS_SET_DATASTAGE:
+      if (ctx->bus_state == kUsbControlSetupAck &&
+          ctx->tick_bits >= ctx->wait) {
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO);
+        transfer_send(ctx, tr);
+        ctx->bus_state = kUsbControlStatusInToken;
+        ctx->hostSt = HS_DS_RXDATA;
+      }
+      break;
+    case HS_DS_RXDATA:
+      ctx->wait = ctx->tick_bits + 24;  // HACK -- 2 bytes
+      ctx->hostSt = HS_DS_SENDACK;
+      break;
+    case HS_DS_SENDACK:
+      if (ctx->bus_state == kUsbControlStatusInData ||
+          ctx->tick_bits >= ctx->wait) {
+        transfer_status(ctx, tr, USB_PID_ACK);
+        ctx->bus_state = kUsbIdle;
+        ctx->hostSt = HS_NEXTFRAME;
+        printf("[usbdpi] setDeviceConfiguration done\n");
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+// Get Descriptor
+void getDescriptor(usbdpi_ctx_t *ctx, uint8_t desc_type, uint8_t desc_idx,
+                   uint16_t desc_len) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
+  switch (ctx->hostSt) {
+    case HS_STARTFRAME: {
+      // TODO - build the bmRequestType by ORing values
+      const uint8_t bmRequestType = 0x80;  // d2h, vendor, endpoint
+      uint16_t wValue = (desc_type << 8) | (uint8_t)desc_idx;
+      transfer_setup(ctx, tr, bmRequestType, USB_REQ_GET_DESCRIPTOR, wValue, 0U,
+                     desc_len);
+    } break;
+    case HS_WAITACK:
+      ctx->wait = ctx->tick_bits + 1164;  // HACK
       ctx->hostSt = HS_REQDATA;
       break;
     case HS_REQDATA:
-      if (ctx->bus_state == kUsbControlSetupAck &&
-          ctx->tick_bits >= ctx->wait) {
+      // TODO - we must wait before trying the IN because we currently
+      // do not retry after a NAK response, but the CPU software can be tardy
+      if (ctx->tick_bits >= ctx->wait &&
+          ctx->bus_state == kUsbControlSetupAck) {
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO);
+
+        transfer_send(ctx, tr);
         ctx->bus_state = kUsbControlDataInToken;
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_IN;
-        ctx->data[1] = 2;
-        ctx->data[2] = 0 | CRC5(2, 11) << 3;
         ctx->hostSt = HS_WAITDATA;
       }
       break;
@@ -339,46 +499,85 @@
       ctx->hostSt = HS_SENDACK;
       break;
     case HS_SENDACK:
-      if (ctx->tick_bits == ctx->wait) {
+      if (ctx->bus_state == kUsbControlDataInData) {
+        if (ctx->step == STEP_GET_CONFIG_DESCRIPTOR) {
+          // Check the GET_DESCRIPTOR response
+          assert(ctx->recving);
+
+          // TODO - detect when the returned data falls short of the requested
+          // transfer length
+          uint8_t *dp = transfer_data_field(ctx->recving);
+          assert(dp);
+          // Collect the returned values
+          // uint8_t  bLength = dp[0];
+          // uint8_t  bDescriptorType = dp[1];
+          uint16_t wTotalLength = dp[2] | (dp[3] << 8);
+          // uint8_t  bNumInterfaces = dp[4];
+
+          ctx->cfg_desc_len = wTotalLength;
+        }
+
+        transfer_token(tr, USB_PID_ACK, ctx->dev_address, ENDPOINT_ZERO);
+
+        transfer_send(ctx, tr);
+        ctx->bus_state = kUsbControlDataInAck;
+        ctx->hostSt = HS_WAIT_PKT;
+
+        ctx->wait = ctx->tick_bits + 200;  // HACK
+      } else if (ctx->tick_bits >= ctx->wait) {
         printf("[usbdpi] Timed out waiting for device\n");
         ctx->hostSt = HS_NEXTFRAME;
         ctx->bus_state = kUsbIdle;
       }
-      if (ctx->bus_state == kUsbControlDataInData) {
-        ctx->bus_state = kUsbControlDataInAck;
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_ACK;
-        ctx->data[1] = 2;
-        ctx->data[2] = 0 | CRC5(2, 11) << 3;
-        ctx->wait = ctx->tick_bits + 200;  // HACK
-        ctx->hostSt = HS_WAIT_PKT;
-      }
       break;
     case HS_WAIT_PKT:
-      if (ctx->tick_bits == ctx->wait) {
+      // TODO - introduce support for multiple packets when we've requested
+      // longer transfers
+      if (ctx->tick_bits >= ctx->wait) {
         ctx->hostSt = HS_EMPTYDATA;
       }
       break;
     case HS_EMPTYDATA:
+      // Status stage of Control Read
+      // Transmit zero-length data packet and await handshake
+      transfer_token(tr, USB_PID_OUT, ctx->dev_address, ENDPOINT_ZERO);
+
+      dp = transfer_data_start(tr, USB_PID_DATA1, 0);
+      transfer_data_end(tr, dp);
+      transfer_send(ctx, tr);
       ctx->bus_state = kUsbControlStatusOut;
-      ctx->state = ST_SYNC;
-      ctx->bytes = 3;
-      ctx->datastart = -1;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_OUT;
-      ctx->data[1] = 2;
-      ctx->data[2] = 0 | CRC5(2, 11) << 3;
-      ctx->wait = ctx->tick_bits + 200;  // HACK
       ctx->hostSt = HS_WAITACK2;
+      ctx->wait = ctx->tick_bits + 200;  // HACK
       break;
     case HS_WAITACK2:
-      if (ctx->tick_bits == ctx->wait ||
-          ctx->bus_state == kUsbControlStatusOutAck) {
+      if (ctx->bus_state == kUsbControlStatusOutAck) {
+        switch (ctx->lastrxpid) {
+          case USB_PID_ACK:
+            ctx->ep_out[ENDPOINT_ZERO].next_data =
+                DATA_TOGGLE_ADVANCE(ctx->ep_out[ENDPOINT_ZERO].next_data);
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+
+          case USB_PID_NAK:
+            // TODO - this means that the device is still busy
+            ctx->hostSt = HS_WAITACK2;
+            ctx->wait = ctx->tick_bits + 200;  // HACK
+            break;
+
+          // TODO - commute these other responses into test failures
+          case USB_PID_STALL:
+            printf("[usbdpi] Device stalled\n");
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+          default:
+            printf("[usbdpi] Unexpected handshake response 0x%02x\n",
+                   ctx->lastrxpid);
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+        }
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf(
+            "[usbdpi] Time out waiting for device response in Status Stage\n");
         ctx->hostSt = HS_NEXTFRAME;
       }
       break;
@@ -387,30 +586,186 @@
   }
 }
 
-// Get Baud
-void readBaud(struct usbdpi_ctx *ctx) {
+// Get Test Configuration (Vendor-specific)
+void getTestConfig(usbdpi_ctx_t *ctx, uint16_t desc_len) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
+  assert(tr);
+  switch (ctx->hostSt) {
+    case HS_STARTFRAME: {
+      const uint8_t bmRequestType = 0xc2;  // d2h, vendor, endpoint
+      transfer_setup(ctx, tr, bmRequestType, USBDPI_VENDOR_TEST_CONFIG, 0U, 0U,
+                     desc_len);
+    } break;
+    case HS_WAITACK:
+      ctx->wait = ctx->tick_bits + 1164;  // HACK
+      ctx->hostSt = HS_REQDATA;
+      break;
+    case HS_REQDATA:
+      // TODO - we must wait before trying the IN because we currently
+      // do not retry after a NAK response, but the CPU software can be tardy
+      if (ctx->tick_bits >= ctx->wait &&
+          ctx->bus_state == kUsbControlSetupAck) {
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, ENDPOINT_ZERO);
+
+        transfer_send(ctx, tr);
+        ctx->bus_state = kUsbControlDataInToken;
+        ctx->hostSt = HS_WAITDATA;
+      }
+      break;
+    case HS_WAITDATA:
+      ctx->wait = ctx->tick_bits + 2000;  // HACK
+      ctx->hostSt = HS_SENDACK;
+      break;
+    case HS_SENDACK:
+      if (ctx->bus_state == kUsbControlDataInData) {
+        switch (ctx->lastrxpid) {
+          case USB_PID_DATA1: {
+            usbdpi_transfer_t *rx = ctx->recving;
+            assert(rx);
+            // TODO - check the length of the received data field!
+            uint8_t *dp = transfer_data_field(rx);
+            assert(dp);
+            // Validate the first part of the test descriptor
+            printf("[usbdpi] Test descriptor 0x%.8X\n", get_le32(dp));
+
+            transfer_dump(rx, stdout);
+
+            // Check the header signature
+            const uint8_t test_sig_head[] = {0x7eu, 0x57u, 0xc0u, 0xf1u};
+            const uint8_t test_sig_tail[] = {0x1fu, 0x0cu, 0x75u, 0xe7u};
+            if (!memcmp(dp, test_sig_head, 4) && 0x10 == get_le16(&dp[4]) &&
+                !memcmp(&dp[12], test_sig_tail, 4)) {
+              ctx->test_number = get_le16(&dp[6]);
+              ctx->test_arg[0] = dp[8];
+              ctx->test_arg[1] = dp[9];
+              ctx->test_arg[2] = dp[10];
+              ctx->test_arg[3] = dp[11];
+
+              printf("[usbdpi] Test number 0x%04x args %02x %02x %02x %02x\n",
+                     ctx->test_number, ctx->test_arg[0], ctx->test_arg[1],
+                     ctx->test_arg[2], ctx->test_arg[3]);
+
+              usbdpi_test_init(ctx);
+            } else {
+              printf(
+                  "[usbdpi] Invalid/unrecognised test descriptor received\n");
+              assert(!"Cannot proceed without test descriptor");
+            }
+          } break;
+
+          case USB_PID_NAK:
+            // TODO - we should retry the request in this case
+            printf("[usbdpi] Unable to retrieve test config\n");
+            assert(!"DPI is unable to retrieve test config");
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+
+          case USB_PID_STALL:
+            printf("[usbdpi] Device stalled\n");
+            assert(!"Device is stalled");
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+          default:
+            printf("[usbdpi] Unexpected handshake response 0x%02x\n",
+                   ctx->lastrxpid);
+            assert(!"Unexpected handshake response");
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+        }
+        transfer_token(tr, USB_PID_ACK, ctx->dev_address, ENDPOINT_ZERO);
+
+        transfer_send(ctx, tr);
+        ctx->bus_state = kUsbControlDataInAck;
+        ctx->hostSt = HS_WAIT_PKT;
+
+        ctx->wait = ctx->tick_bits + 200;  // HACK
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf("[usbdpi] Timed out waiting for device\n");
+        ctx->hostSt = HS_NEXTFRAME;
+        ctx->bus_state = kUsbIdle;
+      }
+      break;
+    case HS_WAIT_PKT:
+      if (ctx->tick_bits >= ctx->wait) {
+        ctx->hostSt = HS_EMPTYDATA;
+      }
+      break;
+    case HS_EMPTYDATA:
+      // Status stage of Control Read
+      // Transmit zero-length data packet and await handshake
+      transfer_token(tr, USB_PID_OUT, ctx->dev_address, ENDPOINT_ZERO);
+
+      dp = transfer_data_start(tr, USB_PID_DATA1, 0);
+      transfer_data_end(tr, dp);
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbControlStatusOut;
+      ctx->hostSt = HS_WAITACK2;
+      ctx->wait = ctx->tick_bits + 200;  // HACK
+      break;
+    case HS_WAITACK2:
+      if (ctx->bus_state == kUsbControlStatusOutAck) {
+        switch (ctx->lastrxpid) {
+          case USB_PID_ACK:
+            ctx->ep_out[ENDPOINT_ZERO].next_data =
+                DATA_TOGGLE_ADVANCE(ctx->ep_out[ENDPOINT_ZERO].next_data);
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+
+          case USB_PID_NAK:
+            // TODO - this means that the device is still busy
+            ctx->hostSt = HS_WAITACK2;
+            ctx->wait = ctx->tick_bits + 200;  // HACK
+            break;
+
+          // TODO - commute these other responses into test failures
+          case USB_PID_STALL:
+            printf("[usbdpi] Device stalled\n");
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+          default:
+            printf("[usbdpi] Unexpected handshake response 0x%02x\n",
+                   ctx->lastrxpid);
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+        }
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf(
+            "[usbdpi] Time out waiting for device response in Status Stage\n");
+        ctx->hostSt = HS_NEXTFRAME;
+      }
+      break;
+    default:
+      break;
+  }
+}
+
+// Set Test Status, reporting progress/success/failure (Vendor-specific)
+void setTestStatus(usbdpi_ctx_t *ctx, uint32_t status, const char *msg) {
+  // TODO - placeholder for reporting of test status reporting and termination
+}
+
+// Get Baud (Vendor-specific)
+void readBaud(usbdpi_ctx_t *ctx, uint8_t endpoint) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
-      ctx->state = ST_SYNC;
-      ctx->bytes = 14;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_SETUP;
-      ctx->data[1] = 0x82;
-      ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
-      ctx->data[3] = USB_PID_DATA0;
-      ctx->data[4] = 0xc2;  // d2h, vendor, endpoint
-      ctx->data[5] = 2;     // get baud
-      ctx->data[6] = 0;     // index 0
-      ctx->data[7] = 0;     // type device
-      ctx->data[8] = 0;     // wIndex = 0
-      ctx->data[9] = 0;
-      ctx->data[10] = 0x2;  // wLength = 2
-      ctx->data[11] = 0;
-      add_crc16(ctx->data, ctx->datastart, 12);
-      // ctx->data[12] = 0x10; // pre-computed CRC32
-      // ctx->data[13] = 0xDD;
+      transfer_token(tr, USB_PID_SETUP, ctx->dev_address, endpoint);
+
+      dp = transfer_data_start(tr, USB_PID_DATA0, 0);
+      dp[0] = 0xc2;  // d2h, vendor, endpoint
+      dp[1] = 2;     // get baud
+      dp[2] = 0;     // index 0
+      dp[3] = 0;     // type device
+      dp[4] = 0;     // wIndex = 0
+      dp[5] = 0;
+      dp[6] = 0x2;  // wLength = 2
+      dp[7] = 0;
+      transfer_data_end(tr, dp + 8);
+
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbControlSetup;
       ctx->hostSt = HS_WAITACK;
       break;
     case HS_WAITACK:
@@ -418,15 +773,13 @@
       ctx->hostSt = HS_REQDATA;
       break;
     case HS_REQDATA:
-      if (ctx->tick_bits == ctx->wait) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_IN;
-        ctx->data[1] = 0x82;
-        ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
+      if (ctx->tick_bits >= ctx->wait) {
+        // NOTE: This IN request produces a NAK from the device because there is
+        //       nothing available, at which point a REAL host should surely
+        //       retry the request at a later time!
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint);
+
+        transfer_send(ctx, tr);
         ctx->hostSt = HS_WAITDATA;
       }
       break;
@@ -435,64 +788,68 @@
       ctx->hostSt = HS_SENDACK;
       break;
     case HS_SENDACK:
-      if (ctx->tick_bits == ctx->wait) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 1;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_ACK;
+      // TODO - are we not ACKing the NAK at this point?
+      if (ctx->tick_bits >= ctx->wait) {
+        transfer_status(ctx, tr, USB_PID_ACK);
         ctx->hostSt = HS_EMPTYDATA;
       }
       break;
     case HS_EMPTYDATA:
-      ctx->state = ST_SYNC;
-      ctx->bytes = 6;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_OUT;
-      ctx->data[1] = 0x82;
-      ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
-      ctx->data[3] = USB_PID_DATA1;
-      ctx->data[4] = 0x0;  // pre-computed CRC32
-      ctx->data[5] = 0x0;
+      // Transmit OUT transaction with zero-length DATA packet
+      transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint);
+      if (INSERT_ERR_DATA_TOGGLE) {
+        // NOTE: This raises a LinkOutErr on the USBDEV because it is expecting
+        //       DATA0
+        uint8_t bad_pid = DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data);
+        dp = transfer_data_start(tr, bad_pid, 0);
+      } else {
+        dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0);
+      }
+      transfer_data_end(tr, dp);
+
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbControlStatusOut;
       ctx->hostSt = HS_WAITACK2;
+
+      ctx->wait = ctx->tick_bits + 200;  // HACK
       break;
     case HS_WAITACK2:
-      ctx->wait = ctx->tick_bits + 32;  // HACK
-      ctx->hostSt = HS_NEXTFRAME;
-      printf("[usbdpi] readBaud done\n");
+      if (ctx->tick_bits >= ctx->wait ||
+          ctx->bus_state == kUsbControlStatusOutAck) {
+        if (ctx->lastrxpid == USB_PID_ACK) {
+          ctx->ep_out[endpoint].next_data =
+              DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data);
+        }
+        ctx->hostSt = HS_NEXTFRAME;
+        printf("[usbdpi] readBaud done\n");
+      }
       break;
     default:
       break;
   }
 }
 
-// Set Baud
-void setBaud(struct usbdpi_ctx *ctx) {
+// Set Baud (Vendor-specific)
+void setBaud(usbdpi_ctx_t *ctx, uint8_t endpoint) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
-      ctx->state = ST_SYNC;
-      ctx->bytes = 14;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_SETUP;
-      ctx->data[1] = 0x82;
-      ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
-      ctx->data[3] = USB_PID_DATA0;
-      ctx->data[4] = 0x42;  // h2d, vendor, endpoint
-      ctx->data[5] = 3;     // set baud
-      ctx->data[6] = 96;    // index 0
-      ctx->data[7] = 0;     // type device
-      ctx->data[8] = 0;     // wIndex = 0
-      ctx->data[9] = 0;
-      ctx->data[10] = 0;  // wLength = 0
-      ctx->data[11] = 0;
-      add_crc16(ctx->data, ctx->datastart, 12);
-      // ctx->data[12] = 0x00; // pre-computed CRC32
-      // ctx->data[13] = 0xBD;
+      transfer_token(tr, USB_PID_SETUP, ctx->dev_address, endpoint);
+
+      dp = transfer_data_start(tr, USB_PID_DATA0, 0);
+      dp[0] = 0x42;  // h2d, vendor, endpoint
+      dp[1] = 3;     // set baud
+      dp[2] = 96;    // index 0
+      dp[3] = 0;     // type device
+      dp[4] = 0;     // wIndex = 0
+      dp[5] = 0;
+      dp[6] = 0;  // wLength = 0
+      dp[7] = 0;
+      transfer_data_end(tr, dp + 8);
+
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbControlSetup;
       ctx->hostSt = HS_WAITACK;
       break;
     case HS_WAITACK:
@@ -500,15 +857,10 @@
       ctx->hostSt = HS_REQDATA;
       break;
     case HS_REQDATA:
-      if (ctx->tick_bits == ctx->wait) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_IN;
-        ctx->data[1] = 0x82;
-        ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
+      if (ctx->tick_bits >= ctx->wait) {
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint);
+
+        transfer_send(ctx, tr);
         ctx->hostSt = HS_WAITDATA;
       }
       break;
@@ -518,12 +870,7 @@
       break;
     case HS_SENDACK:
       if (ctx->tick_bits >= ctx->wait) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 1;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_ACK;
+        transfer_status(ctx, tr, USB_PID_ACK);
         ctx->hostSt = HS_NEXTFRAME;
         ctx->baudrate_set_successfully = 1;
         printf("[usbdpi] setBaud done\n");
@@ -534,48 +881,42 @@
   }
 }
 
-// Test the ischronous transfers (without ACKs)
-void testIso(struct usbdpi_ctx *ctx) {
+// Try an OUT transfer to the device, optionally expecting a Status
+//   packet (eg. ACK|NAK) in response; this is not expected for
+//   Isochronous transfers
+void tryTX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool expect_status) {
+  const uint8_t pattern[] = {
+      "AbCdEfGhIjKlMnOpQrStUvWxYz+0123456789-aBcDeFgHiJkLmNoPqRsTuVwXyZ"};
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
-      ctx->state = ST_SYNC;
-      ctx->bytes = 14;
-      ctx->datastart = 3;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_OUT;
-      ctx->data[1] = 0x82;
-      ctx->data[2] = 1 | CRC5(0x182, 11) << 3;
+      transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint);
 
-      ctx->data[3] = USB_PID_DATA0;
-      ctx->data[4] = 0x42;  // h2d, vendor, endpoint
-      ctx->data[5] = 3;     // set baud
-      ctx->data[6] = 96;    // index 0
-      ctx->data[7] = 0;     // type device
-      ctx->data[8] = 0;     // wIndex = 0
-      ctx->data[9] = 0;
-      ctx->data[10] = 0;  // wLength = 0
-      ctx->data[11] = 0;
+      dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0);
+      memcpy(dp, pattern, sizeof(pattern));
+      transfer_data_end(tr, dp + sizeof(pattern));
 
-      add_crc16(ctx->data, ctx->datastart, 12);
-      // ctx->data[12] = 0x00; // pre-computed CRC32
-      // ctx->data[13] = 0xBD;
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbBulkOut;
       ctx->hostSt = HS_WAITACK;
       break;
-    case HS_WAITACK:  // no actual ACK
+    case HS_WAITACK:  // no actual ACK if Isochronous transfer
       ctx->wait = ctx->tick_bits + 32;
       ctx->hostSt = HS_REQDATA;
       break;
     case HS_REQDATA:
-      if (ctx->tick_bits == ctx->wait) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_IN;
-        ctx->data[1] = 0x82;
-        ctx->data[2] = 1 | CRC5(0x0182, 11) << 3;
+      if (ctx->tick_bits >= ctx->wait) {
+        // Note: Isochronous transfers are not acknowledged and do not employ
+        //       Data Toggle Synchronization
+        if (expect_status && ctx->lastrxpid == USB_PID_ACK) {
+          ctx->ep_out[endpoint].next_data =
+              DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data);
+        }
+
+        transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint);
+        transfer_send(ctx, tr);
+
         ctx->hostSt = HS_WAITDATA;
       }
       break;
@@ -588,23 +929,21 @@
   }
 }
 
-// Request IN. Get back NAK or DATA0/DATA1.
-// sendHi -> also send OUT packet
-// nakData -> send NAK instead of ACK if there is data
-void pollRX(struct usbdpi_ctx *ctx, int sendHi, int nakData) {
+// Request IN. Get back DATA0/DATA1 or NAK.
+//
+// send_hi  -> also send OUT packet
+// nak_data -> send NAK instead of ACK if there is data
+void pollRX(usbdpi_ctx_t *ctx, uint8_t endpoint, bool send_hi, bool nak_data) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
-      ctx->state = ST_SYNC;
-      ctx->bytes = 3;
-      ctx->datastart = -1;
-      ctx->byte = 0;
-      ctx->bit = 1;
-      ctx->data[0] = USB_PID_IN;
-      ctx->data[1] = 0x82;
-      ctx->data[2] = 0x00 | CRC5(0x0082, 11) << 3;
+      transfer_token(tr, USB_PID_IN, ctx->dev_address, endpoint);
+
+      transfer_send(ctx, tr);
+      ctx->bus_state = kUsbBulkInToken;
       ctx->hostSt = HS_WAIT_PKT;
       ctx->lastrxpid = 0;
-      ctx->bus_state = kUsbBulkInToken;
       break;
     case HS_WAIT_PKT:
       // Wait max time for a response + packet
@@ -612,94 +951,80 @@
       ctx->hostSt = HS_ACKIFDATA;
       break;
     case HS_ACKIFDATA:
-      if (ctx->tick_bits >= ctx->wait) {
-        printf("[usbdpi] Timed out waiting for IN response\n");
-        ctx->hostSt = HS_SENDHI;
-      } else if (ctx->bus_state = kUsbBulkInData) {
+      if (ctx->bus_state == kUsbBulkInData) {
+        // TODO - have we got a LFSR-generated data packet?
         if (ctx->lastrxpid != USB_PID_NAK) {
-          // device sent data so ACK it
-          // TODO check DATA0 vs DATA1
-          ctx->state = ST_SYNC;
-          ctx->bytes = 1;
-          ctx->datastart = -1;
-          ctx->byte = 0;
-          ctx->bit = 1;
-          ctx->data[0] = nakData ? USB_PID_NAK : USB_PID_ACK;
+          transfer_status(ctx, tr, nak_data ? USB_PID_NAK : USB_PID_ACK);
         }
         ctx->hostSt = HS_SENDHI;
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf("[usbdpi] Timed out waiting for IN response\n");
+        ctx->hostSt = HS_SENDHI;
       }
       break;
     case HS_SENDHI:
-      if (sendHi) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 9;
-        ctx->datastart = 3;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_OUT;
-        ctx->data[1] = 0x82;
-        ctx->data[2] = 0 | CRC5(0x82, 11) << 3;
-        ctx->data[3] = USB_PID_DATA0;
-        ctx->data[4] = 0x48;  // "H"
-        ctx->data[5] = 0x69;  // "i"
-        ctx->data[6] = 0x21;  // "!"
-        add_crc16(ctx->data, ctx->datastart, 7);
-        // ctx->data[7] = 0xE0; // pre-computed CRC16
-        // ctx->data[8] = 0x61;
+      if (send_hi) {
+        transfer_token(tr, USB_PID_OUT, ctx->dev_address, endpoint);
+
+        dp = transfer_data_start(tr, ctx->ep_out[endpoint].next_data, 0);
+        dp[0] = 0x48;  // "H"
+        dp[1] = 0x69;  // "i"
+        dp[2] = 0x21;  // "!"
+        transfer_data_end(tr, dp + 3);
+
+        transfer_send(ctx, tr);
+        ctx->wait = ctx->tick_bits + 532;  // HACK
+        ctx->hostSt = HS_WAITACK;
+      } else {
+        ctx->hostSt = HS_NEXTFRAME;
       }
-      ctx->inframe = ctx->frame;
-      ctx->hostSt = HS_NEXTFRAME;  // Device will ACK
+      break;
+    case HS_WAITACK:
+      if (ctx->tick_bits >= ctx->wait) {
+        if (ctx->lastrxpid == USB_PID_ACK) {
+          ctx->ep_out[endpoint].next_data =
+              DATA_TOGGLE_ADVANCE(ctx->ep_out[endpoint].next_data);
+        }
+        ctx->hostSt = HS_NEXTFRAME;
+      }
       break;
     default:
       break;
   }
 }
 
-// Test unimplemented endpoints
-void testUnimplEp(struct usbdpi_ctx *ctx, uint8_t pid) {
+// Test behavior in (non-)response to other device and unimplemented endpoints
+void testUnimplEp(usbdpi_ctx_t *ctx, uint8_t pid, uint8_t device,
+                  uint8_t endpoint) {
+  usbdpi_transfer_t *tr = ctx->sending;
+  uint8_t *dp;
   switch (ctx->hostSt) {
     case HS_STARTFRAME:
       if ((pid == USB_PID_SETUP) || (pid == USB_PID_OUT)) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 14;
-        ctx->datastart = 3;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        // The bytes are transmitted LSB to MSB
-        ctx->data[0] = pid;
-        ctx->data[1] =
-            ((UNIMPL_EP_ID & 0x1) << 7) | 0x2;  // endpoint ID LSB, address
-        ctx->data[2] = CRC5((UNIMPL_EP_ID << 7) | 0x2, 11) << 3 |
-                       ((UNIMPL_EP_ID >> 1) & 0x7);  // CRC, endpoint ID MSBs
+        transfer_token(tr, pid, device, endpoint);
 
-        ctx->data[3] = USB_PID_DATA0;
-        ctx->data[4] = 0;  // h2d, std, device
-        ctx->data[5] = 5;  // set address
-        ctx->data[6] = 2;  // device address
-        ctx->data[7] = 0;
+        dp = transfer_data_start(tr, USB_PID_DATA0, 0);
+        dp[0] = 0;  // h2d, std, device
+        dp[1] = 5;  // set address
+        dp[2] = 2;  // device address
+        dp[3] = 0;
         // Trigger bitstuffing, technically the device
-        // behaviour is unspecified with wIndex != 0
-        ctx->data[8] = 0xFF;  // wIndex = 0xFF00
-        ctx->data[9] = 0;
-        ctx->data[10] = 0;  // wLength = 0
-        ctx->data[11] = 0;
-        add_crc16(ctx->data, ctx->datastart, 12);
+        // behavior is unspecified with wIndex != 0
+        dp[4] = 0xFF;  // wIndex = 0xFF00
+        dp[5] = 0;
+        dp[6] = 0;  // wLength = 0
+        dp[7] = 0;
+        transfer_data_end(tr, dp + 8);
+
+        transfer_send(ctx, tr);
         ctx->hostSt = HS_WAITACK;
         break;
       } else if (pid == USB_PID_IN) {
-        ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = 0;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        // The bytes are transmitted LSB to MSB
-        ctx->data[0] = pid;
-        ctx->data[1] =
-            ((UNIMPL_EP_ID & 0x1) << 7) | 0x2;  // endpoint ID LSB, address
-        ctx->data[2] = CRC5((UNIMPL_EP_ID << 7) | 0x2, 11) << 3 |
-                       ((UNIMPL_EP_ID >> 1) & 0x7);  // CRC, endpoint ID MSBs
-        // Since the endpoint is not implemented, the device will respond with a
-        // STALL packet (not DATA0/1 or NAK).
+        transfer_token(tr, pid, device, endpoint);
+        transfer_send(ctx, tr);
+
+        // Since the endpoint is not implemented, the device should respond with
+        // a STALL packet (not DATA0/1 or NAK).
         ctx->hostSt = HS_WAITACK;
         break;
       } else {
@@ -711,7 +1036,7 @@
       }
     case HS_WAITACK:
       // Note: We currently can't observe the responses sent by the device, but
-      // monitor_usb() does log all transactions from host and device and does
+      // usb_monitor() does log all transactions from host and device and does
       // some basic decoding.
       // Depending on the transaction type to unimplemented endpoints, we would
       // expect the following response:
@@ -727,9 +1052,9 @@
 }
 
 // Change DP and DN outputs from host
-int set_driving(struct usbdpi_ctx *ctx, int d2p, int newval) {
+uint32_t set_driving(usbdpi_ctx_t *ctx, uint32_t d2p, uint32_t newval) {
   // Always maintain the current state of VBUS
-  int driving = ctx->driving & P2D_SENSE;
+  uint32_t driving = ctx->driving & P2D_SENSE;
   if (d2p & D2P_DNPU) {
     // Have dn pull-up, so must be flipping pins
     if (newval & P2D_DP) {
@@ -747,35 +1072,34 @@
   return driving;
 }
 
-int inv_driving(struct usbdpi_ctx *ctx, int d2p) {
+// Optionally invert the signals the host is driving, according to bus
+// configuration
+uint32_t inv_driving(usbdpi_ctx_t *ctx, uint32_t d2p) {
   // works for either orientation
   return ctx->driving ^ (P2D_DP | P2D_DN | P2D_D);
 }
 
-char usbdpi_host_to_device(void *ctx_void, const svBitVecVal *usb_d2p) {
-  struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
+uint8_t usbdpi_host_to_device(void *ctx_void, const svBitVecVal *usb_d2p) {
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void;
   assert(ctx);
   int d2p = usb_d2p[0];
   uint32_t last_driving = ctx->driving;
   int force_stat = 0;
   int dat;
 
-  if (ctx->tick == 0) {
-    int i;
-#ifdef NEED_SLEEP
-    for (i = 7; i > 0; i--) {
-      printf("Sleep %d...\n", i);
-      sleep(1);
-    }
-#endif
-  }
+  // The 48MHz clock runs at 4 times the bus clock for a full speed (12Mbps)
+  // device
+  //
+  // TODO - vary the phase over the duration of the test to check device
+  //        synchronization
   ctx->tick++;
   ctx->tick_bits = ctx->tick >> 2;
   if (ctx->tick & 3) {
     return ctx->driving;
   }
 
-  monitor_usb(ctx->mon, ctx->mon_file, ctx->loglevel, ctx->tick,
+  // Monitor, analyse and record USB bus activity
+  usb_monitor(ctx->mon, ctx->loglevel, ctx->tick_bits,
               (ctx->state != ST_IDLE) && (ctx->state != ST_GET), ctx->driving,
               d2p, &(ctx->lastrxpid));
 
@@ -788,102 +1112,182 @@
     return ctx->driving;
   }
 
+  // Are allowed to start transmitting yet; device recovery time elapsed?
   if (ctx->tick < ctx->recovery_time) {
-    ctx->lastframe = ctx->tick_bits;
+    ctx->frame_start = ctx->tick_bits;
     return ctx->driving;
   }
 
-  if ((ctx->tick_bits - ctx->lastframe) >= FRAME_INTERVAL) {
+  // Time to commence a new bus frame?
+  if ((ctx->tick_bits - ctx->frame_start) >= FRAME_INTERVAL) {
     if (ctx->state != ST_IDLE) {
       if (ctx->framepend == 0) {
-        printf("USB: %4x %8d error state %d at frame %d time\n", ctx->frame,
-               ctx->tick, ctx->state, ctx->frame + 1);
+        printf(
+            "[usbdpi] frame 0x%x tick_bits 0x%x error state %d at frame 0x%x "
+            "time\n",
+            ctx->frame, ctx->tick, ctx->state, ctx->frame + 1);
       }
       ctx->framepend = 1;
     } else {
       if (ctx->framepend == 1) {
-        printf("USB: %4x %8d can send frame %d SOF\n", ctx->frame, ctx->tick,
-               ctx->frame + 1);
+        printf("[usbdpi] frame 0x%x tick_bits 0x%x can send frame 0x%x SOF\n",
+               ctx->frame, ctx->tick, ctx->frame + 1);
       }
       ctx->framepend = 0;
       ctx->frame++;
-      ctx->lastframe = ctx->tick_bits;
+      ctx->frame_start = ctx->tick_bits;
 
-      if (ctx->frame >= 20 && ctx->frame < 30) {
-        // Test suspend
+      if (ctx->step >= STEP_IDLE_START && ctx->step < STEP_IDLE_END) {
+        // Test suspend behavior by dropping the SOF signalling
         ctx->state = ST_IDLE;
-        printf("Idle frame %d\n", ctx->frame);
+        printf("[usbdpi] idle frame 0x%x\n", ctx->frame);
       } else {
+        // Ensure that a buffer is available for constructing a transfer
+        usbdpi_transfer_t *tr = ctx->sending;
+        if (!tr) {
+          tr = transfer_alloc(ctx);
+          assert(tr);
+
+          ctx->sending = tr;
+        }
+
+        transfer_frame_start(ctx, tr, ctx->frame);
         ctx->state = ST_SYNC;
-        ctx->bytes = 3;
-        ctx->datastart = -1;
-        ctx->byte = 0;
-        ctx->bit = 1;
-        ctx->data[0] = USB_PID_SOF;
-        ctx->data[1] = ctx->frame & 0xff;
-        ctx->data[2] =
-            ((ctx->frame & 0x700) >> 8) | (CRC5(ctx->frame & 0x7ff, 11) << 3);
       }
-      printf("USB: %8d frame 0x%x CRC5 0x%x\n", ctx->tick, ctx->frame,
-             CRC5(ctx->frame, 11));
+      printf("[usbdpi] frame 0x%x tick_bits 0x%x CRC5 0x%x\n", ctx->frame,
+             ctx->tick, CRC5(ctx->frame, 11));
+
       if (ctx->hostSt == HS_NEXTFRAME) {
+        ctx->step = usbdpi_test_seq_next(ctx, ctx->step);
         ctx->hostSt = HS_STARTFRAME;
+      } else {
+        // TODO - this surely means that something went wrong;
+        // but what shall we do at this point?!
+        assert(!"DPI Host not ready to start new frame");
       }
     }
   }
-  switch (ctx->state) {
-    case ST_IDLE:
-      switch (ctx->frame) {
-        case 1:
-          setDeviceAddress(ctx);
-          break;
-        case 2:
-          readDescriptor(ctx);
-          break;
-        // FIXME: Should have SET_CONFIGURATION here, else non-default endpoints
-        // should be disabled.
 
-        // These should be at 3 and 4 but the read needs the host
-        // not to be sending (until skip fifo is implemented in in_pe engine)
-        // so for now push later when things are quiet (could also adjust
-        // hello_world to not use the uart until frame 4)
-        case 9:
-          pollRX(ctx, 1, 1);
-          break;
-        case 10:
-          readBaud(ctx);
-          break;
-        case 14:
-          pollRX(ctx, 1, 0);
-          break;
-        case 15:
-          setBaud(ctx);
-          break;
-        case 16:
-          pollRX(ctx, 0, 1);
-          break;
-        case 17:
-          testIso(ctx);
-          break;
-        case 18:
-          testIso(ctx);
-          break;
-        case 20:
-          testUnimplEp(ctx, USB_PID_SETUP);
-          break;
-        case 21:
-          testUnimplEp(ctx, USB_PID_OUT);
-          break;
-        case 22:
-          testUnimplEp(ctx, USB_PID_IN);
-          break;
-        default:
-          if (ctx->frame > ctx->inframe &&
-              !(ctx->frame >= 23 && ctx->frame < 33)) {
-            pollRX(ctx, 0, 0);
-          }
+  switch (ctx->state) {
+    // Host state machine advances when the bit-level activity is idle
+    case ST_IDLE: {
+      // Ensure that a buffer is available for constructing a transfer
+      usbdpi_transfer_t *tr = ctx->sending;
+      if (!tr) {
+        tr = transfer_alloc(ctx);
+        assert(tr);
+        ctx->sending = tr;
       }
-      break;
+
+      switch (ctx->step) {
+        case STEP_SET_DEVICE_ADDRESS:
+          setDeviceAddress(ctx, USBDEV_ADDRESS);
+          break;
+
+          // TODO - an actual host issues a number of GET_DESCRIPTOR control/
+          //        transfers to read descriptions of the configurations,
+          //        interfaces and endpoints
+
+        case STEP_GET_DEVICE_DESCRIPTOR:
+          // Initially we fetch just the minimal descriptor length of 0x12U
+          // bytes and the returned information will indicate the full length
+          //
+          // TODO - Set the descriptor length to the minimum because the DPI
+          // model does not yet catch and report errors properly
+          ctx->cfg_desc_len = 12U;
+          getDescriptor(ctx, USB_DESC_TYPE_DEVICE, 0U, 0x12U);
+          break;
+
+        case STEP_GET_CONFIG_DESCRIPTOR:
+          getDescriptor(ctx, USB_DESC_TYPE_CONFIGURATION, 0U, 0x9U);
+          break;
+
+        case STEP_GET_FULL_CONFIG_DESCRIPTOR: {
+          uint16_t wLength = ctx->cfg_desc_len;
+          if (wLength >= USBDEV_MAX_PACKET_SIZE) {
+            // Note: getDescriptor cannot yet receive multiple packets
+            wLength = USBDEV_MAX_PACKET_SIZE;
+          }
+          getDescriptor(ctx, USB_DESC_TYPE_CONFIGURATION, 0U, wLength);
+        } break;
+
+          // TODO - we must receive and respond to test configuration at some
+          //        point; perhaps we can make the software advertise itself
+          //        with different vendor/device combinations to indicate the
+          //        testing we must do
+
+        case STEP_SET_DEVICE_CONFIG:
+          setDeviceConfiguration(ctx, 1);
+          break;
+
+        // Test configuration and status
+        case STEP_GET_TEST_CONFIG:
+          getTestConfig(ctx, 0x10U);
+          break;
+
+        case STEP_SET_TEST_STATUS:
+          setTestStatus(ctx, ctx->test_status, ctx->test_msg);
+          break;
+
+          // These should be at 3 and 4 but the read needs the host
+          // not to be sending (until skip fifo is implemented in in_pe engine)
+          // so for now push later when things are quiet (could also adjust
+          // hello_world to not use the uart until frame 4)
+
+        case STEP_FIRST_READ:
+          pollRX(ctx, ENDPOINT_SERIAL0, true, true);
+          break;
+        case STEP_READ_BAUD:
+          readBaud(ctx, ENDPOINT_ZERO);
+          break;
+        case STEP_SECOND_READ:
+          pollRX(ctx, ENDPOINT_SERIAL0, true, false);
+          break;
+        case STEP_SET_BAUD:
+          setBaud(ctx, ENDPOINT_ZERO);
+          break;
+        case STEP_THIRD_READ:
+          pollRX(ctx, ENDPOINT_SERIAL0, false, true);
+          break;
+        case STEP_TEST_ISO1:
+          testIso(ctx);
+          break;
+        case STEP_TEST_ISO2:
+          testIso(ctx);
+          break;
+
+        // Test each of SETUP, OUT and IN to an unimplemented endpoint
+        case STEP_ENDPT_UNIMPL_SETUP:
+          testUnimplEp(ctx, USB_PID_SETUP, ctx->dev_address,
+                       ENDPOINT_UNIMPLEMENTED);
+          break;
+        case STEP_ENDPT_UNIMPL_OUT:
+          testUnimplEp(ctx, USB_PID_OUT, ctx->dev_address,
+                       ENDPOINT_UNIMPLEMENTED);
+          break;
+        case STEP_ENDPT_UNIMPL_IN:
+          testUnimplEp(ctx, USB_PID_IN, ctx->dev_address,
+                       ENDPOINT_UNIMPLEMENTED);
+          break;
+        case STEP_DEVICE_UK_SETUP:
+          testUnimplEp(ctx, USB_PID_SETUP, UKDEV_ADDRESS, 1u);
+          break;
+
+        case STEP_STREAM_SERVICE:
+          // After the initial testing of the (current) fixed DPI behavior,
+          // we repeatedly try IN transfers, checking and scrambling any
+          // data packets that we received before sending them straight back
+          // to the device for software to check
+          streams_service(ctx);
+          break;
+
+        default:
+          if (ctx->step < STEP_IDLE_START || ctx->step >= STEP_IDLE_END) {
+            pollRX(ctx, ENDPOINT_SERIAL0, false, false);
+          }
+          break;
+      }
+    } break;
 
     case ST_SYNC:
       dat = ((USB_SYNC & ctx->bit)) ? P2D_DP : P2D_DN;
@@ -897,21 +1301,22 @@
       }
       break;
 
-    case ST_SEND:
+    case ST_SEND: {
+      usbdpi_transfer_t *sending = ctx->sending;
+      assert(sending);
       if ((ctx->linebits & 0x3f) == 0x3f &&
           !INSERT_ERR_BITSTUFF) {  // sent 6 ones
         // bit stuff and force a transition
         ctx->driving = inv_driving(ctx, d2p);
         force_stat = 1;
         ctx->linebits = (ctx->linebits << 1);
-      } else if (ctx->byte >= ctx->bytes) {
+      } else if (ctx->byte >= sending->num_bytes) {
         ctx->state = ST_EOP;
         ctx->driving = set_driving(ctx, d2p, 0);  // SE0
         ctx->bit = 1;
         force_stat = 1;
       } else {
-        int nextbit;
-        nextbit = (ctx->data[ctx->byte] & ctx->bit) ? 1 : 0;
+        int nextbit = (sending->data[ctx->byte] & ctx->bit) ? 1 : 0;
         if (nextbit == 0) {
           ctx->driving = inv_driving(ctx, d2p);
         }
@@ -921,12 +1326,12 @@
         if (ctx->bit == 0x100) {
           ctx->bit = 1;
           ctx->byte++;
-          if (ctx->byte == ctx->datastart) {
+          if (ctx->byte == sending->data_start) {
             ctx->state = ST_EOP0;
           }
         }
       }
-      break;
+    } break;
 
     case ST_EOP0:
       ctx->driving = set_driving(ctx, d2p, 0);  // SE0
@@ -938,9 +1343,11 @@
         ctx->driving = set_driving(ctx, d2p, P2D_DP);  // J
       }
       if (ctx->bit == 8) {
+        usbdpi_transfer_t *sending = ctx->sending;
+        assert(sending);
         // Stop driving: host pulldown to SE0 unless there is a pullup on DP
         ctx->driving = set_driving(ctx, d2p, (d2p & D2P_PU) ? P2D_DP : 0);
-        if (ctx->byte == ctx->datastart) {
+        if (ctx->byte == sending->data_start) {
           ctx->bit = 1;
           ctx->state = ST_SYNC;
         } else {
@@ -949,28 +1356,52 @@
       }
       ctx->bit <<= 1;
       break;
+
+    case ST_GET:
+      // Device is driving the bus; nothing to do here
+      break;
+
+    default:
+      assert(!"Unknown/invalid USBDPI drive state");
+      break;
   }
+
   if ((ctx->loglevel & LOG_BIT) &&
       (force_stat || (ctx->driving != last_driving))) {
-    int n;
-    char obuf[MAX_OBUF];
-
-    n = snprintf(
-        obuf, MAX_OBUF, "%4x %8d              %s %s %s\n", ctx->frame,
-        ctx->tick, ctx->driving & P2D_SENSE ? "VBUS" : "    ",
+    usb_monitor_log(
+        ctx->mon, "0x%-3x 0x-%8x              %s %s %s\n", ctx->frame,
+        ctx->tick_bits, ctx->driving & P2D_SENSE ? "VBUS" : "    ",
         (ctx->state != ST_IDLE) ? decode_usb[(ctx->driving >> 1) & 3] : "ZZ ",
         (ctx->driving & P2D_D) ? "1" : "0");
-    ssize_t written = fwrite(obuf, sizeof(char), (size_t)n, ctx->mon_file);
-    assert(written == n);
   }
   return ctx->driving;
 }
 
+// Export some internal diagnostic state for visibility in waveforms
+void usbdpi_diags(void *ctx_void, svBitVecVal *diags) {
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void;
+
+  // Check for overflow, which would cause confusion in waveform interpretation;
+  // if an assertion fires, the mapping from fields to svBitVecVal will need
+  // to be changed, both here and in usbdpi.sv
+  assert(ctx->state <= 0xfU);
+  assert(ctx->hostSt <= 0x1fU);
+  assert(ctx->bus_state <= 0x3fU);
+  assert(ctx->step <= 0x7fU);
+
+  diags[2] = usb_monitor_diags(ctx->mon);
+  diags[1] =
+      (ctx->step << 25) | (ctx->bus_state << 20) | (ctx->tick_bits >> 12);
+  diags[0] = (ctx->tick_bits << 20) | ((ctx->frame & 0x7ffU) << 9) |
+             ((ctx->hostSt & 0x1fU) << 4) | (ctx->state & 0xfU);
+}
+
+// Close the USBDPI model and release resources
 void usbdpi_close(void *ctx_void) {
-  struct usbdpi_ctx *ctx = (struct usbdpi_ctx *)ctx_void;
+  usbdpi_ctx_t *ctx = (usbdpi_ctx_t *)ctx_void;
   if (!ctx) {
     return;
   }
-  fclose(ctx->mon_file);
+  usb_monitor_fin(ctx->mon);
   free(ctx);
 }
diff --git a/hw/dv/dpi/usbdpi/usbdpi.core b/hw/dv/dpi/usbdpi/usbdpi.core
index 4318262..c4b244a 100644
--- a/hw/dv/dpi/usbdpi/usbdpi.core
+++ b/hw/dv/dpi/usbdpi/usbdpi.core
@@ -10,10 +10,18 @@
     files:
       - usbdpi.sv: { file_type: systemVerilogSource }
       - usbdpi.c: { file_type: cppSource }
+      - usbdpi_stream.c: { file_type: cppSource }
+      - usbdpi_test.c: { file_type: cppSource }
       - usb_crc.c: { file_type: cppSource }
-      - monitor_usb.c: { file_type: cppSource }
+      - usb_monitor.c: { file_type: cppSource }
+      - usb_transfer.c: { file_type: cppSource }
+      - usb_utils.c: { file_type: cppSource }
       - usbdpi.h: { file_type: cppSource, is_include_file: true }
-
+      - usbdpi_stream.h: { file_type: cppSource, is_include_file: true }
+      - usbdpi_test.h: { file_type: cppSource, is_include_file: true }
+      - usb_monitor.h: { file_type: cppSource, is_include_file: true }
+      - usb_transfer.h: { file_type: cppSource, is_include_file: true }
+      - usb_utils.h: { file_type: cppSource, is_include_file: true }
 
 targets:
   default:
diff --git a/hw/dv/dpi/usbdpi/usbdpi.h b/hw/dv/dpi/usbdpi/usbdpi.h
index 3028a13..eb2be17 100644
--- a/hw/dv/dpi/usbdpi/usbdpi.h
+++ b/hw/dv/dpi/usbdpi/usbdpi.h
@@ -8,30 +8,59 @@
 #define TOOL_VERILATOR 1
 #define TOOL_INCISIVE 0
 
+#include <assert.h>
 #include <limits.h>
+#include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
+#if USBDPI_STANDALONE
+// For stricter compilation checks, and building faster standalone
+typedef uint32_t svBitVecVal;
+#else
 #include <svdpi.h>
+#endif
+#include "usb_monitor.h"
+#include "usb_transfer.h"
+#include "usbdpi_stream.h"
 
-// How many bits in our frame (1ms on real hardware)
-#define FRAME_INTERVAL 256 * 8
+// Shall we employ a proper simulation of the frame interval (1ms)?
+// TODO - until such time as we can perform multiple control transfers in a
+//        single bus frame, this is impractical because the simulation takes too
+//        long and is apt to time out
+#define FULL_FRAME 0
+
+// How many bits in our frame
+//
+//   (frames on a Full Speed USB link are 1ms, with 12Mbps signalling, but the
+//    USB device supports only data transfers of up to 64 bytes, so a 1ms
+//    frame interval makes the simulation needlessly long)
+#if FULL_FRAME
+#define FRAME_INTERVAL 12000  // 12Mbps, 1ms frame time
+#else
+#define FRAME_INTERVAL 6000  // 0.5ms
+#endif
 
 // How many bits after start to assert VBUS
 #define SENSE_AT 20 * 8
 
-// Number of bytes in max output buffer line
-#define MAX_OBUF 80
-
 // Logging level (parameter to module)
-#define LOG_MON 0x01  // monitor_usb (packet level)
+#define LOG_MON 0x01  // USB monitor logging (packet level)
 #define LOG_BIT 0x08  // bit level
 
 // Error insertion
 #define INSERT_ERR_CRC 0
 #define INSERT_ERR_PID 0
 #define INSERT_ERR_BITSTUFF 0
+#define INSERT_ERR_DATA_TOGGLE 0
 
-// Index of the unimplemented endpoint to test
-#define UNIMPL_EP_ID 15
+// Endpoints used during top-level tests
+#define ENDPOINT_ZERO 0         // Endpoint Zero (for the Default Control Pipe)
+#define ENDPOINT_SERIAL0 1      // For basic serial communications test
+#define ENDPOINT_SERIAL1 2      // Second serial channel
+#define ENDPOINT_ISOCHRONOUS 3  // For basic testing of Isochronous transfers
+#define ENDPOINT_UNIMPLEMENTED \
+  15  // For testing of response to unimplemented
+      // endpoints
 
 #define D2P_BITS 11
 #define D2P_DP 1024
@@ -53,54 +82,163 @@
 #define P2D_DP 4
 #define P2D_D 8
 
-#define ST_IDLE 0
-#define ST_SEND 1
-#define ST_GET 2
-#define ST_SYNC 3
-#define ST_EOP 4
-#define ST_EOP0 5
-
 /* Remember these go LSB first */
 
 /* Sync is KJKJKJKK */
-#define USB_SYNC 0x2a
+#define USB_SYNC 0x2AU
 
-#define USB_PID_OUT 0xE1
-#define USB_PID_IN 0x69
-#define USB_PID_SOF 0xA5
-#define USB_PID_SETUP 0x2D
-#define USB_PID_DATA0 0xC3
-#define USB_PID_DATA1 0x4B
-#define USB_PID_ACK 0xD2
-#define USB_PID_NAK 0x5A
-#define USB_PID_STALL 0x1E
-#define USB_PID_NYET 0x96
+/* USB Packet IDentifiers */
+#define USB_PID_OUT 0xE1U
+#define USB_PID_IN 0x69U
+#define USB_PID_SOF 0xA5U
+#define USB_PID_SETUP 0x2DU
+#define USB_PID_DATA0 0xC3U
+#define USB_PID_DATA1 0x4BU
+#define USB_PID_ACK 0xD2U
+#define USB_PID_NAK 0x5AU
+#define USB_PID_STALL 0x1EU
+#define USB_PID_NYET 0x96U
+#define USB_PID_PING 0xB4U
 
-#define HS_STARTFRAME 0
-#define HS_WAITACK 1
-#define HS_SET_DATASTAGE 2
-#define HS_DS_RXDATA 3
-#define HS_DS_SENDACK 4
-#define HS_DONEDADR 5
-#define HS_REQDATA 6
-#define HS_WAITDATA 7
-#define HS_SENDACK 8
-#define HS_WAIT_PKT 9
-#define HS_ACKIFDATA 10
-#define HS_SENDHI 11
-#define HS_EMPTYDATA 12
-#define HS_WAITACK2 13
-#define HS_NEXTFRAME 14
+/* USB Standard Request Codes */
+#define USB_REQ_GET_STATUS 0x00U
+#define USB_REQ_CLEAR_FEATURE 0x01U
+#define USB_REQ_SET_FEATURE 0x03U
+#define USB_REQ_SET_ADDRESS 0x05U
+#define USB_REQ_GET_DESCRIPTOR 0x06U
+#define USB_REQ_SET_DESCRIPTOR 0x07U
+#define USB_REQ_GET_CONFIGURATION 0x08U
+#define USB_REQ_SET_CONFIGURATION 0x09U
+#define USB_REQ_GET_INTERFACE 0x0AU
+#define USB_REQ_SET_INTERFACE 0x0BU
+#define USB_REQ_SYNCH_FRAME 0x0CU
 
-#define SEND_MAX 32
-#include <stdint.h>
+/* USB Descriptor Types */
+#define USB_DESC_TYPE_DEVICE 0x01U
+#define USB_DESC_TYPE_CONFIGURATION 0x02U
+#define USB_DESC_TYPE_STRING 0x03U
+#define USB_DESC_TYPE_INTERFACE 0x04U
+#define USB_DESC_TYPE_ENDPOINT 0x05U
+#define USB_DESC_TYPE_DEVICE_QUALIFIER 0x06U
+#define USB_DESC_TYPE_OTHER_SPEED_CONFIG 0x07U
+#define USB_DESC_TYPE_INTERFACE_POWER 0x08U
+
+// Device address to be used for the USBDEV IP block
+#define USBDEV_ADDRESS 2
+
+// Unknown device address (check USBDEV ignores traffic to other devices)
+#define UKDEV_ADDRESS 6
+
+// Maximum number of endpoints implemented by the USBDEV IP
+#define USBDEV_MAX_ENDPOINTS 12U
+
+// Maximum number of endpoints supported by the DPI model
+#define USBDPI_MAX_ENDPOINTS 16U
+
+// Maximum number of bidirection LFSR-generated byte streams that may be
+// supported simultaneously
+#define USBDPI_MAX_STREAMS (USBDPI_MAX_ENDPOINTS - 1U)
+
+// Maximum number of simultaneous transfer descriptors
+//   (The host model may simply avoid polling for further IN transfers
+//    whilst there are no further desciptors available)
+#define USBDPI_MAX_TRANSFERS 0x10U
+
+// Time intervals for common transactions, in bits
+// (allowing for bit stuffing and bus turnaround etc; for setting timeouts)
+#define USBDPI_INTERVAL_SETUP_STAGE 200U
+
+// Return the time at which the given number of bit intervals shall have elapsed
+#define USBDPI_TIMEOUT(ctx, bits) ((ctx)->tick_bits + (bits))
+
+// Maximum length of test status message
+#define USBDPI_MAX_TEST_MSG_LEN 80U
+
+// Vendor-specific commands used for test framework
+#define USBDPI_VENDOR_TEST_CONFIG 0x7CU
+#define USBDPI_VENDOR_TEST_STATUS 0x7EU
+
+// Next DATAx PID to be used (transmission) or expected (reception)
+#define DATA_TOGGLE_ADVANCE(pid) \
+  ((pid) == USB_PID_DATA1 ? USB_PID_DATA0 : USB_PID_DATA1)
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+// USBDPI driver states
+typedef enum {
+  ST_IDLE = 0,
+  ST_SEND = 1,
+  ST_GET = 2,
+  ST_SYNC = 3,
+  ST_EOP = 4,
+  ST_EOP0 = 5
+} usbdpi_drv_state_t;
+
+// Test steps
+typedef enum {
+
+  STEP_BUS_RESET = 0u,
+  STEP_SET_DEVICE_ADDRESS,
+  STEP_GET_DEVICE_DESCRIPTOR,
+  STEP_GET_CONFIG_DESCRIPTOR,
+  STEP_GET_FULL_CONFIG_DESCRIPTOR,
+  STEP_SET_DEVICE_CONFIG,
+
+  // Read test configuration
+  // This is a bespoke 'vendor' command via which we inquire of the CPU
+  // software what behaviour is required
+  STEP_GET_TEST_CONFIG,
+  // Report test status (pass/failure) to the CPU software
+  STEP_SET_TEST_STATUS,
+
+  // usbdev_test
+  STEP_FIRST_READ,
+  STEP_READ_BAUD,
+  STEP_SECOND_READ,
+  STEP_SET_BAUD,
+  STEP_THIRD_READ,
+  STEP_TEST_ISO1,
+  STEP_TEST_ISO2,
+  STEP_ENDPT_UNIMPL_SETUP,
+  STEP_ENDPT_UNIMPL_OUT,
+  STEP_ENDPT_UNIMPL_IN,
+  STEP_DEVICE_UK_SETUP,
+  STEP_IDLE_START,
+  STEP_IDLE_END = STEP_IDLE_START + 4,
+
+  // usbdev_stream_test
+  STEP_STREAM_SERVICE = 0x20u,
+
+  // Disconnect the device and stop
+  STEP_BUS_DISCONNECT = 0x7fu
+} usbdpi_test_step_t;
+
+// Host states
+typedef enum {
+  HS_STARTFRAME = 0,
+  HS_WAITACK = 1,
+  HS_SET_DATASTAGE = 2,
+
+  HS_DS_RXDATA = 3,
+  HS_DS_SENDACK = 4,
+  HS_DONEDADR = 5,
+  HS_REQDATA = 6,
+  HS_WAITDATA = 7,
+  HS_SENDACK = 8,
+  HS_WAIT_PKT = 9,
+  HS_ACKIFDATA = 10,
+  HS_SENDHI = 11,
+  HS_EMPTYDATA = 12,
+  HS_WAITACK2 = 13,
+  HS_STREAMOUT = 14,
+  HS_STREAMIN = 15,
+  HS_NEXTFRAME = 16
+} usbdpi_host_state_t;
+
 typedef enum usbdpi_bus_state {
-  kUsbIdle,
+  kUsbIdle = 0,
   kUsbControlSetup,
   kUsbControlSetupAck,
   kUsbControlDataOut,
@@ -108,6 +246,7 @@
   kUsbControlStatusInToken,
   kUsbControlStatusInData,
   kUsbControlStatusInAck,
+
   kUsbControlDataInToken,
   kUsbControlDataInData,
   kUsbControlDataInAck,
@@ -116,6 +255,7 @@
   kUsbIsoToken,
   kUsbIsoDataIn,
   kUsbIsoDataOut,
+
   kUsbBulkOut,
   kUsbBulkOutAck,
   kUsbBulkInToken,
@@ -123,45 +263,186 @@
   kUsbBulkInAck,
 } usbdpi_bus_state_t;
 
+/**
+ * USB DPI state information
+ */
 struct usbdpi_ctx {
+  // Bus signalling state
   usbdpi_bus_state_t bus_state;
-  int loglevel;
-  FILE *mon_file;
-  char mon_pathname[PATH_MAX];
-  void *mon;
-  int retries;
-  int last_pu;
-  int lastrxpid;
-  int tick;
-  int tick_bits;
-  int recovery_time;
-  int frame;
-  int framepend;
-  int lastframe;
-  int inframe;
-  int state;
-  int wait;
   uint32_t driving;
   int linebits;
   int bit;
   int byte;
-  int bytes;
-  int datastart;
-  int hostSt;
-  uint8_t data[SEND_MAX];
+  /**
+   * Test number, retrieved from the software
+   */
+  uint16_t test_number;
+  /**
+   * Test-specific arguments
+   */
+  uint8_t test_arg[4];
+  /**
+   * Test status
+   * TODO - introduce enum indicating the test status, or borrow from
+   *        the existing source tree; we indicate progress too in other places
+   */
+  uint32_t test_status;
+  char test_msg[USBDPI_MAX_TEST_MSG_LEN];
+
+  /**
+   * Context for IN endpoints
+   */
+  struct {
+    /**
+     * Next DATAx PID (DATA0 or DATA1) expected from device
+     */
+    uint8_t next_data;
+  } ep_in[USBDPI_MAX_ENDPOINTS];
+  /**
+   * Context for OUT endpoints
+   */
+  struct {
+    /**
+     * Next DATAx (DATA0 or DATA1) to be transmitted; advanced when ACKed
+     * and reset after SETUP packet transmitted
+     */
+    uint8_t next_data;
+  } ep_out[USBDPI_MAX_ENDPOINTS];
+
+  /**
+   * Number of data streams being used
+   */
+  uint8_t nstreams;
+  /**
+   * Stream number of next stream to attempt IN transfers
+   */
+  uint8_t stream_in;
+  /**
+   * Stream number of next stream to attempt OUT transfers
+   */
+  uint8_t stream_out;
+  /**
+   * Context for streaming data test (usbdev_stream_test)
+   */
+  usbdpi_stream_t stream[USBDPI_MAX_STREAMS];
+
+  // Diagnostic logging and bus monitoring
+  int loglevel;
+  char mon_pathname[FILENAME_MAX];
+
+  /**
+   * USB monitor instance
+   */
+  usb_monitor_ctx_t *mon;
+
+  /**
+   * Host controller state
+   */
+  usbdpi_host_state_t hostSt;
+  /**
+   * Transfer currently being received from the DUT (NULL iff none)
+   */
+  usbdpi_transfer_t *recving;
+  /***
+   * Transfer currently being sent to the DUT (NULL iff none)
+   */
+  usbdpi_transfer_t *sending;
+
+  uint32_t last_pu;
+  uint8_t lastrxpid;
+
+  /**
+   * Count of clock cycles
+   */
+  uint32_t tick;
+  /**
+   * Current time in USB bit intervals
+   */
+  uint32_t tick_bits;
+  /**
+   * End time of recovery interval (following device attachment)
+   */
+  uint32_t recovery_time;
+
+  /**
+   * Test step number
+   */
+  usbdpi_test_step_t step;
+
+  // Bus framing
+  // Note: USB frame numbers are transmitted as 11-bit fields [0,0x7ffU]
+  uint16_t frame;
+  uint16_t framepend;
+
+  /**
+   * Time at which the current frame started (bit intervals)
+   */
+  uint32_t frame_start;
+
+  /**
+   * Wait timeout for current operation (bit intervals)
+   */
+  uint32_t wait;
+
+  /**
+   * DPI driver state
+   */
+  usbdpi_drv_state_t state;
+
   int baudrate_set_successfully;
+
+  /**
+   * Device address currently assigned to the DUT
+   */
+  uint8_t dev_address;
+  /**
+   * Length of configuration descriptor
+   */
+  uint16_t cfg_desc_len;
+  /**
+   * Linked-list of free transfer descriptors
+   */
+  usbdpi_transfer_t *free;
+
+  /**
+   * Small pool of transfer descriptors
+   */
+  usbdpi_transfer_t transfer_pool[USBDPI_MAX_TRANSFERS];
 };
 
+/**
+ * Create a USB DPI instance, returning a 'chandle' for later use
+ */
 void *usbdpi_create(const char *name, int loglevel);
-void usbdpi_device_to_host(void *ctx_void, const svBitVecVal *usb_d2p);
-char usbdpi_host_to_device(void *ctx_void, const svBitVecVal *usb_d2p);
+/**
+ * Close a USB DPI instance
+ */
 void usbdpi_close(void *ctx_void);
-uint32_t CRC5(uint32_t dwInput, int iBitcnt);
-uint32_t CRC16(uint8_t *data, int bytes);
 
-void *monitor_usb_init(void);
-void monitor_usb(void *mon, FILE *mon_file, int log, int tick, int hdrive,
-                 int p2d, int d2p, int *lastpid);
+/**
+ * Respond to device outputs
+ */
+void usbdpi_device_to_host(void *ctx_void, const svBitVecVal *usb_d2p);
+
+/**
+ * Update DPI model outputs
+ */
+uint8_t usbdpi_host_to_device(void *ctx_void, const svBitVecVal *usb_d2p);
+
+/**
+ * Return DPI model diagnostic information for viewing in waveforms
+ */
+void usbdpi_diags(void *ctx_void, svBitVecVal *diags);
+
+/**
+ * Calculate 5-bit CRC used to check token packets
+ */
+uint32_t CRC5(uint32_t dwInput, int iBitcnt);
+
+/**
+ * Calculate 16-bit CRC used to check data fields
+ */
+uint32_t CRC16(const uint8_t *data, int bytes);
 
 #ifdef __cplusplus
 }
diff --git a/hw/dv/dpi/usbdpi/usbdpi.sv b/hw/dv/dpi/usbdpi/usbdpi.sv
index 8940cec..6730993 100644
--- a/hw/dv/dpi/usbdpi/usbdpi.sv
+++ b/hw/dv/dpi/usbdpi/usbdpi.sv
@@ -45,6 +45,9 @@
   import "DPI-C" function
     byte usbdpi_host_to_device(input chandle ctx, input bit [10:0] d2p);
 
+  import "DPI-C" function
+    void usbdpi_diags(input chandle ctx, output bit [95:0] diags);
+
   chandle ctx;
 
   initial begin
@@ -56,6 +59,143 @@
     usbdpi_close(ctx);
   end
 
+  // USB Packet IDentifier values, for waveform viewing
+  typedef enum bit [7:0] {
+    OUT = 8'hE1,
+    IN = 8'h69,
+    SOF = 8'hA5,
+    SETUP = 8'h2D,
+    DATA0 = 8'hC3,
+    DATA1 = 8'h4B,
+    ACK = 8'hD2,
+    NAK = 8'h5A,
+    STALL = 8'h1E,
+    NYET = 8'h96,
+    PING = 8'hB4
+  } usb_pid_t;
+
+  // Host state
+  // Note: MUST be kept consistent with usbdpi_host_state_t in usbdpi.h
+  typedef enum bit [4:0] {
+    HS_STARTFRAME = 0,
+    HS_WAITACK = 1,
+    HS_SET_DATASTAGE = 2,
+    HS_DS_RXDATA = 3,
+    HS_DS_SENDACK = 4,
+    HS_DONEDADR = 5,
+    HS_REQDATA = 6,
+    HS_WAITDATA = 7,
+    HS_SENDACK = 8,
+    HS_WAIT_PKT = 9,
+    HS_ACKIFDATA = 10,
+    HS_SENDHI = 11,
+    HS_EMPTYDATA = 12,
+    HS_WAITACK2 = 13,
+    HS_STREAMOUT = 14,
+    HS_STREAMIN = 15,
+    HS_NEXTFRAME = 16
+  } usbdpi_host_state_t;
+
+  // Bus state
+  // Note: MUST be kept consistent with usbdpi_bus_state in usbdpi.h
+  typedef enum bit [4:0] {
+    kUsbIdle = 0,
+    kUsbControlSetup,
+    kUsbControlSetupAck,
+    kUsbControlDataOut,
+    kUsbControlDataOutAck,  // 4
+    kUsbControlStatusInToken,
+    kUsbControlStatusInData,
+    kUsbControlStatusInAck,
+
+    kUsbControlDataInToken,  // 8
+    kUsbControlDataInData,
+    kUsbControlDataInAck,
+    kUsbControlStatusOut,
+    kUsbControlStatusOutAck,  // 0xc
+    kUsbIsoToken,
+    kUsbIsoDataIn,
+    kUsbIsoDataOut,
+
+    kUsbBulkOut,  // 0x10
+    kUsbBulkOutAck,
+    kUsbBulkInToken,
+    kUsbBulkInData,
+    kUsbBulkInAck
+  } usbdpi_bus_state_t;
+
+  // USB monitor state
+  // Note: MUST be kept consistent with usbdpi_monitor_state_t in usb_monitor.c
+  typedef enum bit [1:0] {
+    MS_IDLE = 0,
+    MS_GET_PID,
+    MS_GET_BYTES
+  } usb_monitor_state_t;
+
+  // USB driver state
+  // Note: MUST be kept consistent with usbdpi_drv_state_t in usbdpi.h
+  typedef enum bit [3:0] {
+    ST_IDLE = 0,
+    ST_SEND = 1,
+    ST_GET = 2,
+    ST_SYNC = 3,
+    ST_EOP = 4,
+    ST_EOP0 = 5
+  } usbdpi_drv_state_t;
+
+  // Test steps
+  typedef enum bit [6:0] {
+
+    STEP_BUS_RESET = 0,
+    STEP_SET_DEVICE_ADDRESS,
+    STEP_GET_DEVICE_DESCRIPTOR,
+    STEP_GET_CONFIG_DESCRIPTOR,
+    STEP_GET_FULL_CONFIG_DESCRIPTOR,
+    STEP_SET_DEVICE_CONFIG,
+
+    STEP_GET_TEST_CONFIG,
+    STEP_SET_TEST_STATUS,
+
+    // usbdev_test
+    STEP_FIRST_READ,
+    STEP_READ_BAUD,
+    STEP_SECOND_READ,
+    STEP_SET_BAUD,
+    STEP_THIRD_READ,
+    STEP_TEST_ISO1,
+    STEP_TEST_ISO2,
+    STEP_ENDPT_UNIMPL_SETUP,
+    STEP_ENDPT_UNIMPL_OUT,
+    STEP_ENDPT_UNIMPL_IN,
+    STEP_DEVICE_UK_SETUP,
+    STEP_IDLE_START,
+    STEP_IDLE_END = STEP_IDLE_START + 4,
+
+    // usbdev_stream_test
+    STEP_STREAM_SERVICE = 'h20,
+
+    // Disconnect the device and stop
+    STEP_BUS_DISCONNECT = 'h7f
+  } usbdpi_test_step_t;
+
+  // Make usb_monitor diagnostic information viewable in waveforms
+  bit [9:0] c_spare1;
+  usb_monitor_state_t c_mon_state;
+  bit [3:0] c_mon_bits;
+  bit [7:0] c_mon_byte;
+  usb_pid_t c_mon_pid;
+  // Make usbdpi diagnostic information viewable in waveforms
+  usbdpi_test_step_t c_step;
+  usbdpi_bus_state_t c_bus_state;
+  bit [31:0] c_tickbits;
+  bit [10:0] c_frame;
+  usbdpi_host_state_t c_hostSt;
+  usbdpi_drv_state_t c_state;
+  always @(posedge clk_48MHz_i)
+    usbdpi_diags(ctx, {c_spare1, c_mon_state, c_mon_bits, c_mon_byte, c_mon_pid,
+                       c_step, c_bus_state, c_tickbits, c_frame, c_hostSt,
+                       c_state});
+
   logic [10:0] d2p;
   logic [10:0] d2p_r;
   logic       unused_dummy;
@@ -64,7 +204,8 @@
   logic       dp_int, dn_int, d_last;
   logic       flip_detect, pullup_detect, rx_enable;
 
-  // Detect a request to flip pins by the DN resistor being applied
+  // Detect a request to flip pins by the DN resistor being applied;
+  //   it's a full speed device so the pullup resistor is on the D+ signal
   assign flip_detect = pullupdn_d2p;
   assign pullup_detect = pullupdp_d2p || pullupdn_d2p;
   assign rx_enable = rx_enable_d2p;
diff --git a/hw/dv/dpi/usbdpi/usbdpi_stream.c b/hw/dv/dpi/usbdpi/usbdpi_stream.c
new file mode 100644
index 0000000..b4ac7d5
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usbdpi_stream.c
@@ -0,0 +1,591 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "usb_utils.h"
+#include "usbdpi.h"
+
+// Seed numbers for the LFSR generators in each transfer direction for
+// the given stream number
+#define USBTST_LFSR_SEED(s) (uint8_t)(0x10U + (s)*7U)
+#define USBDPI_LFSR_SEED(s) (uint8_t)(0x9BU - (s)*7U)
+// Seed number of packet retrying
+#define RETRY_LFSR_SEED(s) (uint8_t)(0x24U + (s)*7U)
+
+// Simple LFSR for 8-bit sequences
+#define LFSR_ADVANCE(lfsr) \
+  (((lfsr) << 1) ^         \
+   ((((lfsr) >> 1) ^ ((lfsr) >> 2) ^ ((lfsr) >> 3) ^ ((lfsr) >> 7)) & 1u))
+
+// Stream signature words
+#define STREAM_SIGNATURE_HEAD 0x579EA01AU
+#define STREAM_SIGNATURE_TAIL 0x160AE975U
+
+// Verbose logging/diagnostic reporting
+// TODO - consider folding this into the existing log level
+static const bool verbose = false;
+
+static const bool expect_sig = true;
+
+// Determine the next stream for which IN data packets shall be requested
+static inline unsigned in_stream_next(usbdpi_ctx_t *ctx);
+
+// Determine the next stream for which OUT data shall be sent
+static inline unsigned out_stream_next(usbdpi_ctx_t *ctx);
+
+// Check a data packet received from the test software (usbdev_stream_test)
+static bool stream_data_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                              const usbdpi_transfer_t *rx, bool accept);
+
+// Generate a data packet as if it had been received from the device
+static usbdpi_transfer_t *stream_data_gen(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                                          unsigned len);
+
+// Check a data packet received from the test software (usbdev_stream_test)
+// and collect the data, combined with our LFSR-generated random stream,
+// for later transmission back to the device
+static usbdpi_transfer_t *stream_data_process(usbdpi_ctx_t *ctx,
+                                              usbdpi_stream_t *s,
+                                              usbdpi_transfer_t *rx);
+
+// Check the stream signature
+static bool stream_sig_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                             usbdpi_transfer_t *rx);
+
+// Determine the next stream for which IN data packets shall be requested
+inline unsigned in_stream_next(usbdpi_ctx_t *ctx) {
+  uint8_t id = ctx->stream_in;
+  if (++id >= ctx->nstreams) {
+    id = 0U;
+  }
+  ctx->stream_in = id;
+  return id;
+}
+
+// Determine the next stream for which OUT data shall be sent
+inline unsigned out_stream_next(usbdpi_ctx_t *ctx) {
+  uint8_t id = ctx->stream_out;
+  if (++id >= ctx->nstreams) {
+    id = 0U;
+  }
+  ctx->stream_out = id;
+  return id;
+}
+
+// Initialize streaming state for the given number of streams
+bool streams_init(usbdpi_ctx_t *ctx, unsigned nstreams, bool retrieve,
+                  bool checking, bool retrying, bool send) {
+  assert(ctx);
+
+  // Can we support the requested number of streams?
+  if (!nstreams || nstreams > USBDPI_MAX_STREAMS) {
+    return false;
+  }
+
+  if (verbose) {
+    printf("[usbdpi] Stream test running with %u streams(s)\n", nstreams);
+    printf("[usbdpi] - retrieve %c checking %c retrying %c send %c\n",
+           retrieve ? 'Y' : 'N', checking ? 'Y' : 'N', retrying ? 'Y' : 'N',
+           send ? 'Y' : 'N');
+  }
+
+  // Remember the number of streams and initialize the arbitration of
+  // IN and OUT traffic
+  ctx->nstreams = nstreams;
+  ctx->stream_in = 0U;
+  ctx->stream_out = nstreams - 1U;
+
+  for (unsigned id = 0U; id < nstreams; id++) {
+    // Poll device for IN packets in streaming test?
+    ctx->stream[id].retrieve = retrieve;
+    // Attempt to sent OUT packets to device in streaming test?
+    ctx->stream[id].send = send;
+    // Checking of received data against expected LFSR output
+    ctx->stream[id].checking = checking;
+    // Request retrying of IN packets, feigning error
+    ctx->stream[id].retrying = retrying;
+    // Endpoints to be used by this stream
+    ctx->stream[id].ep_in = 1U + id;
+    ctx->stream[id].ep_out = 1U + id;
+    // LFSR state for this byte stream
+    ctx->stream[id].tst_lfsr = USBTST_LFSR_SEED(id);
+    ctx->stream[id].dpi_lfsr = USBDPI_LFSR_SEED(id);
+    // LFSR-controlled packet retrying state
+    ctx->stream[id].retry_lfsr = RETRY_LFSR_SEED(id);
+    ctx->stream[id].nretries = 0U;
+    // No received packets
+    ctx->stream[id].received = NULL;
+  }
+  return true;
+}
+
+// Check a data packet received from the test software (usbdev_stream_test)
+bool stream_data_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                       const usbdpi_transfer_t *rx, bool accept) {
+  assert(rx);
+
+  // The byte count _includes_ the DATAx PID and the two CRC bytes
+  //
+  // Note: we are expecting a LFSR-generated byte stream, but we do not
+  //       make assumptions about the number or size of the data packets
+  //       that make up the stream
+
+  unsigned num_bytes = transfer_length(rx);
+  unsigned idx = rx->data_start + 1u;  // Skip past the DATAx PID
+  bool ok = false;
+
+  // Validate the received packet - data length valid and checksum present
+  if (num_bytes >= sizeof(rx->data) || idx + 2u > num_bytes) {
+    printf("[usbdpi] Unexpected/malformed data packet (0x%x 0x%x)\n", idx,
+           num_bytes);
+  } else {
+    // Data field within received packet
+    const uint8_t *sp = &rx->data[idx];
+    num_bytes -= 3u;
+
+    // Check that the CRC16 checksum of the data field is as expected
+    uint16_t rx_crc = sp[num_bytes] | (sp[num_bytes + 1u] << 8);
+    uint16_t crc = CRC16(sp, num_bytes);
+    if (rx_crc != crc) {
+      printf("[usbdpi] Mismatched CRC16 0x%04x received, expected 0x%04x\n",
+             rx_crc, crc);
+    } else {
+      // Data toggle synchronization
+      unsigned pid = ctx->ep_in[s->ep_in].next_data;
+      if (rx->data[0] == pid) {
+        // If we've decided to reject this packet then we still check its
+        // content but we do not advance the data toggle because we're
+        // pretending that we didn't receive it successfully
+        if (accept) {
+          ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(pid);
+        }
+
+        // Tentatively acceptable, but we still have to check and report any and
+        // all mismatched bytes
+        ok = accept;
+      }
+
+      // Iff running a performance investigation, checking may be undesired
+      // because it causes us to reject and retry the transmission
+      if (s->checking) {
+        // Note: use a local copy of the LFSR so that we can check the data
+        //       field even on those packets that we choose to reject
+        uint8_t tst_lfsr = s->tst_lfsr;
+        while (num_bytes-- > 0U) {
+          uint8_t recvd = *sp++;
+          if (recvd != tst_lfsr) {
+            printf(
+                "[usbdpi] Mismatched data from device 0x%02x, "
+                "expected 0x%02x\n",
+                recvd, tst_lfsr);
+            ok = false;
+          }
+          // Advance our local LFSR
+          tst_lfsr = LFSR_ADVANCE(tst_lfsr);
+        }
+
+        // Update the LFSR only if we've accepted valid data and will not
+        // be receiving this data again
+        if (accept && ok) {
+          s->tst_lfsr = tst_lfsr;
+        }
+      } else {
+        printf("[usbdpi] Warning: Stream data checking disabled\n");
+      }
+    }
+  }
+
+  return ok;
+}
+
+// Generate a data packet as if it had been received from the device
+usbdpi_transfer_t *stream_data_gen(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                                   unsigned len) {
+  usbdpi_transfer_t *tr = transfer_alloc(ctx);
+  if (tr) {
+    // Pretend that we have successfully received the packet with the correct
+    // data toggling...
+    uint8_t data = ctx->ep_in[s->ep_in].next_data;
+    ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(data);
+    // ...and that the data is as expected
+    uint8_t *dp = transfer_data_start(tr, data, len);
+    for (unsigned idx = 0U; idx < len; idx++) {
+      dp[idx] = s->tst_lfsr;
+      s->tst_lfsr = LFSR_ADVANCE(s->tst_lfsr);
+    }
+    transfer_data_end(tr, dp + len);
+  }
+  return tr;
+}
+
+// Process a received data packet to produce a corresponding reply packet
+// by XORing our LFSR output with the received data
+//
+// Note: for now we do this even if the received data mismatches because
+//       only the CPU software has the capacity to decide upon and report
+//       test status
+usbdpi_transfer_t *stream_data_process(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                                       usbdpi_transfer_t *rx) {
+  // Note: checkStreamData has already been called on this packet
+  assert(rx);
+
+  // The byte count _includes_ the DATAx PID and the two CRC bytes
+  unsigned num_bytes = rx->num_bytes;
+  unsigned idx = rx->data_start + 1u;  // Skip past the DATAx PID
+
+  // Data field within received packet
+  const uint8_t *sp = &rx->data[idx];
+  num_bytes -= 3u;
+
+  // Allocate a new buffer for the reply
+  usbdpi_transfer_t *reply = transfer_alloc(ctx);
+  assert(reply);
+
+  // Construct OUT token packet to the target endpoint, using the
+  // appropriate DATAx PID
+  const uint8_t ep_out = s->ep_out;
+  transfer_token(reply, USB_PID_OUT, ctx->dev_address, ep_out);
+  uint8_t *dp =
+      transfer_data_start(reply, ctx->ep_out[ep_out].next_data, num_bytes);
+  assert(dp);
+
+  while (num_bytes-- > 0U) {
+    uint8_t recvd = *sp++;
+
+    // Simply XOR the two LFSR-generated streams together
+    *dp++ = recvd ^ s->dpi_lfsr;
+    if (verbose) {
+      printf("[usbdpi] 0x%02x <- 0x%02x ^ 0x%02x\n", *(dp - 1), recvd,
+             s->dpi_lfsr);
+    }
+
+    // Advance our LFSR
+    //
+    // TODO - decide whether we want to do this here; if the device
+    // responds with a NAK, requiring us to retry, or we decide to
+    // resend the packet, then we don't want to advance again
+    s->dpi_lfsr = LFSR_ADVANCE(s->dpi_lfsr);
+  }
+
+  transfer_data_end(reply, dp);
+
+  return reply;
+}
+
+// Check the stream signature
+bool stream_sig_check(usbdpi_ctx_t *ctx, usbdpi_stream_t *s,
+                      usbdpi_transfer_t *rx) {
+  // Packet should be PID, data field and CRC16
+  if (transfer_length(rx) == 3U + 0x10U) {
+    const uint8_t *sig = transfer_data_field(rx);
+    if (sig) {
+      // Signature format:
+      //   Bits Description
+      //   32   head signature
+      //   8    initial vaue of LFSR
+      //   8    stream number
+      //   16   reserved, SBZ
+      //   32   number of bytes to be transferred
+      //   32   tail signature
+      // Note: all 32-bit quantities are in little endian order
+
+      uint32_t num_bytes = get_le32(&sig[8]);
+      if (verbose) {
+        printf("[usbdpi] Stream signature at %p head 0x%x tail 0x%x\n", sig,
+               get_le32(&sig[0]), get_le32(&sig[12]));
+      }
+
+      // Basic validation check; words are transmitted in little endian order
+      if (get_le32(&sig[0]) == STREAM_SIGNATURE_HEAD &&
+          get_le32(&sig[12]) == STREAM_SIGNATURE_TAIL &&
+          // sanity check on transfer length, though we rely upon the CPU
+          // software to send, receive and count the number of bytes
+          num_bytes > 0U && num_bytes < 0x10000000U && !sig[6] && !sig[7]) {
+        // Signature includes the initial value of the device-side LFSR
+        s->tst_lfsr = sig[4];
+        // Update data toggle
+        uint8_t pid = transfer_data_pid(rx);
+        ctx->ep_in[s->ep_in].next_data = DATA_TOGGLE_ADVANCE(pid);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// Service streaming data (usbdev_stream_test)
+void streams_service(usbdpi_ctx_t *ctx) {
+  if (verbose) {
+    //    printf("[usbdpi] streams_service hostSt %u in %u out %u\n",
+    //    ctx->hostSt,
+    //           ctx->stream_in, ctx->stream_out);
+  }
+
+  // Maximum time for transmission of a packet ought to be circa 80 bytes of
+  // data, 640 bits. Allowing for bitstuffing this means we need to leave ~800
+  const unsigned min_time_left = 800U;
+
+  switch (ctx->hostSt) {
+    // --------------------------------------------
+    // Try to transmit a data packet to the device
+    // --------------------------------------------
+    case HS_STARTFRAME:
+    case HS_STREAMOUT: {
+      // Decide whether we have enough time within this frame to attempt
+      // another transmission
+      uint32_t next_frame = ctx->frame_start + FRAME_INTERVAL;
+      if ((next_frame - ctx->tick_bits) > min_time_left) {  // HACK
+        unsigned id = out_stream_next(ctx);
+        usbdpi_stream_t *s = &ctx->stream[id];
+        if (s->send) {
+          // Start by trying to transmit a data packet that we've received, if
+          // any
+          if (s->received) {
+            if (ctx->sending) {
+              transfer_release(ctx, ctx->sending);
+            }
+
+            // Scramble the oldest received packet with our LFSR-generated byte
+            // stream and send it to the device
+            usbdpi_transfer_t *reply = stream_data_process(ctx, s, s->received);
+            transfer_send(ctx, reply);
+
+            uint32_t max_bits =
+                transfer_length(ctx->sending) * 10 + 160;  // HACK
+            ctx->wait = USBDPI_TIMEOUT(ctx, max_bits);
+            ctx->bus_state = kUsbBulkOut;
+            ctx->lastrxpid = 0;
+            ctx->hostSt = HS_WAITACK;
+          } else {
+            // Nothing to send, try receiving...
+            ctx->hostSt = HS_STREAMIN;
+          }
+        } else {
+          // We're not sending anything - discard any received data
+          while (s->received) {
+            usbdpi_transfer_t *tr = s->received;
+            s->received = tr->next;
+            transfer_release(ctx, tr);
+          }
+          ctx->hostSt = HS_STREAMIN;
+        }
+      } else {
+        // Wait until the next bus frame
+        ctx->hostSt = HS_NEXTFRAME;
+      }
+    } break;
+
+    // Await acknowledgement of the packet that we just transmitted
+    case HS_WAITACK:
+      if (ctx->sending) {
+        // Forget reference to the buffer we just tried to send; the received
+        // packet remains in the list of received buffers to try again later
+        transfer_release(ctx, ctx->sending);
+        ctx->sending = NULL;
+      }
+
+      if (ctx->bus_state == kUsbBulkOutAck) {
+        bool proceed = false;
+
+        if (verbose) {
+          printf("[usbdpi] OUT - response is PID 0x%02x from device (%s)\n",
+                 ctx->lastrxpid, decode_pid(ctx->lastrxpid));
+        }
+
+        switch (ctx->lastrxpid) {
+          case USB_PID_ACK: {
+            // Transmitted packet was accepted, so we can retire it...
+            usbdpi_stream_t *s = &ctx->stream[ctx->stream_out];
+            usbdpi_transfer_t *rx = s->received;
+            assert(rx);
+            s->received = rx->next;
+            transfer_release(ctx, rx);
+
+            uint8_t ep_out = s->ep_out;
+            ctx->ep_out[ep_out].next_data =
+                DATA_TOGGLE_ADVANCE(ctx->ep_out[ep_out].next_data);
+            proceed = true;
+          } break;
+
+          // We may receive a NAK from the device if it is unable to receive the
+          // packet right now
+          case USB_PID_NAK:
+            printf(
+                "[usbdpi] frame 0x%x tick_bits 0x%x NAK received from device\n",
+                ctx->frame, ctx->tick_bits);
+            proceed = true;
+            break;
+
+          default:
+            printf("[usbdpi] Unexpected PID 0x%02x from device (%s)\n",
+                   ctx->lastrxpid, decode_pid(ctx->lastrxpid));
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+        }
+
+        ctx->hostSt = proceed ? HS_STREAMIN : HS_NEXTFRAME;
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf("[usbdpi] Timed out waiting for OUT response\n");
+        ctx->hostSt = HS_NEXTFRAME;
+      }
+      break;
+
+    // ---------------------------------------------
+    // Try to collect a data packet from the device
+    // ---------------------------------------------
+    case HS_STREAMIN: {
+      // Decide whether we have enough time within this frame to attempt
+      // another fetch
+      //
+      // TODO - find out what the host behaviour should be at this point;
+      //        the device must be required to respond within a certain
+      //        time interval, and then the bus transmission speed
+      //        determines the maximum delay
+      uint32_t next_frame = ctx->frame_start + FRAME_INTERVAL;
+      if ((next_frame - ctx->tick_bits) > min_time_left) {  // HACK
+        unsigned id = in_stream_next(ctx);
+        usbdpi_stream_t *s = &ctx->stream[id];
+        if (s->retrieve) {
+          // Ensure that a buffer is available for constructing a transfer
+          usbdpi_transfer_t *tr = ctx->sending;
+          if (!tr) {
+            tr = transfer_alloc(ctx);
+            assert(tr);
+
+            ctx->sending = tr;
+          }
+
+          transfer_token(tr, USB_PID_IN, ctx->dev_address, s->ep_in);
+
+          transfer_send(ctx, tr);
+          ctx->bus_state = kUsbBulkInToken;
+          ctx->hostSt = HS_WAIT_PKT;
+          ctx->lastrxpid = 0;
+        } else {
+          // We're not required to poll for IN data, but if we're sending we
+          //   must still fake the reception of valid packet data because
+          //   the sw test will be expecting valid data
+          if (s->send && !s->received) {
+            // For simplicity we just create max length packets
+            const unsigned len = USBDEV_MAX_PACKET_SIZE;
+            s->received = stream_data_gen(ctx, s, len);
+          }
+          ctx->hostSt = HS_STREAMOUT;
+        }
+      } else {
+        // Wait until the next bus frame
+        ctx->hostSt = HS_NEXTFRAME;
+      }
+    } break;
+
+    case HS_WAIT_PKT:
+      // Wait max time for a response + packet
+      ctx->wait = ctx->tick_bits + 18 + 8 + 8 + 64 * 8 + 16;
+      ctx->hostSt = HS_ACKIFDATA;
+      break;
+    case HS_ACKIFDATA:
+      if (ctx->bus_state == kUsbBulkInData && ctx->recving) {
+        // We have a response from the device
+        switch (ctx->lastrxpid) {
+          case USB_PID_DATA0:
+          case USB_PID_DATA1: {
+            // Steal the received packet; it belongs to the stream
+            usbdpi_transfer_t *rx = ctx->recving;
+            ctx->recving = NULL;
+            // Decide whether we want to ACK or NAK this packet
+            unsigned id = ctx->stream_in;
+            usbdpi_stream_t *s = &ctx->stream[id];
+            bool accept = false;
+            if (s->retrying && s->nretries) {
+              s->nretries--;
+            } else {
+              // Decide the number of retries for the next data packet
+              // Note: by randomizing the number of retries, rather than
+              // independently deciding each accept/reject, we guarantee an
+              // upper bound on the run time
+              switch (s->retry_lfsr & 7U) {
+                case 7U:
+                  s->nretries = 3U;
+                  break;
+                case 6U:
+                case 5U:
+                  s->nretries = 2U;
+                  break;
+                case 4U:
+                  s->nretries = 1U;
+                  break;
+                default:
+                  s->nretries = 0U;
+                  break;
+              }
+              s->retry_lfsr = LFSR_ADVANCE(s->retry_lfsr);
+              accept = true;
+            }
+            if (!accept) {
+              printf("[usbdpi] Requesting resend of data\n");
+              usb_monitor_log(ctx->mon, "[usbdpi] Requesting resend of data\n");
+            }
+
+            if (expect_sig && !s->sig_recvd) {
+              // Note: the stream signature is primarily of use on a physical
+              // USB connection to a host since the endpoint to port mapping is
+              // variable. With t-l sim we can rely upon the first packet being
+              // the signature and nothing else
+              accept = stream_sig_check(ctx, s, rx);
+              transfer_release(ctx, rx);
+              // TODO - run time error, signal test failure to the software
+              assert(accept);
+              if (accept) {
+                s->sig_recvd = true;
+              }
+            } else {
+              if (stream_data_check(ctx, s, rx, accept)) {
+                // Collect the received packets in preparation for later
+                // transmission with modification back to the device
+                usbdpi_transfer_t *tr = s->received;
+                if (tr) {
+                  while (tr->next)
+                    tr = tr->next;
+                  tr->next = rx;
+                } else {
+                  s->received = rx;
+                }
+              } else {
+                transfer_release(ctx, rx);
+                accept = false;
+              }
+            }
+
+            usbdpi_transfer_t *tr = ctx->sending;
+            assert(tr);
+
+            transfer_status(ctx, tr, accept ? USB_PID_ACK : USB_PID_NAK);
+
+            ctx->hostSt = HS_STREAMOUT;
+          } break;
+
+          case USB_PID_NAK:
+            // No data available
+            ctx->hostSt = HS_STREAMOUT;
+            break;
+
+          default:
+            printf("[usbdpi] Unexpected PID 0x%02x from device (%s)\n",
+                   ctx->lastrxpid, decode_pid(ctx->lastrxpid));
+            ctx->hostSt = HS_NEXTFRAME;
+            break;
+        }
+      } else if (ctx->tick_bits >= ctx->wait) {
+        printf("[usbdpi] Timed out waiting for IN response\n");
+        ctx->hostSt = HS_NEXTFRAME;
+      }
+      break;
+
+    default:
+      break;
+  }
+}
diff --git a/hw/dv/dpi/usbdpi/usbdpi_stream.h b/hw/dv/dpi/usbdpi/usbdpi_stream.h
new file mode 100644
index 0000000..a48acfc
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usbdpi_stream.h
@@ -0,0 +1,89 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_STREAM_H_
+#define OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_STREAM_H_
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "usb_transfer.h"
+
+// Forwards declaration of USBDPI context
+typedef struct usbdpi_ctx usbdpi_ctx_t;
+
+// Context for streaming data test (usbdev_stream_test)
+typedef struct usbdpi_stream {
+  /**
+   * Test requires polling device for IN packets
+   */
+  bool retrieve;
+  /**
+   * Test requires attempting to send OUT packets to device
+   */
+  bool send;
+  /**
+   * Request trying of IN packets, feigning error
+   */
+  bool retrying;
+  /**
+   * Checking of received byte stream; disable iff
+   * investigating IN performance
+   */
+  bool checking;
+  /**
+   * Have we received the stream signature yet?
+   */
+  bool sig_recvd;
+  /**
+   * Device endpoint from which IN packets are retrieved
+   */
+  uint8_t ep_in;
+  /**
+   * Device endpoint to which OUT packets are sent
+   */
+  uint8_t ep_out;
+  /**
+   * Device-generated LFSR; predicts data expected from usbdev_stream_test
+   */
+  uint8_t tst_lfsr;
+  /**
+   * DPI-generated LFSR-generated data, to be combined with received data
+   */
+  uint8_t dpi_lfsr;
+  /**
+   * LFSR state for p-randomized retrying of received data
+   */
+  uint8_t retry_lfsr;
+  /**
+   * Number of times (still) to retry before accepting the current data packet
+   */
+  uint8_t nretries;
+  /***
+   * Linked-list of received transfers
+   */
+  usbdpi_transfer_t *received;
+} usbdpi_stream_t;
+
+/**
+ * Initialize streaming state for the given number of streams
+ *
+ * @param  ctx       USBDPI context state
+ * @param  nstreams  Number of concurrent byte streams
+ * @param  retrieve  Retrieve IN packets from device
+ * @param  checking  Checking of received data against expected LFSR output
+ * @param  retrying  Request retrying of IN packets, feigning error
+ * @param  send      Attempt to send OUT packets to device
+ * @return           true iff initialized successfully
+ */
+bool streams_init(usbdpi_ctx_t *ctx, unsigned nstreams, bool retrieve,
+                  bool checking, bool retrying, bool send);
+
+/**
+ * Service streaming data (usbdev_stream_test)
+ *
+ * @param  ctx       USBDPI context state
+ */
+void streams_service(usbdpi_ctx_t *ctx);
+
+#endif  // OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_STREAM_H_
diff --git a/hw/dv/dpi/usbdpi/usbdpi_test.c b/hw/dv/dpi/usbdpi/usbdpi_test.c
new file mode 100644
index 0000000..845a463
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usbdpi_test.c
@@ -0,0 +1,171 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "usbdpi_test.h"
+
+#include <assert.h>
+
+#include "usbdpi_stream.h"
+
+// Test-specific initialization
+void usbdpi_test_init(usbdpi_ctx_t *ctx) {
+  // Test-specific initialization code
+  bool ok = false;
+  switch (ctx->test_number) {
+    case 0:
+      // The simple smoke test (usbdev_test) does not require any parameters
+      // or special initialization at present
+      ok = true;
+      break;
+
+    // Initialize streaming test
+    case 1: {
+      // Number of concurrent byte streams
+      const unsigned nstreams = ctx->test_arg[0] & 0xfU;
+      // Poll device for IN packets in streaming test?
+      bool retrieve = (ctx->test_arg[0] & 0x10U) != 0U;
+      // Checking of received data against expected LFSR output
+      bool checking = (ctx->test_arg[0] & 0x20U) != 0U;
+      // Request retrying of IN packets, feigning error
+      bool retrying = (ctx->test_arg[0] & 0x40U) != 0U;
+      // Attempt to send OUT packets to device
+      bool send = (ctx->test_arg[0] & 0x80U) != 0U;
+
+      if (nstreams <= USBDPI_MAX_STREAMS) {
+        ok = streams_init(ctx, nstreams, retrieve, checking, retrying, send);
+      }
+    } break;
+
+    default:
+      assert(!"Unrecognised/unsupported test in USBDPI");
+      break;
+  }
+
+  // TODO - commute this to a proper test failure at run time
+  assert(ok);
+}
+
+// Return the next step in the test sequence
+usbdpi_test_step_t usbdpi_test_seq_next(usbdpi_ctx_t *ctx,
+                                        usbdpi_test_step_t step) {
+  // Default to disconnecting and stopping
+  usbdpi_test_step_t next_step = STEP_BUS_DISCONNECT;
+
+  switch (step) {
+    // Bus reset (VBUS newly present...)
+    case STEP_BUS_RESET:
+      next_step = STEP_SET_DEVICE_ADDRESS;
+      break;
+    // Standard device set up sequence
+    case STEP_SET_DEVICE_ADDRESS:
+      next_step = STEP_GET_DEVICE_DESCRIPTOR;
+      break;
+    case STEP_GET_DEVICE_DESCRIPTOR:
+      next_step = STEP_GET_CONFIG_DESCRIPTOR;
+      break;
+    case STEP_GET_CONFIG_DESCRIPTOR:
+      next_step = STEP_GET_FULL_CONFIG_DESCRIPTOR;
+      break;
+    case STEP_GET_FULL_CONFIG_DESCRIPTOR:
+      next_step = STEP_SET_DEVICE_CONFIG;
+      break;
+      // TODO - a real host issues additional GET_DESCRIPTOR commands at
+      // this point, reading device qualifier and endpoint information
+
+    case STEP_SET_DEVICE_CONFIG:
+      next_step = STEP_GET_TEST_CONFIG;
+      break;
+
+    case STEP_SET_TEST_STATUS:
+      next_step = STEP_BUS_DISCONNECT;
+      break;
+
+    case STEP_BUS_DISCONNECT:
+      // deassert VBUS
+      ctx->driving &= ~P2D_SENSE;
+      break;
+
+    // At this point we have discovered which test we are to perform
+    default:
+      switch (ctx->test_number) {
+        // Streaming test (usbdev_stream_test)
+        case 1:
+          switch (next_step) {
+            case STEP_GET_TEST_CONFIG:
+              next_step = STEP_STREAM_SERVICE;
+              break;
+
+            default:
+              // The single state that keeps aggressively polling for IN
+              // packets, processing them and pushing them OUT to the
+              // device. The device test software is responsible for
+              // terminating the test and reporting its status
+              next_step = STEP_STREAM_SERVICE;
+              break;
+          }
+          break;
+
+        // Default behavior; usbdev_test
+        default:
+          // TODO - for now we're just maintaining the existing timing
+          // sequence
+          switch (step) {
+            case STEP_GET_TEST_CONFIG:
+              next_step = STEP_FIRST_READ;
+              break;
+
+            case STEP_FIRST_READ:
+              next_step = STEP_READ_BAUD;
+              break;
+            // case STEP_READ_BAUD:
+            //   next_step = STEP_SECOND_READ;
+            //   break;
+            case STEP_SECOND_READ:
+              next_step = STEP_SET_BAUD;
+              break;
+            case STEP_SET_BAUD:
+              next_step = STEP_THIRD_READ;
+              break;
+            case STEP_THIRD_READ:
+              next_step = STEP_TEST_ISO1;
+              break;
+            case STEP_TEST_ISO1:
+              next_step = STEP_TEST_ISO2;
+              break;
+            // case STEP_TEST_ISO2:
+            //   next_step = STEP_ENDPT_UNIMPL_SETUP;
+            //   break;
+            case STEP_ENDPT_UNIMPL_SETUP:
+              next_step = STEP_ENDPT_UNIMPL_SETUP;
+              break;
+            case STEP_ENDPT_UNIMPL_OUT:
+              next_step = STEP_ENDPT_UNIMPL_IN;
+              break;
+            case STEP_ENDPT_UNIMPL_IN:
+              next_step = STEP_DEVICE_UK_SETUP;
+              break;
+            case STEP_DEVICE_UK_SETUP:
+              next_step = STEP_IDLE_START;
+              break;
+
+            case STEP_IDLE_END:
+              // Final resting state
+              next_step = STEP_IDLE_END;
+              break;
+
+            // TODO - for now this is still required to advance through
+            // the test steps
+            case STEP_IDLE_START:
+            case STEP_TEST_ISO2:
+            case STEP_READ_BAUD:
+            default:
+              next_step = (usbdpi_test_step_t)((unsigned)step + 1U);
+              break;
+          }
+          break;
+      }
+      break;
+  }
+  return next_step;
+}
diff --git a/hw/dv/dpi/usbdpi/usbdpi_test.h b/hw/dv/dpi/usbdpi/usbdpi_test.h
new file mode 100644
index 0000000..cb90870
--- /dev/null
+++ b/hw/dv/dpi/usbdpi/usbdpi_test.h
@@ -0,0 +1,16 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_TEST_H_
+#define OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_TEST_H_
+#include "usbdpi.h"
+
+// Test-specific initialization
+void usbdpi_test_init(usbdpi_ctx_t *ctx);
+
+// Return the next step in the test sequence
+usbdpi_test_step_t usbdpi_test_seq_next(usbdpi_ctx_t *ctx,
+                                        usbdpi_test_step_t step);
+
+#endif  // OPENTITAN_HW_DV_DPI_USBDPI_USBDPI_TEST_H_
diff --git a/hw/dv/sv/README.md b/hw/dv/sv/README.md
new file mode 100644
index 0000000..e2dc2c6
--- /dev/null
+++ b/hw/dv/sv/README.md
@@ -0,0 +1,4 @@
+# Common SystemVerilog and UVM Components
+
+## Content
+{{% sectionContent %}}
diff --git a/hw/dv/sv/alert_esc_agent/README.md b/hw/dv/sv/alert_esc_agent/README.md
new file mode 100644
index 0000000..19b9146
--- /dev/null
+++ b/hw/dv/sv/alert_esc_agent/README.md
@@ -0,0 +1,23 @@
+# ALERT_ESC Agent
+
+ALERT_ESC UVM Agent is extended from DV library agent classes.
+
+## Description
+
+This agent implements both alert(alert_rx, alert_tx) and escalation (esc_rx,
+esc_tx) interface protocols, and can be configured to behave in both host and
+device modes. For design documentation, please refer to [alert_handler
+spec](../../../top_earlgrey/ip_autogen/alert_handler/README.md).
+
+### Alert Agent
+
+Alert agent supports both synchronous and asynchronous modes.
+
+#### Alert Device Agent
+
+For IPs that send out alerts, it is recommended to attach alert device agents to
+the block level testbench.
+Please refer to [cip_lib documentation](../cip_lib/README.md)
+regarding instructions to configure alert device agent in DV testbenches.
+
+### Escalation Agent
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_agent.sv b/hw/dv/sv/alert_esc_agent/alert_esc_agent.sv
index 318da30..a510e82 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_agent.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_agent.sv
@@ -32,6 +32,8 @@
     // override driver
     if (cfg.is_active) begin
       if (cfg.is_alert) begin
+        // use for reactive device
+        cfg.has_req_fifo = 1;
         if (cfg.if_mode == Host) begin
           alert_esc_base_driver::type_id::set_type_override(alert_sender_driver::get_type());
         end else begin
@@ -47,6 +49,7 @@
     end
 
     super.build_phase(phase);
+
     void'($value$plusargs("bypass_alert_ready_to_end_check=%0b",
           cfg.bypass_alert_ready_to_end_check));
 
@@ -79,4 +82,31 @@
     end
   endfunction
 
+  // Create automatic response (from monitor) to ping and alert requests.
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+    if (cfg.is_alert && cfg.has_req_fifo) begin
+      monitor.req_analysis_port.connect(sequencer.req_analysis_fifo.analysis_export);
+    end
+  endfunction
+
+  virtual task run_phase(uvm_phase phase);
+    if (cfg.is_alert && cfg.is_active && cfg.start_default_rsp_seq) begin
+      // For host mode, run alert ping auto-response sequence.
+      if (cfg.if_mode == dv_utils_pkg::Host) begin
+        alert_sender_ping_rsp_seq m_seq =
+            alert_sender_ping_rsp_seq::type_id::create("m_seq", this);
+        uvm_config_db#(uvm_object_wrapper)::set(null, {sequencer.get_full_name(), ".run_phase"},
+                                                "default_sequence", m_seq.get_type());
+        sequencer.start_phase_sequence(phase);
+      end else begin
+        // For device mode, run alert auto-response sequence
+        alert_receiver_alert_rsp_seq m_seq =
+            alert_receiver_alert_rsp_seq::type_id::create("m_seq", this);
+        uvm_config_db#(uvm_object_wrapper)::set(null, {sequencer.get_full_name(), ".run_phase"},
+                                                "default_sequence", m_seq.get_type());
+        sequencer.start_phase_sequence(phase);
+      end
+    end
+  endtask
 endclass : alert_esc_agent
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_agent_cfg.sv b/hw/dv/sv/alert_esc_agent/alert_esc_agent_cfg.sv
index 65e28c8..53aa55c 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_agent_cfg.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_agent_cfg.sv
@@ -21,6 +21,18 @@
   // For IP level test, please set `exp_fatal_alert` in post_start() instead.
   bit bypass_alert_ready_to_end_check = 0;
 
+  bit bypass_esc_ready_to_end_check = 0;
+
+  // When this flag is set to 1, and agent in alert host mode, the sequence will automatically
+  // response to alert ping requests.
+  bit start_default_rsp_seq = 1;
+
+  // Control if ping response will timeout or not.
+  bit ping_timeout = 0;
+
+  // Monitor will set this value to 1 when the agent is under ping handshake.
+  bit under_ping_handshake = 0;
+
   // dut clk frequency, used to generate alert async_clk frequency
   int clk_freq_mhz;
 
@@ -47,6 +59,8 @@
   int unsigned handshake_timeout_cycle = 100_000;
   int unsigned ping_timeout_cycle = 32;
 
+  bit under_reset;
+
   `uvm_object_utils_begin(alert_esc_agent_cfg)
     `uvm_field_int(alert_delay_min, UVM_DEFAULT)
     `uvm_field_int(alert_delay_max, UVM_DEFAULT)
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_agent_cov.sv b/hw/dv/sv/alert_esc_agent/alert_esc_agent_cov.sv
index 68b2abc..cb32c7c 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_agent_cov.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_agent_cov.sv
@@ -39,11 +39,6 @@
   }
 endgroup : alert_esc_trans_cg
 
-covergroup alert_ping_lpg_cg with function sample(bit alert_lpg_en);
-  option.per_instance = 1;
-  cp_alert_ping_lpg: coverpoint alert_lpg_en;
-endgroup
-
 covergroup alert_lpg_cg with function sample(bit alert_lpg_en);
   option.per_instance = 1;
   cp_alert_lpg: coverpoint alert_lpg_en;
@@ -57,7 +52,6 @@
   esc_handshake_complete_cg   m_esc_handshake_complete_cg;
   alert_esc_trans_cg          m_alert_trans_cg;
   alert_esc_trans_cg          m_esc_trans_cg;
-  alert_ping_lpg_cg           m_alert_ping_lpg_cg;
   alert_lpg_cg                m_alert_lpg_cg;
 
   `uvm_component_utils(alert_esc_agent_cov)
@@ -70,7 +64,6 @@
       else m_esc_trans_cg = new();
     end
     if (cfg.en_lpg_cov && cfg.is_alert) begin
-      m_alert_ping_lpg_cg = new();
       m_alert_lpg_cg = new();
     end
     if (cfg.is_alert)    m_alert_handshake_complete_cg = new();
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_agent_pkg.sv b/hw/dv/sv/alert_esc_agent/alert_esc_agent_pkg.sv
index 811aa96..c841433 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_agent_pkg.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_agent_pkg.sv
@@ -11,8 +11,8 @@
   import prim_esc_pkg::*;
 
   // 2 clock cycles between two alert handshakes, 1 clock cycle to go back to `Idle` state,
-  // 1 clock cycle for monitor to detect alert.
-  parameter uint ALERT_B2B_DELAY = 4;
+  // 1 clock cycle for monitor to detect alert, and 1 for synchronizer skip.
+  parameter uint ALERT_B2B_DELAY = 5;
 
   typedef class alert_esc_seq_item;
   typedef class alert_esc_agent_cfg;
@@ -68,7 +68,6 @@
   `include "alert_esc_base_monitor.sv"
   `include "alert_monitor.sv"
   `include "esc_monitor.sv"
-  `include "alert_esc_agent.sv"
 
   // Sequences
   `include "seq_lib/alert_receiver_base_seq.sv"
@@ -80,4 +79,5 @@
   `include "seq_lib/esc_receiver_base_seq.sv"
   `include "seq_lib/esc_receiver_esc_rsp_seq.sv"
 
+  `include "alert_esc_agent.sv"
 endpackage
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_base_driver.sv b/hw/dv/sv/alert_esc_agent/alert_esc_base_driver.sv
index 87fa0d1..69ec111 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_base_driver.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_base_driver.sv
@@ -33,7 +33,7 @@
       seq_item_port.get(req);
       `downcast(req_clone, req.clone());
       req_clone.set_id_info(req);
-      // TODO: if any of the queue size is larger than 2, need additional support
+      // receiver mode
       if (req.r_alert_ping_send) r_alert_ping_send_q.push_back(req_clone);
       if (req.r_alert_rsp)       r_alert_rsp_q.push_back(req_clone);
       if (req.r_esc_rsp)         r_esc_rsp_q.push_back(req_clone);
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_base_monitor.sv b/hw/dv/sv/alert_esc_agent/alert_esc_base_monitor.sv
index c8f8c3e..4e082aa 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_base_monitor.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_base_monitor.sv
@@ -45,6 +45,7 @@
 
   // this function can be used in derived classes to reset local signals/variables if needed
   virtual function void reset_signals();
+    cfg.under_ping_handshake = 0;
   endfunction
 
 endclass
diff --git a/hw/dv/sv/alert_esc_agent/alert_esc_if.sv b/hw/dv/sv/alert_esc_agent/alert_esc_if.sv
index 60ff9ca..db659db 100644
--- a/hw/dv/sv/alert_esc_agent/alert_esc_if.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_esc_if.sv
@@ -36,7 +36,7 @@
   assign async_clk = (is_async) ? 'z : clk ;
 
   // async interface for alert_tx has two_clock cycles delay
-  // TODO: this is not needed once the CDC module is implemented
+  // ICEBOX(#3275): this is not needed once the CDC module is implemented
   always_ff @(posedge clk or negedge rst_n) begin
     if (!rst_n) begin
       alert_tx_sync_dly2 <= {2'b01};
diff --git a/hw/dv/sv/alert_esc_agent/alert_monitor.sv b/hw/dv/sv/alert_esc_agent/alert_monitor.sv
index b07db74..46000a5 100644
--- a/hw/dv/sv/alert_esc_agent/alert_monitor.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_monitor.sv
@@ -11,8 +11,6 @@
 
   `uvm_component_utils(alert_monitor)
 
-  bit under_ping_rsp;
-
   `uvm_component_new
 
   virtual task run_phase(uvm_phase phase);
@@ -23,6 +21,7 @@
       reset_thread();
       int_fail_thread();
       alert_init_thread();
+      wait_ping_thread();
     join_none
   endtask : run_phase
 
@@ -45,10 +44,6 @@
     end
   endtask : alert_init_thread
 
-  virtual function void reset_signals();
-    under_ping_rsp = 0;
-  endfunction : reset_signals
-
   // This task called inside forever loop in the `reset_thread` task, intended to be a nonblocking
   // process. However, it can still block alert handshake via the `cfg.alert_init_done` flag.
   // To handle the scenario where reset is issued during alert init, we use a fork join_any thread.
@@ -85,11 +80,8 @@
     bit ping_p, alert_p;
     forever @(cfg.vif.monitor_cb) begin
       if (ping_p != cfg.vif.monitor_cb.alert_rx_final.ping_p) begin
-        if (cfg.en_lpg_cov && cfg.en_cov) begin
-          cov.m_alert_ping_lpg_cg.sample(cfg.en_alert_lpg);
-        end
         if (!cfg.en_alert_lpg) begin
-          under_ping_rsp = 1;
+          cfg.under_ping_handshake = 1;
           req = alert_esc_seq_item::type_id::create("req");
           req.alert_esc_type = AlertEscPingTrans;
 
@@ -109,7 +101,7 @@
                   req.alert_handshake_sta = AlertReceived;
                   wait_ack();
                   req.alert_handshake_sta = AlertAckReceived;
-                  under_ping_rsp = 0;
+                  cfg.under_ping_handshake = 0;
                 end
                 begin
                   wait (under_reset || cfg.en_alert_lpg);
@@ -136,7 +128,7 @@
               if (cfg.vif.alert_rx_final.ack_p == 1'b1) alert_esc_port.write(req);
             end
           end
-          under_ping_rsp = 0;
+          cfg.under_ping_handshake = 0;
         end
       end
       ping_p = cfg.vif.monitor_cb.alert_rx_final.ping_p;
@@ -150,18 +142,21 @@
     forever @(cfg.vif.monitor_cb) begin
       // If ping and alert are triggered at the same clock cycle, the alert is considered a ping
       // response
-      if (!alert_p && is_valid_alert() && !under_ping_rsp &&
+      if (!alert_p && is_valid_alert() && !cfg.under_ping_handshake &&
           ping_p == cfg.vif.monitor_cb.alert_rx_final.ping_p) begin
         if (cfg.en_lpg_cov && cfg.en_cov) begin
           cov.m_alert_lpg_cg.sample(cfg.en_alert_lpg);
         end
-        if (!cfg.en_alert_lpg) begin
+
+        if (!cfg.en_alert_lpg && !cfg.under_reset) begin
           req = alert_esc_seq_item::type_id::create("req");
           req.alert_esc_type = AlertEscSigTrans;
           req.alert_handshake_sta = AlertReceived;
 
           // Write alert packet to scb when receiving alert signal
           alert_esc_port.write(req);
+          // Write alert packet to sequence for auto alert responses.
+          req_analysis_port.write(req);
 
           // Duplicate req for writing alert packet at the end of alert handshake
           `downcast(req, req.clone())
@@ -222,6 +217,25 @@
     end
   endtask : int_fail_thread
 
+  virtual task wait_ping_thread();
+    forever begin
+      alert_esc_seq_item req = alert_esc_seq_item::type_id::create("req");
+      logic ping_p_value;
+      req.alert_esc_type = AlertEscPingTrans;
+      wait (!under_reset && !cfg.en_alert_lpg);
+
+      `DV_SPINWAIT_EXIT(
+          ping_p_value = cfg.vif.monitor_cb.alert_rx_final.ping_p;
+          while (cfg.vif.monitor_cb.alert_rx_final.ping_p === ping_p_value) begin
+            ping_p_value = cfg.vif.monitor_cb.alert_rx_final.ping_p;
+            @(cfg.vif.monitor_cb);
+          end
+          req_analysis_port.write(req);,
+          wait (under_reset || cfg.en_alert_lpg);)
+      @(cfg.vif.monitor_cb);
+    end
+  endtask
+
   virtual task wait_alert();
     while (cfg.vif.monitor_cb.alert_tx_final.alert_p !== 1'b1) @(cfg.vif.monitor_cb);
   endtask : wait_alert
diff --git a/hw/dv/sv/alert_esc_agent/alert_receiver_driver.sv b/hw/dv/sv/alert_esc_agent/alert_receiver_driver.sv
index 315eab2..69c6c38 100644
--- a/hw/dv/sv/alert_esc_agent/alert_receiver_driver.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_receiver_driver.sv
@@ -103,7 +103,10 @@
       @(cfg.vif.receiver_cb);
       repeat (ping_delay) @(cfg.vif.receiver_cb);
       set_ping();
-      // TODO: add ping fail and differential signal fail scenarios
+      // Ping fail and differential signal fail scenarios are not implemented now.
+      // This driver is used for IP (instantiate prim_alert_sender) to finish alert handshake.
+      // The current use-case does not test ping fail and differential signal fail scenarios.
+      // These scenarios are check in prim_alert direct sequences.
       fork
         begin : isolation_fork
           fork
@@ -119,7 +122,7 @@
         end
       join
     end else begin
-    // TODO: differential signal fail
+    // Differential signal fail is not implemented.
     end
   endtask
 
@@ -148,7 +151,7 @@
         end : isolation_fork
       join
     end else begin
-    // TODO: differential signal fail
+    // Differential signal fail is not implemented.
     end
   endtask : set_ack_pins
 
diff --git a/hw/dv/sv/alert_esc_agent/alert_sender_driver.sv b/hw/dv/sv/alert_esc_agent/alert_sender_driver.sv
index 4a10bb1..3fb0e89 100644
--- a/hw/dv/sv/alert_esc_agent/alert_sender_driver.sv
+++ b/hw/dv/sv/alert_esc_agent/alert_sender_driver.sv
@@ -97,26 +97,15 @@
           $sformatf("starting to send sender item, alert_send=%0b, ping_rsp=%0b, int_err=%0b",
           req.s_alert_send, req.s_alert_ping_rsp, req.int_err), UVM_HIGH)
 
-      fork
-        begin : isolation_fork
-          fork
-            begin
-              wait_ping();
-              alert_atomic.get(1);
-              if (!req.ping_timeout) begin
-                drive_alert_pins(req);
-              end else begin
-                repeat (cfg.ping_timeout_cycle) wait_sender_clk();
-              end
-              alert_atomic.put(1);
-            end
-            begin
-              wait (under_reset || cfg.en_alert_lpg);
-            end
-          join_any
-          disable fork;
-        end
-      join
+      `DV_SPINWAIT_EXIT(
+          alert_atomic.get(1);
+          if (!cfg.ping_timeout) begin
+            drive_alert_pins(req);
+          end else begin
+            repeat (cfg.ping_timeout_cycle) wait_sender_clk();
+          end
+          alert_atomic.put(1);,
+          wait (under_reset || cfg.en_alert_lpg);)
 
       `uvm_info(`gfn,
           $sformatf("finished sending sender item, alert_send=%0b, ping_rsp=%0b, int_err=%0b",
@@ -217,14 +206,6 @@
     cfg.vif.sender_cb.alert_tx_int.alert_n <= 1'b0;
   endtask
 
-  virtual task wait_ping();
-    logic ping_p_value = cfg.vif.alert_rx.ping_p;
-    while (cfg.vif.alert_rx.ping_p === ping_p_value) begin
-      ping_p_value = cfg.vif.alert_rx.ping_p;
-      wait_sender_clk();
-    end
-  endtask : wait_ping
-
   virtual task wait_sender_clk();
     @(cfg.vif.sender_cb);
   endtask
diff --git a/hw/dv/sv/alert_esc_agent/data/alert_agent_additional_testplan.hjson b/hw/dv/sv/alert_esc_agent/data/alert_agent_additional_testplan.hjson
index 2f0c1a9..d6fc894 100644
--- a/hw/dv/sv/alert_esc_agent/data/alert_agent_additional_testplan.hjson
+++ b/hw/dv/sv/alert_esc_agent/data/alert_agent_additional_testplan.hjson
@@ -5,6 +5,8 @@
 // This DV coverplan documents additional alert agent covergroups.
 // To enable these covergroups, please ensure the following variables are enabled:
 // `cfg.en_ping_cov` and `cfg.en_lpg_cov`
+// Note that if LPG is enabled, the alert ping request handshake won't be initiated, so we cannot
+// collect that coverage in this agent.
 {
   covergroups: [
     {
@@ -12,13 +14,6 @@
       desc: '''Cover if the transaction is a ping request or an actual alert request.'''
     }
     {
-      name:  alert_ping_lpg_cg
-      desc: '''Covers alert lpg status during a ping request.
-
-      Cover if its lower-power-group (lpg) is enabled or disabled during a ping request.
-      '''
-    }
-    {
       name:  alert_lpg_cg
       desc: '''Covers alert lpg status during an alert request.
 
diff --git a/hw/dv/sv/alert_esc_agent/doc/index.md b/hw/dv/sv/alert_esc_agent/doc/index.md
deleted file mode 100644
index 17999ba..0000000
--- a/hw/dv/sv/alert_esc_agent/doc/index.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-title: "ALERT_ESC Agent"
----
-
-ALERT_ESC UVM Agent is extended from DV library agent classes.
-
-## Description
-
-This agent implements both alert(alert_rx, alert_tx) and escalation (esc_rx,
-esc_tx) interface protocols, and can be configured to behave in both host and
-device modes. For design documentation, please refer to [alert_handler
-spec]({{< relref "hw/top_earlgrey/ip_autogen/alert_handler/doc/_index.md" >}}).
-
-### Alert Agent
-
-Alert agent supports both synchronous and asynchronous modes.
-
-#### Alert Device Agent
-
-For IPs that send out alerts, it is recommended to attach alert device agents to
-the block level testbench.
-Please refer to [cip_lib documentation]({{< relref "hw/dv/sv/cip_lib/doc/index.md" >}})
-regarding instructions to configure alert device agent in DV testbenches.
-
-### Escalation Agent
diff --git a/hw/dv/sv/alert_esc_agent/esc_monitor.sv b/hw/dv/sv/alert_esc_agent/esc_monitor.sv
index 36769cf..9358a88 100644
--- a/hw/dv/sv/alert_esc_agent/esc_monitor.sv
+++ b/hw/dv/sv/alert_esc_agent/esc_monitor.sv
@@ -13,8 +13,6 @@
 
   `uvm_component_new
 
-  bit under_esc_ping;
-
   virtual task run_phase(uvm_phase phase);
     fork
       esc_thread();
@@ -24,10 +22,6 @@
     join_none
   endtask : run_phase
 
-  virtual function void reset_signals();
-    under_esc_ping = 0;
-  endfunction
-
   virtual task esc_thread();
     alert_esc_seq_item req, req_clone;
     forever @(cfg.vif.monitor_cb) begin
@@ -41,8 +35,9 @@
         // esc_p/n only goes high for a cycle, detect it is a ping signal
         if (get_esc() === 1'b0) begin
           int ping_cnter = 1;
-          under_esc_ping = 1;
+          cfg.under_ping_handshake = 1;
           req.alert_esc_type = AlertEscPingTrans;
+          alert_esc_port.write(req);
           check_esc_resp(.req(req), .is_ping(1));
           while (req.esc_handshake_sta != EscRespComplete &&
                  ping_cnter < cfg.ping_timeout_cycle &&
@@ -69,7 +64,7 @@
           end
             // wait a clk cycle to enter the esc_p/n mode
           if (cfg.get_esc_en()) @(cfg.vif.monitor_cb);
-          under_esc_ping = 0;
+          cfg.under_ping_handshake = 0;
         end
 
         // when it is not a ping, there are two preconditions to reach this code:
@@ -109,7 +104,7 @@
   virtual task unexpected_resp_thread();
     alert_esc_seq_item req;
     forever @(cfg.vif.monitor_cb) begin
-      while (get_esc() === 1'b0 && !under_esc_ping) begin
+      while (get_esc() === 1'b0 && !cfg.under_ping_handshake) begin
         @(cfg.vif.monitor_cb);
         if (cfg.vif.monitor_cb.esc_rx.resp_p === 1'b1 &&
             cfg.vif.monitor_cb.esc_rx.resp_n === 1'b0 && !under_reset) begin
@@ -227,9 +222,11 @@
 
   // end phase when no escalation signal is triggered
   virtual task monitor_ready_to_end();
-    forever @(cfg.vif.monitor_cb.esc_rx.resp_p || cfg.vif.monitor_cb.esc_tx.esc_p) begin
-      ok_to_end = !cfg.vif.monitor_cb.esc_rx.resp_p && cfg.vif.monitor_cb.esc_rx.resp_n &&
-                  !get_esc();
+    if (!cfg.bypass_esc_ready_to_end_check) begin
+      forever @(cfg.vif.monitor_cb.esc_rx.resp_p || cfg.vif.monitor_cb.esc_tx.esc_p) begin
+        ok_to_end = !cfg.vif.monitor_cb.esc_rx.resp_p && cfg.vif.monitor_cb.esc_rx.resp_n &&
+                    !get_esc();
+      end
     end
   endtask
 
diff --git a/hw/dv/sv/alert_esc_agent/esc_sender_driver.sv b/hw/dv/sv/alert_esc_agent/esc_sender_driver.sv
index 4087d50..c904d55 100644
--- a/hw/dv/sv/alert_esc_agent/esc_sender_driver.sv
+++ b/hw/dv/sv/alert_esc_agent/esc_sender_driver.sv
@@ -24,12 +24,16 @@
   endtask
 
   virtual task get_and_drive();
-    // TODO
+    // LC_CTRL uses virtual interface to directly drive escalation requests.
+    // Other escalation handshakes are checked in prim_esc direct sequence.
+    // So the following task is not implemented.
     drive_esc();
   endtask : get_and_drive
 
   virtual task drive_esc();
-    // TODO
+    // LC_CTRL uses virtual interface to directly drive escalation requests.
+    // Other escalation handshakes are checked in prim_esc direct sequence.
+    // So this task is not implemented.
     wait(!under_reset);
   endtask
 
@@ -38,4 +42,3 @@
     cfg.vif.esc_tx_int.esc_n <= 1'b1;
   endtask
 endclass : esc_sender_driver
-
diff --git a/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_alert_rsp_seq.sv b/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_alert_rsp_seq.sv
index 311e4f7..5886f70 100644
--- a/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_alert_rsp_seq.sv
+++ b/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_alert_rsp_seq.sv
@@ -13,4 +13,39 @@
     r_alert_rsp       == 1;
   }
 
+  virtual task body();
+    if (cfg.start_default_rsp_seq) begin
+      default_rsp_thread();
+    end else begin
+      super.body();
+    end
+  endtask
+
+  // Sends alert rsps back to host.
+  virtual task default_rsp_thread();
+    alert_esc_seq_item req_q[$];
+    fork
+      forever begin : get_req
+        p_sequencer.req_analysis_fifo.get(req);
+        if (req.alert_esc_type == AlertEscSigTrans) req_q.push_back(req);
+      end : get_req
+      forever begin : send_rsp
+        if (cfg.in_reset) begin
+          req_q.delete();
+          wait (!cfg.in_reset);
+        end
+        wait (req_q.size());
+        rsp = req_q.pop_front();
+        start_item(rsp);
+        `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
+            r_alert_ping_send == 0;
+            r_alert_rsp       == 1;
+            int_err           == 0;
+        )
+        finish_item(rsp);
+        get_response(rsp);
+      end : send_rsp
+    join
+  endtask
+
 endclass : alert_receiver_alert_rsp_seq
diff --git a/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_base_seq.sv b/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_base_seq.sv
index 51eae95..36f0e86 100644
--- a/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_base_seq.sv
+++ b/hw/dv/sv/alert_esc_agent/seq_lib/alert_receiver_base_seq.sv
@@ -23,7 +23,7 @@
     `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
         r_alert_ping_send == local::r_alert_ping_send;
         r_alert_rsp       == local::r_alert_rsp;
-        int_err           == 0; // TODO: current do not support alert_receiver int_err
+        int_err           == 0; // This agent do not support alert_receiver int_err
     )
     `uvm_info(`gfn, $sformatf("seq_item: ping_send=%0b alert_rsp=%0b int_err=%0b",
                               req.r_alert_ping_send, req.r_alert_rsp, req.int_err), UVM_MEDIUM)
diff --git a/hw/dv/sv/alert_esc_agent/seq_lib/alert_sender_ping_rsp_seq.sv b/hw/dv/sv/alert_esc_agent/seq_lib/alert_sender_ping_rsp_seq.sv
index 6692103..b107a05 100644
--- a/hw/dv/sv/alert_esc_agent/seq_lib/alert_sender_ping_rsp_seq.sv
+++ b/hw/dv/sv/alert_esc_agent/seq_lib/alert_sender_ping_rsp_seq.sv
@@ -13,4 +13,40 @@
     s_alert_ping_rsp == 1;
   }
 
+  virtual task body();
+    if (cfg.start_default_rsp_seq) begin
+      default_rsp_thread();
+    end else begin
+      super.body();
+    end
+  endtask
+
+  // Sends random rsps (device data and ack) back to host.
+  virtual task default_rsp_thread();
+    alert_esc_seq_item req_q[$];
+    fork
+      forever begin : get_req
+        p_sequencer.req_analysis_fifo.get(req);
+        if (req.alert_esc_type == AlertEscPingTrans) req_q.push_back(req);
+      end : get_req
+      forever begin : send_rsp
+        if (cfg.in_reset) begin
+          req_q.delete();
+          wait (!cfg.in_reset);
+        end
+        wait (req_q.size());
+        rsp = req_q.pop_front();
+        start_item(rsp);
+        `DV_CHECK_RANDOMIZE_WITH_FATAL(rsp,
+            s_alert_send     == local::s_alert_send;
+            s_alert_ping_rsp == local::s_alert_ping_rsp;
+            int_err          == 0;
+            ping_timeout     == 0;
+        )
+        finish_item(rsp);
+        get_response(rsp);
+      end : send_rsp
+    join
+  endtask
+
 endclass : alert_sender_ping_rsp_seq
diff --git a/hw/dv/sv/bus_params_pkg/README.md b/hw/dv/sv/bus_params_pkg/README.md
new file mode 100644
index 0000000..826f934
--- /dev/null
+++ b/hw/dv/sv/bus_params_pkg/README.md
@@ -0,0 +1,10 @@
+# bus_params_pkg
+
+This package provides an isolation for all common verification components under
+`hw/dv/sv/` from the OpenTitan specific design constants provided in
+`hw/top_earlgrey/rtl/top_pkg.sv`. This mostly includes common bus parameters
+such as address and data widths.
+
+When vendoring the common DV code into another repo, the other repo must
+provide its own implementation of `bus_params_pkg` so that the
+dependency on the `top_pkg` can be avoided.
diff --git a/hw/dv/sv/bus_params_pkg/doc/index.md b/hw/dv/sv/bus_params_pkg/doc/index.md
deleted file mode 100644
index 95d7484..0000000
--- a/hw/dv/sv/bus_params_pkg/doc/index.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: "bus_params_pkg"
----
-
-This package provides an isolation for all common verification components under
-`hw/dv/sv/` from the OpenTitan specific design constants provided in
-`hw/top_earlgrey/rtl/top_pkg.sv`. This mostly includes common bus parameters
-such as address and data widths.
-
-When vendoring the common DV code into another repo, the other repo must
-provide its own implementation of `bus_params_pkg` so that the
-dependency on the `top_pkg` can be avoided.
diff --git a/hw/dv/sv/cip_lib/README.md b/hw/dv/sv/cip_lib/README.md
new file mode 100644
index 0000000..3b7bd7a
--- /dev/null
+++ b/hw/dv/sv/cip_lib/README.md
@@ -0,0 +1,604 @@
+# Comportable IP Testbench Architecture
+
+## Overview
+Going along the lines of what it takes to design an IP that adheres to the
+[Comportability Specifications](../../../../doc/contributing/hw/comportability/README.md),
+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 environment framework that
+each IP level environment components can extend from to get started with DV. The goal
+here is to maximize code reuse across all test benches so that we can improve the
+efficiency and time to market. The features described here are not exhaustive,
+so it is highly recommended to the reader that they examine the code directly. In
+course of development, we also periodically identify pieces of verification logic that
+might be developed for one IP but is actually a good candidate to be added to
+these library classes instead. This doc is instead intended to provide the user
+a foray into what these are and how are the meant to be used.
+
+
+## CIP environment block diagram
+![CIP environment block diagram](./doc/env.svg)
+
+## CIP library classes
+The CIP library includes the base ral model, env cfg object, coverage
+object, virtual sequencer, scoreboard, env, base virtual sequence and finally
+the test class. To achieve run-time polymorphism, these classes are type
+parameterized to indicate what type of child objects are to be created. In the
+IP environments, the extended classes indicate the correct type parameters.
+
+### cip_base_env_cfg
+This class is intended to contain all of the settings, knobs, features, interface
+handles and downstream agent cfg handles. Features that are common to all IPs in
+accordance with the comportability spec are made a part of this base class, while the
+extended IP env cfg class will contain settings specific to that IP. An instance of
+the env cfg class is created in `cip_base_test::build_phase` and the handle is
+passed over uvm_config_db for the CIP env components to pick up. This allows
+the handle to the env cfg object to be available in the env's build_phase. Settings
+in the env cfg can then be used to configure the env based on the test needs.
+
+A handle to this class instance is passed on to the scoreboard, virtual
+sequencer and coverage objects so that all such common settings and features
+are instantly accessible everywhere.
+
+This class is type parameterized in the following way:
+```systemverilog
+class cip_base_env_cfg #(type RAL_T = dv_base_reg_block) extends uvm_object;
+```
+The IP env cfg class will then extend from this class with the RAL_T parameter set
+to the actual IP RAL model class. This results in IP RAL model getting factory
+overridden automatically in the base env cfg itself during creation, so there is no
+need for manual factory override. We follow the same philosophy in all CIP library
+classes.
+
+The following is a list of common features and settings:
+* **clk_rst_if**: A handle to the clk_rst_if that controls the main clk and reset
+  to the DUT.
+* **intr_vif**: This is a handle to the `pins_if #(NUM_MAX_INTERRUPTS=64)` interface
+  instance created in the tb to hookup the DUT interrupts. The actual number of
+  interrupts might be much less than 64, but that is ok - we just connect as
+  many as the DUT provides. The reason for going with a fixed width pins_if is
+  to allow the intr_vif to be available in this base env cfg class (which does not
+  know how many interrupt each IP DUT provides).
+* **devmode_vif**: This is a handle to the `pins_if #(1)` interface instance created
+  in the tb to hookup the DUT input `devmode`.
+* **tl_agent_cfg**: The downstream TileLink host agent created in the cip_base_env
+  class requires the agent cfg handle to tell it how to configure the agent.
+* **alert_agent_cfgs**: Similar to tl_agent_cfg, the downstream alert device agent
+  created in the cip_base_env class requires the agent cfg handles to tell it how to
+  configure the agent. In default, alert agent is configured in device mode,
+  asynchronous, active and the ping coverage is turned off.
+* **ral**: This is the instance to the auto-generated RAL model that is
+  extended from `dv_base_reg_block`. In the base class, this is created using
+  the RAL_T class parameter which the extended IP env cfg class sets correctly.
+* **tl_intg_alert_name**: Name of the alert that will be triggered on  TLUL
+  integrity error detection. The default name used for this type of alert is
+  "fatal_fault". The block may use a different name too - in that case, please
+  update this member to reflect the correct name in the `initialize()` method.
+* **tl_intg_alert_fields**: An associative array of CSR fields keyed with the
+  objection handle of the corresponding CSR field and valued with the expected
+  value. This is the list of CSR fields that are modified when an alert triggers
+  due to TL integrity violation event. The DV user is required to build this list
+  in the `initialize()` method after `super.initialize(csr_base_addr);`
+```systemverilog
+virtual function void initialize(bit [31:0] csr_base_addr = '1);
+  super.initialize(csr_base_addr); // ral model is created in `super.initialize`
+  tl_intg_alert_fields[ral.a_status_reg.a_field] = value;
+```
+
+Apart from these, there are several common settings such as `zero_delays`,
+`clk_freq_mhz`, which are randomized as well as knobs such as `en_scb` and
+`en_cov` to turn on/off scoreboard and coverage collection respectively.
+
+The base class provides a virtual method called `initialize()` which is called
+in `cip_base_test::build_phase` to create some of the objects listed above. If
+the extended IP env cfg class has more such objects added,  then the `initialize()`
+method is required to be overridden to create those objects as well.
+
+We make all downstream interface agent cfg handles as a part of IP extension of
+cip_base_env_cfg so that all settings for the env and all downstream agents are
+available within the env cfg handle. Since the env cfg handle is passed to all cip
+components, all those settings are also accessible.
+
+### cip_base_env_cov
+This is the base coverage object that contain all functional coverpoints and
+covergroups. The main goal is to have all functional coverage elements
+implemented in a single place. This class is extended from `uvm_component`
+so that it allows items to be set via `'uvm_config_db` using the component's
+hierarchy. This is created in cip_base_env and a handle to it is passed to the
+scoreboard and the virtual sequencer. This allows coverage to be sampled in
+scoreboard as well as the test sequences.
+
+This class is type parameterized with the env cfg class type `CFG_T` so that it
+can derive coverage on some of the env cfg settings.
+```systemverilog
+class cip_base_env_cov #(type CFG_T = cip_base_env_cfg) extends uvm_component;
+```
+
+The following covergroups are defined outside of the class for use by all IP
+testbenches:
+* `intr_cg`: Covers individual and cross coverage on intr_enable and intr_state for all interrupts in IP
+* `intr_test_cg`: Covers intr_test coverage and its cross with intr_enable and intr_state for all interrupts in IP
+* `intr_pins_cg`: Covers values and transitions on all interrupt output pins of IP
+* `sticky_intr_cov`: Covers sticky interrupt functionality of all applicable interrupts in IP
+
+Covergroups `intr_cg`, `intr_test_cg` and `intr_pins_cg` are instantiated
+and allocated in `cip_base_env_cov` by default in all IPs.
+On the other hand, `sticky_intr_cov` is instantiated with string key.
+The string key represents the interrupts names that are sticky. This is specific
+to each IP and is required to be created and instantiated in extended `cov` class.
+
+### cip_base_virtual_sequencer
+This is the base virtual sequencer class that contains a handle to the
+`tl_sequencer` to allow layered test sequences to be created. The extended IP
+virtual sequencer class will include handles to the IP specific agent
+sequencers.
+
+This class is type-parameterized with the env cfg class type `CFG_T` and coverage
+class type `COV_T` so that all test sequences can access the env cfg settings and
+sample the coverage via the `p_sequencer` handle.
+```systemverilog
+class cip_base_virtual_sequencer #(type CFG_T = cip_base_env_cfg,
+                                   type COV_T = cip_base_env_cov) extends uvm_sequencer;
+```
+
+### cip_base_scoreboard
+This is the base scoreboard component that already connects with the TileLink
+agent monitor to grab tl packets via analysis port at the address and the data
+phases. It provides a virtual task called `process_tl_access` that the extended
+IP scoreboard needs to implement. Please see code for additional details. The
+extended IP scoreboard class will connect with the IP-specific interface monitors
+if applicable to grab items from those analysis ports.
+
+This class is type-parameterized with the env cfg class type `CFG_T`, ral class
+type `RAL_T` and the coverage class type `COV_T`.
+```systemverilog
+class cip_base_scoreboard #(type RAL_T = dv_base_reg_block,
+                            type CFG_T = cip_base_env_cfg,
+                            type COV_T = cip_base_env_cov) extends uvm_component;
+```
+There are several virtual tasks and functions that are to be overridden
+in extended IP scoreboard class. Please take a look at the code for more
+details.
+
+### cip_base_env
+This is the base UVM env that puts all of the above components together
+and creates and makes connections across them. In the build phase, it retrieves
+the env cfg class type handle from `uvm_config_db` as well as all the virtual
+interfaces (which are actually part of the env cfg class). It then uses the env
+cfg settings to modify the downstream agent cfg settings as required. All of
+the above components are created based on env cfg settings, along with the TileLink
+host agent and alert device agents if the module has alerts. In the connect phase,
+the scoreboard connects with the monitor within the TileLink agent to be able to
+grab packets from the TL interface during address and the data phases. The scoreboard
+also connects the alert monitor within the alert_esc_agent to grab packets
+regarding alert handshake status. In the end of elaboration phase, the ral
+model within the env cfg handle is locked and the ral sequencer and adapters are
+set to be used with the TileLink interface.
+
+This class is type parameterized with env cfg class type CFG_T, coverage class type
+`COV_T`, virtual sequencer class type `VIRTUAL_SEQUENCER_T` and scoreboard class
+type `SCOREBOARD_T`.
+```systemverilog
+class cip_base_env #(type CFG_T               = cip_base_env_cfg,
+                     type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer,
+                     type SCOREBOARD_T        = cip_base_scoreboard,
+                     type COV_T               = cip_base_env_cov) extends uvm_env;
+```
+
+### cip_base_vseq
+This is the base virtual sequence class that will run on the cip virtual
+sequencer. This base class provides 'sequencing' set of tasks such as
+`dut_init()` and `dut_shutdown()` which are called within `pre_start` and
+`post_start` respectively. This sequence also provides an array of
+sub-sequences some of which are complete tests within themselves, but
+implemented as tasks. The reason for doing so is SystemVerilog does not
+support multi-inheritance so all sub-sequences that are identified as being
+common to all IP benches implemented as tasks in this base virtual sequence class.
+Some examples:
+* **task run_csr_vseq_wrapper**: This is a complete CSR test suite in itself -
+  Extended IP CSR vseq can simply call this in the body. This is paired with a
+  helper function `add_csr_exclusions`.
+* **function add_csr_exclusions**: This is extended in the IP CSR vseq to add
+  exclusions when running the CSR suite of tests.
+* **task tl_access**: This is a common generic task to create a read or a write
+  access over the TileLink host interface.
+* **task cfg_interrupts, check_interrupts**: All interrupt CSRs are standardized
+  according to the comportability spec, which allows us to create common tasks
+  to turn on / off interrupts as well as check them.
+* **task run_tl_errors_vseq**: This task will test all kinds of TileLink error
+  cases, including unmapped address error, protocol error, memory access error
+  etc. All the items sent in this task will trigger d_error and won't change the
+  CSR/memory value.
+* **task run_tl_intg_err_vseq**: This task will test TLUL integrity error. It contains
+  2 parallel threads. The first one invokes the `csr_rw` seq to drive random, legal
+  CSR accesses. The second drives a bad TLUL transaction that violates the payload
+  integrity. The bad packet is created by corrupting upto 3 bits either in the integrity
+  (ECC) fields (`a_user.cmd_intg`, `a_user.d_intg`), or in their corresponding command /
+  data payload itself. The sequence then verifies that the DUT not only returns an error
+  response (with `d_error` = 1), but also triggers a fatal alert and updates status CSRs
+  such as `ERR_CODE`. The list of CSRs that are impacted by this alert event, maintained
+  in `cfg.tl_intg_alert_fields`, are also checked for correctness.
+* **task run_seq_with_rand_reset_vseq**: This task runs 3 parallel threads,
+  which are a sequence provided, run_tl_errors_vseq and reset sequence. After
+  reset occurs, the other threads will be killed and then all the CSRs will be read
+  for check. This task runs multiple iterations to ensure DUT won't be broken after
+  reset and TL errors.
+  To ensure the reset functionality works correctly, user will have to disable
+  any internal reset from the stress_all sequence. Below is an example of
+  disabling internal reset in `hmac_stress_all_vseq.sv`:
+* **task run_same_csr_outstanding_vseq**: This task tests the same CSR with
+  non-blocking accesses as the regular CSR sequences don't cover that due to
+  limitation of uvm_reg.
+* **task run_mem_partial_access_vseq**: This task tests the partial access to the
+  memories by randomizing mask, size, and the 2 LSB bits of the address. It also runs
+  with non-blocking access enabled.
+  ```
+  // randomly trigger internal dut_init reset sequence
+  // disable any internal reset if used in stress_all_with_rand_reset vseq
+  if (do_dut_init) hmac_vseq.do_dut_init = $urandom_range(0, 1);
+  else hmac_vseq.do_dut_init = 0;
+  ```
+
+This class is type parameterized with the env cfg class type `CFG_T`, ral class type
+`RAL_T` and the virtual sequencer class type `VIRTUAL_SEQUENCER_T` so that the
+env cfg settings, the ral CSRs are accessible and the `p_sequencer` type can be
+declared.
+
+```systemverilog
+class cip_base_vseq #(type RAL_T               = dv_base_reg_block,
+                      type CFG_T               = cip_base_env_cfg,
+                      type COV_T               = cip_base_env_cov,
+                      type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer) extends uvm_sequence;
+```
+All virtual sequences in the extended IP will eventually extend from this class and
+can hence, call these tasks and functions directly as needed.
+
+### cip_base_test
+This basically creates the IP UVM env and its env cfg class instance. Any env cfg
+setting that may be required to be controlled externally via plusargs are looked
+up here, before the env instance is created. This also sets a few variables that
+pertain to how / when should the test exit on timeout or failure. In the run
+phase, the test calls `run_seq` which basically uses factory to create the
+virtual sequence instance using the `UVM_TEST_SEQ` string that is passed via
+plusarg. As a style guide, it is preferred to encapsulate a complete test within
+a virtual sequence and use the same `UVM_TEST` plusarg for all of the tests (which
+points to the extended IP test class), and only change the `UVM_TEST_SEQ` plusarg.
+
+This class is type parameterized with the env cfg class type `CFG_T` and the env
+class type `ENV_T` so that when extended IP test class creates the env and env cfg
+specific to that IP.
+```systemverilog
+class cip_base_test #(type CFG_T = cip_base_env_cfg,
+                      type ENV_T = cip_base_env) extends uvm_test;
+```
+
+### cip_tl_seq_item
+This is extended class of tl_seq_item to generate correct integrity values in
+`a_user` and `d_user`.
+
+## Extending from CIP library classes
+Let's say we are verifying an actual comportable IP `uart` which has `uart_tx`
+and `uart_rx` interface. User then develops the `uart_agent` to be able to talk
+to that interface. User invokes the `ralgen` tool to generate the `uart_reg_block`,
+and then starts developing UVM environment by extending from the CIP library
+classes in the following way.
+
+### uart_env_cfg
+```systemverilog
+class uart_env_cfg extends cip_base_env_cfg #(.RAL_T(uart_reg_block));
+```
+User adds the `uart_agent_cfg` object as a member so that it remains as a
+part of the env cfg and can be accessed everywhere. In the base class's
+`initialize()` function call, an instance of `uart_reg_block` is created, not
+the `dv_base_reg_block`, since we override the `RAL_T` type.
+
+### uart_env_cov
+```systemverilog
+class uart_env_cov extends cip_base_env_cov #(.CFG_T(uart_env_cfg));
+```
+User adds `uart` IP specific coverage items and uses the `cov` handle in
+scoreboard and test sequences to sample the coverage.
+
+### uart_virtual_sequencer
+```systemverilog
+class uart_virtual_sequencer extends cip_base_virtual_sequencer #(.CFG_T(uart_env_cfg),
+                                                                  .COV_T(uart_env_cov));
+```
+User adds the `uart_sequencer` handle to allow layered test sequences
+to send traffic to / from TileLink as well as `uart` interfaces.
+
+### uart_scoreboard
+```systemverilog
+class uart_scoreboard extends cip_base_scoreboard #(.CFG_T(uart_env_cfg),
+                                                    .RAL_T(uart_reg_block),
+                                                    .COV_T(uart_env_cov));
+```
+User adds analysis ports to grab packets from the `uart_monitor` to
+perform end-to-end checking.
+
+### uart_env
+```systemverilog
+class uart_env extends cip_base_env #(.CFG_T               (uart_env_cfg),
+                                      .COV_T               (uart_env_cov),
+                                      .VIRTUAL_SEQUENCER_T (uart_virtual_sequencer),
+                                      .SCOREBOARD_T        (uart_scoreboard));
+```
+User creates `uart_agent` object in the env and use it to connect with the
+virtual sequencer and the scoreboard. User also uses the env cfg settings to
+manipulate the uart agent cfg settings if required.
+
+### uart_base_vseq
+```systemverilog
+class uart_base_vseq extends cip_base_vseq #(.CFG_T               (uart_env_cfg),
+                                             .RAL_T               (uart_reg_block),
+                                             .COV_T               (uart_env_cov),
+                                             .VIRTUAL_SEQUENCER_T (uart_virtual_sequencer));
+```
+User adds a base virtual sequence as a starting point and adds common tasks and
+functions to perform `uart` specific operations. User then extends from
+`uart_base_vseq` to add layered test sequences.
+
+### uart_base_test
+```systemverilog
+class uart_base_test extends cip_base_test #(.ENV_T(uart_env), .CFG_T(uart_env_cfg));
+```
+User sets `UVM_TEST` plus arg to `uart_base_test` so that all tests create the UVM env
+that is automatically tailored to UART IP. Each test then sets the
+`UVM_TEST_SEQ` plusarg to run the specific test sequence, along with additional
+plusargs as required.
+
+## Configure Alert Device Agent from CIP library classes
+
+To configure alert device agents in a block level testbench environment that is extended
+from this CIP library class, please follow the steps below:
+* **${ip_name}_env_pkg.sv**: Add parameter `LIST_OF_ALERTS[]` and `NUM_ALERTS`.
+  Please make sure the alert names and order are correct.
+  For example in `otp_ctrl_env_pkg.sv`:
+  ```systemverilog
+  parameter string LIST_OF_ALERTS[] = {"fatal_macro_error", "fatal_check_error"};
+  parameter uint NUM_ALERTS         = 2;
+  ```
+* **${ip_name}_env_cfg.sv**: In function `initialize()`, assign `LIST_OF_ALERTS`
+  parameter to `list_of_alerts` variable which is created in `cip_base_env_cfg.sv`.
+  Note that this assignment should to be written before calling `super.initialize()`,
+  so that creating alert host agents will take the updated `list_of_alerts` variable.
+  For example in `otp_ctrl_env_cfg.sv`:
+  ```systemverilog
+  virtual function void initialize(bit [31:0] csr_base_addr = '1);
+    list_of_alerts = otp_ctrl_env_pkg::LIST_OF_ALERTS;
+    super.initialize(csr_base_addr);
+  ```
+* **tb.sv**: Add `DV_ALERT_IF_CONNECT` macro that declares alert interfaces,
+  connect alert interface wirings with DUT, and set alert_if to uvm_config_db.
+  Then connect alert_rx/tx to the DUT ports.
+  For example in otp_ctrl's `tb.sv`:
+  ```systemverilog
+  `DV_ALERT_IF_CONNECT()
+  otp_ctrl dut (
+    .clk_i                      (clk        ),
+    .rst_ni                     (rst_n      ),
+    .alert_rx_i                 (alert_rx   ),
+    .alert_tx_o                 (alert_tx   ),
+  ```
+Note that if the testbench is generated from `uvmdvgen.py`, using the `-hr` switch
+will automatically generate the skeleton code listed above for alert device agent.
+Details on how to use `uvmdvgen.py` please refer to the
+[uvmdvgen document](../../../../util/uvmdvgen/README.md).
+
+## CIP Testbench
+![CIP testbench diagram](./doc/tb.svg)
+The block diagram above shows the CIP testbench architecture, that puts
+together the static side `tb` which instantiates the `dut`, and the dynamic
+side, which is the UVM environment extended from CIP library. The diagram
+lists some common items that need to be instantiated in `tb`
+and set into `uvm_config_db` for the testbench to work.
+
+## Security Verification in cip_lib
+CIP contains reusable security verification components, sequences and function coverage.
+This section describes the details of them and the steps to enable them.
+
+### Security Verification for bus integrity
+The countermeasure of bus integrity can be fully verified via importing [tl_access_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/tl_access_tests.hjson) and [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/tl_device_access_types_testplan.hjson).
+The `tl_intg_err` test injects errors on control, data, or the ECC bits and verifies that the integrity error will trigger a fatal alert (provided via `cfg.tl_intg_alert_name`) and error status (provided via `cfg.tl_intg_alert_fields`) is set.
+Refer to section [cip_base_env_cfg](#cip_base_env_cfg) for more information on these 2 variables.
+The user may update these 2 variables as follows.
+```systemverilog
+class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
+  virtual function void initialize(bit [31:0] csr_base_addr = '1);
+    super.initialize(csr_base_addr);
+    tl_intg_alert_name = "fatal_fault_err";
+    // csr / field name may vary in different IPs
+    tl_intg_alert_fields[ral.fault_status.intg_err] = 1;
+```
+
+### Security Verification for memory integrity
+The memory integrity countermeasure stores the data integrity in the memory rather than generating the integrity on-the-fly during a read.
+The [passthru_mem_intg_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/passthru_mem_intg_tests.hjson) can fully verify this countermeasure.
+The details of the test sequences are described in the [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/passthru_mem_intg_testplan.hjson). Users need to override the task `inject_intg_fault_in_passthru_mem` to inject an integrity fault to the memory in the block common_vseq.
+
+The following is an example from `sram_ctrl`, in which it flips up to `MAX_TL_ECC_ERRORS` bits of the data and generates a backdoor write to the memory.
+```systemverilog
+class sram_ctrl_common_vseq extends sram_ctrl_base_vseq;
+  ...
+  virtual function void inject_intg_fault_in_passthru_mem(dv_base_mem mem,
+                                                          bit [bus_params_pkg::BUS_AW-1:0] addr);
+    bit[bus_params_pkg::BUS_DW-1:0] rdata;
+    bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] flip_bits;
+
+    rdata = cfg.mem_bkdr_util_h.sram_encrypt_read32_integ(addr, cfg.scb.key, cfg.scb.nonce);
+
+    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(flip_bits,
+        $countones(flip_bits) inside {[1:cip_base_pkg::MAX_TL_ECC_ERRORS]};)
+
+    `uvm_info(`gfn, $sformatf("Backdoor change mem (addr 0x%0h) value 0x%0h by flipping bits %0h",
+                              addr, rdata, flip_bits), UVM_LOW)
+
+    cfg.mem_bkdr_util_h.sram_encrypt_write32_integ(addr, rdata, cfg.scb.key, cfg.scb.nonce,
+                                                   flip_bits);
+  endfunction
+endclass
+```
+
+### Security Verification for shadow CSRs
+The countermeasure of shadow CSRs can be fully verified via importing [shadow_reg_errors_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/shadow_reg_errors_tests.hjson) and [shadow_reg_errors_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/shadow_reg_errors_testplan.hjson).
+The details of the test sequences are described in the testplan. Users need to assign the status CSR fields to `cfg.shadow_update_err_status_fields` and `cfg.shadow_storage_err_status_fields` for update error and storage error respectively.
+```systemverilog
+class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
+  virtual function void initialize(bit [31:0] csr_base_addr = '1);
+    super.initialize(csr_base_addr);
+    // csr / field name may vary in different IPs
+    shadow_update_err_status_fields[ral.err_code.invalid_shadow_update] = 1;
+    shadow_storage_err_status_fields[ral.fault_status.shadow] = 1;
+```
+
+### Security Verification for REGWEN CSRs
+If the REGWEN CSR meets the following criteria, it can be fully verified by the common [csr_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/csr_tests.hjson).
+ - The REGWEN CSR and its related lockable CSRs are HW read-only registers.
+ - The related lockable CSRs are not WO type, otherwise the read value is always 0 and CSR tests can't really verify if the write value is taken or not.
+ - No CSR exclusions have been added to the REGWEN CSR and its related lockable CSRs.
+If not, users need to write a test to verify it separately since cip_lib and dv_base_reg can't predict its value.
+For example, the [sram_ctrl_regwen_vseq](https://github.com/lowRISC/opentitan/blob/master/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_regwen_vseq.sv) has been added to verify `ctrl_regwen` and the the lockable register `ctrl` since `ctrl` is a `WO` register and excluded in CSR tests.
+
+Functional coverage for REGWEN CSRs and their related lockable CSRs is generated automatically in dv_base_reg.
+The details of functional coverage is described in [csr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/csr_testplan.hjson).
+
+### Security Verification for MUBI type CSRs
+A functional covergroup of MUBI type CSR is automatically created in the RAL model for each MUBI CSR, which ensures `True`, `False` and at least N of other values (N = width of the MUBI type) have been collected.
+This covergroup won't be sampled in CSR tests, since CSR tests only test the correctness of the value of register read / write but it won't check the block behavior when a different value is supplied to the MUBI CSR.
+Users should randomize the values of all the MUBI CSRs in non-CSR tests and check the design behaves correctly.
+The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` can be used to get the random values.
+
+### Security Verification for MUBI/LC_TX type ports
+In OpenTitan [Design Verification Methodology](../../../../doc/contributing/dv/methodology/README.md), it's mandatory to have 100% toggle coverage on all the ports.
+However, the MUBI defined values (`True` and `False`) are complement numbers.
+If users only test with `True` and `False` without using other values, toggle coverage can be 100%.
+Hence, user should add a functional covergroup for each MUBI type input port, via binding the interface `cip_mubi_cov_if` which contains a covergroup for MUBI.
+The type `lc_ctrl_pkg::lc_tx_t` is different than the Mubi4 type, as its defined values are different.
+So, it needs to be bound with the interface `cip_lc_tx_cov_if`.
+The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` and `cip_base_pkg::get_rand_lc_tx_val` can be used to get the random values.
+
+The following is an example from `sram_ctrl`, in which it binds the coverage interface to 2 MUBI input ports.
+```systemverilog
+module sram_ctrl_cov_bind;
+
+  bind sram_ctrl cip_mubi_cov_if #(.Width(4)) u_hw_debug_en_mubi_cov_if (
+    .rst_ni (rst_ni),
+    .mubi   (lc_hw_debug_en_i)
+  );
+
+  bind sram_ctrl cip_lc_tx_cov_if u_lc_escalate_en_cov_if (
+    .rst_ni (rst_ni),
+    .val    (lc_escalate_en_i)
+  );
+endmodule
+```
+Note: The `sim_tops` in sim_cfg.hjson should be updated to include this bind file.
+
+### Security Verification for common countermeasure primitives
+A [security countermeasure verification framework](../../../../doc/contributing/dv/sec_cm_dv_framework/README.md) is implemented in cip_lib to verify common countermeasure primitives in a semi-automated way.
+
+#### Design Verification
+cip_lib imports [sec_cm_pkg](https://github.com/lowRISC/opentitan/tree/master/hw/dv/sv/sec_cm), which automatically locates all the common countermeasure primitives and binds an interface to each of them.
+In the cib_base_vseq, it injects a fault to each of these primitives and verifies that the fault will lead to a fatal alert.
+The details of the sequences can be found in testplans - [sec_cm_count_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_count_testplan.hjson), [sec_cm_fsm_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_fsm_testplan.hjson) and [sec_cm_double_lfsr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_double_lfsr_testplan.hjson).
+If the block uses common security countermeasure primitives (prim_count, prim_sparse_fsm_flop, prim_double_lfsr), users can enable this sequence to fully verify them via following steps.
+
+1. Import the applicable sec_cm testplans.
+If more checks or sequences are needed, add another testpoint in the block testplan.
+For example, when the fault is detected by countermeasure, some subsequent operations won’t be executed.
+Add a testpoint in the testplan to capture this sequence and the checks.
+
+2. Import [sec_cm_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/sec_cm_tests.hjson) in sim_cfg.hjson file, as well as add applicable sec_cm bind files for `sim_tops`.
+The `ip_sec_cm` test will be added and all common countermeasure primitives will be verified in this test.
+
+```
+sim_tops: ["ip_ctrl_bind", "ip_ctrl_cov_bind",
+           // only add the corresponding bind file if DUT has the primitive
+           "sec_cm_prim_sparse_fsm_flop_bind",
+           "sec_cm_prim_count_bind",
+           "sec_cm_prim_double_lfsr_bind"]
+```
+
+3. Import sec_cm_pkg in the env_pkg
+```systemverilog
+package ip_env_pkg;
+  import uvm_pkg::*;
+  import sec_cm_pkg::*;
+  …
+```
+
+4. Set alert name for countermeasure if the alert name is different from the default name - “fatal_fault”.
+```systemverilog
+class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
+  virtual function void initialize(bit [31:0] csr_base_addr = '1);
+    super.initialize(csr_base_addr);
+    sec_cm_alert_name = "fatal_check_error";
+```
+
+5. Override the `check_sec_cm_fi_resp` task in ip_common_vseq to add additional sequences and checks after fault injection.
+This is an example from keymgr, in which CSR `fault_status` will be updated according to the location of the fault and the operation after fault inject will lead design to enter `StInvalid` state.
+```systemverilog
+class keymgr_common_vseq extends keymgr_base_vseq;
+  virtual task check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy);
+    bit[TL_DW-1:0] exp;
+
+    super.check_sec_cm_fi_resp(if_proxy);
+
+    case (if_proxy.sec_cm_type)
+      SecCmPrimCount: begin
+        // more than one prim_count are used, distinguishing them through the path of the primitive.
+        if (!uvm_re_match("*.u_reseed_ctrl*", if_proxy.path)) begin
+          exp[keymgr_pkg::FaultReseedCnt] = 1;
+        end else begin
+          exp[keymgr_pkg::FaultCtrlCnt] = 1;
+        end
+      end
+      SecCmPrimSparseFsmFlop: begin
+        exp[keymgr_pkg::FaultCtrlFsm] = 1;
+      end
+      default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
+    endcase
+    csr_rd_check(.ptr(ral.fault_status), .compare_value(exp));
+
+    // after an advance, keymgr should enter StInvalid
+    keymgr_advance();
+    csr_rd_check(.ptr(ral.op_status), .compare_value(keymgr_pkg::OpDoneFail));
+    csr_rd_check(.ptr(ral.working_state), .compare_value(keymgr_pkg::StInvalid));
+  endtask : check_sec_cm_fi_resp
+```
+
+6. Fault injection may trigger unexpected SVA errors. Override the `sec_cm_fi_ctrl_svas` function to disable them. `sec_cm_fi_ctrl_svas(.enable(1))` will be invoked before injecting fault. After reset, `sec_cm_fi_ctrl_svas(.enable(0))` will be called to re-enable the SVA checks.
+```systemverilog
+class keymgr_common_vseq extends keymgr_base_vseq;
+   virtual function void sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, bit enable);
+    case (if_proxy.sec_cm_type)
+      SecCmPrimCount: begin
+        if (enable) begin
+          $asserton(0, "tb.keymgr_kmac_intf");
+          $asserton(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
+          $asserton(0, "tb.dut.u_ctrl.DataEn_A");
+          $asserton(0, "tb.dut.u_ctrl.DataEnDis_A");
+          $asserton(0, "tb.dut.u_ctrl.CntZero_A");
+          $asserton(0, "tb.dut.u_kmac_if.LastStrb_A");
+          $asserton(0, "tb.dut.KmacDataKnownO_A");
+        end else begin
+          $assertoff(0, "tb.keymgr_kmac_intf");
+          $assertoff(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
+          $assertoff(0, "tb.dut.u_ctrl.DataEn_A");
+          $assertoff(0, "tb.dut.u_ctrl.DataEnDis_A");
+          $assertoff(0, "tb.dut.u_ctrl.CntZero_A");
+          $assertoff(0, "tb.dut.u_kmac_if.LastStrb_A");
+          $assertoff(0, "tb.dut.KmacDataKnownO_A");
+        end
+      end
+      SecCmPrimSparseFsmFlop: begin
+        // No need to disable any assertion
+      end
+
+      default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
+    endcase
+  endfunction: sec_cm_fi_ctrl_svas
+```
+
+#### Formal Verification
+Please refer to [formal document](../../../formal/README.md) on how to create a FPV environment for common countermeasures.
diff --git a/hw/dv/sv/cip_lib/cip_base_env.sv b/hw/dv/sv/cip_lib/cip_base_env.sv
index 0330b18..498ddd3 100644
--- a/hw/dv/sv/cip_lib/cip_base_env.sv
+++ b/hw/dv/sv/cip_lib/cip_base_env.sv
@@ -33,10 +33,18 @@
                                                              cfg.devmode_vif)) begin
       `uvm_fatal(get_full_name(), "failed to get devmode_vif from uvm_config_db")
     end
-    if (cfg.has_shadowed_regs &&
-        !uvm_config_db#(rst_shadowed_vif)::get(this, "", "rst_shadowed_vif", cfg.rst_shadowed_vif))
-        begin
-      `uvm_fatal(get_full_name(), "failed to get rst_shadowed_vif from uvm_config_db")
+
+    // Only get rst_shadowed_vif if it is an IP level testbench,
+    // and the IP contains shadowed registers.
+    if (cfg.is_chip == 0) begin
+      foreach(cfg.ral_models[i]) begin
+        if (cfg.ral_models[i].has_shadowed_regs() &&
+            !uvm_config_db#(rst_shadowed_vif)::get(this, "", "rst_shadowed_vif",
+                                                   cfg.rst_shadowed_vif)) begin
+          `uvm_fatal(get_full_name(), "failed to get rst_shadowed_vif from uvm_config_db")
+          break;
+        end
+      end
     end
 
     // Create & configure the TL agent.
@@ -54,12 +62,18 @@
     foreach(cfg.list_of_alerts[i]) begin
       string alert_name = cfg.list_of_alerts[i];
       string agent_name = {"m_alert_agent_", alert_name};
+      string common_seq_type;
+      void'($value$plusargs("run_%0s", common_seq_type));
       m_alert_agent[alert_name] = alert_esc_agent::type_id::create(agent_name, this);
       uvm_config_db#(alert_esc_agent_cfg)::set(this, agent_name, "cfg",
-          cfg.m_alert_agent_cfg[alert_name]);
-      cfg.m_alert_agent_cfg[alert_name].en_cov = cfg.en_cov;
-      // TODO, should be async clk?
-      cfg.m_alert_agent_cfg[alert_name].clk_freq_mhz = int'(cfg.clk_freq_mhz);
+          cfg.m_alert_agent_cfgs[alert_name]);
+      cfg.m_alert_agent_cfgs[alert_name].en_cov = cfg.en_cov;
+      cfg.m_alert_agent_cfgs[alert_name].clk_freq_mhz = int'(cfg.clk_freq_mhz);
+
+      // Alert_test sequence will wait until alert checked then drive response manually.
+      if (common_seq_type == "alert_test") begin
+        cfg.m_alert_agent_cfgs[alert_name].start_default_rsp_seq = 0;
+      end
     end
 
     // Create and configure the EDN agent if available.
@@ -70,14 +84,13 @@
         m_edn_pull_agent[i] = push_pull_agent#(.DeviceDataWidth(EDN_DATA_WIDTH))::
                               type_id::create(agent_name, this);
         uvm_config_db#(push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH)))::
-                      set(this, agent_name, "cfg", cfg.m_edn_pull_agent_cfg[i]);
-        cfg.m_edn_pull_agent_cfg[i].en_cov = cfg.en_cov;
+                      set(this, agent_name, "cfg", cfg.m_edn_pull_agent_cfgs[i]);
+        cfg.m_edn_pull_agent_cfgs[i].en_cov = cfg.en_cov;
       end
       if (!uvm_config_db#(virtual clk_rst_if)::get(this, "", "edn_clk_rst_vif",
           cfg.edn_clk_rst_vif)) begin
         `uvm_fatal(get_full_name(), "failed to get edn_clk_rst_vif from uvm_config_db")
       end
-      // TODO: is this correct?
       cfg.edn_clk_rst_vif.set_freq_mhz(cfg.edn_clk_freq_mhz);
     end
 
@@ -93,12 +106,12 @@
         cfg.m_tl_agent_cfgs[i].d_ready_delay_min = 0;
         cfg.m_tl_agent_cfgs[i].d_ready_delay_max = 0;
       end
-      foreach (cfg.m_alert_agent_cfg[i]) begin
-        cfg.m_alert_agent_cfg[i].alert_delay_min = 0;
-        cfg.m_alert_agent_cfg[i].alert_delay_max = 0;
+      foreach (cfg.m_alert_agent_cfgs[i]) begin
+        cfg.m_alert_agent_cfgs[i].alert_delay_min = 0;
+        cfg.m_alert_agent_cfgs[i].alert_delay_max = 0;
       end
-      foreach (cfg.m_edn_pull_agent_cfg[i]) begin
-        cfg.m_edn_pull_agent_cfg[i].zero_delays = 1;
+      foreach (cfg.m_edn_pull_agent_cfgs[i]) begin
+        cfg.m_edn_pull_agent_cfgs[i].zero_delays = 1;
       end
     end
 
@@ -106,6 +119,13 @@
     foreach (cfg.m_tl_agent_cfgs[i]) begin
       cfg.m_tl_agent_cfgs[i].synchronise_ports = 1'b1;
     end
+
+    // if en_cov is off, disable all other functional coverage
+    if (!cfg.en_cov) begin
+      cfg.en_tl_err_cov = 0;
+      cfg.en_tl_intg_err_cov = 0;
+      sec_cm_pkg::en_sec_cm_cov = 0;
+    end
   endfunction
 
   virtual function void connect_phase(uvm_phase phase);
@@ -130,7 +150,7 @@
       end
     end
     foreach(cfg.list_of_alerts[i]) begin
-      if (cfg.m_alert_agent_cfg[cfg.list_of_alerts[i]].is_active) begin
+      if (cfg.m_alert_agent_cfgs[cfg.list_of_alerts[i]].is_active) begin
         virtual_sequencer.alert_esc_sequencer_h[cfg.list_of_alerts[i]] =
             m_alert_agent[cfg.list_of_alerts[i]].sequencer;
       end
@@ -152,4 +172,3 @@
   endfunction : end_of_elaboration_phase
 
 endclass
-
diff --git a/hw/dv/sv/cip_lib/cip_base_env_cfg.sv b/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
index f987897..9c152da 100644
--- a/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
+++ b/hw/dv/sv/cip_lib/cip_base_env_cfg.sv
@@ -3,15 +3,20 @@
 // SPDX-License-Identifier: Apache-2.0
 
 class cip_base_env_cfg #(type RAL_T = dv_base_reg_block) extends dv_base_env_cfg #(RAL_T);
+  // control knobs to enable/disable covergroups
+  // these are default enabled. If en_cov is off, these will be disabled as well.
+  bit en_tl_err_cov      = 1;
+  bit en_tl_intg_err_cov = 1;
+
   // Downstream agent cfg objects.
 
-  // if the block supports only one RAL, just use `m_tl_agent_cfg`
-  // if there are multiple RALs, `m_tl_agent_cfg` is the default one for RAL with type `RAL_T`
-  // for the other RAL, can get from ral_models[string] and agent cfg from m_tl_agent_cfgs[string]
+  // If the block supports only one RAL, just use `m_tl_agent_cfg`.
+  // If there are multiple RALs, `m_tl_agent_cfg` is the default one for RAL with type `RAL_T`.
+  // For the other RAL, can get from ral_models[string] and agent cfg from m_tl_agent_cfgs[string].
   tl_agent_cfg        m_tl_agent_cfg;
   rand tl_agent_cfg   m_tl_agent_cfgs[string];
 
-  // Override this alert name at `initialize` if it's not as below
+  // Override this alert name at `initialize` if it's not as below.
   string              tl_intg_alert_name = "fatal_fault";
   string              sec_cm_alert_name  = "fatal_fault";
 
@@ -21,32 +26,34 @@
   // Format: tl_intg_alert_fields[ral.a_reg.a_field] = value
   uvm_reg_data_t      tl_intg_alert_fields[dv_base_reg_field];
 
-  // flag to indicate tl mem acess are gated due to local or global escalation
+  // Flag to indicate tl mem acess are gated due to local or global escalation.
   bit                 tl_mem_access_gated;
 
-  // Similar as the associative array above, if DUT has shadow registers, these two associative
+  // Flag to indicate if it is an IP or chip level testbench.
+  bit                 is_chip;
+
+  // Similar to the associative array above, if DUT has shadow registers, these two associative
   // arrays contains register fields related to shadow register's update and storage error status.
   uvm_reg_data_t      shadow_update_err_status_fields[dv_base_reg_field];
   uvm_reg_data_t      shadow_storage_err_status_fields[dv_base_reg_field];
 
-  alert_esc_agent_cfg m_alert_agent_cfg[string];
-  push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH)) m_edn_pull_agent_cfg[];
+  alert_esc_agent_cfg m_alert_agent_cfgs[string];
+  push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH)) m_edn_pull_agent_cfgs[];
 
   // EDN clk freq setting, if EDN is present.
   rand uint edn_clk_freq_mhz;
   rand clk_freq_diff_e tlul_and_edn_clk_freq_diff;
 
-  // Common interfaces - intrrupts, alerts, edn clk.
+  // Common interfaces - interrupts, alerts, edn clk.
   intr_vif            intr_vif;
   devmode_vif         devmode_vif;
   rst_shadowed_vif    rst_shadowed_vif;
   virtual clk_rst_if  edn_clk_rst_vif;
 
   // en_devmode default sets to 1 because all IPs' devmode_i is tied off internally to 1
-  // TODO: enable random drive devmode once design supports
+  // ICEBOX(#16739): enable random drive devmode once design supports
   bit  has_devmode = 1;
   bit  en_devmode = 1;
-  bit  has_shadowed_regs = 0;
 
   // If the data intg is passthru for the memory and the data intg value in mem is incorrect, it
   // won't trigger d_error in this mem block and the check is done in the processor
@@ -75,17 +82,18 @@
       // This could have used a function, but per the LRM that could cause circular
       // constraints against the "solve ... before" above. Same thing below for the
       // "big" setting.
-      (edn_clk_freq_mhz - clk_freq_mhz) inside {[-2 : 2]};
+      // cast to `int`, as edn_clk_freq_mhz and clk_freq_mhz are unsigned.
+      int'(edn_clk_freq_mhz - clk_freq_mhz) inside {[-2 : 2]};
     } else if (tlul_and_edn_clk_freq_diff == ClkFreqDiffBig) {
       // max diff is 100-24=76
-      !((edn_clk_freq_mhz - clk_freq_mhz) inside {[-70 : 70]});
+      !(int'(edn_clk_freq_mhz - clk_freq_mhz) inside {[-70 : 70]});
     }
 
     `DV_COMMON_CLK_CONSTRAINT(edn_clk_freq_mhz)
   }
   `uvm_object_param_utils_begin(cip_base_env_cfg #(RAL_T))
     `uvm_field_aa_object_string(m_tl_agent_cfgs,   UVM_DEFAULT)
-    `uvm_field_aa_object_string(m_alert_agent_cfg, UVM_DEFAULT)
+    `uvm_field_aa_object_string(m_alert_agent_cfgs, UVM_DEFAULT)
     `uvm_field_int             (num_interrupts,    UVM_DEFAULT)
   `uvm_object_utils_end
 
@@ -120,24 +128,24 @@
       check_alert_configs();
       foreach(list_of_alerts[i]) begin
         string alert_name = list_of_alerts[i];
-        // TODO: fix obj name
-        m_alert_agent_cfg[alert_name] = alert_esc_agent_cfg::type_id::create("m_alert_agent_cfg");
-        `DV_CHECK_RANDOMIZE_FATAL(m_alert_agent_cfg[alert_name])
-        m_alert_agent_cfg[alert_name].if_mode = dv_utils_pkg::Device;
-        m_alert_agent_cfg[alert_name].is_async = 1; // default async_on, can override this
-        m_alert_agent_cfg[alert_name].en_ping_cov = 0;
-        m_alert_agent_cfg[alert_name].en_lpg_cov = 0;
+        m_alert_agent_cfgs[alert_name] = alert_esc_agent_cfg::type_id::create(
+            $sformatf("m_alert_agent_cfgs[%s]", alert_name));
+        `DV_CHECK_RANDOMIZE_FATAL(m_alert_agent_cfgs[alert_name])
+        m_alert_agent_cfgs[alert_name].if_mode = dv_utils_pkg::Device;
+        m_alert_agent_cfgs[alert_name].is_async = 1; // default async_on, can override this
+        m_alert_agent_cfgs[alert_name].en_ping_cov = 0;
+        m_alert_agent_cfgs[alert_name].en_lpg_cov = 0;
       end
     end
 
-    m_edn_pull_agent_cfg = new[num_edn];
-    foreach (m_edn_pull_agent_cfg[i]) begin
-      m_edn_pull_agent_cfg[i] = push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH))::type_id::
-                                 create("m_edn_pull_agent_cfg");
-      `DV_CHECK_RANDOMIZE_FATAL(m_edn_pull_agent_cfg[i])
-      m_edn_pull_agent_cfg[i].agent_type = PullAgent;
-      m_edn_pull_agent_cfg[i].if_mode    = Device;
-      m_edn_pull_agent_cfg[i].hold_d_data_until_next_req = 1;
+    m_edn_pull_agent_cfgs = new[num_edn];
+    foreach (m_edn_pull_agent_cfgs[i]) begin
+      m_edn_pull_agent_cfgs[i] = push_pull_agent_cfg#(.DeviceDataWidth(EDN_DATA_WIDTH))::type_id::
+                                 create("m_edn_pull_agent_cfgs");
+      `DV_CHECK_RANDOMIZE_FATAL(m_edn_pull_agent_cfgs[i])
+      m_edn_pull_agent_cfgs[i].agent_type = PullAgent;
+      m_edn_pull_agent_cfgs[i].if_mode    = Device;
+      m_edn_pull_agent_cfgs[i].hold_d_data_until_next_req = 1;
     end
 
     if (jtag_riscv_map != null) ral.set_base_addr(ral.default_map.get_base_addr(), jtag_riscv_map);
diff --git a/hw/dv/sv/cip_lib/cip_base_scoreboard.sv b/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
index 76b7952..aa887c4 100644
--- a/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
+++ b/hw/dv/sv/cip_lib/cip_base_scoreboard.sv
@@ -21,18 +21,26 @@
 
   mem_model#() exp_mem[string];
 
+  // Holds the information related to expected alerts.
+  typedef struct packed {
+    bit expected;
+    bit is_fatal;
+    int max_delay;
+  } expected_alert_t;
+
   // alert checking related parameters
   bit do_alert_check = 1;
   bit check_alert_sig_int_err = 1;
   bit under_alert_handshake[string];
-  bit exp_alert[string];
-  bit is_fatal_alert[string];
-  int alert_chk_max_delay[string];
+  expected_alert_t expected_alert[string];
   int alert_count[string];
 
   // intg check
   bit en_d_user_intg_chk = 1;
 
+  // ecc error expected
+  bit ecc_error_addr[bit [AddrWidth - 1 : 0]];
+
   // covergroups
   tl_errors_cg_wrap   tl_errors_cgs_wrap[string];
   tl_intg_err_cg_wrap tl_intg_err_cgs_wrap[string];
@@ -52,7 +60,9 @@
       alert_fifos[alert_name] = new($sformatf("alert_fifo[%s]", alert_name), this);
     end
     edn_fifos = new[cfg.num_edn];
-    foreach (cfg.m_edn_pull_agent_cfg[i]) edn_fifos[i] = new($sformatf("edn_fifos[%0d]", i), this);
+    foreach (cfg.m_edn_pull_agent_cfgs[i]) begin
+      edn_fifos[i] = new($sformatf("edn_fifos[%0d]", i), this);
+    end
     foreach (cfg.m_tl_agent_cfgs[i]) begin
       exp_mem[i] = mem_model#()::type_id::create({"exp_mem_", i}, this);
     end
@@ -66,34 +76,38 @@
       bit has_ro_mem;
 
       if (has_mem) begin
-        get_all_mem_attrs(cfg.ral_models[ral_name], has_mem_byte_access_err, has_wo_mem, has_ro_mem);
+        get_all_mem_attrs(cfg.ral_models[ral_name], has_mem_byte_access_err, has_wo_mem,
+                          has_ro_mem);
       end
+      if (cfg.en_tl_err_cov) begin
+        tl_errors_cgs_wrap[ral_name] = new($sformatf("tl_errors_cgs_wrap[%0s]", ral_name));
+        if (!has_csr) begin
+          tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_csr_size_err.option.weight = 0;
+        end
+        if (!has_unmapped) begin
+          tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_unmapped_err.option.weight = 0;
+        end
+        if (!has_mem_byte_access_err) begin
+          tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_byte_access_err.option.weight = 0;
+        end
+        if (!has_wo_mem) begin
+          tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_wo_err.option.weight = 0;
+        end
+        if (!has_ro_mem) begin
+          tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_ro_err.option.weight = 0;
+        end
 
-      tl_errors_cgs_wrap[ral_name] = new($sformatf("tl_errors_cgs_wrap[%0s]", ral_name));
-      if (!has_csr) begin
-        tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_csr_size_err.option.weight = 0;
       end
-      if (!has_unmapped) begin
-        tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_unmapped_err.option.weight = 0;
-      end
-      if (!has_mem_byte_access_err) begin
-        tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_byte_access_err.option.weight = 0;
-      end
-      if (!has_wo_mem) begin
-        tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_wo_err.option.weight = 0;
-      end
-      if (!has_ro_mem) begin
-        tl_errors_cgs_wrap[ral_name].tl_errors_cg.cp_mem_ro_err.option.weight = 0;
-      end
+      if (cfg.en_tl_intg_err_cov) begin
+        if (has_mem) begin
+          tl_intg_err_mem_subword_cgs_wrap[ral_name] = new(
+              $sformatf("tl_intg_err_mem_subword_cgs_wrap[%0s]", ral_name));
+        end
 
-      if (has_mem) begin
-        tl_intg_err_mem_subword_cgs_wrap[ral_name] = new(
-            $sformatf("tl_intg_err_mem_subword_cgs_wrap[%0s]", ral_name));
-      end
-
-      tl_intg_err_cgs_wrap[ral_name] = new($sformatf("tl_intg_err_cgs_wrap[%0s]", ral_name));
-      if (!has_mem || !has_csr) begin
-        tl_intg_err_cgs_wrap[ral_name].tl_intg_err_cg.cp_is_mem.option.weight = 0;
+        tl_intg_err_cgs_wrap[ral_name] = new($sformatf("tl_intg_err_cgs_wrap[%0s]", ral_name));
+        if (!has_mem || !has_csr) begin
+          tl_intg_err_cgs_wrap[ral_name].tl_intg_err_cg.cp_is_mem.option.weight = 0;
+        end
       end
     end
   endfunction
@@ -193,8 +207,6 @@
             `uvm_error(`gfn, $sformatf("alert %s has unexpected signal int error", alert_name))
           end else if (item.ping_timeout && cfg.en_scb_ping_chk == 1) begin
             `uvm_error(`gfn, $sformatf("alert %s has unexpected timeout error", alert_name))
-          end else if (item.alert_esc_type == AlertEscPingTrans && cfg.en_scb_ping_chk == 1) begin
-            `uvm_error(`gfn, $sformatf("alert %s has unexpected alert ping response", alert_name))
           end
         end
       join_none
@@ -203,10 +215,10 @@
 
   // Called at the start of each alert handshake. The default implementation depends on the
   // do_alert_check flag. If that is set, it checks that an alert is expected (by checking
-  // exp_alert[alert_name]).
+  // expected_alert[alert_name].expected).
   virtual function void on_alert(string alert_name, alert_esc_seq_item item);
     if (do_alert_check) begin
-      `DV_CHECK_EQ(exp_alert[alert_name], 1,
+      `DV_CHECK_EQ(expected_alert[alert_name].expected, 1,
                    $sformatf("alert %0s triggered unexpectedly", alert_name))
     end
   endfunction
@@ -236,23 +248,23 @@
     return alert_count[alert_name];
   endfunction
 
-  // this task is implemented to check if expected alert is triggered within certain clock cycles
-  // if alert is fatal alert, it will expect alert handshakes until reset
-  // if alert is not fatal alert, it will set exp_alert back to 0 once finish alert_checking
+  // This task checks if expected alert is triggered within certain clock cycles.
+  // If alert is fatal it will expect alert handshakes until reset, otherwise it will clear
+  // expected_alert's expected flag when check is finished.
   virtual task check_alerts();
     foreach (cfg.list_of_alerts[i]) begin
       automatic string alert_name = cfg.list_of_alerts[i];
       fork
         forever begin
-          wait(exp_alert[alert_name] == 1 && cfg.under_reset == 0);
-          if (is_fatal_alert[alert_name]) begin
+          wait(expected_alert[alert_name].expected == 1 && cfg.under_reset == 0);
+          if (expected_alert[alert_name].is_fatal) begin
             while (cfg.under_reset == 0) begin
               check_alert_triggered(alert_name);
               wait(under_alert_handshake[alert_name] == 0 || cfg.under_reset == 1);
             end
           end else begin
             check_alert_triggered(alert_name);
-            exp_alert[alert_name] = 0;
+            expected_alert[alert_name].expected = 0;
           end
         end
       join_none
@@ -261,12 +273,13 @@
 
   virtual task check_alert_triggered(string alert_name);
     // Add 1 extra negedge edge clock to make sure no race condition.
-    repeat(alert_esc_agent_pkg::ALERT_B2B_DELAY + 1 + alert_chk_max_delay[alert_name]) begin
+    repeat(alert_esc_agent_pkg::ALERT_B2B_DELAY + 1 + expected_alert[alert_name].max_delay) begin
       cfg.clk_rst_vif.wait_n_clks(1);
       if (under_alert_handshake[alert_name] || cfg.under_reset) return;
     end
+    if (!cfg.en_scb) return;
     `uvm_error(`gfn, $sformatf("alert %0s did not trigger max_delay:%0d",
-                               alert_name, alert_chk_max_delay[alert_name]))
+                               alert_name, expected_alert[alert_name].max_delay))
   endtask
 
   // This function is used for individual IPs to set when they expect certain alert to trigger
@@ -286,15 +299,16 @@
         // delay a negedge clk to avoid race condition between this function and
         // `under_alert_handshake` variable
         cfg.clk_rst_vif.wait_n_clks(1);
-        if (under_alert_handshake[alert_name] || exp_alert[alert_name]) begin
-          `uvm_info(`gfn, $sformatf("Current %0s alert status under_alert_handshake=%0b,\
-                    exp_alert=%0b, request ignored", alert_name, under_alert_handshake[alert_name],
-                    exp_alert[alert_name]), UVM_MEDIUM)
+        if (under_alert_handshake[alert_name] || expected_alert[alert_name].expected) begin
+          `uvm_info(`gfn, $sformatf(
+                    "Current %0s status: under_alert_handshake=%0b, exp_alert=%0b, request ignored",
+                    alert_name,
+                    under_alert_handshake[alert_name],
+                    expected_alert[alert_name].expected
+                    ), UVM_MEDIUM)
         end else begin
           `uvm_info(`gfn, $sformatf("alert %0s is expected to trigger", alert_name), UVM_HIGH)
-          is_fatal_alert[alert_name] = is_fatal;
-          exp_alert[alert_name] = 1;
-          alert_chk_max_delay[alert_name] = max_delay;
+          expected_alert[alert_name] = '{1, is_fatal, max_delay};
         end
       end
     join_none
@@ -360,7 +374,7 @@
     bit exp_d_error;
 
     bit unmapped_err, mem_access_err, bus_intg_err, byte_wr_err, csr_size_err, tl_item_err;
-    bit mem_byte_access_err, mem_wo_err, mem_ro_err, custom_err;
+    bit mem_byte_access_err, mem_wo_err, mem_ro_err, custom_err, ecc_err;
 
     cip_tl_seq_item cip_item;
     tl_intg_err_e tl_intg_err_type;
@@ -377,10 +391,13 @@
 
     mem_access_err = !is_tl_mem_access_allowed(item, ral_name, mem_byte_access_err, mem_wo_err,
                                                mem_ro_err, custom_err);
-    if (mem_access_err || cfg.tl_mem_access_gated) begin
+    if (mem_access_err) begin
       // Some memory implementations may not return an error response on invalid accesses.
-      exp_d_error |= mem_byte_access_err | mem_wo_err | mem_ro_err | custom_err |
-                     cfg.tl_mem_access_gated;
+      exp_d_error |= mem_byte_access_err | mem_wo_err | mem_ro_err | custom_err;
+    end
+
+    if (is_mem_addr(item, ral_name) && cfg.tl_mem_access_gated) begin
+      exp_d_error |= cfg.tl_mem_access_gated;
     end
 
     bus_intg_err = !item.is_a_chan_intg_ok(.throw_error(0));
@@ -392,14 +409,19 @@
     byte_wr_err = is_tl_access_unsupported_byte_wr(item, ral_name);
     csr_size_err = !is_tl_csr_write_size_gte_csr_width(item, ral_name);
     tl_item_err = item.get_exp_d_error();
+
+    // For flash, address has to be 8byte aligned.
+    ecc_err = ecc_error_addr.exists({item.a_addr[AddrWidth-1:3],3'b0});
+
     `downcast(cip_item, item)
     cip_item.get_a_chan_err_info(tl_intg_err_type, num_cmd_err_bits, num_data_err_bits,
                                  write_w_instr_type_err, instr_type_err);
     exp_d_error |= byte_wr_err | bus_intg_err | csr_size_err | tl_item_err |
-                   write_w_instr_type_err | instr_type_err;
+                   write_w_instr_type_err | instr_type_err |
+                   ecc_err;
 
     invalid_access = unmapped_err | mem_access_err | bus_intg_err | csr_size_err | tl_item_err |
-                     write_w_instr_type_err | instr_type_err;
+                     write_w_instr_type_err | instr_type_err | cfg.tl_mem_access_gated;
 
     if (channel == DataChannel) begin
       // integrity at d_user is from DUT, which should be always correct, except data integrity for
@@ -410,14 +432,16 @@
             .throw_error(cfg.m_tl_agent_cfgs[ral_name].check_tl_errs)));
 
       // sample covergroup
-      tl_intg_err_cgs_wrap[ral_name].sample(tl_intg_err_type, num_cmd_err_bits, num_data_err_bits,
-                                            is_mem_addr(item, ral_name));
+      if (cfg.en_tl_intg_err_cov) begin
+        tl_intg_err_cgs_wrap[ral_name].sample(tl_intg_err_type, num_cmd_err_bits, num_data_err_bits,
+                                              is_mem_addr(item, ral_name));
 
-      if (tl_intg_err_mem_subword_cgs_wrap.exists(ral_name)) begin
-        tl_intg_err_mem_subword_cgs_wrap[ral_name].sample(
-            .tl_intg_err_type(tl_intg_err_type),
-            .write(item.a_opcode != tlul_pkg::Get),
-            .num_enable_bytes($countones(item.a_mask)));
+        if (tl_intg_err_mem_subword_cgs_wrap.exists(ral_name)) begin
+          tl_intg_err_mem_subword_cgs_wrap[ral_name].sample(
+              .tl_intg_err_type(tl_intg_err_type),
+              .write(item.a_opcode != tlul_pkg::Get),
+              .num_enable_bytes($countones(item.a_mask)));
+        end
       end
     end
 
@@ -425,20 +449,25 @@
       `DV_CHECK_EQ(item.d_error, exp_d_error,
           $sformatf({"On interface %0s, TL item: %0s, unmapped_err: %0d, mem_access_err: %0d, ",
                     "bus_intg_err: %0d, byte_wr_err: %0d, csr_size_err: %0d, tl_item_err: %0d, ",
-                    "write_w_instr_type_err: %0d, ", "cfg.tl_mem_access_gated: %0d"},
+                    "write_w_instr_type_err: %0d, ", "cfg.tl_mem_access_gated: %0d ",
+                    "ecc_err: %0d"},
                     ral_name, item.sprint(uvm_default_line_printer), unmapped_err, mem_access_err,
                     bus_intg_err, byte_wr_err, csr_size_err, tl_item_err, write_w_instr_type_err,
-                    cfg.tl_mem_access_gated))
+                    cfg.tl_mem_access_gated, ecc_err))
 
       // In data read phase, check d_data when d_error = 1.
       if (item.d_error && (item.d_opcode == tlul_pkg::AccessAckData)) begin
         check_tl_read_value_after_error(item, ral_name);
       end
 
+      // we don't have cross coverage for simultaneous errors because 1) they're not important,
+      // 2) there are so many errors and combinations, 3) if we do cross them, we need to take
+      // out many invalid combinations.
       // these errors all have the same outcome. Only sample coverages when there is just one
       // error, so that we know the error actually triggers the outcome
-      if ($onehot({unmapped_err, mem_byte_access_err, mem_wo_err, mem_ro_err,
-                   bus_intg_err, byte_wr_err, csr_size_err, tl_item_err})) begin
+      if (cfg.en_tl_err_cov && $onehot({unmapped_err, mem_byte_access_err, mem_wo_err, mem_ro_err,
+                   bus_intg_err, byte_wr_err, csr_size_err, tl_item_err, write_w_instr_type_err,
+                   instr_type_err})) begin
         tl_errors_cgs_wrap[ral_name].sample(.unmapped_err(unmapped_err),
                                             .csr_size_err(csr_size_err),
                                             .mem_byte_access_err(mem_byte_access_err),
@@ -546,14 +575,17 @@
 
   // check if csr write size greater or equal to csr width
   virtual function bit is_tl_csr_write_size_gte_csr_width(tl_seq_item item, string ral_name);
+    uvm_reg_addr_t addr = cfg.ral_models[ral_name].get_normalized_addr(item.a_addr);
+    dv_base_reg       csr;
+    dv_base_reg_block blk;
     if (!is_tl_access_mapped_addr(item, ral_name) || is_mem_addr(item, ral_name)) return 1;
-    if (cfg.ral_models[ral_name].get_supports_sub_word_csr_writes()) return 1;
+    // The RAL may be composed of sub-blocks each with its own settings. Find the
+    // sub-block to which this address (CSR) belongs.
+    `downcast(csr, cfg.ral_models[ral_name].default_map.get_reg_by_offset(addr))
+    `downcast(blk, csr.get_parent())
+    if (blk.get_supports_sub_word_csr_writes()) return 1;
     if (item.is_write()) begin
-      dv_base_reg    csr;
-      uvm_reg_addr_t addr = cfg.ral_models[ral_name].get_normalized_addr(item.a_addr);
       uint           csr_msb_pos;
-      `DV_CHECK_FATAL($cast(csr,
-                            cfg.ral_models[ral_name].default_map.get_reg_by_offset(addr)))
       csr_msb_pos = csr.get_msb_pos();
       if (csr_msb_pos >= 24 && item.a_mask[3:0] != 'b1111 ||
           csr_msb_pos >= 16 && item.a_mask[2:0] != 'b111  ||
@@ -565,18 +597,20 @@
     return 1;
   endfunction
 
+  protected virtual function void reset_alert_state();
+    foreach (cfg.list_of_alerts[i]) begin
+      alert_fifos[cfg.list_of_alerts[i]].flush();
+      expected_alert[cfg.list_of_alerts[i]] = '0;
+      under_alert_handshake[cfg.list_of_alerts[i]] = 0;
+    end
+  endfunction
+
   virtual function void reset(string kind = "HARD");
     super.reset(kind);
     foreach (tl_a_chan_fifos[i]) tl_a_chan_fifos[i].flush();
     foreach (tl_d_chan_fifos[i]) tl_d_chan_fifos[i].flush();
     foreach (edn_fifos[i]) edn_fifos[i].flush();
-    foreach(cfg.list_of_alerts[i]) begin
-      alert_fifos[cfg.list_of_alerts[i]].flush();
-      exp_alert[cfg.list_of_alerts[i]]             = 0;
-      under_alert_handshake[cfg.list_of_alerts[i]] = 0;
-      is_fatal_alert[cfg.list_of_alerts[i]]        = 0;
-      alert_chk_max_delay[cfg.list_of_alerts[i]]   = 0;
-    end
+    reset_alert_state();
     cfg.tl_mem_access_gated = 0;
   endfunction
 
diff --git a/hw/dv/sv/cip_lib/cip_macros.svh b/hw/dv/sv/cip_lib/cip_macros.svh
index ce28fb5..0bf2ae1 100644
--- a/hw/dv/sv/cip_lib/cip_macros.svh
+++ b/hw/dv/sv/cip_lib/cip_macros.svh
@@ -7,10 +7,10 @@
 
 // Declare array of alert interface, using parameter NUM_ALERTS and LIST_OF_ALERTS, and connect to
 // arrays of wires (alert_tx and alert_rx). User need to manually connect these wires to DUT
-// Also set each alert_if to uvm_config_db to use in env
+// Also set each alert_if to uvm_config_db to use in env.
 `ifndef DV_ALERT_IF_CONNECT
-`define DV_ALERT_IF_CONNECT \
-  alert_esc_if alert_if[NUM_ALERTS](.clk(clk), .rst_n(rst_n)); \
+`define DV_ALERT_IF_CONNECT(CLK_ = clk, RST_N_ = rst_n) \
+  alert_esc_if alert_if[NUM_ALERTS](.clk(CLK_), .rst_n(RST_N_)); \
   prim_alert_pkg::alert_rx_t [NUM_ALERTS-1:0] alert_rx; \
   prim_alert_pkg::alert_tx_t [NUM_ALERTS-1:0] alert_tx; \
   for (genvar k = 0; k < NUM_ALERTS; k++) begin : connect_alerts_pins \
@@ -26,7 +26,6 @@
 // Declare edn clock, reset and push_pull_if. Connect them and set edn_clk_rst_if and edn_if for
 // using them in env
 // Use this macro in tb.sv if the IP connects to a EDN interface
-// TODO, tie core reset with EDN reset for now
 `ifndef DV_EDN_IF_CONNECT
 `define DV_EDN_IF_CONNECT \
   wire edn_rst_n; \
@@ -57,17 +56,17 @@
 `ifndef _DV_MUBI_DIST
 `define _DV_MUBI_DIST(VAR_, TRUE_, FALSE_, MAX_, T_WEIGHT_, F_WEIGHT_, OTHER_WEIGHT_) \
   if (TRUE_ > FALSE_) { \
-    VAR_ dist {TRUE_                         := T_WEIGHT_ * (MAX_ - 1), \
-               FALSE_                        := F_WEIGHT_ * (MAX_ - 1), \
-               [0 : FALSE_ - 1]              := OTHER_WEIGHT_,          \
-               [FALSE_ + 1 : TRUE_ - 1]      := OTHER_WEIGHT_,          \
-               [TRUE_ + 1 : MAX_]            := OTHER_WEIGHT_};         \
-  } else {                                                              \
-    VAR_ dist {TRUE_                         := T_WEIGHT_ * (MAX_ - 1), \
-               FALSE_                        := F_WEIGHT_ * (MAX_ - 1), \
-               [0 : TRUE_ - 1]               := OTHER_WEIGHT_,          \
-               [TRUE_ + 1 : FALSE_ - 1]      := OTHER_WEIGHT_,          \
-               [FALSE_+ 1 : MAX_]            := OTHER_WEIGHT_};         \
+    VAR_ dist {TRUE_                         := (T_WEIGHT_) * ((MAX_) - 1), \
+               FALSE_                        := (F_WEIGHT_) * ((MAX_) - 1), \
+               [0 : FALSE_ - 1]              := (OTHER_WEIGHT_),            \
+               [FALSE_ + 1 : TRUE_ - 1]      := (OTHER_WEIGHT_),            \
+               [TRUE_ + 1 : (MAX_)]          := (OTHER_WEIGHT_)};           \
+  } else {                                                                  \
+    VAR_ dist {TRUE_                         := (T_WEIGHT_) * ((MAX_) - 1), \
+               FALSE_                        := (F_WEIGHT_) * ((MAX_) - 1), \
+               [0 : TRUE_ - 1]               := (OTHER_WEIGHT_),            \
+               [TRUE_ + 1 : FALSE_ - 1]      := (OTHER_WEIGHT_),            \
+               [FALSE_+ 1 : (MAX_)]          := (OTHER_WEIGHT_)};           \
   }
 `endif
 
@@ -102,4 +101,21 @@
   `_DV_MUBI_DIST(VAR_, MuBi16True, MuBi16False, T_WEIGHT_, (1 << 16) - 1, F_WEIGHT_, OTHER_WEIGHT_)
 `endif
 
+
+// A macro to simplify the creation of coverpoints for lc_tx_t variables.
+`ifndef DV_LC_TX_T_CP_BINS
+`define DV_LC_TX_T_CP_BINS         \
+    bins on = {lc_ctrl_pkg::On};   \
+    bins off = {lc_ctrl_pkg::Off}; \
+    bins others = default;
+`endif
+
+// A macro to simplify the creation of coverpoints for mubi4_t variables.
+`ifndef DV_MUBI4_CP_BINS
+`define DV_MUBI4_CP_BINS                      \
+    bins true = {prim_mubi_pkg::MuBi4True};   \
+    bins false = {prim_mubi_pkg::MuBi4False}; \
+    bins others = default;
+`endif
+
 `endif  // __CIP_MACROS_SVH__
diff --git a/hw/dv/sv/cip_lib/doc/index.md b/hw/dv/sv/cip_lib/doc/index.md
deleted file mode 100644
index 30e4bc5..0000000
--- a/hw/dv/sv/cip_lib/doc/index.md
+++ /dev/null
@@ -1,605 +0,0 @@
-# Comportable IP Testbench Architecture
-
-
-## Overview
-Going along the lines of what it takes to design an IP that adheres to the
-[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 environment framework that
-each IP level environment components can extend from to get started with DV. The goal
-here is to maximize code reuse across all test benches so that we can improve the
-efficiency and time to market. The features described here are not exhaustive,
-so it is highly recommended to the reader that they examine the code directly. In
-course of development, we also periodically identify pieces of verification logic that
-might be developed for one IP but is actually a good candidate to be added to
-these library classes instead. This doc is instead intended to provide the user
-a foray into what these are and how are the meant to be used.
-
-
-## CIP environment block diagram
-![CIP environment block diagram](env.svg)
-
-## CIP library classes
-The CIP library includes the base ral model, env cfg object, coverage
-object, virtual sequencer, scoreboard, env, base virtual sequence and finally
-the test class. To achieve run-time polymorphism, these classes are type
-parameterized to indicate what type of child objects are to be created. In the
-IP environments, the extended classes indicate the correct type parameters.
-
-### cip_base_env_cfg
-This class is intended to contain all of the settings, knobs, features, interface
-handles and downstream agent cfg handles. Features that are common to all IPs in
-accordance with the comportability spec are made a part of this base class, while the
-extended IP env cfg class will contain settings specific to that IP. An instance of
-the env cfg class is created in `cip_base_test::build_phase` and the handle is
-passed over uvm_config_db for the CIP env components to pick up. This allows
-the handle to the env cfg object to be available in the env's build_phase. Settings
-in the env cfg can then be used to configure the env based on the test needs.
-
-A handle to this class instance is passed on to the scoreboard, virtual
-sequencer and coverage objects so that all such common settings and features
-are instantly accessible everywhere.
-
-This class is type parameterized in the following way:
-```systemverilog
-class cip_base_env_cfg #(type RAL_T = dv_base_reg_block) extends uvm_object;
-```
-The IP env cfg class will then extend from this class with the RAL_T parameter set
-to the actual IP RAL model class. This results in IP RAL model getting factory
-overridden automatically in the base env cfg itself during creation, so there is no
-need for manual factory override. We follow the same philosophy in all CIP library
-classes.
-
-The following is a list of common features and settings:
-* **clk_rst_if**: A handle to the clk_rst_if that controls the main clk and reset
-  to the DUT.
-* **intr_vif**: This is a handle to the `pins_if #(NUM_MAX_INTERRUPTS=64)` interface
-  instance created in the tb to hookup the DUT interrupts. The actual number of
-  interrupts might be much less than 64, but that is ok - we just connect as
-  many as the DUT provides. The reason for going with a fixed width pins_if is
-  to allow the intr_vif to be available in this base env cfg class (which does not
-  know how many interrupt each IP DUT provides).
-* **devmode_vif**: THis is a handle to the `pins_if #(1)` interface instance created
-  in the tb to hookup the DUT input `devmode`.
-* **tl_agent_cfg**: The downstream TileLink host agent created in the cip_base_env
-  class requires the agent cfg handle to tell it how to configure the agent.
-* **alert_agent_cfgs**: Similar to tl_agent_cfg, the downstream alert device agent
-  created in the cip_base_env class requires the agent cfg handles to tell it how to
-  configure the agent. In default, alert agent is configured in device mode,
-  asynchronous, active and the ping coverage is turned off.
-* **ral**: This is the instance to the auto-generated RAL model that is
-  extended from `dv_base_reg_block`. In the base class, this is created using
-  the RAL_T class parameter which the extended IP env cfg class sets correctly.
-* **tl_intg_alert_name**: Name of the alert that will be triggered on  TLUL
-  integrity error detection. The default name used for this type of alert is
-  "fatal_fault". The block may use a different name too - in that case, please
-  update this member to reflect the correct name in the `initialize()` method.
-* **tl_intg_alert_fields**: An associative array of CSR fields keyed with the
-  objection handle of the corresponding CSR field and valued with the expected
-  value. This is the list of CSR fields that are modified when an alert triggers
-  due to TL integrity violation event. The DV user is required to build this list
-  in the `initialize()` method after `super.initialize(csr_base_addr);`
-```systemverilog
-virtual function void initialize(bit [31:0] csr_base_addr = '1);
-  super.initialize(csr_base_addr); // ral model is created in `super.initialize`
-  tl_intg_alert_fields[ral.a_status_reg.a_field] = value;
-```
-
-Apart from these, there are several common settings such as `zero_delays`,
-`clk_freq_mhz`, which are randomized as well as knobs such as `en_scb` and
-`en_cov` to turn on/off scoreboard and coverage collection respectively.
-
-The base class provides a virtual method called `initialize()` which is called
-in `cip_base_test::build_phase` to create some of the objects listed above. If
-the extended IP env cfg class has more such objects added,  then the `initialize()`
-method is required to be overridden to create those objects as well.
-
-We make all downstream interface agent cfg handles as a part of IP extension of
-cip_base_env_cfg so that all settings for the env and all downstream agents are
-available within the env cfg handle. Since the env cfg handle is passed to all cip
-components, all those settings are also accessible.
-
-### cip_base_env_cov
-This is the base coverage object that contain all functional coverpoints and
-covergroups. The main goal is to have all functional coverage elements
-implemented in a single place. This class is extended from `uvm_component`
-so that it allows items to be set via `'uvm_config_db` using the component's
-hierarchy. This is created in cip_base_env and a handle to it is passed to the
-scoreboard and the virtual sequencer. This allows coverage to be sampled in
-scoreboard as well as the test sequences.
-
-This class is type parameterized with the env cfg class type `CFG_T` so that it
-can derive coverage on some of the env cfg settings.
-```systemverilog
-class cip_base_env_cov #(type CFG_T = cip_base_env_cfg) extends uvm_component;
-```
-
-The following covergroups are defined outside of the class for use by all IP
-testbenches:
-* `intr_cg`: Covers individual and cross coverage on intr_enable and intr_state for all interrupts in IP
-* `intr_test_cg`: Covers intr_test coverage and its cross with intr_enable and intr_state for all interrupts in IP
-* `intr_pins_cg`: Covers values and transitions on all interrupt output pins of IP
-* `sticky_intr_cov`: Covers sticky interrupt functionality of all applicable interrupts in IP
-
-Covergroups `intr_cg`, `intr_test_cg` and `intr_pins_cg` are instantiated
-and allocated in `cip_base_env_cov` by default in all IPs.
-On the other hand, `sticky_intr_cov` is instantiated with string key.
-The string key represents the interrupts names that are sticky. This is specific
-to each IP and is required to be created and instantiated in extended `cov` class.
-
-### cip_base_virtual_sequencer
-This is the base virtual sequencer class that contains a handle to the
-`tl_sequencer` to allow layered test sequences to be created. The extended IP
-virtual sequencer class will include handles to the IP specific agent
-sequencers.
-
-This class is type-parameterized with the env cfg class type `CFG_T` and coverage
-class type `COV_T` so that all test sequences can access the env cfg settings and
-sample the coverage via the `p_sequencer` handle.
-```systemverilog
-class cip_base_virtual_sequencer #(type CFG_T = cip_base_env_cfg,
-                                   type COV_T = cip_base_env_cov) extends uvm_sequencer;
-```
-
-### cip_base_scoreboard
-This is the base scoreboard component that already connects with the TileLink
-agent monitor to grab tl packets via analysis port at the address and the data
-phases. It provides a virtual task called `process_tl_access` that the extended
-IP scoreboard needs to implement. Please see code for additional details. The
-extended IP scoreboard class will connect with the IP-specific interface monitors
-if applicable to grab items from those analysis ports.
-
-This class is type-parameterized with the env cfg class type `CFG_T`, ral class
-type `RAL_T` and the coverage class type `COV_T`.
-```systemverilog
-class cip_base_scoreboard #(type RAL_T = dv_base_reg_block,
-                            type CFG_T = cip_base_env_cfg,
-                            type COV_T = cip_base_env_cov) extends uvm_component;
-```
-There are several virtual tasks and functions that are to be overridden
-in extended IP scoreboard class. Please take a look at the code for more
-details.
-
-### cip_base_env
-This is the base UVM env that puts all of the above components together
-and creates and makes connections across them. In the build phase, it retrieves
-the env cfg class type handle from `uvm_config_db` as well as all the virtual
-interfaces (which are actually part of the env cfg class). It then uses the env
-cfg settings to modify the downstream agent cfg settings as required. All of
-the above components are created based on env cfg settings, along with the TileLink
-host agent and alert device agents if the module has alerts. In the connect phase,
-the scoreboard connects with the monitor within the TileLink agent to be able to
-grab packets from the TL interface during address and the data phases. The scoreboard
-also connects the alert monitor within the alert_esc_agent to grab packets
-regarding alert handshake status. In the end of elaboration phase, the ral
-model within the env cfg handle is locked and the ral sequencer and adapters are
-set to be used with the TileLink interface.
-
-This class is type parameterized with env cfg class type CFG_T, coverage class type
-`COV_T`, virtual sequencer class type `VIRTUAL_SEQUENCER_T` and scoreboard class
-type `SCOREBOARD_T`.
-```systemverilog
-class cip_base_env #(type CFG_T               = cip_base_env_cfg,
-                     type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer,
-                     type SCOREBOARD_T        = cip_base_scoreboard,
-                     type COV_T               = cip_base_env_cov) extends uvm_env;
-```
-
-### cip_base_vseq
-This is the base virtual sequence class that will run on the cip virtual
-sequencer. This base class provides 'sequencing' set of tasks such as
-`dut_init()` and `dut_shutdown()` which are called within `pre_start` and
-`post_start` respectively. This sequence also provides an array of
-sub-sequences some of which are complete tests within themselves, but
-implemented as tasks. The reason for doing so is SystemVerilog does not
-support multi-inheritance so all sub-sequences that are identified as being
-common to all IP benches implemented as tasks in this base virtual sequence class.
-Some examples:
-* **task run_csr_vseq_wrapper**: This is a complete CSR test suite in itself -
-  Extended IP CSR vseq can simply call this in the body. This is paired with a
-  helper function `add_csr_exclusions`.
-* **function add_csr_exclusions**: This is extended in the IP CSR vseq to add
-  exclusions when running the CSR suite of tests.
-* **task tl_access**: This is a common generic task to create a read or a write
-  access over the TileLink host interface.
-* **task cfg_interrupts, check_interrupts**: All interrupt CSRs are standardized
-  according to the comportability spec, which allows us to create common tasks
-  to turn on / off interrupts as well as check them.
-* **task run_tl_errors_vseq**: This task will test all kinds of TileLink error
-  cases, including unmapped address error, protocol error, memory access error
-  etc. All the items sent in this task will trigger d_error and won't change the
-  CSR/memory value.
-* **task run_tl_intg_err_vseq**: This task will test TLUL integrity error. It contains
-  2 parallel threads. The first one invokes the `csr_rw` seq to drive random, legal
-  CSR accesses. The second drives a bad TLUL transaction that violates the payload
-  integrity. The bad packet is created by corrupting upto 3 bits either in the integrity
-  (ECC) fields (`a_user.cmd_intg`, `a_user.d_intg`), or in their corresponding command /
-  data payload itself. The sequence then verifies that the DUT not only returns an error
-  response (with `d_error` = 1), but also triggers a fatal alert and updates status CSRs
-  such as `ERR_CODE`. The list of CSRs that are impacted by this alert event, maintained
-  in `cfg.tl_intg_alert_fields`, are also checked for correctness.
-* **task run_seq_with_rand_reset_vseq**: This task runs 3 parallel threads,
-  which are a sequence provided, run_tl_errors_vseq and reset sequence. After
-  reset occurs, the other threads will be killed and then all the CSRs will be read
-  for check. This task runs multiple iterations to ensure DUT won't be broken after
-  reset and TL errors.
-  To ensure the reset functionality works correctly, user will have to disable
-  any internal reset from the stress_all sequence. Below is an example of
-  disabling internal reset in `hmac_stress_all_vseq.sv`:
-* **task run_same_csr_outstanding_vseq**: This task tests the same CSR with
-  non-blocking accesses as the regular CSR sequences don't cover that due to
-  limitation of uvm_reg.
-* **task run_mem_partial_access_vseq**: This task tests the partial access to the
-  memories by randomizing mask, size, and the 2 LSB bits of the address. It also runs
-  with non-blocking access enabled.
-  ```
-  // randomly trigger internal dut_init reset sequence
-  // disable any internal reset if used in stress_all_with_rand_reset vseq
-  if (do_dut_init) hmac_vseq.do_dut_init = $urandom_range(0, 1);
-  else hmac_vseq.do_dut_init = 0;
-  ```
-
-This class is type parameterized with the env cfg class type `CFG_T`, ral class type
-`RAL_T` and the virtual sequencer class type `VIRTUAL_SEQUENCER_T` so that the
-env cfg settings, the ral CSRs are accessible and the `p_sequencer` type can be
-declared.
-
-```systemverilog
-class cip_base_vseq #(type RAL_T               = dv_base_reg_block,
-                      type CFG_T               = cip_base_env_cfg,
-                      type COV_T               = cip_base_env_cov,
-                      type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer) extends uvm_sequence;
-```
-All virtual sequences in the extended IP will eventually extend from this class and
-can hence, call these tasks and functions directly as needed.
-
-### cip_base_test
-This basically creates the IP UVM env and its env cfg class instance. Any env cfg
-setting that may be required to be controlled externally via plusargs are looked
-up here, before the env instance is created. This also sets a few variables that
-pertain to how / when should the test exit on timeout or failure. In the run
-phase, the test calls `run_seq` which basically uses factory to create the
-virtual sequence instance using the `UVM_TEST_SEQ` string that is passed via
-plusarg. As a style guide, it is preferred to encapsulate a complete test within
-a virtual sequence and use the same `UVM_TEST` plusarg for all of the tests (which
-points to the extended IP test class), and only change the `UVM_TEST_SEQ` plusarg.
-
-This class is type parameterized with the env cfg class type `CFG_T` and the env
-class type `ENV_T` so that when extended IP test class creates the env and env cfg
-specific to that IP.
-```systemverilog
-class cip_base_test #(type CFG_T = cip_base_env_cfg,
-                      type ENV_T = cip_base_env) extends uvm_test;
-```
-
-### cip_tl_seq_item
-This is extended class of tl_seq_item to generate correct integrity values in
-`a_user` and `d_user`.
-
-## Extending from CIP library classes
-Let's say we are verifying an actual comportable IP `uart` which has `uart_tx`
-and `uart_rx` interface. User then develops the `uart_agent` to be able to talk
-to that interface. User invokes the `ralgen` tool to generate the `uart_reg_block`,
-and then starts developing UVM environment by extending from the CIP library
-classes in the following way.
-
-### uart_env_cfg
-```systemverilog
-class uart_env_cfg extends cip_base_env_cfg #(.RAL_T(uart_reg_block));
-```
-User adds the `uart_agent_cfg` object as a member so that it remains as a
-part of the env cfg and can be accessed everywhere. In the base class's
-`initialize()` function call, an instance of `uart_reg_block` is created, not
-the `dv_base_reg_block`, since we override the `RAL_T` type.
-
-### uart_env_cov
-```systemverilog
-class uart_env_cov extends cip_base_env_cov #(.CFG_T(uart_env_cfg));
-```
-User adds `uart` IP specific coverage items and uses the `cov` handle in
-scoreboard and test sequences to sample the coverage.
-
-### uart_virtual_sequencer
-```systemverilog
-class uart_virtual_sequencer extends cip_base_virtual_sequencer #(.CFG_T(uart_env_cfg),
-                                                                  .COV_T(uart_env_cov));
-```
-User adds the `uart_sequencer` handle to allow layered test sequences
-to send traffic to / from TileLink as well as `uart` interfaces.
-
-### uart_scoreboard
-```systemverilog
-class uart_scoreboard extends cip_base_scoreboard #(.CFG_T(uart_env_cfg),
-                                                    .RAL_T(uart_reg_block),
-                                                    .COV_T(uart_env_cov));
-```
-User adds analysis ports to grab packets from the `uart_monitor` to
-perform end-to-end checking.
-
-### uart_env
-```systemverilog
-class uart_env extends cip_base_env #(.CFG_T               (uart_env_cfg),
-                                      .COV_T               (uart_env_cov),
-                                      .VIRTUAL_SEQUENCER_T (uart_virtual_sequencer),
-                                      .SCOREBOARD_T        (uart_scoreboard));
-```
-User creates `uart_agent` object in the env and use it to connect with the
-virtual sequencer and the scoreboard. User also uses the env cfg settings to
-manipulate the uart agent cfg settings if required.
-
-### uart_base_vseq
-```systemverilog
-class uart_base_vseq extends cip_base_vseq #(.CFG_T               (uart_env_cfg),
-                                             .RAL_T               (uart_reg_block),
-                                             .COV_T               (uart_env_cov),
-                                             .VIRTUAL_SEQUENCER_T (uart_virtual_sequencer));
-```
-User adds a base virtual sequence as a starting point and adds common tasks and
-functions to perform `uart` specific operations. User then extends from
-`uart_base_vseq` to add layered test sequences.
-
-### uart_base_test
-```systemverilog
-class uart_base_test extends cip_base_test #(.ENV_T(uart_env), .CFG_T(uart_env_cfg));
-```
-User sets `UVM_TEST` plus arg to `uart_base_test` so that all tests create the UVM env
-that is automatically tailored to UART IP. Each test then sets the
-`UVM_TEST_SEQ` plusarg to run the specific test sequence, along with additional
-plusargs as required.
-
-## Configure Alert Device Agent from CIP library classes
-
-To configure alert device agents in block level testbench environment that extended
-from this CIP library claaes, please follow the steps below:
-* **${ip_name}_env_pkg.sv**: Add parameter `LIST_OF_ALERTS[]` and `NUM_ALERTS`.
-  Please make sure the alert names and order are correct.
-  For example in `otp_ctrl_env_pkg.sv`:
-  ```systemverilog
-  parameter string LIST_OF_ALERTS[] = {"fatal_macro_error", "fatal_check_error"};
-  parameter uint NUM_ALERTS         = 2;
-  ```
-* **${ip_name}_env_cfg.sv**: In function `initialize()`, assign `LIST_OF_ALERTS`
-  parameter to `list_of_alerts` variable which is created in `cip_base_env_cfg.sv`.
-  Note that this assignment should to be written before calling `super.initialize()`,
-  so that creating alert host agents will take the updated `list_of_alerts` variable.
-  For example in `otp_ctrl_env_cfg.sv`:
-  ```systemverilog
-  virtual function void initialize(bit [31:0] csr_base_addr = '1);
-    list_of_alerts = otp_ctrl_env_pkg::LIST_OF_ALERTS;
-    super.initialize(csr_base_addr);
-  ```
-* **tb.sv**: Add `DV_ALERT_IF_CONNECT` macro that declares alert interfaces,
-  connect alert interface wirings with DUT, and set alert_if to uvm_config_db.
-  Then connect alert_rx/tx to the DUT ports.
-  For example in otp_ctrl's `tb.sv`:
-  ```systemverilog
-  `DV_ALERT_IF_CONNECT
-  otp_ctrl dut (
-    .clk_i                      (clk        ),
-    .rst_ni                     (rst_n      ),
-    .alert_rx_i                 (alert_rx   ),
-    .alert_tx_o                 (alert_tx   ),
-  ```
-Note that if the testbench is generated from `uvmdvgen.py`, using the `-hr` switch
-will automatically generate the skeleton code listed above for alert device agent.
-Details on how to use `uvmdvgen.py` please refer to the
-[uvmdvgen document]({{< relref "util/uvmdvgen/doc" >}}).
-
-## CIP Testbench
-![CIP testbench diagram](tb.svg)
-The block diagram above shows the CIP testbench architecture, that puts
-together the static side `tb` which instantiates the `dut`, and the dynamic
-side, which is the UVM environment extended from CIP library. The diagram
-lists some common items that need to be instantiated in `tb`
-and set into `uvm_config_db` for the testbench to work.
-
-## Security Verification in cip_lib
-CIP contains reusable security verification components, sequences and function coverage.
-This section describes the details of them and the steps to enable them.
-
-### Security Verification for bus integrity
-The countermeasure of bus integrity can be fully verified via importing [tl_access_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/tl_access_tests.hjson) and [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/tl_device_access_types_testplan.hjson).
-The `tl_intg_err` test injects errors on control, data, or the ECC bits and verifies that the integrity error will trigger a fatal alert (provided via `cfg.tl_intg_alert_name`) and error status (provided via `cfg.tl_intg_alert_fields`) is set.
-Refer to section [cip_base_env_cfg](#cip_base_env_cfg) for more information on these 2 variables.
-The user may update these 2 variables as follows.
-```systemverilog
-class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
-  virtual function void initialize(bit [31:0] csr_base_addr = '1);
-    super.initialize(csr_base_addr);
-    tl_intg_alert_name = "fatal_fault_err";
-    // csr / field name may vary in different IPs
-    tl_intg_alert_fields[ral.fault_status.intg_err] = 1;
-```
-
-### Security Verification for memory integrity
-The memory integrity countermeasure stores the data integrity in the memory rather than generating the integrity on-the-fly during a read.
-The [passthru_mem_intg_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/passthru_mem_intg_tests.hjson) can fully verify this countermeasure.
-The details of the test sequences are described in the [tl_device_access_types_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/passthru_mem_intg_testplan.hjson). Users need to override the task `inject_intg_fault_in_passthru_mem` to inject an integrity fault to the memory in the block common_vseq.
-
-The following is an example from `sram_ctrl`, in which it flips up to `MAX_TL_ECC_ERRORS` bits of the data and generates a backdoor write to the memory.
-```systemverilog
-class sram_ctrl_common_vseq extends sram_ctrl_base_vseq;
-  ...
-  virtual function void inject_intg_fault_in_passthru_mem(dv_base_mem mem,
-                                                          bit [bus_params_pkg::BUS_AW-1:0] addr);
-    bit[bus_params_pkg::BUS_DW-1:0] rdata;
-    bit[tlul_pkg::DataIntgWidth+bus_params_pkg::BUS_DW-1:0] flip_bits;
-
-    rdata = cfg.mem_bkdr_util_h.sram_encrypt_read32_integ(addr, cfg.scb.key, cfg.scb.nonce);
-
-    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(flip_bits,
-        $countones(flip_bits) inside {[1:cip_base_pkg::MAX_TL_ECC_ERRORS]};)
-
-    `uvm_info(`gfn, $sformatf("Backdoor change mem (addr 0x%0h) value 0x%0h by flipping bits %0h",
-                              addr, rdata, flip_bits), UVM_LOW)
-
-    cfg.mem_bkdr_util_h.sram_encrypt_write32_integ(addr, rdata, cfg.scb.key, cfg.scb.nonce,
-                                                   flip_bits);
-  endfunction
-endclass
-```
-
-### Security Verification for shadow CSRs
-The countermeasure of shadow CSRs can be fully verified via importing [shadow_reg_errors_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/shadow_reg_errors_tests.hjson) and [shadow_reg_errors_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/shadow_reg_errors_testplan.hjson).
-The details of the test sequences are described in the testplan. Users need to assign the status CSR fields to `cfg.shadow_update_err_status_fields` and `cfg.shadow_storage_err_status_fields` for update error and storage error respectively.
-```systemverilog
-class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
-  virtual function void initialize(bit [31:0] csr_base_addr = '1);
-    super.initialize(csr_base_addr);
-    // csr / field name may vary in different IPs
-    shadow_update_err_status_fields[ral.err_code.invalid_shadow_update] = 1;
-    shadow_storage_err_status_fields[ral.fault_status.shadow] = 1;
-```
-
-### Security Verification for REGWEN CSRs
-If the REGWEN CSR meets the following criteria, it can be fully verified by the common [csr_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/csr_tests.hjson).
- - The REGWEN CSR and its related lockable CSRs are HW read-only registers.
- - The related lockable CSRs are not WO type, otherwise the read value is always 0 and CSR tests can't really verify if the write value is taken or not.
- - No CSR exclusions have been added to the REGWEN CSR and its related lockable CSRs.
-If not, users need to write a test to verify it separately since cip_lib and dv_base_reg can't predict its value.
-For example, the [sram_ctrl_regwen_vseq](https://github.com/lowRISC/opentitan/blob/master/hw/ip/sram_ctrl/dv/env/seq_lib/sram_ctrl_regwen_vseq.sv) has been added to verify `ctrl_regwen` and the the lockable register `ctrl` since `ctrl` is a `WO` register and excluded in CSR tests.
-
-Functional coverage for REGWEN CSRs and their related lockable CSRs is generated automatically in dv_base_reg.
-The details of functional coverage is described in [csr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/csr_testplan.hjson).
-
-### Security Verification for MUBI type CSRs
-A functional covergroup of MUBI type CSR is automatically created in the RAL model for each MUBI CSR, which ensures `True`, `False` and at least N of other values (N = width of the MUBI type) have been collected.
-This covergroup won't be sampled in CSR tests, since CSR tests only test the correctness of the value of register read / write but it won't check the block behavior when a different value is supplied to the MUBI CSR.
-Users should randomize the values of all the MUBI CSRs in non-CSR tests and check the design behaves correctly.
-The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` can be used to get the random values.
-
-### Security Verification for MUBI/LC_TX type ports
-In OpenTitan [Design Verification Methodology]({{< relref "doc/ug/dv_methodology" >}}), it's mandatory to have 100% toggle coverage on all the ports.
-However, the MUBI defined values (`True` and `False`) are complement numbers.
-If users only test with `True` and `False` without using other values, toggle coverage can be 100%.
-Hence, user should add a functional covergroup for each MUBI type input port, via binding the interface `cip_mubi_cov_if` which contains a covergroup for MUBI.
-The type `lc_ctrl_pkg::lc_tx_t` is different than the Mubi4 type, as its defined values are different.
-So, it needs to be bound with the interface `cip_lc_tx_cov_if`.
-The helper functions `cip_base_pkg::get_rand_mubi4|8|12|16_val(t_weight, f_weight, other_weight)` and `cip_base_pkg::get_rand_lc_tx_val` can be used to get the random values.
-
-The following is an example from `sram_ctrl`, in which it binds the coverage interface to 2 MUBI input ports.
-```systemverilog
-module sram_ctrl_cov_bind;
-
-  bind sram_ctrl cip_mubi_cov_if #(.Width(4)) u_hw_debug_en_mubi_cov_if (
-    .rst_ni (rst_ni),
-    .mubi   (lc_hw_debug_en_i)
-  );
-
-  bind sram_ctrl cip_lc_tx_cov_if u_lc_escalate_en_cov_if (
-    .rst_ni (rst_ni),
-    .val    (lc_escalate_en_i)
-  );
-endmodule
-```
-Note: The `sim_tops` in sim_cfg.hjson should be updated to include this bind file.
-
-### Security Verification for common countermeasure primitives
-A [security countermeasure verification framework]({{< relref "doc/ug/sec_cm_dv_framework" >}}) is implemented in cip_lib to verify common countermeasure primitives in a semi-automated way.
-
-#### Design Verification
-cip_lib imports [sec_cm_pkg](https://github.com/lowRISC/opentitan/tree/master/hw/dv/sv/sec_cm), which automatically locates all the common countermeasure primitives and binds an interface to each of them.
-In the cib_base_vseq, it injects a fault to each of these primitives and verifies that the fault will lead to a fatal alert.
-The details of the sequences can be found in testplans - [sec_cm_count_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_count_testplan.hjson), [sec_cm_fsm_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_fsm_testplan.hjson) and [sec_cm_double_lfsr_testplan](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/testplans/sec_cm_double_lfsr_testplan.hjson).
-If the block uses common security countermeasure primitives (prim_count, prim_sparse_fsm_flop, prim_double_lfsr), users can enable this sequence to fully verify them via following steps.
-
-1. Import the applicable sec_cm testplans.
-If more checks or sequences are needed, add another testpoint in the block testplan.
-For example, when the fault is detected by countermeasure, some subsequent operations won’t be executed.
-Add a testpoint in the testplan to capture this sequence and the checks.
-
-2. Import [sec_cm_tests](https://github.com/lowRISC/opentitan/blob/master/hw/dv/tools/dvsim/tests/sec_cm_tests.hjson) in sim_cfg.hjson file, as well as add applicable sec_cm bind files for `sim_tops`.
-The `ip_sec_cm` test will be added and all common countermeasure primitives will be verified in this test.
-
-```
-sim_tops: ["ip_ctrl_bind", "ip_ctrl_cov_bind",
-           // only add the corresponding bind file if DUT has the primitive
-           "sec_cm_prim_sparse_fsm_flop_bind",
-           "sec_cm_prim_count_bind",
-           "sec_cm_prim_double_lfsr_bind"]
-```
-
-3. Import sec_cm_pkg in the env_pkg
-```systemverilog
-package ip_env_pkg;
-  import uvm_pkg::*;
-  import sec_cm_pkg::*;
-  …
-```
-
-4. Set alert name for countermeasure if the alert name is different from the default name - “fatal_fault”.
-```systemverilog
-class ip_env_cfg extends cip_base_env_cfg #(.RAL_T(ip_reg_block));
-  virtual function void initialize(bit [31:0] csr_base_addr = '1);
-    super.initialize(csr_base_addr);
-    sec_cm_alert_name = "fatal_check_error";
-```
-
-5. Override the `check_sec_cm_fi_resp` task in ip_common_vseq to add additional sequences and checks after fault injection.
-This is an example from keymgr, in which CSR `fault_status` will be updated according to the location of the fault and the operation after fault inject will lead design to enter `StInvalid` state.
-```systemverilog
-class keymgr_common_vseq extends keymgr_base_vseq;
-  virtual task check_sec_cm_fi_resp(sec_cm_base_if_proxy if_proxy);
-    bit[TL_DW-1:0] exp;
-
-    super.check_sec_cm_fi_resp(if_proxy);
-
-    case (if_proxy.sec_cm_type)
-      SecCmPrimCount: begin
-        // more than one prim_count are used, distinguishing them through the path of the primitive.
-        if (!uvm_re_match("*.u_reseed_ctrl*", if_proxy.path)) begin
-          exp[keymgr_pkg::FaultReseedCnt] = 1;
-        end else begin
-          exp[keymgr_pkg::FaultCtrlCnt] = 1;
-        end
-      end
-      SecCmPrimSparseFsmFlop: begin
-        exp[keymgr_pkg::FaultCtrlFsm] = 1;
-      end
-      default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
-    endcase
-    csr_rd_check(.ptr(ral.fault_status), .compare_value(exp));
-
-    // after an advance, keymgr should enter StInvalid
-    keymgr_advance();
-    csr_rd_check(.ptr(ral.op_status), .compare_value(keymgr_pkg::OpDoneFail));
-    csr_rd_check(.ptr(ral.working_state), .compare_value(keymgr_pkg::StInvalid));
-  endtask : check_sec_cm_fi_resp
-```
-
-6. Fault injection may trigger unexpected SVA errors. Override the `sec_cm_fi_ctrl_svas` function to disable them. `sec_cm_fi_ctrl_svas(.enable(1))` will be invoked before injecting fault. After reset, `sec_cm_fi_ctrl_svas(.enable(0))` will be called to re-enable the SVA checks.
-```systemverilog
-class keymgr_common_vseq extends keymgr_base_vseq;
-   virtual function void sec_cm_fi_ctrl_svas(sec_cm_base_if_proxy if_proxy, bit enable);
-    case (if_proxy.sec_cm_type)
-      SecCmPrimCount: begin
-        if (enable) begin
-          $asserton(0, "tb.keymgr_kmac_intf");
-          $asserton(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
-          $asserton(0, "tb.dut.u_ctrl.DataEn_A");
-          $asserton(0, "tb.dut.u_ctrl.DataEnDis_A");
-          $asserton(0, "tb.dut.u_ctrl.CntZero_A");
-          $asserton(0, "tb.dut.u_kmac_if.LastStrb_A");
-          $asserton(0, "tb.dut.KmacDataKnownO_A");
-        end else begin
-          $assertoff(0, "tb.keymgr_kmac_intf");
-          $assertoff(0, "tb.dut.tlul_assert_device.gen_device.dDataKnown_A");
-          $assertoff(0, "tb.dut.u_ctrl.DataEn_A");
-          $assertoff(0, "tb.dut.u_ctrl.DataEnDis_A");
-          $assertoff(0, "tb.dut.u_ctrl.CntZero_A");
-          $assertoff(0, "tb.dut.u_kmac_if.LastStrb_A");
-          $assertoff(0, "tb.dut.KmacDataKnownO_A");
-        end
-      end
-      SecCmPrimSparseFsmFlop: begin
-        // No need to disable any assertion
-      end
-
-      default: `uvm_fatal(`gfn, $sformatf("unexpected sec_cm_type %s", if_proxy.sec_cm_type.name))
-    endcase
-  endfunction: sec_cm_fi_ctrl_svas
-```
-
-#### Formal Verification
-Please refer to [formal document]({{< relref "hw/formal/doc" >}}) on how to create a FPV environment for common countermeasures.
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq.sv b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq.sv
index f679ce3..848ad33 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq.sv
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq.sv
@@ -17,24 +17,24 @@
     end : isolation_fork \
   join
 
-class cip_base_vseq #(type RAL_T               = dv_base_reg_block,
-                      type CFG_T               = cip_base_env_cfg,
-                      type COV_T               = cip_base_env_cov,
-                      type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer)
-                      extends dv_base_vseq #(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T);
+class cip_base_vseq #(
+  type RAL_T               = dv_base_reg_block,
+  type CFG_T               = cip_base_env_cfg,
+  type COV_T               = cip_base_env_cov,
+  type VIRTUAL_SEQUENCER_T = cip_base_virtual_sequencer
+) extends dv_base_vseq #(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T);
   `uvm_object_new
   // knobs to disable post_start clear interrupt routine
   bit do_clear_all_interrupts = 1'b1;
 
   bit expect_fatal_alerts = 1'b0;
 
-  // knobs to enable alert auto reponse, once disabled it won't be able to enable again unless
-  // reset is issued
-  bit en_auto_alerts_response = 1'b1;
-
   // knob to enable/disable running csr_vseq with passthru_mem_tl_intg_err
   bit en_csr_vseq_w_passthru_mem_intg = 1;
 
+  // knob to enable/disable running csr_vseq with tl_intg_err
+  bit en_csr_vseq_w_tl_intg = 1;
+
   // csr queues
   dv_base_reg all_csrs[$];
   dv_base_reg intr_state_csrs[$];
@@ -44,7 +44,7 @@
 
   // address mask struct
   typedef struct packed {
-    bit [BUS_AW-1:0]  addr;
+    bit [BUS_AW-1:0] addr;
     bit [BUS_DBW-1:0] mask;
   } addr_mask_t;
 
@@ -64,10 +64,10 @@
   rand uint rand_reset_delay;
   constraint rand_reset_delay_c {
     rand_reset_delay dist {
-        [1         :1000]       :/ 1,
-        [1001      :100_000]    :/ 2,
-        [100_001   :1_000_000]  :/ 6,
-        [1_000_001 :5_000_000]  :/ 1
+      [1 : 1000]              :/ 1,
+      [1001 : 100_000]        :/ 2,
+      [100_001 : 1_000_000]   :/ 6,
+      [1_000_001 : 5_000_000] :/ 1
     };
   }
 
@@ -76,13 +76,13 @@
   rand uint csr_access_abort_pct;
   constraint csr_access_abort_pct_c {
     csr_access_abort_pct dist {
-      0      :/ 50,
-      [1:99] :/ 40,
-      100    :/ 10
+      0        :/ 50,
+      [1 : 99] :/ 40,
+      100      :/ 10
     };
   }
 
-  `uvm_object_param_utils_begin(cip_base_vseq #(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T))
+  `uvm_object_param_utils_begin(cip_base_vseq#(RAL_T, CFG_T, COV_T, VIRTUAL_SEQUENCER_T))
     `uvm_field_string(common_seq_type, UVM_DEFAULT)
   `uvm_object_utils_end
 
@@ -95,12 +95,10 @@
 
     // Wait for alert init done, then start the sequence.
     foreach (cfg.list_of_alerts[i]) begin
-      if (cfg.m_alert_agent_cfg[cfg.list_of_alerts[i]].is_active) begin
-        wait (cfg.m_alert_agent_cfg[cfg.list_of_alerts[i]].alert_init_done == 1);
+      if (cfg.m_alert_agent_cfgs[cfg.list_of_alerts[i]].is_active) begin
+        `DV_WAIT(cfg.m_alert_agent_cfgs[cfg.list_of_alerts[i]].alert_init_done == 1)
       end
     end
-
-    if (en_auto_alerts_response && cfg.list_of_alerts.size()) run_alert_rsp_seq_nonblocking();
   endtask
 
   function void pre_randomize();
@@ -114,8 +112,6 @@
 
   task pre_start();
     if (common_seq_type == "") void'($value$plusargs("run_%0s", common_seq_type));
-    if (common_seq_type == "alert_test") en_auto_alerts_response = 0;
-
     csr_utils_pkg::max_outstanding_accesses = 1 << BUS_AIW;
     super.pre_start();
     extract_common_csrs();
@@ -168,8 +164,8 @@
 
   // tl_access task: does a single BUS_DW-bit write or read transaction to the specified address
   // note that this task does not update ral model; optionally also checks for error response
-  // TODO: randomize size, addr here based on given addr range, data, and mask, eventually can be
-  // reused for mem_read, partial read, and hmac msg fifo write
+  // The `size` and `addr[1:0]` are randomized based on the given `mask`, and this is also applied
+  // to mem access. If it doesn't support partial access, use mask = '1.
   virtual task tl_access(input bit [BUS_AW-1:0]  addr,
                          input bit               write,
                          inout bit [BUS_DW-1:0]  data,
@@ -184,6 +180,7 @@
                          input mubi4_t           instr_type = MuBi4False,
                          tl_sequencer            tl_sequencer_h = p_sequencer.tl_sequencer_h,
                          input tl_intg_err_e     tl_intg_err_type = TlIntgErrNone);
+
     bit completed, saw_err;
     tl_access_w_abort(addr, write, data, completed, saw_err, tl_access_timeout_ns, mask, check_rsp,
                       exp_err_rsp, exp_data, compare_mask, check_exp_data, blocking, instr_type,
@@ -206,12 +203,13 @@
       input bit [BUS_DW-1:0]  compare_mask = '1,
       input bit               check_exp_data = 1'b0,
       input bit               blocking = csr_utils_pkg::default_csr_blocking,
-      input mubi4_t           instr_type = MuBi4False,
+      input                   mubi4_t instr_type = MuBi4False,
       tl_sequencer            tl_sequencer_h = p_sequencer.tl_sequencer_h,
-      input tl_intg_err_e     tl_intg_err_type = TlIntgErrNone,
+      input                   tl_intg_err_e tl_intg_err_type = TlIntgErrNone,
       input int               req_abort_pct = 0);
 
     cip_tl_seq_item rsp;
+
     if (blocking) begin
       tl_access_sub(addr, write, data, completed, saw_err, rsp, tl_access_timeout_ns, mask,
                     check_rsp, exp_err_rsp, exp_data, compare_mask, check_exp_data, req_abort_pct,
@@ -232,8 +230,8 @@
                              inout bit [BUS_DW-1:0]  data,
                              output bit              completed,
                              output bit              saw_err,
-                             output cip_tl_seq_item  rsp,
-                             input uint         tl_access_timeout_ns = default_spinwait_timeout_ns,
+                             output                  cip_tl_seq_item rsp,
+                             input                   uint tl_access_timeout_ns = default_spinwait_timeout_ns,
                              input bit [BUS_DBW-1:0] mask = '1,
                              input bit               check_rsp = 1'b1,
                              input bit               exp_err_rsp = 1'b0,
@@ -241,9 +239,9 @@
                              input bit [BUS_DW-1:0]  compare_mask = '1,
                              input bit               check_exp_data = 1'b0,
                              input int               req_abort_pct = 0,
-                             input mubi4_t           instr_type = MuBi4False,
-                             tl_sequencer            tl_sequencer_h = p_sequencer.tl_sequencer_h,
-                             input tl_intg_err_e     tl_intg_err_type = TlIntgErrNone);
+                             input                   mubi4_t instr_type = MuBi4False,
+                                                     tl_sequencer tl_sequencer_h = p_sequencer.tl_sequencer_h,
+                             input                   tl_intg_err_e tl_intg_err_type = TlIntgErrNone);
     `DV_SPINWAIT(
         // thread to read/write tlul
         cip_tl_host_single_seq tl_seq;
@@ -261,6 +259,7 @@
             write == local::write;
             mask  == local::mask;
             data  == local::data;)
+
         `uvm_send_pri(tl_seq, 100)
         rsp = tl_seq.rsp;
 
@@ -273,10 +272,10 @@
           end
         end
         if (check_rsp && !cfg.under_reset && tl_intg_err_type == TlIntgErrNone) begin
-          `DV_CHECK_EQ(rsp.d_error, exp_err_rsp, "unexpected error response")
+          `DV_CHECK_EQ(rsp.d_error, exp_err_rsp,
+                       $sformatf("unexpected error response for addr: 0x%x", rsp.a_addr))
         end
 
-
         // Expose whether the transaction ran and whether it generated an error. Note that we
         // probably only want to do a RAL update if it ran and caused no error.
         completed = rsp.rsp_completed;
@@ -294,26 +293,13 @@
   // are descriptions of some of the args:
 
   // interrupts: bit vector indicating which interrupts to process
-  // suffix: if there are more than BUS_DW interrupts, then add suffix 'hi' or 'lo' to the interrupt
-  // TODO add support for suffix
-  // csr to configure the right one (ex: intr_enable_hi, intr_enable_lo, etc)
-  // indices[$]: registers could be indexed (example, rv_timer) in which case, push as many desired
-  // index values as required by the design to the queue
   // scope: for top level, specify which ip / sub module's interrupt to clear
 
   // common task
   local function dv_base_reg get_interrupt_csr(string csr_name,
-                                               string suffix = "",
-                                               int indices[$] = {},
                                                dv_base_reg_block scope = null);
-    if (indices.size() != 0) begin
-      foreach (indices[i]) begin
-        suffix = {suffix, (i == 0) ? "" : "_", $sformatf("%0d", i)};
-      end
-      csr_name = {csr_name, suffix};
-    end
     // check within scope first, if supplied
-    if (scope != null)  begin
+    if (scope != null) begin
       get_interrupt_csr = scope.get_dv_base_reg_by_name(csr_name);
     end else begin
       get_interrupt_csr = ral.get_dv_base_reg_by_name(csr_name);
@@ -339,14 +325,12 @@
   // see description above for other args
   virtual task cfg_interrupts(bit [BUS_DW-1:0] interrupts,
                               bit enable = 1'b1,
-                              string suffix = "",
-                              int indices[$] = {},
                               dv_base_reg_block scope = null);
 
     uvm_reg          csr;
     bit [BUS_DW-1:0] data;
 
-    csr = get_interrupt_csr("intr_enable", "", indices, scope);
+    csr = get_interrupt_csr("intr_enable", scope);
     data = csr.get_mirrored_value();
     if (enable) data |= interrupts;
     else        data &= ~interrupts;
@@ -360,8 +344,6 @@
   // see description above for other args
   virtual task check_interrupts(bit [BUS_DW-1:0] interrupts,
                                 bit check_set,
-                                string suffix = "",
-                                int indices[$] = {},
                                 dv_base_reg_block scope = null,
                                 bit [BUS_DW-1:0] clear = '1);
     uvm_reg          csr_intr_state, csr_intr_enable;
@@ -373,7 +355,7 @@
 
     act_pins = cfg.intr_vif.sample() & interrupts;
     if (check_set) begin
-      csr_intr_enable = get_interrupt_csr("intr_enable", "", indices, scope);
+      csr_intr_enable = get_interrupt_csr("intr_enable", scope);
       exp_pins = interrupts & csr_intr_enable.get_mirrored_value();
       exp_intr_state = interrupts;
     end else begin
@@ -381,7 +363,7 @@
       exp_intr_state = ~interrupts;
     end
     `DV_CHECK_EQ(act_pins, exp_pins)
-    csr_intr_state = get_interrupt_csr("intr_state", "", indices, scope);
+    csr_intr_state = get_interrupt_csr("intr_state", scope);
     csr_rd_check(.ptr(csr_intr_state), .compare_value(exp_intr_state), .compare_mask(interrupts));
 
     if (check_set && |(interrupts & clear)) begin
@@ -433,6 +415,7 @@
   // generic task to check interrupt test reg functionality
   virtual task run_intr_test_vseq(int num_times = 1);
     dv_base_reg intr_csrs[$];
+    dv_base_reg intr_test_csrs[$];
 
     foreach (all_csrs[i]) begin
       string csr_name = all_csrs[i].get_name();
@@ -441,6 +424,9 @@
           !uvm_re_match("intr_state*", csr_name)) begin
         intr_csrs.push_back(get_interrupt_csr(csr_name));
       end
+      if (!uvm_re_match("intr_test*", csr_name)) begin
+        intr_test_csrs.push_back(get_interrupt_csr(csr_name));
+      end
     end
 
     num_times = num_times * intr_csrs.size();
@@ -477,6 +463,11 @@
         end // if (!uvm_re_match
       end // foreach (intr_csrs[i])
     end // for (int trans = 1; ...
+    // Write 0 to intr_test to clean up status interrupts, otherwise, status interrupts may remain
+    // active. And writing any value to a status interrupt CSR (intr_state) can't clear its value.
+    foreach (intr_test_csrs[i]) begin
+      csr_wr(.ptr(intr_test_csrs[i]), .value(0));
+    end
   endtask
 
   // Task to clear register intr status bits
@@ -493,7 +484,10 @@
         else break;
       end
     end
-    if (!cfg.under_reset) `DV_CHECK_EQ(cfg.intr_vif.sample(), {NUM_MAX_INTERRUPTS{1'b0}})
+    if (!cfg.under_reset) begin
+      dv_utils_pkg::interrupt_t mask = dv_utils_pkg::interrupt_t'((1 << cfg.num_interrupts) - 1);
+      `DV_CHECK_EQ(cfg.intr_vif.sample() & mask, '0)
+    end
   endtask
 
   virtual task check_no_fatal_alerts();
@@ -512,14 +506,17 @@
       int check_cycles = $urandom_range(max_alert_handshake_cycles,
                                         max_alert_handshake_cycles * 3);
 
-      // This task allows recoverable alerts to fire, or fatal alert being triggered once by
-      // `alert_test` register.
+      // This task wait for recoverable alerts handshake to complete, or fatal alert being
+      // triggered once by `alert_test` register.
       cfg.clk_rst_vif.wait_clks(max_alert_handshake_cycles);
+      foreach (cfg.m_alert_agent_cfgs[alert_name]) begin
+        `DV_SPINWAIT(cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();)
+      end
 
       repeat(check_cycles) begin
         cfg.clk_rst_vif.wait_clks(1);
-        foreach (cfg.m_alert_agent_cfg[alert_name]) begin
-          `DV_CHECK_EQ(0, cfg.m_alert_agent_cfg[alert_name].vif.get_alert(),
+        foreach (cfg.m_alert_agent_cfgs[alert_name]) begin
+          `DV_CHECK_EQ(0, cfg.m_alert_agent_cfgs[alert_name].vif.get_alert(),
                        $sformatf("Alert %0s fired unexpectedly!", alert_name))
         end
       end
@@ -536,61 +533,55 @@
 
       repeat ($urandom_range(num_alerts, num_alerts * 10)) begin
         bit [BUS_DW-1:0] alert_req = $urandom_range(0, (1'b1 << num_alerts) - 1);
+        // Write random value to alert_test register.
         csr_wr(.ptr(alert_test_csr), .value(alert_req));
         `uvm_info(`gfn, $sformatf("Write alert_test with val %0h", alert_req), UVM_HIGH)
-        fork
-          begin
-            for (int i = 0; i < num_alerts; i++) begin
-              automatic int index = i;
-              automatic string alert_name = cfg.list_of_alerts[index];
-              if (alert_req[index]) begin
-                fork
-                  begin
-                    // if previous alert_handler just finish, there is a max of two clock_cycle
-                    // pause in between
-                    wait_alert_trigger(alert_name, .max_wait_cycle(2));
+        for (int i = 0; i < num_alerts; i++) begin
+          string alert_name = cfg.list_of_alerts[i];
 
-                    // write alert_test during alert handshake will be ignored
-                    if ($urandom_range(1, 10) == 10) begin
-                      csr_wr(.ptr(alert_test_csr), .value(1'b1 << index));
-                      `uvm_info(`gfn, "Write alert_test again during alert handshake", UVM_HIGH)
-                    end
+          // If the field has already been written, check if the corresponding alert fires
+          // correctly and check if writing to this alert_test field again won't corrupt the
+          // current alert mechanism.
+          if (alert_req[i]) begin
+            // if previous alert_handler just finish, there is a max of two clock_cycle
+            // pause in between
+            wait_alert_trigger(alert_name, .max_wait_cycle(2));
 
-                    // drive alert response sequence
-                    drive_alert_rsp_and_check_handshake(alert_name, index);
-                  end
-                join_none
-              end else begin
-                fork
-                  begin
-                    cfg.clk_rst_vif.wait_clks($urandom_range(0, 3));
-                    `DV_CHECK_EQ(cfg.m_alert_agent_cfg[alert_name].vif.get_alert(), 0,
-                                 $sformatf("alert_test did not set alert:%0s", alert_name))
+            // write alert_test during alert handshake will be ignored
+            if ($urandom_range(1, 10) == 10) begin
+              csr_wr(.ptr(alert_test_csr), .value(1'b1 << i));
+              `uvm_info(`gfn, "Write alert_test again during alert handshake", UVM_HIGH)
+            end
 
-                    // test alert_test write when there is ongoing alert handshake
-                    if ($urandom_range(1, 10) == 10) begin
-                      `uvm_info(`gfn,
-                                $sformatf("Write alert_test with val %0h during alert_handshake",
-                                1'b1 << index), UVM_HIGH)
-                      csr_wr(.ptr(alert_test_csr), .value(1'b1 << index));
-                      `DV_SPINWAIT_EXIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.get_alert())
-                                        cfg.clk_rst_vif.wait_clks(1);,
-                                        cfg.clk_rst_vif.wait_clks(2);,
-                                        $sformatf("expect alert_%0d:%0s to fire",
-                                                  index, alert_name))
-                      drive_alert_rsp_and_check_handshake(alert_name, index);
-                    end
-                  end
-                join_none
-              end
-            end // end for loop
-            wait fork;
+            // drive alert response sequence
+            drive_alert_rsp_and_check_handshake(alert_name, i);
+
+         // If the field has not been written, check if the corresponding alert does not fire.
+         // Randomly decide to write this field and check if the alert fires.
+         end else begin
+           cfg.clk_rst_vif.wait_clks($urandom_range(0, 3));
+           `DV_CHECK_EQ(cfg.m_alert_agent_cfgs[alert_name].vif.get_alert(), 0,
+                        $sformatf("alert_test did not set alert:%0s", alert_name))
+
+            // write alert_test field when there is ongoing alert handshake
+            if ($urandom_range(1, 10) == 10) begin
+              `uvm_info(`gfn,
+                        $sformatf("Write alert_test with val %0h during alert_handshake",
+                        1'b1 << i), UVM_HIGH)
+              csr_wr(.ptr(alert_test_csr), .value(1'b1 << i));
+              `DV_SPINWAIT_EXIT(while (!cfg.m_alert_agent_cfgs[alert_name].vif.get_alert())
+                                cfg.clk_rst_vif.wait_clks(1);,
+                                cfg.clk_rst_vif.wait_clks(2);,
+                                $sformatf("expect alert_%0d:%0s to fire",
+                                          i, alert_name))
+              drive_alert_rsp_and_check_handshake(alert_name, i);
+            end
           end
-        join
+        end // end for loop
 
         // check no alert triggers continuously
         foreach (cfg.list_of_alerts[i]) begin
-          `DV_CHECK_EQ(cfg.m_alert_agent_cfg[cfg.list_of_alerts[i]].vif.get_alert(), 0,
+          `DV_CHECK_EQ(cfg.m_alert_agent_cfgs[cfg.list_of_alerts[i]].vif.get_alert(), 0,
                        $sformatf("expect alert:%0s to stay low", cfg.list_of_alerts[i]))
         end
       end // end repeat
@@ -601,13 +592,13 @@
   // clock domain crossing, 2 for pauses, 1 for idle state.
   // So use 7 cycle for default max_wait_cycle.
   virtual task wait_alert_trigger(string alert_name, int max_wait_cycle = 7, bit wait_complete = 0);
-    `DV_SPINWAIT_EXIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.is_alert_handshaking())
+    `DV_SPINWAIT_EXIT(while (!cfg.m_alert_agent_cfgs[alert_name].vif.is_alert_handshaking())
                       cfg.clk_rst_vif.wait_clks(1);,
                       // another thread to wait for given cycles. If timeout, report an error.
                       cfg.clk_rst_vif.wait_clks(max_wait_cycle);
                       `uvm_error(`gfn, $sformatf("expect alert:%0s to fire", alert_name)))
     if (wait_complete) begin
-      `DV_SPINWAIT(cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();,
+      `DV_SPINWAIT(cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();,
                    $sformatf("timeout wait for alert handshake:%0s", alert_name))
     end
   endtask
@@ -618,10 +609,10 @@
     `DV_CHECK_RANDOMIZE_FATAL(ack_seq);
     ack_seq.start(p_sequencer.alert_esc_sequencer_h[alert_name]);
 
-    `DV_SPINWAIT(cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();,
+    `DV_SPINWAIT(cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();,
                  $sformatf("timeout wait for alert_%0d handshake:%0s", alert_index, alert_name))
 
-    if (cfg.m_alert_agent_cfg[alert_name].is_async) cfg.clk_rst_vif.wait_clks(2);
+    if (cfg.m_alert_agent_cfgs[alert_name].is_async) cfg.clk_rst_vif.wait_clks(2);
   endtask
 
   // override csr_vseq to control adapter to abort transaction
@@ -630,8 +621,19 @@
                             bit    do_rand_wr_and_reset = 1,
                             dv_base_reg_block models[$] = {},
                             string ral_name = "");
+    bit has_shadow_reg;
+    dv_base_reg regs[$];
 
-    if (csr_access_abort_pct.rand_mode()) begin
+    // if there is any shadow reg, we shouldn't abort TL access, otherwise, it may do only one
+    // write to the shadow reg, which may cause an unexpected recoverable error.
+    foreach (cfg.ral_models[i]) cfg.ral_models[i].get_dv_base_regs(regs);
+    foreach (regs[i]) begin
+      if (regs[i].get_is_shadowed()) begin
+        has_shadow_reg = 1;
+        break;
+      end
+    end
+    if (!has_shadow_reg && csr_access_abort_pct.rand_mode()) begin
       `DV_CHECK_MEMBER_RANDOMIZE_FATAL(csr_access_abort_pct)
     end else begin
       csr_access_abort_pct = 0;
@@ -662,6 +664,7 @@
   // override this task from {block}_common_vseq if needed
   virtual task rand_reset_eor_clean_up();
   endtask
+
   // Run the given sequence and possibly a TL errors vseq (if do_tl_err is set). Suddenly inject a
   // reset after at most reset_delay_bound cycles. When we come out of reset, check all CSR values
   // to ensure they are the documented reset values.
@@ -710,8 +713,8 @@
               do_read_and_check_all_csrs = 1'b1;
               ongoing_reset = 1'b0;
             end
-          `DV_CHECK_EQ(has_outstanding_access(),  0, "No CSR outstanding items after reset!")
           join_any
+          `DV_CHECK_EQ(has_outstanding_access(),  0, "No CSR outstanding items after reset!")
           disable fork;
           `uvm_info(`gfn, $sformatf("\nStress w/ reset is done for run %0d/%0d", i, num_times),
                     UVM_LOW)
@@ -755,46 +758,52 @@
     for (int trans = 1; trans <= num_times; trans++) begin
       `uvm_info(`gfn, $sformatf("Running same CSR outstanding test iteration %0d/%0d",
                                  trans, num_times), UVM_LOW)
-      all_csrs.shuffle();
 
       // first iteration already issued dut_init in pre_start
       if (trans != 1 && $urandom_range(0, 1)) dut_init();
 
-      foreach (all_csrs[i]) begin
-        uvm_reg_data_t exp_data = all_csrs[i].get_mirrored_value();
-        uvm_reg_data_t rd_data, wr_data, rd_mask, wr_mask;
-        csr_excl_item  csr_excl = get_excl_item(all_csrs[i]);
+      foreach (cfg.ral_models[ral_name]) begin
+        dv_base_reg csrs[$];
+        cfg.ral_models[ral_name].get_dv_base_regs(csrs);
+        csrs.shuffle();
 
-        rd_mask = get_mask_excl_fields(all_csrs[i], CsrExclWriteCheck, csr_test_type);
-        wr_mask = get_mask_excl_fields(all_csrs[i], CsrExclWrite, csr_test_type);
+        foreach (csrs[i]) begin
+          uvm_reg_data_t exp_data = csrs[i].get_mirrored_value();
+          uvm_reg_data_t rd_data, wr_data, rd_mask, wr_mask;
+          csr_excl_item  csr_excl = get_excl_item(csrs[i]);
 
-        repeat ($urandom_range(2, 20)) begin
-          // do read, exclude CsrExclWriteCheck, CsrExclCheck
-          if ($urandom_range(0, 1) &&
-              !csr_excl.is_excl(all_csrs[i], CsrExclWriteCheck, csr_test_type)) begin
-            tl_access(.addr(all_csrs[i].get_address()), .write(0), .data(rd_data),
-                      .exp_data(exp_data), .check_exp_data(1), .compare_mask(rd_mask),
-                      .blocking(0));
-          end
-          // do write, exclude CsrExclWrite
-          if ($urandom_range(0, 1) &&
-              !csr_excl.is_excl(all_csrs[i], CsrExclWrite, csr_test_type)) begin
-            // Shadowed register requires two writes and thus call predict function twice.
-            int num_write = all_csrs[i].get_is_shadowed() ? 2 : 1;
+          rd_mask = get_mask_excl_fields(csrs[i], CsrExclWriteCheck, csr_test_type);
+          wr_mask = get_mask_excl_fields(csrs[i], CsrExclWrite, csr_test_type);
 
-            `DV_CHECK_STD_RANDOMIZE_FATAL(wr_data)
-            wr_data &= wr_mask;
-            repeat (num_write) begin
-              tl_access(.addr(all_csrs[i].get_address()), .write(1), .data(wr_data), .blocking(0));
-              void'(all_csrs[i].predict(.value(wr_data), .kind(UVM_PREDICT_WRITE)));
+          repeat ($urandom_range(2, 20)) begin
+            // do read, exclude CsrExclWriteCheck, CsrExclCheck
+            if ($urandom_range(0, 1) &&
+                !csr_excl.is_excl(csrs[i], CsrExclWriteCheck, csr_test_type)) begin
+              tl_access(.addr(csrs[i].get_address()), .write(0), .data(rd_data),
+                        .exp_data(exp_data), .check_exp_data(1), .compare_mask(rd_mask),
+                        .blocking(0), .tl_sequencer_h(p_sequencer.tl_sequencer_hs[ral_name]));
             end
-            exp_data = all_csrs[i].get_mirrored_value();
-          end
-        end
-        csr_utils_pkg::wait_no_outstanding_access();
+            // do write, exclude CsrExclWrite
+            if ($urandom_range(0, 1) &&
+                !csr_excl.is_excl(csrs[i], CsrExclWrite, csr_test_type)) begin
+              // Shadowed register requires two writes and thus call predict function twice.
+              int num_write = csrs[i].get_is_shadowed() ? 2 : 1;
 
-        // Manually lock lockable flds because we use tl_access() instead of csr_wr().
-        if (all_csrs[i].is_wen_reg()) all_csrs[i].lock_lockable_flds(`gmv(all_csrs[i]));
+              `DV_CHECK_STD_RANDOMIZE_FATAL(wr_data)
+              wr_data &= wr_mask;
+              repeat (num_write) begin
+                tl_access(.addr(csrs[i].get_address()), .write(1), .data(wr_data), .blocking(0),
+                          .tl_sequencer_h(p_sequencer.tl_sequencer_hs[ral_name]));
+                void'(csrs[i].predict(.value(wr_data), .kind(UVM_PREDICT_WRITE)));
+              end
+              exp_data = csrs[i].get_mirrored_value();
+            end
+          end
+          csr_utils_pkg::wait_no_outstanding_access();
+
+          // Manually lock lockable flds because we use tl_access() instead of csr_wr().
+          if (csrs[i].is_wen_reg()) csrs[i].lock_lockable_flds(`gmv(csrs[i]));
+        end
       end
     end
   endtask
@@ -806,11 +815,11 @@
             // 1 extra cycle to make sure no race condition
             repeat (alert_esc_agent_pkg::ALERT_B2B_DELAY + 1) begin
               cfg.clk_rst_vif.wait_n_clks(1);
-              if (cfg.m_alert_agent_cfg[alert_name].vif.get_alert() == 1) break;
+              if (cfg.m_alert_agent_cfgs[alert_name].vif.get_alert() == 1) break;
             end
-            `DV_CHECK_EQ(cfg.m_alert_agent_cfg[alert_name].vif.get_alert(), 1,
+            `DV_CHECK_EQ(cfg.m_alert_agent_cfgs[alert_name].vif.get_alert(), 1,
                          $sformatf("fatal error %0s does not trigger!", alert_name))
-            cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();
+            cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();
           end,
           wait(cfg.under_reset);)
     join_none
@@ -938,31 +947,6 @@
                                  .reset_delay_bound(1000));
   endtask
 
-  virtual task run_alert_rsp_seq_nonblocking();
-    foreach (cfg.list_of_alerts[i]) begin
-      if (cfg.m_alert_agent_cfg[cfg.list_of_alerts[i]].is_active) begin
-        automatic string alert_name = cfg.list_of_alerts[i];
-        fork
-          begin
-            fork
-              forever begin
-                alert_receiver_alert_rsp_seq ack_seq =
-                    alert_receiver_alert_rsp_seq::type_id::create("ack_seq");
-                `DV_CHECK_RANDOMIZE_FATAL(ack_seq);
-                ack_seq.start(p_sequencer.alert_esc_sequencer_h[alert_name]);
-              end
-              begin
-                wait(!en_auto_alerts_response || cfg.under_reset);
-                cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();
-              end
-            join_any
-            disable fork;
-          end
-        join_none
-      end
-    end
-  endtask
-
   // TLUL mask must be contiguous, e.g. 'b1001, 'b1010 aren't allowed
   virtual function bit[BUS_DBW-1:0] get_rand_contiguous_mask(bit [BUS_DBW-1:0] valid_mask = '1);
     bit [BUS_DBW-1:0] mask;
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
index fc878a4..25fa31f 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__shadow_reg_errors.svh
@@ -13,8 +13,19 @@
 
   foreach (cfg.ral_models[i]) cfg.ral_models[i].get_shadowed_regs(shadowed_csrs);
 
-  // Add exclusions for shadow regs and their status regs in csr_rw sequence.
+  // Add exclusions for in csr_rw sequence:
+  // 1) alert_test register because it can also trigger alert
+  // 2) shadow regs and their status regs, because they will affect the shadow_reg thread
   if (en_csr_rw_seq) begin
+    // Only exclude `alert_test` register if the block has alerts.
+    if (cfg.list_of_alerts.size() > 0) begin
+      csr_excl_item csr_excl;
+      uvm_reg alert_test_reg = ral.get_reg_by_name("alert_test");
+      `DV_CHECK(alert_test_reg != null)
+      csr_excl = get_excl_item(alert_test_reg);
+      csr_excl.add_excl(alert_test_reg.get_full_name(), CsrExclWrite, CsrRwTest);
+    end
+
     foreach (shadowed_csrs[i]) begin
       csr_excl_item csr_excl = get_excl_item(shadowed_csrs[i]);
       csr_excl.add_excl(shadowed_csrs[i].get_full_name(), CsrExclAll, CsrRwTest);
@@ -129,8 +140,8 @@
   wdata = get_shadow_reg_diff_val(shadowed_csr, wdata);
   shadow_reg_wr(.csr(shadowed_csr), .wdata(wdata), .en_shadow_wr(1));
 
-  if (cfg.m_alert_agent_cfg.exists(alert_name)) begin
-    `DV_CHECK_EQ(cfg.m_alert_agent_cfg[alert_name].vif.get_alert(), 0,
+  if (cfg.m_alert_agent_cfgs.exists(alert_name)) begin
+    `DV_CHECK_EQ(cfg.m_alert_agent_cfgs[alert_name].vif.get_alert(), 0,
                  $sformatf("Unexpected alert: %s fired", alert_name))
   end
 
@@ -142,7 +153,7 @@
 // This non-blocking task checks if the alert is continuously firing until reset is issued.
 virtual task shadow_reg_errors_check_fatal_alert_nonblocking(dv_base_reg shadowed_csr,
                                                              string alert_name);
-  if (cfg.m_alert_agent_cfg.exists(alert_name)) begin
+  if (cfg.m_alert_agent_cfgs.exists(alert_name)) begin
     // add clock cycle delay in case of alert coming out 1 cycle later.
     cfg.clk_rst_vif.wait_clks(2);
     check_fatal_alert_nonblocking(alert_name);
@@ -230,15 +241,15 @@
   // external alert, it triggers it as a local alert instead.
   // - Wait for the first alert to trigger then check if the alert is firing continuously, because
   // `dut_init` is a blocking task and alert is firing once alert_init is done.
-  if (cfg.m_alert_agent_cfg.exists(alert_name) && alert_name != "") begin
-    `DV_SPINWAIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.get_alert())
+  if (cfg.m_alert_agent_cfgs.exists(alert_name) && alert_name != "") begin
+    `DV_SPINWAIT(while (!cfg.m_alert_agent_cfgs[alert_name].vif.get_alert())
                  cfg.clk_rst_vif.wait_clks(1);,
                  $sformatf("expect fatal alert:%0s to fire after rst_ni glitched", alert_name))
 
     // `dut_init` task is used to toggle IP reset pin. If the IP has more than one reset pin, alert
     // might already fire when the main reset is deasserted. So the below line we wait for a full
     // alert handshake before check fatal alert.
-    cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();
+    cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();
     check_fatal_alert_nonblocking(alert_name);
   end
 
@@ -297,13 +308,13 @@
     begin
       string alert_name = csr.get_update_err_alert_name();
       // This logic gates alert_handler testbench because it does not have outgoing alert.
-      if (cfg.m_alert_agent_cfg.exists(alert_name)) begin
+      if (cfg.m_alert_agent_cfgs.exists(alert_name)) begin
         fork
           begin
-            `DV_SPINWAIT(while (!cfg.m_alert_agent_cfg[alert_name].vif.get_alert()) begin
+            `DV_SPINWAIT(while (!cfg.m_alert_agent_cfgs[alert_name].vif.get_alert()) begin
                            cfg.clk_rst_vif.wait_clks(1);
                          end
-                         cfg.m_alert_agent_cfg[alert_name].vif.wait_ack_complete();,
+                         cfg.m_alert_agent_cfgs[alert_name].vif.wait_ack_complete();,
                          $sformatf("%0s update_err alert timeout", csr.get_name()))
           end
           begin
diff --git a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
index 051a038..bc8e5a9 100644
--- a/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
+++ b/hw/dv/sv/cip_lib/seq_lib/cip_base_vseq__tl_errors.svh
@@ -164,6 +164,7 @@
     bit [BUS_DW-1:0] data;
     mubi4_t          instr_type;
 
+    if (cfg.under_reset) return;
     `DV_CHECK_STD_RANDOMIZE_FATAL(addr);
     `DV_CHECK_STD_RANDOMIZE_FATAL(data);
 
@@ -189,16 +190,9 @@
 endtask
 
 virtual task run_tl_errors_vseq(int num_times = 1, bit do_wait_clk = 0);
-  // TODO(#6628): Target specific tlul_assert devices rather than
-  //              globally enabling/disabling all of them.
-  //
-  //  With this approach, ALL tlul assertions are being disabled and then enabled.
-  //  A better solution (as per the linked issue) is to move the assertion enable
-  //  function calls back to encapsulate the `for` loop inside `run_tl_errors_vseq_sub()`
-  //  and pass in an appropriate "path" argument to the function to enable/disable
-  //  ONLY the corresponding tlul_assert monitor.
   set_tl_assert_en(.enable(0));
   for (int trans = 1; trans <= num_times; trans++) begin
+    if (cfg.under_reset) return;
     `uvm_info(`gfn, $sformatf("Running run_tl_errors_vseq %0d/%0d", trans, num_times), UVM_LOW)
     `loop_ral_models_to_create_threads(run_tl_errors_vseq_sub(do_wait_clk, ral_name);)
   end
@@ -279,7 +273,6 @@
 endtask : run_tl_errors_vseq_sub
 
 virtual task run_tl_intg_err_vseq(int num_times = 1);
-  // TODO(#6628) as above TODO
   set_tl_assert_en(.enable(0));
 
   // If there are multiple TLUL interfaces, race condition may occurs as one TLUL is updating intg
@@ -309,8 +302,10 @@
   fork
     // run csr_rw seq to send some normal CSR accesses in parallel
     begin
-      `uvm_info(`gfn, "Run csr_rw seq", UVM_HIGH)
-      run_csr_vseq(.csr_test_type("rw"), .ral_name(ral_name));
+      if (en_csr_vseq_w_tl_intg) begin
+        `uvm_info(`gfn, "Run csr_rw seq", UVM_HIGH)
+        run_csr_vseq(.csr_test_type("rw"), .ral_name(ral_name));
+      end
     end
     begin
       // check integrity status before injecting fault
diff --git a/hw/dv/sv/common_ifs/README.md b/hw/dv/sv/common_ifs/README.md
new file mode 100644
index 0000000..54124d2
--- /dev/null
+++ b/hw/dv/sv/common_ifs/README.md
@@ -0,0 +1,114 @@
+# Common Interfaces
+
+## Overview
+In this directory, we provide commonly used interfaces used to construct
+testbenches for DV. These interfaces are instantiated inside `tb` module for
+connecting dut signals. They are described in detail below.
+
+### `clk_if`
+This is a passive clock interface that is used to wait for clock events in
+testbenches. This interface has two clocking blocks, `cb` and `cbn`, for
+synchronizing to positive and negative clock edges, respectively. The interface
+also has the following tasks:
+* `wait_clks`: waits for specified number of positive clock edges
+* `wait_n_clks`: waits for specified number of negative clock edges
+
+### `clk_rst_if`
+
+Unlike `clk_if`, this interface can generate a clock and a reset signal. These
+are connected as `inout` signals and the interface observes them passively
+unless the `set_active` function is called.
+
+Just like `clk_if`, this interface has clocking blocks `cb` and `cbn`, together
+with `wait_clks` and `wait_n_clks` utility tasks. It also has
+* `wait_for_reset`: wait for a reset signaled on `rst_n`
+
+To generate a clock signal, call `set_active` at the start of the simulation.
+This is typically called from an `initial` block in the testbench. To configure
+the frequency and duty cycle of the generated clock, use the following
+functions:
+* `set_freq_mhz` / `set_freq_khz`: set the clock frequency in MHz / KHz. This
+  is 50MHz by default.
+* `set_period_ps`: set the clock period in picoseconds. This is 20_000ps by default
+  (giving a clock period of 50MHz).
+* `set_duty_cycle`: set the duty cycle (as a percentage: 1 - 99). This is 50 by
+  default.
+
+The clock can also have jitter added. This is generated as an offset in
+picoseconds added to randomly selected clock half-periods. It can be enabled
+and configured with:
+* `set_jitter_chance_pc`: set the percentage probability of adding a jitter to
+  a given half-period. By default, this is 0 and the clock has no jitter.
+* `set_max_jitter_ps`: set the maximum jitter to add to each clock half-period
+  in picoseconds. This is 1000ps (1 ns) by default.
+
+To start and stop the clock or apply a reset, use the following tasks. These
+will have no effect if `set_active` has not been called.
+* `start_clk`: start the clock. The clock is started by default, so this
+  task is only needed after a call to `stop_clk`.
+* `stop_clk`: stop / gate the clk
+* `apply_reset`: signal a reset on `rst_n`. The length of this reset and
+  whether it is synchronous or not can be configured with arguments to the
+  function.
+
+### `pins_if`
+
+This parameterized interface provides the ability to drive or sample any signal
+in the DUT.
+
+```systemverilog
+interface pins_if #(
+  parameter int Width = 1
+) (
+  inout [Width-1:0] pins
+);
+```
+
+By default, it behaves as a passive interface. The values of the pins can be
+read with the following functions:
+* `sample`: sample and return all the pin values
+* `sample_pin`: sample just the given pin
+
+The interface can also be configured to drive, pull up, or pull down its
+outputs. To do this, call
+* `drive` / `drive_pin`: Drive the output to the given value.
+* `drive_en` / `drive_en_pin`: Configure output enable; when enabled, this
+  drives value previously stored by a call to `drive` or `drive_pin`.
+* `set_pullup_en` / `set_pullup_en_pin`: Configure pull-up setting. If true and
+  output enable is false, drives the output to `1`.
+* `set_pulldown_en` / `set_pulldown_en_pin`: Configure pull-down setting. If
+  true and both output_enable and pull-up are false, drives the output to `0`.
+
+The diagram below gives a schematic view of `pins_if`. The driver shown is
+replicated for each bit.
+
+![Block diagram](pins_if.svg)
+
+### `entropy_subsys_fifo_exception_if`
+
+The IPs in the entropy subsystem (`entropy_src`, `CSRNG` and `EDN`) raise fatal alerts if they
+detect that data is lost in one of the intermediate FIFOS, either through:
+  - Write errors (Overflow) wvalid_i asserted when full
+  - Read errors  (Underflow) rready_i asserted when empty (i.e. rvalid_o == 0)
+  - State errors: Anomalous behavior rvalid_o deasserted (FIFO is not empty) when full
+
+Furthermore some instances of prim packer fifo's also raise recoverable alerts if firmware
+tries to write data to it when it is not ready.
+
+The events described above need not be treated as errors in general purpose designs.
+In many general designs where the fifo is capable of applying backpressure (stalling
+the read or write inputs) these do not have to be an error. One could in principal
+only signal an alert if it is observed that data was indeed lost (e.g., if `wvalid_i` is
+deasserted or data changes before `wready_o` is high).  However most of the entropy complex
+fifo stages do not respond to such backpressure, and thus the conditions above do
+indicate a loss of data.
+
+The entropy_subsys_fifo_exception_if.sv has pins that can map either to synchronous
+FIFOs or to packer fifos.  Though some interface ports will be unused in any case
+the interface parameter `IsPackerFifo` indicates which ports to use (True for
+packer FIFOs or False for synchronous FIFOs.
+
+The interface then generates error pulses in the `mon_cb` clocking block under
+the signal `mon_cb.error_pulses`, which has one line per possible error condition.
+The enumeration entropy_subsys_fifo_exception_pkg::fifo_exception_e lists the
+types of exceptions and provides the mapping to the corresponding error pulse line.
diff --git a/hw/dv/sv/common_ifs/clk_rst_if.sv b/hw/dv/sv/common_ifs/clk_rst_if.sv
index 0add9c5..85109ba 100644
--- a/hw/dv/sv/common_ifs/clk_rst_if.sv
+++ b/hw/dv/sv/common_ifs/clk_rst_if.sv
@@ -24,6 +24,7 @@
   `include "dv_macros.svh"
   `include "uvm_macros.svh"
   import uvm_pkg::*;
+  import common_ifs_pkg::*;
 `endif
 
   // Enables clock to be generated and driven by this interface.
@@ -97,7 +98,7 @@
   bit sole_clock = 1'b0;
 
   // use IfName as a part of msgs to indicate which clk_rst_vif instance
-  string msg_id = {"clk_rst_if::", IfName};
+  string msg_id = $sformatf("[%m(clk_rst_if):%s]", IfName);
 
   clocking cb @(posedge clk);
   endclocking
@@ -115,6 +116,14 @@
     repeat (num_clks) @cbn;
   endtask
 
+  // Wait for 'num_clks' clocks based on the positive clock edge or reset, whichever comes first.
+  task automatic wait_clks_or_rst(int num_clks);
+    fork
+      wait_clks(num_clks);
+      wait_for_reset(.wait_negedge(1'b1), .wait_posedge(1'b0));
+    join_any
+  endtask
+
   // wait for rst_n to assert and then deassert
   task automatic wait_for_reset(bit wait_negedge = 1'b1, bit wait_posedge = 1'b1);
     if (wait_negedge && ($isunknown(rst_n) || rst_n === 1'b1)) @(negedge rst_n);
@@ -149,16 +158,10 @@
     clk_freq_scale_up = freq_scale_up;
   endfunction
 
-  // call this function at t=0 (from tb top) to enable clk and rst_n to be driven
+  // Enables the clock and reset to be driven.
   function automatic void set_active(bit drive_clk_val = 1'b1, bit drive_rst_n_val = 1'b1);
-    time t = $time;
-    if (t == 0) begin
-      drive_clk = drive_clk_val;
-      drive_rst_n = drive_rst_n_val;
-    end
-    else begin
-      `dv_fatal("This function can only be called at t=0", msg_id)
-    end
+    drive_clk = drive_clk_val;
+    drive_rst_n = drive_rst_n_val;
   endfunction
 
   // set the clk period in ps
@@ -247,35 +250,27 @@
   endtask
 
   // apply reset with specified scheme
-  // TODO make this enum?
-  // rst_n_scheme
-  // 0 - fullly synchronous reset - it is asserted and deasserted on clock edges
-  // 1 - async assert, sync dessert (default)
-  // 2 - async assert, async dessert
-  // 3 - clk gated when reset asserted
   // Note: for power on reset, please ensure pre_reset_dly_clks is set to 0
-  // TODO #2338 issue workaround - $urandom call moved from default argument value to function body
   task automatic apply_reset(int pre_reset_dly_clks   = 0,
-                             integer reset_width_clks = 'x,
+                             int reset_width_clks = $urandom_range(50, 100),
                              int post_reset_dly_clks  = 0,
-                             int rst_n_scheme         = 1);
+                             rst_scheme_e rst_n_scheme  = RstAssertAsyncDeassertSync);
     int dly_ps;
-    if ($isunknown(reset_width_clks)) reset_width_clks = $urandom_range(50, 100);
     dly_ps = $urandom_range(0, clk_period_ps);
     wait_clks(pre_reset_dly_clks);
     case (rst_n_scheme)
-      0: begin : sync_assert_deassert
+      RstAssertSyncDeassertSync: begin
         o_rst_n <= 1'b0;
         wait_clks(reset_width_clks);
         o_rst_n <= 1'b1;
       end
-      1: begin : async_assert_sync_deassert
+      RstAssertAsyncDeassertSync: begin
         #(dly_ps * 1ps);
         o_rst_n <= 1'b0;
         wait_clks(reset_width_clks);
         o_rst_n <= 1'b1;
       end
-      2: begin : async_assert_async_deassert
+      RstAssertAsyncDeassertASync: begin
         #(dly_ps * 1ps);
         o_rst_n <= 1'b0;
         wait_clks(reset_width_clks);
diff --git a/hw/dv/sv/common_ifs/common_ifs.core b/hw/dv/sv/common_ifs/common_ifs.core
index 31ca693..efa3872 100644
--- a/hw/dv/sv/common_ifs/common_ifs.core
+++ b/hw/dv/sv/common_ifs/common_ifs.core
@@ -8,8 +8,10 @@
 filesets:
   files_dv:
     depend:
+      - lowrisc:prim:assert
       - lowrisc:dv:pins_if
     files:
+      - common_ifs_pkg.sv
       - clk_if.sv
       - clk_rst_if.sv
     file_type: systemVerilogSource
diff --git a/hw/dv/sv/common_ifs/common_ifs_pkg.sv b/hw/dv/sv/common_ifs/common_ifs_pkg.sv
new file mode 100644
index 0000000..4a1fd9a
--- /dev/null
+++ b/hw/dv/sv/common_ifs/common_ifs_pkg.sv
@@ -0,0 +1,15 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package common_ifs_pkg;
+  // dep packages
+  import uvm_pkg::*;
+
+  // Enum representing reset scheme
+  typedef enum bit [1:0] {
+    RstAssertSyncDeassertSync,
+    RstAssertAsyncDeassertSync,
+    RstAssertAsyncDeassertASync
+  } rst_scheme_e;
+endpackage
diff --git a/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.core b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.core
new file mode 100644
index 0000000..3e2b9c5
--- /dev/null
+++ b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.core
@@ -0,0 +1,20 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:entropy_subsys_fifo_exception_if"
+description: "DV interface for detecting FIFO exceptions, particularly in entropy subsystem IPs"
+
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_lib
+    files:
+      - entropy_subsys_fifo_exception_pkg.sv
+      - entropy_subsys_fifo_exception_if.sv
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.sv b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.sv
new file mode 100644
index 0000000..ab3636a
--- /dev/null
+++ b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_if.sv
@@ -0,0 +1,59 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Interface for capturing overflow/underflow/status events in
+// entropy complex (entropy_src, CSRNG, EDN) fifos
+
+
+interface entropy_subsys_fifo_exception_if#(
+  parameter bit IsPackerFifo = 0
+)
+(
+   input logic clk_i,
+   input logic rst_ni,
+
+   input logic wready_o,
+   input logic wvalid_i,
+   input logic rready_i,
+   input logic rvalid_o,
+   input logic full_o
+);
+
+   import entropy_subsys_fifo_exception_pkg::*;
+
+   logic read_err_d, write_err_d, state_err_d;
+   logic read_err_q, write_err_q, state_err_q;
+   logic read_err_pulse, write_err_pulse, state_err_pulse;
+   logic write_forbidden;
+
+   logic [N_FIFO_ERR_TYPES-1:0] error_pulses;
+   assign error_pulses[FIFO_READ_ERR]  = read_err_pulse;
+   assign error_pulses[FIFO_WRITE_ERR] = write_err_pulse;
+   assign error_pulses[FIFO_STATE_ERR] = state_err_pulse;
+
+   // Error conidtions map to the types of events that cause errors in the
+   // entropy subsystem IPs
+   assign write_forbidden = IsPackerFifo ? !wready_o : full_o;
+
+   assign write_err_d  = wvalid_i && write_forbidden;
+   assign read_err_d   = IsPackerFifo ?  1'b0 : (!rvalid_o && rready_i);
+   assign state_err_d  = IsPackerFifo ?  1'b0 : (!rvalid_o && full_o);
+
+   always @(posedge clk_i) begin
+     state_err_q <= state_err_d;
+     read_err_q  <= read_err_d;
+     write_err_q <= write_err_d;
+   end
+
+   clocking mon_cb @(posedge clk_i);
+     input write_forbidden;
+     input error_pulses;
+   endclocking
+
+
+   assign write_err_pulse = write_err_d && !write_err_q;
+   assign state_err_pulse = state_err_d && !state_err_q;
+   assign read_err_pulse = read_err_d && !read_err_q;
+
+endinterface : entropy_subsys_fifo_exception_if
diff --git a/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_pkg.sv b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_pkg.sv
new file mode 100644
index 0000000..9ca3e6d
--- /dev/null
+++ b/hw/dv/sv/common_ifs/entropy_subsys_fifo_exception_pkg.sv
@@ -0,0 +1,18 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+//
+// Datatypes for describing FIFO exceptions in the Entropy Subsystem
+//
+
+package entropy_subsys_fifo_exception_pkg;
+
+  typedef enum int {
+    FIFO_READ_ERR  = 0,
+    FIFO_WRITE_ERR,
+    FIFO_STATE_ERR,
+    N_FIFO_ERR_TYPES
+  } fifo_exception_e;
+
+endpackage
diff --git a/hw/dv/sv/common_ifs/index.md b/hw/dv/sv/common_ifs/index.md
deleted file mode 100644
index 41dc3bf..0000000
--- a/hw/dv/sv/common_ifs/index.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# Common interfaces
-
-
-## Overview
-In this directory, we provide commonly used interfaces used to construct
-testbenches for DV. These interfaces are instantiated inside `tb` module for
-connecting dut signals. They are described in detail below.
-
-### `clk_if`
-This is a passive clock interface that is used to wait for clock events in
-testbenches. This interface has two clocking blocks, `cb` and `cbn`, for
-synchronizing to positive and negative clock edges, respectively. The interface
-also has the following tasks:
-* `wait_clks`: waits for specified number of positive clock edges
-* `wait_n_clks`: waits for specified number of negative clock edges
-
-### `clk_rst_if`
-
-Unlike `clk_if`, this interface can generate a clock and a reset signal. These
-are connected as `inout` signals and the interface observes them passively
-unless the `set_active` function is called.
-
-Just like `clk_if`, this interface has clocking blocks `cb` and `cbn`, together
-with `wait_clks` and `wait_n_clks` utility tasks. It also has
-* `wait_for_reset`: wait for a reset signalled on `rst_n`
-
-To generate a clock signal, call `set_active` at the start of the simulation.
-This is typically called from an `initial` block in the testbench. To configure
-the frequency and duty cycle of the generated clock, use the following
-functions:
-* `set_freq_mhz` / `set_freq_khz`: set the clock frequency in MHz / KHz. This
-  is 50MHz by default.
-* `set_period_ps`: set the clock period in picoseconds. This is 20_000ps by default
-  (giving a clock period of 50MHz).
-* `set_duty_cycle`: set the duty cycle (as a percentage: 1 - 99). This is 50 by
-  default.
-
-The clock can also have jitter added. This is generated as an offset in
-picoseconds added to randomly selected clock half-periods. It can be enabled
-and configured with:
-* `set_jitter_chance_pc`: set the percentage probability of adding a jitter to
-  a given half-period. By default, this is 0 and the clock has no jitter.
-* `set_max_jitter_ps`: set the maximum jitter to add to each clock half-period
-  in picoseconds. This is 1000ps (1 ns) by default.
-
-To start and stop the clock or apply a reset, use the following tasks. These
-will have no effect if `set_active` has not been called.
-* `start_clk`: start the clock. The clock is started by default, so this
-  task is only needed after a call to `stop_clk`.
-* `stop_clk`: stop / gate the clk
-* `apply_reset`: signal a reset on `rst_n`. The length of this reset and
-  whether it is synchronous or not can be configured with arguments to the
-  function.
-
-### `pins_if`
-
-This parameterized interface provides the ability to drive or sample any signal
-in the DUT.
-
-```systemverilog
-interface pins_if #(
-  parameter int Width = 1
-) (
-  inout [Width-1:0] pins
-);
-```
-
-By default, it behaves as a passive interface. The values of the pins can be
-read with the following functions:
-* `sample`: sample and return all the pin values
-* `sample_pin`: sample just the given pin
-
-The interface can also be configured to drive, pull up, or pull down its
-outputs. To do this, call
-* `drive` / `drive_pin`: Drive the output to the given value.
-* `drive_en` / `drive_en_pin`: Configure output enable; when enabled, this
-  drives value previously stored by a call to `drive` or `drive_pin`.
-* `set_pullup_en` / `set_pullup_en_pin`: Configure pull-up setting. If true and
-  output enable is false, drives the output to `1`.
-* `set_pulldown_en` / `set_pulldown_en_pin`: Configure pull-down setting. If
-  true and both output_enable and pull-up are false, drives the output to `0`.
-
-The diagram below gives a schematic view of `pins_if`. The driver shown is
-replicated for each bit.
-
-![Block diagram](pins_if.svg)
diff --git a/hw/dv/sv/common_ifs/pins_if.sv b/hw/dv/sv/common_ifs/pins_if.sv
index b387b20..2316880 100644
--- a/hw/dv/sv/common_ifs/pins_if.sv
+++ b/hw/dv/sv/common_ifs/pins_if.sv
@@ -8,11 +8,15 @@
 `ifndef SYNTHESIS
 
 interface pins_if #(
-  parameter int Width = 1
+  parameter int Width = 1,
+  parameter bit [8*4-1:0] PullStrength = "Pull"
 ) (
   inout [Width-1:0] pins
 );
 
+  `include "prim_assert.sv"
+
+  `ASSERT_INIT(PullStrengthParamValid, PullStrength inside {"Weak", "Pull"})
 
   logic [Width-1:0] pins_o;       // value to be driven out
   bit   [Width-1:0] pins_oe = '0; // output enable
@@ -71,6 +75,13 @@
     return pins;
   endfunction
 
+  // Fully disconnect this interface, including the pulls.
+  function automatic void disconnect();
+    pins_oe = {Width{1'b0}};
+    pins_pu = {Width{1'b0}};
+    pins_pd = {Width{1'b0}};
+  endfunction
+
   // make connections
   for (genvar i = 0; i < Width; i++) begin : gen_each_pin
 `ifdef VERILATOR
@@ -78,10 +89,18 @@
                      pins_pu[i] ? 1'b1 :
                      pins_pd[i] ? 1'b0 : 1'bz;
 `else
-    // Drive the pin with pull strength based on whether pullup / pulldown is enabled.
-    assign (pull0, pull1) pins[i] = ~pins_oe[i] ? (pins_pu[i] ? 1'b1 :
-                                                   pins_pd[i] ? 1'b0 : 1'bz) : 1'bz;
-
+    // Drive the pin based on whether pullup / pulldown is enabled.
+    //
+    // If output is not enabled, then the pin is pulled up or down with the `PullStrength` strength
+    // Pullup has priority over pulldown.
+    if (PullStrength == "Pull") begin : gen_pull_strength_pull
+      assign (pull0, pull1) pins[i] = ~pins_oe[i] ? (pins_pu[i] ? 1'b1 :
+                                                     pins_pd[i] ? 1'b0 : 1'bz) : 1'bz;
+    end : gen_pull_strength_pull
+    else if (PullStrength == "Weak") begin : gen_pull_strength_weak
+      assign (weak0, weak1) pins[i] = ~pins_oe[i] ? (pins_pu[i] ? 1'b1 :
+                                                     pins_pd[i] ? 1'b0 : 1'bz) : 1'bz;
+    end : gen_pull_strength_weak
 
     // If output enable is 1, strong driver assigns pin to 'value to be driven out';
     // the external strong driver can still affect pin, if exists.
diff --git a/hw/dv/sv/csr_utils/README.md b/hw/dv/sv/csr_utils/README.md
new file mode 100644
index 0000000..2652d90
--- /dev/null
+++ b/hw/dv/sv/csr_utils/README.md
@@ -0,0 +1,158 @@
+# CSR utilities
+
+This csr_utils folder intends to implement CSR related methods and test sequences for DV
+to share across all testbenches.
+
+### CSR utility package
+`csr_utils_pkg` provides common methods and properties to support and manage CSR accesses
+and CSR related test sequences.
+
+#### Global types and variables
+All common types and variables are defined at this package level. Examples are:
+```systemverilog
+  uint       outstanding_accesses        = 0;
+  uint       default_timeout_ns          = 1_000_000;
+```
+
+##### Outstanding_accesses
+`csr_utils_pkg` used an internal variable to store the number of accesses
+(read or write) that have not yet completed. This variable is shared among all methods of
+register reading and writing. Directly accessing this variable is discouraged. Instead,
+the following methods are used to control this variable to keep track of non-blocking
+accesses made in the testbench:
+```systemverilog
+  function automatic void increment_outstanding_access();
+    outstanding_accesses++;
+  endfunction
+
+  function automatic void decrement_outstanding_access();
+    outstanding_accesses--;
+  endfunction
+
+  task automatic wait_no_outstanding_access();
+    wait(outstanding_accesses == 0);
+  endtask
+
+  function automatic void clear_outstanding_access();
+    outstanding_accesses = 0;
+  endfunction
+```
+
+##### CSR spinwait
+One of the commonly used tasks in `csr_utils_pkg` is `csr_spinwait`. This task
+can poll a CSR or CSR field continuously or periodically until it reads out the
+expected value. This task also has a timeout check in case due to DUT or testbench
+issue, the CSR or CSR field never returns the expected value.
+Example below uses the `csr_spinwait` to wait until the CSR `fifo_status` field
+`fifo_full` reaches value bit 1:
+```systemverilog
+csr_spinwait(.ptr(ral.status.fifo_full), .exp_data(1'b0));
+```
+
+##### Read and check all CSRs
+The purpose of the `read_and_check_all_csrs` task is to read all valid CSRs from
+the given `uvm_reg_block` and check against their expected values from RAL. This
+task is primarily implemented to use after reset, to make sure all the CSRs are
+being reset to the default value.
+
+##### Under_reset
+Due to `csr_utils_pkg` is not connected to any interface, methods inside
+this package are not able to get reset information. Current the `under_reset`
+bit is declared with two functions:
+```systemverilog
+function automatic void reset_asserted();
+  under_reset = 1;
+endfunction
+
+function automatic void reset_deasserted();
+  under_reset = 0;
+endfunction
+```
+This reset information is updated in `dv_lib/dv_base_vseq.sv`. When the
+`apply_reset` task is triggered, it will set and reset the `under_reset` bit
+via the functions above.
+
+#### Global CSR util methods
+##### Global methods for CSR and MEM attributes
+This package provides methods to access CSR or Memory attributes, such as address,
+value, etc. Examples are:
+ * `get_csr_addrs`
+ * `get_mem_addr_ranges`
+ * `decode_csr_or_field`
+
+##### Global methods for CSR access
+The CSR access methods are based on `uvm_reg` methods, such as `uvm_reg::read()`,
+`uvm_reg::write()`, `uvm_reg::update()`. For all CSR methods, user can
+pass either a register or a field handle. Examples are:
+ * `csr_rd_check`: Given the uvm_reg or uvm_reg_field object, this method will
+   compare the CSR value with the expected value (given as an input) or with
+   the RAL mirrored value
+ * `csr_update`: Given the uvm_reg object, this method will update the value of the
+   register in DUT to match the desired value
+
+To enhance the usability, these methods support CSR blocking, non-blocking
+read/write, and a timeout checking.
+ * A blocking thread will not execute the next sequence until the current CSR
+   access is finished
+ * A non-blocking thread allows multiple CSR accesses to be issued back-to-back
+   without waiting for the response
+ * A timeout check will discard the ongoing CSR access by disabling the forked
+   thread and will throw a UVM_ERROR once the process exceeds the max timeout setting
+
+### CSR sequence library
+`csr_seq_lib.sv` provides common CSR related test sequences to share across all testbenches.
+These test sequences are based off the standard sequences provided in UVM1.2 RAL.
+The parent class (DUT-specific test or sequence class) that creates them needs to provide them
+with the DUT RAL model. The list of CSRs are then extracted from the RAL model to performs the checks.
+In addition, the test sequences provide an ability to exclude a CSR from writes or reads (or both)
+depending on the behavior of the CSR in the design. This is explained more in the
+[CSR exclusion methodology](#csr-exclusion-methodology) section below.
+All CSR accesses in these sequences are made non-blocking to ensure back-to-back scenarios
+are exercised.
+Supported CSR test sequences are:
+ * `csr_hw_reset`: Write all CSRs with random values and then reset the DUT.
+   After reset, read all CSRs and compare with expected values
+ * `csr_rw`: Write a randomly selected CSRs, then read out the updated
+   CSR or CSR field and compare with expected value
+ * `csr_bit_bash`: Randomly select a CSR and write 1's and 0's to
+   every bit, then read the CSR to compare with expected value
+ * `csr_aliasing`: Randomly write a CSR, then read all CSRs to
+   verify that only the CSR that was written was updated
+ * `mem_walk`: Write and read all valid addresses in the memory. Compare
+   the read results with the expected values
+
+### CSR exclusion methodology
+The CSR test sequences listed above intend to perform a basic check to CSR
+read/write accesses, but do not intend to check specific DUT functionalities. Thus the
+sequences might need to exclude reading or writing certain CSRs depending on the
+specific testbench.
+`csr_excl_item` is a class that supports adding exclusions to CSR test sequences.
+Examples of useful functions in this class are:
+* `add_excl`: Add exclusions to the CSR test sequences. This function has two inputs:
+  - Exclusion scope: A hierarchical path name at all levels including block,
+    CSR, and field. This input supports * and ? wildcards for glob style matching
+  - CSR_exclude type: An enumeration defined as below:
+    ```systemverilog
+    typedef enum bit[2:0] {
+      CsrNoExcl         = 3'b000, // no exclusions
+      CsrExclInitCheck  = 3'b001, // exclude csr from init val check
+      CsrExclWriteCheck = 3'b010, // exclude csr from write-read check
+      CsrExclCheck      = 3'b011, // exclude csr from init or write-read check
+      CsrExclWrite      = 3'b100, // exclude csr from write
+      CsrExclAll        = 3'b111  // exclude csr from init or write or write-read check
+    } csr_excl_type_e;
+    ```
+
+  One example to use this function in HMAC to exclude all CSRs or fields with
+  names starting with "key":
+  ```systemverilog
+  csr_excl.add_excl({scope, ".", "key?"}, CsrExclWrite);
+  ```
+
+* `has_excl`: Check if the CSR has a match in the existing exclusions loopup,
+  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`,
+that provides a common framework for all testbenches to run these CSR test sequences and
+add exclusions.
diff --git a/hw/dv/sv/csr_utils/csr_seq_lib.sv b/hw/dv/sv/csr_utils/csr_seq_lib.sv
index ac9ded6..34cbd94 100644
--- a/hw/dv/sv/csr_utils/csr_seq_lib.sv
+++ b/hw/dv/sv/csr_utils/csr_seq_lib.sv
@@ -4,7 +4,6 @@
 
 // CSR suite of sequences that do writes and reads to csrs
 // includes hw_reset, rw, bit_bash and aliasing tests for csrs, and mem_walk for uvm_mems
-// TODO: when mem backdoor is implemented, add uvm_mem_access_seq for backdoor rd
 // The sequences perform csr writes and reads and follow the standard csr test suite. If external
 // checker is enabled, then the external entity is required to update the mirrored value on
 // writes. If not enabled, the sequences themselves call predict function to update the mirrored
@@ -63,7 +62,6 @@
     int   chunk_size;
 
     // extract all csrs from the model
-    // TODO: add and use function here instead that allows pre filtering csrs
     all_csrs.delete();
     foreach (models[i]) begin
       models[i].get_registers(all_csrs);
@@ -101,7 +99,6 @@
   endfunction
 
   // check if this csr/fld is excluded from test based on the excl info in blk.csr_excl
-  // TODO, consider to put excl info in dv_base_reg and dv_base_reg_field
   function bit is_excl(uvm_object obj,
                        csr_excl_type_e csr_excl_type,
                        csr_test_type_e csr_test_type);
@@ -180,8 +177,6 @@
     uvm_reg_data_t wdata;
 
     // check all hdl paths are valid
-    // TODO: Move this check to env::end_of_elaboration_phase instead. Regular tests may also choose
-    // to access CSRs via backdoor.
     if (!test_backdoor_path_done) begin
       foreach (models[i]) begin
         bkdr_reg_path_e path_kind;
@@ -428,7 +423,6 @@
       err_msg = $sformatf("Wrote %0s[%0d]: %0b", rg.get_full_name(), k, val[k]);
       csr_wr(.ptr(rg), .value(val), .blocking(1), .predict(!external_checker));
 
-      // TODO, outstanding access to same reg isn't supported in uvm_reg. Need to add another seq
       // uvm_reg waits until transaction is completed, before start another read/write in same reg
       csr_rd_check(.ptr           (rg),
                    .blocking      (0),
diff --git a/hw/dv/sv/csr_utils/csr_utils_pkg.sv b/hw/dv/sv/csr_utils/csr_utils_pkg.sv
index 1037d98..907ec80 100644
--- a/hw/dv/sv/csr_utils/csr_utils_pkg.sv
+++ b/hw/dv/sv/csr_utils/csr_utils_pkg.sv
@@ -14,14 +14,15 @@
   `include "dv_macros.svh"
 
   // local types and variables
-  uint        outstanding_accesses        = 0;
-  uint        default_timeout_ns          = 2_000_000; // 2ms
-  uint        default_spinwait_timeout_ns = 10_000_000; // 10ms
-  string      msg_id                      = "csr_utils";
-  bit         default_csr_blocking        = 1;
-  uvm_check_e default_csr_check           = UVM_CHECK;
-  bit         under_reset                 = 0;
-  int         max_outstanding_accesses    = 100;
+  uint              outstanding_accesses        = 0;
+  uint              default_timeout_ns          = 2_000_000; // 2ms
+  uint              default_spinwait_timeout_ns = 10_000_000; // 10ms
+  string            msg_id                      = "csr_utils";
+  bit               default_csr_blocking        = 1;
+  uvm_check_e       default_csr_check           = UVM_CHECK;
+  bit               under_reset                 = 0;
+  int               max_outstanding_accesses    = 100;
+  uvm_reg_frontdoor default_user_frontdoor      = null;
 
   function automatic void increment_outstanding_access();
     outstanding_accesses++;
@@ -115,13 +116,14 @@
 
   // Use `csr_wr` to construct `csr_update` to avoid replicated codes to handle nonblocking,
   // shadow writes etc
-  task automatic csr_update(input  uvm_reg      csr,
-                            input  uvm_check_e  check = default_csr_check,
-                            input  uvm_path_e   path = UVM_DEFAULT_PATH,
-                            input  bit          blocking = default_csr_blocking,
-                            input  uint         timeout_ns = default_timeout_ns,
-                            input  uvm_reg_map  map = null,
-                            input  bit          en_shadow_wr = 1);
+  task automatic csr_update(input  uvm_reg            csr,
+                            input  uvm_check_e        check = default_csr_check,
+                            input  uvm_path_e         path = UVM_DEFAULT_PATH,
+                            input  bit                blocking = default_csr_blocking,
+                            input  uint               timeout_ns = default_timeout_ns,
+                            input  uvm_reg_map        map = null,
+                            input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor,
+                            input  bit                en_shadow_wr = 1);
     uvm_reg_field fields[$];
     uvm_reg_data_t value;
 
@@ -136,19 +138,21 @@
     end
 
     csr_wr(.ptr(csr), .value(value), .check(check), .path(path), .blocking(blocking), .backdoor(0),
-           .timeout_ns(timeout_ns), .predict(0), .map(map), .en_shadow_wr(en_shadow_wr));
+           .timeout_ns(timeout_ns), .predict(0), .map(map), .user_ftdr(user_ftdr),
+           .en_shadow_wr(en_shadow_wr));
   endtask
 
-  task automatic csr_wr(input uvm_object     ptr,
-                        input uvm_reg_data_t value,
-                        input uvm_check_e    check = default_csr_check,
-                        input uvm_path_e     path = UVM_DEFAULT_PATH,
-                        input bit            blocking = default_csr_blocking,
-                        input bit            backdoor = 0,
-                        input uint           timeout_ns = default_timeout_ns,
-                        input bit            predict = 0,
-                        input uvm_reg_map    map = null,
-                        input bit            en_shadow_wr = 1);
+  task automatic csr_wr(input uvm_object          ptr,
+                        input uvm_reg_data_t      value,
+                        input uvm_check_e         check = default_csr_check,
+                        input uvm_path_e          path = UVM_DEFAULT_PATH,
+                        input bit                 blocking = default_csr_blocking,
+                        input bit                 backdoor = 0,
+                        input uint                timeout_ns = default_timeout_ns,
+                        input bit                 predict = 0,
+                        input uvm_reg_map         map = null,
+                        input uvm_reg_frontdoor   user_ftdr = default_user_frontdoor,
+                        input bit                 en_shadow_wr = 1);
     if (backdoor) begin
       csr_poke(ptr, value, check, predict);
     end else begin
@@ -161,11 +165,13 @@
       end
 
       if (blocking) begin
-        csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
+        csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, user_ftdr,
+                   en_shadow_wr);
       end else begin
         fork
           begin
-            csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, en_shadow_wr);
+            csr_wr_sub(csr_or_fld.csr, value, check, path, timeout_ns, predict, map, user_ftdr,
+                       en_shadow_wr);
           end
         join_none
         // Add #0 to ensure that this thread starts executing before any subsequent call
@@ -175,17 +181,18 @@
   endtask
 
   // subroutine of csr_wr, don't use it directly
-  task automatic csr_wr_sub(input uvm_reg        csr,
-                            input uvm_reg_data_t value,
-                            input uvm_check_e    check = default_csr_check,
-                            input uvm_path_e     path = UVM_DEFAULT_PATH,
-                            input uint           timeout_ns = default_timeout_ns,
-                            input bit            predict = 0,
-                            input uvm_reg_map    map = null,
-                            input bit            en_shadow_wr = 1);
+  task automatic csr_wr_sub(input uvm_reg             csr,
+                            input uvm_reg_data_t      value,
+                            input uvm_check_e         check = default_csr_check,
+                            input uvm_path_e          path = UVM_DEFAULT_PATH,
+                            input uint                timeout_ns = default_timeout_ns,
+                            input bit                 predict = 0,
+                            input uvm_reg_map         map = null,
+                            input uvm_reg_frontdoor   user_ftdr = default_user_frontdoor,
+                            input bit                 en_shadow_wr = 1);
     fork
       begin : isolation_fork
-        string        msg_id = {csr_utils_pkg::msg_id, "::csr_wr"};
+        string msg_id = {csr_utils_pkg::msg_id, "::csr_wr"};
 
         fork
           begin
@@ -196,19 +203,19 @@
             csr_pre_write_sub(csr, en_shadow_wr);
 
             csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
-                                   .predict(predict), .map(map));
+                                   .predict(predict), .map(map), .user_ftdr(user_ftdr));
             if (en_shadow_wr && dv_reg.get_is_shadowed()) begin
               csr_wr_and_predict_sub(.csr(csr), .value(value), .check(check), .path(path),
-                                     .predict(predict), .map(map));
+                                     .predict(predict), .map(map), .user_ftdr(user_ftdr));
             end
 
             csr_post_write_sub(csr, en_shadow_wr);
             decrement_outstanding_access();
           end
           begin
-            wait_timeout(timeout_ns, msg_id,
-                         $sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
-                                   csr.get_full_name(), csr.get_address()));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
+                             $sformatf("Timeout waiting to csr_wr %0s (addr=0x%0h)",
+                                       csr.get_full_name(), csr.get_address()))
           end
         join_any
         disable fork;
@@ -217,14 +224,19 @@
   endtask
 
   // internal task, don't use it directly
-  task automatic csr_wr_and_predict_sub(uvm_reg        csr,
-                                        uvm_reg_data_t value,
-                                        uvm_check_e    check,
-                                        uvm_path_e     path,
-                                        bit            predict,
-                                        uvm_reg_map    map);
+  task automatic csr_wr_and_predict_sub(uvm_reg             csr,
+                                        uvm_reg_data_t      value,
+                                        uvm_check_e         check,
+                                        uvm_path_e          path,
+                                        bit                 predict,
+                                        uvm_reg_map         map,
+                                        uvm_reg_frontdoor   user_ftdr);
     uvm_status_e status;
+
+    if (user_ftdr != null) csr.set_frontdoor(user_ftdr);
     csr.write(.status(status), .value(value), .path(path), .map(map), .prior(100));
+    // TODO: need to remove the frontdoor to switch back to the default,
+    // but this doesn't work: if (user_ftdr != null) ptr.set_frontdoor(null);
 
     if (under_reset) return;
     if (check == UVM_CHECK) begin
@@ -293,23 +305,24 @@
     end
   endtask
 
-  task automatic csr_rd(input  uvm_object     ptr, // accept reg or field
-                        output uvm_reg_data_t value,
-                        input  uvm_check_e    check = default_csr_check,
-                        input  uvm_path_e     path = UVM_DEFAULT_PATH,
-                        input  bit            blocking = default_csr_blocking,
-                        input  bit            backdoor = 0,
-                        input  uint           timeout_ns = default_timeout_ns,
-                        input  uvm_reg_map    map = null);
+  task automatic csr_rd(input  uvm_object         ptr, // accept reg or field
+                        output uvm_reg_data_t     value,
+                        input  uvm_check_e        check = default_csr_check,
+                        input  uvm_path_e         path = UVM_DEFAULT_PATH,
+                        input  bit                blocking = default_csr_blocking,
+                        input  bit                backdoor = 0,
+                        input  uint               timeout_ns = default_timeout_ns,
+                        input  uvm_reg_map        map = null,
+                        input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     uvm_status_e status;
     if (blocking) begin
       csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
-                 .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
+                 .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map), .user_ftdr(user_ftdr));
     end else begin
       `DV_CHECK_EQ(backdoor, 0, "Don't enable backdoor with blocking = 0", error, msg_id)
       fork
         csr_rd_sub(.ptr(ptr), .value(value), .status(status), .check(check), .path(path),
-                   .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
+                   .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map), .user_ftdr(user_ftdr));
       join_none
       // Add #0 to ensure that this thread starts executing before any subsequent call
       #0;
@@ -317,18 +330,19 @@
   endtask
 
   // subroutine of csr_rd, don't use it directly
-  task automatic csr_rd_sub(input  uvm_object     ptr, // accept reg or field
-                            output uvm_reg_data_t value,
-                            output uvm_status_e   status,
-                            input  bit            backdoor = 0,
-                            input  uvm_check_e    check = default_csr_check,
-                            input  uvm_path_e     path = UVM_DEFAULT_PATH,
-                            input  uint           timeout_ns = default_timeout_ns,
-                            input  uvm_reg_map    map = null);
+  task automatic csr_rd_sub(input  uvm_object         ptr, // accept reg or field
+                            output uvm_reg_data_t     value,
+                            output uvm_status_e       status,
+                            input  bit                backdoor = 0,
+                            input  uvm_check_e        check = default_csr_check,
+                            input  uvm_path_e         path = UVM_DEFAULT_PATH,
+                            input  uint               timeout_ns = default_timeout_ns,
+                            input  uvm_reg_map        map = null,
+                            input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     if (backdoor) begin
-        csr_peek(ptr, value, check);
-        status = UVM_IS_OK;
-        return;
+      csr_peek(ptr, value, check);
+      status = UVM_IS_OK;
+      return;
     end
     fork
       begin : isolation_fork
@@ -339,6 +353,7 @@
           begin
             increment_outstanding_access();
             csr_or_fld = decode_csr_or_field(ptr);
+            if (user_ftdr != null) csr_or_fld.csr.set_frontdoor(user_ftdr);
             if (csr_or_fld.field != null) begin
               csr_or_fld.field.read(.status(status), .value(value), .path(path), .map(map),
                                     .prior(100));
@@ -346,6 +361,8 @@
               csr_or_fld.csr.read(.status(status), .value(value), .path(path), .map(map),
                                   .prior(100));
             end
+            // TODO: need to remove the frontdoor to switch back to the default,
+            // but this doesn't work: if (user_ftdr != null) ptr.set_frontdoor(null);
             if (check == UVM_CHECK && !under_reset) begin
               `DV_CHECK_EQ(status, UVM_IS_OK,
                            $sformatf("trying to read csr/field %0s", ptr.get_full_name()),
@@ -354,9 +371,9 @@
             decrement_outstanding_access();
           end
           begin
-            wait_timeout(timeout_ns, msg_id,
-                         $sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
-                                   ptr.get_full_name(), csr_or_fld.csr.get_address()));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
+                             $sformatf("Timeout waiting to csr_rd %0s (addr=0x%0h)",
+                                       ptr.get_full_name(), csr_or_fld.csr.get_address()))
           end
         join_any
         disable fork;
@@ -396,18 +413,19 @@
     if (csr_or_fld.field != null) value = get_field_val(csr_or_fld.field, value);
   endtask
 
-  task automatic csr_rd_check(input  uvm_object     ptr,
-                              input  uvm_check_e    check = default_csr_check,
-                              input  uvm_path_e     path = UVM_DEFAULT_PATH,
-                              input  bit            blocking = default_csr_blocking,
-                              input  bit            backdoor = 0,
-                              input  uint           timeout_ns = default_timeout_ns,
-                              input  bit            compare = 1'b1,
-                              input  bit            compare_vs_ral = 1'b0,
-                              input  uvm_reg_data_t compare_mask = '1,
-                              input  uvm_reg_data_t compare_value = 0,
-                              input  string         err_msg = "",
-                              input  uvm_reg_map    map = null);
+  task automatic csr_rd_check(input  uvm_object         ptr,
+                              input  uvm_check_e        check = default_csr_check,
+                              input  uvm_path_e         path = UVM_DEFAULT_PATH,
+                              input  bit                blocking = default_csr_blocking,
+                              input  bit                backdoor = 0,
+                              input  uint               timeout_ns = default_timeout_ns,
+                              input  bit                compare = 1'b1,
+                              input  bit                compare_vs_ral = 1'b0,
+                              input  uvm_reg_data_t     compare_mask = '1,
+                              input  uvm_reg_data_t     compare_value = 0,
+                              input  string             err_msg = "",
+                              input  uvm_reg_map        map = null,
+                              input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     fork
       begin : isolation_fork
         fork
@@ -416,24 +434,29 @@
             uvm_status_e    status;
             uvm_reg_data_t  obs;
             uvm_reg_data_t  exp;
+            uvm_reg_data_t  reset_val;
             string          msg_id = {csr_utils_pkg::msg_id, "::csr_rd_check"};
 
             csr_or_fld = decode_csr_or_field(ptr);
 
             csr_rd_sub(.ptr(ptr), .value(obs), .status(status), .check(check), .path(path),
-                       .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map));
+                       .backdoor(backdoor), .timeout_ns(timeout_ns), .map(map),
+                       .user_ftdr(user_ftdr));
 
             // get mirrored value after read to make sure the read reg access is updated
             if (csr_or_fld.field != null) begin
               exp = csr_or_fld.field.get_mirrored_value();
+              reset_val = csr_or_fld.field.get_reset();
             end else begin
               exp = csr_or_fld.csr.get_mirrored_value();
+              reset_val = csr_or_fld.csr.get_reset();
             end
             if (compare && status == UVM_IS_OK && !under_reset) begin
               obs = obs & compare_mask;
               exp = (compare_vs_ral ? exp : compare_value) & compare_mask;
-              `DV_CHECK_EQ(obs, exp, {"Regname: ", ptr.get_full_name(), " ", err_msg},
-                    error, msg_id)
+              `DV_CHECK_EQ(obs, exp, $sformatf("Regname: %0s reset value: 0x%0h %0s",
+                                               ptr.get_full_name(), reset_val, err_msg),
+                           error, msg_id)
             end
           end
         join_none
@@ -499,16 +522,17 @@
    endtask
 
   // poll a csr or csr field continuously until it reads the expected value.
-  task automatic csr_spinwait(input uvm_object      ptr,
-                              input uvm_reg_data_t  exp_data,
-                              input uvm_check_e     check = default_csr_check,
-                              input uvm_path_e      path = UVM_DEFAULT_PATH,
-                              input uvm_reg_map     map = null,
-                              input uint            spinwait_delay_ns = 0,
-                              input uint            timeout_ns = default_spinwait_timeout_ns,
-                              input compare_op_e    compare_op = CompareOpEq,
-                              input bit             backdoor = 0,
-                              input uvm_verbosity   verbosity = UVM_HIGH);
+  task automatic csr_spinwait(input uvm_object          ptr,
+                              input uvm_reg_data_t      exp_data,
+                              input uvm_check_e         check = default_csr_check,
+                              input uvm_path_e          path = UVM_DEFAULT_PATH,
+                              input uvm_reg_map         map = null,
+                              input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor,
+                              input uint                spinwait_delay_ns = 0,
+                              input uint                timeout_ns = default_spinwait_timeout_ns,
+                              input compare_op_e        compare_op = CompareOpEq,
+                              input bit                 backdoor = 0,
+                              input uvm_verbosity       verbosity = UVM_HIGH);
     fork
       begin : isolation_fork
         csr_field_t     csr_or_fld;
@@ -521,7 +545,7 @@
           while (!under_reset) begin
             if (spinwait_delay_ns) #(spinwait_delay_ns * 1ns);
             csr_rd(.ptr(ptr), .value(read_data), .check(check), .path(path),
-                   .blocking(1), .map(map), .backdoor(backdoor));
+                   .blocking(1), .map(map), .user_ftdr(user_ftdr), .backdoor(backdoor));
             `uvm_info(msg_id, $sformatf("ptr %0s == 0x%0h",
                                         ptr.get_full_name(), read_data), verbosity)
             case (compare_op)
@@ -539,8 +563,8 @@
             endcase
           end
           begin
-            wait_timeout(timeout_ns, msg_id, $sformatf("timeout %0s (addr=0x%0h) == 0x%0h",
-                ptr.get_full_name(), csr_or_fld.csr.get_address(), exp_data));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id, $sformatf("timeout %0s (addr=0x%0h) == 0x%0h",
+                ptr.get_full_name(), csr_or_fld.csr.get_address(), exp_data))
           end
         join_any
         disable fork;
@@ -548,30 +572,32 @@
     join
   endtask
 
-  task automatic mem_rd(input  uvm_mem     ptr,
-                        input  int         offset,
-                        output bit[31:0]   data,
-                        input  uvm_check_e check = default_csr_check,
-                        input  bit         blocking = default_csr_blocking,
-                        input  uint        timeout_ns = default_timeout_ns,
-                        input  uvm_reg_map map = null);
+  task automatic mem_rd(input  uvm_mem            ptr,
+                        input  int                offset,
+                        output bit[31:0]          data,
+                        input  uvm_check_e        check = default_csr_check,
+                        input  bit                blocking = default_csr_blocking,
+                        input  uint               timeout_ns = default_timeout_ns,
+                        input  uvm_reg_map        map = null,
+                        input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     if (blocking) begin
-      mem_rd_sub(ptr, offset, data, check, timeout_ns, map);
+      mem_rd_sub(ptr, offset, data, check, timeout_ns, map, user_ftdr);
     end else begin
       fork
-        mem_rd_sub(ptr, offset, data, check, timeout_ns, map);
+        mem_rd_sub(ptr, offset, data, check, timeout_ns, map, user_ftdr);
       join_none
       // Add #0 to ensure that this thread starts executing before any subsequent call
       #0;
     end
   endtask : mem_rd
 
-  task automatic mem_rd_sub(input  uvm_mem     ptr,
-                            input  int         offset,
-                            output bit[31:0]   data,
-                            input  uvm_check_e check = default_csr_check,
-                            input  uint        timeout_ns = default_timeout_ns,
-                            input  uvm_reg_map map = null);
+  task automatic mem_rd_sub(input  uvm_mem            ptr,
+                            input  int                offset,
+                            output bit[31:0]          data,
+                            input  uvm_check_e        check = default_csr_check,
+                            input  uint               timeout_ns = default_timeout_ns,
+                            input  uvm_reg_map        map = null,
+                            input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     fork
       begin : isolating_fork
         uvm_status_e status;
@@ -580,7 +606,10 @@
         fork
           begin
             increment_outstanding_access();
+            if (user_ftdr != null) ptr.set_frontdoor(user_ftdr);
             ptr.read(.status(status), .offset(offset), .value(data), .map(map), .prior(100));
+            // TODO: need to remove the frontdoor to switch back to the default,
+            // but this doesn't work: if (user_ftdr != null) ptr.set_frontdoor(null);
             if (check == UVM_CHECK && !under_reset) begin
               `DV_CHECK_EQ(status, UVM_IS_OK,
                            $sformatf("trying to read mem %0s", ptr.get_full_name()), error, msg_id)
@@ -588,9 +617,9 @@
             decrement_outstanding_access();
           end
           begin : mem_rd_timeout
-            wait_timeout(timeout_ns, msg_id,
-                         $sformatf("Timeout waiting to mem_rd %0s (addr=0x%0h)",
-                                   ptr.get_full_name(), offset));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
+                             $sformatf("Timeout waiting to mem_rd %0s (addr=0x%0h)",
+                                       ptr.get_full_name(), offset))
           end
         join_any
         disable fork;
@@ -598,30 +627,32 @@
     join
   endtask : mem_rd_sub
 
-  task automatic mem_wr(input uvm_mem     ptr,
-                        input int         offset,
-                        input bit[31:0]   data,
-                        input bit         blocking = default_csr_blocking,
-                        input uint        timeout_ns = default_timeout_ns,
-                        input uvm_check_e check = default_csr_check,
-                        input uvm_reg_map map = null);
+  task automatic mem_wr(input uvm_mem             ptr,
+                        input int                 offset,
+                        input bit[31:0]           data,
+                        input bit                 blocking = default_csr_blocking,
+                        input uint                timeout_ns = default_timeout_ns,
+                        input uvm_check_e         check = default_csr_check,
+                        input uvm_reg_map         map = null,
+                        input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
     if (blocking) begin
-      mem_wr_sub(ptr, offset, data, timeout_ns, check, map);
+      mem_wr_sub(ptr, offset, data, timeout_ns, check, map, user_ftdr);
     end else begin
       fork
-        mem_wr_sub(ptr, offset, data, timeout_ns, check, map);
+        mem_wr_sub(ptr, offset, data, timeout_ns, check, map, user_ftdr);
       join_none
       // Add #0 to ensure that this thread starts executing before any subsequent call
       #0;
     end
   endtask : mem_wr
 
-  task automatic mem_wr_sub(input uvm_mem     ptr,
-                            input int         offset,
-                            input bit[31:0]   data,
-                            input uint        timeout_ns = default_timeout_ns,
-                            input uvm_check_e check = default_csr_check,
-                            input uvm_reg_map map = null);
+  task automatic mem_wr_sub(input uvm_mem             ptr,
+                            input int                 offset,
+                            input bit[31:0]           data,
+                            input uint                timeout_ns = default_timeout_ns,
+                            input uvm_check_e         check = default_csr_check,
+                            input uvm_reg_map         map = null,
+                            input  uvm_reg_frontdoor  user_ftdr = default_user_frontdoor);
      fork
       begin : isolation_fork
         uvm_status_e status;
@@ -630,7 +661,10 @@
         fork
           begin
             increment_outstanding_access();
+            if (user_ftdr != null) ptr.set_frontdoor(user_ftdr);
             ptr.write(.status(status), .offset(offset), .value(data), .map(map), .prior(100));
+            // TODO: need to remove the frontdoor to switch back to the default,
+            // but this doesn't work: if (user_ftdr != null) ptr.set_frontdoor(null);
             if (check == UVM_CHECK && !under_reset) begin
               `DV_CHECK_EQ(status, UVM_IS_OK,
                            $sformatf("trying to write mem %0s", ptr.get_full_name()),
@@ -639,9 +673,9 @@
             decrement_outstanding_access();
           end
           begin
-            wait_timeout(timeout_ns, msg_id,
-                         $sformatf("Timeout waiting to mem_wr %0s (addr=0x%0h)",
-                                   ptr.get_full_name(), offset));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id,
+                             $sformatf("Timeout waiting to mem_wr %0s (addr=0x%0h)",
+                                       ptr.get_full_name(), offset))
           end
         join_any
         disable fork;
diff --git a/hw/dv/sv/csr_utils/doc/index.md b/hw/dv/sv/csr_utils/doc/index.md
deleted file mode 100644
index 948a21d..0000000
--- a/hw/dv/sv/csr_utils/doc/index.md
+++ /dev/null
@@ -1,160 +0,0 @@
----
-title: "CSR utilities"
----
-
-This csr_utils folder intends to implement CSR related methods and test sequences for DV
-to share across all testbenches.
-
-### CSR utility package
-`csr_utils_pkg` provides common methods and properties to support and manage CSR accesses
-and CSR related test sequences.
-
-#### Global types and variables
-All common types and variables are defined at this package level. Examples are:
-```systemverilog
-  uint       outstanding_accesses        = 0;
-  uint       default_timeout_ns          = 1_000_000;
-```
-
-##### Outstanding_accesses
-`csr_utils_pkg` used an internal variable to store the number of accesses
-(read or write) that have not yet completed. This variable is shared among all methods of
-register reading and writing. Directly accessing this variable is discouraged. Instead,
-the following methods are used to control this variable to keep track of non-blocking
-accesses made in the testbench:
-```systemverilog
-  function automatic void increment_outstanding_access();
-    outstanding_accesses++;
-  endfunction
-
-  function automatic void decrement_outstanding_access();
-    outstanding_accesses--;
-  endfunction
-
-  task automatic wait_no_outstanding_access();
-    wait(outstanding_accesses == 0);
-  endtask
-
-  function automatic void clear_outstanding_access();
-    outstanding_accesses = 0;
-  endfunction
-```
-
-##### CSR spinwait
-One of the commonly used tasks in `csr_utils_pkg` is `csr_spinwait`. This task
-can poll a CSR or CSR field continuously or periodically until it reads out the
-expected value. This task also has a timeout check in case due to DUT or testbench
-issue, the CSR or CSR field never returns the expected value.
-Example below uses the `csr_spinwait` to wait until the CSR `fifo_status` field
-`fifo_full` reaches value bit 1:
-```systemverilog
-csr_spinwait(.ptr(ral.status.fifo_full), .exp_data(1'b0));
-```
-
-##### Read and check all CSRs
-The purpose of the `read_and_check_all_csrs` task is to read all valid CSRs from
-the given `uvm_reg_block` and check against their expected values from RAL. This
-task is primarily implemented to use after reset, to make sure all the CSRs are
-being reset to the default value.
-
-##### Under_reset
-Due to `csr_utils_pkg` is not connected to any interface, methods inside
-this package are not able to get reset information. Current the `under_reset`
-bit is declared with two functions:
-```systemverilog
-function automatic void reset_asserted();
-  under_reset = 1;
-endfunction
-
-function automatic void reset_deasserted();
-  under_reset = 0;
-endfunction
-```
-This reset information is updated in `dv_lib/dv_base_vseq.sv`. When the
-`apply_reset` task is triggered, it will set and reset the `under_reset` bit
-via the functions above.
-
-#### Global CSR util methods
-##### Global methods for CSR and MEM attributes
-This package provides methods to access CSR or Memory attributes, such as address,
-value, etc. Examples are:
- * `get_csr_addrs`
- * `get_mem_addr_ranges`
- * `decode_csr_or_field`
-
-##### Global methods for CSR access
-The CSR access methods are based on `uvm_reg` methods, such as `uvm_reg::read()`,
-`uvm_reg::write()`, `uvm_reg::update()`. For all CSR methods, user can
-pass either a register or a field handle. Examples are:
- * `csr_rd_check`: Given the uvm_reg or uvm_reg_field object, this method will
-   compare the CSR value with the expected value (given as an input) or with
-   the RAL mirrored value
- * `csr_update`: Given the uvm_reg object, this method will update the value of the
-   register in DUT to match the desired value
-
-To enhance the usability, these methods support CSR blocking, non-blocking
-read/write, and a timeout checking.
- * A blocking thread will not execute the next sequence until the current CSR
-   access is finished
- * A non-blocking thread allows multiple CSR accesses to be issued back-to-back
-   without waiting for the response
- * A timeout check will discard the ongoing CSR access by disabling the forked
-   thread and will throw a UVM_ERROR once the process exceeds the max timeout setting
-
-### CSR sequence library
-`csr_seq_lib.sv` provides common CSR related test sequences to share across all testbenches.
-These test sequences are based off the standard sequences provided in UVM1.2 RAL.
-The parent class (DUT-specific test or sequence class) that creates them needs to provide them
-with the DUT RAL model. The list of CSRs are then extracted from the RAL model to performs the checks.
-In addition, the test sequences provide an ability to exclude a CSR from writes or reads (or both)
-depending on the behavior of the CSR in the design. This is explained more in the
-[CSR exclusion methodology](#csr-exclusion-methodology) section below.
-All CSR accesses in these sequences are made non-blocking to ensure back-to-back scenarios
-are exercised.
-Supported CSR test sequences are:
- * `csr_hw_reset`: Write all CSRs with random values and then reset the DUT.
-   After reset, read all CSRs and compare with expected values
- * `csr_rw`: Write a randomly selected CSRs, then read out the updated
-   CSR or CSR field and compare with expected value
- * `csr_bit_bash`: Randomly select a CSR and write 1's and 0's to
-   every bit, then read the CSR to compare with expected value
- * `csr_aliasing`: Randomly write a CSR, then read all CSRs to
-   verify that only the CSR that was written was updated
- * `mem_walk`: Write and read all valid addresses in the memory. Compare
-   the read results with the expected values
-
-### CSR exclusion methodology
-The CSR test sequences listed above intend to perform a basic check to CSR
-read/write accesses, but do not intend to check specific DUT functionalities. Thus the
-sequences might need to exclude reading or writing certain CSRs depending on the
-specific testbench.
-`csr_excl_item` is a class that supports adding exclusions to CSR test sequences.
-Examples of useful functions in this class are:
-* `add_excl`: Add exclusions to the CSR test sequences. This function has two inputs:
-  - Exclusion scope: A hierarchical path name at all levels including block,
-    CSR, and field. This input supports * and ? wildcards for glob style matching
-  - CSR_exclude type: An enumeration defined as below:
-    ```systemverilog
-    typedef enum bit[2:0] {
-      CsrNoExcl         = 3'b000, // no exclusions
-      CsrExclInitCheck  = 3'b001, // exclude csr from init val check
-      CsrExclWriteCheck = 3'b010, // exclude csr from write-read check
-      CsrExclCheck      = 3'b011, // exclude csr from init or write-read check
-      CsrExclWrite      = 3'b100, // exclude csr from write
-      CsrExclAll        = 3'b111  // exclude csr from init or write or write-read check
-    } csr_excl_type_e;
-    ```
-
-  One example to use this function in HMAC to exclude all CSRs or fields with
-  names starting with "key":
-  ```systemverilog
-  csr_excl.add_excl({scope, ".", "key?"}, CsrExclWrite);
-  ```
-
-* `has_excl`: Check if the CSR has a match in the existing exclusions loopup,
-  and is not intended to use externally
-
-### CSR sequence framework
-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/csrng_agent/README.md b/hw/dv/sv/csrng_agent/README.md
new file mode 100644
index 0000000..2baca95
--- /dev/null
+++ b/hw/dv/sv/csrng_agent/README.md
@@ -0,0 +1,5 @@
+# CSRNG UVM Agent
+
+CSRNG UVM Agent is extended from DV library agent classes.
+Models host or device csrng functionality.
+Contains 2 push_pull_agents, one for cmd_req and one for genbits.
diff --git a/hw/dv/sv/csrng_agent/csrng_agent.sv b/hw/dv/sv/csrng_agent/csrng_agent.sv
index fa5bd61..eee4f07 100644
--- a/hw/dv/sv/csrng_agent/csrng_agent.sv
+++ b/hw/dv/sv/csrng_agent/csrng_agent.sv
@@ -33,7 +33,7 @@
     cfg.m_cmd_push_agent_cfg.is_active   = cfg.is_active;
     cfg.m_cmd_push_agent_cfg.agent_type  = PushAgent;
     cfg.m_cmd_push_agent_cfg.if_mode     = cfg.if_mode;
-    cfg.m_cmd_push_agent_cfg.zero_delays = cfg.cmd_ack_zero_delays;
+    cfg.m_cmd_push_agent_cfg.zero_delays = cfg.cmd_zero_delays;
 
     m_genbits_push_agent = push_pull_agent#(csrng_pkg::FIPS_GENBITS_BUS_WIDTH)::type_id::
                            create("m_genbits_push_agent", this);
@@ -46,6 +46,8 @@
       cfg.m_genbits_push_agent_cfg.device_delay_min = cfg.min_genbits_rdy_dly;
       cfg.m_genbits_push_agent_cfg.device_delay_max = cfg.max_genbits_rdy_dly;
     end else begin
+      cfg.m_cmd_push_agent_cfg.device_delay_min = cfg.min_cmd_rdy_dly;
+      cfg.m_cmd_push_agent_cfg.device_delay_max = cfg.max_cmd_rdy_dly;
       cfg.has_req_fifo = 1;
       cfg.m_genbits_push_agent_cfg.if_mode = dv_utils_pkg::Host;
     end
diff --git a/hw/dv/sv/csrng_agent/csrng_agent_cfg.sv b/hw/dv/sv/csrng_agent/csrng_agent_cfg.sv
index 987a268..70392ae 100644
--- a/hw/dv/sv/csrng_agent/csrng_agent_cfg.sv
+++ b/hw/dv/sv/csrng_agent/csrng_agent_cfg.sv
@@ -18,7 +18,9 @@
   uint   min_cmd_ack_dly, max_cmd_ack_dly,
          min_genbits_dly, max_genbits_dly,
          min_genbits_rdy_dly, max_genbits_rdy_dly,
+         min_cmd_rdy_dly, max_cmd_rdy_dly,
          reseed_cnt, generate_cnt, generate_between_reseeds_cnt;
-  bit    cmd_ack_zero_delays;
+  bit    cmd_zero_delays;
+  bit    under_reset;
 
 endclass
diff --git a/hw/dv/sv/csrng_agent/csrng_agent_cov.sv b/hw/dv/sv/csrng_agent/csrng_agent_cov.sv
index 665317a..44f9f74 100644
--- a/hw/dv/sv/csrng_agent/csrng_agent_cov.sv
+++ b/hw/dv/sv/csrng_agent/csrng_agent_cov.sv
@@ -2,14 +2,154 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
+`define CSRNG_GLEN_COVBIN \
+    csrng_glen: coverpoint item.glen { \
+      // TODO: EDN testbench currently sends a max of 64 endpoints, which is 64/4 genbits. \
+      bins glens[4] = {[1:16]}; \
+    }
+
+`define CSRNG_STS_COVBIN \
+    csrng_sts: coverpoint sts { \
+      bins pass = {0}; \
+      bins fail = {1}; \
+    }
+
+// covergroups
+// Depends on whether the agent is device or host mode, the "csrng_cmd_cp" are slightly different:
+// In device mode: acmd INV, GENB, GENU are in the illegal bin.
+covergroup device_cmd_cg with function sample(csrng_item item, bit sts);
+  option.name         = "csrng_device_cmd_cg";
+  option.per_instance = 1;
+
+  csrng_cmd_cp: coverpoint item.acmd {
+    bins ins = {INS};
+    bins res = {RES};
+    bins gen = {GEN};
+    bins uni = {UNI};
+    illegal_bins il = default;
+  }
+  csrng_clen_cp: coverpoint item.clen {
+    bins zero = {0};
+    bins non_zero_bins[2] = {[1:15]};
+  }
+  // TODO: do not use enum to sample non-true/false data
+  csrng_flag_cp: coverpoint item.flags {
+    bins mubi_true = {MuBi4True};
+    bins mubi_false = {MuBi4False};
+  }
+  csrng_sts: coverpoint sts {
+    bins pass = {0};
+    bins fail = {1};
+  }
+
+  csrng_cmd_cross: cross csrng_cmd_cp, csrng_clen_cp, csrng_sts, csrng_flag_cp {
+    // Only a value of zero should be used for clen when UNI command is used.
+    ignore_bins uni_clen = binsof(csrng_cmd_cp.uni) && binsof(csrng_clen_cp.non_zero_bins);
+  }
+endgroup
+
+covergroup host_cmd_cg with function sample(csrng_item item, bit sts);
+  option.name         = "csrng_host_cmd_cg";
+  option.per_instance = 1;
+
+  csrng_cmd_cp: coverpoint item.acmd {
+    bins inv = {INV};
+    bins ins = {INS};
+    bins res = {RES};
+    bins gen = {GEN};
+    bins upd = {UPD};
+    bins uni = {UNI};
+    bins genb = {GENB};
+    bins genu = {GENU};
+    illegal_bins il = default;
+  }
+  csrng_clen_cp: coverpoint item.clen {
+    bins zero = {0};
+    bins other_bins[2] = {[1:15]};
+  }
+  // TODO: do not use enum to sample non-true/false data
+  csrng_flag_cp: coverpoint item.flags {
+    bins mubi_true = {MuBi4True};
+    bins mubi_false = {MuBi4False};
+  }
+  csrng_sts: coverpoint sts {
+    bins pass = {0};
+    bins fail = {1};
+  }
+
+  csrng_cmd_clen_flag_cross: cross csrng_cmd_cp, csrng_clen_cp, csrng_flag_cp;
+
+  csrng_cmd_clen_flag_sts_cross: cross csrng_cmd_cp, csrng_clen_cp, csrng_flag_cp, csrng_sts {
+    // Illegal commands (INV, GENB, GENU) don't get a response, thus don't have a status.
+    ignore_bins illegal_cmds = binsof(csrng_cmd_cp) intersect {INV, GENB, GENU};
+    // All legal commands get a response, and the status must be OK.  Legal commands with an error
+    // status are thus illegal.
+    illegal_bins legal_cmds_with_error_sts = !binsof(csrng_cmd_cp) intersect {INV, GENB, GENU}
+                                             with (csrng_sts);
+  }
+
+endgroup
+
+// Depends on whether the agent is device or host mode, the "csrng_genbits_cross" are slightly
+// different:
+// In device mode: csrng agent can drive `sts` to pass or fail randomly.
+// In host mode: DUT will also return pass for genbits data.
+covergroup device_genbits_cg with function sample(csrng_item item, bit sts);
+  option.name         = "csrng_device_genbits_cg";
+  option.per_instance = 1;
+
+  `CSRNG_GLEN_COVBIN
+  `CSRNG_STS_COVBIN
+
+  csrng_genbits_cross: cross csrng_glen, csrng_sts;
+endgroup
+
+covergroup host_genbits_cg with function sample(csrng_item item, bit sts);
+  option.name         = "csrng_host_genbits_cg";
+  option.per_instance = 1;
+
+  `CSRNG_GLEN_COVBIN
+  `CSRNG_STS_COVBIN
+
+  csrng_genbits_cross: cross csrng_glen, csrng_sts {
+    // Generate may not return fail as status.
+    illegal_bins sts_fail = binsof(csrng_sts) intersect {1};
+  }
+endgroup
+
 class csrng_agent_cov extends dv_base_agent_cov#(csrng_agent_cfg);
+  host_cmd_cg       m_host_cmd_cg;
+  device_cmd_cg     m_device_cmd_cg;
+  host_genbits_cg   m_host_genbits_cg;
+  device_genbits_cg m_device_genbits_cg;
+
   `uvm_component_utils(csrng_agent_cov)
+  `uvm_component_new
 
-  // covergroups
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
 
-  function new(string name, uvm_component parent);
-    super.new(name, parent);
-    // instantiate all covergroups here
+    if (cfg.if_mode == dv_utils_pkg::Device) begin
+      m_device_cmd_cg = new();
+      m_device_genbits_cg = new();
+    end else begin
+      m_host_cmd_cg = new();
+      m_host_genbits_cg = new();
+    end
+  endfunction
+
+  function void sample_csrng_cmds(csrng_item item, bit sts);
+    if (cfg.if_mode == dv_utils_pkg::Device) begin
+      m_device_cmd_cg.sample(item, cfg.vif.cmd_rsp.csrng_rsp_sts);
+      if (item.acmd == csrng_pkg::GEN) begin
+        m_device_genbits_cg.sample(item, cfg.vif.cmd_rsp.csrng_rsp_sts);
+      end
+    end else begin
+      m_host_cmd_cg.sample(item, cfg.vif.cmd_rsp.csrng_rsp_sts);
+      if (item.acmd == csrng_pkg::GEN) begin
+        m_host_genbits_cg.sample(item, cfg.vif.cmd_rsp.csrng_rsp_sts);
+      end
+    end
   endfunction
 
 endclass
diff --git a/hw/dv/sv/csrng_agent/csrng_agent_pkg.sv b/hw/dv/sv/csrng_agent/csrng_agent_pkg.sv
index 3226ccb..c0822a9 100644
--- a/hw/dv/sv/csrng_agent/csrng_agent_pkg.sv
+++ b/hw/dv/sv/csrng_agent/csrng_agent_pkg.sv
@@ -9,6 +9,7 @@
   import dv_lib_pkg::*;
   import push_pull_agent_pkg::*;
   import csrng_pkg::*;
+  import prim_mubi_pkg::*;
 
   // macro includes
   `include "uvm_macros.svh"
diff --git a/hw/dv/sv/csrng_agent/csrng_device_driver.sv b/hw/dv/sv/csrng_agent/csrng_device_driver.sv
index 6d5047a..997057a 100644
--- a/hw/dv/sv/csrng_agent/csrng_device_driver.sv
+++ b/hw/dv/sv/csrng_agent/csrng_device_driver.sv
@@ -21,6 +21,7 @@
 
   // drive trans received from sequencer
   virtual task get_and_drive();
+    wait (cfg.under_reset == 0);
     forever begin
       seq_item_port.get_next_item(req);
       `uvm_info(`gfn, $sformatf("Received item: %s", req.convert2string()), UVM_HIGH)
@@ -28,14 +29,20 @@
       rsp.set_id_info(req);
       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(
           cmd_ack_dly, cmd_ack_dly inside {cfg.min_cmd_ack_dly, cfg.max_cmd_ack_dly};)
-      repeat(cmd_ack_dly) @(cfg.vif.device_cb);
-      cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_ack <= 1'b1;
-      `DV_CHECK_STD_RANDOMIZE_FATAL(rsp_sts)
-      cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_sts <= rsp_sts;
-      @(cfg.vif.device_cb);
-      cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_ack <= 1'b0;
-      cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_sts <= 1'b0;
-      `uvm_info(`gfn, "item sent", UVM_HIGH)
+      `DV_SPINWAIT_EXIT(
+          repeat(cmd_ack_dly) @(cfg.vif.device_cb);
+          cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_ack <= 1'b1;
+          `DV_CHECK_STD_RANDOMIZE_FATAL(rsp_sts)
+          cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_sts <= rsp_sts;
+          @(cfg.vif.device_cb);
+          cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_ack <= 1'b0;
+          cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_sts <= 1'b0;,
+          wait (cfg.under_reset == 1);)
+
+      // Write ack bit again in case the race condition with `reset_signals`.
+      if (cfg.under_reset) cfg.vif.device_cb.cmd_rsp_int.csrng_rsp_ack <= 1'b0;
+
+      `uvm_info(`gfn, cfg.under_reset ? "item sent during reset" : "item sent", UVM_HIGH)
       seq_item_port.item_done(rsp);
     end
   endtask
diff --git a/hw/dv/sv/csrng_agent/csrng_if.sv b/hw/dv/sv/csrng_agent/csrng_if.sv
index e4291fa..926c289 100644
--- a/hw/dv/sv/csrng_agent/csrng_if.sv
+++ b/hw/dv/sv/csrng_agent/csrng_if.sv
@@ -69,4 +69,15 @@
     while (!mon_cb.cmd_rsp.csrng_rsp_ack);
   endtask
 
+  task automatic wait_cmd_ack_or_rst_n();
+    // Immediately return if reset is currently not inactive.
+    if (rst_n !== 1'b1) return;
+    // Otherwise wait for cmd_ack or negedge of reset, whichever comes first.
+    fork
+      wait_cmd_ack();
+      do @(clk); while (rst_n === 1'b1); // @(negedge rst_n) does not trigger for some reason
+    join_any
+    disable fork;
+  endtask
+
 endinterface
diff --git a/hw/dv/sv/csrng_agent/csrng_item.sv b/hw/dv/sv/csrng_agent/csrng_item.sv
index 73536f3..1b31163 100644
--- a/hw/dv/sv/csrng_agent/csrng_item.sv
+++ b/hw/dv/sv/csrng_agent/csrng_item.sv
@@ -10,7 +10,8 @@
   `uvm_object_new
 
   rand acmd_e       acmd;
-  rand bit [3:0]    clen, flags;
+  rand mubi4_t      flags;
+  rand bit [3:0]    clen;
   rand bit [18:0]   glen;
   rand bit [31:0]   cmd_data_q[$];
 
@@ -27,7 +28,7 @@
   }
 
   constraint c_flags {
-    flags inside {[0:1]};
+    flags inside {MuBi4True, MuBi4False};
   }
 
   // TODO: Try glen > 32, glen = 0 on GEN cmd
diff --git a/hw/dv/sv/csrng_agent/csrng_monitor.sv b/hw/dv/sv/csrng_agent/csrng_monitor.sv
index 9f9ea10..60cdc86 100644
--- a/hw/dv/sv/csrng_agent/csrng_monitor.sv
+++ b/hw/dv/sv/csrng_agent/csrng_monitor.sv
@@ -20,8 +20,6 @@
   uvm_tlm_analysis_fifo#(push_pull_item#(.HostDataWidth(csrng_pkg::CSRNG_CMD_WIDTH)))
       csrng_cmd_fifo;
 
-  bit in_reset;
-
   `uvm_component_new
 
   function void build_phase(uvm_phase phase);
@@ -47,44 +45,59 @@
   virtual protected task handle_reset();
     forever begin
       @(negedge cfg.vif.rst_n);
-      in_reset = 1;
+      cfg.under_reset = 1;
+      csrng_cmd_fifo.flush();
       // TODO: sample any reset-related covergroups
       @(posedge cfg.vif.rst_n);
-      in_reset = 0;
+      cfg.under_reset = 0;
     end
   endtask
 
   virtual task collect_valid_trans();
-    push_pull_item#(.HostDataWidth(csrng_pkg::CSRNG_CMD_WIDTH))  item;
-    csrng_item   cs_item;
-
-    cs_item = csrng_item::type_id::create("cs_item");
-
     forever begin
-      for (int i = 0; i <= cs_item.clen; i++) begin
-        csrng_cmd_fifo.get(item);
-        `uvm_info(`gfn, $sformatf("Received cs_item: %s", item.convert2string()), UVM_HIGH)
-        if (i == 0) begin
-          cs_item.acmd  = acmd_e'(item.h_data[3:0]);
-          cs_item.clen  = item.h_data[7:4];
-          cs_item.flags = item.h_data[11:8];
-          cs_item.glen  = item.h_data[30:12];
-          cs_item.cmd_data_q.delete();
-        end
-        else begin
-          cs_item.cmd_data_q.push_back(item.h_data);
-        end
-      end
-      if (cs_item.acmd == csrng_pkg::GEN) begin
-        for (int i = 0; i < cs_item.glen; i++) begin
-          @(posedge cfg.vif.mon_cb.cmd_rsp.genbits_valid);
-          cs_item.genbits_q.push_back(cfg.vif.mon_cb.cmd_rsp.genbits_bus);
-        end
-      end
-      cfg.vif.wait_cmd_ack();
-      `uvm_info(`gfn, $sformatf("Writing analysis_port: %s", cs_item.convert2string()), UVM_HIGH)
-      analysis_port.write(cs_item);
-    end
+      wait (cfg.under_reset == 0);
+
+      `DV_SPINWAIT_EXIT(
+          push_pull_item#(.HostDataWidth(csrng_pkg::CSRNG_CMD_WIDTH))  item;
+          csrng_item   cs_item = csrng_item::type_id::create("cs_item");
+          for (int i = 0; i <= cs_item.clen; i++) begin
+            csrng_cmd_fifo.get(item);
+            `uvm_info(`gfn, $sformatf("Received cs_item: %s", item.convert2string()), UVM_HIGH)
+            if (i == 0) begin
+              cs_item.acmd  = acmd_e'(item.h_data[3:0]);
+              cs_item.clen  = item.h_data[7:4];
+              if (item.h_data[11:8] == MuBi4True) begin
+                cs_item.flags = MuBi4True;
+              end else begin
+                cs_item.flags = MuBi4False;
+              end
+              cs_item.glen  = item.h_data[30:12];
+              cs_item.cmd_data_q.delete();
+            end else begin
+              cs_item.cmd_data_q.push_back(item.h_data);
+            end
+          end
+          if (cs_item.acmd == csrng_pkg::GEN) begin
+            for (int i = 0; i < cs_item.glen; i++) begin
+              @(posedge cfg.vif.mon_cb.cmd_rsp.genbits_valid);
+              cs_item.genbits_q.push_back(cfg.vif.mon_cb.cmd_rsp.genbits_bus);
+            end
+          end
+          // Illegal commands fail without getting acknowledged.
+          if (!(cs_item.acmd inside {csrng_pkg::INV, csrng_pkg::GENB,
+                                     csrng_pkg::GENU})) begin
+            cfg.vif.wait_cmd_ack_or_rst_n();
+          end
+          `uvm_info(`gfn, $sformatf("Writing analysis_port: %s", cs_item.convert2string()),
+                    UVM_HIGH)
+          analysis_port.write(cs_item);
+          if (cfg.en_cov) cov.sample_csrng_cmds(cs_item, cfg.vif.cmd_rsp.csrng_rsp_sts);
+
+          ,
+
+          // Wait reset
+          wait (cfg.under_reset);)
+     end
   endtask
 
   // This task is only used for device agents responding
@@ -97,38 +110,51 @@
   virtual protected task collect_request();
     csrng_item   cs_item;
     forever begin
+      wait(cfg.under_reset == 0);
       @(cfg.vif.cmd_push_if.mon_cb);
-      if (cfg.vif.cmd_push_if.mon_cb.valid) begin
+      if ((cfg.vif.cmd_push_if.mon_cb.valid) && (cfg.vif.cmd_push_if.mon_cb.ready)) begin
         // TODO: sample any covergroups
         // TODO: Implement suggestion in PR #5456
         cs_item = csrng_item::type_id::create("cs_item");
         cs_item.acmd = acmd_e'(cfg.vif.mon_cb.cmd_req.csrng_req_bus[3:0]);
         cs_item.clen = cfg.vif.mon_cb.cmd_req.csrng_req_bus[7:4];
-        cs_item.flags = cfg.vif.mon_cb.cmd_req.csrng_req_bus[11:8];
-        if (cs_item.acmd == csrng_pkg::RES) begin
-          cfg.reseed_cnt += 1;
+        if (cfg.vif.mon_cb.cmd_req.csrng_req_bus[11:8] == MuBi4True) begin
+          cs_item.flags = MuBi4True;
+        end
+        else begin
+          cs_item.flags = MuBi4False;
         end
         if (cs_item.acmd == csrng_pkg::GEN) begin
-          cfg.generate_cnt += 1;
-          if (cfg.reseed_cnt == 1) begin
-            cfg.generate_between_reseeds_cnt += 1;
-          end
           cs_item.glen = cfg.vif.mon_cb.cmd_req.csrng_req_bus[30:12];
         end
-        for (int i = 0; i < cs_item.clen; i++) begin
-          do begin
-            @(cfg.vif.cmd_push_if.mon_cb);
-          end
-          while (!(cfg.vif.cmd_push_if.mon_cb.valid) || !(cfg.vif.cmd_push_if.mon_cb.ready));
-          cs_item.cmd_data_q.push_back(cfg.vif.mon_cb.cmd_req.csrng_req_bus);
-        end
+
+        `DV_SPINWAIT_EXIT(
+            for (int i = 0; i < cs_item.clen; i++) begin
+              do begin
+                @(cfg.vif.cmd_push_if.mon_cb);
+              end
+              while (!(cfg.vif.cmd_push_if.mon_cb.valid) || !(cfg.vif.cmd_push_if.mon_cb.ready));
+              cs_item.cmd_data_q.push_back(cfg.vif.mon_cb.cmd_req.csrng_req_bus);
+            end,
+            wait(cfg.under_reset))
+
         `uvm_info(`gfn, $sformatf("Writing req_analysis_port: %s", cs_item.convert2string()),
              UVM_HIGH)
         req_analysis_port.write(cs_item);
         // After picking up a request, wait until a response is sent before
         // detecting another request, as this is not a pipelined protocol.
         `DV_SPINWAIT_EXIT(while (!cfg.vif.mon_cb.cmd_rsp.csrng_rsp_ack) @(cfg.vif.mon_cb);,
-                          wait(in_reset))
+                          wait(cfg.under_reset))
+
+        // Increment the counters only if ack is sent.
+        if (!cfg.under_reset) begin
+          if (cs_item.acmd == csrng_pkg::RES) cfg.reseed_cnt += 1;
+          if (cs_item.acmd == csrng_pkg::GEN) begin
+            cfg.generate_cnt += 1;
+            if (cfg.reseed_cnt == 1) cfg.generate_between_reseeds_cnt += 1;
+          end
+        end
+
         rsp_sts_ap.write(cfg.vif.cmd_rsp.csrng_rsp_sts);
        end
     end
diff --git a/hw/dv/sv/csrng_agent/doc/index.md b/hw/dv/sv/csrng_agent/doc/index.md
deleted file mode 100644
index 84fd750..0000000
--- a/hw/dv/sv/csrng_agent/doc/index.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-title: "CSRNG UVM Agent"
----
-
-CSRNG UVM Agent is extended from DV library agent classes.
-Models host or device csrng functionality.
-Contains 2 push_pull_agents, one for cmd_req and one for genbits.
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg.core b/hw/dv/sv/dv_base_reg/dv_base_reg.core
index 39a147b..d961118 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg.core
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg.core
@@ -19,6 +19,7 @@
       - dv_base_reg_block.sv: {is_include_file: true}
       - dv_base_reg_map.sv: {is_include_file: true}
       - dv_base_lockable_field_cov.sv: {is_include_file: true}
+      - dv_base_shadowed_field_cov.sv: {is_include_file: true}
       - dv_base_mubi_cov.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg.sv b/hw/dv/sv/dv_base_reg/dv_base_reg.sv
index 24864df..5daf3f3 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg.sv
@@ -35,6 +35,14 @@
     atomic_en_shadow_wr = new(1);
   endfunction : new
 
+  // Create this register and its fields' IP-specific functional coverage.
+  function void create_cov();
+    dv_base_reg_field fields[$];
+    get_dv_base_reg_fields(fields);
+    foreach(fields[i]) fields[i].create_cov();
+    // Create register-specific covergroups here.
+  endfunction
+
   // this is similar to get_name, but it gets the
   // simple name of the aliased register instead.
   function string get_alias_name ();
@@ -200,8 +208,6 @@
   // update local variables used for the special regs.
   // - shadow register: shadow reg won't be updated until the second write has no error
   // - lock register: if wen_fld is set to 0, change access policy to all the lockable_flds
-  // TODO: create an `enable_field_access_policy` variable and set the template code during
-  // automation.
   virtual function void pre_do_predict(uvm_reg_item rw, uvm_predict_e kind);
 
     // Skip updating shadow value or access type if:
@@ -235,6 +241,7 @@
              flds[i].update_shadowed_val(~wr_data);
           end else begin
             shadow_update_err = 1;
+            flds[i].sample_shadow_field_cov(.update_err(1));
           end
         end
       end
@@ -278,7 +285,7 @@
         return;
       end else begin
         `uvm_info(`gfn, $sformatf(
-            "Shadow reg %0s has update error, update rw.value from %0h to %0h",
+            "Update shadow reg %0s rw.value from %0h to %0h",
             get_name(), rw.value[0], get_committed_val()), UVM_HIGH)
         rw.value[0] = get_committed_val();
       end
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg_block.sv b/hw/dv/sv/dv_base_reg/dv_base_reg_block.sv
index 7adc55b..84d7dc1 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg_block.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg_block.sv
@@ -56,6 +56,10 @@
   // Custom RAL models may support sub-word CSR writes smaller than CSR width.
   protected bit supports_sub_word_csr_writes = 1'b0;
 
+  // Enables functional coverage of comportable IP-specific specialized registers, such as regwen
+  // and mubi. This flag can only be disabled before invoking `create_dv_reg_cov`.
+  protected bit en_dv_reg_cov = 1;
+
   bit has_unmapped_addrs;
   addr_range_t unmapped_addr_ranges[$];
 
@@ -113,6 +117,20 @@
     `uvm_fatal(`gfn, "this method is not supposed to be called directly!")
   endfunction
 
+  // This function is invoked at the end of `build` method in uvm_reg_base.sv template to create
+  // IP-specific functional coverage for this block and its registers and fields.
+  function void create_cov();
+    dv_base_reg_block blks[$];
+    dv_base_reg regs[$];
+
+    get_dv_base_reg_blocks(blks);
+    foreach (blks[i]) blks[i].create_cov();
+
+    get_dv_base_regs(regs);
+    foreach (regs[i]) regs[i].create_cov();
+    // Create block-specific covergroups here.
+  endfunction
+
   function void get_dv_base_reg_blocks(ref dv_base_reg_block blks[$]);
     uvm_reg_block uvm_blks[$];
     this.get_blocks(uvm_blks);
@@ -143,6 +161,12 @@
     end
   endfunction
 
+  function bit has_shadowed_regs();
+    dv_base_reg regs[$];
+    get_shadowed_regs(regs);
+    return (regs.size() > 0);
+  endfunction
+
   // Internal function, used to compute the address mask for this register block.
   //
   // This is quite an expensive computation, so we memoize the results in addr_mask[map].
@@ -153,10 +177,14 @@
     uvm_reg_block  blocks[$];
     int unsigned   alignment;
 
-    // TODO: assume IP only contains 1 reg block, find a better way to handle chip-level and IP
-    // with multiple reg blocks
+    // Assumption:
+    // Only chip-level RAL has multiple sub reg_blocks. Its addr_mask is '1
+    // In block-level, we have one RAL for one TLUL interface. We don't put multiple reg_block in a
+    // RAL, as UVM RAL can't handle the case that each sub-block uses a different map:
+    //  - ral.blk1 -> use map_TL1
+    //  - ral.blk2 -> use map_TL2
     get_blocks(blocks);
-    if (blocks.size > 0) begin
+    if (blocks.size > 0) begin // if true, this is a chip-level RAL
       addr_mask[map] = '1;
       return;
     end
@@ -405,4 +433,16 @@
     return retval;
   endfunction
 
+  function void set_en_dv_reg_cov(bit val);
+    uvm_reg csrs[$];
+    get_registers(csrs);
+    `DV_CHECK_FATAL(!csrs.size(),
+        "Cannot set en_dv_base_reg_cov when covergroups are built already!")
+    en_dv_reg_cov = val;
+  endfunction
+
+  function bit get_en_dv_reg_cov();
+    return en_dv_reg_cov;
+  endfunction
+
 endclass
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv b/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
index 8b401a6..ea23467 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg_field.sv
@@ -17,9 +17,15 @@
   // This is used for get_field_by_name
   string alias_name = "";
 
+  // Default mubi_width = 0 indicates this register field is not a mubi type.
+  protected int mubi_width;
+
   // variable for mubi coverage, which is only created when this is a mubi reg
   dv_base_mubi_cov mubi_cov;
 
+  // variable for shadowed coverage, which is only created when this is a shadowed reg
+  local dv_base_shadowed_field_cov shadowed_cov;
+
   `uvm_object_utils(dv_base_reg_field)
   `uvm_object_new
 
@@ -83,10 +89,18 @@
     uvm_reg_data_t field_val = rw.value[0] & ((1 << get_n_bits())-1);
 
     // update intr_state mirrored value if this is an intr_test reg
-    if (is_intr_test_fld) begin
+    // if kind is UVM_PREDICT_DIRECT or UVM_PREDICT_READ, super.do_predict can handle
+    if (kind == UVM_PREDICT_WRITE && is_intr_test_fld) begin
       uvm_reg_field intr_state_fld = get_intr_state_field();
+      bit predict_val;
+      if (intr_state_fld.get_access == "RO") begin // status interrupt
+        predict_val = field_val;
+      end else begin // regular W1C interrupt
+        `DV_CHECK_STREQ(intr_state_fld.get_access, "W1C")
+        predict_val = field_val | `gmv(intr_state_fld);
+      end
       // use UVM_PREDICT_READ to avoid uvm_warning due to UVM_PREDICT_DIRECT
-      void'(intr_state_fld.predict(field_val | `gmv(intr_state_fld), .kind(UVM_PREDICT_READ)));
+      void'(intr_state_fld.predict(predict_val, .kind(UVM_PREDICT_READ)));
     end
 
     super.do_predict(rw, kind, be);
@@ -136,7 +150,6 @@
     foreach (flds[i]) begin
       lockable_flds.push_back(flds[i]);
       flds[i].regwen_fld = this;
-      flds[i].create_lockable_fld_cov();
     end
   endfunction
 
@@ -144,9 +157,20 @@
     lockable_field_cov = dv_base_lockable_field_cov::type_id::create(`gfn);
   endfunction
 
-  function void create_mubi_cov(int mubi_width);
+  function void create_mubi_cov(int width);
     mubi_cov = dv_base_mubi_cov::type_id::create(`gfn);
-    mubi_cov.create_cov(mubi_width);
+    mubi_cov.create_cov(width);
+  endfunction
+
+  function void create_shadowed_fld_cov();
+    shadowed_cov = dv_base_shadowed_field_cov::type_id::create(`gfn);
+  endfunction
+
+  function void create_cov();
+    string csr_name = this.get_parent().get_name();
+    if (mubi_width > 0)     create_mubi_cov(mubi_width);
+    if (regwen_fld != null) create_lockable_fld_cov();
+    if (!uvm_re_match("*_shadowed", csr_name)) create_shadowed_fld_cov();
   endfunction
 
   // Returns true if this field can lock the specified register/field, else return false.
@@ -175,6 +199,22 @@
     lockable_flds_q = lockable_flds;
   endfunction
 
+  // Return if the RAL block is locked or not.
+  function bit is_locked();
+    uvm_reg_block blk = this.get_parent().get_parent();
+    return blk.is_locked();
+  endfunction
+
+  // If the register field is a mubi type, set the mubi width before the RAL is locked.
+  function void set_mubi_width(int width);
+    if (is_locked()) `uvm_fatal(`gfn, "Cannot set mubi width when the block is locked")
+    mubi_width = width;
+  endfunction
+
+  function int get_mubi_width();
+    return mubi_width;
+  endfunction
+
   // shadow register field read will clear its phase tracker
   virtual task post_read(uvm_reg_item rw);
     if (rw.status == UVM_IS_OK) begin
@@ -201,9 +241,14 @@
     uvm_reg_data_t committed_val_temp = committed_val & mask;
     `uvm_info(`gfn, $sformatf("shadow_val %0h, commmit_val %0h", shadowed_val_temp,
                               committed_val_temp), UVM_HIGH)
+    sample_shadow_field_cov(.storage_err(1));
     return shadowed_val_temp != committed_val_temp;
   endfunction
 
+  function void sample_shadow_field_cov(bit update_err = 0, bit storage_err = 0);
+    if (shadowed_cov != null) shadowed_cov.shadow_field_errs_cg.sample(update_err, storage_err);
+  endfunction
+
   function void update_staged_val(uvm_reg_data_t val);
     staged_val = val;
   endfunction
diff --git a/hw/dv/sv/dv_base_reg/dv_base_reg_pkg.sv b/hw/dv/sv/dv_base_reg/dv_base_reg_pkg.sv
index cfe03e3..827d5ac 100644
--- a/hw/dv/sv/dv_base_reg/dv_base_reg_pkg.sv
+++ b/hw/dv/sv/dv_base_reg/dv_base_reg_pkg.sv
@@ -68,9 +68,9 @@
     string        msg_id = {dv_base_reg_pkg::msg_id, "::decode_csr_or_field"};
 
     if ($cast(csr, ptr)) begin
-      // return csr object with null field; set the mask to all 1s and shift to 0
+      // return csr object with null field; set the mask to the width's all 1s and shift to 0
       result.csr = csr;
-      result.mask = '1;
+      result.mask = (1 << csr.get_n_bits()) - 1;
       result.shift = 0;
     end
     else if ($cast(fld, ptr)) begin
@@ -112,8 +112,30 @@
     get_field_val = (value >> shift) & mask;
   endfunction : get_field_val
 
+  // Returns a mask value from the provided fields. All fields must belong to the same CSR.
+  function automatic uvm_reg_data_t get_mask_from_fields(uvm_reg_field fields[$]);
+    uvm_reg_data_t mask = '0;
+    uvm_reg csr;
+    if (fields.size() == 0) return '1;
+    foreach (fields[i]) begin
+      uvm_reg_data_t fmask;
+      uint           fshift;
+      if (csr == null) csr = fields[i].get_parent();
+      else if (csr != fields[i].get_parent()) begin
+        `uvm_fatal(msg_id, $sformatf({"The provided fields belong to at least two different CSRs: ",
+                                      "%s, %s. All fields must belong to the same CSR"},
+                                     fields[i-1].`gfn, fields[i].`gfn))
+      end
+      fmask = (1 << fields[i].get_n_bits()) - 1;
+      fshift = fields[i].get_lsb_pos();
+      mask |= fmask << fshift;
+    end
+    return mask;
+  endfunction
+
   `include "csr_excl_item.sv"
   `include "dv_base_lockable_field_cov.sv"
+  `include "dv_base_shadowed_field_cov.sv"
   `include "dv_base_mubi_cov.sv"
   `include "dv_base_reg_field.sv"
   `include "dv_base_reg.sv"
diff --git a/hw/dv/sv/dv_base_reg/dv_base_shadowed_field_cov.sv b/hw/dv/sv/dv_base_reg/dv_base_shadowed_field_cov.sv
new file mode 100644
index 0000000..e1dc3c6
--- /dev/null
+++ b/hw/dv/sv/dv_base_reg/dv_base_shadowed_field_cov.sv
@@ -0,0 +1,29 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// coverage object of shadowed errors - update and storage errors.
+
+class dv_base_shadowed_field_cov extends uvm_object;
+  `uvm_object_utils(dv_base_shadowed_field_cov)
+
+  covergroup shadow_field_errs_cg(string name) with function sample(bit update_err = 0,
+                                                                    bit storage_err = 0);
+    option.per_instance = 1;
+    option.name         = name;
+
+    cp_update_err: coverpoint update_err {
+      bins update_err = {1};
+    }
+
+    cp_storage_err: coverpoint storage_err {
+      bins storage_err = {1};
+    }
+  endgroup
+
+  // use reg_field name as this name
+  function new(string name = "");
+    shadow_field_errs_cg = new($sformatf("%0s_shadowed_errs_cov", name));
+  endfunction : new
+
+endclass
diff --git a/hw/dv/sv/dv_lib/README.md b/hw/dv/sv/dv_lib/README.md
new file mode 100644
index 0000000..9a61b3c
--- /dev/null
+++ b/hw/dv/sv/dv_lib/README.md
@@ -0,0 +1,53 @@
+# DV Library Classes
+
+# DV library classes
+
+## Overview
+The DV library classes form the base layer / framework for constructing UVM
+testbenches. These classes provide features (settings, methods, hooks and other
+constructs used in verification) that are generic enough to be reused across
+all testbenches.
+
+In this doc, we will capture some of the most salient / frequently used features
+in extended classes. These classes are being updated frequently. So, for a more
+detailed understanding, please read the class definitions directly.
+
+The DV library classes fall into 3 categories - UVM RAL (register abstraction
+layer), UVM agent, and UVM environment extensions.
+
+### UVM RAL extensions
+The RAL model generated using the [reggen](../../../../util/reggen/doc/setup_and_use.md) tool
+extend from these classes. These themselves extend from the corresponding RAL
+classes provided in UVM.
+
+#### `dv_base_reg_field`
+Currently, this class does not provide any additional features. One of the
+features planned for future is setting exclusion tags at the field level for the
+CSR suite of tests that will be extracted automatically from the Hjson-based
+IP CSR specification.
+
+#### `dv_base_reg`
+This class provides the following functions to support verification:
+* `gen_n_used_bits()`: This function returns the actual number of bits used in
+  the CSR (sum of all available field widths).
+* `get_msb_pos()`: This function returns the MSB bit position of all available
+  fields. CSR either ends at this bit (`BUS_DW` - 1) or has reserved / invalid
+  bits beyond this bit.
+
+#### `dv_base_reg_block`
+* ` build(uvm_reg_addr_t base_addr)`: This function is implemented as a pseudo
+  pure virtual function (returns a fatal error if called directly). It is used
+  for building the complete RAL model. For a polymorphic approach, the DV user
+  can use this class handle to create the extended (IP specific) class instance
+  and call this function to build the actual RAL model. This is exactly how it
+  is done in [dv_base_env_cfg](#dv_base_env_cfg).
+
+#### `dv_base_reg_map`
+Currently, this class does not provide any additional features. Having this
+extension provides an opportunity to add common features in future.
+
+### UVM Agent extensions
+TODO
+
+### UVM Environment extensions
+TODO
diff --git a/hw/dv/sv/dv_lib/doc/index.md b/hw/dv/sv/dv_lib/doc/index.md
deleted file mode 100644
index 3092fab..0000000
--- a/hw/dv/sv/dv_lib/doc/index.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-title: "DV Library Classes"
----
-
-# DV library classes
-
-## Overview
-The DV library classes form the base layer / framework for constructing UVM
-testbenches. These classes provide features (settings, methods, hooks and other
-constructs used in verification) that are generic enough to be reused across
-all testbenches.
-
-In this doc, we will capture some of the most salient / frequently used features
-in extended classes. These classes are being updated frequently. So, for a more
-detailed understanding, please read the class definitions directly.
-
-The DV library classes fall into 3 categories - UVM RAL (register abstraction
-layer), UVM agent, and UVM environment extensions.
-
-### UVM RAL extensions
-The RAL model generated using the [reggen]({{< relref "util/reggen/doc" >}}) tool
-extend from these classes. These themselves extend from the corresponding RAL
-classes provided in UVM.
-
-#### `dv_base_reg_field`
-Currently, this class does not provide any additional features. One of the
-features planned for future is setting exclusion tags at the field level for the
-CSR suite of tests that will be extracted automatically from the Hjson-based
-IP CSR specification.
-
-#### `dv_base_reg`
-This class provides the following functions to support verification:
-* `gen_n_used_bits()`: This function returns the actual number of bits used in
-  the CSR (sum of all available field widths).
-* `get_msb_pos()`: This function returns the MSB bit position of all available
-  fields. CSR either ends at this bit (`BUS_DW` - 1) or has reserved / invalid
-  bits beyond this bit.
-
-#### `dv_base_reg_block`
-* ` build(uvm_reg_addr_t base_addr)`: This function is implemented as a pseudo
-  pure virtual function (returns a fatal error if called directly). It is used
-  for building the complete RAL model. For a polymorphic approach, the DV user
-  can use this class handle to create the extended (IP specific) class instance
-  and call this function to build the actual RAL model. This is exactly how it
-  is done in [dv_base_env_cfg](#dv_base_env_cfg).
-
-#### `dv_base_reg_map`
-Currently, this class does not provide any additional features. Having this
-extension provides an opportunity to add common features in future.
-
-### UVM Agent extensions
-TODO
-
-### UVM Environment extensions
-TODO
diff --git a/hw/dv/sv/dv_lib/dv_base_agent_cfg.sv b/hw/dv/sv/dv_lib/dv_base_agent_cfg.sv
index 047b3ad..338fadf 100644
--- a/hw/dv/sv/dv_lib/dv_base_agent_cfg.sv
+++ b/hw/dv/sv/dv_lib/dv_base_agent_cfg.sv
@@ -23,6 +23,8 @@
   // Indicates that the interface is under reset. The derived monitor detects and maintains it.
   bit in_reset;
 
+  bit en_monitor = 1'b1;
+
   `uvm_object_utils_begin(dv_base_agent_cfg)
     `uvm_field_int (is_active,            UVM_DEFAULT)
     `uvm_field_int (en_cov,               UVM_DEFAULT)
diff --git a/hw/dv/sv/dv_lib/dv_base_env_cov.sv b/hw/dv/sv/dv_lib/dv_base_env_cov.sv
index 663de0d..48bf92a 100644
--- a/hw/dv/sv/dv_lib/dv_base_env_cov.sv
+++ b/hw/dv/sv/dv_lib/dv_base_env_cov.sv
@@ -2,9 +2,6 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-// TODO - We are enclosing generic covergroups inside class so that we can
-// take avoid tool limitation of not allowing arrays of covergroup
-// Refer to Issue#375 for more details
 class bit_toggle_cg_wrap;
 
   // Covergroup: bit_toggle_cg
diff --git a/hw/dv/sv/dv_lib/dv_base_test.sv b/hw/dv/sv/dv_lib/dv_base_test.sv
index 3018954..a82f1d2 100644
--- a/hw/dv/sv/dv_lib/dv_base_test.sv
+++ b/hw/dv/sv/dv_lib/dv_base_test.sv
@@ -45,7 +45,6 @@
     void'($value$plusargs("en_scb=%0b", cfg.en_scb));
     void'($value$plusargs("en_scb_tl_err_chk=%0b", cfg.en_scb_tl_err_chk));
     void'($value$plusargs("en_scb_mem_chk=%0b", cfg.en_scb_mem_chk));
-    void'($value$plusargs("en_scb_ping_chk=%0b", cfg.en_scb_ping_chk));
     // Enable fastest design performance by configuring zero delays in all agents.
     void'($value$plusargs("zero_delays=%0b", cfg.zero_delays));
 
@@ -78,7 +77,6 @@
     if (run_test_seq) begin
       run_seq(test_seq_s, phase);
     end
-    // TODO: add hook for end of test checking.
   endtask : run_phase
 
   // Add message demotes here - hook to use by extended tests
@@ -100,7 +98,4 @@
     phase.drop_objection(this, $sformatf("%s objection dropped", `gn));
     `uvm_info(`gfn, {"Finished test sequence ", test_seq_s}, UVM_MEDIUM)
   endtask
-
-  // TODO: Add default report_phase implementation.
-
 endclass : dv_base_test
diff --git a/hw/dv/sv/dv_lib/dv_lib.core b/hw/dv/sv/dv_lib/dv_lib.core
index e6e3f9e..e922e3f 100644
--- a/hw/dv/sv/dv_lib/dv_lib.core
+++ b/hw/dv/sv/dv_lib/dv_lib.core
@@ -11,6 +11,7 @@
       - lowrisc:dv:dv_utils
       - lowrisc:dv:csr_utils
       - lowrisc:dv:dv_base_reg
+      - lowrisc:dv:common_ifs
       - lowrisc:opentitan:bus_params_pkg
     files:
       - dv_lib_pkg.sv
diff --git a/hw/dv/sv/dv_utils/doc/index.md b/hw/dv/sv/dv_utils/doc/index.md
deleted file mode 100644
index b0b70b6..0000000
--- a/hw/dv/sv/dv_utils/doc/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: "DV Utils"
----
diff --git a/hw/dv/sv/dv_utils/dv_macros.svh b/hw/dv/sv/dv_utils/dv_macros.svh
index c571eca..f2e8af5 100644
--- a/hw/dv/sv/dv_utils/dv_macros.svh
+++ b/hw/dv/sv/dv_utils/dv_macros.svh
@@ -85,7 +85,7 @@
 `ifndef DV_CHECK
   `define DV_CHECK(T_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!(T_)) begin \
+      if (T_) ; else begin \
         `dv_``SEV_($sformatf("Check failed (%s) %s ", `"T_`", MSG_), ID_) \
       end \
     end
@@ -94,7 +94,7 @@
 `ifndef DV_CHECK_EQ
   `define DV_CHECK_EQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) == (EXP_))) begin \
+      if ((ACT_) == (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s == %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -104,7 +104,7 @@
 `ifndef DV_CHECK_NE
   `define DV_CHECK_NE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) != (EXP_))) begin \
+      if ((ACT_) != (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s != %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -114,7 +114,7 @@
 `ifndef DV_CHECK_CASE_EQ
   `define DV_CHECK_CASE_EQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) === (EXP_))) begin \
+      if ((ACT_) === (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s === %s (0x%0h [%0b] vs 0x%0h [%0b]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -124,7 +124,7 @@
 `ifndef DV_CHECK_CASE_NE
   `define DV_CHECK_CASE_NE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) !== (EXP_))) begin \
+      if ((ACT_) !== (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s !== %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -134,7 +134,7 @@
 `ifndef DV_CHECK_LT
   `define DV_CHECK_LT(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) < (EXP_))) begin \
+      if ((ACT_) < (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s < %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -144,7 +144,7 @@
 `ifndef DV_CHECK_GT
   `define DV_CHECK_GT(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) > (EXP_))) begin \
+      if ((ACT_) > (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s > %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -154,7 +154,7 @@
 `ifndef DV_CHECK_LE
   `define DV_CHECK_LE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) <= (EXP_))) begin \
+      if ((ACT_) <= (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s <= %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -164,7 +164,7 @@
 `ifndef DV_CHECK_GE
   `define DV_CHECK_GE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
     begin \
-      if (!((ACT_) >= (EXP_))) begin \
+      if ((ACT_) >= (EXP_)) ; else begin \
         `dv_``SEV_($sformatf("Check failed %s >= %s (%0d [0x%0h] vs %0d [0x%0h]) %s", \
                              `"ACT_`", `"EXP_`", ACT_, ACT_, EXP_, EXP_, MSG_), ID_) \
       end \
@@ -173,15 +173,29 @@
 
 `ifndef DV_CHECK_STREQ
   `define DV_CHECK_STREQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
-    if (!((ACT_) == (EXP_))) begin \
-      `dv_``SEV_($sformatf("Check failed \"%s\" == \"%s\" %s", ACT_, EXP_, MSG_), ID_) \
+    begin \
+      if ((ACT_) == (EXP_)) ; else begin \
+        `dv_``SEV_($sformatf("Check failed \"%s\" == \"%s\" %s", ACT_, EXP_, MSG_), ID_) \
+      end \
     end
 `endif
 
 `ifndef DV_CHECK_STRNE
   `define DV_CHECK_STRNE(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
-    if (!((ACT_) != (EXP_))) begin \
-      `dv_``SEV_($sformatf("Check failed \"%s\" != \"%s\" %s", ACT_, EXP_, MSG_), ID_) \
+    begin \
+      if ((ACT_) != (EXP_)) ; else begin \
+        `dv_``SEV_($sformatf("Check failed \"%s\" != \"%s\" %s", ACT_, EXP_, MSG_), ID_) \
+      end \
+    end
+`endif
+
+`ifndef DV_CHECK_Q_EQ
+  `define DV_CHECK_Q_EQ(ACT_, EXP_, MSG_="", SEV_=error, ID_=`gfn) \
+    begin \
+      `DV_CHECK_EQ(ACT_.size(), EXP_.size(), MSG_, SEV_, ID_) \
+      foreach (ACT_[i]) begin \
+        `DV_CHECK_EQ(ACT_[i], EXP_[i], $sformatf("for i = %0d %s", i, MSG_), SEV_, ID_) \
+      end \
     end
 `endif
 
@@ -374,10 +388,26 @@
   end
 `endif
 
+// macro that waits for a given delay and then reports an error
+`ifndef DV_WAIT_TIMEOUT
+`define DV_WAIT_TIMEOUT(TIMEOUT_NS_, ID_  = `gfn, ERROR_MSG_ = "timeout occurred!", REPORT_FATAL_ = 1) \
+  begin \
+    #(TIMEOUT_NS_ * 1ns); \
+    if (REPORT_FATAL_) `dv_fatal(ERROR_MSG_, ID_) \
+    else               `dv_error(ERROR_MSG_, ID_) \
+  end
+`endif
+
 // wait a task or statement with timer watchdog
 `ifndef DV_SPINWAIT
 `define DV_SPINWAIT(WAIT_, MSG_ = "timeout occurred!", TIMEOUT_NS_ = default_spinwait_timeout_ns, ID_ =`gfn) \
-  `DV_SPINWAIT_EXIT(WAIT_, wait_timeout(TIMEOUT_NS_, ID_, MSG_);, "", ID_)
+  `DV_SPINWAIT_EXIT(WAIT_, `DV_WAIT_TIMEOUT(TIMEOUT_NS_, ID_, MSG_);, "", ID_)
+`endif
+
+// a shorthand of `DV_SPINWAIT(wait(...))
+`ifndef DV_WAIT
+`define DV_WAIT(WAIT_COND_, MSG_ = "wait timeout occurred!", TIMEOUT_NS_ = default_spinwait_timeout_ns, ID_ =`gfn) \
+  `DV_SPINWAIT(wait (WAIT_COND_);, MSG_, TIMEOUT_NS_, ID_)
 `endif
 
 // Control assertions in the DUT.
@@ -408,6 +438,7 @@
       end else begin \
         `uvm_info(ID_, $sformatf("Disabling assertions: %0s", `DV_STRINGIFY(HIER_)), UVM_LOW) \
         $assertoff(LEVELS_, HIER_); \
+        $assertkill(LEVELS_, HIER_); \
       end \
     end \
   end
@@ -436,6 +467,27 @@
   end
 `endif
 
+// Retrieves a queue of plusarg value from a string.
+//
+// The plusarg is parsed as a string, which needs to be converted into a queue of string which given delimiter.
+// This functionality is provided by the UVM helper function below.
+//
+// QUEUE_: The queue of string to which the plusarg value will be set (must be declared already).
+// PLUSARG_: the name of the plusarg (as raw text). This is typically the same as the enum variable.
+// DELIMITER_: the delimiter that separate each item in the plusarg string value.
+// CHECK_EXISTS_: Throws a fatal error if the plusarg is not set.
+`ifndef DV_GET_QUEUE_PLUSARG
+`define DV_GET_QUEUE_PLUSARG(QUEUE_, PLUSARG_, DELIMITER_ = ",", CHECK_EXISTS_ = 0, ID_ = `gfn) \
+  begin \
+    string str; \
+    if ($value$plusargs("``PLUSARG_``=%0s", str)) begin \
+      str_split(str, QUEUE_, DELIMITER_); \
+    end else if (CHECK_EXISTS_) begin \
+      `uvm_fatal(ID_, "Please pass the plusarg +``PLUSARG_``=<``ENUM_``-literal>") \
+    end \
+  end
+`endif
+
 // Enable / disable assertions at a module hierarchy identified by LABEL_.
 //
 // This goes in conjunction with `DV_ASSERT_CTRL() macro above, but is invoked in the entity that is
@@ -515,16 +567,20 @@
 `ifndef dv_fatal
   // verilog_lint: waive macro-name-style
   `define dv_fatal(MSG_, ID_ = $sformatf("%m")) \
-    $fatal("%0t: (%0s:%0d) [%0s] %0s", $time, `__FILE__, `__LINE__, ID_, MSG_);
+    $fatal(1, "%0t: (%0s:%0d) [%0s] %0s", $time, `__FILE__, `__LINE__, ID_, MSG_);
 `endif
 
 `endif // UVM
 
 // Macros for constrain clk with common frequencies
-// constrain clock to run at 24Mhz - 100Mhz and use higher weights on 24, 25, 48, 50, 100
+//
+// Nominal clock frequency range is 24Mhz - 100Mhz and use higher weights on 24, 25, 48, 50, 100,
+// To mimic manufacturing conditions (when clocks are uncalibrated), we need to be able to go as
+// low as 5MHz.
 `ifndef DV_COMMON_CLK_CONSTRAINT
 `define DV_COMMON_CLK_CONSTRAINT(FREQ_) \
   FREQ_ dist { \
+    [5:23]  :/ 2, \
     [24:25] :/ 2, \
     [26:47] :/ 1, \
     [48:50] :/ 2, \
@@ -549,3 +605,33 @@
 `ifndef DV_MAX2
   `define DV_MAX2(a, b) ((a) > (b) ? (a) : (b))
 `endif
+
+// Creates a signal probe function to sample / force / release an internal signal.
+//
+// If there is a need to sample / force an internal signal, then it must be done in the testbench,
+// or in an interface bound to the DUT. This macro creates a standardized signal probe function
+// meant to be invoked an interface. The generated function can then be invoked in test sequences
+// or other UVM classes. The macro takes 2 arguments - name of the function and the hierarchical
+// path to the signal. If invoked in an interface which is bound to the DUT, the signal can be a
+// partial hierarchical path within the DUT. The generated function accepts 2 arguments - the first
+// indicates the probe action (sample, force or release) of type dv_utils_pkg::signal_probe_e. The
+// second argument is the value to be forced. If sample action is chosen, then it returns the
+// sampled value (for other actions as well).
+//
+// The suggested naming convention for the function is:
+//   signal_probe_<DUT_or_IP_block_name>_<signal_name>
+//
+// This macro must be invoked in an interface or module.
+`ifndef DV_CREATE_SIGNAL_PROBE_FUNCTION
+`define DV_CREATE_SIGNAL_PROBE_FUNCTION(FUNC_NAME_, SIGNAL_PATH_, SIGNAL_WIDTH_ = uvm_pkg::UVM_HDL_MAX_WIDTH) \
+  function static logic [SIGNAL_WIDTH_-1:0] FUNC_NAME_(dv_utils_pkg::signal_probe_e kind,     \
+                                                       logic [SIGNAL_WIDTH_-1:0] value = '0); \
+    case (kind)                                                                               \
+      dv_utils_pkg::SignalProbeSample: ;                                                      \
+      dv_utils_pkg::SignalProbeForce: force SIGNAL_PATH_ = value;                             \
+      dv_utils_pkg::SignalProbeRelease: release SIGNAL_PATH_;                                 \
+      default: `uvm_fatal(`"FUNC_NAME_`", $sformatf("Bad value: %0d", kind))                  \
+    endcase                                                                                   \
+    return SIGNAL_PATH_;                                                                      \
+  endfunction
+`endif
diff --git a/hw/dv/sv/dv_utils/dv_test_status.core b/hw/dv/sv/dv_utils/dv_test_status.core
index bbec1b0..e11cfd4 100644
--- a/hw/dv/sv/dv_utils/dv_test_status.core
+++ b/hw/dv/sv/dv_utils/dv_test_status.core
@@ -7,6 +7,8 @@
 
 filesets:
   files_dv:
+    depend:
+      - lowrisc:prim:util
     files:
       - dv_test_status_pkg.sv
     file_type: systemVerilogSource
diff --git a/hw/dv/sv/dv_utils/dv_test_status_pkg.sv b/hw/dv/sv/dv_utils/dv_test_status_pkg.sv
index 3a81ddf..3933673 100644
--- a/hw/dv/sv/dv_utils/dv_test_status_pkg.sv
+++ b/hw/dv/sv/dv_utils/dv_test_status_pkg.sv
@@ -10,6 +10,13 @@
   // signature along with a banner. The signature can be used by external scripts to determine if
   // the test passed or failed.
   function automatic void dv_test_status(bit passed);
+`ifdef INC_ASSERT
+    if (prim_util_pkg::end_of_simulation) begin
+      // The first arg '1' is the error code, arbitrarily set to 1.
+      $fatal(1, "prim_util_pkg::end_of_simulation was already signaled!");
+    end
+    prim_util_pkg::end_of_simulation = 1'b1;
+`endif
     if (passed) begin
       $display("\nTEST PASSED CHECKS");
       $display(" _____         _                                  _ _ ");
diff --git a/hw/dv/sv/dv_utils/dv_utils_pkg.sv b/hw/dv/sv/dv_utils/dv_utils_pkg.sv
index 62f3b76..fd2a48c 100644
--- a/hw/dv/sv/dv_utils/dv_utils_pkg.sv
+++ b/hw/dv/sv/dv_utils/dv_utils_pkg.sv
@@ -68,6 +68,13 @@
     BusOpRead  = 1'b1
   } bus_op_e;
 
+ // Enum representing a probe operation on an internal signal.
+  typedef enum {
+    SignalProbeSample,  // Sample the signal.
+    SignalProbeForce,   // Force the signal with some value.
+    SignalProbeRelease  // Release the previous force.
+  } signal_probe_e;
+
   // Enum representing a type of host requests - read only, write only or random read & write
   typedef enum int {
     HostReqNone      = 0,
@@ -138,16 +145,6 @@
     return report_server.get_severity_count(UVM_FATAL) > 0;
   endfunction
 
-  // task that waits for the specfied timeout
-  task automatic wait_timeout(input uint    timeout_ns,
-                              input string  error_msg_id  = msg_id,
-                              input string  error_msg     = "timeout occurred!",
-                              input bit     report_fatal  = 1);
-    #(timeout_ns * 1ns);
-    if (report_fatal) `uvm_fatal(error_msg_id, error_msg)
-    else              `uvm_error(error_msg_id, error_msg)
-  endtask : wait_timeout
-
   // get masked data based on provided byte mask; if csr reg handle is provided (optional) then
   // masked bytes from csr's mirrored value are returned, else masked bytes are 0's
   function automatic bit [bus_params_pkg::BUS_DW-1:0]
@@ -213,6 +210,153 @@
     join_none
   endtask : poll_for_stop
 
+  // Extracts the address and size of a const symbol in a SW test (supplied as an ELF file).
+  //
+  // Used by a testbench to modify the given symbol in an executable (elf) generated for an embedded
+  // CPU within the DUT. This function only returns the extracted address and size of the symbol
+  // using the readelf utility. Readelf comes with binutils, a package typically available on linux
+  // machines. If not available, the assumption is, it can be relatively easily installed.
+  // The actual job of writing the new value into the symbol is handled externally (often via a
+  // backdoor mechanism to write the memory).
+  // Return 1 on success and 0 on failure.
+  function automatic bit sw_symbol_get_addr_size(input string elf_file,
+                                                 input string symbol,
+                                                 input bit does_not_exist_ok,
+                                                 output longint unsigned addr,
+                                                 output longint unsigned size);
+
+    string msg_id = "sw_symbol_get_addr_size";
+    string escaped_symbol = "";
+    string symbol_for_filename = "";
+
+    `DV_CHECK_STRNE_FATAL(elf_file, "", "Input arg \"elf_file\" cannot be an empty string", msg_id)
+    `DV_CHECK_STRNE_FATAL(symbol,   "", "Input arg \"symbol\" cannot be an empty string", msg_id)
+
+    // If the symbol has special characters, such as '$', escape it for the cmd below, but when
+    // creating the file, omit the special characters entirely.
+    foreach (symbol[i]) begin
+      if (symbol[i] == "$") begin
+        escaped_symbol = {escaped_symbol, "\\", symbol[i]};
+      end else begin
+        escaped_symbol = {escaped_symbol, symbol[i]};
+        symbol_for_filename = {symbol_for_filename, symbol[i]};
+      end
+    end
+
+    begin
+      int ret;
+      string line;
+      int out_file_d = 0;
+      string out_file = $sformatf("%0s.dat", symbol_for_filename);
+      string cmd = $sformatf(
+          // use `--wide` to avoid truncating the output, in case of long symbol name
+          // `\s%0s$` ensures we are looking for an exact match, with no pre- or postfixes.
+          "/usr/bin/readelf -s --wide %0s | grep \"\\s%0s$\" | awk \'{print $2\" \"$3}\' > %0s",
+          elf_file, escaped_symbol, out_file);
+
+      // TODO #3838: shell pipes are bad 'mkay?
+      ret = $system(cmd);
+      `DV_CHECK_EQ_FATAL(ret, 0, $sformatf("Command \"%0s\" failed with exit code %0d", cmd, ret),
+                         msg_id)
+
+      out_file_d = $fopen(out_file, "r");
+      `DV_CHECK_FATAL(out_file_d, $sformatf("Failed to open \"%0s\"", out_file), msg_id)
+
+      ret = $fgets(line, out_file_d);
+
+      // If the symbol did not exist in the elf (empty file), and we are ok with that, then return.
+      if (!ret && does_not_exist_ok) return 0;
+
+      `DV_CHECK_FATAL(ret, $sformatf("Failed to read line from \"%0s\"", out_file), msg_id)
+
+      // The first line should have the addr in hex followed by its size as integer.
+      ret = $sscanf(line, "%h %d", addr, size);
+      `DV_CHECK_EQ_FATAL(ret, 2, $sformatf("Failed to extract {addr size} from line \"%0s\"", line),
+                         msg_id)
+
+      // Attempt to read the next line should be met with EOF.
+      void'($fgets(line, out_file_d));
+      ret = $feof(out_file_d);
+      `DV_CHECK_FATAL(ret, $sformatf("EOF expected to be reached for \"%0s\"", out_file), msg_id)
+      $fclose(out_file_d);
+
+      ret = $system($sformatf("rm -rf %0s", out_file));
+      `DV_CHECK_EQ_FATAL(ret, 0, $sformatf("Failed to delete \"%0s\"", out_file), msg_id)
+      return 1;
+    end
+  endfunction
+
+  // Reads VMEM file contents into a queue of data.
+  //
+  // TODO: Add support for non-contiguous memory.
+  // TODO: Add support for ECC bits.
+  // TODO: Add support for non-BUS_DW sized VMEM data.
+  // vmem_file: Path to VMEM image, compatible with $readmemh mathod.
+  // vmem_data: A queue of BUS_DW sized data returned to the caller.
+  function automatic void read_vmem(input string vmem_file,
+                                    output logic [bus_params_pkg::BUS_DW-1:0] vmem_data[$]);
+    int fd;
+    uvm_reg_addr_t last_addr;
+    string text, msg = "\n", lines[$];
+
+    fd = $fopen(vmem_file, "r");
+    `DV_CHECK_FATAL(fd, $sformatf("Failed to open \"%0s\"", vmem_file), msg_id)
+    while (!$feof(fd)) begin
+      string line;
+      void'($fgets(line, fd));
+      line = str_utils_pkg::str_strip(line);
+      if (line == "") continue;
+      text = {text, line, "\n"};
+    end
+    $fclose(fd);
+    `DV_CHECK_STRNE_FATAL(text, "", , msg_id)
+
+    // Remove all block and single comments.
+    text = str_utils_pkg::str_remove_sections(.s(text), .start_delim("/*"), .end_delim("*/"));
+    text = str_utils_pkg::str_remove_sections(.s(text), .start_delim("//"), .end_delim("\n"),
+                                              .remove_end_delim(0));
+
+    vmem_data.delete();
+    str_utils_pkg::str_split(text, lines, "\n");
+    foreach (lines[i]) begin
+      string tokens[$];
+      uvm_reg_addr_t addr;
+
+      // Split the line by space. The first item must be the address that starts with '@'.
+      str_utils_pkg::str_split(lines[i], tokens, " ");
+      `DV_CHECK_FATAL(tokens.size() >= 2,
+                      $sformatf("Line \"%s\" in VMEM file %s appears to be malformed",
+                                lines[i], vmem_file), msg_id)
+      if (!str_utils_pkg::str_starts_with(tokens[0], "@")) begin
+        `uvm_fatal(msg_id, $sformatf({"The first word \"%s\" on line \"%s\" in the VMEM file %s ",
+                                      " does not appear to be a valid address"},
+                                    tokens[0], lines[i], vmem_file))
+      end
+      tokens[0] = tokens[0].substr(1, tokens[0].len() - 1);
+      `DV_CHECK_FATAL(tokens[0].len() <= bus_params_pkg::BUS_AW / 8 * 2,
+                      $sformatf("Address width > %0d bytes is not supported: 0x%0s",
+                                bus_params_pkg::BUS_AW / 8, tokens[0]), msg_id)
+      addr = tokens[0].atohex();
+      tokens = tokens[1:$];
+      if (i > 0) begin
+        `DV_CHECK_FATAL(addr == last_addr,
+                        $sformatf("Non-contiguous data unsupported - last_addr: 0x%0h, addr: 0x%0h",
+                                  last_addr, addr), msg_id)
+      end
+      foreach (tokens[i]) begin
+        logic [bus_params_pkg::BUS_DW-1:0] data;
+        `DV_CHECK_FATAL(tokens[i].len() <= bus_params_pkg::BUS_DW / 8 * 2,
+                        $sformatf("Data width > %0d bytes is not supported: 0x%0s",
+                                  bus_params_pkg::BUS_DW / 8, tokens[i]), msg_id)
+        data = tokens[i].atohex();
+        msg = {msg, $sformatf("[0x%0h] = 0x%0h\n", addr + i, data)};
+        vmem_data.push_back(data);
+      end
+      last_addr = addr + tokens.size();
+    end
+    `uvm_info(msg_id, $sformatf("Contents of VMEM file %s:%s", vmem_file, msg), UVM_HIGH)
+  endfunction
+
   // sources
 `ifdef UVM
   `include "dv_report_catcher.sv"
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.core b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.core
new file mode 100644
index 0000000..6b16f01
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.core
@@ -0,0 +1,30 @@
+CAPI=2:
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+name: "lowrisc:dv:entropy_src_xht_agent:0.1"
+description: "entropy_src external HT DV UVM agent"
+filesets:
+  files_dv:
+    depend:
+      - lowrisc:dv:dv_utils
+      - lowrisc:dv:dv_lib
+      - lowrisc:ip:entropy_src_pkg
+      - lowrisc:dv:digestpp_dpi:0.1
+    files:
+      - entropy_src_xht_if.sv
+      - entropy_src_xht_agent_pkg.sv
+      - entropy_src_xht_item.sv: {is_include_file: true}
+      - entropy_src_xht_agent_cfg.sv: {is_include_file: true}
+      - entropy_src_xht_device_driver.sv: {is_include_file: true}
+      - entropy_src_xht_monitor.sv: {is_include_file: true}
+      - entropy_src_xht_sequencer.sv: {is_include_file: true}
+      - entropy_src_xht_agent.sv: {is_include_file: true}
+      - seq_lib/entropy_src_xht_base_device_seq.sv: {is_include_file: true}
+      - seq_lib/entropy_src_xht_seq_list.sv: {is_include_file: true}
+    file_type: systemVerilogSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.sv
new file mode 100644
index 0000000..d7f9a5f
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent.sv
@@ -0,0 +1,43 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_agent extends dv_base_agent #(
+  .CFG_T       (entropy_src_xht_agent_cfg),
+  .DRIVER_T    (entropy_src_xht_device_driver),
+  .SEQUENCER_T (entropy_src_xht_sequencer),
+  .MONITOR_T   (entropy_src_xht_monitor),
+  .COV_T       (dv_base_agent_cov#(entropy_src_xht_agent_cfg))
+);
+
+  `uvm_component_utils(entropy_src_xht_agent)
+
+  `uvm_component_new
+
+  function void build_phase(uvm_phase phase);
+    super.build_phase(phase);
+    cfg.has_req_fifo = 1;
+
+    if (!uvm_config_db#(virtual entropy_src_xht_if)::get(
+        this, "", "vif", cfg.vif)) begin
+      `uvm_fatal(`gfn, "failed to get entropy_src_xht_if handle from uvm_config_db")
+    end
+
+  endfunction
+
+  function void connect_phase(uvm_phase phase);
+    super.connect_phase(phase);
+    monitor.req_analysis_port.connect(sequencer.req_analysis_fifo.analysis_export);
+  endfunction
+
+  virtual task run_phase(uvm_phase phase);
+    entropy_src_xht_base_device_seq m_seq
+        = entropy_src_xht_base_device_seq::type_id::create("m_seq", this);
+    if (cfg.start_default_seq) begin
+      uvm_config_db#(uvm_object_wrapper)::set(null, {sequencer.get_full_name(), ".run_phase"},
+                                              "default_sequence", m_seq.get_type());
+      sequencer.start_phase_sequence(phase);
+    end
+  endtask
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_cfg.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_cfg.sv
new file mode 100644
index 0000000..0d70f72
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_cfg.sv
@@ -0,0 +1,18 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_agent_cfg extends dv_base_agent_cfg;
+
+  virtual entropy_src_xht_if vif;
+
+  bit     start_default_seq = 1'b0;
+  bit     unfiltered_monitor_traffic = 1'b0;
+
+  `uvm_object_utils_begin(entropy_src_xht_agent_cfg)
+    `uvm_field_int(start_default_seq, UVM_DEFAULT)
+  `uvm_object_utils_end
+
+  `uvm_object_new
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_pkg.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_pkg.sv
new file mode 100644
index 0000000..06dc37f
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_agent_pkg.sv
@@ -0,0 +1,24 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+package entropy_src_xht_agent_pkg;
+  // dep packages
+  import uvm_pkg::*;
+  import dv_utils_pkg::*;
+  import dv_lib_pkg::*;
+  import entropy_src_pkg::*;
+
+  // macro includes
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  `include "entropy_src_xht_item.sv"
+  `include "entropy_src_xht_agent_cfg.sv"
+  `include "entropy_src_xht_device_driver.sv"
+  `include "entropy_src_xht_monitor.sv"
+  `include "entropy_src_xht_sequencer.sv"
+  `include "entropy_src_xht_seq_list.sv"
+  `include "entropy_src_xht_agent.sv"
+
+endpackage : entropy_src_xht_agent_pkg
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_device_driver.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_device_driver.sv
new file mode 100644
index 0000000..0b5a37c
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_device_driver.sv
@@ -0,0 +1,37 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_device_driver extends dv_base_driver #(
+    .ITEM_T(entropy_src_xht_item),
+    .CFG_T (entropy_src_xht_agent_cfg)
+);
+
+  `uvm_component_utils(entropy_src_xht_device_driver)
+  `uvm_component_new
+
+  virtual task reset_signals();
+    forever begin
+      @(negedge cfg.vif.rst_n);
+      `uvm_info(`gfn, "Driver is under reset", UVM_DEBUG)
+      cfg.vif.xht_cb.rsp <= ENTROPY_SRC_XHT_RSP_DEFAULT;
+      @(posedge cfg.vif.rst_n);
+      `uvm_info(`gfn, "Driver is out of reset", UVM_DEBUG)
+    end
+  endtask
+
+  virtual task get_and_drive();
+    forever begin
+      @(cfg.vif.xht_cb);
+      seq_item_port.try_next_item(req);
+      if (req == null) begin
+        cfg.vif.xht_cb.rsp.test_fail_hi_pulse <= 1'b0;
+        cfg.vif.xht_cb.rsp.test_fail_lo_pulse <= 1'b0;
+      end else begin
+        cfg.vif.xht_cb.rsp <= req.rsp;
+        seq_item_port.item_done(req);
+      end
+    end
+  endtask
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_if.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_if.sv
new file mode 100644
index 0000000..8bfbf76
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_if.sv
@@ -0,0 +1,31 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+interface entropy_src_xht_if import entropy_src_pkg::*;
+(
+  input wire clk,
+  input wire rst_n
+);
+
+  entropy_src_xht_req_t req;
+  entropy_src_xht_rsp_t rsp;
+
+  logic entropy_bit_valid_q;
+
+  always @(posedge clk) begin
+    entropy_bit_valid_q <= req.entropy_bit_valid;
+  end
+
+  clocking xht_cb @(posedge clk);
+    input req;
+    output rsp;
+  endclocking
+
+  clocking mon_cb @(posedge clk);
+    input req;
+    input rsp;
+    input entropy_bit_valid_q;
+  endclocking
+
+endinterface
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_item.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_item.sv
new file mode 100644
index 0000000..381ad09
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_item.sv
@@ -0,0 +1,13 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_item extends uvm_sequence_item;
+
+  entropy_src_xht_req_t req;
+  entropy_src_xht_rsp_t rsp;
+
+  `uvm_object_utils(entropy_src_xht_item)
+  `uvm_object_new
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_monitor.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_monitor.sv
new file mode 100644
index 0000000..9f035a9
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_monitor.sv
@@ -0,0 +1,83 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_monitor extends dv_base_monitor #(
+  .ITEM_T (entropy_src_xht_item),
+  .CFG_T  (entropy_src_xht_agent_cfg),
+  .COV_T  (dv_base_agent_cov#(entropy_src_xht_agent_cfg))
+);
+
+  `uvm_component_utils(entropy_src_xht_monitor)
+  `uvm_component_new
+
+  task run_phase(uvm_phase phase);
+    fork
+      monitor_reset();
+      begin
+        @(posedge cfg.vif.rst_n);
+        collect_req(phase);
+      end
+    join
+  endtask
+
+  virtual protected task monitor_reset();
+    forever begin
+      @(negedge cfg.vif.rst_n);
+      `uvm_info(`gfn, "Reset detected!", UVM_DEBUG)
+      cfg.in_reset = 1;
+      @(posedge cfg.vif.rst_n);
+      `uvm_info(`gfn, "Reset release detected!", UVM_DEBUG)
+      cfg.in_reset = 0;
+    end
+  endtask
+
+  // Shorthand for restarting forever loop on reset detection.
+  `define WAIT_FOR_RESET    \
+    if (cfg.in_reset) begin \
+      wait (!cfg.in_reset); \
+      continue;             \
+    end
+
+  virtual protected task collect_req(uvm_phase phase);
+    forever begin
+      @(cfg.vif.mon_cb);
+      `WAIT_FOR_RESET
+      create_and_write_item();
+    end
+  endtask
+
+  virtual protected function void create_and_write_item();
+    entropy_src_xht_item item;
+
+    bit  event_filter;
+
+    // The entropy_src_extht_req_t encodes three types of events that the agent must monitor
+    // - entropy_bit_valid
+    // - clear, and
+    // - window_wrap pulse.
+    // All other req fields are data or parameters (unchanging except with a clear pulse)
+    //
+    // Meanwhile the entropy_src_rsp_t data is continuously monitored.
+    // Creating a new item at every clock however has a noticible performance impact on simulations
+    // therefore we typically assume that the xht_sequence only outputs updates in the cycle after
+    // entropy_bit_valid, and so we add entropy_bit_valid_q to our event filter.
+    //
+    // To get scoreboard access to all bus samples, set cfg.unfiltered_monitor_traffic to 1. This
+    // will allow for simulations in which an xht sequence takes longer than one cycle to respond.
+    event_filter = (cfg.vif.mon_cb.req.entropy_bit_valid ||
+                    cfg.vif.mon_cb.req.clear ||
+                    cfg.vif.mon_cb.req.window_wrap_pulse ||
+                    cfg.vif.mon_cb.entropy_bit_valid_q);
+
+    if (cfg.unfiltered_monitor_traffic || event_filter) begin
+      `uvm_info(`gfn, "Sending item", UVM_DEBUG)
+      item = entropy_src_xht_item::type_id::create("item");
+      item.rsp = cfg.vif.mon_cb.rsp;
+      item.req = cfg.vif.mon_cb.req;
+      analysis_port.write(item);
+      req_analysis_port.write(item);
+    end
+  endfunction
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_sequencer.sv b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_sequencer.sv
new file mode 100644
index 0000000..eb653f8
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/entropy_src_xht_sequencer.sv
@@ -0,0 +1,12 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_sequencer extends dv_base_sequencer #(
+    .ITEM_T (entropy_src_xht_item),
+    .CFG_T  (entropy_src_xht_agent_cfg)
+);
+  `uvm_component_utils(entropy_src_xht_sequencer)
+  `uvm_component_new
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_base_device_seq.sv b/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_base_device_seq.sv
new file mode 100644
index 0000000..eba627f
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_base_device_seq.sv
@@ -0,0 +1,124 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class entropy_src_xht_base_device_seq extends dv_base_seq #(
+  .REQ         (entropy_src_xht_item),
+  .CFG_T       (entropy_src_xht_agent_cfg),
+  .SEQUENCER_T (entropy_src_xht_sequencer)
+);
+
+  bit [RNG_BUS_WIDTH - 1:0] window_data_q[$];
+  bit [15:0]                test_cnt_hi;
+  bit [15:0]                test_cnt_lo;
+
+  `uvm_object_utils(entropy_src_xht_base_device_seq)
+
+  `uvm_object_new
+
+  virtual function void update_item_rsp(entropy_src_xht_item item);
+    if (item.req.clear) begin
+       window_data_q.delete();
+       test_cnt_hi = ENTROPY_SRC_XHT_RSP_DEFAULT.test_cnt_hi;
+       test_cnt_lo = ENTROPY_SRC_XHT_RSP_DEFAULT.test_cnt_lo;
+    end else begin
+      if (item.req.active && item.req.entropy_bit_valid) begin
+        window_data_q.push_back(item.req.entropy_bit);
+        if (window_data_q.size() == item.req.health_test_window) begin
+          // Use a hash function to create output values that cover the
+          // complete range of test_cnt's, but also depend deterministically
+          // on the window data.
+          `uvm_info(`gfn, "Generating SHA3 hash", UVM_FULL)
+          test_cnt_hash(test_cnt_hi, test_cnt_lo);
+        end
+      end
+      if (item.req.active && item.req.window_wrap_pulse) begin
+        window_data_q.delete();
+      end
+    end
+
+    item.rsp.test_cnt_hi = test_cnt_hi;
+    item.rsp.test_cnt_lo = test_cnt_lo;
+    item.rsp.test_fail_hi_pulse = test_cnt_hi > item.req.thresh_hi;
+    item.rsp.test_fail_lo_pulse = test_cnt_lo < item.req.thresh_lo;
+    item.rsp.continuous_test = 1'b0;
+  endfunction
+
+  // For generating hypothetical HT results here we apply three basic requirements
+  // 1. The outputs should be deterministic with respect to the input data
+  // 2. The test outputs should have good coverage over the whole range of test_cnts (16 bits)
+  // 3. The low output should be less than or equal the high output.
+  // Given these three constraints, this function arbitrarily generates a SHA3 hash of the input
+  // data, and extracts the lowest 32 bits of the digest to create two synthetic test_cnt outputs.
+  function void test_cnt_hash(output bit [15:0] hi, output bit [15:0] lo);
+    localparam int ShaWidth = 384;
+    localparam int RngWordsPerByte = 8/RNG_BUS_WIDTH;
+
+    bit [7:0]  sha_msg[];
+    int        msg_size;
+    int        msg_idx = 0;
+    bit [7:0]  sha_digest[ShaWidth / 8];
+    int        rng_cntr = 0;
+    bit        whole_byte;
+    bit [7:0]  sha_byte = '0;
+    bit [15:0] output_a, output_b;
+
+    // The "message" will be equal to the number of bytes in the window
+    // rounding up to include any partial bytes.
+    msg_size = (window_data_q.size() + RngWordsPerByte - 1) / RngWordsPerByte;
+    sha_msg = new[msg_size];
+
+    for (int i = 0; i < window_data_q.size(); i++) begin
+      sha_byte = {sha_byte[(8 - RNG_BUS_WIDTH - 1):0], window_data_q[i]};
+      rng_cntr++;
+      whole_byte = (rng_cntr == RngWordsPerByte);
+      // Add data to the message whenever we have a whole byte,
+      // or if there is ever a dangling nibble.
+      if (whole_byte || (i == (window_data_q.size() - 1)) ) begin
+        sha_msg[msg_idx] = sha_byte;
+        msg_idx++;
+        rng_cntr = 0;
+      end
+    end
+
+    digestpp_dpi_pkg::c_dpi_sha3_384(sha_msg, msg_idx, sha_digest);
+    // Arbitrarily capture the lowest 4 bytes as a pair of outputs.
+    output_a = {sha_digest[3], sha_digest[2]};
+    output_b = {sha_digest[1], sha_digest[0]};
+
+    hi = (output_a < output_b) ? output_b : output_a;
+    lo = (output_a < output_b) ? output_a : output_b;
+
+  endfunction
+
+  virtual task body();
+    entropy_src_xht_item req_q[$];
+    `uvm_info(`gfn, "Starting seq", UVM_HIGH)
+    fork
+      forever begin : get_req
+        p_sequencer.req_analysis_fifo.get(req);
+        req_q.push_back(req);
+      end : get_req
+      forever begin : send_rsp
+        `uvm_info(`gfn, "Waiting for activity", UVM_DEBUG)
+        `DV_SPINWAIT_EXIT(wait(req_q.size());, wait(cfg.in_reset))
+        if (cfg.in_reset) begin
+          `uvm_info(`gfn, "Reset detected!", UVM_DEBUG)
+          test_cnt_hi = ENTROPY_SRC_XHT_RSP_DEFAULT.test_cnt_hi;
+          test_cnt_lo = ENTROPY_SRC_XHT_RSP_DEFAULT.test_cnt_lo;
+          wait (!cfg.in_reset)
+          req_q.delete();
+          window_data_q.delete();
+        end else begin
+          `uvm_info(`gfn, "Got Item!", UVM_DEBUG)
+          rsp = req_q.pop_front();
+          start_item(rsp);
+          update_item_rsp(rsp);
+          finish_item(rsp);
+          get_response(rsp);
+        end
+      end : send_rsp
+    join
+  endtask
+
+endclass
diff --git a/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_seq_list.sv b/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_seq_list.sv
new file mode 100644
index 0000000..1cdfd54
--- /dev/null
+++ b/hw/dv/sv/entropy_src_xht_agent/seq_lib/entropy_src_xht_seq_list.sv
@@ -0,0 +1,5 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+`include "entropy_src_xht_base_device_seq.sv"
diff --git a/hw/dv/sv/flash_phy_prim_agent/README.md b/hw/dv/sv/flash_phy_prim_agent/README.md
new file mode 100644
index 0000000..593f09b
--- /dev/null
+++ b/hw/dv/sv/flash_phy_prim_agent/README.md
@@ -0,0 +1,3 @@
+# Flash Phy Primitive UVM Agent
+
+FLASH_PHY_PRIM UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/flash_phy_prim_agent/doc/index.md b/hw/dv/sv/flash_phy_prim_agent/doc/index.md
deleted file mode 100644
index f75378e..0000000
--- a/hw/dv/sv/flash_phy_prim_agent/doc/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# FLASH_PHY_PRIM UVM Agent
-
-FLASH_PHY_PRIM UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/flash_phy_prim_agent/flash_phy_prim_monitor.sv b/hw/dv/sv/flash_phy_prim_agent/flash_phy_prim_monitor.sv
index bfe7b6b..6ad9e71 100644
--- a/hw/dv/sv/flash_phy_prim_agent/flash_phy_prim_monitor.sv
+++ b/hw/dv/sv/flash_phy_prim_agent/flash_phy_prim_monitor.sv
@@ -14,19 +14,31 @@
   // flash_phy_prim_agent_cov: cov
 
   uvm_analysis_port #(flash_phy_prim_item) eg_rtl_port[NumBanks];
+  uvm_analysis_port #(flash_phy_prim_item) rd_cmd_port[NumBanks];
+  uvm_analysis_port #(flash_phy_prim_item) eg_rtl_lm_port[NumBanks];
   flash_phy_prim_item w_item[NumBanks];
   flash_phy_prim_item r_item[NumBanks];
+  flash_phy_prim_item lm_item[NumBanks];
   logic [PhyDataW-1:0] write_buffer[NumBanks][$];
 
   `uvm_component_new
 
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
-    foreach(eg_rtl_port[i]) begin
+    foreach (eg_rtl_port[i]) begin
       eg_rtl_port[i] = new($sformatf("eg_rtl_port[%0d]", i), this);
+      rd_cmd_port[i] = new($sformatf("rd_cmd_port[%0d]", i), this);
+      eg_rtl_lm_port[i] = new($sformatf("eg_rtl_lm_port[%0d]", i), this);
     end
   endfunction
 
+  task reset_task;
+    forever begin
+      @(negedge cfg.vif.rst_n);
+      foreach (write_buffer[i]) write_buffer[i] = '{};
+    end
+  endtask // reset_task
+
   task run_phase(uvm_phase phase);
     if (cfg.scb_otf_en) begin
       `DV_SPINWAIT(wait(cfg.mon_start);,
@@ -34,6 +46,7 @@
       fork
         super.run_phase(phase);
         monitor_core();
+        reset_task();
       join_none
     end
   endtask
@@ -81,7 +94,15 @@
                 if (cfg.vif.req[j].rd_req & cfg.vif.req[j].prog_req) begin
                   `uvm_error(`gfn, $sformatf("Both prog and rd req are set"))
                 end else if (~cfg.vif.req[j].rd_req & cfg.vif.req[j].prog_req) begin
+                  // collect transaction for last time check
+                  collect_lm_item(j);
                   collect_wr_data(j);
+                end else if (cfg.vif.req[j].rd_req) begin
+                  collect_rd_cmd(j);
+                end else if (cfg.vif.req[j].pg_erase_req | cfg.vif.req[j].bk_erase_req |
+                             cfg.vif.req[j].erase_suspend_req) begin
+                  // collect erase error transactions
+                  collect_lm_item(j);
                 end
               end
             end
@@ -91,6 +112,14 @@
     end
   endtask // collect_trans
 
+  task collect_rd_cmd(int bank);
+    flash_phy_prim_item rcmd;
+    rcmd = flash_phy_prim_item::type_id::create("rcmd");
+    rcmd.req = cfg.vif.req[bank];
+
+    rd_cmd_port[bank].write(rcmd);
+  endtask // collect_rd_cmd
+
   task collect_wr_data(int bank);
     if (write_buffer[bank].size() == 0) begin
       w_item[bank] = flash_phy_prim_item::type_id::create($sformatf("w_item[%0d]", bank));
@@ -109,4 +138,13 @@
       write_buffer[bank] = {};
     end
   endtask // collect_item
+  function void collect_lm_item(int bank);
+    flash_phy_prim_item item;
+    `uvm_create_obj(flash_phy_prim_item, item)
+    item.req = cfg.vif.req[bank];
+    item.rsp = cfg.vif.rsp[bank];
+    eg_rtl_lm_port[bank].write(item);
+    `uvm_info("lm_debug", $sformatf("I sent bank%0d", bank),UVM_MEDIUM)
+  endfunction // collect_lm_item
+
 endclass
diff --git a/hw/dv/sv/i2c_agent/README.md b/hw/dv/sv/i2c_agent/README.md
new file mode 100644
index 0000000..39a8780
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/README.md
@@ -0,0 +1,3 @@
+# I2C DV UVM Agent
+
+I2C UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/i2c_agent/doc/index.md b/hw/dv/sv/i2c_agent/doc/index.md
deleted file mode 100644
index 5da5bcf..0000000
--- a/hw/dv/sv/i2c_agent/doc/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "I2C DV UVM Agent"
----
-
-I2C UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/i2c_agent/i2c_agent.core b/hw/dv/sv/i2c_agent/i2c_agent.core
index b207128..d9c04da 100644
--- a/hw/dv/sv/i2c_agent/i2c_agent.core
+++ b/hw/dv/sv/i2c_agent/i2c_agent.core
@@ -21,6 +21,8 @@
       - i2c_agent.sv: {is_include_file: true}
       - seq_lib/i2c_seq_list.sv: {is_include_file: true}
       - seq_lib/i2c_base_seq.sv: {is_include_file: true}
+      - seq_lib/i2c_device_response_seq.sv: {is_include_file: true}
+      - seq_lib/i2c_target_base_seq.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
 targets:
diff --git a/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv b/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv
index b823d10..737ca3b 100644
--- a/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv
+++ b/hw/dv/sv/i2c_agent/i2c_agent_cfg.sv
@@ -3,9 +3,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
 class i2c_agent_cfg extends dv_base_agent_cfg;
-
-  bit en_monitor = 1'b1; // enable monitor
-
   // this parameters can be set by test to slow down the agent's responses
   int host_latency_cycles = 0;
   int device_latency_cycles = 0;
@@ -13,13 +10,55 @@
   i2c_target_addr_mode_e target_addr_mode = Addr7BitMode;
 
   timing_cfg_t    timing_cfg;
+  bit host_stretch_test_mode = 0;
 
   virtual i2c_if  vif;
 
-  // this variables can be configured from test
+  bit     host_scl_start;
+  bit     host_scl_stop;
+  bit     host_scl_force_high;
+  bit     host_scl_force_low;
+
+  // In i2c test, between every transaction, assuming a new timing
+  // parameter is programmed. This means during a transaction,
+  // test should not update timing parameter.
+  // This variable indicates target DUT receives the end of the transaction
+  // and allow tb to program a new timing parameter.
+  bit     got_stop = 0;
+
+  int     sent_rd_byte = 0;
+  int     rcvd_rd_byte = 0;
+
+  // this variables can be configured from host test
   uint i2c_host_min_data_rw = 1;
   uint i2c_host_max_data_rw = 10;
 
+  // If 'host_scl_pause' is enabled, 'host_scl_pause_cyc' should be set to non zero value.
+  bit     host_scl_pause_en = 0;
+  bit     host_scl_pause_req = 0;
+  bit     host_scl_pause_ack = 0;
+  bit     host_scl_pause = 0;
+  int     host_scl_pause_cyc = 0;
+
+  // ack followed by stop test mode
+  bit     allow_ack_stop = 0;
+  bit     ack_stop_det = 0;
+  bit     allow_bad_addr = 0;
+  // target address is stored when dut is programmed
+  bit [6:0] target_addr0;
+  bit [6:0] target_addr1;
+  // store history of good and bad read target address
+  // '1' good. '0' bad
+  bit       read_addr_q[$];
+  bit       valid_addr;
+  bit       is_read;
+
+  // when this is set, driver can send 's' or 'p' in the middle of txn
+  bit       hot_glitch;
+
+  // reset agent only without resetting dut
+  bit       agent_rst = 0;
+
   `uvm_object_utils_begin(i2c_agent_cfg)
     `uvm_field_int(en_monitor,                                UVM_DEFAULT)
     `uvm_field_enum(i2c_target_addr_mode_e, target_addr_mode, UVM_DEFAULT)
diff --git a/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv b/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
index 02934be..1da83b8 100644
--- a/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
+++ b/hw/dv/sv/i2c_agent/i2c_agent_pkg.sv
@@ -17,9 +17,20 @@
   // Bus/Transaction types for the agent driver
   typedef enum logic [3:0] {
     None, DevAck, RdData, WrData,
-    HostStart, HostRStart, HostData, HostNAck, HostStop
+    HostStart, HostRStart, HostData, HostAck,
+    HostNAck, HostStop
   } drv_type_e;
 
+  // Driver phase
+  typedef enum int {
+    DrvIdle,
+    DrvStart,
+    DrvAddr,
+    DrvWr,
+    DrvRd,
+    DrvStop
+  } drv_phase_e;
+
   // register values
   typedef struct {
     // derived parameters
diff --git a/hw/dv/sv/i2c_agent/i2c_driver.sv b/hw/dv/sv/i2c_agent/i2c_driver.sv
index 3ee0d7a..8fcd506 100644
--- a/hw/dv/sv/i2c_agent/i2c_driver.sv
+++ b/hw/dv/sv/i2c_agent/i2c_driver.sv
@@ -9,7 +9,9 @@
 
   rand bit [7:0] rd_data[256]; // max length of read transaction
   byte wr_data;
-  byte address;
+  int scl_spinwait_timeout_ns = 1_000_000; // 1ms
+  bit scl_pause = 0;
+
   // get an array with unique read data
   constraint rd_data_c { unique { rd_data }; }
 
@@ -23,12 +25,25 @@
     end
   endtask : reset_signals
 
+  virtual task run_phase(uvm_phase phase);
+    fork
+      reset_signals();
+      get_and_drive();
+      begin
+        if (cfg.if_mode == Host) drive_scl();
+      end
+      begin
+        if (cfg.if_mode == Host) host_scl_pause_ctrl();
+      end
+      proc_hot_glitch();
+    join_none
+  endtask
+
   virtual task get_and_drive();
     i2c_item req;
-
     @(posedge cfg.vif.rst_ni);
     forever begin
-      release_bus();
+      if (cfg.if_mode == Device) release_bus();
       // driver drives bus per mode
       seq_item_port.get_next_item(req);
       fork
@@ -36,40 +51,82 @@
           fork
             begin
               if (cfg.if_mode == Device) drive_device_item(req);
-              else                       drive_host_item(req);
+              else drive_host_item(req);
             end
             // handle on-the-fly reset
             begin
               process_reset();
               req.clear_all();
             end
+            begin
+              // Agent hot reset. It only resets I2C agent.
+              // The DUT funtions normally without reset.
+              // This event only happens in directed test case so cannot set the timeout.
+              // It will be killed by disable fork when 'drive_*_item' is finished.
+              wait(cfg.agent_rst);
+              `uvm_info(`gfn, "drvdbg agent reset", UVM_MEDIUM)
+              req.clear_all();
+            end
           join_any
           disable fork;
         end: iso_fork
       join
       seq_item_port.item_done();
+
+      // When agent reset happens, flush all sequence items from sequencer request queue,
+      // before it starts a new sequence.
+      if (cfg.agent_rst) begin
+        i2c_item dummy;
+        do begin
+          seq_item_port.try_next_item(dummy);
+          if (dummy != null) seq_item_port.item_done();
+        end while (dummy != null);
+
+        repeat(2) @(cfg.vif.cb);
+        cfg.agent_rst = 0;
+      end
     end
   endtask : get_and_drive
 
-  // TODO: drive_host_item is WiP
   virtual task drive_host_item(i2c_item req);
-     unique case (req.drv_type)
+    // During pause period, let drive_scl control scl
+    `DV_WAIT(scl_pause == 1'b0,, scl_spinwait_timeout_ns, "drive_host_item")
+    `uvm_info(`gfn, $sformatf("drv: %s", req.drv_type.name), UVM_MEDIUM)
+    if (cfg.allow_bad_addr & !cfg.valid_addr) begin
+      if (req.drv_type inside {HostAck, HostNAck} & cfg.is_read) return;
+    end
+    case (req.drv_type)
       HostStart: begin
+        cfg.vif.drv_phase = DrvAddr;
         cfg.vif.host_start(cfg.timing_cfg);
+        cfg.host_scl_start = 1;
       end
       HostRStart: begin
         cfg.vif.host_rstart(cfg.timing_cfg);
       end
       HostData: begin
-        for (int i = $bits(wr_data) -1; i >= 0; i--) begin
-          cfg.vif.host_data(cfg.timing_cfg, wr_data[i]);
+        `uvm_info(`gfn, $sformatf("Driving host item 0x%x", req.wdata), UVM_MEDIUM)
+        for (int i = $bits(req.wdata) -1; i >= 0; i--) begin
+          cfg.vif.host_data(cfg.timing_cfg, req.wdata[i]);
         end
+        // Wait one more cycle for ack
+        cfg.vif.wait_scl(.iter(1), .tc(cfg.timing_cfg));
+      end
+      HostAck: begin
+        // Wait for read data and send ack
+        cfg.vif.wait_scl(.iter(8), .tc(cfg.timing_cfg));
+        cfg.vif.host_data(cfg.timing_cfg, 0);
       end
       HostNAck: begin
-        cfg.vif.host_nack(cfg.timing_cfg);
+        // Wait for read data and send nack
+        cfg.vif.wait_scl(.iter(8), .tc(cfg.timing_cfg));
+        cfg.vif.host_data(cfg.timing_cfg, 1);
       end
       HostStop: begin
+        cfg.vif.drv_phase = DrvStop;
+        cfg.host_scl_stop = 1;
         cfg.vif.host_stop(cfg.timing_cfg);
+        if (cfg.allow_bad_addr & !cfg.valid_addr)cfg.got_stop = 1;
       end
       default: begin
         `uvm_fatal(`gfn, $sformatf("\n  host_driver, received invalid request"))
@@ -79,10 +136,12 @@
 
   virtual task drive_device_item(i2c_item req);
     bit [7:0] rd_data_cnt = 8'd0;
+    bit [7:0] rdata;
 
-    unique case (req.drv_type)
+    case (req.drv_type)
       DevAck: begin
         cfg.timing_cfg.tStretchHostClock = gen_num_stretch_host_clks(cfg.timing_cfg);
+         `uvm_info(`gfn, $sformatf("sending an ack"), UVM_MEDIUM)
         fork
           // host clock stretching allows a high-speed host to communicate
           // with a low-speed device by setting TIMEOUT_CTRL.EN bit
@@ -95,20 +154,18 @@
         join
       end
       RdData: begin
-        if (rd_data_cnt == 8'd0) `DV_CHECK_MEMBER_RANDOMIZE_FATAL(rd_data)
+        `uvm_info(`gfn, $sformatf("Send readback data %0x", req.rdata), UVM_MEDIUM)
         for (int i = 7; i >= 0; i--) begin
-          cfg.vif.device_send_bit(cfg.timing_cfg, rd_data[rd_data_cnt][i]);
+          cfg.vif.device_send_bit(cfg.timing_cfg, req.rdata[i]);
         end
         `uvm_info(`gfn, $sformatf("\n  device_driver, trans %0d, byte %0d  %0x",
             req.tran_id, req.num_data+1, rd_data[rd_data_cnt]), UVM_DEBUG)
         // rd_data_cnt is rollled back (no overflow) after reading 256 bytes
         rd_data_cnt++;
       end
-      WrData:
-        // TODO: consider adding memory (associative array) in device_driver
-        for (int i = 7; i >= 0; i--) begin
-          cfg.vif.get_bit_data("host", cfg.timing_cfg, wr_data[i]);
-        end
+      WrData: begin
+        // nothing to do
+      end
       default: begin
         `uvm_fatal(`gfn, $sformatf("\n  device_driver, received invalid request"))
       end
@@ -120,7 +177,12 @@
     // intr_stretch_timeout_o interrupt would be generated uniformly
     // To test this feature more regressive, there might need a dedicated vseq (V2)
     // in which TIMEOUT_CTRL.EN is always set.
-    return $urandom_range(tc.tClockPulse, tc.tClockPulse + 2*tc.tTimeOut);
+
+    // If Stretch value is greater than 2*tTimeOut, it will create 2 interrupt events.
+    // Which can cause faluse error in 'host_stretch_testmode'.
+    // So, this value should be associated with tTimeout in host stretch testmode
+    if (cfg.host_stretch_test_mode) return (tc.tTimeOut + 1);
+    else return $urandom_range(tc.tClockPulse, tc.tClockPulse + 2*tc.tTimeOut);
   endfunction : gen_num_stretch_host_clks
 
   virtual task process_reset();
@@ -130,8 +192,117 @@
   endtask : process_reset
 
   virtual task release_bus();
+    `uvm_info(`gfn, "Driver released the bus", UVM_HIGH)
     cfg.vif.scl_o = 1'b1;
     cfg.vif.sda_o = 1'b1;
   endtask : release_bus
 
+  task drive_scl();
+    // This timeout is extremely long since read trasnactions will stretch
+    // whenever there are unhanded write commands or format bytes.
+    int scl_spinwait_timeout_ns = 100_000_000; // 100ms
+    forever begin
+      @(cfg.vif.cb);
+      wait(cfg.host_scl_start);
+      fork begin
+        fork
+          // Original scl driver thread
+          while(!cfg.host_scl_stop) begin
+            cfg.vif.scl_o <= 1'b0;
+            cfg.vif.wait_for_dly(cfg.timing_cfg.tClockLow);
+            cfg.vif.wait_for_dly(cfg.timing_cfg.tSetupBit);
+            cfg.vif.scl_o <= 1'b1;
+            `DV_WAIT(cfg.vif.scl_i === 1'b1,, scl_spinwait_timeout_ns, "i2c_drv_scl")
+            cfg.vif.wait_for_dly(cfg.timing_cfg.tClockPulse);
+
+            // There is a corner case s.t.
+            // pause req -> drv got stop back to back.
+            // if that happens, skip to the next txn cycle to pause
+            // to avoid unsuccessful host timeout
+            if (cfg.host_scl_pause_ack & !cfg.host_scl_stop) begin
+              scl_pause = 1;
+              cfg.vif.wait_for_dly(cfg.host_scl_pause_cyc);
+              scl_pause = 0;
+              cfg.host_scl_pause_ack = 0;
+            end
+            if (!cfg.host_scl_stop) cfg.vif.scl_o = 1'b0;
+            cfg.vif.wait_for_dly(cfg.timing_cfg.tHoldBit);
+          end
+          // Force quit thread
+          begin
+            wait(cfg.host_scl_force_high | cfg.host_scl_force_low);
+            cfg.host_scl_stop = 1;
+            if (cfg.host_scl_force_high) begin
+              cfg.vif.scl_o <= 1'b1;
+              cfg.vif.sda_o <= 1'b1;
+            end else begin
+              cfg.vif.scl_o <= 1'b0;
+              cfg.vif.sda_o <= 1'b0;
+            end
+          end
+        join_any
+        disable fork;
+      end join
+      cfg.host_scl_start = 0;
+      cfg.host_scl_stop = 0;
+    end
+  endtask
+
+  task host_scl_pause_ctrl();
+     forever begin
+        @(cfg.vif.cb);
+        if (cfg.host_scl_pause_req & cfg.host_scl_start & !cfg.host_scl_stop) begin
+          cfg.host_scl_pause_ack = 1;
+          `DV_WAIT(cfg.host_scl_pause_ack == 0,,
+                   scl_spinwait_timeout_ns, "host_scl_pause_ctrl")
+          cfg.host_scl_pause_req = 0;
+        end
+     end
+  endtask
+
+  // When 'cfg.hot_glitch' is triggered, it wait for data read state
+  // then add 'start' or 'stop' during data read state.
+  // Agent reset (without dut reset) is asserted after this event to
+  // clear driver and monitor state.
+  task proc_hot_glitch();
+    forever begin
+      @(cfg.vif.cb);
+      if (cfg.hot_glitch) begin
+        wait_for_read_data_state();
+        randcase
+          1: add_start();
+          1: add_stop();
+        endcase
+        cfg.agent_rst = 1;
+        cfg.hot_glitch = 0;
+        cfg.host_scl_force_high = 0;
+        cfg.host_scl_force_low = 0;
+        wait(!cfg.agent_rst);
+      end
+    end
+  endtask
+
+  // Task looking for data read state.
+  task wait_for_read_data_state();
+    int wait_timeout_ns = 500_000_000; // 500 ms
+    `DV_WAIT(cfg.vif.drv_phase == DrvRd,, wait_timeout_ns, "wait_for_read_data_state");
+    repeat(2) @(posedge cfg.vif.scl_i);
+  endtask
+
+  // Force quit scl stuck at high.
+  // 'start' will be added by the next sequence.
+  task add_start();
+    `uvm_info(`gfn, "proc_hot_glitch: add start", UVM_MEDIUM)
+    cfg.host_scl_force_high = 1;
+    cfg.vif.wait_for_dly(cfg.timing_cfg.tSetupStart);
+  endtask // add_start
+
+  // Force quit scl stuck at low.
+  // 'stop' is manually added here.
+  task add_stop();
+    `uvm_info(`gfn, "proc_hot_glitch: add stop", UVM_MEDIUM)
+    cfg.host_scl_force_low = 1;
+    cfg.host_scl_stop = 1;
+    cfg.vif.host_stop(cfg.timing_cfg);
+  endtask
 endclass : i2c_driver
diff --git a/hw/dv/sv/i2c_agent/i2c_if.sv b/hw/dv/sv/i2c_agent/i2c_if.sv
index d801f63..6f65e07 100644
--- a/hw/dv/sv/i2c_agent/i2c_if.sv
+++ b/hw/dv/sv/i2c_agent/i2c_if.sv
@@ -3,10 +3,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
 import i2c_agent_pkg::*;
+import uvm_pkg::*;
 
-interface i2c_if;
-  logic clk_i;
-  logic rst_ni;
+interface i2c_if(
+  input clk_i,
+  input rst_ni,
+  inout wire scl_io,
+  inout wire sda_io
+);
 
   // standard i2c interface pins
   logic scl_i;
@@ -14,9 +18,51 @@
   logic sda_i;
   logic sda_o;
 
+  assign scl_i = scl_io;
+  assign sda_i = sda_io;
+  assign scl_io = scl_o ? 1'bz : 1'b0;
+  assign sda_io = sda_o ? 1'bz : 1'b0;
+
+  string msg_id = "i2c_if";
+
+  int scl_spinwait_timeout_ns = 10_000_000; // 10ms
+
+  // Trace drivers' status
+  drv_phase_e drv_phase;
+
+  clocking cb @(posedge clk_i);
+    input scl_i;
+    input sda_i;
+    output scl_o;
+    output sda_o;
+  endclocking
   //---------------------------------
   // common tasks
   //---------------------------------
+
+  // This is literally same as '@(posedge scl_i)'
+  // In target mode, @(posedge scl_i) gives some glitch,
+  // so I have to use clocking block sampled signals.
+  task automatic p_edge_scl();
+    wait(cb.scl_i == 0);
+    wait(cb.scl_i == 1);
+  endtask
+
+  task automatic sample_target_data(timing_cfg_t tc, output bit data);
+    bit sample[16];
+    int idx = 0;
+    int su_idx;
+
+    wait(cb.scl_i == 0);
+    while (cb.scl_i == 0) begin
+      @(posedge clk_i);
+      sample[idx] = cb.sda_i;
+      idx = (idx + 1) % 16;
+    end
+    su_idx = (idx + 16 - 1 - tc.tSetupBit) % 16;
+    data = sample[su_idx];
+  endtask // sample_target_data
+
   task automatic wait_for_dly(int dly);
     repeat (dly) @(posedge clk_i);
   endtask : wait_for_dly
@@ -24,10 +70,14 @@
   task automatic wait_for_host_start(ref timing_cfg_t tc);
     forever begin
       @(negedge sda_i);
+      if (scl_i) begin
       wait_for_dly(tc.tHoldStart);
+      end else continue;
       @(negedge scl_i);
+      if (!sda_i) begin
       wait_for_dly(tc.tClockStart);
       break;
+      end else continue;
     end
   endtask: wait_for_host_start
 
@@ -77,7 +127,7 @@
   endtask: wait_for_host_stop_or_rstart
 
   task automatic wait_for_host_ack(ref timing_cfg_t tc);
-    @(negedge sda_i);
+    `uvm_info(msg_id, "Wait for host ack::Begin", UVM_HIGH)
     wait_for_dly(tc.tClockLow + tc.tSetupBit);
     forever begin
       @(posedge scl_i);
@@ -87,10 +137,11 @@
       end
     end
     wait_for_dly(tc.tHoldBit);
+    `uvm_info(msg_id, "Wait for host ack::Ack received", UVM_HIGH)
   endtask: wait_for_host_ack
 
   task automatic wait_for_host_nack(ref timing_cfg_t tc);
-    @(negedge sda_i);
+    `uvm_info(msg_id, "Wait for host nack::Begin", UVM_HIGH)
     wait_for_dly(tc.tClockLow + tc.tSetupBit);
     forever begin
       @(posedge scl_i);
@@ -100,6 +151,7 @@
       end
     end
     wait_for_dly(tc.tHoldBit);
+    `uvm_info(msg_id, "Wait for host nack::nack received", UVM_HIGH)
   endtask: wait_for_host_nack
 
   task automatic wait_for_host_ack_or_nack(timing_cfg_t tc,
@@ -144,14 +196,17 @@
                                  input bit bit_i);
     sda_o = 1'b1;
     wait_for_dly(tc.tClockLow);
+    `uvm_info(msg_id, "device_send_bit::Drive bit", UVM_HIGH)
     sda_o = bit_i;
     wait_for_dly(tc.tSetupBit);
     @(posedge scl_i);
+    `uvm_info(msg_id, "device_send_bit::Value sampled ", UVM_HIGH)
     // flip sda_target2host during the clock pulse of scl_host2target causes sda_unstable irq
     sda_o = ~sda_o;
     wait_for_dly(tc.tSdaUnstable);
     sda_o = ~sda_o;
     wait_for_dly(tc.tClockPulse + tc.tHoldBit - tc.tSdaUnstable);
+
     // not release/change sda_o until host clock stretch passes
     if (tc.enbTimeOut) wait(!scl_i);
     sda_o = 1'b1;
@@ -183,66 +238,89 @@
   task automatic get_bit_data(string src = "host",
                               ref timing_cfg_t tc,
                               output bit bit_o);
-    wait_for_dly(tc.tClockLow + tc.tSetupBit);
     @(posedge scl_i);
     if (src == "host") begin // host transmits data (addr/wr_data)
       bit_o = sda_i;
+      `uvm_info(msg_id, $sformatf("get bit data %d", bit_o), UVM_HIGH)
       // force sda_target2host low during the clock pulse of scl_host2target
       sda_o = 1'b0;
       wait_for_dly(tc.tSdaInterference);
       sda_o = 1'b1;
-      wait_for_dly(tc.tClockPulse + tc.tHoldBit - tc.tSdaInterference);
+      // The code below was originally written as
+      // wait_for_dly(tc.tClockPulse + tc.tHoldBit - tc.tSdaInterference);
+      // But this functionally should be identical to just waiting for the
+      // the nedgedge and then proceeding.  Keep a reference to the original
+      // just in case there is another test sequence that relied on this.
+      @(negedge scl_i);
+      wait_for_dly(tc.tHoldBit - tc.tSdaInterference);
     end else begin // target transmits data (rd_data)
-      bit_o = sda_o;
+      bit_o = sda_i;
       wait_for_dly(tc.tClockPulse + tc.tHoldBit);
     end
   endtask: get_bit_data
 
   task automatic host_start(ref timing_cfg_t tc);
-      sda_o = 1'b0;
-      wait_for_dly(tc.tHoldStart);
-      scl_o = 1'b0;
-      wait_for_dly(tc.tClockStart);
+    `DV_WAIT(scl_i === 1'b1,, scl_spinwait_timeout_ns, "host_start")
+    sda_o = 1'b0;
+    wait_for_dly(tc.tHoldStart);
+    scl_o = 1'b0;
+    wait_for_dly(tc.tClockStart);
   endtask: host_start
 
   task automatic host_rstart(ref timing_cfg_t tc);
-      scl_o = 1'b0;
-      wait_for_dly(tc.tSetupStart);
-      sda_o = 1'b0;
-      scl_o = 1'b1;
-      wait_for_dly(tc.tHoldStart);
-      scl_o = 1'b0;
-      wait_for_dly(tc.tClockStart);
+    @(posedge scl_i && sda_i);
+    wait_for_dly(tc.tSetupStart);
+    sda_o = 1'b0;
+    wait_for_dly(tc.tHoldStart);
+    wait_for_dly(tc.tHoldBit);
   endtask: host_rstart
 
   task automatic host_data(ref timing_cfg_t tc, input bit bit_i);
-      sda_o = bit_i;
-      wait_for_dly(tc.tClockLow);
-      wait_for_dly(tc.tSetupBit);
-      scl_o = 1'b1;
-      wait_for_dly(tc.tClockPulse);
-      scl_o = 1'b0;
-      wait_for_dly(tc.tHoldBit);
+    wait(scl_i === 1'b0);
+    sda_o = bit_i;
+    wait_for_dly(tc.tClockLow);
+    wait_for_dly(tc.tSetupBit);
+    wait(scl_i === 1'b1);
+    wait_for_dly(tc.tClockPulse);
+    wait(scl_i === 1'b0);
+    wait_for_dly(tc.tHoldBit);
+    sda_o = 1;
   endtask: host_data
 
   task automatic host_stop(ref timing_cfg_t tc);
-      sda_o = 1'b0;
-      wait_for_dly(tc.tClockStop);
-      scl_o = 1'b1;
-      wait_for_dly(tc.tSetupStop);
-      sda_o = 1'b0;
-      wait_for_dly(tc.tHoldStop);
+    // Stop is an SDA low to high transition whilst SCL is high. If both are high we cannot indicate
+    // a stop condition for this SCL pulse as that would require a high to low SDA transition which
+    // is the start signal.
+    if (scl_i === 1'b1 && sda_o === 1'b1) begin
+      `uvm_fatal(msg_id, "Cannot begin host_stop when both scl and sda are high")
+    end
+
+    // Ensure SDA Is low before SCL positive edge so a low to high transition can be generated. If
+    // SCL is high already SDA will be low already due to check above.
+    sda_o = 1'b0;
+    wait(scl_i === 1'b1);
+    wait_for_dly(tc.tClockStop);
+    scl_o = 1'b1;
+    wait_for_dly(tc.tSetupStop);
+    sda_o = 1'b1;
+    wait_for_dly(tc.tHoldStop);
   endtask: host_stop
 
   task automatic host_nack(ref timing_cfg_t tc);
-      sda_o = 1'b0;
-      wait_for_dly(tc.tClockLow);
-      sda_o = 1'b1;
-      wait_for_dly(tc.tSetupBit);
-      scl_o = 1'b1;
-      wait_for_dly(tc.tClockPulse);
-      scl_o = 1'b0;
-      wait_for_dly(tc.tHoldBit);
+    sda_o = 1'b0;
+    wait_for_dly(tc.tClockLow);
+    sda_o = 1'b1;
+    wait_for_dly(tc.tSetupBit);
+    scl_o = 1'b1;
+    wait_for_dly(tc.tClockPulse);
+    scl_o = 1'b0;
+    wait_for_dly(tc.tHoldBit);
   endtask: host_nack
 
+  task automatic wait_scl(int iter = 1, timing_cfg_t tc);
+    repeat(iter) begin
+      @(posedge scl_i);
+      wait_for_dly(tc.tClockPulse + tc.tHoldBit);
+    end
+  endtask // wait_scl
 endinterface : i2c_if
diff --git a/hw/dv/sv/i2c_agent/i2c_item.sv b/hw/dv/sv/i2c_agent/i2c_item.sv
index 22f09f4..ba0fd9d 100644
--- a/hw/dv/sv/i2c_agent/i2c_item.sv
+++ b/hw/dv/sv/i2c_agent/i2c_item.sv
@@ -23,7 +23,14 @@
   rand bit [7:0]           fbyte;
   rand bit                 nakok, rcont, read, stop, start;
 
-  constraint fbyte_c     { fbyte      inside {[0 : 127]}; }
+  // Incoming write data
+  logic [7:0]              wdata;
+  logic [7:0]              rdata;
+
+  // Use for debug print
+  string                   pname = "";
+
+  constraint fbyte_c     { fbyte      inside {[0 : 127] }; }
   constraint rcont_c     {
      solve read, stop before rcont;
      // for read request, rcont and stop must be complementary set
@@ -41,16 +48,18 @@
     `uvm_field_int(num_data,                UVM_DEFAULT)
     `uvm_field_int(start,                   UVM_DEFAULT)
     `uvm_field_int(stop,                    UVM_DEFAULT)
-    `uvm_field_queue_int(data_q,            UVM_DEFAULT)
+    `uvm_field_int(wdata,                   UVM_DEFAULT | UVM_NOCOMPARE)
+    `uvm_field_queue_int(data_q,            UVM_DEFAULT | UVM_NOPRINT)
     `uvm_field_queue_int(fmt_ovf_data_q,    UVM_DEFAULT | UVM_NOCOMPARE)
-    `uvm_field_int(rstart,                  UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
+    `uvm_field_int(rdata,                   UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
+    `uvm_field_int(rstart,                  UVM_DEFAULT | UVM_NOCOMPARE)
     `uvm_field_int(fbyte,                   UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
     `uvm_field_int(ack,                     UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
     `uvm_field_int(nack,                    UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
-    `uvm_field_int(read,                    UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
-    `uvm_field_int(rcont,                   UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
+    `uvm_field_int(read,                    UVM_DEFAULT | UVM_NOCOMPARE)
+    `uvm_field_int(rcont,                   UVM_DEFAULT | UVM_NOCOMPARE)
     `uvm_field_int(nakok,                   UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
-    `uvm_field_enum(drv_type_e,  drv_type,  UVM_DEFAULT | UVM_NOPRINT | UVM_NOCOMPARE)
+    `uvm_field_enum(drv_type_e,  drv_type,  UVM_DEFAULT | UVM_NOCOMPARE)
   `uvm_object_utils_end
 
   `uvm_object_new
@@ -61,6 +70,8 @@
     drv_type = None;
     data_q.delete();
     fmt_ovf_data_q.delete();
+    wdata = 0;
+    rdata = 0;
   endfunction : clear_data
 
   function void clear_flag();
@@ -77,4 +88,19 @@
     clear_flag();
   endfunction : clear_all
 
+  virtual function string convert2string();
+    string str = "";
+    str = {str, $sformatf("%s:tran_id  = %0d\n", pname, tran_id)};
+    str = {str, $sformatf("%s:bus_op   = %s\n",    pname, bus_op.name)};
+    str = {str, $sformatf("%s:addr     = 0x%2x\n", pname, addr)};
+    str = {str, $sformatf("%s:num_data = %0d\n", pname, num_data)};
+    str = {str, $sformatf("%s:start    = %1b\n", pname, start)};
+    str = {str, $sformatf("%s:stop     = %1b\n", pname, stop)};
+    str = {str, $sformatf("%s:read     = %1b\n", pname, read)};
+    str = {str, $sformatf("%s:rstart   = %1b\n", pname, rstart)};
+    foreach (data_q[i]) begin
+      str = {str, $sformatf("%s:data_q[%0d]=0x%2x\n", pname, i, data_q[i])};
+    end
+    return str;
+  endfunction
 endclass : i2c_item
diff --git a/hw/dv/sv/i2c_agent/i2c_monitor.sv b/hw/dv/sv/i2c_agent/i2c_monitor.sv
index a7e15b5..fe2f98b 100644
--- a/hw/dv/sv/i2c_agent/i2c_monitor.sv
+++ b/hw/dv/sv/i2c_agent/i2c_monitor.sv
@@ -15,6 +15,8 @@
   local i2c_item  mon_dut_item;
   local bit [7:0] mon_data;
   local uint      num_dut_tran = 0;
+  bit             mon_rstart = 0;
+  bit             target_read_phase = 0;
 
   `uvm_component_new
 
@@ -34,34 +36,70 @@
 
   virtual task run_phase(uvm_phase phase);
     wait(cfg.vif.rst_ni);
-    forever begin
+    if (cfg.if_mode == Host) begin
+      i2c_item full_item;
+      bit skip_the_loop;
       fork
-        begin: iso_fork
-          fork
-            begin
-              collect_thread(phase);
-            end
-            begin // if (on-the-fly) reset is monitored, drop the item
-              wait_for_reset_and_drop_item();
-              `uvm_info(`gfn, $sformatf("\nmonitor is reset, drop item\n%s",
-                  mon_dut_item.sprint()), UVM_DEBUG)
-            end
-          join_any
-          disable fork;
-        end: iso_fork
-      join
+        forever begin
+          wait(cfg.en_monitor);
+          cfg.valid_addr = 0;
+          skip_the_loop = 0;
+          cfg.vif.drv_phase = DrvIdle;
+          if (mon_dut_item.stop ||
+              (!mon_dut_item.stop && !mon_dut_item.start && !mon_dut_item.rstart)) begin
+            cfg.vif.wait_for_host_start(cfg.timing_cfg);
+            `uvm_info(`gfn, "\nmonitor, detect HOST START", UVM_MEDIUM)
+          end else begin
+            mon_dut_item.rstart = 1'b1;
+          end
+          mon_dut_item.tran_id = num_dut_tran;
+          mon_dut_item.start = 1'b1;
+          target_addr(skip_the_loop);
+          if (skip_the_loop) continue;
+          if (mon_dut_item.bus_op == BusOpRead) target_read();
+          else target_write();
+
+          // send rsp_item to scoreboard
+          `downcast(full_item, mon_dut_item.clone());
+          full_item.stop = 1'b1;
+          if (mon_dut_item.bus_op == BusOpRead) begin
+            full_item.read = 1;
+            analysis_port.write(full_item);
+          end
+          num_dut_tran++;
+          mon_dut_item.clear_data();
+        end // forever begin
+        ack_stop_mon();
+      join_none
+    end else begin
+      forever begin
+        fork
+          begin: iso_fork
+            fork
+              begin
+                collect_thread(phase);
+              end
+              begin // if (on-the-fly) reset is monitored, drop the item
+                wait_for_reset_and_drop_item();
+                `uvm_info(`gfn, $sformatf("\nmonitor is reset, drop item\n%s",
+                                          mon_dut_item.sprint()), UVM_DEBUG)
+              end
+            join_any
+            disable fork;
+          end: iso_fork
+        join
+      end
     end
   endtask : run_phase
 
-  // collect transactions forever
+  // Collect transactions forever
   virtual protected task collect_thread(uvm_phase phase);
     i2c_item full_item;
-
     wait(cfg.en_monitor);
     if (mon_dut_item.stop ||
        (!mon_dut_item.stop && !mon_dut_item.start && !mon_dut_item.rstart)) begin
       cfg.vif.wait_for_host_start(cfg.timing_cfg);
-      `uvm_info(`gfn, "\nmonitor, detect HOST START", UVM_DEBUG)
+      `uvm_info(`gfn, "\nmonitor, detect HOST START", UVM_HIGH)
     end else begin
       mon_dut_item.rstart = 1'b1;
     end
@@ -92,17 +130,19 @@
     mon_dut_item.tran_id = num_dut_tran;
     for (int i = cfg.target_addr_mode - 1; i >= 0; i--) begin
       cfg.vif.get_bit_data("host", cfg.timing_cfg, mon_dut_item.addr[i]);
-      `uvm_info(`gfn, $sformatf("\nmonitor, address[%0d] %b", i, mon_dut_item.addr[i]), UVM_DEBUG)
+      `uvm_info(`gfn, $sformatf("\nmonitor, address[%0d] %b", i, mon_dut_item.addr[i]), UVM_HIGH)
     end
-    `uvm_info(`gfn, $sformatf("\nmonitor, address 0x%0x", mon_dut_item.addr), UVM_DEBUG)
+    `uvm_info(`gfn, $sformatf("\nmonitor, address %0x", mon_dut_item.addr), UVM_HIGH)
     cfg.vif.get_bit_data("host", cfg.timing_cfg, rw_req);
+    `uvm_info(`gfn, $sformatf("\nmonitor, rw %d", rw_req), UVM_HIGH)
     mon_dut_item.bus_op = (rw_req) ? BusOpRead : BusOpWrite;
     // get ack after transmitting address
     mon_dut_item.drv_type = DevAck;
     `downcast(clone_item, mon_dut_item.clone());
+    `uvm_info(`gfn, $sformatf("Req analysis port: address thread"), UVM_HIGH)
     req_analysis_port.write(clone_item);
     cfg.vif.wait_for_device_ack(cfg.timing_cfg);
-    `uvm_info(`gfn, $sformatf("\nmonitor, address, detect TARGET ACK"), UVM_DEBUG)
+    `uvm_info(`gfn, "\nmonitor, address, detect TARGET ACK", UVM_HIGH)
   endtask : address_thread
 
   virtual protected task read_thread();
@@ -116,22 +156,23 @@
       // ask driver response read data
       mon_dut_item.drv_type = RdData;
       `downcast(clone_item, mon_dut_item.clone());
+      `uvm_info(`gfn, "Req analysis port: read thread", UVM_HIGH)
       req_analysis_port.write(clone_item);
       // sample read data
       for (int i = 7; i >= 0; i--) begin
         cfg.vif.get_bit_data("device", cfg.timing_cfg, mon_data[i]);
         `uvm_info(`gfn, $sformatf("\nmonitor, rd_data, trans %0d, byte %0d, bit[%0d] %0b",
-            mon_dut_item.tran_id, mon_dut_item.num_data+1, i, mon_data[i]), UVM_DEBUG)
+            mon_dut_item.tran_id, mon_dut_item.num_data+1, i, mon_data[i]), UVM_HIGH)
       end
       mon_dut_item.data_q.push_back(mon_data);
       mon_dut_item.num_data++;
       `uvm_info(`gfn, $sformatf("\nmonitor, rd_data, trans %0d, byte %0d 0x%0x",
-          mon_dut_item.tran_id, mon_dut_item.num_data, mon_data), UVM_DEBUG)
+          mon_dut_item.tran_id, mon_dut_item.num_data, mon_data), UVM_HIGH)
       // sample host ack/nack (in the last byte, nack can be issue if rcont is set)
       cfg.vif.wait_for_host_ack_or_nack(cfg.timing_cfg, mon_dut_item.ack, mon_dut_item.nack);
       `DV_CHECK_NE_FATAL({mon_dut_item.ack, mon_dut_item.nack}, 2'b11)
       `uvm_info(`gfn, $sformatf("\nmonitor, detect HOST %s",
-          (mon_dut_item.ack) ? "ACK" : "NO_ACK"), UVM_DEBUG)
+          (mon_dut_item.ack) ? "ACK" : "NO_ACK"), UVM_HIGH)
       // if nack is issued, next bit must be stop or rstart
       if (mon_dut_item.nack) begin
         cfg.vif.wait_for_host_stop_or_rstart(cfg.timing_cfg,
@@ -139,7 +180,7 @@
                                              mon_dut_item.stop);
         `DV_CHECK_NE_FATAL({mon_dut_item.rstart, mon_dut_item.stop}, 2'b11)
         `uvm_info(`gfn, $sformatf("\nmonitor, rd_data, detect HOST %s",
-            (mon_dut_item.stop) ? "STOP" : "RSTART"), UVM_DEBUG)
+            (mon_dut_item.stop) ? "STOP" : "RSTART"), UVM_HIGH)
       end
     end
   endtask : read_thread
@@ -149,11 +190,15 @@
 
     mon_dut_item.stop   = 1'b0;
     mon_dut_item.rstart = 1'b0;
+    `uvm_info(`gfn, $sformatf("host_write_thread begin: tran_id:%0d num_data%0d",
+                              mon_dut_item.tran_id, mon_dut_item.num_data), UVM_HIGH)
+
     while (!mon_dut_item.stop && !mon_dut_item.rstart) begin
       fork
         begin : iso_fork_write
           fork
             begin
+               `uvm_info(`gfn, "Req analysis port: write thread data", UVM_HIGH)
               // ask driver's response a write request
               mon_dut_item.drv_type = WrData;
               `downcast(clone_item, mon_dut_item.clone());
@@ -161,10 +206,16 @@
               for (int i = 7; i >= 0; i--) begin
                 cfg.vif.get_bit_data("host", cfg.timing_cfg, mon_data[i]);
               end
+              `uvm_info(`gfn, $sformatf("Monitor collected data %0x", mon_data), UVM_HIGH)
               mon_dut_item.num_data++;
               mon_dut_item.data_q.push_back(mon_data);
+              `uvm_info(`gfn, $sformatf("host_write_thread data %2x num_data:%0d",
+                                        mon_data, mon_dut_item.num_data), UVM_HIGH)
+              // send device ack to host write
+              mon_dut_item.wdata = mon_data;
               mon_dut_item.drv_type = DevAck;
               `downcast(clone_item, mon_dut_item.clone());
+              `uvm_info(`gfn, $sformatf("Req analysis port: write thread ack"), UVM_HIGH)
               req_analysis_port.write(clone_item);
               cfg.vif.wait_for_device_ack(cfg.timing_cfg);
             end
@@ -174,23 +225,215 @@
                                                    mon_dut_item.stop);
               `DV_CHECK_NE_FATAL({mon_dut_item.rstart, mon_dut_item.stop}, 2'b11)
               `uvm_info(`gfn, $sformatf("\nmonitor, wr_data, detect HOST %s %0b",
-                  (mon_dut_item.stop) ? "STOP" : "RSTART", mon_dut_item.stop), UVM_DEBUG)
+                  (mon_dut_item.stop) ? "STOP" : "RSTART", mon_dut_item.stop), UVM_HIGH)
             end
           join_any
           disable fork;
         end : iso_fork_write
       join
     end
+    `uvm_info(`gfn, $sformatf("host_write_thread end: tran_id:%0d num_data:%0d",
+                              mon_dut_item.tran_id, mon_dut_item.num_data), UVM_HIGH)
   endtask : write_thread
 
   // update of_to_end to prevent sim finished when there is any activity on the bus
   // ok_to_end = 0 (bus busy) / 1 (bus idle)
   virtual task monitor_ready_to_end();
-    forever begin
-      @(cfg.vif.scl_i or cfg.vif.sda_i or cfg.vif.scl_o or cfg.vif.sda_o);
-      ok_to_end = (cfg.vif.scl_i == 1'b1) && (cfg.vif.sda_i == 1'b1);
+    if (cfg.if_mode == Host) begin
+      int scl_cnt = 0;
+      if (cfg.en_monitor) begin
+        ok_to_end = 0;
+      end
+      forever begin
+        @(cfg.vif.cb);
+        if (cfg.vif.scl_i) scl_cnt++;
+        else scl_cnt = 0;
+        if (scl_cnt > 100) ok_to_end = 1;
+      end
+    end else begin
+      forever begin
+        @(cfg.vif.scl_i or cfg.vif.sda_i or cfg.vif.scl_o or cfg.vif.sda_o);
+        ok_to_end = (cfg.vif.scl_i == 1'b1) && (cfg.vif.sda_i == 1'b1);
+      end
     end
   endtask : monitor_ready_to_end
 
-endclass : i2c_monitor
+  task target_addr(ref bit skip);
+    bit r_bit = 1'b0;
+    skip = 0;
+    cfg.vif.drv_phase = DrvAddr;
+    // collecting address
+    for (int i = cfg.target_addr_mode - 1; i >= 0; i--) begin
+      cfg.vif.p_edge_scl();
+      mon_dut_item.addr[i] = cfg.vif.cb.sda_i;
+      `uvm_info(`gfn, $sformatf("\nmonitor, address[%0d] %b", i, mon_dut_item.addr[i]),
+                UVM_HIGH)
+    end
+    `uvm_info(`gfn, $sformatf("\nmonitor, address %0x", mon_dut_item.addr), UVM_MEDIUM)
+    cfg.vif.p_edge_scl();
+    r_bit = cfg.vif.cb.sda_i;
+    `uvm_info(`gfn, $sformatf("\nmonitor, rw %d", r_bit), UVM_MEDIUM)
+    mon_dut_item.bus_op = (r_bit) ? BusOpRead : BusOpWrite;
+    cfg.valid_addr = is_target_addr(mon_dut_item.addr);
+    cfg.is_read = r_bit;
 
+    if (mon_dut_item.bus_op == BusOpRead) begin
+      cfg.read_addr_q.push_back(cfg.valid_addr);
+    end
+    `uvm_info(`gfn, $sformatf("allow_bad_addr : %0d is_target_addr:%0d",
+                              cfg.allow_bad_addr, cfg.valid_addr), UVM_MEDIUM)
+    if (cfg.allow_bad_addr & !cfg.valid_addr) begin
+      // skip rest of transaction and wait for next start
+      `uvm_info(`gfn, $sformatf("illegal address :0x%x", mon_dut_item.addr), UVM_MEDIUM)
+      mon_dut_item.clear_all();
+      skip = 1;
+    end else begin
+      // expect target addr ack
+      cfg.vif.sample_target_data(cfg.timing_cfg, r_bit);
+      `DV_CHECK_CASE_EQ(r_bit, 1'b0)
+    end
+  endtask
+
+  // Rewrite read / write task using glitch free edge functions.
+  task target_read();
+    mon_dut_item.stop   = 1'b0;
+    mon_dut_item.rstart = 1'b0;
+    mon_dut_item.ack    = 1'b0;
+    mon_dut_item.nack   = 1'b0;
+    mon_rstart = 0;
+    target_read_phase = 1;
+    // Previous data collecting thread replied on nack / stop
+    // For ack / stop test, this thread need to be forked with
+    // separate ack_stop_monitor
+    cfg.vif.drv_phase = DrvRd;
+
+    fork begin
+      fork
+        // This data collecting thread is closed upon setting mon_rstart
+        // mon_rstart, when ack_stop_mon task detect ack / stop event
+        while (!mon_dut_item.stop && !mon_dut_item.rstart) begin
+          // ask driver response read data
+          mon_dut_item.drv_type = RdData;
+          for (int i = 7; i >= 0; i--) begin
+            cfg.vif.sample_target_data(cfg.timing_cfg, mon_data[i]);
+            `uvm_info(`gfn, $sformatf("\nmonitor, target_read, trans %0d, byte %0d, bit[%0d] %0b",
+                      mon_dut_item.tran_id, mon_dut_item.num_data+1, i, mon_data[i]), UVM_HIGH)
+          end
+          cfg.vif.wait_for_host_ack_or_nack(cfg.timing_cfg, mon_dut_item.ack, mon_dut_item.nack);
+          `DV_CHECK_NE_FATAL({mon_dut_item.ack, mon_dut_item.nack}, 2'b11)
+          `uvm_info(`gfn, $sformatf("\nmonitor, target_read detect HOST %s",
+                                    (mon_dut_item.ack) ? "ACK" : "NO_ACK"), UVM_MEDIUM)
+
+          cfg.rcvd_rd_byte++;
+          // Hold data push until send ack / nack
+          mon_dut_item.data_q.push_back(mon_data);
+          mon_dut_item.num_data++;
+          `uvm_info(`gfn, $sformatf("\nmonitor, target_read, trans %0d, byte %0d 0x%0x",
+                    mon_dut_item.tran_id, mon_dut_item.num_data, mon_data), UVM_MEDIUM)
+
+          // if nack is issued, next bit must be stop or rstart
+          if (mon_dut_item.nack) begin
+            cfg.vif.wait_for_host_stop_or_rstart(cfg.timing_cfg,
+                                                 mon_dut_item.rstart,
+                                                 mon_dut_item.stop);
+            `DV_CHECK_NE_FATAL({mon_dut_item.rstart, mon_dut_item.stop}, 2'b11)
+            `uvm_info(`gfn, $sformatf("\nmonitor, target_read, detect HOST %s",
+                                      (mon_dut_item.stop) ? "STOP" : "RSTART"), UVM_MEDIUM)
+            if (mon_dut_item.stop) cfg.got_stop = 1;
+          end
+        end
+        begin
+          // This is undeterministic event so cannot set the timeout,
+          // but this thread will be terminated by the other thread.
+          wait((cfg.allow_ack_stop & mon_rstart) | cfg.agent_rst);
+          if (cfg.agent_rst) begin
+            int wait_timeout_ns = 1_000_000; // 1 ms
+            @(cfg.vif.cb);
+            mon_dut_item.clear_all();
+            `DV_WAIT((!cfg.agent_rst),, wait_timeout_ns, "target_read:agent reset de-assert");
+            cfg.got_stop = 1;
+            `uvm_info(`gfn, "monitor forceout from target_read", UVM_MEDIUM)
+          end
+        end
+      join_any
+      disable fork;
+    end join
+    target_read_phase = 0;
+
+    `uvm_info(`gfn, $sformatf("target_read end stop:%0d rs:%0d",
+                              mon_dut_item.stop, mon_dut_item.rstart), UVM_HIGH)
+  endtask
+
+  task target_write();
+    bit r_bit;
+    mon_dut_item.stop   = 1'b0;
+    mon_dut_item.rstart = 1'b0;
+    cfg.vif.drv_phase = DrvWr;
+    fork begin
+      fork
+        while (!mon_dut_item.stop && !mon_dut_item.rstart) begin
+          mon_dut_item.drv_type = WrData;
+          for (int i = 7; i >= 0; i--) begin
+            cfg.vif.p_edge_scl();
+          end
+          // check for ack
+          cfg.vif.p_edge_scl();
+          r_bit = cfg.vif.cb.sda_i;
+          `uvm_info(`gfn, $sformatf("\nmonitor, target_write detect HOST %s",
+                                   (!r_bit) ? "ACK" : "NO_ACK"), UVM_MEDIUM)
+          // if nack is issued, next bit must be stop or rstart
+
+          if (!r_bit) begin
+            cfg.vif.wait_for_host_stop_or_rstart(cfg.timing_cfg,
+                                                 mon_dut_item.rstart,
+                                                 mon_dut_item.stop);
+            `DV_CHECK_NE_FATAL({mon_dut_item.rstart, mon_dut_item.stop}, 2'b11)
+            `uvm_info(`gfn, $sformatf("\nmonitor, target_write detect HOST %s",
+                                      (mon_dut_item.stop) ? "STOP" : "RSTART"), UVM_MEDIUM)
+            if (mon_dut_item.stop) cfg.got_stop = 1;
+          end
+        end
+        begin
+          int wait_timeout_ns = 1_000_000; // 1 ms
+          wait(cfg.agent_rst);
+          @(cfg.vif.cb);
+          mon_dut_item.clear_all();
+          `DV_WAIT((!cfg.agent_rst),, wait_timeout_ns, "target_write:agent reset de-assert");
+          cfg.got_stop = 1;
+          `uvm_info(`gfn,"mon forceout from target_write", UVM_MEDIUM)
+        end
+      join_any
+      disable fork;
+    end join
+  endtask // target_write
+
+  task ack_stop_mon();
+    bit stop, rstart;
+    forever begin
+      @(cfg.vif.cb);
+      if (target_read_phase) begin
+        cfg.vif.wait_for_host_stop_or_rstart(cfg.timing_cfg, rstart, stop);
+        `DV_CHECK_NE_FATAL({rstart, stop}, 2'b11)
+        if ((rstart | stop) & mon_dut_item.ack) begin
+          if (cfg.allow_ack_stop) begin
+            `uvm_info("ack_stop_mon",
+                      $sformatf("detect ack_stop %2b (rs,p)", {rstart, stop}), UVM_MEDIUM)
+            mon_rstart = 1;
+            if (stop) cfg.ack_stop_det = 1;
+          end else begin
+            `uvm_error(`gfn, "ack_stop detected")
+          end
+          mon_dut_item.rstart = rstart;
+          mon_dut_item.stop = stop;
+          if (stop) cfg.got_stop = 1;
+          rstart = 0;
+          stop = 0;
+        end
+      end
+    end
+  endtask
+
+  function bit is_target_addr(bit [6:0] addr);
+    return (addr == cfg.target_addr0 || addr == cfg.target_addr1);
+  endfunction
+endclass : i2c_monitor
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv
index 84d69d9..0d2c69c 100644
--- a/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_base_seq.sv
@@ -11,10 +11,13 @@
   `uvm_object_new
 
   // queue monitor requests which ask the re-active driver to response host dut
-  i2c_item req_q[$];
+  REQ req_q[$];
 
   // data to be sent to target dut
   bit [7:0] data_q[$];
+
+  // Stops running this sequence
+  protected bit stop;
   // constrain size of data sent/received
   constraint data_q_size_c {
     data_q.size() inside {[cfg.i2c_host_min_data_rw : cfg.i2c_host_max_data_rw]};
@@ -22,33 +25,43 @@
 
   virtual task body();
     if (cfg.if_mode == Device) begin
-      // get seq for agent running in Device mode
-      fork
-        forever begin
-          i2c_item  req;
-          p_sequencer.req_analysis_fifo.get(req);
-          req_q.push_back(req);
-        end
-        forever begin
-          i2c_item  rsp;
-          wait(req_q.size > 0);
-          rsp = req_q.pop_front();
-          start_item(rsp);
-          finish_item(rsp);
-        end
-      join
+      send_device_mode_txn();
     end else begin
-      // get seq for agent running in Host mode
-      req = i2c_item::type_id::create("req");
-      start_item(req);
-      `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
-                                     data_q.size() == local::data_q.size();
-                                     foreach (data_q[i]) {
-                                       data_q[i] == local::data_q[i];
-                                     })
-      finish_item(req);
-      get_response(rsp);
+      send_host_mode_txn();
     end
   endtask : body
 
+  virtual task send_device_mode_txn();
+    // get seq for agent running in Device mode
+    bit [7:0] rdata;
+    forever begin
+      p_sequencer.req_analysis_fifo.get(req);
+      // if it's a read type response, randomize the return data
+      if (req.drv_type == RdData) begin
+        `DV_CHECK_STD_RANDOMIZE_FATAL(rdata)
+        req.rdata = rdata;
+      end
+      start_item(req);
+      finish_item(req);
+    end
+  endtask
+
+  virtual task send_host_mode_txn();
+    // get seq for agent running in Host mode
+    req = REQ::type_id::create("req");
+    start_item(req);
+    `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
+                                   data_q.size() == local::data_q.size();
+                                   foreach (data_q[i]) {
+                                     data_q[i] == local::data_q[i];
+                                   })
+    finish_item(req);
+    get_response(rsp);
+  endtask
+
+  virtual task seq_stop();
+    stop = 1'b1;
+    wait_for_sequence_state(UVM_FINISHED);
+  endtask : seq_stop
+
 endclass : i2c_base_seq
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_device_response_seq.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_device_response_seq.sv
new file mode 100644
index 0000000..4459f88
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_device_response_seq.sv
@@ -0,0 +1,61 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_device_response_seq extends i2c_base_seq;
+  `uvm_object_utils(i2c_device_response_seq)
+  `uvm_object_new
+
+  REQ w_q[256][$];
+
+  protected virtual task get_dev_req(output REQ req);
+    fork
+      begin: isolation_thread
+        fork
+          begin
+            REQ item;
+            p_sequencer.req_analysis_fifo.get(item);
+            `downcast(req, item)
+          end
+          wait (stop);
+        join_any
+        #0;
+        disable fork;
+      end
+    join
+  endtask
+
+  task send_device_mode_txn();
+    // get seq for agent running in Device mode
+    fork
+      forever begin
+        REQ req;
+        get_dev_req(req);
+        if (req != null) begin
+          `uvm_info(`gfn, $sformatf("bus_op:%s drv_type:%s data_q:%p",req.bus_op.name,
+                                    req.drv_type.name, req.data_q), UVM_HIGH)
+          if (req.bus_op == BusOpWrite && req.drv_type == DevAck && req.data_q.size() > 0) begin
+            w_q[req.addr].push_back(req);
+          end else if (req.bus_op == BusOpRead && req.drv_type == RdData) begin
+            if (w_q[req.addr].size() == 0) begin
+              `uvm_fatal(`gfn, $sformatf("Read requested on empty Write queue"))
+            end else begin
+              rsp = w_q[req.addr].pop_front();
+              req.rdata = rsp.wdata;
+            end
+          end
+          start_item(req);
+          finish_item(req);
+        end
+        if (stop) break;
+      end // forever begin
+      forever begin
+        @(negedge cfg.vif.rst_ni);
+        foreach (w_q[i]) begin
+          w_q[i].delete();
+        end
+      end
+    join
+  endtask // send_device_mode
+
+endclass // i2c_device_response_seq
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv
index 89f7fe7..1e943c9 100644
--- a/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_seq_list.sv
@@ -3,3 +3,5 @@
 // SPDX-License-Identifier: Apache-2.0
 
 `include "i2c_base_seq.sv"
+`include "i2c_device_response_seq.sv"
+`include "i2c_target_base_seq.sv"
diff --git a/hw/dv/sv/i2c_agent/seq_lib/i2c_target_base_seq.sv b/hw/dv/sv/i2c_agent/seq_lib/i2c_target_base_seq.sv
new file mode 100644
index 0000000..676af76
--- /dev/null
+++ b/hw/dv/sv/i2c_agent/seq_lib/i2c_target_base_seq.sv
@@ -0,0 +1,24 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class i2c_target_base_seq extends i2c_base_seq;
+  `uvm_object_utils(i2c_target_base_seq)
+  `uvm_object_new
+
+  virtual task body();
+    // i2c_item needs more test specific parameters for proper randomization.
+    // Rather than keeping duplicate parameters in i2c_agent_cfg and i2c_seq_cfg,
+    // use i2c_seq_cfg parameters to simplify implementation.
+    // So, instead of randomizing i2c_item here, assuming i2c_item is randomized in
+    // vseq and fed to req_q here.
+    `DV_SPINWAIT_EXIT(wait (req_q.size() > 0);
+                      while (req_q.size() > 0) begin
+                        req = req_q.pop_front();
+                        start_item(req);
+                        finish_item(req);
+                      end
+                      , wait (stop);)
+  endtask : body
+
+endclass : i2c_target_base_seq
diff --git a/hw/dv/sv/jtag_agent/README.md b/hw/dv/sv/jtag_agent/README.md
new file mode 100644
index 0000000..6bed9e9
--- /dev/null
+++ b/hw/dv/sv/jtag_agent/README.md
@@ -0,0 +1,79 @@
+# JTAG DV UVM Agent
+
+JTAG UVM Agent is extended from the
+[DV library agent](../dv_lib/README.md) classes, which
+provide common knobs and verification logic that help ease the effort of
+writing interface agents.
+
+## Block diagram
+
+![jtag_agent](./doc/block_diagram.svg)
+
+## jtag_if
+
+This interface is used to drive and monitor the JTAG transactions. It is the
+physical layer of the testbench, which directly connects to the JTAG signals
+of the DUT.
+
+## jtag_dtm_reg_block
+
+This RAL model represents the JTAG DTM register space. This region is defined
+in the
+[RISC-V debug specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf),
+chapter 6. It mirrors the registers in the design, which is useful to predict
+what we thought we wrote to these registers and compare what we expect the
+design to return on reads. This RAL model is hand-written, given that it is
+only a few set of registers. It can be used to drive reads and writes using
+our standard [CSR routines](../csr_utils/README.md). It
+can also be used in higher level testbench components to develop the predicted
+verification logic.
+
+## jtag_dtm_reg_adapter
+
+This object extends from the standard `uvm_reg_adapter` class, to implement the
+underlying mechanics of accessing the modeled JTAG DTM registers in
+`jtag_dtm_reg_block` using the JTAG sequencer. An instance of this class is
+created in the `jtag_agent`, and hooked up to the `default_map` of
+`jtag_reg_block` to facilitate the CSR accesses via the RAL model.
+
+## jtag_agent_cfg
+
+This agent configuration class provides a handle to the `jtag_if` instance.
+It also creates the JTAG DTM RAL model as a member. An instance of this class
+is created externally and passed on to `jtag_agent` via `uvm_config_db`. The
+`jtag_agent` then sets the `cfg` handle in all sub-components, so that they all
+have access to it.
+
+## jtag_item
+
+This represents the JTAG transaction class. It holds the IR as well as DR
+information. It is used by the driver as well as the monitor. The
+`ir_len` and `dr_len` fields indicate whether to drive or sample an IR update,
+or DR update or both types of transactions. If each of these lengths
+is 0, then that type of transaction is not driven / monitored.
+
+The `dout` field indicates the captured DR value.
+
+## jtag_driver
+
+This component receives transactions from sequences via the sequencer using
+the standard UVM TLM mechanism. If the `ir_len` and `dr_len` in the received
+transaction item are both non-zero, it drives the IR update first, followed
+by the DR update. If any of these lengths is 0, then that type of transaction
+is not driven. It throws an error if both of these lengths are 0.
+
+When driving the DR update, it also captures the DR by sampling `tdi` into the
+transaction item's `dout` field, which is sent back to the sequence as
+response.
+
+Currently, the driver is only implemented in host mode. The device mode
+implementation will be addressed in future.
+
+## jtag_monitor
+
+The JTAG monitor implements an FSM to detect an IR update cycle or a DR update
+cycle. It discretely captures the IR update and DR update separately and writes
+the transaction to the `analysis_port`. Transactions coming from this monitor
+on the `analysis_port` will only have either the `ir_len` or the `dr_len` set
+to a non-zero value. The higher level testbench components must check these
+lengths to determine whether the IR was updated or the DR.
diff --git a/hw/dv/sv/jtag_agent/doc/index.md b/hw/dv/sv/jtag_agent/doc/index.md
deleted file mode 100644
index 6694a3f..0000000
--- a/hw/dv/sv/jtag_agent/doc/index.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-title: "JTAG DV UVM Agent"
----
-
-JTAG UVM Agent is extended from the
-[DV library agent]({{<relref "hw/dv/sv/dv_lib/doc" >}}) classes, which
-provide common knobs and verification logic that help ease the effort of
-writing interface agents.
-
-## Block diagram
-
-![jtag_agent](block_diagram.svg)
-
-## jtag_if
-
-This interface is used to drive and monitor the JTAG transactions. It is the
-physical layer of the testbench, which directly connects to the JTAG signals
-of the DUT.
-
-## jtag_dtm_reg_block
-
-This RAL model represents the JTAG DTM register space. This region is defined
-in the
-[RISC-V debug specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf),
-chapter 6. It mirrors the registers in the design, which is useful to predict
-what we thought we wrote to these registers and compare what we expect the
-design to return on reads. This RAL model is hand-written, given that it is
-only a few set of registers. It can be used to drive reads and writes using
-our standard [CSR routines]({{<relref "hw/dv/sv/csr_utils/doc">}}). It
-can also be used in higher level testbench components to develop the predicted
-verification logic.
-
-## jtag_dtm_reg_adapter
-
-This object extends from the standard `uvm_reg_adapter` class, to implement the
-underlying mechanics of accessing the modeled JTAG DTM registers in
-`jtag_dtm_reg_block` using the JTAG sequencer. An instance of this class is
-created in the `jtag_agent`, and hooked up to the `default_map` of
-`jtag_reg_block` to facilitate the CSR accesses via the RAL model.
-
-## jtag_agent_cfg
-
-This agent configuration class provides a handle to the `jtag_if` instance.
-It also creates the JTAG DTM RAL model as a member. An instance of this class
-is created externally and passed on to `jtag_agent` via `uvm_config_db`. The
-`jtag_agent` then sets the `cfg` handle in all sub-components, so that they all
-have access to it.
-
-## jtag_item
-
-This represents the JTAG transaction class. It holds the IR as well as DR
-information. It is used by the driver as well as the monitor. The
-`ir_len` and `dr_len` fields indicate whether to drive or sample an IR update,
-or DR update or both types of transactions. If each of these lengths
-is 0, then that type of transaction is not driven / monitored.
-
-The `dout` field indicates the captured DR value.
-
-## jtag_driver
-
-This component receives transactions from sequences via the sequencer using
-the standard UVM TLM mechanism. If the `ir_len` and `dr_len` in the received
-transaction item are both non-zero, it drives the IR update first, followed
-by the DR update. If any of these lengths is 0, then that type of transaction
-is not driven. It throws an error if both of these lengths are 0.
-
-When driving the DR update, it also captures the DR by sampling `tdi` into the
-transaction item's `dout` field, which is sent back to the sequence as
-response.
-
-Currently, the driver is only implemented in host mode. The device mode
-implementation will be addressed in future.
-
-## jtag_monitor
-
-The JTAG monitor implements an FSM to detect an IR update cycle or a DR update
-cycle. It discretely captures the IR update and DR update separately and writes
-the transaction to the `analysis_port`. Transactions coming from this monitor
-on the `analysis_port` will only have either the `ir_len` or the `dr_len` set
-to a non-zero value. The higher level testbench components must check these
-lengths to determine whether the IR was updated or the DR.
diff --git a/hw/dv/sv/jtag_agent/jtag_driver.sv b/hw/dv/sv/jtag_agent/jtag_driver.sv
index b5a466e..193962b 100644
--- a/hw/dv/sv/jtag_agent/jtag_driver.sv
+++ b/hw/dv/sv/jtag_agent/jtag_driver.sv
@@ -17,20 +17,26 @@
   logic [JTAG_IRW-1:0]  selected_ir;
   uint                  selected_ir_len;
 
-  // reset signals
+  // do reset signals (function)
+  virtual function void do_reset_signals();
+    if (cfg.if_mode == Host) begin
+      cfg.vif.tck_en <= 1'b0;
+      cfg.vif.tms <= 1'b0;
+      cfg.vif.tdi <= 1'b0;
+      selected_ir = '{default:0};
+      selected_ir_len = 0;
+    end
+    else begin
+      cfg.vif.tdo <= 1'b0;
+    end
+  endfunction
+
+  // reset signals task
   virtual task reset_signals();
+    do_reset_signals();
     forever begin
       @(negedge cfg.vif.trst_n);
-      if (cfg.if_mode == Host) begin
-        cfg.vif.tck_en <= 1'b0;
-        `HOST_CB.tms <= 1'b0;
-        `HOST_CB.tdi <= 1'b0;
-        selected_ir = '{default:0};
-        selected_ir_len = 0;
-      end
-      else begin
-        `DEVICE_CB.tdo <= 1'b0;
-      end
+      do_reset_signals();
       @(posedge cfg.vif.trst_n);
     end
   endtask
@@ -48,6 +54,10 @@
   // drive trans received from sequencer
   virtual task get_and_drive_host_mode();
     forever begin
+      if (!cfg.vif.trst_n) begin
+        `DV_WAIT(cfg.vif.trst_n)
+        cfg.vif.wait_tck(1);
+      end
       seq_item_port.get_next_item(req);
       $cast(rsp, req.clone());
       rsp.set_id_info(req);
diff --git a/hw/dv/sv/jtag_agent/jtag_dtm_reg_block.sv b/hw/dv/sv/jtag_agent/jtag_dtm_reg_block.sv
index 4c78280..c8c54bc 100644
--- a/hw/dv/sv/jtag_agent/jtag_dtm_reg_block.sv
+++ b/hw/dv/sv/jtag_agent/jtag_dtm_reg_block.sv
@@ -253,6 +253,8 @@
       .individually_accessible(0));
 
     dmihardreset.set_original_access("W1C");
+    // Writing 1 to this field will clear the dmi register, causing read-check mismatches.
+    csr_excl.add_excl(dmihardreset.get_full_name(), CsrExclWrite, CsrNonInitTests);
 
     zero1 = (dv_base_reg_field::type_id::create("zero1"));
     zero1.configure(
diff --git a/hw/dv/sv/jtag_agent/jtag_if.sv b/hw/dv/sv/jtag_agent/jtag_if.sv
index cae6756..5dd76e4 100644
--- a/hw/dv/sv/jtag_agent/jtag_if.sv
+++ b/hw/dv/sv/jtag_agent/jtag_if.sv
@@ -3,18 +3,20 @@
 // SPDX-License-Identifier: Apache-2.0
 
 // jtag interface with default 50MHz tck
-interface jtag_if #(time JtagDefaultTckPeriodNs = 20ns) ();
+interface jtag_if #(parameter int unsigned JtagDefaultTckPeriodPs = 20_000) ();
 
   // interface pins
+  // TODO; make these wires and add `_oe` versions to internally control the driving of these
+  // signals.
   logic tck;
   logic trst_n;
-  wire  tms;
-  wire  tdi;
-  wire  tdo;
+  logic tms;
+  logic tdi;
+  logic tdo;
 
   // generate local tck
-  bit   tck_en;
-  time  tck_period_ns = JtagDefaultTckPeriodNs;
+  bit tck_en;
+  int unsigned tck_period_ps = JtagDefaultTckPeriodPs;
 
   // Use negedge to drive jtag inputs because design also use posedge clock edge to sample.
   clocking host_cb @(posedge tck);
@@ -28,7 +30,7 @@
   clocking device_cb @(posedge tck);
     input  tms;
     input  tdi;
-    output tdo;
+    // output tdo; TODO: add this back later once device mode is supported.
   endclocking
   modport device_mp(clocking device_cb, input trst_n);
 
@@ -41,11 +43,16 @@
 
   // debug signals
 
+  // Sets the TCK frequency.
+  function automatic void set_tck_period_ps(int unsigned value);
+    tck_period_ps = value;
+  endfunction
+
   // task to wait for tck cycles
   task automatic wait_tck(int cycles);
     repeat (cycles) begin
       if (tck_en) @(posedge tck);
-      else        #(tck_period_ns * 1ns);
+      else        #(tck_period_ps * 1ps);
     end
   endtask
 
@@ -61,9 +68,9 @@
     tck = 1'b1;
     forever begin
       if (tck_en) begin
-        #(tck_period_ns / 2);
+        #(tck_period_ps / 2 * 1ps);
         tck = ~tck;
-        #(tck_period_ns / 2);
+        #(tck_period_ps / 2 * 1ps);
         tck = ~tck;
       end else begin
         @(tck_en);
diff --git a/hw/dv/sv/jtag_dmi_agent/README.md b/hw/dv/sv/jtag_dmi_agent/README.md
new file mode 100644
index 0000000..f664526
--- /dev/null
+++ b/hw/dv/sv/jtag_dmi_agent/README.md
@@ -0,0 +1,126 @@
+# JTAG_DMI UVM Agent
+
+JTAG_DMI UVM Agent is extended from DV library agent classes. Contrary to what
+the name suggests, it actually does not come with an agent. It is called an
+agent because it comes with a monitor, an RAL model and a RAL frontdoor
+accessor class, wrapped in a package that provides some additional utility
+methods.
+
+## Block diagram
+
+![jtag_dmi_agent](./doc/jtag_dmi_agent.svg)
+
+## jtag_dmi_item
+
+This class represents a predicted DMI transaction, captured by the monitor.
+It consists of `req_op`, `addr`, `wdata`, `rsp_op` and `rdata`. The first three
+captures a DMI request as initiated by the host, whereas the last two capture
+the response type and read data respectively.
+
+## jtag_dmi_monitor
+
+This component converts the raw JTAG transactions captured from the
+`jtag_monitor` in the `jtag_agent` over its TLM analysis port into a stream of
+`jtag_dmi_item` transactions which are sent over this component's analysis
+port. A JTAG DMI access is made by writing to the JTAG DTM DMI register with
+the request, and polling it subsequently for completion. This monitor analyzes
+the incoming raw JTAG transactions to see if the JTAG DMI register was
+accessed, by matching the IR value. If it was, then it captures the subsequent
+updates to its DR to extract the predicted DMI requests. It uses the JTAG DTM
+RAL model to parse the DTM DMI register fields from these writes and reads.
+Likewise, it examines the `dout` value of the transaction item (i.e. the read
+value of the DTM DMI register) which indicates the status of the previously
+initiated DMI request, as well as the read data. Partial requests are
+immediately written to the `req_analysis_port` and are held in a local queue.
+When it detects that the access completed, it pops the partial request from the
+queue, updates the request with the response information, and writes the
+completed transaction to the `analysis_port`.
+
+Note that these transactions are predictions, based on reads and writes to the
+JTAG DTM register. The actual internal DMI interface is not probed.
+
+Any raw JTAG transactions that were not made to the JTAG DTM DMI register are
+passed through the `non_dmi_jtag_dtm_analysis_port`.
+
+This monitor is required to be instantiated alongside the `jtag_agent` in the
+testbench environment that seeks to consume the captured JTAG DMI transactions.
+
+## jtag_dmi_reg_block
+
+This class represents the JTAG DMI register space, as indicated in the [RISCV
+debug specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf).
+The registers were specified in the adjoining `jtag_dmi.hjson` file and
+converted to the model using our [reggen](../../../../util/reggen/doc/setup_and_use.md)
+tool. It has been hand-edited after generation to remove the comportability
+assumptions. See the notes in the file for details.
+
+The package provides a convenience function
+`jtag_dmi_agent_pkg::create_jtag_dmi_reg_block()` that creates and returns
+a handle to this model. It also attaches the frontdoor accessor class to
+all registers, which replaces the traditional access of registers using a
+map and an adapter. Like the JTAG DMI monitor, an object of this class is
+also required to be manually instantiated in the parent environment.
+
+This register model serves to be generic in nature. It does not cater to
+a specific implementation. The actual implementation of this register space may
+differ in the design. The reset values and access policies of these registers
+may need to be altered in the parent environment before use, based on the
+implementation.
+
+## jtag_dmi_reg_frontdoor
+
+Accesses to the DMI register space over JTAG happen indirectly via writes
+to the DTM register called `dmi` which is specified in the
+`jtag_dtm_reg_block` provided by the `jtag_agent`. Hence, the accesses to the
+DMI register space cannot be made via the traditional register map and adapter
+method. This sequence writes the DTM `dmi` register to issue a DMI read or
+a write request, then polls it for completion. The DTM `dmi` register
+on subsequent reads, indicates the status of the request (success, busy or
+fail), in addition to the read data. If the request status is busy, it keeps
+polling, until either a JTAG reset (`trst_n`) asserts or a timeout occurs.
+
+It uses an externally created semaphore `jtag_dtm_ral_sem_h` to atomicize
+accesses to the DTM `dmi` register, since accesses to all DMI registers go
+through this shared resource. This semaphore is also created and set by the
+convenience function `jtag_dmi_agent_pkg::create_jtag_dmi_reg_block()`.
+
+## sba_access_item
+
+This class represents the driven or predicted SBA transaction. It is used by
+the routines provided in `jtag_rv_debugger_pkg`, as well as by the
+`sba_access_monitor`. It contains request and response related fields. It also
+has special control knobs to modify the behavior of the design when initiating
+accesses. These knobs - `readonaddr`, `readondata`, and `autoincrement` are
+written to the SBCS register, if different from the defaults.
+
+## sba_access_monitor
+
+This monitor retrieves the predicted JTAG DMI transactions from the
+`jtag_dmi_monitor` to further analyze accesses to the SBA subset of the DMI
+register space, using the handle to the `jtag_dmi_reg_block` instance, which is
+set externally. It examines the stream of reads and writes to the SBA registers
+to predict outgoing SBA transactions. For this prediction to work correctly,
+the stimulus needs to be sent correctly as well, which is facilitated by the
+routines provided by the `jtag_rv_debugger_pkg`.
+
+Any JTAG DMI transactions that were not made to the SBA registers are passed
+through the `non_sba_jtag_dmi_analysis_port`.
+
+This monitor is required to be instantiated alongside the `jtag_agent` in the
+testbench environment that seeks to consume the predicted SBA transactions.
+
+## jtag_rv_debugger
+
+The JTAG DMI register space has registers to initiate and manage accesses into
+the system through an external system bus (whatever that may be). Please see the
+RISCV debug specification for more details. These registers are already
+a part of the `jtag_dmi_reg_block` model.
+
+This class models an external debugger by providing methods to perform debug
+activies such as issuing CPU halt request, non-debug domain reset request,
+run abstract commands to access CPU registers and the system memory, inserting
+breakpoints, single-stepping, injecting programs into SRAM and having the
+CPU starting fetching instructions from arbitrary memory locations. It also
+provides methods to initiate and manage accesses into the system bus access
+interface (SBA) using these SBA registers, including starting an access,
+waiting for completion and clearing the error bits.
diff --git a/hw/dv/sv/jtag_dmi_agent/doc/index.md b/hw/dv/sv/jtag_dmi_agent/doc/index.md
deleted file mode 100644
index b19aa8e..0000000
--- a/hw/dv/sv/jtag_dmi_agent/doc/index.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# JTAG_DMI UVM Agent
-
-JTAG_DMI UVM Agent is extended from DV library agent classes. Contrary to what
-the name suggests, it actually does not come with an agent. It is called an
-agent because it comes with a monitor, an RAL model and a RAL frontdoor
-accessor class, wrapped in a package that provides some additional utility
-methods.
-
-## Block diagram
-
-![jtag_dmi_agent](jtag_dmi_agent.svg)
-
-## jtag_dmi_item
-
-This class represents a predicted DMI transaction, captured by the monitor.
-It consists of `req_op`, `addr`, `wdata`, `rsp_op` and `rdata`. The first three
-captures a DMI request as initiated by the host, whereas the last two capture
-the response type and read data respectively.
-
-## jtag_dmi_monitor
-
-This component converts the raw JTAG transactions captured from the
-`jtag_monitor` in the `jtag_agent` over its TLM analysis port into a stream of
-`jtag_dmi_item` transactions which are sent over this component's analysis
-port. A JTAG DMI access is made by writing to the JTAG DTM DMI register with
-the request, and polling it subsequently for completion. This monitor analyzes
-the incoming raw JTAG transactions to see if the JTAG DMI register was
-accessed, by matching the IR value. If it was, then it captures the subsequent
-updates to its DR to extract the predicted DMI requests. It uses the JTAG DTM
-RAL model to parse the DTM DMI register fields from these writes and reads.
-Likewise, it examines the `dout` value of the transaction item (i.e. the read
-value of the DTM DMI register) which indicates the status of the previously
-initiated DMI request, as well as the read data. Partial requests are
-immediately written to the `req_analysis_port` and are held in a local queue.
-When it detects that the access completed, it pops the partial request from the
-queue, updates the request with the response information, and writes the
-completed transaction to the `analysis_port`.
-
-Note that these transactions are predictions, based on reads and writes to the
-JTAG DTM register. The actual internal DMI interface is not probed.
-
-Any raw JTAG transactions that were not made to the JTAG DTM DMI register are
-passed through the `non_dmi_jtag_dtm_analysis_port`.
-
-This monitor is required to be instantiated alongside the `jtag_agent` in the
-testbench environment that seeks to consume the captured JTAG DMI transactions.
-
-## jtag_dmi_reg_block
-
-This class represents the JTAG DMI register space, as indicated in the [RISCV
-debug specification 0.13.2](https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/riscv-debug-release.pdf).
-The registers were specified in the adjoining `jtag_dmi.hjson` file and
-converted to the model using our [reggen]({{< relref "util/reggen/doc">}})
-tool. It has been hand-edited after generation to remove the comportability
-assumptions. See the notes in the file for details.
-
-The package provides a convenience function
-`jtag_dmi_agent_pkg::create_jtag_dmi_reg_block()` that creates and returns
-a handle to this model. It also attaches the frontdoor accessor class to
-all registers, which replaces the traditional access of registers using a
-map and an adapter. Like the JTAG DMI monitor, an object of this class is
-also required to be manually instantiated in the parent environment.
-
-This register model serves to be generic in nature. It does not cater to
-a specific implementation. The actual implementation of this register space may
-differ in the design. The reset values and access policies of these registers
-may need to be altered in the parent environment before use, based on the
-implementation.
-
-## jtag_dmi_reg_frontdoor
-
-Accesses to the DMI register space over JTAG happen indirectly via writes
-to the DTM register called `dmi` which is specified in the
-`jtag_dtm_reg_block` provided by the `jtag_agent`. Hence, the accesses to the
-DMI register space cannot be made via the traditional register map and adapter
-method. This sequence writes the DTM `dmi` register to issue a DMI read or
-a write request, then polls it for completion. The DTM `dmi` register
-on subsequent reads, indicates the status of the request (success, busy or
-fail), in addition to the read data. If the request status is busy, it keeps
-polling, until either a JTAG reset (`trst_n`) asserts or a timeout occurs.
-
-It uses an externally created semaphore `jtag_dtm_ral_sem_h` to atomicize
-accesses to the DTM `dmi` register, since accesses to all DMI registers go
-through this shared resource. This semaphore is also created and set by the
-convenience function `jtag_dmi_agent_pkg::create_jtag_dmi_reg_block()`.
-
-## sba_access_item
-
-This class represents the driven or predicted SBA transaction. It is used by
-the routines provided in `sba_access_utils_pkg`, as well as by the
-`sba_access_monitor`. It contains request and response related fields. It also
-has special control knobs to modify the behavior of the design when initiating
-accesses. These knobs - `readonaddr`, `readondata`, and `autoincrement` are
-written to the SBCS register, if different from the defaults.
-
-## sba_access_monitor
-
-This monitor retrieves the predicted JTAG DMI transactions from the
-`jtag_dmi_monitor` to further analyze accesses to the SBA subset of the DMI
-register space, using the handle to the `jtag_dmi_reg_block` instance, which is
-set externally. It examines the stream of reads and writes to the SBA registers
-to predict outgoing SBA transactions. For this prediction to work correctly,
-the stimulus needs to be sent correctly as well, which is facilitated by the
-routines provided by the `sba_access_utils_pkg`.
-
-Any JTAG DMI transactions that were not made to the SBA registers are passed
-through the `non_sba_jtag_dmi_analysis_port`.
-
-This monitor is required to be instantiated alongside the `jtag_agent` in the
-testbench environment that seeks to consume the predicted SBA transactions.
-
-## sba_access_utils_pkg
-
-The JTAG DMI register space has registers to initiate and manage accesses into
-the system through an external system bus (whatever that may be). Please see the
-RISCV debug specification for more details. These registers are already
-a part of the `jtag_dmi_reg_block` model.
-
-This package provides some convenience tasks to initiate and manage accesses
-into the system bus access interface (SBA) using these SBA registers, including
-starting an access, waiting for completion and clearing the error bits. These
-tasks take the handle to the `jtag_dmi_reg_block` model as one of the
-arguments.
diff --git a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_agent.core b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_agent.core
index 2325d70..db44d11 100644
--- a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_agent.core
+++ b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_agent.core
@@ -19,9 +19,11 @@
       - jtag_dmi_reg_frontdoor.sv: {is_include_file: true}
       - jtag_dmi_item.sv: {is_include_file: true}
       - jtag_dmi_monitor.sv: {is_include_file: true}
-      - sba_access_utils_pkg.sv
+      - jtag_rv_debugger_pkg.sv
       - sba_access_item.sv: {is_include_file: true}
       - sba_access_monitor.sv: {is_include_file: true}
+      - jtag_rv_debugger.sv: {is_include_file: true}
+      - sba_access_reg_frontdoor.sv: {is_include_file: true}
     file_type: systemVerilogSource
 
 targets:
diff --git a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_block.sv b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_block.sv
index 2d9172f..5319dec 100644
--- a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_block.sv
+++ b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_block.sv
@@ -949,6 +949,23 @@
   endfunction : build
 endclass : jtag_dmi_reg_haltsum3
 
+class jtag_dmi_reg_field_sbcs_sberror extends dv_base_reg_field;
+  `uvm_object_utils(jtag_dmi_reg_field_sbcs_sberror)
+
+  function new(string name = "jtag_dmi_reg_field_sbcs_sberror");
+    super.new(name);
+  endfunction: new
+
+  // Field is cleared when written to 1 exactly.
+  virtual function uvm_reg_data_t XpredictX(uvm_reg_data_t cur_val,
+                                            uvm_reg_data_t wr_val,
+                                            uvm_reg_map map);
+    if (get_access(map) == "W1C" && wr_val == 1) return 0;
+    else return super.XpredictX(cur_val, wr_val, map);
+  endfunction
+
+endclass
+
 class jtag_dmi_reg_sbcs extends dv_base_reg;
   // fields
   rand dv_base_reg_field sbaccess8;
@@ -957,7 +974,7 @@
   rand dv_base_reg_field sbaccess64;
   rand dv_base_reg_field sbaccess128;
   rand dv_base_reg_field sbasize;
-  rand dv_base_reg_field sberror;
+  rand jtag_dmi_reg_field_sbcs_sberror sberror;
   rand dv_base_reg_field sbreadondata;
   rand dv_base_reg_field sbautoincrement;
   rand dv_base_reg_field sbaccess;
@@ -1067,7 +1084,7 @@
 
     sbasize.set_original_access("RO");
 
-    sberror = (dv_base_reg_field::
+    sberror = (jtag_dmi_reg_field_sbcs_sberror::
                type_id::create("sberror"));
     sberror.configure(
       .parent(this),
diff --git a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_frontdoor.sv b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_frontdoor.sv
index 7e52954..7165e9d 100644
--- a/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_frontdoor.sv
+++ b/hw/dv/sv/jtag_dmi_agent/jtag_dmi_reg_frontdoor.sv
@@ -84,7 +84,7 @@
         fork
           wait(jtag_agent_cfg_h.in_reset);
           // TODO: Make timeout more configurable.
-          wait_timeout(jtag_agent_cfg_h.vif.tck_period_ns * 10000);
+          `DV_WAIT_TIMEOUT(jtag_agent_cfg_h.vif.tck_period_ps * 10_000_000 /*10 ms*/)
         join_any
       end
     )
diff --git a/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger.sv b/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger.sv
new file mode 100644
index 0000000..c1653fa
--- /dev/null
+++ b/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger.sv
@@ -0,0 +1,1267 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// A class that provides common RISCV debugging utilities over JTAG.
+//
+// The utilities provided here allow the user to mimic an external debugger performing tasks such
+// as halting the CPU, asserting a non-debug domain reset, accessing the CPU registers and memory
+// using abstract commands, inserting breakpoints, single-stepping, causing the CPU to execute
+// arbitrary instructions and accessing the chip resources over the SBA interface.
+class jtag_rv_debugger extends uvm_object;
+  `uvm_object_utils(jtag_rv_debugger)
+
+  // A pre-created handle to the jtag_agent_cfg object.
+  protected jtag_agent_cfg cfg;
+
+  // A pre-created handle to the JTAG DMI reg block with a frontdoor accessor attached.
+  protected jtag_dmi_reg_block ral;
+
+  // The elf file executed by the CPU.
+  protected string elf_file;
+
+  // Enable prediction on writes.
+  bit predict_dmi_writes = 1;
+
+  // Number of harts in the system. TODO: add support for multi-hart system.
+  int num_harts = 1;
+
+  // Number of breakpoints (triggers) supported.
+  int num_triggers;
+
+  // Indicates whether aarpostincrement is supported.
+  bit aarpostincrement_supported;
+
+  // An sba_access_reg_frontdoor instance to provide access to system CSRs via SBA using JTAG.
+  sba_access_reg_frontdoor m_sba_access_reg_frontdoor;
+
+  // Internal status signals to optimize debugger performance.
+  // TODO: Use these bits effectively.
+  protected bit dmactive_is_set;
+  protected bit cpu_is_halted;
+  protected bit step_entered;
+  protected logic [BUS_AW-1:0] symbol_table[string];
+
+  function new (string name = "");
+    super.new(name);
+    m_sba_access_reg_frontdoor = sba_access_reg_frontdoor::type_id::create(
+        "m_sba_access_reg_frontdoor");
+    m_sba_access_reg_frontdoor.debugger_h = this;
+  endfunction : new
+
+  // Sets the jtag_agent_cfg handle.
+  virtual function void set_cfg(jtag_agent_cfg cfg);
+    this.cfg = cfg;
+  endfunction
+
+  // Returns the jtag_agent_cfg handle.
+  virtual function jtag_agent_cfg get_cfg();
+    return cfg;
+  endfunction
+
+  // Sets the jtag_dmi_reg_block handle.
+  virtual function void set_ral(jtag_dmi_reg_block ral);
+    this.ral = ral;
+  endfunction
+
+  // Returns the jtag_dmi_reg_block handle.
+  virtual function jtag_dmi_reg_block get_ral();
+    return ral;
+  endfunction
+
+  // Sets the elf file name.
+  virtual function void set_elf_file(string elf_file);
+    this.elf_file = elf_file;
+    symbol_table.delete();
+  endfunction
+
+  // Returns the elf file name.
+  virtual function string get_elf_file();
+    return elf_file;
+  endfunction
+
+  // Asserts TRST_N for a few cycles.
+  virtual task do_trst_n(int cycles = $urandom_range(5, 20));
+    `uvm_info(`gfn, "Asserting TRST_N", UVM_MEDIUM)
+    cfg.vif.do_trst_n(cycles);
+    dmactive_is_set = 0;
+    cpu_is_halted = 0;
+    step_entered = 0;
+  endtask
+
+  // Enables the debug module.
+  virtual task set_dmactive(bit value);
+    `uvm_info(`gfn, $sformatf("Setting dmactive = %0b", value), UVM_MEDIUM)
+    csr_wr(.ptr(ral.dmcontrol.dmactive), .value(value), .blocking(1), .predict(predict_dmi_writes));
+    dmactive_is_set = value;
+    if (!value) begin
+      cpu_is_halted = 0;
+      step_entered = 0;
+    end
+  endtask
+
+  // Issues a CPU halt request.
+  virtual task set_haltreq(bit value, int hart = 0);
+    `uvm_info(`gfn, $sformatf("Setting haltreq = %0b", value), UVM_MEDIUM)
+    csr_wr(.ptr(ral.dmcontrol.haltreq), .value(value), .blocking(1), .predict(predict_dmi_writes));
+  endtask
+
+  // Waits for the CPU to be in halted state.
+  virtual task wait_cpu_halted(int hart = 0);
+    uvm_reg_data_t data;
+    `uvm_info(`gfn, "Waiting for CPU to halt", UVM_MEDIUM)
+    `DV_SPINWAIT(
+      do begin
+        csr_rd(.ptr(ral.dmstatus), .value(data), .blocking(1));
+      end while (dv_base_reg_pkg::get_field_val(ral.dmstatus.anyhalted, data) == 0);
+    )
+    if (num_harts == 1) begin
+      `DV_CHECK_EQ(dv_base_reg_pkg::get_field_val(ral.dmstatus.allhalted, data), 1)
+    end
+    `uvm_info(`gfn, "CPU has halted", UVM_MEDIUM)
+  endtask
+
+  // Issues an NDM reset request.
+  virtual task set_ndmreset(bit value);
+    `uvm_info(`gfn, $sformatf("Setting ndmreset = %0b", value), UVM_MEDIUM)
+    csr_wr(.ptr(ral.dmcontrol.ndmreset), .value(value), .blocking(1), .predict(predict_dmi_writes));
+  endtask
+
+  // Issues a CPU resume reset request.
+  virtual task set_resumereq(bit value);
+    `uvm_info(`gfn, $sformatf("Setting resumereq = %0b", value), UVM_MEDIUM)
+    csr_wr(.ptr(ral.dmcontrol.resumereq), .value(value), .blocking(1),
+           .predict(predict_dmi_writes));
+  endtask
+
+  // Waits for the CPU to be in resumed state.
+  virtual task wait_cpu_resumed(int hart = 0);
+    uvm_reg_data_t data;
+    `uvm_info(`gfn, "Waiting for CPU to resume", UVM_MEDIUM)
+    `DV_SPINWAIT(
+      do begin
+        csr_rd(.ptr(ral.dmstatus), .value(data), .blocking(1));
+      end while (dv_base_reg_pkg::get_field_val(ral.dmstatus.anyhalted, data) == 1);
+    )
+    `DV_CHECK_EQ(dv_base_reg_pkg::get_field_val(ral.dmstatus.anyrunning, data), 1)
+    if (num_harts == 1) begin
+      `DV_CHECK_EQ(dv_base_reg_pkg::get_field_val(ral.dmstatus.allhalted, data), 0)
+      `DV_CHECK_EQ(dv_base_reg_pkg::get_field_val(ral.dmstatus.allrunning, data), 1)
+    end
+    `uvm_info(`gfn, "CPU has resumed", UVM_MEDIUM)
+  endtask
+
+  // Checks if we are ready for executing abstract commands.
+  //
+  // Recommended to be run once at the very start of the debug session.
+  // ready: Returns whether the debug module is ready to accept abstract commands.
+  virtual task abstract_cmd_dm_ready(output bit ready);
+    uvm_reg_data_t data;
+    csr_rd(.ptr(ral.dmcontrol), .value(data), .blocking(1));
+    ready = 1;
+    ready &= dv_base_reg_pkg::get_field_val(ral.dmcontrol.dmactive, data) == 1;
+    ready &= dv_base_reg_pkg::get_field_val(ral.dmcontrol.ackhavereset, data) == 0;
+    ready &= dv_base_reg_pkg::get_field_val(ral.dmcontrol.resumereq, data) == 0;
+    ready &= dv_base_reg_pkg::get_field_val(ral.dmcontrol.haltreq, data) == 0;
+    csr_rd(.ptr(ral.dmstatus), .value(data), .blocking(1));
+    ready &= dv_base_reg_pkg::get_field_val(ral.dmstatus.allhalted, data) == 1;
+    csr_rd(.ptr(ral.abstractcs), .value(data), .blocking(1));
+    ready &= dv_base_reg_pkg::get_field_val(ral.abstractcs.busy, data) == 0;
+    ready &= dv_base_reg_pkg::get_field_val(ral.abstractcs.cmderr, data) == AbstractCmdErrNone;
+  endtask
+
+  // Waits for an abstract cmd to finish executing.
+  //
+  // status: Returns the status of the previous executed command.
+  // cmderr_clear: Clear the cmd error status (default off).
+  virtual task abstract_cmd_busy_wait(output abstract_cmd_err_e status,
+                                      input  bit cmderr_clear = 0);
+    uvm_reg_data_t data;
+    `uvm_info(`gfn, "Waiting for an abstract command (if any) to complete execution", UVM_MEDIUM)
+    `DV_SPINWAIT(
+      do begin
+        csr_rd(.ptr(ral.abstractcs), .value(data), .blocking(1));
+      end while (dv_base_reg_pkg::get_field_val(ral.abstractcs.busy, data) == 1);
+    )
+    status = abstract_cmd_err_e'(dv_base_reg_pkg::get_field_val(ral.abstractcs.cmderr, data));
+    if (status != AbstractCmdErrNone && cmderr_clear) begin
+      csr_wr(.ptr(ral.abstractcs.cmderr), .value(1), .blocking(1), .predict(predict_dmi_writes));
+    end
+    `uvm_info(`gfn, "Abstract command completed", UVM_MEDIUM)
+  endtask
+
+  // Disables the abstract cmd by setting the control field to 0.
+  virtual task abstract_cmd_disable();
+    uvm_reg_data_t rw_data = '0;
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.cmdtype, rw_data,
+                                                            AbstractCmdRegAccess);
+    csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+  endtask
+
+  // Writes the abstractauto register to enable automatic command execution.
+  //
+  // The default value for the args are set to 0 to prioritize disabling of the autoexec feature.
+  virtual task write_abstract_cmd_auto(bit autoexecdata = 0, bit autoexecprogbuf = 0);
+    uvm_reg_data_t rw_data = '0;
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(
+        ral.abstractauto.autoexecprogbuf, rw_data, autoexecprogbuf);
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(
+        ral.abstractauto.autoexecdata, rw_data, autoexecdata);
+    csr_wr(.ptr(ral.abstractauto), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+  endtask
+
+  // Reads (a) CPU register(s) via an abstract command.
+  //
+  // regno: The starting CPU register number.
+  // value_q: A queue of returned reads.
+  // size: The Number of successive reads. The regno is incremented this many times.
+  // postexec: Have the CPU execute the progbuf immediately after the command. The progbuf is
+  // assumed to have been loaded already.
+  // TODO: add support for sub-word transfer size.
+  // TODO: Add support for reg read via program buffer.
+  virtual task abstract_cmd_reg_read(input abstract_cmd_regno_t regno,
+                                     output logic [BUS_DW-1:0] value_q[$],
+                                     output abstract_cmd_err_e status,
+                                     input int size = 1,
+                                     input bit postexec = 0);
+    uvm_reg_data_t rw_data = '0;
+    abstract_cmd_reg_access_t cmd = '0;
+
+    if (aarpostincrement_supported && size > 1) begin
+      write_abstract_cmd_auto(.autoexecdata(1));
+      cmd.aarpostincrement = 1;
+    end
+
+    cmd.aarsize = 2;
+    cmd.postexec = postexec;
+    cmd.transfer = 1;
+    cmd.regno = regno;
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.cmdtype, rw_data,
+                                                            AbstractCmdRegAccess);
+    if (aarpostincrement_supported) begin
+      // Write command only once at the start.
+      rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.control, rw_data,
+                                                              cmd);
+      csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+    end
+
+    for (int i = 0; i < size; i++) begin
+      if (!aarpostincrement_supported) begin
+        // Manually increment regno and write command for each read.
+        cmd.regno = regno + i;
+        rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.control, rw_data,
+                                                                cmd);
+        csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+      end
+      abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+      // Let the caller handle error cases.
+      if (status != AbstractCmdErrNone) return;
+      // Before the last read, reset the autoexecdata bit.
+      if (aarpostincrement_supported && size > 1 && i == size - 1) write_abstract_cmd_auto();
+      csr_rd(.ptr(ral.abstractdata[0]), .value(value_q[i]), .blocking(1));
+      `uvm_info(`gfn, $sformatf("Read CPU register 0x%0h: 0x%0h", regno + i, value_q[i]),
+                UVM_MEDIUM)
+    end
+  endtask
+
+  // Writes (a) CPU register(s) via an abstract command.
+  //
+  // regno: The starting CPU register number.
+  // value_q: A queue of data written. regno is incremented for each value.
+  // size: The Number of successive reads.
+  // postexec: Have the CPU execute the progbuf immediately after the command. The progbuf is
+  // assumed to have been loaded already.
+  // TODO: Add support for reg read via program buffer.
+  virtual task abstract_cmd_reg_write(input abstract_cmd_regno_t regno,
+                                      input logic [BUS_DW-1:0] value_q[$],
+                                      output abstract_cmd_err_e status,
+                                      input bit postexec = 0);
+    uvm_reg_data_t rw_data = '0;
+    abstract_cmd_reg_access_t cmd = '0;
+
+    csr_wr(.ptr(ral.abstractdata[0]), .value(value_q[0]), .blocking(1),
+           .predict(predict_dmi_writes));
+
+    cmd.aarsize = 2;
+    cmd.postexec = postexec;
+    cmd.transfer = 1;
+    cmd.write = 1;
+    cmd.regno = regno;
+    cmd.aarpostincrement = aarpostincrement_supported && (value_q.size() > 1);
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.cmdtype, rw_data,
+                                                            AbstractCmdRegAccess);
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.control, rw_data,
+                                                            cmd);
+    csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+    `uvm_info(`gfn, $sformatf("Wrote 0x%0h to CPU register 0x%0h", value_q[0], cmd.regno),
+              UVM_MEDIUM)
+
+    if (aarpostincrement_supported && (value_q.size() > 1)) begin
+      write_abstract_cmd_auto(.autoexecdata(1));
+    end
+
+    for (int i = 1; i < value_q.size(); i++) begin
+      abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+      // Let the caller handle error cases.
+      if (status != AbstractCmdErrNone) return;
+      csr_wr(.ptr(ral.abstractdata[0]), .value(value_q[i]), .blocking(1),
+             .predict(predict_dmi_writes));
+      if (!aarpostincrement_supported) begin
+        // Manually increment regno and write command for each write.
+        cmd.regno = regno + i;
+        rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.control, rw_data,
+                                                                cmd);
+        csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+      end
+      `uvm_info(`gfn, $sformatf("Wrote 0x%0h to CPU register 0x%0h", value_q[i], regno + i),
+                UVM_MEDIUM)
+    end
+    abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+    // After the last write, reset the autoexecdata bit.
+    if (aarpostincrement_supported && (value_q.size() > 1)) write_abstract_cmd_auto();
+  endtask
+
+  // Reads (a) system memory location(s) via an abstract command.
+  //
+  // The system memory address space can be accessed either via `AbstractCmdMemAccess` abstract
+  // command type, or indirectly using the CPU using the program buffer. The former is not currently
+  // supported.
+  // addr: The starting system address.
+  // value_q: The read data returned to the caller.
+  // status: The status of the op returned to the caller.
+  // size: The size of the read access in terms of full bus words.
+  // route: The route taken to access the system memory.
+  virtual task abstract_cmd_mem_read(input logic [BUS_AW-1:0] addr,
+                                     output logic [BUS_DW-1:0] value_q[$],
+                                     output abstract_cmd_err_e status,
+                                     input int size = 1,
+                                     input mem_access_route_e route = MemAccessViaProgbuf);
+    `DV_CHECK(size)
+    case (route)
+      MemAccessViaAbstractCmd: begin
+        `uvm_fatal(`gfn, "Accessing the system memory using AbstractCmdMemAccess is not supproted")
+      end
+      MemAccessViaProgbuf: begin
+        logic [BUS_DW-1:0] progbuf_q[$];
+        logic [BUS_DW-1:0] rwdata[$];
+        logic [BUS_DW-1:0] s0, s1;
+
+        if (size == 1) progbuf_q = '{LwS0S0, Ebreak};
+        else progbuf_q = '{LwS1S0, AddiS0S04, Ebreak};
+
+        abstract_cmd_reg_read(.regno(RvCoreCsrGprS0), .value_q(rwdata), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        s0 = rwdata[0];
+        if (size > 1) begin
+          abstract_cmd_reg_read(.regno(RvCoreCsrGprS1), .value_q(rwdata), .status(status));
+          if (status != AbstractCmdErrNone) return;
+          s1 = rwdata[0];
+        end
+        `uvm_info(`gfn, $sformatf("Saved CPU registers S0 = 0x%0h and S1 = 0x%0h",  s0, s1),
+                  UVM_HIGH)
+
+        value_q.delete();
+        write_progbuf(progbuf_q);
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS0), .value_q({addr}), .status(status),
+                               .postexec(1));
+        if (status != AbstractCmdErrNone) return;
+        abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+        if (status != AbstractCmdErrNone) return;
+        if (size == 1) begin
+          abstract_cmd_reg_read(.regno(RvCoreCsrGprS0), .value_q(value_q), .status(status));
+          if (status != AbstractCmdErrNone) return;
+        end else begin
+          abstract_cmd_reg_read(.regno(RvCoreCsrGprS1), .value_q(rwdata), .status(status),
+                                .postexec(1));
+          if (status != AbstractCmdErrNone) return;
+          value_q[0] = rwdata[0];
+          `uvm_info(`gfn, $sformatf("Read from system memory: [0x%0h] = 0x%0h", addr,
+                                    value_q[0]), UVM_MEDIUM)
+          write_abstract_cmd_auto(.autoexecdata(1));
+          for (int i = 1; i < size; i++) begin
+            abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+            if (status != AbstractCmdErrNone) return;
+            // Before the last read, reset the autoexecdata bit.
+            if (i == size - 1) write_abstract_cmd_auto();
+            csr_rd(.ptr(ral.abstractdata[0]), .value(value_q[i]), .blocking(1));
+            `uvm_info(`gfn, $sformatf("Read from system memory: [0x%0h] = 0x%0h", addr + i * 4,
+                                      value_q[i]), UVM_MEDIUM)
+          end
+        end
+
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS0), .value_q({s0}), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        if (size > 1) begin
+          abstract_cmd_reg_write(.regno(RvCoreCsrGprS1), .value_q({s1}), .status(status));
+          if (status != AbstractCmdErrNone) return;
+        end
+        `uvm_info(`gfn, $sformatf("Restored CPU registers S0 = 0x%0h and S1 = 0x%0h",  s0, s1),
+                  UVM_HIGH)
+      end
+      MemAccessViaSba: begin
+        `uvm_fatal(`gfn, "Please invoke sba_read instead")
+      end
+      default: begin
+        `uvm_fatal(`gfn, $sformatf("The mem access route %s is invalid or unsupported",
+                                   route.name()))
+      end
+    endcase
+  endtask
+
+  // Writes (a) system memory location(s) via an abstract command.
+  //
+  // The system memory address space can be accessed either via `AbstractCmdMemAccess` abstract
+  // command type, or indirectly using the CPU using the program buffer. The former is not currently
+  // supported.
+  // addr: The starting system address.
+  // value_q: The data set to be written.
+  // status: The status of the op returned to the caller.
+  // route: The route taken to access the system memory.
+  virtual task abstract_cmd_mem_write(input logic [BUS_AW-1:0] addr,
+                                      input logic [BUS_DW-1:0] value_q[$],
+                                      output abstract_cmd_err_e status,
+                                      input mem_access_route_e route = MemAccessViaProgbuf);
+    `DV_CHECK(value_q.size())
+    case (route)
+      MemAccessViaAbstractCmd: begin
+        `uvm_fatal(`gfn, "Accessing the system memory using AbstractCndMemAccess is not supproted")
+      end
+      MemAccessViaProgbuf: begin
+        logic [BUS_DW-1:0] progbuf_q[$];
+        logic [BUS_DW-1:0] rwdata[$];
+        logic [BUS_DW-1:0] s0, s1;
+
+        if (value_q.size() == 1) progbuf_q = '{SwS1S0, Ebreak};
+        else progbuf_q = '{SwS1S0, AddiS0S04, Ebreak};
+
+        abstract_cmd_reg_read(.regno(RvCoreCsrGprS0), .value_q(rwdata), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        s0 = rwdata[0];
+        abstract_cmd_reg_read(.regno(RvCoreCsrGprS1), .value_q(rwdata), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        s1 = rwdata[0];
+        `uvm_info(`gfn, $sformatf("Saved CPU registers S0 = 0x%0h and S1 = 0x%0h",  s0, s1),
+                  UVM_HIGH)
+
+        write_progbuf(progbuf_q);
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS0), .value_q({addr}), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS1), .value_q({value_q[0]}), .status(status),
+                               .postexec(1));
+        if (status != AbstractCmdErrNone) return;
+        `uvm_info(`gfn, $sformatf("Wrote to system memory: [0x%0h] = 0x%0h", addr, value_q[0]),
+                  UVM_MEDIUM)
+        if (value_q.size() > 1) begin
+          write_abstract_cmd_auto(.autoexecdata(1));
+          for (int i = 1; i < value_q.size(); i++) begin
+            csr_wr(.ptr(ral.abstractdata[0]), .value(value_q[i]), .blocking(1),
+                   .predict(predict_dmi_writes));
+            abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+            if (status != AbstractCmdErrNone) return;
+            `uvm_info(`gfn, $sformatf("Wrote to system memory: [0x%0h] = 0x%0h", addr + i * 4,
+                                      value_q[i]), UVM_MEDIUM)
+          end
+          write_abstract_cmd_auto();
+        end
+
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS0), .value_q({s0}), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        abstract_cmd_reg_write(.regno(RvCoreCsrGprS1), .value_q({s1}), .status(status));
+        if (status != AbstractCmdErrNone) return;
+        `uvm_info(`gfn, $sformatf("Restored CPU registers S0 = 0x%0h and S1 = 0x%0h",  s0, s1),
+                  UVM_HIGH)
+      end
+      MemAccessViaSba: begin
+        `uvm_fatal(`gfn, "Please invoke sba_write instead")
+      end
+      default: begin
+        `uvm_fatal(`gfn, $sformatf("The mem access route %s is invalid or unsupported",
+                                   route.name()))
+      end
+    endcase
+  endtask
+
+  // Have the CPU execute arbitrary instructions in the program buffer.
+  virtual task abstract_cmd_exec_progbuf(input logic [BUS_DW-1:0] progbuf_q[$],
+                                         output abstract_cmd_err_e status);
+    uvm_reg_data_t rw_data = '0;
+    abstract_cmd_reg_access_t cmd = '0;
+    write_progbuf(progbuf_q);
+    cmd.postexec = 1;
+    cmd.transfer = 0;
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.cmdtype, rw_data,
+                                                            AbstractCmdRegAccess);
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.control, rw_data,
+                                                            cmd);
+    csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+    abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+  endtask
+
+  // Issue a quick access command.
+  virtual task abstract_cmd_quick_access(output abstract_cmd_err_e status,
+                                         input logic [BUS_DW-1:0] progbuf_q[$] = {});
+    uvm_reg_data_t rw_data = '0;
+    // TODO: Check if core is not halted.
+    write_progbuf(progbuf_q);
+    rw_data = csr_utils_pkg::get_csr_val_with_updated_field(ral.command.cmdtype, rw_data,
+                                                            AbstractCmdQuickAccess);
+    csr_wr(.ptr(ral.command), .value(rw_data), .blocking(1), .predict(predict_dmi_writes));
+    abstract_cmd_busy_wait(.status(status), .cmderr_clear(1));
+    // Note: The external testbench is expected to check the CPU automatically halted, executed the
+    // progbuf and then, resumed.
+  endtask
+
+  // Write arbitrary commands into progbuf.
+  virtual task write_progbuf(logic [BUS_DW-1:0] progbuf_q[$]);
+    `DV_CHECK_LE_FATAL(progbuf_q.size(), ral.abstractcs.progbufsize.get())
+    foreach (progbuf_q[i]) begin
+      csr_wr(.ptr(ral.progbuf[i]), .value(progbuf_q[i]), .blocking(1),
+             .predict(predict_dmi_writes));
+    end
+    `uvm_info(`gfn, $sformatf("Wrote progbuf: %p", progbuf_q), UVM_MEDIUM)
+  endtask
+
+  // Returns the saved PC via DPC register.
+  virtual task read_pc(output logic [BUS_DW-1:0] pc);
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+    abstract_cmd_reg_read(.regno(RvCoreCsrDpc), .value_q(cmd_data), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    `uvm_info(`gfn, $sformatf("DPC = 0x%0h", cmd_data[0]), UVM_LOW)
+    pc = cmd_data[0];
+  endtask
+
+  // Set PC to a new value with address or the symbol
+  virtual task write_pc(logic [BUS_AW-1:0] addr = 'x, string symbol = "");
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+
+    addr_or_symbol_set(addr, symbol);
+    abstract_cmd_reg_write(.regno(RvCoreCsrDpc), .value_q({addr}), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    `uvm_info(`gfn, $sformatf("Wrote new DPC = 0x%0h", addr), UVM_LOW)
+  endtask
+
+  // Returns the DCSR value.
+  virtual task read_dcsr(output rv_core_csr_dcsr_t dcsr);
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+    abstract_cmd_reg_read(.regno(RvCoreCsrDcsr), .value_q(cmd_data), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    dcsr = rv_core_csr_dcsr_t'(cmd_data[0]);
+    `uvm_info(`gfn, $sformatf("DCSR = %p", dcsr), UVM_MEDIUM)
+  endtask
+
+  // Set the DCSR value.
+  virtual task write_dcsr(rv_core_csr_dcsr_t dcsr);
+    abstract_cmd_err_e status;
+    abstract_cmd_reg_write(.regno(RvCoreCsrDcsr), .value_q({BUS_DW'(dcsr)}), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    step_entered = dcsr.step;
+  endtask
+
+  // Internal helper function which returns the address of a symbol from the elf file.
+  protected virtual function logic [BUS_AW-1:0] get_symbol_address(string symbol);
+    longint unsigned addr, size;
+    // Return address if we already looked it up before.
+    if (symbol_table.exists(symbol)) return symbol_table[symbol];
+    `DV_CHECK(dv_utils_pkg::sw_symbol_get_addr_size(.elf_file(elf_file), .symbol(symbol),
+                  .does_not_exist_ok(0), .addr(addr), .size(size)))
+    return BUS_AW'(addr);
+  endfunction
+
+  // Internal helper function enforces either address or the symbol to be set.
+  //
+  // It throws an error if both, symbol and addr are set to known values. It also throws an error if
+  // neither of them are set. If the symbol is set, then the address is updated to the symbol's
+  // physical address.
+  //
+  // addr: The address. If set to 'x, it is updated to the symbol's physical address and returned.
+  // symbol: The symbolic address.
+  protected virtual function void addr_or_symbol_set(inout logic [BUS_AW-1:0] addr,
+                                                     input string symbol);
+    if (addr === 'x && symbol == "") begin
+      `uvm_fatal(`gfn, "Either addr or symbol expected to be set. Neither are set.")
+    end else if (addr !== 'x && symbol != "") begin
+      `uvm_fatal(`gfn, "Either addr or symbol expected to be set. Both are set.")
+    end
+    if (symbol != "") addr = get_symbol_address(symbol);
+  endfunction
+
+  // Checks if the halted CPU's PC is at the given address or symbol.
+  //
+  // exp_addr: Expected address.
+  // exp_symbol: Expected symbolic address.
+  // error_if_eq: 0: throw error on mismatch, 1: throw error on match.
+  // NOTE: Either exp_addr or exp_symbol must be set (pseudo-function overloading).
+  virtual task check_pc(logic [BUS_AW-1:0] exp_addr = 'x,
+                        string exp_symbol = "",
+                        bit error_if_eq = 0);
+    logic [BUS_DW-1:0] pc;
+    addr_or_symbol_set(exp_addr, exp_symbol);
+    read_pc(pc);
+    if (error_if_eq)  `DV_CHECK_NE(pc, exp_addr)
+    else              `DV_CHECK_EQ(pc, exp_addr)
+  endtask
+
+  // Checks if the reason for entering the debug mode matches the expected cause.
+  //
+  // exp_debug_cause: The expected debug cause.
+  virtual task check_debug_cause(rv_core_csr_dcsr_cause_e exp_debug_cause);
+    rv_core_csr_dcsr_t dcsr;
+    read_dcsr(dcsr);
+    `DV_CHECK_EQ(dcsr.cause, exp_debug_cause)
+  endtask
+
+  // Check if trigger select is valid.
+  virtual task is_valid_tselect_index(input int index, output bit valid);
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+    abstract_cmd_reg_write(.regno(RvCoreCsrTSelect), .value_q({index}), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    abstract_cmd_reg_read(.regno(RvCoreCsrTSelect), .value_q(cmd_data), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    valid = (index == cmd_data[0]);
+  endtask
+
+  // List breakpoints.
+  //
+  // TODO: Only match type breakpoints on address supported.
+  // For each breakpoint, list the following information:
+  // Breakpoint on addr|data 0x%0d before|after load|store|execute op.
+  // breakpoints: The list of breakpoints returned to the caller.
+  // delete_breakpoints: Optionally, enable deleting all breakpoints to allow further execution.
+  virtual task info_breakpoints(output breakpoint_t breakpoints[$],
+                                input bit delete_breakpoints = 0);
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+
+    breakpoints.delete();
+    for (int i = 0; i >= 0; i++) begin  // Infinite loop.
+      bit valid;
+      is_valid_tselect_index(i, valid);
+      if (valid) begin
+        rv_core_csr_trigger_mcontrol_t mcontrol;
+        abstract_cmd_reg_read(.regno(RvCoreCsrTData1), .value_q(cmd_data), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        mcontrol = rv_core_csr_trigger_mcontrol_t'(cmd_data[0]);
+        if (!(mcontrol.trigger_type == RvTriggerTypeMatch &&
+              (mcontrol.load || mcontrol.store || mcontrol.execute))) continue;
+        // This slot has as a valid breakpoint.
+        abstract_cmd_reg_read(.regno(RvCoreCsrTData2), .value_q(cmd_data), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        breakpoints.push_back(
+            '{trigger_type: RvTriggerTypeMatch,
+              index       : i,
+              tdata1      : BUS_DW'(mcontrol),
+              tdata2      : cmd_data[0]});
+        if (delete_breakpoints) clear_tdata1(0);
+      end else break;
+    end
+    print_breakpoints(breakpoints);
+  endtask
+
+  // Prints breakpoints in human-friendly way.
+  virtual function void print_breakpoints(ref breakpoint_t breakpoints[$]);
+    string format = {"\n\t%0d: %0s type breakpoint on %0s 0x%0h %0s 3'b%0b(execute|store|load) ",
+                     "op, with action %0s"};
+    string msg = "";
+
+    foreach (breakpoints[i]) begin
+      rv_core_csr_trigger_mcontrol_t mcontrol = rv_core_csr_trigger_mcontrol_t'(
+          breakpoints[i].tdata1);
+      msg = {msg, $sformatf(format,
+                            breakpoints[i].index,
+                            breakpoints[i].trigger_type.name(),
+                            mcontrol.select ? "data" : "addr",
+                            breakpoints[i].tdata2,
+                            mcontrol.timing ? "after" : "before",
+                            {mcontrol.execute, mcontrol.store, mcontrol.load},
+                            mcontrol.action.name())};
+    end
+    if (msg != "") `uvm_info(`gfn, msg, UVM_LOW)
+    else           `uvm_info(`gfn, "No breakpoints added yet", UVM_LOW)
+  endfunction
+
+  // Insert a breakpoint at the given address or symbol.
+  //
+  // addr: The PC address.
+  // symbol: Symbolic address.
+  // TODO: Only trigger on exact address match before execution supported.
+  virtual task add_breakpoint(logic [BUS_AW-1:0] addr = 'x, string symbol = "");
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+
+    addr_or_symbol_set(addr, symbol);
+    `uvm_info(`gfn, $sformatf("Adding breakpoint on 0x%0h(%0s)", addr, symbol), UVM_LOW)
+    for (int i = 0; i >= 0; i++) begin  // Infinite loop.
+      bit valid;
+      is_valid_tselect_index(i, valid);
+      if (valid) begin
+        rv_core_csr_trigger_mcontrol_t mcontrol;
+        abstract_cmd_reg_read(.regno(RvCoreCsrTData1), .value_q(cmd_data), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        mcontrol = rv_core_csr_trigger_mcontrol_t'(cmd_data[0]);
+        if (mcontrol.trigger_type == RvTriggerTypeMatch &&
+            (mcontrol.load || mcontrol.store || mcontrol.execute)) continue;
+        // This slot is available for a new breakpoint.
+        mcontrol.trigger_type = RvTriggerTypeMatch;
+        mcontrol.dmode = 1;
+        mcontrol.select = 0;
+        mcontrol.timing = 0;
+        mcontrol.action = RvTriggerActionEnterDebugMode;
+        mcontrol.match = 0;
+        mcontrol.u = 1;
+        mcontrol.execute = 1;
+        mcontrol.store = 0;
+        mcontrol.load = 0;
+        abstract_cmd_reg_write(.regno(RvCoreCsrTData1), .value_q({BUS_DW'(mcontrol)}),
+                               .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        abstract_cmd_reg_write(.regno(RvCoreCsrTData2), .value_q({addr}), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        return;
+      end else break;
+    end
+    `uvm_error(`gfn, "No free breakpoint slots available")
+  endtask
+
+  // Restores previously deleted breakpoints.
+  virtual task restore_breakpoints(breakpoint_t breakpoints[$]);
+    `DV_CHECK(breakpoints.size())
+    foreach (breakpoints[i]) begin
+      abstract_cmd_err_e status;
+      abstract_cmd_reg_write(.regno(RvCoreCsrTSelect), .value_q({breakpoints[i].index}),
+                             .status(status));
+      `DV_CHECK_EQ(status, AbstractCmdErrNone)
+      abstract_cmd_reg_write(.regno(RvCoreCsrTData1), .value_q({breakpoints[i].tdata1}),
+                             .status(status));
+      `DV_CHECK_EQ(status, AbstractCmdErrNone)
+      abstract_cmd_reg_write(.regno(RvCoreCsrTData2), .value_q({breakpoints[i].tdata2}),
+                             .status(status));
+      `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    end
+  endtask
+
+  // Clear trigger data1 register and optionally, tdata2 as well.
+  //
+  // clear_tdata2: Knob to clear tdata2 as well.
+  // Note: It is upto the the caller to ensure the right trigger is selected first. Also, we are
+  // leaving tdata2 in a stale state.
+  protected virtual task clear_tdata1(bit clear_tdata2);
+    abstract_cmd_err_e status;
+    abstract_cmd_reg_write(.regno(RvCoreCsrTData1), .value_q({0}), .status(status));
+    `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    if (clear_tdata2) begin
+      abstract_cmd_reg_write(.regno(RvCoreCsrTData2), .value_q({0}), .status(status));
+      `DV_CHECK_EQ(status, AbstractCmdErrNone)
+    end
+  endtask
+
+  // Delete an existing breakpoint by index, or on the given address or symbol.
+  //
+  // index: The selected trigger index.
+  // addr: The PC address.
+  // symbol: Symbolic PC address.
+  // clear_tdata2: Knob to clear tdata2 as well.
+  // Note: Only either set index, addr or symbol.
+  virtual task delete_breakpoint(logic [BUS_AW-1:0] addr = 'x,
+                                 string symbol = "",
+                                 int index = -1,
+                                 bit clear_tdata2 = 0);
+    abstract_cmd_err_e status;
+    logic [BUS_DW-1:0] cmd_data[$];
+
+    if (index != -1) begin
+      bit valid;
+      if (addr !== 'x || symbol != "") `uvm_fatal(`gfn, "Only set either index, or addr or symbol")
+      is_valid_tselect_index(index, valid);
+
+      if (valid) begin
+        `uvm_info(`gfn, $sformatf("Deleting breakpoint at index %0d", index), UVM_LOW)
+        clear_tdata1(clear_tdata2);
+      end else begin
+        `uvm_error(`gfn, $sformatf("Index %0d appears to be out of bounds", index))
+      end
+      return;
+    end
+
+    addr_or_symbol_set(addr, symbol);
+    for (int i = 0; i >= 0; i++) begin  // Infinite loop.
+      bit valid;
+      is_valid_tselect_index(i, valid);
+      if (valid) begin
+        rv_core_csr_trigger_mcontrol_t mcontrol;
+        abstract_cmd_reg_read(.regno(RvCoreCsrTData1), .value_q(cmd_data), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        mcontrol = rv_core_csr_trigger_mcontrol_t'(cmd_data[0]);
+        if (!(mcontrol.trigger_type == RvTriggerTypeMatch &&
+              (mcontrol.load || mcontrol.store || mcontrol.execute))) continue;
+        // This slot has as a valid breakpoint. Check if address matches.
+        abstract_cmd_reg_read(.regno(RvCoreCsrTData2), .value_q(cmd_data), .status(status));
+        `DV_CHECK_EQ(status, AbstractCmdErrNone)
+        if (addr == cmd_data[0]) begin
+          `uvm_info(`gfn, $sformatf("Deleting breakpoint on 0x%0h(%0s)", addr, symbol), UVM_LOW)
+          clear_tdata1(clear_tdata2);
+          return;
+        end
+      end else break;
+    end
+    `uvm_error(`gfn, $sformatf("No breakpoint found associated with addr 0x%0h (%s)", addr,
+                               symbol))
+  endtask
+
+  // Single step.
+  //
+  // Read-modify-writes the DCSR register with the step field set to 1. The `step_entered` local
+  // status bit is checked for improved performance.
+  virtual task step(int num = 1);
+    if (!step_entered) begin
+      rv_core_csr_dcsr_t dcsr;
+      read_dcsr(dcsr);
+      dcsr.step = 1;
+      write_dcsr(dcsr);
+    end
+    `uvm_info(`gfn, "Single stepping", UVM_LOW)
+    set_resumereq(1);
+    wait_cpu_halted();
+  endtask
+
+  // Resume execution after entering a halted state.
+  //
+  // Equivalent of `continue` gdb command (`continue` is a reserved keyword in SystemVerilog, so we
+  // pick the name `continue_execution()` instead).
+  // If there is a breakpoint on the DPC register, then this task deletes all breakpoints,
+  // single-steps, restores all breakpoints and then resumes.
+  // do_wait_cpu_resumed: Optionally, poll the dmstatus register to wait for the CPU to have
+  //                      resumed (default off).
+  virtual task continue_execution(bit do_wait_cpu_resumed = 0);
+    logic [BUS_DW-1:0] pc;
+    breakpoint_t breakpoints[$];
+    rv_core_csr_dcsr_t dcsr;
+
+    read_pc(pc);
+    info_breakpoints(.breakpoints(breakpoints));
+    foreach (breakpoints[i]) begin
+      if (breakpoints[i].tdata2 == pc) begin
+        delete_breakpoint(.addr(pc));
+        step();
+        restore_breakpoints(.breakpoints({breakpoints[i]}));
+        break;
+      end
+    end
+
+    read_dcsr(dcsr);
+    dcsr.step = 0;
+    write_dcsr(dcsr);
+    `uvm_info(`gfn, "Continuing execution", UVM_LOW)
+    set_resumereq(1);
+
+    // NOTE: It may take a short while for the CPU to resume. If there are other upcoming
+    // breakpoints, the CPU may execute and immediately halt again, by the time `wait_cpu_resumed()`
+    // reads the dmstatus register to ascertain the CPU has indeed resumed. That will result in a
+    // timeout error. So, it is left to the caller to synchronize these events. The step below is
+    // hence conditioned on an optional argument which is set to 0 by default.
+    if (do_wait_cpu_resumed) wait_cpu_resumed();
+  endtask
+
+  // Execute calls as one instruction.
+  virtual task next();
+    `uvm_fatal(`gfn, "Not implemented")
+  endtask
+
+  // Finish executing the current function.
+  //
+  // It is assumed that the CPU is in a halted state at this point, and has just begun executing an
+  // arbitrary function. It reads the CPU's RA register and adds a breakpoint on it before
+  // continuing the execution.
+  //
+  // Note that this task adds a new breakpoint on the return address. After the CPU resumes, it
+  // waits for a few TCK cycles to let the CPU exit the debug mode. Then, it waits for the CPU to
+  // halt again. When it halts, the newly added breakpoint is deleted.
+  // return_address: Use an externally provided return address. If set to X, it reads the return
+  //                 address from the RA register.
+  // noreturn_function: Indicate that the function does not return. If set to 1, a breakpoint on RA
+  //                    is not added, and the task exits immediately after having the CPU continue
+  //                    executing the function.
+  virtual task finish(logic [BUS_AW-1:0] return_address = 'x, bit noreturn_function = 0);
+    if (return_address === 'x) begin
+      abstract_cmd_err_e status;
+      logic [BUS_DW-1:0] cmd_data[$];
+      abstract_cmd_reg_read(.regno(RvCoreCsrGprRa), .value_q(cmd_data), .status(status));
+      `DV_CHECK_EQ(status, AbstractCmdErrNone)
+      return_address = BUS_AW'(cmd_data[0]);
+    end
+    `uvm_info(`gfn, $sformatf("Adding a breakpoint on the return address: 0x%0h",
+                              return_address), UVM_MEDIUM)
+
+    if (!noreturn_function) add_breakpoint(.addr(return_address));
+    continue_execution();
+    if (!noreturn_function) begin
+      // It is better to wait for a few TCKs than call wait_cpu_resumed(), since the latter takes a
+      // long time, within which the CPU could have re-entered the halted state.
+      cfg.vif.wait_tck(20);
+      wait_cpu_halted();
+      check_pc(.exp_addr(return_address));
+      delete_breakpoint(.addr(return_address));
+    end
+  endtask
+
+  // Have the CPU execute a function with the specified arguments.
+  //
+  // addr: The function address.
+  // symbol: The function's symbol.
+  // args: The function args.
+  // noreturn_function: Indicate that the CPU does not return from this function.
+  virtual task call(logic [BUS_AW-1:0] addr = 'x,
+                    string symbol = "",
+                    logic [BUS_DW-1:0] args[$] = {},
+                    bit noreturn_function = 0);
+    byte status;
+    abstract_cmd_err_e abs_status;
+    logic [BUS_DW-1:0] cmd_data[$];
+    logic [BUS_DW-1:0] gprs[abstract_cmd_regno_t], new_sp;
+
+    `uvm_info(`gfn, "Saving all necessary GPRS", UVM_MEDIUM)
+    `DV_CHECK(args.size() <= 8)
+    abstract_cmd_reg_read(.regno(RvCoreCsrGprRa), .value_q(cmd_data), .status(abs_status));
+    `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    gprs[RvCoreCsrGprRa] = cmd_data[0];
+    abstract_cmd_reg_read(.regno(RvCoreCsrGprSp), .value_q(cmd_data), .status(abs_status));
+    `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    gprs[RvCoreCsrGprSp] = cmd_data[0];
+    // Setup a new stack 32 bytes from the current stack pointer, and align it to 32 bytes.
+    new_sp = (cmd_data[0] - 32) & ~('h1f);
+    abstract_cmd_reg_read(.regno(RvCoreCsrGprGp), .value_q(cmd_data), .status(abs_status));
+    `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    gprs[RvCoreCsrGprGp] = cmd_data[0];
+    for (int i = 0; i < cmd_data.size(); i++) begin
+      abstract_cmd_reg_read(.regno(RvCoreCsrGprA0 + i), .value_q(cmd_data), .status(abs_status));
+      `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+      gprs[RvCoreCsrGprA0 + i] = cmd_data[0];
+    end
+    read_pc(gprs[RvCoreCsrDpc]);
+
+    `uvm_info(`gfn, $sformatf("Setting new SP and RA: 0x%0h", new_sp), UVM_MEDIUM)
+    abstract_cmd_reg_write(.regno(RvCoreCsrGprRa), .value_q({new_sp}), .status(abs_status));
+    `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    abstract_cmd_reg_write(.regno(RvCoreCsrGprSp), .value_q({new_sp}), .status(abs_status));
+    `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    `uvm_info(`gfn, "Writing the opcode 'ebreak' at the SP address", UVM_MEDIUM)
+    mem_write(.addr(new_sp), .value_q({Ebreak}), .status(status));
+    `DV_CHECK_EQ(status, 0)
+    `uvm_info(`gfn, $sformatf("Writing function arguments [%p] to A registers", args), UVM_MEDIUM)
+    foreach (args[i]) begin
+      abstract_cmd_reg_write(.regno(RvCoreCsrGprA0 + i), .value_q({args[i]}), .status(abs_status));
+      `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+    end
+
+    write_pc(addr, symbol);
+    finish(.return_address(new_sp), .noreturn_function(noreturn_function));
+
+    if (!noreturn_function) begin
+      foreach (gprs[i]) begin
+        abstract_cmd_reg_write(.regno(i), .value_q({gprs[i]}), .status(abs_status));
+        `DV_CHECK_EQ(abs_status, AbstractCmdErrNone)
+      end
+      `uvm_info(`gfn, "Restored all necessary GPRS", UVM_MEDIUM)
+    end
+  endtask
+
+  // Initiates a single SBA access through JTAG (-> DTM -> DMI -> SBA).
+  //
+  // It writes DMI SBA registers to create a read or write access on the system bus, poll for its
+  // completion and return the response (on reads).
+  //
+  // req: The SBA access request item. It will be updated with the responses.
+  // sba_access_err_clear: Knob to clear the SBA access errors.
+  virtual task sba_access(sba_access_item req, bit sba_access_err_clear = 1'b1);
+    uvm_reg_data_t rdata, wdata;
+
+    // Update sbcs for the new transaction.
+    wdata = ral.sbcs.get_mirrored_value();
+    wdata = get_csr_val_with_updated_field(ral.sbcs.sbaccess, wdata, req.size);
+    if (req.bus_op == BusOpRead) begin
+      wdata = get_csr_val_with_updated_field(ral.sbcs.sbreadonaddr, wdata, req.readonaddr);
+      wdata = get_csr_val_with_updated_field(ral.sbcs.sbreadondata, wdata, req.readondata);
+    end else begin
+      // If we set these bits on writes, it complicates things.
+      wdata = get_csr_val_with_updated_field(ral.sbcs.sbreadonaddr, wdata, 0);
+      wdata = get_csr_val_with_updated_field(ral.sbcs.sbreadondata, wdata, 0);
+    end
+    wdata = get_csr_val_with_updated_field(ral.sbcs.sbautoincrement, wdata,
+                                           (req.autoincrement > 0));
+    if (wdata != ral.sbcs.sbaccess.get_mirrored_value()) begin
+      csr_wr(.ptr(ral.sbcs), .value(wdata), .blocking(1), .predict(predict_dmi_writes));
+    end
+
+    // Update the sbaddress0.
+    csr_wr(.ptr(ral.sbaddress0), .value(req.addr), .blocking(1), .predict(predict_dmi_writes));
+
+    // These steps are run in several branches in the following code. Macroize them.
+    `define BUSYWAIT_AND_EXIT_ON_ERR                                                            \
+        sba_access_busy_wait_and_clear_error(.req(req),                                         \
+            .sba_access_err_clear(sba_access_err_clear));                                       \
+                                                                                                \
+        // We don't know what to do if any of the following is true - return to the caller:     \
+        //                                                                                      \
+        // - The request timed out or returned is_busy_err                                      \
+        // - The access reported non-0 sberror and sba_access_err_clear is set to 0             \
+        // - Request was malformed (An SBA access is not made in the case)                      \
+        // - The access returned SbaErrOther error response (The DUT may need special handling) \
+        if (req.timed_out ||                                                                    \
+            req.is_busy_err ||                                                                  \
+            (req.is_err != SbaErrNone && !sba_access_err_clear) ||                              \
+            req.is_err inside {SbaErrBadAlignment, SbaErrBadSize, SbaErrOther}) begin           \
+          return;                                                                               \
+        end                                                                                     \
+
+    // Write / read sbdata, wait for transaction to complete.
+    if (req.bus_op == BusOpRead) begin
+      case ({req.readondata, req.readonaddr})
+        2'b00: begin
+          // No SBA read will be triggered. Read sbdata0 and and check status anyway (25% of the
+          // time). The external scoreboard is expected to verify that no SBA transaction is seen.
+          if (!cfg.in_reset && !$urandom_range(0, 3)) begin  // 25%
+            csr_rd(.ptr(ral.sbdata0), .value(rdata), .blocking(1));
+            `BUSYWAIT_AND_EXIT_ON_ERR
+          end
+        end
+        2'b01: begin
+          // SBA read already triggered on write to sbaddress0. Read sbdata0 to fetch the response
+          // data. If autoincrement is set, then return to the caller - it is not a valid usecase.
+          `BUSYWAIT_AND_EXIT_ON_ERR
+          if (!cfg.in_reset) begin
+            csr_rd(.ptr(ral.sbdata0), .value(rdata), .blocking(1));
+            req.rdata[0] = rdata;
+          end
+        end
+        2'b10, 2'b11: begin
+          if (!req.readonaddr) begin
+            // The first read to sbdata returns stale data. Discard it.
+            if (!cfg.in_reset) begin
+              csr_rd(.ptr(ral.sbdata0), .value(rdata), .blocking(1));
+            end
+          end
+
+          // Read sbdata req.autoincrement+1 number of times.
+          //
+          // Randomly also inject reads to sbaddress0 to verify address is correctly incremented
+          // (checked by sba_access_monitor or the external scoreboard).
+          for (int i = 0; i < req.autoincrement + 1; i++) begin
+            // The previous step triggered an SBA read. Wait for it to complete.
+            `BUSYWAIT_AND_EXIT_ON_ERR
+            if (!cfg.in_reset && i > 0 && !$urandom_range(0, 3)) begin  //25%
+              csr_rd(.ptr(ral.sbaddress0), .value(rdata));
+            end
+            // Before the last read, set readondata to 0 to prevent new SBA read triggers.
+            if (!cfg.in_reset && i == req.autoincrement) begin
+              wdata = get_csr_val_with_updated_field(ral.sbcs.sbreadondata, wdata, 0);
+              csr_wr(.ptr(ral.sbcs), .value(wdata), .blocking(1), .predict(predict_dmi_writes));
+            end
+            if (!cfg.in_reset) begin
+              csr_rd(.ptr(ral.sbdata0), .value(rdata));
+              req.rdata[i] = rdata;
+            end
+          end
+        end
+        default: begin
+          `uvm_fatal(`gfn, "Unreachable!")
+        end
+      endcase
+    end else begin
+      // Write sbdata req.autoincrement+1 number of times.
+      //
+      // Randomly also inject reads to sbaddress0 to verify address is correctly incremented
+      // (done externally by the scoreboard).
+      for (int i = 0; i < req.autoincrement + 1; i++) begin
+        if (!cfg.in_reset) begin
+          csr_wr(.ptr(ral.sbdata0), .value(req.wdata[i]), .blocking(1),
+                 .predict(predict_dmi_writes));
+        end
+        `BUSYWAIT_AND_EXIT_ON_ERR
+        if (i > 0 && !cfg.in_reset && !$urandom_range(0, 3)) begin  //25%
+          csr_rd(.ptr(ral.sbaddress0), .value(rdata), .blocking(1));
+        end
+      end
+    end
+
+  `undef BUSYWAIT_AND_EXIT_ON_ERR
+  endtask
+
+  // Read the SBA access status.
+  virtual task sba_access_status(input sba_access_item req, output logic is_busy);
+    uvm_reg_data_t data;
+    csr_rd(.ptr(ral.sbcs), .value(data), .blocking(1));
+    is_busy = get_field_val(ral.sbcs.sbbusy, data);
+    req.is_busy_err = get_field_val(ral.sbcs.sbbusyerror, data);
+    req.is_err = sba_access_err_e'(get_field_val(ral.sbcs.sberror, data));
+    if (!is_busy) begin
+      `uvm_info(`gfn, $sformatf("SBA req completed: %0s", req.sprint(uvm_default_line_printer)),
+                UVM_HIGH)
+    end
+  endtask
+
+  // Reads sbcs register to poll and wait for access to complete.
+  virtual task sba_access_busy_wait(input sba_access_item req);
+    logic is_busy;
+    `DV_SPINWAIT_EXIT(
+      do begin
+        sba_access_status(req, is_busy);
+        if (req.is_err != SbaErrNone) `DV_CHECK_EQ(is_busy, 0)
+      end while (is_busy && !req.is_busy_err);,
+      begin
+        fork
+          // TODO: Provide callbacks to support waiting for custom exit events.
+          wait(cfg.in_reset);
+          begin
+            // TODO: Make this timeout controllable.
+            #(cfg.vif.tck_period_ps * 100000 * 1ps);
+            req.timed_out = 1'b1;
+            `uvm_info(`gfn, $sformatf("SBA req timed out: %0s",
+                                      req.sprint(uvm_default_line_printer)), UVM_LOW)
+          end
+        join_any
+      end
+    )
+  endtask
+
+  // Clear SBA access busy error and access error sticky bits if they are set.
+  //
+  // Note that the req argument will continue to reflect the error bits.
+  virtual task sba_access_error_clear(sba_access_item req, bit clear_sbbusyerror = 1'b1,
+                                      bit clear_sberror = 1'b1);
+    uvm_reg_data_t data = ral.sbcs.get_mirrored_value();
+    if (clear_sbbusyerror && req.is_busy_err) begin
+      data = get_csr_val_with_updated_field(ral.sbcs.sbbusyerror, data, 1);
+    end
+    if (clear_sberror && req.is_err != SbaErrNone) begin
+      data = get_csr_val_with_updated_field(ral.sbcs.sberror, data, 1);
+    end
+    csr_wr(.ptr(ral.sbcs), .value(data), .blocking(1), .predict(predict_dmi_writes));
+  endtask
+
+  // Wrapper task for busy wait followed by clearing of sberror if an error was reported.
+  virtual task sba_access_busy_wait_and_clear_error(sba_access_item req, bit sba_access_err_clear);
+    sba_access_busy_wait(req);
+    // Only clear sberror - let the caller handle sbbusyerror.
+    if (!cfg.in_reset && sba_access_err_clear && req.is_err != SbaErrNone) begin
+      sba_access_error_clear(.req(req), .clear_sbbusyerror(0));
+    end
+  endtask
+
+  // Simplified version of sba_access(), for 32-bit reads.
+  virtual task sba_read(input logic [BUS_AW-1:0] addr,
+                        output logic [BUS_DW-1:0] value_q[$],
+                        output sba_access_err_e status,
+                        input int size = 1);
+    sba_access_item sba_item = sba_access_item::type_id::create("sba_item");
+    sba_item.bus_op = BusOpRead;
+    sba_item.addr = addr;
+    sba_item.size = SbaAccessSize32b;
+    sba_item.autoincrement = size > 1 ? size : 0;
+    sba_item.readonaddr = size == 1;
+    sba_item.readondata = size > 1;
+    sba_access(sba_item);
+    `DV_CHECK_EQ(sba_item.rdata.size(), size)
+    value_q = sba_item.rdata;
+    status = sba_item.is_err;
+    `DV_CHECK(!sba_item.is_busy_err)
+    `DV_CHECK(!sba_item.timed_out)
+  endtask
+
+  // Simplified version of sba_access(), for 32-bit writes.
+  virtual task sba_write(input logic [BUS_AW-1:0] addr,
+                         input logic [BUS_DW-1:0] value_q[$],
+                         output sba_access_err_e status);
+    sba_access_item sba_item = sba_access_item::type_id::create("sba_item");
+    sba_item.bus_op = BusOpWrite;
+    sba_item.addr = addr;
+    sba_item.wdata = value_q;
+    sba_item.size = SbaAccessSize32b;
+    sba_item.autoincrement = value_q.size() > 1 ? value_q.size() : 0;
+    sba_access(sba_item);
+    status = sba_item.is_err;
+    `DV_CHECK(!sba_item.is_busy_err)
+    `DV_CHECK(!sba_item.timed_out)
+  endtask
+
+  // Wrapper task for reading the system memory either via SBA or via program buffer.
+  //
+  // addr: The starting system address.
+  // value_q: The read data returned to the caller.
+  // status: The status of the op returned to the caller.
+  // size: The size of the read access in terms of full bus words.
+  // route: The route taken to access the system memory.
+  virtual task mem_read(input logic [BUS_AW-1:0] addr,
+                        output logic [BUS_DW-1:0] value_q[$],
+                        output byte status,
+                        input mem_access_route_e route = randomize_mem_access_route(),
+                        input int size = 1);
+    case (route)
+      MemAccessViaAbstractCmd, MemAccessViaProgbuf: begin
+        abstract_cmd_err_e abs_status;
+        abstract_cmd_mem_read(.addr(addr), .value_q(value_q), .status(abs_status), .size(size),
+                              .route(route));
+        status = byte'(abs_status);
+      end
+      MemAccessViaSba: begin
+        sba_access_err_e sba_status;
+        sba_read(.addr(addr), .value_q(value_q), .status(sba_status), .size(size));
+        status = byte'(sba_status);
+      end
+      default: `uvm_fatal(`gfn, $sformatf("Invalid route: 0x%0h", route))
+    endcase
+  endtask
+
+  // Wrapper task for writing to the system memory either via SBA or via program buffer.
+  //
+  // addr: The starting address of the system memory.
+  // value_q: The read data returned to the caller.
+  // status: The status of the op returned to the caller.
+  // size: The size of the read access in terms of full bus words.
+  // route: The route taken to access the system memory.
+  virtual task mem_write(input logic [BUS_AW-1:0] addr,
+                         input logic [BUS_DW-1:0] value_q[$],
+                         output byte status,
+                         input mem_access_route_e route = randomize_mem_access_route());
+    case (route)
+      MemAccessViaAbstractCmd, MemAccessViaProgbuf: begin
+        abstract_cmd_err_e abs_status;
+        abstract_cmd_mem_write(.addr(addr), .value_q(value_q), .status(abs_status), .route(route));
+        status = byte'(abs_status);
+      end
+      MemAccessViaSba: begin
+        sba_access_err_e sba_status;
+        sba_write(.addr(addr), .value_q(value_q), .status(sba_status));
+        status = byte'(sba_status);
+      end
+      default: `uvm_fatal(`gfn, $sformatf("Invalid route: 0x%0h", route))
+    endcase
+  endtask
+
+  // Load a memory location in the device with a custom image.
+  //
+  // Reads a VMEM file and writes the contents to the memory.
+  // vmem_file: Path to vmem file. Must be BUS_DW word sized.
+  // addr: The starting address of the system memory.
+  // route: The route taken to access the system memory.
+  virtual task load_image(string vmem_file,
+                          logic [BUS_AW-1:0] addr,
+                          input mem_access_route_e route = randomize_mem_access_route());
+    logic [BUS_DW-1:0] vmem_data[$];
+    byte status;
+    dv_utils_pkg::read_vmem(vmem_file, vmem_data);
+    `DV_CHECK_FATAL(vmem_data.size(), "vmem_data is empty!")
+    mem_write(.addr(addr), .value_q(vmem_data), .status(status), .route(route));
+    `DV_CHECK_EQ(status, 0)
+  endtask
+
+  virtual function mem_access_route_e randomize_mem_access_route();
+    mem_access_route_e route;
+    `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(route,
+                                       route inside {MemAccessViaProgbuf, MemAccessViaSba};)
+    return route;
+  endfunction
+
+endclass
diff --git a/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger_pkg.sv b/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger_pkg.sv
new file mode 100644
index 0000000..305b991
--- /dev/null
+++ b/hw/dv/sv/jtag_dmi_agent/jtag_rv_debugger_pkg.sv
@@ -0,0 +1,228 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// A package that models a RISC-V JTAG debugger.
+//
+// These utilities are semi-compliant with RISCV debug specification 0.13.2:
+// https://github.com/riscv/riscv-debug-spec/raw/4e0bb0fc2d843473db2356623792c6b7603b94d4/
+// riscv-debug-release.pdf
+//
+// OpenTitan uses the implementation of this spec in the PULP debug repository located at:
+// https://github.com/pulp-platform/riscv-dbg
+//
+// This is built on top of the capabilities provided by the jtag_dmi_agent_pkg.
+//
+// To access the chip resources over the SBA interface, it provides an SBA request item type as a
+// class, in sba_access_item. It also provides an sba_access_monitor, which generates predicted SBA
+// traffic based on SBA accesses sent.
+package jtag_rv_debugger_pkg;
+  import uvm_pkg::*;
+  import dv_utils_pkg::*;
+  import bus_params_pkg::*;
+  import dv_base_reg_pkg::*;
+  import csr_utils_pkg::*;
+  import dv_lib_pkg::*;
+  import jtag_agent_pkg::*;
+  import jtag_dmi_agent_pkg::*;
+
+  `include "uvm_macros.svh"
+  `include "dv_macros.svh"
+
+  // Abstract command types.
+  typedef enum logic [7:0] {
+    AbstractCmdRegAccess = 0,
+    AbstractCmdQuickAccess = 1,
+    AbstractCmdMemAccess = 2
+  } abstract_cmd_type_e;
+
+  // The register number.
+  typedef logic [15:0] abstract_cmd_regno_t;
+
+  // Abstract command register access struct.
+  typedef struct packed {
+    logic                 zero1;
+    logic [2:0]           aarsize;
+    logic                 aarpostincrement;
+    logic                 postexec;
+    logic                 transfer;
+    logic                 write;
+    abstract_cmd_regno_t  regno;
+  } abstract_cmd_reg_access_t;
+
+  // The abstract command error types.
+  typedef enum logic [2:0] {
+    AbstractCmdErrNone,
+    AbstractCmdErrBusy,
+    AbstractCmdErrUnsupported,
+    AbstractCmdErrException,
+    AbstractCmdErrHartUnavailable,
+    AbstractCmdErrBusError,
+    AbstractCmdErrReserved,
+    AbstractCmdErrOther
+  } abstract_cmd_err_e;
+
+  // SBA access size.
+  typedef enum logic [2:0] {
+    SbaAccessSize8b,
+    SbaAccessSize16b,
+    SbaAccessSize32b,
+    SbaAccessSize64b,
+    SbaAccessSize128b
+  } sba_access_size_e;
+
+  // SBA access error types.
+  typedef enum logic [2:0] {
+    SbaErrNone = 0,
+    SbaErrTimeout = 1,
+    SbaErrBadAddr = 2,
+    SbaErrBadAlignment = 3,
+    SbaErrBadSize = 4,
+    SbaErrOther = 7
+  } sba_access_err_e;
+
+  // The standard RISCV registers.
+  //
+  // Only some are captured here. More can be added if the need arises.
+  // See: https://riscv.org/wp-content/uploads/2019/03/riscv-debug-release.pdf (section 4.8) for
+  // more details.
+  typedef enum abstract_cmd_regno_t {
+    // Debug CSRs.
+    RvCoreCsrDcsr = 'h7b0,
+    RvCoreCsrDpc = 'h7b1,
+    RvCoreCsrDScratch0 = 'h7b2,
+    RvCoreCsrDScratch1 = 'h7b3,
+
+    // Trigger CSRs.
+    RvCoreCsrTSelect = 'h7a0,
+    RvCoreCsrTData1 = 'h7a1,
+    RvCoreCsrTData2 = 'h7a2,
+    RvCoreCsrTData3 = 'h7a3,
+    RvCoreCsrTInfo = 'h7a4,
+    RvCoreCsrMContext = 'h7a8,
+    RvCoreCsrSContext = 'h7aa,
+
+    // GPRs ABI Name (these map to x0-x31) (based on RV ISA).
+    RvCoreCsrGprZero = 'h1000,
+    RvCoreCsrGprRa,
+    RvCoreCsrGprSp,
+    RvCoreCsrGprGp,
+    RvCoreCsrGprTp,
+    RvCoreCsrGprT[0:2],
+    RvCoreCsrGprS[0:1],
+    RvCoreCsrGprA[0:7],
+    RvCoreCsrGprS[2:11],
+    RvCoreCsrGprT[3:6]
+  } rv_core_csr_e;
+
+  // CPU privilege levels
+  typedef enum logic [1:0] {
+    RvPrivilegeLevelM = 2'b11,
+    RvPrivilegeLevelS = 2'b01,
+    RvPrivilegeLevelU = 2'b00
+  } rv_privilege_level_e;
+
+  // Debug mode causes.
+  typedef enum logic [2:0] {
+    RvDebugCauseNone = 0,
+    RvDebugCauseEbreak = 1,
+    RvDebugCauseTrigger = 2,
+    RvDebugCauseHaltReq = 3,
+    RvDebugCauseStep = 4,
+    RvDebugCauseResetHaltReq = 5
+  } rv_core_csr_dcsr_cause_e;
+
+  // Ways to access the system memory.
+  typedef enum logic [1:0] {
+    MemAccessViaAbstractCmd,  // Unsupported.
+    MemAccessViaProgbuf,
+    MemAccessViaSba
+  } mem_access_route_e;
+
+  // Typical commands used in debugger activities.
+  typedef enum logic [31:0] {
+    Illegal = 'h0,
+    Fence = 'hf,
+    Nop = 'h13,
+    AddiS0S04 = 'h440413, // addr s0, s0, 4
+    SwS1S0 = 'h942023,  // swr s1 0(s0)
+    LwS0S0 = 'h42403,  // lw s0, 0(s0)
+    LwS1S0 = 'h42483,  // lw s1, 0(s0)
+    FenceI = 'h100f,
+    Ebreak = 'h100073,
+    Wfi = 'h10500073
+  } rv_opcode_e;
+
+  // DCSR fields.
+  //
+  // See: https://riscv.org/wp-content/uploads/2019/03/riscv-debug-release.pdf (section 4.8.1) for
+  // more details.
+  typedef struct packed {
+    logic [3:0]               xdebugver;
+    logic [11:0]              zero2;
+    logic                     ebreakm;
+    logic                     zero1;
+    logic                     ebreaks;
+    logic                     ebreaku;
+    logic                     stepie;
+    logic                     stopcount;
+    logic                     stoptime;
+    rv_core_csr_dcsr_cause_e  cause;
+    logic                     zero0;
+    logic                     mprven;
+    logic                     nmip;
+    logic                     step;
+    rv_privilege_level_e      prv;
+  } rv_core_csr_dcsr_t;
+
+  // Types of triggers.
+  typedef enum logic [3:0] {
+    RvTriggerTypeNone = 0,
+    RvTriggerTypeLegacy = 1,
+    RvTriggerTypeMatch = 2,
+    RvTriggerTypeICount = 3,
+    RvTriggerTypeException = 4
+  } rv_core_csr_trigger_type_e;
+
+  typedef enum logic [3:0] {
+    RvTriggerActionRaiseBreakpointException = 0,
+    RvTriggerActionEnterDebugMode = 1,
+    RvTriggerActionReserved[2:15]
+  } rv_core_csr_trigger_action_e;
+
+  // Address / data match trigger control CSR.
+  typedef struct packed {
+    rv_core_csr_trigger_type_e    trigger_type;  // 31:28
+    logic                         dmode;
+    logic [5:0]                   maskmax;
+    logic                         hit;  // 20
+    logic                         select;
+    logic                         timing;
+    logic [1:0]                   sizelo;
+    rv_core_csr_trigger_action_e  action;
+    logic                         chain;  // 12
+    logic [3:0]                   match;
+    logic                         m;
+    logic                         zero;
+    logic                         s;
+    logic                         u;
+    logic                         execute;
+    logic                         store;
+    logic                         load;
+  } rv_core_csr_trigger_mcontrol_t;
+
+  // A generic breakpoint datastructure.
+  typedef struct packed {
+    rv_core_csr_trigger_type_e  trigger_type;
+    int unsigned                index;
+    logic [BUS_DW-1:0]          tdata1;
+    logic [BUS_DW-1:0]          tdata2;
+  } breakpoint_t;
+
+  // Sources.
+  `include "sba_access_item.sv"
+  `include "sba_access_monitor.sv"
+  `include "sba_access_reg_frontdoor.sv"
+  `include "jtag_rv_debugger.sv"
+
+endpackage
diff --git a/hw/dv/sv/jtag_dmi_agent/sba_access_item.sv b/hw/dv/sv/jtag_dmi_agent/sba_access_item.sv
index e8ea9bb..9627769 100644
--- a/hw/dv/sv/jtag_dmi_agent/sba_access_item.sv
+++ b/hw/dv/sv/jtag_dmi_agent/sba_access_item.sv
@@ -7,50 +7,48 @@
   rand bus_op_e           bus_op;
   rand sba_access_size_e  size;
   rand logic [BUS_AW-1:0] addr;
-  rand logic [BUS_DW-1:0] wdata;
+  rand logic [BUS_DW-1:0] wdata[$];
 
   // Special stimulus configuration knobs.
   rand logic readonaddr;
   rand logic readondata;
-  rand logic autoincrement;
+
+  // Have the design autoincrement the address.
+  //
+  // If set to 0, the autoincrement feature is disabled and a single access is made. If set to N (N
+  // > 0), N + 1 accesses are made by jtag_rv_debugger::sba_acess() task. This is only used during
+  // the generation of stimulus. It is not used by the sba_access_monitor.
+  uint autoincrement;
 
   // Response side signals.
-  rand logic [BUS_DW-1:0] rdata;
-  rand  sba_access_err_e  is_err;
-  rand logic              is_busy_err;
-  rand logic              timed_out;
+  logic [BUS_DW-1:0] rdata[$];
+  sba_access_err_e   is_err;
+  logic              is_busy_err;
+  logic              timed_out;
+
+  constraint wdata_size_c {
+    wdata.size() == (autoincrement + 1);
+  }
 
   `uvm_object_utils_begin(sba_access_item)
     `uvm_field_enum(bus_op_e, bus_op,         UVM_DEFAULT)
     `uvm_field_enum(sba_access_size_e, size,  UVM_DEFAULT)
     `uvm_field_int (addr,                     UVM_DEFAULT)
-    `uvm_field_int (wdata,                    UVM_DEFAULT)
+    `uvm_field_queue_int(wdata,               UVM_DEFAULT)
     `uvm_field_int (readonaddr,               UVM_DEFAULT)
     `uvm_field_int (readondata,               UVM_DEFAULT)
     `uvm_field_int (autoincrement,            UVM_DEFAULT)
-    `uvm_field_int (rdata,                    UVM_DEFAULT)
+    `uvm_field_queue_int(rdata,               UVM_DEFAULT)
     `uvm_field_int (is_busy_err,              UVM_DEFAULT)
     `uvm_field_enum(sba_access_err_e, is_err, UVM_DEFAULT)
     `uvm_field_int (timed_out,                UVM_DEFAULT)
   `uvm_object_utils_end
 
-  `uvm_object_new
-
-  virtual function void disable_req_randomization();
-    bus_op.rand_mode(0);
-    addr.rand_mode(0);
-    size.rand_mode(0);
-    wdata.rand_mode(0);
-    readonaddr.rand_mode(0);
-    readondata.rand_mode(0);
-    autoincrement.rand_mode(0);
-  endfunction
-
-  virtual function void disable_rsp_randomization();
-    rdata.rand_mode(0);
-    is_err.rand_mode(0);
-    is_busy_err.rand_mode(0);
-    timed_out.rand_mode(0);
-  endfunction
+  function new (string name = "");
+    super.new(name);
+    is_err = SbaErrNone;
+    is_busy_err = 0;
+    timed_out = 0;
+  endfunction : new
 
 endclass
diff --git a/hw/dv/sv/jtag_dmi_agent/sba_access_monitor.sv b/hw/dv/sv/jtag_dmi_agent/sba_access_monitor.sv
index 5eef3c1..84bf7d7 100644
--- a/hw/dv/sv/jtag_dmi_agent/sba_access_monitor.sv
+++ b/hw/dv/sv/jtag_dmi_agent/sba_access_monitor.sv
@@ -57,8 +57,6 @@
     end
     if (jtag_dmi_ral.sbcs.sbaccess128.get_reset()) begin
       sba_addrs.push_back(jtag_dmi_ral.sbdata2.get_address());
-    end
-    if (jtag_dmi_ral.sbcs.sbaccess128.get_reset()) begin
       sba_addrs.push_back(jtag_dmi_ral.sbdata3.get_address());
     end
     `uvm_info(`gfn, $sformatf("sba_addrs: %0p", sba_addrs), UVM_LOW)
@@ -101,8 +99,9 @@
       csr = jtag_dmi_ral.default_map.get_reg_by_offset(dmi_item.addr);
 
       if (dmi_item.req_op == DmiOpRead) begin
-        process_sba_csr_read(csr, dmi_item);
-        void'(csr.predict(.value(dmi_item.rdata), .kind(UVM_PREDICT_READ)));
+        if (process_sba_csr_read(csr, dmi_item)) begin
+          void'(csr.predict(.value(dmi_item.rdata), .kind(UVM_PREDICT_READ)));
+        end
       end
       else if (dmi_item.req_op == DmiOpWrite) begin
         void'(csr.predict(.value(dmi_item.wdata), .kind(UVM_PREDICT_WRITE)));
@@ -111,62 +110,104 @@
     end
   endtask
 
-  virtual protected function void process_sba_csr_read(uvm_reg csr, jtag_dmi_item dmi_item);
+  // Predict what is expected to happen if one of the SBA registers is read.
+  //
+  // If the sbcs register is read, and sbbusy bit drops on a pending write, we consider the
+  // transaction to have completed - we write the predicted SBA transaction to the analysis port.
+  //
+  // Returns a bit to the caller indicating whether to predict the CSR read or not.
+  virtual protected function bit process_sba_csr_read(uvm_reg csr, jtag_dmi_item dmi_item);
+    uvm_reg_data_t readondata  = `gmv(jtag_dmi_ral.sbcs.sbreadondata);
+    uvm_reg_data_t readonaddr  = `gmv(jtag_dmi_ral.sbcs.sbreadonaddr);
+    uvm_reg_data_t sbbusy      = `gmv(jtag_dmi_ral.sbcs.sbbusy);
+    uvm_reg_data_t sbbusyerror = `gmv(jtag_dmi_ral.sbcs.sbbusyerror);
+    uvm_reg_data_t sberror     = `gmv(jtag_dmi_ral.sbcs.sberror);
+    bit do_predict = 1;
+
     case (csr.get_name())
       "sbcs": begin
-        uvm_reg_data_t sbbusy       = get_field_val(jtag_dmi_ral.sbcs.sbbusy, dmi_item.rdata);
-        uvm_reg_data_t sbbusyerror  = get_field_val(jtag_dmi_ral.sbcs.sbbusyerror, dmi_item.rdata);
-        uvm_reg_data_t sberror      = get_field_val(jtag_dmi_ral.sbcs.sberror, dmi_item.rdata);
+        // Update the status bits from transaction item.
+        sbbusy      = get_field_val(jtag_dmi_ral.sbcs.sbbusy, dmi_item.rdata);
+        sbbusyerror = get_field_val(jtag_dmi_ral.sbcs.sbbusyerror, dmi_item.rdata);
+        sberror     = get_field_val(jtag_dmi_ral.sbcs.sberror, dmi_item.rdata);
 
-        // We should have predicted an SBA access if sbbusy got set.
-        if (sbbusy) `DV_CHECK(sba_req_q.size(), "sbbusy indicated, but no SBA access was predicted")
+        // We should have predicted an SBA access if any of the status bits got set.
+        if (sbbusy || sbbusyerror) begin
+          `DV_CHECK(sba_req_q.size(),
+                    $sformatf({"One of these bits is set, but no SBA access was predicted: ",
+                               "sbbusy=%0b, sbbusyerror=%0b"}, sbbusy, sbbusyerror))
+        end
 
-        // Check if our error predictions were correct.
-        `DV_CHECK_EQ(sbbusyerror, jtag_dmi_ral.sbcs.sbbusyerror.get_mirrored_value())
-        `DV_CHECK_EQ(sberror, jtag_dmi_ral.sbcs.sberror.get_mirrored_value())
+        // Check if we correctly predicted busy error.
+        `DV_CHECK_EQ(sbbusyerror, `gmv(jtag_dmi_ral.sbcs.sbbusyerror))
+        if (sbbusyerror) sba_req_q[0].is_busy_err = 1'b1;
 
-        if (sba_req_q.size() > 0) begin
+        // Check if we correctly predicted the malformed SBA access request errors.
+        //
+        // We can only predict SbaErrBadAlignment and SbaErrBadSize errors. For the externally
+        // indicated errors SbaErrTimeout, SbaErrBadAddr and SbaErrOther, we pass the actually seen
+        // sberror to the sba_access_item that is written to the analysis port. The external entity
+        // reading from this port is expected to verify the correctness of these errors.
+        if (sberror inside {SbaErrNone, SbaErrBadAlignment, SbaErrBadSize}) begin
+          `DV_CHECK_EQ(sberror, `gmv(jtag_dmi_ral.sbcs.sberror))
+        end
+
+        if (sba_req_q.size()) begin
+          if (sberror) sba_req_q[0].is_err = sba_access_err_e'(sberror);
           if (!sbbusy) begin
-            predict_autoincr_sba_addr();
+            // Write the predicted SBA write transaction to the analysis port.
             if (sba_req_q[0].bus_op == BusOpWrite) begin
-              // Mark the write access as complete if sbbusy deasserted.
-              ITEM_T tr = sba_req_q.pop_front();
-              tr.is_err = SbaErrNone;
-              tr.is_busy_err = 0;
-              analysis_port.write(tr);
+              analysis_port.write(sba_req_q.pop_front());
+              predict_autoincr_sba_addr();
             end
           end
         end
       end
       "sbaddress0": begin
-        `DV_CHECK_EQ(dmi_item.rdata, jtag_dmi_ral.sbaddress0.get_mirrored_value())
+        uvm_reg_data_t exp_addr = `gmv(jtag_dmi_ral.sbaddress0);
+        uvm_reg_data_t autoincrement = `gmv(jtag_dmi_ral.sbcs.sbautoincrement);
+        if (autoincrement) begin
+          sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
+          // Depending on when the sbaddress0 is read, the predicted sbaddress0 value could be off
+          // (less than) the observed by at most 1 increment value.
+          `DV_CHECK(dmi_item.rdata inside {exp_addr, exp_addr + (1 << size)})
+          // Skip updating the mirrored value (at the call site) since we predict the addr
+          // separately.
+          do_predict = 0;
+        end else begin
+          `DV_CHECK_EQ(dmi_item.rdata, exp_addr)
+        end
       end
       "sbdata0": begin
         // `DV_CHECK_EQ(dmi_item.rdata, jtag_dmi_ral.sbdata0.get_mirrored_value())
-        if (sba_req_q.size() > 0) begin
-          // If SBA read access completed, then return the data read from this register. We count on
-          // stimulus to have read the sbcs register before to ensure the access actually completed.
-          if (sba_req_q[0].bus_op == BusOpRead &&
-              !jtag_dmi_ral.sbcs.sbbusy.get_mirrored_value()) begin
-            ITEM_T tr = sba_req_q.pop_front();
-            tr.rdata = dmi_item.rdata;
-            tr.is_err = SbaErrNone;
-            tr.is_busy_err = 0;
-            analysis_port.write(tr);
+        // If SBA read access completed, then return the data read from this register. We count
+        // on stimulus to have read the sbcs register before to ensure the access actually
+        // completed. The external scoreboard is expected to verify the correctness of externally
+        // indicated errors SbaErrTimeout, SbaErrBadAddr and SbaErrOther, when the stimulus reads
+        // the sbcs register during a pending SBA read transaction.
+        //
+        // The stimulus (in jtag_rv_debugger:sba_access()) terminates the SBA access after
+        // reading sbdata0 on read transactions.
+        if (sba_req_q.size()) begin
+          if (sba_req_q[0].bus_op == BusOpRead && !sbbusy) begin
+            sba_req_q[0].rdata[0] = dmi_item.rdata;
+            analysis_port.write(sba_req_q.pop_front());
+            predict_autoincr_sba_addr();
           end
         end
         // If readondata is set, then a read to this register will trigger a new SBA read.
-        if (jtag_dmi_ral.sbcs.sbreadondata.get_mirrored_value()) begin
+        if (readondata) begin
           void'(predict_sba_req(BusOpRead));
         end
-        void'(csr.predict(.value(dmi_item.rdata), .kind(UVM_PREDICT_READ)));
       end
       default: begin
-        `uvm_info(`gfn, $sformatf("Read to SBA CSR %0s is unsupported", csr.`gfn), UVM_LOW)
+        `uvm_fatal(`gfn, $sformatf("Read to SBA CSR %0s is unsupported", csr.`gfn))
       end
     endcase
+    return do_predict;
   endfunction
 
+  // Predict what is expected to happen if one of the SBA registers is written.
   virtual protected function void process_sba_csr_write(uvm_reg csr);
     case (csr.get_name())
       "sbcs": begin
@@ -183,14 +224,14 @@
         void'(predict_sba_req(BusOpWrite));
       end
       default: begin
-        `uvm_info(`gfn, $sformatf("Write to SBA CSR %0s is unsupported", csr.`gfn), UVM_LOW)
+        `uvm_fatal(`gfn, $sformatf("Write to SBA CSR %0s is unsupported", csr.`gfn))
       end
     endcase
   endfunction
 
   virtual task monitor_ready_to_end();
     forever begin
-      if (sba_req_q.size() == 0) begin
+      if (!sba_req_q.size()) begin
         ok_to_end = 1'b1;
         wait (sba_req_q.size());
       end else begin
@@ -222,9 +263,9 @@
   // item: The returned expected request predicted to be sent.
   // returns 1 if a new SBA request is expected to be sent, else 0.
   virtual protected function bit predict_sba_req(input bus_op_e bus_op);
-    uvm_reg_addr_t addr = jtag_dmi_ral.sbaddress0.get_mirrored_value();
-    uvm_reg_data_t data = jtag_dmi_ral.sbdata0.get_mirrored_value();
-    sba_access_size_e size = sba_access_size_e'(jtag_dmi_ral.sbcs.sbaccess.get_mirrored_value());
+    uvm_reg_addr_t addr = `gmv(jtag_dmi_ral.sbaddress0);
+    uvm_reg_data_t data = `gmv(jtag_dmi_ral.sbdata0);
+    sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
     sba_access_item item;
 
     // Is the address aligned?
@@ -241,7 +282,7 @@
     end
 
     // Is there already a pending transaction?
-    if (jtag_dmi_ral.sbcs.sbbusy.get_mirrored_value()) begin
+    if (`gmv(jtag_dmi_ral.sbcs.sbbusy)) begin
       void'(jtag_dmi_ral.sbcs.sbbusyerror.predict(.value(1), .kind(UVM_PREDICT_DIRECT)));
       return 0;
     end
@@ -253,9 +294,12 @@
     item.is_err = SbaErrNone;
     item.is_busy_err = 0;
     item.timed_out = 0;
-    if (bus_op == BusOpWrite) item.wdata = data;
-
-    `DV_CHECK_EQ(sba_req_q.size(), 0)
+    if (bus_op == BusOpWrite) item.wdata[0] = data;
+    `uvm_info(`gfn, $sformatf("Predicted new SBA req: %0s",
+                              item.sprint(uvm_default_line_printer)), UVM_MEDIUM)
+    `DV_CHECK_EQ(sba_req_q.size(), 0,
+                 $sformatf("Predicted new SBA req before previous req %0s was popped.",
+                           sba_req_q[$].sprint(uvm_default_line_printer)))
     sba_req_q.push_back(item);
     req_analysis_port.write(item);
     void'(jtag_dmi_ral.sbcs.sbbusy.predict(.value(1), .kind(UVM_PREDICT_DIRECT)));
@@ -265,11 +309,13 @@
   // If autoincr is set then predict the new address. Invoked after the successful completion of
   // previous transfer.
   virtual function void predict_autoincr_sba_addr();
-    if (jtag_dmi_ral.sbcs.sbautoincrement.get_mirrored_value()) begin
-      sba_access_size_e size = sba_access_size_e'(jtag_dmi_ral.sbcs.sbaccess.get_mirrored_value());
-      uvm_reg_data_t addr = jtag_dmi_ral.sbaddress0.get_mirrored_value();
+    if (`gmv(jtag_dmi_ral.sbcs.sbautoincrement)) begin
+      sba_access_size_e size = sba_access_size_e'(`gmv(jtag_dmi_ral.sbcs.sbaccess));
+      uvm_reg_data_t addr = `gmv(jtag_dmi_ral.sbaddress0);
       void'(jtag_dmi_ral.sbaddress0.predict(.value(addr + (1 << size)),
                                             .kind(UVM_PREDICT_DIRECT)));
+      `uvm_info(`gfn, $sformatf("Predicted sbaddr after autoincr: 0x%0h -> 0x%0h",
+                                addr, `gmv(jtag_dmi_ral.sbaddress0)), UVM_HIGH)
     end
   endfunction
 
diff --git a/hw/dv/sv/jtag_dmi_agent/sba_access_reg_frontdoor.sv b/hw/dv/sv/jtag_dmi_agent/sba_access_reg_frontdoor.sv
new file mode 100644
index 0000000..680e12f
--- /dev/null
+++ b/hw/dv/sv/jtag_dmi_agent/sba_access_reg_frontdoor.sv
@@ -0,0 +1,69 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// Frontdoor (indirect) access of CSRs over SBA using the JTAG interface.
+//
+// The system CSRs are accessed indirectly using the jtag_rv_debugger::sba_access() utility. Note
+// that if this frontdoor is attached to the chip RAL model, then it takes precedence over the
+// TL-based register map / adapter based built-in frontdoor. To attach this frontdoor, simply set
+// the user_ftdr arg of csr_utils_pkg::csr_rd*|wr|update|spinwait() tasks to this class' instance.
+typedef class jtag_rv_debugger;
+class sba_access_reg_frontdoor extends uvm_reg_frontdoor;
+  `uvm_object_utils(sba_access_reg_frontdoor)
+
+  // Handle to JTAG RV debugger instance.
+  jtag_rv_debugger debugger_h;
+
+  `uvm_object_new
+
+  virtual task body();
+    uvm_reg_data_t  data;
+    csr_field_t     csr_or_fld;
+    sba_access_item sba_item;
+
+    `uvm_info(`gfn, $sformatf("CSR req via SBA started: %0s", rw_info.convert2string()), UVM_HIGH)
+    `DV_CHECK_FATAL(rw_info.element_kind inside {UVM_REG, UVM_FIELD})
+    `DV_CHECK_FATAL(rw_info.kind inside {UVM_READ, UVM_WRITE})
+    `DV_CHECK_FATAL(rw_info.path == UVM_FRONTDOOR)
+
+    csr_or_fld = decode_csr_or_field(rw_info.element);
+    sba_item = sba_access_item::type_id::create("sba_item");
+    sba_item.addr = csr_or_fld.csr.get_address();
+    sba_item.size = SbaAccessSize32b;
+    if (rw_info.kind == UVM_WRITE) begin
+      sba_item.bus_op = BusOpWrite;
+      data = rw_info.value[0];
+      if (csr_or_fld.field != null) begin
+        data = get_csr_val_with_updated_field(csr_or_fld.field, `gmv(csr_or_fld.csr),
+                                              rw_info.value[0]);
+      end
+      sba_item.wdata[0] = data;
+    end else begin
+      sba_item.bus_op = BusOpRead;
+      sba_item.readonaddr = 1;
+      sba_item.readondata = 0;
+    end
+
+    debugger_h.sba_access(sba_item);
+
+    if (sba_item.is_err || sba_item.is_busy_err || sba_item.timed_out) begin
+      `uvm_info(`gfn, $sformatf({"CSR req via SBA has error: is_err = %0b, is_busy_err = %0b, ",
+                                 "timed_out = %0b"}, sba_item.is_err, sba_item.is_busy_err,
+                                sba_item.timed_out), UVM_LOW)
+      rw_info.status = UVM_NOT_OK;
+    end else begin
+      rw_info.status = UVM_IS_OK;
+      if (rw_info.kind == UVM_READ) begin
+        rw_info.value = new[1];
+        data = sba_item.rdata[0];
+        if (csr_or_fld.field != null) begin
+          data = get_field_val(csr_or_fld.field, data);
+        end
+        rw_info.value[0] = data;
+      end
+    end
+    `uvm_info(`gfn, $sformatf("CSR req via SBA completed: %0s", rw_info.convert2string()), UVM_HIGH)
+  endtask
+
+endclass
diff --git a/hw/dv/sv/jtag_dmi_agent/sba_access_utils_pkg.sv b/hw/dv/sv/jtag_dmi_agent/sba_access_utils_pkg.sv
deleted file mode 100644
index ee3a810..0000000
--- a/hw/dv/sv/jtag_dmi_agent/sba_access_utils_pkg.sv
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-// A package that provides common SBA access utilities from JTAG.
-package sba_access_utils_pkg;
-  import uvm_pkg::*;
-  import dv_utils_pkg::*;
-  import bus_params_pkg::*;
-  import dv_base_reg_pkg::*;
-  import csr_utils_pkg::*;
-  import dv_lib_pkg::*;
-  import jtag_agent_pkg::*;
-  import jtag_dmi_agent_pkg::*;
-  `include "uvm_macros.svh"
-  `include "dv_macros.svh"
-
-  typedef enum logic [2:0] {
-    SbaAccessSize8b,
-    SbaAccessSize16b,
-    SbaAccessSize32b,
-    SbaAccessSize64b,
-    SbaAccessSize128b
-  } sba_access_size_e;
-
-  typedef enum logic [2:0] {
-    SbaErrNone = 0,
-    SbaErrTimeout = 1,
-    SbaErrBadAddr = 2,
-    SbaErrBadAlignment = 3,
-    SbaErrBadSize = 4,
-    SbaErrOther = 7
-  } sba_access_err_e;
-
-  localparam string MsgId = "sba_access_utils";
-  typedef class sba_access_item;
-
-  // Initiates a single SBA access through JTAG (-> DTM -> DMI -> SBA).
-  //
-  // It writes DMI SBA registers to create a read or write access on the system bus, poll for its
-  // completion and return the response (on reads).
-  //
-  // jtag_dmi_ral: A handle to the DMI RAL block that has the SBA registers.
-  // cfg: A handle to the jtag_agent_cfg.
-  // req: The SBA access request item. It will be updated with the responses.
-  // sba_access_err_clear: Knob to clear the SBA access errors.
-  task automatic sba_access(input jtag_dmi_reg_block jtag_dmi_ral,
-                            input jtag_agent_cfg cfg,
-                            input sba_access_item req,
-                            input bit sba_access_err_clear = 1'b1);
-
-    uvm_reg_data_t rdata, wdata;
-    logic is_busy;
-
-    // Update sbcs for the new transaction.
-    wdata = jtag_dmi_ral.sbcs.get_mirrored_value();
-    wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbaccess, wdata, req.size);
-    if (req.bus_op == BusOpRead) begin
-      wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbreadonaddr, wdata, req.readonaddr);
-      wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbreadondata, wdata, req.readondata);
-    end else begin
-      // If we set these bits on writes, it complicates things.
-      wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbreadonaddr, wdata, 0);
-      wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbreadondata, wdata, 0);
-    end
-    wdata = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbautoincrement, wdata,
-                                           req.autoincrement);
-    if (wdata != jtag_dmi_ral.sbcs.sbaccess.get_mirrored_value()) begin
-      csr_wr(.ptr(jtag_dmi_ral.sbcs), .value(wdata), .predict(1));
-    end
-
-    csr_wr(.ptr(jtag_dmi_ral.sbaddress0), .value(req.addr), .predict(1));
-    if (req.bus_op == BusOpRead) begin
-      // Writing to addr with readonaddr would have already trigger an SBA read.
-      if (req.readondata && !req.readonaddr) begin
-        csr_rd(.ptr(jtag_dmi_ral.sbdata0), .value(rdata));
-      end
-      if (!req.readonaddr && !req.readondata) begin
-        `uvm_info(MsgId, {"readonaddr and readondata are not set. ",
-                          "Read request will not be triggered. Returning."}, UVM_MEDIUM)
-        return;
-      end
-    end else begin
-      csr_wr(.ptr(jtag_dmi_ral.sbdata0), .value(req.wdata), .predict(1));
-    end
-
-    // Wait for the access to complete.
-    sba_access_busy_wait(jtag_dmi_ral, cfg, req, is_busy);
-
-    // If the access returns with an error, then the request was not made - return back to the
-    // caller.
-    if (req.is_busy_err || req.is_err != SbaErrNone) begin
-      if (!cfg.in_reset && sba_access_err_clear) begin
-        sba_access_error_clear(jtag_dmi_ral, req);
-      end
-      return;
-    end
-
-    // Return the data on reads.
-    if (req.bus_op == BusOpRead && !cfg.in_reset && !is_busy) begin
-      csr_rd(.ptr(jtag_dmi_ral.sbdata0), .value(req.rdata));
-    end
-
-    // If readondata is set, the read above will trigger another SBA read, which needs to be handled
-    // by the caller.
-  endtask
-
-  // Read the SBA access status.
-  task automatic sba_access_status(input jtag_dmi_reg_block jtag_dmi_ral,
-                                   input sba_access_item req,
-                                   output logic is_busy);
-    uvm_reg_data_t data;
-    csr_rd(.ptr(jtag_dmi_ral.sbcs), .value(data));
-    is_busy = get_field_val(jtag_dmi_ral.sbcs.sbbusy, data);
-    req.is_busy_err = get_field_val(jtag_dmi_ral.sbcs.sbbusyerror, data);
-    req.is_err = sba_access_err_e'(get_field_val(jtag_dmi_ral.sbcs.sberror, data));
-    `uvm_info(MsgId, $sformatf("SBA req status: %0s", req.sprint(uvm_default_line_printer)),
-              UVM_HIGH)
-  endtask
-
-  // Reads sbcs register to poll and wait for access to complete.
-  task automatic sba_access_busy_wait(input jtag_dmi_reg_block jtag_dmi_ral,
-                                      input jtag_agent_cfg cfg,
-                                      input sba_access_item req,
-                                      output logic is_busy);
-    `DV_SPINWAIT_EXIT(
-      do begin
-        sba_access_status(jtag_dmi_ral, req, is_busy);
-        if (req.is_err != SbaErrNone) `DV_CHECK_EQ(is_busy, 0, , , MsgId)
-      end while (is_busy && !req.is_busy_err);,
-      begin
-        fork
-          // TODO: Provide callbacks to support waiting for custom exit events.
-          wait(cfg.in_reset);
-          begin
-            // TODO: Make this timeout controllable.
-            #(cfg.vif.tck_period_ns * 100000 * 1ns);
-            req.timed_out = 1'b1;
-            `uvm_info(MsgId, $sformatf("SBA req timed out: %0s",
-                                       req.sprint(uvm_default_line_printer)), UVM_LOW)
-          end
-        join_any
-      end,
-      , MsgId
-    )
-  endtask
-
-  // Clear SBA access busy error and access error sticky bits if they are set.
-  //
-  // Note that the req argument will continue to reflect the error bits.
-  task automatic sba_access_error_clear(jtag_dmi_reg_block jtag_dmi_ral, sba_access_item req);
-    uvm_reg_data_t data = jtag_dmi_ral.sbcs.get_mirrored_value();
-    if (req.is_busy_err) begin
-      data = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sbbusyerror, data, 1);
-    end
-    if (req.is_err != SbaErrNone) begin
-      data = get_csr_val_with_updated_field(jtag_dmi_ral.sbcs.sberror, data, 1);
-    end
-    csr_wr(.ptr(jtag_dmi_ral.sbcs), .value(data), .predict(1));
-  endtask
-
-  `include "sba_access_item.sv"
-  `include "sba_access_monitor.sv"
-
-endpackage
diff --git a/hw/dv/sv/jtag_riscv_agent/README.md b/hw/dv/sv/jtag_riscv_agent/README.md
new file mode 100644
index 0000000..67abe44
--- /dev/null
+++ b/hw/dv/sv/jtag_riscv_agent/README.md
@@ -0,0 +1,4 @@
+# JTAG RISCV DV UVM Agent
+
+JTAG RISCV UVM Agent is extended from DV library agent classes.
+This is a high-level agent that builds on top of the JTAG agent.
diff --git a/hw/dv/sv/jtag_riscv_agent/doc/index.md b/hw/dv/sv/jtag_riscv_agent/doc/index.md
deleted file mode 100644
index 574ebe3..0000000
--- a/hw/dv/sv/jtag_riscv_agent/doc/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: "JTAG RISCV DV UVM Agent"
----
-
-JTAG RISCV UVM Agent is extended from DV library agent classes.
-This is a high-level agent that builds on top of the JTAG agent.
diff --git a/hw/dv/sv/jtag_riscv_agent/jtag_riscv_agent_pkg.sv b/hw/dv/sv/jtag_riscv_agent/jtag_riscv_agent_pkg.sv
index c3d4f32..37ac4d3 100644
--- a/hw/dv/sv/jtag_riscv_agent/jtag_riscv_agent_pkg.sv
+++ b/hw/dv/sv/jtag_riscv_agent/jtag_riscv_agent_pkg.sv
@@ -128,8 +128,8 @@
             end
           end
           begin
-            wait_timeout(timeout_ns, msg_id, $sformatf("timeout (addr=0x%0h) == 0x%0h",
-                csr_addr, exp_data));
+            `DV_WAIT_TIMEOUT(timeout_ns, msg_id, $sformatf("timeout (addr=0x%0h) == 0x%0h",
+                csr_addr, exp_data))
           end
         join_any
         disable fork;
diff --git a/hw/dv/sv/jtag_riscv_agent/jtag_riscv_driver.sv b/hw/dv/sv/jtag_riscv_agent/jtag_riscv_driver.sv
index 93ee27d..5c6ed34 100644
--- a/hw/dv/sv/jtag_riscv_agent/jtag_riscv_driver.sv
+++ b/hw/dv/sv/jtag_riscv_agent/jtag_riscv_driver.sv
@@ -38,9 +38,11 @@
       rsp.set_id_info(req);
       seq_item_port.item_done();
 
-      drive_jtag(rsp);
-      if (cfg.in_reset) rsp.status = cfg.status_in_reset;
+      `DV_SPINWAIT_EXIT(
+          drive_jtag(rsp);,
+          wait(cfg.in_reset == 1);,)
 
+      if (cfg.in_reset) rsp.status = cfg.status_in_reset;
       seq_item_port.put_response(rsp);
     end
   endtask
@@ -68,7 +70,7 @@
     send_riscv_ir_req(JtagDmiAccess);
 
     if (drive_req.activate_rv_dm) begin
-      activate_rv_dm();
+      activate_rv_dm(drive_req.status);
     end else begin
       if (cfg.is_rv_dm) begin
         bit [DMI_DATAW-1:0] sbcs_val = (2'b10 << SbAccess) | ('b1 << SbBusy);
@@ -178,7 +180,7 @@
     end
   endtask
 
-  protected virtual task activate_rv_dm();
+  protected virtual task activate_rv_dm(output jtag_op_status_e activation_status);
     bit [bus_params_pkg::BUS_DW-1:0] dmctrl_val, sbcs_val;
     bit [DMI_OPW-1:0] status;
     int cnter;
@@ -196,6 +198,7 @@
       end else begin
         `uvm_error(`gfn, msg)
       end
+      activation_status = DmiFail;
       return;
     end
 
@@ -209,6 +212,7 @@
     `DV_CHECK_EQ(sbcs_val[SbAccess32], 1, "expect SBA width to be 32 bits!", error, msg_id)
 
     cfg.rv_dm_activated = 1;
+    activation_status = DmiNoErr;
   endtask
 
   protected virtual task wait_sbcs_idle();
diff --git a/hw/dv/sv/jtag_riscv_agent/seq_lib/jtag_riscv_csr_seq.sv b/hw/dv/sv/jtag_riscv_agent/seq_lib/jtag_riscv_csr_seq.sv
index 874b738..e10cfbb 100644
--- a/hw/dv/sv/jtag_riscv_agent/seq_lib/jtag_riscv_csr_seq.sv
+++ b/hw/dv/sv/jtag_riscv_agent/seq_lib/jtag_riscv_csr_seq.sv
@@ -7,8 +7,8 @@
 
   rand bit [  DMI_OPW-1:0] op;
   rand bit [DMI_DATAW-1:0] data;
-  // Need to convert from csr(byte) address to word address if not in rv_dm mode.
-  rand bit [DMI_ADDRW+1:0] addr;
+  // This is not DTM address, but address for the CSR registers
+  rand bit [DMI_DATAW+1:0] addr;
   rand bit                 do_write;
 
   constraint op_c {
diff --git a/hw/dv/sv/key_sideload_agent/README.md b/hw/dv/sv/key_sideload_agent/README.md
new file mode 100644
index 0000000..35361db
--- /dev/null
+++ b/hw/dv/sv/key_sideload_agent/README.md
@@ -0,0 +1,3 @@
+# KEY_SIDELOAD UVM Agent
+
+KEY_SIDELOAD UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/key_sideload_agent/doc/README.md b/hw/dv/sv/key_sideload_agent/doc/README.md
deleted file mode 100644
index c6fca08..0000000
--- a/hw/dv/sv/key_sideload_agent/doc/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "KEY_SIDELOAD UVM Agent"
----
-
-KEY_SIDELOAD UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/kmac_app_agent/README.md b/hw/dv/sv/kmac_app_agent/README.md
new file mode 100644
index 0000000..85ea2c2
--- /dev/null
+++ b/hw/dv/sv/kmac_app_agent/README.md
@@ -0,0 +1,3 @@
+# KMAC_APP UVM Agent
+
+KMAC_APP UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/kmac_app_agent/doc/index.md b/hw/dv/sv/kmac_app_agent/doc/index.md
deleted file mode 100644
index 4f6ddd5..0000000
--- a/hw/dv/sv/kmac_app_agent/doc/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "KMAC_APP UVM Agent"
----
-
-KMAC_APP UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/kmac_app_agent/kmac_app_intf.sv b/hw/dv/sv/kmac_app_agent/kmac_app_intf.sv
index 96f4483..3ce51a1 100644
--- a/hw/dv/sv/kmac_app_agent/kmac_app_intf.sv
+++ b/hw/dv/sv/kmac_app_agent/kmac_app_intf.sv
@@ -59,11 +59,6 @@
   `ASSERT(StrbNotZero_A, kmac_data_req.valid |-> kmac_data_req.strb > 0,
           clk, !rst_n || if_mode == dv_utils_pkg::Host)
 
-  // strb should be all 1s unless it's last cycle
-  `ASSERT(StrbAllSetIfNotLast_A, kmac_data_req.valid && !kmac_data_req.last |->
-                                 kmac_data_req.strb == '1,
-                                 clk, !rst_n || if_mode == dv_utils_pkg::Host)
-
   // Check strb is aligned to LSB, for example: if strb[1]==0, strb[$:2] should be 0 too
   for (genvar k = 1; k < KmacDataIfWidth / 8 - 1; k++) begin : gen_strb_check
     `ASSERT(StrbAlignLSB_A, kmac_data_req.valid && kmac_data_req.strb[k] === 0 |->
@@ -76,6 +71,6 @@
   // Done should be asserted after last, before we start another request
   `ASSERT(DoneAssertAfterLast_A,
     (kmac_data_req.last && kmac_data_req.valid && kmac_data_rsp.ready) |=>
-    !kmac_data_req.valid throughout rsp_done[->1], clk, !rst_n)
+    !kmac_data_req.valid throughout rsp_done[->1], clk, !rst_n || rsp_error)
 
 endinterface
diff --git a/hw/dv/sv/kmac_app_agent/kmac_app_monitor.sv b/hw/dv/sv/kmac_app_agent/kmac_app_monitor.sv
index 5885554..6849de7 100644
--- a/hw/dv/sv/kmac_app_agent/kmac_app_monitor.sv
+++ b/hw/dv/sv/kmac_app_agent/kmac_app_monitor.sv
@@ -52,8 +52,8 @@
         // before the full data has been sent from the connected App host (KeyMgr/ROM/LC).
         // As a result, we need to set `ok_to_end` here otherwise the monitor's corresponding
         // objection will never drop in this scenario.
-        // TODO, we can consider to handle premature ending better. Once sequence ends transaction
-        // prematurely, issue a reset to get out of this while-loop, so that we can keep
+        // Once sequence ends transaction prematurely,
+        // issue a reset to get out of this while-loop, so that we can keep
         // ok_to_end = 0 for the entire transaction.
         ok_to_end = 1;
         data_fifo.get(data_item);
diff --git a/hw/dv/sv/mem_bkdr_scb/README.md b/hw/dv/sv/mem_bkdr_scb/README.md
new file mode 100644
index 0000000..6577972
--- /dev/null
+++ b/hw/dv/sv/mem_bkdr_scb/README.md
@@ -0,0 +1,37 @@
+# Memory Backdoor Scoreboard
+
+The mem_model_pkg checks write value matches with previous write value, but
+there are some limitations.
+  - Can’t check memory ECC if encoding and decoding match each other.
+  - Can’t check the read value if the address hasn't been written after init
+     or after a key request.
+  - Can’t check the write value if the address isn’t read after the write.
+  - Not aware of any B2B hazard (such as RAW).
+This scoreboard can cover all above limitations, as it checks read/write value
+matches with backdoor value. B2B hazard will be handled when predicting expected
+value. All kinds of hazard corner cases will be sampled in functional coverage.
+
+### `get_bkdr_val(mem_addr_t addr);`
+User must override this pure virtual function to return backdoor value from the
+memory based on the given address.
+
+### `read_start(mem_addr_t addr, mem_mask_t mask)`
+This function should be called when a read request is latched by design.
+Predicted read value is calculated in this function:
+ - If there is a pending write with same address (RAW hazard), the expected value is
+ from this write (also depends on which bytes is enabled)
+ - If no RAW hazard, the expected value is from latching backdoor value at the
+ time of calling this function.
+
+### `read_finish(mem_data_t act_data, mem_addr_t addr, mem_mask_t mask, bit en_check_consistency)`
+This function should be called when a read transaction is done. It compares the read
+value with expected value calculated at `read_start`.
+
+### `write_start(mem_addr_t addr, mem_mask_t mask)`
+This function should be called when a write request is latched by design.
+Write items will be stored in the queue for checking RAW hazard and future comparison.
+
+### `write_finish(mem_addr_t addr, mem_mask_t mask, bit en_check_consistency)`
+This function should be called once the write data is written into the memory.
+This function will read back the data from backdoor and compare with write value stored
+in write_item_q.
diff --git a/hw/dv/sv/mem_bkdr_scb/doc/index.md b/hw/dv/sv/mem_bkdr_scb/doc/index.md
deleted file mode 100644
index 2b3688a..0000000
--- a/hw/dv/sv/mem_bkdr_scb/doc/index.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: "Memory Backdoor Scoreboard"
----
-
-The mem_model_pkg checks write value matches with previous write value, but
-there are some limitations.
-  - Can’t check memory ECC if encoding and decoding match each other.
-  - Can’t check the read value if the address hasn't been written after init
-     or after a key request.
-  - Can’t check the write value if the address isn’t read after the write.
-  - Not aware of any B2B hazard (such as RAW).
-This scoreboard can cover all above limitations, as it checks read/write value
-matches with backdoor value. B2B hazard will be handled when predicting expected
-value. All kinds of hazard corner cases will be sampled in functional coverage.
-
-### `get_bkdr_val(mem_addr_t addr);`
-User must override this pure virtual function to return backdoor value from the
-memory based on the given address.
-
-### `read_start(mem_addr_t addr, mem_mask_t mask)`
-This function should be called when a read request is latched by design.
-Predicted read value is calculated in this function:
- - If there is a pending write with same address (RAW hazard), the expected value is
- from this write (also depends on which bytes is enabled)
- - If no RAW hazard, the expected value is from latching backdoor value at the
- time of calling this function.
-
-### `read_finish(mem_data_t act_data, mem_addr_t addr, mem_mask_t mask, bit en_check_consistency)`
-This function should be called when a read transaction is done. It compares the read
-value with expected value calculated at `read_start`.
-
-### `write_start(mem_addr_t addr, mem_mask_t mask)`
-This function should be called when a write request is latched by design.
-Write items will be stored in the queue for checking RAW hazard and future comparison.
-
-### `write_finish(mem_addr_t addr, mem_mask_t mask, bit en_check_consistency)`
-This function should be called once the write data is written into the memory.
-This function will read back the data from backdoor and compare with write value stored
-in write_item_q.
diff --git a/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv b/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv
index f00724e..76c6e1d 100644
--- a/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv
+++ b/hw/dv/sv/mem_bkdr_scb/mem_bkdr_scb.sv
@@ -96,7 +96,6 @@
     mem_mask_t raw_mask;
     mem_data_t raw_bit_mask, exp_bit_mask;
 
-    // TODO, sample is_raw in coverage
     is_raw = check_raw_hazard(addr, raw_data, raw_mask);
     if (is_raw) begin
       raw_bit_mask = expand_bit_mask(raw_mask);
diff --git a/hw/dv/sv/mem_bkdr_util/README.md b/hw/dv/sv/mem_bkdr_util/README.md
new file mode 100644
index 0000000..51a1d10
--- /dev/null
+++ b/hw/dv/sv/mem_bkdr_util/README.md
@@ -0,0 +1,20 @@
+# Memory Backdoor Utility Class
+
+The `mem_bkdr_util` class provides a way to manipulate the memory array directly via backdoor.
+It includes a set of functions to backdoor read or write any address location within the memory.
+The class instance is created in the testbench module and passed to the UVM environment via `uvm_config_db`.
+
+### Methods
+This interface supports basic backdoor methods to access memory. Useful methods are:
+* `is_addr_valid`: Check if input address is valid
+  The input address is assumed to be the byte addressable address into memory
+  starting at 0. It is user's responsibility to mask the upper bits.
+* `read8`, `read16`, `read32`, `read64`: Functions to read one byte, two bytes, four bytes, and eight bytes respectively
+  at specified input address
+* `write8`, `write16`, `write32`, `write64`: Functions to write one byte, two bytes, four bytes, and eight bytes respectively
+  with input data at specified input address
+* `load_mem_from_file`: Load memory from a file specified by input string
+* `print_mem`: Print the content of the memory
+* `clear_mem`: Clear the memory to all 0s
+* `set_mem`: Set the memory to all 1s
+* `randomize_mem`: Randomize contents of the memory
diff --git a/hw/dv/sv/mem_bkdr_util/doc/index.md b/hw/dv/sv/mem_bkdr_util/doc/index.md
deleted file mode 100644
index fc45baa..0000000
--- a/hw/dv/sv/mem_bkdr_util/doc/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-tile: "Memory backdoor utility class"
----
-
-The `mem_bkdr_util` class provides a way to manipulate the memory array directly via backdoor.
-It includes a set of functions to backdoor read or write any address location within the memory.
-The class instance is created in the testbench module and passed to the UVM environment via `uvm_config_db`.
-
-### Methods
-This interface supports basic backdoor methods to access memory. Useful methods are:
-* `is_addr_valid`: Check if input address is valid
-  The input address is assumed to be the byte addressable address into memory
-  starting at 0. It is user's responsibility to mask the upper bits.
-* `read8`, `read16`, `read32`, `read64`: Functions to read one byte, two bytes, four bytes, and eight bytes respectively
-  at specified input address
-* `write8`, `write16`, `write32`, `write64`: Functions to write one byte, two bytes, four bytes, and eight bytes respectively
-  with input data at specified input address
-* `load_mem_from_file`: Load memory from a file specified by input string
-* `print_mem`: Print the content of the memory
-* `clear_mem`: Clear the memory to all 0s
-* `set_mem`: Set the memory to all 1s
-* `randomize_mem`: Randomize contents of the memory
diff --git a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util.sv b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util.sv
index c70877c..69b2d47 100644
--- a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util.sv
+++ b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util.sv
@@ -29,8 +29,6 @@
   `define HAS_ECC (!(err_detection_scheme inside {ErrDetectionNone, ParityEven, ParityOdd}))
   `define HAS_PARITY (err_detection_scheme inside {ParityEven, ParityOdd})
 
-  // TODO: Indicates whether the memory implements scrambling.
-
   // Other memory specifics derived from the settings above.
   protected uint32_t data_width;  // ignoring ECC bits
   protected uint32_t byte_width;
@@ -40,6 +38,9 @@
   protected uint32_t addr_width;
   protected uint32_t byte_addr_width;
 
+  // Address range of this memory in the system address map.
+  protected addr_range_t addr_range;
+
   // Indicates the maximum number of errors that can be injected.
   //
   // If parity is enabled, this limit applies to a single byte in the memory width. We cannot inject
@@ -61,7 +62,7 @@
   // package.
   function new(string name = "", string path, int unsigned depth,
                longint unsigned n_bits, err_detection_e err_detection_scheme,
-               int extra_bits_per_subword = 0);
+               int extra_bits_per_subword = 0, int unsigned system_base_addr = 0);
 
     bit res;
     super.new(name);
@@ -111,6 +112,8 @@
     addr_lsb   = $clog2(bytes_per_word);
     addr_width = $clog2(depth);
     byte_addr_width = addr_width + addr_lsb;
+    addr_range.start_addr = system_base_addr;
+    addr_range.end_addr = system_base_addr + size_bytes - 1;
     max_errors = width;
     if (name == "") set_name({path, "::mem_bkdr_util"});
     `uvm_info(`gfn, this.convert2string(), UVM_MEDIUM)
@@ -129,7 +132,9 @@
             $sformatf("addr_lsb = %0d\n", addr_lsb),
             $sformatf("addr_width = %0d\n", addr_width),
             $sformatf("byte_addr_width = %0d\n", byte_addr_width),
-            $sformatf("max_errors = %0d\n", max_errors)};
+            $sformatf("max_errors = %0d\n", max_errors),
+            $sformatf("addr_range.start_addr = 0x%0h\n", addr_range.start_addr),
+            $sformatf("addr_range.end_addr = 0x%0h\n", addr_range.end_addr)};
   endfunction
 
   function string get_path();
@@ -176,6 +181,10 @@
     return byte_addr_width;
   endfunction
 
+  function bit is_valid_addr(int unsigned system_addr);
+    return system_addr inside {[addr_range.start_addr:addr_range.end_addr]};
+  endfunction
+
   function string get_file();
     return file;
   endfunction
@@ -199,8 +208,6 @@
   // Returns the entire width of the memory at the given address, including the ECC bits. The data
   // returned is 'raw' i.e. it includes the parity bits. It also does not de-scramble the data if
   // encryption is enabled.
-  //
-  // TODO: Factor in encryption into this function itself?
   virtual function uvm_hdl_data_t read(bit [bus_params_pkg::BUS_AW-1:0] addr);
     bit res;
     uint32_t index;
diff --git a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__flash.sv b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__flash.sv
index c78daa2..5f2edd9 100644
--- a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__flash.sv
+++ b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__flash.sv
@@ -11,17 +11,11 @@
 localparam int unsigned FlashNumRoundsHalf = crypto_dpi_prince_pkg::NumRoundsHalf;
 localparam int unsigned FlashAddrWidth = 16;
 
-localparam bit[FlashDataWidth-1:0] IPoly = FlashDataWidth'(1'b1) << 15 |
-                                           FlashDataWidth'(1'b1) << 9  |
-                                           FlashDataWidth'(1'b1) << 7  |
-                                           FlashDataWidth'(1'b1) << 4  |
-                                           FlashDataWidth'(1'b1) << 3  |
-                                           FlashDataWidth'(1'b1) << 0;
-
 function bit [FlashDataWidth-1:0] flash_gf_mult2(bit [FlashDataWidth-1:0] operand);
   bit [FlashDataWidth-1:0] mult_out;
 
-  mult_out = operand[FlashDataWidth-1] ? (operand << 1) ^ IPoly : (operand << 1);
+  mult_out = operand[FlashDataWidth-1] ? (operand << 1) ^
+    flash_phy_pkg::ScrambleIPoly : (operand << 1);
   return mult_out;
 endfunction
 
diff --git a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__otp.sv b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__otp.sv
index 9f1aeb6..aeb67ae 100644
--- a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__otp.sv
+++ b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__otp.sv
@@ -9,7 +9,16 @@
   for (int i = 0; i < LcStateSize; i += 4) begin
     write32(i + LcStateOffset, lc_state[i*8+:32]);
   end
-endfunction
+endfunction : otp_write_lc_partition_state
+
+virtual function lc_ctrl_state_pkg::lc_state_e otp_read_lc_partition_state();
+  lc_ctrl_state_pkg::lc_state_e lc_state;
+  for (int i = 0; i < LcStateSize; i += 4) begin
+    lc_state[i*8 +: 32] = read32(i + LcStateOffset);
+  end
+
+  return lc_state;
+endfunction : otp_read_lc_partition_state
 
 virtual function void otp_write_lc_partition_cnt(lc_ctrl_state_pkg::lc_cnt_e lc_cnt);
   for (int i = 0; i < LcTransitionCntSize; i += 4) begin
@@ -139,3 +148,29 @@
 
   write64(HwCfgDigestOffset, digest);
 endfunction
+
+// Functions that clear the provisioning state of the buffered partitions.
+// This is useful in tests that make front-door accesses for provisioning purposes.
+virtual function void otp_clear_secret0_partition();
+  for (int i = 0; i < Secret0Size; i += 4) begin
+    write32(i + Secret0Offset, 32'h0);
+  end
+endfunction
+
+virtual function void otp_clear_secret1_partition();
+  for (int i = 0; i < Secret1Size; i += 4) begin
+    write32(i + Secret1Offset, 32'h0);
+  end
+endfunction
+
+virtual function void otp_clear_secret2_partition();
+  for (int i = 0; i < Secret2Size; i += 4) begin
+    write32(i + Secret2Offset, 32'h0);
+  end
+endfunction
+
+virtual function void otp_clear_hw_cfg_partition();
+  for (int i = 0; i < HwCfgSize; i += 4) begin
+    write32(i + HwCfgOffset, 32'h0);
+  end
+endfunction
diff --git a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__rom.sv b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__rom.sv
index eab25b1..d021ca5 100644
--- a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__rom.sv
+++ b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util__rom.sv
@@ -69,7 +69,7 @@
 
 
 virtual function void rom_encrypt_write32_integ(logic [bus_params_pkg::BUS_AW-1:0] addr,
-                                                logic [31:0]                       data,
+                                                logic [38:0]                       data,
                                                 logic [SRAM_KEY_WIDTH-1:0]         key,
                                                 logic [SRAM_BLOCK_WIDTH-1:0]       nonce,
                                                 bit                                scramble_data,
@@ -94,13 +94,13 @@
   // Calculate the scrambled address
   scrambled_addr = sram_scrambler_pkg::encrypt_sram_addr(rom_addr, addr_width, nonce_arr);
 
-  if(scramble_data) begin
+  if (scramble_data) begin
     // Calculate the integrity constant
     integ_data = prim_secded_pkg::prim_secded_inv_39_32_enc(data);
 
     // flip some bits to inject integrity fault
     integ_data ^= flip_bits;
-  
+
     // Calculate the scrambled data
     wdata_arr = {<<{integ_data}};
     wdata_arr = sram_scrambler_pkg::encrypt_sram_data(
@@ -155,17 +155,24 @@
   int digest_start_addr = kmac_data_bytes;
   bit scramble_data = 0; // digest and kmac data aren't scrambled
 
-  // kmac data is twice of kmac_data_bytes as we use 64 bit bus to send 32 bit data + 7 intg
-  kmac_data_arr = new[kmac_data_bytes * 2];
+  // Each 4 byte of data is transferred as 5 bytes
+  int xfer_bytes = kmac_data_bytes * 5 / 4;
+  kmac_data_arr = new[xfer_bytes];
+  `uvm_info(`gfn, $sformatf("Actual bytes: %d, xfer'd: %d", kmac_data_bytes, xfer_bytes), UVM_DEBUG)
 
   for (int i = 0; i < kmac_data_bytes; i += 4) begin
-    bit [63:0] data64;
+    bit [39:0] data40;
 
-    // it returns 39 bits, including integrity. and the 39 bits data will be sent to 64 bits bus to
-    // the kmac
-    data64 = 64'(rom_encrypt_read32(i, key, nonce, scramble_data));
-    for (int j = 0; j < 8; j++) begin
-      kmac_data_arr[i * 2 + j] = data64[j * 8 +: 8];
+    // it returns 39 bits, including integrity. and the 39 bits data will be sent to 40 bits bus to
+    // the kmac. The kmac bus has byte strobes that are used to indicate 5 bytes instead of the full
+    // 8.
+    data40 = 40'(rom_encrypt_read32(i, key, nonce, scramble_data));
+    for (int j = 0; j < 5; j++) begin
+      // At byte position 0, we want bytes 0, 1, 2, 3, 4
+      // At byte position 4, we want bytes 5, 6, 7, 8, 9
+      // At byte position 8, we want bytes 10, 11, 12, 13, 14
+      int idx = i + (i / 4) + j;
+      kmac_data_arr[idx] = data40[j * 8 +: 8];
     end
   end
   digestpp_dpi_pkg::c_dpi_cshake256(kmac_data_arr, "", "ROM_CTRL", kmac_data_arr.size,
diff --git a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util_pkg.sv b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util_pkg.sv
index 3117dfa..68e205f 100644
--- a/hw/dv/sv/mem_bkdr_util/mem_bkdr_util_pkg.sv
+++ b/hw/dv/sv/mem_bkdr_util/mem_bkdr_util_pkg.sv
@@ -5,7 +5,7 @@
 package mem_bkdr_util_pkg;
   // dep packages
   import bus_params_pkg::BUS_AW;
-  import dv_utils_pkg::uint32_t;
+  import dv_utils_pkg::uint32_t, dv_utils_pkg::addr_range_t;
   import lc_ctrl_state_pkg::*;
   import otp_ctrl_part_pkg::*;
   import otp_ctrl_reg_pkg::*;
diff --git a/hw/dv/sv/mem_model/README.md b/hw/dv/sv/mem_model/README.md
new file mode 100644
index 0000000..68d0f6f
--- /dev/null
+++ b/hw/dv/sv/mem_model/README.md
@@ -0,0 +1,27 @@
+# Memory Model
+
+The memory model UVC models a memory device which any host interface can read
+from or write to. It is implemented as a `uvm_object`, and instantiates an
+associative array of bytes `system_memory`. This class is parameterized by both
+the address width and the data width, and creates two `typedefs` to represent
+both, `mem_addr_t` and `mem_data_t`.
+The `mem_model` class has four main functions, which are detailed below.
+
+### `read_byte(mem_addr_t addr)`
+This function looks up the byte of data corresponding to the memory address
+passed in, and returns it. If the address does not exist in `system_memory`, it
+will randomize the returned data and throw a `UVM_ERROR`.
+
+### `write_byte(mem_addr_t addr, bit [7:0] data)`
+This function simply assigns the given data to the specified memory address
+location in `system_memory`.
+
+### `write(input mem_addr_t addr, mem_data_t data)`
+This function writes a full memory word of width `mem_data_t` to the specified
+address, breaking it down into a series of back-to-back calls to `write_byte()`
+to correctly byte-address the memory.
+
+### `read(mem_addr_t addr)`
+This function reads a full memory word of width `mem_data_t` from the specified
+address, breaking it down into a series of back-to-back calls to `read_byte()`
+to correctly byte-address the memory.
diff --git a/hw/dv/sv/mem_model/doc/index.md b/hw/dv/sv/mem_model/doc/index.md
deleted file mode 100644
index 6851bea..0000000
--- a/hw/dv/sv/mem_model/doc/index.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: "Memory Model"
----
-
-The memory model UVC models a memory device which any host interface can read
-from or write to. It is implemented as a `uvm_object`, and instantiates an
-associative array of bytes `system_memory`. This class is paramterized by both
-the address width and the data width, and creates two `typedefs` to represent
-both, `mem_addr_t` and `mem_data_t`.
-The `mem_model` class has four main functions, which are detailed below.
-
-### `read_byte(mem_addr_t addr)`
-This function looks up the byte of data corresponding to the memory address
-passed in, and returns it. If the address does not exist in `system_memory`, it
-will randomize the returned data and throw a `UVM_ERROR`.
-
-### `write_byte(mem_addr_t addr, bit [7:0] data)`
-This function simply assigns the given data to the specified memory address
-location in `system_memory`.
-
-### `write(input mem_addr_t addr, mem_data_t data)`
-This function writes a full memory word of width `mem_data_t` to the specified
-address, breaking it down into a series of back-to-back calls to `write_byte()`
-to correctly byte-address the memory.
-
-### `read(mem_addr_t addr)`
-This function reads a full memory word of width `mem_data_t` from the specified
-address, breaking it down into a series of back-to-back calls to `read_byte()`
-to correctly byte-address the memory.
diff --git a/hw/dv/sv/mem_model/mem_model.sv b/hw/dv/sv/mem_model/mem_model.sv
index f3e36d5..b698c28 100644
--- a/hw/dv/sv/mem_model/mem_model.sv
+++ b/hw/dv/sv/mem_model/mem_model.sv
@@ -7,11 +7,11 @@
 
   localparam int MaskWidth  = DataWidth / 8;
 
-  typedef bit [AddrWidth-1:0] mem_addr_t;
-  typedef bit [DataWidth-1:0] mem_data_t;
-  typedef bit [MaskWidth-1:0] mem_mask_t;
+  typedef logic [AddrWidth-1:0] mem_addr_t;
+  typedef logic [DataWidth-1:0] mem_data_t;
+  typedef logic [MaskWidth-1:0] mem_mask_t;
 
-  bit [7:0] system_memory[mem_addr_t];
+  logic [7:0] system_memory[mem_addr_t];
 
   `uvm_object_param_utils(mem_model#(AddrWidth, DataWidth))
 
@@ -27,7 +27,7 @@
 
   function bit [7:0] read_byte(mem_addr_t addr);
     bit [7:0] data;
-    if (system_memory.exists(addr)) begin
+    if (addr_exists(addr)) begin
       data = system_memory[addr];
       `uvm_info(`gfn, $sformatf("Read Mem  : Addr[0x%0h], Data[0x%0h]", addr, data), UVM_HIGH)
     end else begin
@@ -37,15 +37,16 @@
     return data;
   endfunction
 
-  function void write_byte(mem_addr_t addr, bit [7:0] data);
+  function void write_byte(mem_addr_t addr, logic [7:0] data);
    `uvm_info(`gfn, $sformatf("Write Mem : Addr[0x%0h], Data[0x%0h]", addr, data), UVM_HIGH)
     system_memory[addr] = data;
   endfunction
 
-  function void compare_byte(mem_addr_t addr, bit [7:0] act_data);
+  function void compare_byte(mem_addr_t addr, logic [7:0] act_data);
    `uvm_info(`gfn, $sformatf("Compare Mem : Addr[0x%0h], Act Data[0x%0h], Exp Data[0x%0h]",
-                             addr, act_data, system_memory[addr]), UVM_HIGH)
-    `DV_CHECK_EQ(act_data, system_memory[addr], $sformatf("addr 0x%0h read out mismatch", addr))
+                             addr, act_data, system_memory[addr]), UVM_MEDIUM)
+    `DV_CHECK_CASE_EQ(act_data, system_memory[addr],
+                      $sformatf("addr 0x%0h read out mismatch", addr))
   endfunction
 
   function void write(input mem_addr_t addr, mem_data_t data, mem_mask_t mask = '1);
@@ -77,8 +78,12 @@
     for (int i = 0; i < DataWidth / 8; i++) begin
       mem_addr_t byte_addr = addr + i;
       byte_data = act_data[7:0];
-      if (mask[0] && (!compare_exist_addr_only || system_memory.exists(byte_addr))) begin
-        compare_byte(byte_addr, byte_data);
+      if (mask[0]) begin
+        if (addr_exists(byte_addr)) begin
+          compare_byte(byte_addr, byte_data);
+        end else if (!compare_exist_addr_only) begin
+          `uvm_error(`gfn, $sformatf("address 0x%0x not exists", byte_addr))
+        end
       end else begin
         // Nothing to do here: since this byte wasn't selected by the mask, there are no
         // requirements about what data came back.
@@ -88,4 +93,7 @@
     end
   endfunction
 
+  function bit addr_exists(mem_addr_t addr);
+    return system_memory.exists(addr);
+  endfunction
 endclass
diff --git a/hw/dv/sv/pattgen_agent/README.md b/hw/dv/sv/pattgen_agent/README.md
new file mode 100644
index 0000000..6879de3
--- /dev/null
+++ b/hw/dv/sv/pattgen_agent/README.md
@@ -0,0 +1,3 @@
+# PATTGEN UVM Agent
+
+PATTGEN UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/pattgen_agent/doc/index.md b/hw/dv/sv/pattgen_agent/doc/index.md
deleted file mode 100644
index ded3041..0000000
--- a/hw/dv/sv/pattgen_agent/doc/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "PATTGEN UVM Agent"
----
-
-PATTGEN UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/pattgen_agent/pattgen_agent_cfg.sv b/hw/dv/sv/pattgen_agent/pattgen_agent_cfg.sv
index 703b2b3..7ddf445 100644
--- a/hw/dv/sv/pattgen_agent/pattgen_agent_cfg.sv
+++ b/hw/dv/sv/pattgen_agent/pattgen_agent_cfg.sv
@@ -14,6 +14,13 @@
 
   uint length[NUM_PATTGEN_CHANNELS-1:0];
 
+  // pre-divider check enable
+  bit [NUM_PATTGEN_CHANNELS-1:0] chk_prediv;
+
+  // expected clk_cnt value
+  // calculated by 2 * (pattgen.PREDIV_CH + 1)
+  uint div[NUM_PATTGEN_CHANNELS];
+
   `uvm_object_utils_begin(pattgen_agent_cfg)
     `uvm_field_int(error_injected,  UVM_DEFAULT)
     `uvm_field_sarray_int(polarity, UVM_DEFAULT)
diff --git a/hw/dv/sv/pattgen_agent/pattgen_monitor.sv b/hw/dv/sv/pattgen_agent/pattgen_monitor.sv
index 11614c5..8b83230 100644
--- a/hw/dv/sv/pattgen_agent/pattgen_monitor.sv
+++ b/hw/dv/sv/pattgen_agent/pattgen_monitor.sv
@@ -33,6 +33,7 @@
         automatic uint channel = i;
         collect_channel_trans(channel);
         reset_thread();
+        mon_pcl(channel);
       join_none
     end
   endtask : collect_trans
@@ -46,6 +47,8 @@
       wait(cfg.en_monitor);
       dut_item = pattgen_item::type_id::create("dut_item");
       bit_cnt = 0;
+      `uvm_info(`gfn, $sformatf("PATTGEN_MON%d start: bit_cnt:%0d  len:%0d",
+                                channel, bit_cnt, cfg.length[channel]), UVM_MEDIUM)
       fork
         begin : isolation_thread
           fork
@@ -55,7 +58,8 @@
                 wait(cfg.vif.rst_ni);
                 get_pattgen_bit(channel, bit_data);
                 `uvm_info(`gfn, $sformatf("\n--> monitor: channel %0d, polar %b, data[%0d] %b",
-                    channel, cfg.polarity[channel], bit_cnt, bit_data), UVM_DEBUG)
+                                          channel, cfg.polarity[channel], bit_cnt, bit_data),
+                          UVM_HIGH)
                 dut_item.data_q.push_back(bit_data);
                 bit_cnt++;
               end while (bit_cnt < cfg.length[channel]);
@@ -67,7 +71,7 @@
                 // monitor only pushes item if chan_done intr is asserted
                 item_port[channel].write(dut_item);
                 `uvm_info(`gfn, $sformatf("\n--> monitor: send dut_item for channel %0d\n%s",
-                    channel, dut_item.sprint()), UVM_DEBUG)
+                                          channel, dut_item.sprint()), UVM_MEDIUM)
                 bit_cnt = 0;
                 cfg.channel_done[channel] = 1'b0;
               end
@@ -100,7 +104,7 @@
     @(posedge cfg.error_injected[channel]);
     bit_cnt = 0;
     `uvm_info(`gfn, $sformatf("\n--> monitor: drop dut_item for channel %0d\n%s",
-        channel, item.sprint()), UVM_DEBUG)
+                              channel, item.sprint()), UVM_DEBUG)
     @(negedge cfg.error_injected[channel]);
   endtask: error_channel_process
 
@@ -124,11 +128,7 @@
         begin : isolation_thread
           fork
             begin
-              if (cfg.polarity[channel]) begin
-                @(negedge cfg.vif.pcl_tx[channel]);
-              end else begin
-                @(posedge cfg.vif.pcl_tx[channel]);
-              end
+              get_pcl_edge(channel);
               bit_o = cfg.vif.pda_tx[channel];
               stop_thread = 1'b1;
             end
@@ -142,4 +142,38 @@
     end
   endtask : get_pattgen_bit
 
+  // Monitor vif.pcl_tx[channel]
+  // This task runs with cfg.chk_prediv[channel].
+  // if chk_prediv is set, check free running counter 'free_run_cnt'
+  // is matched with expected 'div' value to assure
+  // pattgen clk div is accurate.
+  virtual task mon_pcl(int channel);
+    int free_run_cnt, skip_one_cyc;
+
+    wait(cfg.chk_prediv[channel]);
+    get_pcl_edge(channel);
+    `uvm_info(`gfn, $sformatf("monitor: start mon_pcl channel %0d", channel), UVM_MEDIUM)
+
+    fork
+      forever begin
+        @(posedge cfg.vif.clk_i);
+        free_run_cnt++;
+      end
+      forever begin
+        get_pcl_edge(channel);
+        #1;
+        `DV_CHECK_EQ(free_run_cnt, cfg.div[channel])
+        free_run_cnt = 0;
+      end
+    join_none
+  endtask // mon_pcl
+
+  // Wait edge based on the polarity
+  task get_pcl_edge(int channel);
+    if (cfg.polarity[channel]) begin
+      @(negedge cfg.vif.pcl_tx[channel]);
+    end else begin
+      @(posedge cfg.vif.pcl_tx[channel]);
+    end
+  endtask
 endclass : pattgen_monitor
diff --git a/hw/dv/sv/push_pull_agent/README.md b/hw/dv/sv/push_pull_agent/README.md
new file mode 100644
index 0000000..839deb8
--- /dev/null
+++ b/hw/dv/sv/push_pull_agent/README.md
@@ -0,0 +1,13 @@
+# PUSH_PULL UVM Agent
+
+PUSH_PULL UVM Agent is extended from DV library agent classes.
+
+## Description
+
+This agent implements both Push (ready/valid) and Pull (req/ack) interface
+protocols, and can be configured to behave in both host and device modes.
+
+The agent configuration object (`push_pull_agent_cfg`) contains an enum `agent_type`
+that is used to select either push or pull modes.
+To configure the agent to use the ready/valid protocol, set `agent_type = PushAgent`, and
+to configure the agent to use the req/ack protocol, set `agent_type = PullAgent`.
diff --git a/hw/dv/sv/push_pull_agent/doc/index.md b/hw/dv/sv/push_pull_agent/doc/index.md
deleted file mode 100644
index b7cda25..0000000
--- a/hw/dv/sv/push_pull_agent/doc/index.md
+++ /dev/null
@@ -1,15 +0,0 @@
----
-title: "PUSH_PULL UVM Agent"
----
-
-PUSH_PULL UVM Agent is extended from DV library agent classes.
-
-## Description
-
-This agent implements both Push (ready/valid) and Pull (req/ack) interface
-protocols, and can be configured to behave in both host and device modes.
-
-The agent configuration object (`push_pull_agent_cfg`) contains an enum `agent_type`
-that is used to select either push or pull modes.
-To configure the agent to use the ready/valid protocol, set `agent_type = PushAgent`, and
-to configure the agent to use the req/ack protocol, set `agent_type = PullAgent`.
diff --git a/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv b/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv
index 475436e..52b7b96 100644
--- a/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv
+++ b/hw/dv/sv/push_pull_agent/push_pull_agent_cfg.sv
@@ -45,6 +45,9 @@
   // Enable starting the device sequence by default if configured in Device mode.
   bit start_default_device_seq = 1;
 
+  // Ignore backpressure (ready signal) if configured as a Push Host
+  bit ignore_push_host_backpressure = 0;
+
   // These data queues allows users to specify data to be driven by the sequence at a higher level.
   //
   // To specify some Host data to be sent, `set_h_user_data()` should be called from a higher layer
diff --git a/hw/dv/sv/push_pull_agent/push_pull_driver_lib.sv b/hw/dv/sv/push_pull_agent/push_pull_driver_lib.sv
index 6cb84c8..92dca53 100644
--- a/hw/dv/sv/push_pull_agent/push_pull_driver_lib.sv
+++ b/hw/dv/sv/push_pull_agent/push_pull_driver_lib.sv
@@ -129,10 +129,14 @@
         repeat (req.host_delay) @(`CB);
         `CB.valid_int <= 1'b1;
         `CB.h_data_int <= req.h_data;
-        do @(`CB); while (!`CB.ready);
+        do @(`CB); while (!cfg.ignore_push_host_backpressure && !`CB.ready);
         `CB.valid_int <= 1'b0;
         if (!cfg.hold_h_data_until_next_req) `CB.h_data_int <= 'x;,
         wait (cfg.in_reset);)
+    // In case there is race condition between the logic above and reset_signals task.
+    // We always set the valid_int again to 0 to make sure the data comes out of reset is not
+    // valid.
+    if (cfg.in_reset) `CB.valid_int <= '0;
   endtask
 
   `undef CB
diff --git a/hw/dv/sv/push_pull_agent/seq_lib/push_pull_indefinite_host_seq.sv b/hw/dv/sv/push_pull_agent/seq_lib/push_pull_indefinite_host_seq.sv
index 18e2d80..90c1226 100644
--- a/hw/dv/sv/push_pull_agent/seq_lib/push_pull_indefinite_host_seq.sv
+++ b/hw/dv/sv/push_pull_agent/seq_lib/push_pull_indefinite_host_seq.sv
@@ -17,6 +17,8 @@
 
   `uvm_object_new
 
+  int items_processed;
+
   typedef enum int {
     Continue = 0,
     Stop     = 1,
@@ -25,7 +27,12 @@
 
   stop_status_e stop_status;
 
+  task wait_for_items_processed(int n);
+    wait(items_processed >= n);
+  endtask
+
   virtual task body();
+    items_processed = 0;
     fork : isolation_fork
       begin
         fork
@@ -39,6 +46,7 @@
             randomize_item(req);
             finish_item(req);
             get_response(rsp);
+            items_processed++;
           end : send_req
         join_any;
         disable fork;
diff --git a/hw/dv/sv/pwm_monitor/README.md b/hw/dv/sv/pwm_monitor/README.md
new file mode 100644
index 0000000..aa4b4a8
--- /dev/null
+++ b/hw/dv/sv/pwm_monitor/README.md
@@ -0,0 +1,3 @@
+# PWM UVM Monitor
+
+PWM UVM Monitor is extended from DV library agent classes.
diff --git a/hw/dv/sv/pwm_monitor/doc/README.md b/hw/dv/sv/pwm_monitor/doc/README.md
deleted file mode 100644
index 9f2aba1..0000000
--- a/hw/dv/sv/pwm_monitor/doc/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "PWM UVM Monitor"
----
-
-PWM UVM Monitor is extended from DV library agent classes.
diff --git a/hw/dv/sv/pwm_monitor/pwm_if.sv b/hw/dv/sv/pwm_monitor/pwm_if.sv
index 65a8924..9c83afc 100644
--- a/hw/dv/sv/pwm_monitor/pwm_if.sv
+++ b/hw/dv/sv/pwm_monitor/pwm_if.sv
@@ -2,16 +2,14 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-interface pwm_if;
-  // core clock
-  logic clk;
-  logic rst_n;
-
-  logic pwm;
-  logic pwm_en;
-
+interface pwm_if (
+  input logic clk,
+  input logic rst_n,
+  input logic pwm
+);
 
   clocking cb @(posedge clk);
-    input pwm, pwm_en;
+    input pwm;
   endclocking
+
 endinterface : pwm_if
diff --git a/hw/dv/sv/pwm_monitor/pwm_item.sv b/hw/dv/sv/pwm_monitor/pwm_item.sv
index cb3132c..485f91a 100644
--- a/hw/dv/sv/pwm_monitor/pwm_item.sv
+++ b/hw/dv/sv/pwm_monitor/pwm_item.sv
@@ -26,7 +26,6 @@
 
     function string convert2string();
       string txt ="";
-
       txt = "\n------| PWM ITEM |------";
       txt = { txt, $sformatf("\n Item from monitor %d", monitor_id) };
       txt = { txt, $sformatf("\n Period %d clocks", period) };
diff --git a/hw/dv/sv/pwm_monitor/pwm_monitor.sv b/hw/dv/sv/pwm_monitor/pwm_monitor.sv
index ea00e56..407fd3b 100644
--- a/hw/dv/sv/pwm_monitor/pwm_monitor.sv
+++ b/hw/dv/sv/pwm_monitor/pwm_monitor.sv
@@ -9,78 +9,59 @@
   `uvm_component_utils(pwm_monitor)
   `uvm_component_new
 
-  uvm_analysis_port #(pwm_item) item_port;
-
-  //item
-  pwm_item dut_item, item_clone;
-
-  // interface handle
-  virtual pwm_if vif;
-
-  bit  prev_pwm_state  = '0;
-  uint cnt             =  0;
-  uint phase_count     =  0;
-
   function void build_phase(uvm_phase phase);
     super.build_phase(phase);
-    item_port = new($sformatf("%s_item_port", this.get_name()), this);
-    // get interface
-    if (!uvm_config_db#(virtual pwm_if)::get(this, "*.env.m_pwm_monitor*",
-                        $sformatf("%s_vif", get_name()), vif)) begin
-      `uvm_fatal(`gfn, $sformatf("\n  mon: failed to get %s_vif handle from uvm_config_db",
-                get_name()))
+    // get config
+    if (!uvm_config_db#(pwm_monitor_cfg)::get(this, "", "cfg", cfg)) begin
+      `uvm_fatal(`gfn, $sformatf("failed to get cfg from uvm_config_db"))
     end
 
-    // get config
-    if (!uvm_config_db#(pwm_monitor_cfg)::get(this, "*", $sformatf("%s_cfg", get_name()),cfg)) begin
-      `uvm_fatal(`gfn, $sformatf("\n  mon: failed to get %s_cfg from uvm_config_db", get_name()))
+    // get interface
+    if (!uvm_config_db#(virtual pwm_if)::get(this, "", "vif", cfg.vif)) begin
+      `uvm_fatal(`gfn, $sformatf("failed to get vif handle from uvm_config_db"))
     end
   endfunction
 
   // collect transactions forever - already forked in dv_base_monitor::run_phase
   virtual protected task collect_trans(uvm_phase phase);
-    wait(vif.rst_n);
-    `uvm_info(`gfn, $sformatf("getting delay %d", cfg.ok_to_end_delay_ns), UVM_HIGH)
-    dut_item = pwm_item::type_id::create($sformatf("%s_item", this.get_name()));
+    uint count_cycles, active_cycles;
+    logic pwm_prev = 0;
 
+    wait(cfg.vif.rst_n);
     forever begin
+      if (!cfg.active) begin
+        wait (cfg.active);
+        count_cycles = 0;
+        active_cycles = 0;
+      end
 
-      @(vif.cb);
-      if (cfg.active) begin
-        // increment high/low cnt
-        cnt     += 1;
-        // detect event
-        if (vif.cb.pwm != prev_pwm_state) begin
-          `uvm_info(`gfn, $sformatf("edge detected %0b -> %0b", prev_pwm_state, vif.cb.pwm),
-                     UVM_HIGH)
-          if (vif.cb.pwm == cfg.invert) begin
-            // store count in high count
-            dut_item.active_cnt  = cnt;
-          end else begin
-            dut_item.inactive_cnt  = cnt;
-            dut_item.invert        = cfg.invert;
-            dut_item.period        = cnt + dut_item.active_cnt;
-            dut_item.monitor_id    = cfg.monitor_id;
-            dut_item.duty_cycle    = dut_item.get_duty_cycle();
+      @(cfg.vif.cb);
+      count_cycles++;
+      if (cfg.vif.cb.pwm != pwm_prev) begin
+        `uvm_info(`gfn, $sformatf("Detected edge: %0b->%0b at %0d cycles (from last edge)",
+                                  pwm_prev, cfg.vif.cb.pwm, count_cycles), UVM_HIGH)
+        pwm_prev = cfg.vif.cb.pwm;
+        if (cfg.vif.cb.pwm == cfg.invert) begin
+          // We got to the first (active) half duty cycle point. Save the count and restart.
+          active_cycles = count_cycles;
+        end else begin
+          uint phase_count;
+          pwm_item item = pwm_item::type_id::create("item");
+          item.invert       = cfg.invert;
+          item.monitor_id   = cfg.monitor_id;
+          item.active_cnt   = active_cycles;
+          item.inactive_cnt = count_cycles;
+          item.period       = count_cycles + active_cycles;
+          item.duty_cycle   = item.get_duty_cycle();
 
-            // Each PWM pulse cycle is divided into 2^DC_RESN+1 beats, per beat the 16-bit
-            // phase counter increments by 2^(16-DC_RESN-1)(modulo 65536)
-            phase_count            = ((dut_item.period / (2**(cfg.resolution + 1))) *
-              (2**(16 - (cfg.resolution - 1))));
-            dut_item.phase         = (phase_count % 65536);
-
-            // item done
-            `downcast(item_clone, dut_item.clone());
-            item_port.write(item_clone);
-            dut_item = new($sformatf("%s_item", this.get_name()));
-          end
-          cnt = 0;
+          // Each PWM pulse cycle is divided into 2^DC_RESN+1 beats, per beat the 16-bit
+          // phase counter increments by 2^(16-DC_RESN-1)(modulo 65536)
+          phase_count = ((item.period / (2 ** (cfg.resolution + 1))) *
+                         (2 ** (16 - (cfg.resolution - 1))));
+          item.phase = (phase_count % 65536);
+          analysis_port.write(item);
         end
-        prev_pwm_state = vif.cb.pwm;
-      end else begin
-        // clear what was previously collected
-        dut_item = new($sformatf("%s_item", this.get_name()));
-        cnt = 0;
+        count_cycles = 0;
       end
     end
   endtask
@@ -89,9 +70,9 @@
   // ok_to_end = 0 (bus busy) / 1 (bus idle)
   virtual task monitor_ready_to_end();
     forever begin
-      @(vif.cb)
+      @(cfg.vif.cb)
       ok_to_end = ~cfg.active;
     end
-  endtask : monitor_ready_to_end
+  endtask
 
-endclass : pwm_monitor
+endclass
diff --git a/hw/dv/sv/pwm_monitor/pwm_monitor_cfg.sv b/hw/dv/sv/pwm_monitor/pwm_monitor_cfg.sv
index 221a8ce..1085737 100644
--- a/hw/dv/sv/pwm_monitor/pwm_monitor_cfg.sv
+++ b/hw/dv/sv/pwm_monitor/pwm_monitor_cfg.sv
@@ -10,6 +10,9 @@
   bit active     = 1'b0; // 1: collect items 0: ignore
   int resolution = 0;
 
+  // interface handle
+  virtual pwm_if vif;
+
   `uvm_object_utils_begin(pwm_monitor_cfg)
   `uvm_object_utils_end
   `uvm_object_new
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/doc/README.md b/hw/dv/sv/pwrmgr_clk_ctrl_agent/doc/README.md
deleted file mode 100644
index b78c812..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/doc/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "PWRMGR_CLK_CTRL UVM Agent"
----
-
-PWRMGR_CLK_CTRL UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.core b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.core
deleted file mode 100644
index 8245e9e..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.core
+++ /dev/null
@@ -1,30 +0,0 @@
-CAPI=2:
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-name: "lowrisc:dv:pwrmgr_clk_ctrl_agent:0.1"
-description: "PWRMGR_CLK_CTRL DV UVM agent"
-filesets:
-  files_dv:
-    depend:
-      - lowrisc:dv:dv_utils
-      - lowrisc:dv:dv_lib
-      - lowrisc:ip:pwrmgr_pkg
-      - lowrisc:dv:pwrmgr_clk_ctrl_common_pkg
-    files:
-      - pwrmgr_clk_ctrl_if.sv
-      - pwrmgr_clk_ctrl_agent_pkg.sv
-      - pwrmgr_clk_ctrl_item.sv: {is_include_file: true}
-      - pwrmgr_clk_ctrl_agent_cfg.sv: {is_include_file: true}
-      - pwrmgr_clk_ctrl_agent_cov.sv: {is_include_file: true}
-      - pwrmgr_clk_ctrl_driver.sv: {is_include_file: true}
-      - pwrmgr_clk_ctrl_monitor.sv: {is_include_file: true}
-      - pwrmgr_clk_ctrl_agent.sv: {is_include_file: true}
-      - seq_lib/pwrmgr_clk_ctrl_base_seq.sv: {is_include_file: true}
-      - seq_lib/pwrmgr_clk_ctrl_seq_list.sv: {is_include_file: true}
-    file_type: systemVerilogSource
-
-targets:
-  default:
-    filesets:
-      - files_dv
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.sv
deleted file mode 100644
index 1d25800..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent.sv
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_agent extends dv_base_agent #(
-  .CFG_T          (pwrmgr_clk_ctrl_agent_cfg),
-  .DRIVER_T       (pwrmgr_clk_ctrl_driver),
-  .SEQUENCER_T    (pwrmgr_clk_ctrl_sequencer),
-  .MONITOR_T      (pwrmgr_clk_ctrl_monitor),
-  .COV_T          (pwrmgr_clk_ctrl_agent_cov)
-);
-
-  `uvm_component_utils(pwrmgr_clk_ctrl_agent)
-
-  `uvm_component_new
-
-  function void build_phase(uvm_phase phase);
-    super.build_phase(phase);
-    // get pwrmgr_clk_ctrl_if handle
-    if (!uvm_config_db#(virtual pwrmgr_clk_ctrl_if)::get(this, "", "vif", cfg.vif)) begin
-      `uvm_fatal(`gfn, "failed to get pwrmgr_clk_ctrl_if handle from uvm_config_db")
-    end
-  endfunction
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cfg.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cfg.sv
deleted file mode 100644
index 56209b8..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cfg.sv
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_agent_cfg extends dv_base_agent_cfg;
-
-  bit clk_ctrl_en = 1;
-  // interface handle used by driver, monitor & the sequencer, via cfg handle
-  virtual pwrmgr_clk_ctrl_if vif;
-  virtual clk_rst_if clk_rst_vif;
-  virtual clk_rst_if esc_clk_rst_vif;
-  `uvm_object_utils_begin(pwrmgr_clk_ctrl_agent_cfg)
-  `uvm_object_utils_end
-
-  `uvm_object_new
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cov.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cov.sv
deleted file mode 100644
index 1e3125a..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_cov.sv
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_agent_cov extends dv_base_agent_cov #(pwrmgr_clk_ctrl_agent_cfg);
-  `uvm_component_utils(pwrmgr_clk_ctrl_agent_cov)
-
-  // the base class provides the following handles for use:
-  // pwrmgr_clk_ctrl_agent_cfg: cfg
-
-  // covergroups
-
-  function new(string name, uvm_component parent);
-    super.new(name, parent);
-    // instantiate all covergroups here
-  endfunction : new
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_pkg.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_pkg.sv
deleted file mode 100644
index f28c076..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_agent_pkg.sv
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-package pwrmgr_clk_ctrl_agent_pkg;
-  // dep packages
-  import uvm_pkg::*;
-  import dv_utils_pkg::*;
-  import dv_lib_pkg::*;
-  import pwrmgr_clk_ctrl_common_pkg::*;
-
-  // macro includes
-  `include "uvm_macros.svh"
-  `include "dv_macros.svh"
-
-  // parameters
-
-  // local types
-  // forward declare classes to allow typedefs below
-  typedef class pwrmgr_clk_ctrl_item;
-  typedef class pwrmgr_clk_ctrl_agent_cfg;
-
-  // reuse dv_base_sequencer as is with the right parameter set
-  typedef dv_base_sequencer #(.ITEM_T(pwrmgr_clk_ctrl_item),
-                              .CFG_T (pwrmgr_clk_ctrl_agent_cfg)) pwrmgr_clk_ctrl_sequencer;
-
-  // functions
-
-  // package sources
-  `include "pwrmgr_clk_ctrl_item.sv"
-  `include "pwrmgr_clk_ctrl_agent_cfg.sv"
-  `include "pwrmgr_clk_ctrl_agent_cov.sv"
-  `include "pwrmgr_clk_ctrl_driver.sv"
-  `include "pwrmgr_clk_ctrl_monitor.sv"
-  `include "pwrmgr_clk_ctrl_agent.sv"
-  `include "pwrmgr_clk_ctrl_seq_list.sv"
-
-endpackage: pwrmgr_clk_ctrl_agent_pkg
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.core b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.core
deleted file mode 100644
index 8240bf8..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.core
+++ /dev/null
@@ -1,16 +0,0 @@
-CAPI=2:
-# Copyright lowRISC contributors.
-# Licensed under the Apache License, Version 2.0, see LICENSE for details.
-# SPDX-License-Identifier: Apache-2.0
-name: "lowrisc:dv:pwrmgr_clk_ctrl_common_pkg:0.1"
-description: "A target for common pwrmgr_clk_ctrl package to share among dv and fpv"
-filesets:
-  files_dv:
-    files:
-      - pwrmgr_clk_ctrl_common_pkg.sv
-    file_type: systemVerilogSource
-
-targets:
-  default: &default_target
-    filesets:
-      - files_dv
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.sv
deleted file mode 100644
index bb2b229..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_common_pkg.sv
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-// This package is shared among DV and FPV testbenches.
-package pwrmgr_clk_ctrl_common_pkg;
-
-  // Parameters
-  // Do not use uint because it is declared in dv_utils_pkg.
-  parameter bit [31:0] MAIN_CLK_DELAY_MIN = 15;  // cycle of aon clk
-  parameter bit [31:0] MAIN_CLK_DELAY_MAX = 258; // cycle of aon clk
-  parameter bit [31:0] ESC_CLK_DELAY_MIN = 1;    // cycle of aon clk
-  parameter bit [31:0] ESC_CLK_DELAY_MAX = 10;   // cycle of aon clk
-  parameter bit [31:0] ESC_RST_DELAY_MIN = 2;
-  parameter bit [31:0] ESC_RST_DELAY_MAX = 20;
-
-endpackage: pwrmgr_clk_ctrl_common_pkg
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_driver.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_driver.sv
deleted file mode 100644
index 6c3576f..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_driver.sv
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_driver extends dv_base_driver #(.ITEM_T(pwrmgr_clk_ctrl_item),
-                                              .CFG_T (pwrmgr_clk_ctrl_agent_cfg));
-  `uvm_component_utils(pwrmgr_clk_ctrl_driver)
-
-  // the base class provides the following handles for use:
-  // pwrmgr_clk_ctrl_agent_cfg: cfg
-
-  `uvm_component_new
-
-  virtual task run_phase(uvm_phase phase);
-    // base class forks off reset_signals() and get_and_drive() tasks
-    super.run_phase(phase);
-  endtask
-
-  // reset signals
-  virtual task reset_signals();
-  endtask
-
-  // drive trans received from sequencer
-  virtual task get_and_drive();
-    forever begin
-      seq_item_port.get_next_item(req);
-      $cast(rsp, req.clone());
-      rsp.set_id_info(req);
-      `uvm_info(`gfn, $sformatf("rcvd item:\n%0s", req.sprint()), UVM_HIGH)
-      // TODO: do the driving part
-      //
-      // send rsp back to seq
-      `uvm_info(`gfn, "item sent", UVM_HIGH)
-      seq_item_port.item_done(rsp);
-    end
-  endtask
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_if.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_if.sv
deleted file mode 100644
index a8dff85..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_if.sv
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-interface pwrmgr_clk_ctrl_if (
-  input logic clk,
-  input logic rst_n,
-  input logic clk_slow,
-  input logic rst_slow_n
-);
-  import uvm_pkg::*;
-  import pwrmgr_pkg::*;
-
-
-  // interface pins
-  pwrmgr_pkg::pwr_ast_req_t pwr_ast_req;
-  pwrmgr_pkg::pwr_clk_req_t pwr_clk_req;
-  pwrmgr_pkg::pwr_rst_req_t pwr_rst_req;
-
-  logic       rst_lc_on;
-  always_comb rst_lc_on = |pwr_rst_req.rst_lc_req;
-
-  clocking cb @(posedge clk);
-  endclocking // cb
-
-  clocking scb @(posedge clk_slow);
-  endclocking // scb
-
-  // wait for rst_n to assert and then deassert
-  task automatic wait_for_reset(bit wait_negedge = 1'b1, bit wait_posedge = 1'b1);
-    if (wait_negedge && ($isunknown(rst_n) || rst_n === 1'b1)) @(negedge rst_n);
-    if (wait_posedge && (rst_n === 1'b0)) @(posedge rst_n);
-  endtask
-
-endinterface
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_item.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_item.sv
deleted file mode 100644
index 84dc232..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_item.sv
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_item extends uvm_sequence_item;
-
-  // random variables
-
-  `uvm_object_utils_begin(pwrmgr_clk_ctrl_item)
-  `uvm_object_utils_end
-
-  `uvm_object_new
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_monitor.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_monitor.sv
deleted file mode 100644
index 64bd4e9..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/pwrmgr_clk_ctrl_monitor.sv
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_monitor extends dv_base_monitor #(
-    .ITEM_T (pwrmgr_clk_ctrl_item),
-    .CFG_T  (pwrmgr_clk_ctrl_agent_cfg),
-    .COV_T  (pwrmgr_clk_ctrl_agent_cov)
-  );
-  `uvm_component_utils(pwrmgr_clk_ctrl_monitor)
-
-  // the base class provides the following handles for use:
-  // pwrmgr_clk_ctrl_agent_cfg: cfg
-  // pwrmgr_clk_ctrl_agent_cov: cov
-  // uvm_analysis_port #(pwrmgr_clk_ctrl_item): analysis_port
-
-  `uvm_component_new
-
-  typedef enum {P_EDGE = 1, N_EDGE = -1} edge_capture_e;
-  edge_capture_e pwr_clk_req_io_ip_clk_edge_capture[$];
-  edge_capture_e pwr_ast_req_io_clk_edge_capture[$];
-  edge_capture_e pwr_rst_req_rst_lc_req_edge_capture[$];
-
-  function void build_phase(uvm_phase phase);
-    super.build_phase(phase);
-  endfunction
-
-  task run_phase(uvm_phase phase);
-    super.run_phase(phase);
-  endtask
-
-  // collect transactions forever - already forked in dv_base_monitor::run_phase
-  virtual protected task collect_trans(uvm_phase phase);
-    cfg.vif.wait_for_reset();
-
-    `uvm_info(`gfn, $sformatf("clk_ctrl %s", (cfg.clk_ctrl_en)? "enabled" :
-                              "disabled"), UVM_MEDIUM)
-    if (cfg.clk_ctrl_en) begin
-      if (cfg.vif.pwr_ast_req.io_clk_en == 0) cfg.clk_rst_vif.stop_clk();
-      if (cfg.vif.pwr_clk_req.io_ip_clk_en == 0) cfg.esc_clk_rst_vif.stop_clk();
-      fork
-        monitor_pwr_ast_o();
-        ctrl_main_clk();
-        monitor_pwr_clk_o();
-        ctrl_esc_clk();
-        monitor_pwr_rst_o();
-        ctrl_esc_rst();
-      join_none
-    end
-  endtask
-
-  task monitor_pwr_ast_o();
-    logic ival = cfg.vif.pwr_ast_req.io_clk_en;
-    forever begin
-      if (ival) begin
-        // capture negedge
-        @(negedge cfg.vif.pwr_ast_req.io_clk_en);
-        pwr_ast_req_io_clk_edge_capture.push_back(N_EDGE);
-        ival = 0;
-      end else begin
-        // capture posedge
-        @(posedge cfg.vif.pwr_ast_req.io_clk_en);
-        pwr_ast_req_io_clk_edge_capture.push_back(P_EDGE);
-        ival = 1;
-      end // else: !if(ival)
-      // handle corner case where reset and clock edge comes very close
-      @cfg.vif.cb;
-    end
-  endtask // monitor_pwr_ast_o
-
-  task monitor_pwr_clk_o();
-    logic ival = cfg.vif.pwr_clk_req.io_ip_clk_en;
-    forever begin
-      if (ival) begin
-        // capture negedge
-        @(negedge cfg.vif.pwr_clk_req.io_ip_clk_en);
-        pwr_clk_req_io_ip_clk_edge_capture.push_back(N_EDGE);
-        ival = 0;
-      end else begin
-        // capture posedge
-        @(posedge cfg.vif.pwr_clk_req.io_ip_clk_en);
-        pwr_clk_req_io_ip_clk_edge_capture.push_back(P_EDGE);
-        ival = 1;
-      end
-      // move cb to here
-      // to address the case where reset and clock edge comes very close
-      @cfg.vif.cb;
-    end
-  endtask // monitor_pwr_clk_o
-
-  task monitor_pwr_rst_o();
-    logic ival;
-    // Filter out x's in the beginning
-    @(cfg.vif.cb);
-    ival = cfg.vif.rst_lc_on;
-    forever begin
-      if (ival) begin
-        @(negedge cfg.vif.rst_lc_on);
-        pwr_rst_req_rst_lc_req_edge_capture.push_back(N_EDGE);
-        ival = 0;
-      end else begin
-        @(posedge cfg.vif.rst_lc_on);
-        pwr_rst_req_rst_lc_req_edge_capture.push_back(P_EDGE);
-        ival = 1;
-      end
-      @cfg.vif.cb;
-    end
-  endtask // monitor_pwr_rst_o
-
-  task ctrl_main_clk();
-    edge_capture_e val;
-    forever begin
-      @cfg.vif.cb;
-      if (pwr_ast_req_io_clk_edge_capture.size() > 0) begin
-        val = pwr_ast_req_io_clk_edge_capture.pop_front();
-        if (val == P_EDGE) begin
-          repeat ($urandom_range(MAIN_CLK_DELAY_MIN, MAIN_CLK_DELAY_MAX)) @cfg.vif.cb;
-          cfg.clk_rst_vif.start_clk();
-        end else begin
-          repeat ($urandom_range(MAIN_CLK_DELAY_MIN, MAIN_CLK_DELAY_MAX)) @cfg.vif.cb;
-          cfg.clk_rst_vif.stop_clk();
-        end
-      end
-    end
-  endtask // ctrl_main_clk
-
-  task ctrl_esc_clk();
-    edge_capture_e val;
-    forever begin
-      @cfg.vif.cb;
-      if (pwr_clk_req_io_ip_clk_edge_capture.size() > 0) begin
-        val = pwr_clk_req_io_ip_clk_edge_capture.pop_front();
-        if (val == P_EDGE) begin
-          repeat ($urandom_range(ESC_CLK_DELAY_MIN, ESC_CLK_DELAY_MAX)) @cfg.vif.cb;
-          cfg.esc_clk_rst_vif.start_clk();
-        end else begin
-          repeat ($urandom_range(ESC_CLK_DELAY_MIN, ESC_CLK_DELAY_MAX)) @cfg.vif.cb;
-          cfg.esc_clk_rst_vif.stop_clk();
-        end
-      end
-    end
-  endtask // ctrl_esc_clk
-
-  task ctrl_esc_rst();
-    edge_capture_e val;
-    forever begin
-      @cfg.vif.cb;
-      if (pwr_rst_req_rst_lc_req_edge_capture.size() > 0) begin
-        val = pwr_rst_req_rst_lc_req_edge_capture.pop_front();
-        if (val == P_EDGE) begin
-          repeat ($urandom_range(ESC_RST_DELAY_MIN, ESC_RST_DELAY_MAX)) @cfg.vif.cb;
-          cfg.esc_clk_rst_vif.drive_rst_pin(0);
-        end else begin
-          repeat ($urandom_range(ESC_RST_DELAY_MIN, ESC_RST_DELAY_MAX)) @cfg.vif.cb;
-          cfg.esc_clk_rst_vif.drive_rst_pin(1);
-        end
-      end
-    end // forever begin
-  endtask
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_base_seq.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_base_seq.sv
deleted file mode 100644
index 904179b..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_base_seq.sv
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-class pwrmgr_clk_ctrl_base_seq extends dv_base_seq #(
-    .REQ         (pwrmgr_clk_ctrl_item),
-    .CFG_T       (pwrmgr_clk_ctrl_agent_cfg),
-    .SEQUENCER_T (pwrmgr_clk_ctrl_sequencer)
-  );
-  `uvm_object_utils(pwrmgr_clk_ctrl_base_seq)
-
-  `uvm_object_new
-
-  virtual task body();
-    `uvm_fatal(`gtn, "Need to override this when you extend from this class!")
-  endtask
-
-endclass
diff --git a/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_seq_list.sv b/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_seq_list.sv
deleted file mode 100644
index c2b35a5..0000000
--- a/hw/dv/sv/pwrmgr_clk_ctrl_agent/seq_lib/pwrmgr_clk_ctrl_seq_list.sv
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-`include "pwrmgr_clk_ctrl_base_seq.sv"
diff --git a/hw/dv/sv/rng_agent/README.md b/hw/dv/sv/rng_agent/README.md
new file mode 100644
index 0000000..42667cf
--- /dev/null
+++ b/hw/dv/sv/rng_agent/README.md
@@ -0,0 +1,9 @@
+# RNG UVM Agent
+
+RNG UVM Agent is extended from DV library agent classes.
+
+## Description
+
+This agent continually drives entropy/entropy_ok when the enable is asserted.
+From enable asserted, it drives after entropy_agent_cfg.entropy_ok_delay_clks
+and holds each entropy value for entropy_agent_cfg.entropy_hold_clks.
diff --git a/hw/dv/sv/rng_agent/doc/index.md b/hw/dv/sv/rng_agent/doc/index.md
deleted file mode 100644
index 17934ea..0000000
--- a/hw/dv/sv/rng_agent/doc/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: "RNG UVM Agent"
----
-
-RNG UVM Agent is extended from DV library agent classes.
-
-## Description
-
-This agent continually drives entropy/entropy_ok when the enable is asserted.
-From enable asserted, it drives after entropy_agent_cfg.entropy_ok_delay_clks
-and holds each entropy value for entropy_agent_cfg.entropy_hold_clks.
diff --git a/hw/dv/sv/scoreboard/README.md b/hw/dv/sv/scoreboard/README.md
new file mode 100644
index 0000000..ce9da37
--- /dev/null
+++ b/hw/dv/sv/scoreboard/README.md
@@ -0,0 +1,3 @@
+# Scoreboard
+
+**TODO**
diff --git a/hw/dv/sv/scoreboard/doc/index.md b/hw/dv/sv/scoreboard/doc/index.md
deleted file mode 100644
index 6086743..0000000
--- a/hw/dv/sv/scoreboard/doc/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: "Scorecard"
----
diff --git a/hw/dv/sv/sec_cm/prim_count_if.sv b/hw/dv/sv/sec_cm/prim_count_if.sv
index 03836a4..74ef890 100644
--- a/hw/dv/sv/sec_cm/prim_count_if.sv
+++ b/hw/dv/sv/sec_cm/prim_count_if.sv
@@ -7,11 +7,11 @@
 // This contains a proxy class and store the object in sec_cm_pkg, which can be used in vseq to
 // control inject_fault and restore_fault
 interface prim_count_if #(
-  parameter int Width = 2,
-  parameter prim_count_pkg::prim_count_style_e CntStyle = prim_count_pkg::DupCnt
+  parameter int Width = 2
 ) (
   input clk_i,
-  input rst_ni);
+  input rst_ni
+);
 
   `include "dv_macros.svh"
   `include "uvm_macros.svh"
@@ -19,25 +19,23 @@
 
   string msg_id = $sformatf("%m");
 
-  prim_count_pkg::prim_count_style_e cnt_style = CntStyle;
-
   string path = dv_utils_pkg::get_parent_hier($sformatf("%m"));
   string signal_forced;
 
   class prim_count_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
     `uvm_object_new
 
-    logic[Width-1:0] orig_value;
+    logic [Width-1:0] orig_value;
 
     virtual task automatic inject_fault();
-      logic[Width-1:0] force_value;
+      logic [Width-1:0] force_value;
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_read(signal_forced, orig_value))
       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(force_value, force_value != orig_value;)
       `DV_CHECK(uvm_hdl_force(signal_forced, force_value))
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  signal_forced, orig_value, force_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", signal_forced, orig_value, force_value), UVM_LOW)
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_release(signal_forced))
@@ -52,12 +50,8 @@
 
   prim_count_if_proxy if_proxy;
   initial begin
-    case (cnt_style)
-      prim_count_pkg::CrossCnt: signal_forced = $sformatf("%s.up_cnt_q", path);
-      prim_count_pkg::DupCnt: signal_forced = $sformatf("%s.up_cnt_q[0]", path);
-      default: `uvm_fatal(msg_id, $sformatf("unsupported style %s", cnt_style.name()))
-    endcase
-    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced), , msg_id)
+    signal_forced = $sformatf("%s.cnt_q[%0d]", path, $urandom_range(0, 1));
+    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced),, msg_id)
 
     // Store the proxy object for TB to use
     if_proxy = new("if_proxy");
@@ -67,4 +61,5 @@
 
     `uvm_info(msg_id, $sformatf("Interface proxy class is added for %s", path), UVM_MEDIUM)
   end
+
 endinterface
diff --git a/hw/dv/sv/sec_cm/prim_double_lfsr_if.sv b/hw/dv/sv/sec_cm/prim_double_lfsr_if.sv
index 152eb8c..18c9053 100644
--- a/hw/dv/sv/sec_cm/prim_double_lfsr_if.sv
+++ b/hw/dv/sv/sec_cm/prim_double_lfsr_if.sv
@@ -10,7 +10,8 @@
   parameter int Width = 2
 ) (
   input clk_i,
-  input rst_ni);
+  input rst_ni
+);
 
   `include "dv_macros.svh"
   `include "uvm_macros.svh"
@@ -25,26 +26,25 @@
   class prim_double_lfsr_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
     `uvm_object_new
 
-    logic[Width-1:0] orig_value;
+    logic [Width-1:0] orig_value;
 
     virtual task automatic inject_fault();
-      logic[Width-1:0] force_value;
+      logic [Width-1:0] force_value;
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_read(signal_forced, orig_value))
-      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(force_value,
-                                         force_value != orig_value;)
+      `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(force_value, force_value != orig_value;)
 
       `DV_CHECK(uvm_hdl_force(signal_forced, force_value))
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  signal_forced, orig_value, force_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", signal_forced, orig_value, force_value), UVM_LOW)
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_release(signal_forced))
     endtask
 
     virtual task automatic restore_fault();
-      logic[Width-1:0] restore_value;
+      logic [Width-1:0] restore_value;
       `DV_CHECK(uvm_hdl_read(signal_for_restore, restore_value))
       `DV_CHECK(uvm_hdl_deposit(signal_forced, restore_value))
       `uvm_info(msg_id, $sformatf("Forcing %s to matching value %0d", signal_forced, restore_value),
@@ -55,8 +55,8 @@
   prim_double_lfsr_if_proxy if_proxy;
 
   initial begin
-    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced), , msg_id)
-    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_for_restore), , msg_id)
+    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced),, msg_id)
+    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_for_restore),, msg_id)
 
     // Store the proxy object for TB to use
     if_proxy = new("if_proxy");
diff --git a/hw/dv/sv/sec_cm/prim_onehot_check_if.sv b/hw/dv/sv/sec_cm/prim_onehot_check_if.sv
index f4cb05c..f153249 100644
--- a/hw/dv/sv/sec_cm/prim_onehot_check_if.sv
+++ b/hw/dv/sv/sec_cm/prim_onehot_check_if.sv
@@ -8,17 +8,18 @@
 // control inject_fault and restore_fault
 interface prim_onehot_check_if #(
   parameter int unsigned AddrWidth   = 5,
-  parameter int unsigned OneHotWidth = 2**AddrWidth,
+  parameter int unsigned OneHotWidth = 2 ** AddrWidth,
   parameter bit          AddrCheck   = 1,
   parameter bit          EnableCheck = 1,
   parameter bit          StrictCheck = 1
 ) (
-  input                          clk_i,
-  input                          rst_ni,
+  input clk_i,
+  input rst_ni,
 
-  input  logic [OneHotWidth-1:0] oh_i,
-  input  logic [AddrWidth-1:0]   addr_i,
-  input  logic                   en_i);
+  input logic [OneHotWidth-1:0] oh_i,
+  input logic [  AddrWidth-1:0] addr_i,
+  input logic                   en_i
+);
 
   `include "dv_macros.svh"
   `include "uvm_macros.svh"
@@ -38,18 +39,43 @@
   string addr_signal_forced = $sformatf("%s.addr_i", path);
 
   class prim_onehot_check_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
-    `uvm_object_new
+    logic [OneHotWidth-1:0] oh_orig_value;
+    logic [AddrWidth-1:0]   addr_orig_value;
+    logic                   en_orig_value;
 
-    logic[OneHotWidth-1:0] oh_orig_value;
-    logic[AddrWidth-1:0]   addr_orig_value;
-    logic                  en_orig_value;
+    covergroup onehot_fault_cg (string name) with function sample(
+          onehot_fault_type_e onehot_fault_type);
+      option.name = name;
+      option.per_instance = 1;
+
+      cp_onehot_fault: coverpoint onehot_fault_type {
+        option.weight = AddrWidth > 1;  // set to 0 to disable it if it's not supported
+        option.at_least = AddrWidth > 1; // If 0, we expect 0 hits.
+        bins hit = {OnehotFault};
+      }
+      cp_onehot_enable_fault: coverpoint onehot_fault_type {
+        option.weight = EnableCheck;  // set to 0 to disable it if it's not supported
+        option.at_least = EnableCheck; // If 0, we expect 0 hits.
+        bins hit = {OnehotEnableFault};
+      }
+      cp_onehot_addr_fault: coverpoint onehot_fault_type {
+        option.weight = AddrCheck;  // set to 0 to disable it if it's not supported
+        option.at_least = AddrCheck; // If 0, we expect 0 hits.
+        bins hit = {OnehotAddrFault};
+      }
+    endgroup
+
+    function new(string name = "");
+      super.new(name);
+      if (sec_cm_pkg::en_sec_cm_cov) onehot_fault_cg = new(msg_id);
+    endfunction : new
 
     virtual task inject_fault();
-      onehot_fault_type_e  onehot_fault_type;
-      bit[OneHotWidth-1:0] oh_force_value;
-      bit[AddrWidth-1:0]   addr_force_value;
-      bit                  en_force_value;
-      bit                  success;
+      onehot_fault_type_e                   onehot_fault_type;
+      bit                 [OneHotWidth-1:0] oh_force_value;
+      bit                 [  AddrWidth-1:0] addr_force_value;
+      bit                                   en_force_value;
+      bit                                   success;
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_read(oh_signal_forced, oh_orig_value))
@@ -58,10 +84,9 @@
       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(onehot_fault_type,
                                          AddrWidth == 1 -> onehot_fault_type != OnehotFault;
                                          !AddrCheck -> onehot_fault_type != OnehotAddrFault;
-                                         !EnableCheck -> onehot_fault_type != OnehotEnableFault;
-                                         )
+                                         !EnableCheck -> onehot_fault_type != OnehotEnableFault;)
 
-      // TODO, add covergroup for onehot_fault_type
+      if (sec_cm_pkg::en_sec_cm_cov) onehot_fault_cg.sample(onehot_fault_type);
       case (onehot_fault_type)
         OnehotFault: begin
           success = std::randomize(en_force_value, oh_force_value, addr_force_value) with {
@@ -86,18 +111,21 @@
               oh_force_value[addr_force_value] != (|oh_force_value);
             };
         end
-        default: `uvm_fatal(msg_id, $sformatf("Unexpected onehot_fault_type: %s",
-                                              onehot_fault_type.name))
+        default:
+        `uvm_fatal(msg_id, $sformatf("Unexpected onehot_fault_type: %s", onehot_fault_type.name))
       endcase
       `uvm_info(msg_id, $sformatf("onehot_fault_type: %0s", onehot_fault_type.name), UVM_LOW)
       `DV_CHECK_FATAL(success)
 
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  en_signal_forced, en_orig_value, en_force_value), UVM_LOW)
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  oh_signal_forced, oh_orig_value, oh_force_value), UVM_LOW)
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  addr_signal_forced, addr_orig_value, addr_force_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", en_signal_forced, en_orig_value, en_force_value),
+                UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", oh_signal_forced, oh_orig_value, oh_force_value),
+                UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", addr_signal_forced, addr_orig_value, addr_force_value
+                ), UVM_LOW)
 
       `DV_CHECK(uvm_hdl_force(en_signal_forced, en_force_value))
       `DV_CHECK(uvm_hdl_force(oh_signal_forced, oh_force_value))
@@ -109,10 +137,10 @@
     endtask
 
     virtual task restore_fault();
-      `uvm_info(msg_id, $sformatf("Forcing %s original value %0d",
-                                  en_signal_forced, en_orig_value), UVM_LOW)
-      `uvm_info(msg_id, $sformatf("Forcing %s original value %0d",
-                                  oh_signal_forced, oh_orig_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf("Forcing %s original value %0d", en_signal_forced, en_orig_value),
+                UVM_LOW)
+      `uvm_info(msg_id, $sformatf("Forcing %s original value %0d", oh_signal_forced, oh_orig_value),
+                UVM_LOW)
       `uvm_info(msg_id, $sformatf("Forcing %s original value %0d",
                                   addr_signal_forced, addr_orig_value), UVM_LOW)
       `DV_CHECK(uvm_hdl_deposit(en_signal_forced, en_orig_value))
@@ -124,8 +152,8 @@
   prim_onehot_check_if_proxy if_proxy;
 
   initial begin
-    `DV_CHECK_FATAL(uvm_hdl_check_path(en_signal_forced), , msg_id)
-    `DV_CHECK_FATAL(uvm_hdl_check_path(oh_signal_forced), , msg_id)
+    `DV_CHECK_FATAL(uvm_hdl_check_path(en_signal_forced),, msg_id)
+    `DV_CHECK_FATAL(uvm_hdl_check_path(oh_signal_forced),, msg_id)
 
     // Store the proxy object for TB to use
     if_proxy = new("if_proxy");
diff --git a/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv b/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
index f21569b..f24c687 100644
--- a/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
+++ b/hw/dv/sv/sec_cm/prim_sparse_fsm_flop_if.sv
@@ -11,7 +11,8 @@
   parameter string CustomForceName = ""
 ) (
   input clk_i,
-  input rst_ni);
+  input rst_ni
+);
 
   `include "dv_macros.svh"
   `include "uvm_macros.svh"
@@ -32,22 +33,25 @@
   class prim_sparse_fsm_flop_if_proxy extends sec_cm_pkg::sec_cm_base_if_proxy;
     `uvm_object_new
 
-    logic[Width-1:0] orig_value;
+    logic [Width-1:0] orig_value;
 
     virtual task automatic inject_fault();
-      logic[Width-1:0] force_value;
+      logic [Width-1:0] force_value;
 
       @(negedge clk_i);
       `DV_CHECK(uvm_hdl_read(signal_forced, orig_value))
       `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(force_value,
-          $countones(force_value ^ orig_value) inside {[1: MaxFlipBits]};)
+                                         $countones(
+                                             force_value ^ orig_value
+                                         ) inside {[1 : MaxFlipBits]};)
 
-      `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                  signal_forced, orig_value, force_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s from %0d to %0d", signal_forced, orig_value, force_value), UVM_LOW)
       `DV_CHECK(uvm_hdl_force(signal_forced, force_value))
       if (CustomForceName != "") begin
-        `uvm_info(msg_id, $sformatf("Forcing %s from %0d to %0d",
-                                    custom_signal_forced, orig_value, force_value), UVM_LOW)
+        `uvm_info(msg_id, $sformatf(
+                  "Forcing %s from %0d to %0d", custom_signal_forced, orig_value, force_value),
+                  UVM_LOW)
         `DV_CHECK(uvm_hdl_deposit(custom_signal_forced, force_value))
       end
       @(negedge clk_i);
@@ -69,8 +73,8 @@
       `uvm_info(msg_id, $sformatf("Forcing %s to original value %0d", signal_forced, orig_value),
                 UVM_LOW)
       `DV_CHECK(uvm_hdl_deposit(signal_forced, orig_value))
-      `uvm_info(msg_id, $sformatf("Forcing %s to original value %0d", custom_signal_forced,
-                orig_value), UVM_LOW)
+      `uvm_info(msg_id, $sformatf(
+                "Forcing %s to original value %0d", custom_signal_forced, orig_value), UVM_LOW)
       `DV_CHECK(uvm_hdl_deposit(custom_signal_forced, orig_value))
     endtask
   endclass
@@ -78,7 +82,7 @@
   prim_sparse_fsm_flop_if_proxy if_proxy;
 
   initial begin
-    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced), , msg_id)
+    `DV_CHECK_FATAL(uvm_hdl_check_path(signal_forced),, msg_id)
 
     // Store the proxy object for TB to use
     if_proxy = new("if_proxy");
diff --git a/hw/dv/sv/sec_cm/sec_cm_pkg.sv b/hw/dv/sv/sec_cm/sec_cm_pkg.sv
index ebc6f11..d9ebbd8 100644
--- a/hw/dv/sv/sec_cm/sec_cm_pkg.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_pkg.sv
@@ -25,4 +25,41 @@
 
   // store all the sec_cm proxy classes
   sec_cm_base_if_proxy sec_cm_if_proxy_q[$];
+
+  // coverage enable knob
+  bit en_sec_cm_cov = 1;
+
+  // Finds and returns a sec_cm interface proxy class instance from the sec_cm_if_proxy_q queue.
+  //
+  // This function matches the first instance whose `path` matches the input argument `path`.
+  // A fatal error is thrown if there is no instance in the queue that matches the path.
+  // The argument `is_regex` indicates that the `path` is not a full path, but a regular expression.
+  function automatic sec_cm_base_if_proxy find_sec_cm_if_proxy(string path, bit is_regex = 0);
+    sec_cm_base_if_proxy proxies[$];
+    if (is_regex) begin
+      proxies = sec_cm_pkg::sec_cm_if_proxy_q.find_first() with (!uvm_re_match(path, item.path));
+    end else begin
+      proxies = sec_cm_pkg::sec_cm_if_proxy_q.find_first() with (item.path == path);
+    end
+    if (proxies.size()) begin
+      `uvm_info(msg_id, $sformatf(
+                "find_sec_cm_if_proxy: found proxy for path %s: type = %0d, full path = %0s",
+                path,
+                proxies[0].sec_cm_type,
+                proxies[0].path
+                ), UVM_MEDIUM)
+      return proxies[0];
+    end else `uvm_fatal(msg_id, $sformatf("find_sec_cm_if_proxy: no proxy with path %s", path))
+    return null;
+  endfunction
+
+  function automatic void list_if_proxies(uvm_verbosity verbosity = UVM_MEDIUM);
+    `uvm_info(msg_id, "The sec_cm proxies:", verbosity)
+    foreach (sec_cm_if_proxy_q[i]) begin
+      `uvm_info(msg_id, $sformatf(
+                "Path %0s, type %d", sec_cm_if_proxy_q[i].path, sec_cm_if_proxy_q[i].sec_cm_type),
+                verbosity)
+    end
+  endfunction
+
 endpackage
diff --git a/hw/dv/sv/sec_cm/sec_cm_prim_count_bind.sv b/hw/dv/sv/sec_cm/sec_cm_prim_count_bind.sv
index 8454cbd..ad0bb34 100644
--- a/hw/dv/sv/sec_cm/sec_cm_prim_count_bind.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_prim_count_bind.sv
@@ -2,6 +2,6 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-module sec_cm_prim_count_bind();
-  bind prim_count prim_count_if #(.CntStyle(CntStyle), .Width(Width)) u_prim_count_if (.*);
+module sec_cm_prim_count_bind ();
+  bind prim_count prim_count_if #(.Width(Width)) u_prim_count_if (.*);
 endmodule
diff --git a/hw/dv/sv/sec_cm/sec_cm_prim_double_lfsr_bind.sv b/hw/dv/sv/sec_cm/sec_cm_prim_double_lfsr_bind.sv
index 60bc252..8f3c69d 100644
--- a/hw/dv/sv/sec_cm/sec_cm_prim_double_lfsr_bind.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_prim_double_lfsr_bind.sv
@@ -2,7 +2,6 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-module sec_cm_prim_double_lfsr_bind();
-  bind prim_double_lfsr prim_double_lfsr_if #(
-         .Width(LfsrDw)) u_prim_double_lfsr_if (.*);
+module sec_cm_prim_double_lfsr_bind ();
+  bind prim_double_lfsr prim_double_lfsr_if #(.Width(LfsrDw)) u_prim_double_lfsr_if (.*);
 endmodule
diff --git a/hw/dv/sv/sec_cm/sec_cm_prim_onehot_check_bind.sv b/hw/dv/sv/sec_cm/sec_cm_prim_onehot_check_bind.sv
index 5dcde0e..5d25a60 100644
--- a/hw/dv/sv/sec_cm/sec_cm_prim_onehot_check_bind.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_prim_onehot_check_bind.sv
@@ -2,11 +2,12 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-module sec_cm_prim_onehot_check_bind();
-  bind prim_onehot_check prim_onehot_check_if #(.AddrWidth(AddrWidth),
-                                                .OneHotWidth(OneHotWidth),
-                                                .AddrCheck(AddrCheck),
-                                                .EnableCheck(EnableCheck),
-                                                .StrictCheck(StrictCheck)
-                                              ) u_prim_onehot_check_if (.*);
+module sec_cm_prim_onehot_check_bind ();
+  bind prim_onehot_check prim_onehot_check_if #(
+    .AddrWidth  (AddrWidth),
+    .OneHotWidth(OneHotWidth),
+    .AddrCheck  (AddrCheck),
+    .EnableCheck(EnableCheck),
+    .StrictCheck(StrictCheck)
+  ) u_prim_onehot_check_if (.*);
 endmodule
diff --git a/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv b/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
index c820882..aa82a91 100644
--- a/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
+++ b/hw/dv/sv/sec_cm/sec_cm_prim_sparse_fsm_flop_bind.sv
@@ -2,7 +2,9 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
-module sec_cm_prim_sparse_fsm_flop_bind();
+module sec_cm_prim_sparse_fsm_flop_bind ();
   bind prim_sparse_fsm_flop prim_sparse_fsm_flop_if #(
-         .Width(Width), .CustomForceName(CustomForceName)) u_prim_sparse_fsm_flop_if (.*);
+    .Width(Width),
+    .CustomForceName(CustomForceName)
+  ) u_prim_sparse_fsm_flop_if (.*);
 endmodule
diff --git a/hw/dv/sv/sim_sram/README.md b/hw/dv/sv/sim_sram/README.md
new file mode 100644
index 0000000..b936539
--- /dev/null
+++ b/hw/dv/sv/sim_sram/README.md
@@ -0,0 +1,83 @@
+# Simulation SRAM
+
+The `sim_sram` module intercepts an outbound TL interface to carve out a chunk of "fake" memory used for simulation purposes only.
+This chunk of memory must not overlap with any device on the system address map - it must be an invalid address range from the system's perspective.
+
+It has 4 interfaces - clock, reset, input TL interface and the output TL interface.
+It instantiates a [1:2 TL socket](../../../ip/tlul/README.md#bus-primitives) to split the incoming TL access into two.
+One of the outputs of the socket is sinked within the module while the other is returned back.
+
+If the user chooses to instantiate an actual SRAM instance, the sinked TL interface is connected to TL SRAM adapter which converts the TL access into an SRAM access.
+The [technology-independent](../../../ip/prim/README.md) `prim_ram_1p` module is used for instantiating the memory.
+
+![Block Diagram](./doc/sim_sram.svg)
+
+This module is not meant to be synthesized.
+An easy way to ensure this is to instantiate it in simulation if and only if a special macro is defined.
+Though, it is written with the synthesizable subset of SystemVerilog to be Verilator-friendly.
+
+The most typical usecase for this module is for a SW test running on an embedded CPU in the design to be able to exchange information with the testbench.
+In DV, this is envisioned to be used for the following purposes:
+- write the status of the SW test
+- use as a SW logging device
+- write the output of an operation
+- signal an event
+
+These usecases apply to Verilator as well.
+However, at this time, the Verilator based simulations rely on the on-device UART for logging.
+
+This module must be instantiated before any XBAR / bus fabric element in the design which can return an error response when it sees an access to an invalid address range.
+Ideally, it should be placed as close to the CPU (or equivalent) as possible to reduce the simulation time.
+
+## Customizations
+
+`sim_sram` exposes the following parameters:
+
+- `InstantiateSram` (default: 0):
+
+  Controls whether to instantiate the SRAM or not.
+  The most typical (and a very much recommended) operating mode is for the SW test to only write data to this SRAM, to make it portable across various simulation platforms (DV, Verilator, FPGA etc.).
+  The testbench can simply probe the sinked output of the socket to monitor writes to specific addresses within this range.
+  So, in reality the actual SRAM instance is not needed for most cases.
+  However, it does enable the possibility of having a testbench driven SW test control (the SW test can read the contents of the SRAM to know how to proceed) for tests that are custom written for a particular simulation platform.
+  It also enables handshaking between the SW and the testbench without using any of the on-chip resources, should there be a need for them to synchronize during simulation.
+
+- `AddrWidth` (default: 32):
+
+  This should be the same as the address width of the TL interface.
+
+- `Width` (default: 32):
+
+  This should be the same as the data width of the TL interface.
+
+- `Depth` (default: 8):
+
+  Depth of the SRAM in bus words.
+
+- `ErrOnRead` (default: 1):
+
+  If the SW test reads from the SRAM, throw an error (see `InstantiateSram` above for more details).
+
+In addition, the module instantiates the `sim_sram_if` interface to allow the testbench to control the `start_addr` of the SRAM (defaults to 0) at run-time by hierarchically referencing it:
+```systemverilog
+  // In top level testbench which instantiates the `sim_sram`:
+  initial u_sim_sram.u_sim_sram_if.start_addr = 32'h3000_0000;
+```
+This interface also exposes `tl_i`, which is connected to the sinked TL output of the socket (qualified TL access made to the SRAM).
+The handle to this interface can be supplied to the UVM side via `uvm_config_db` for fine-grained control and monitoring.
+
+## Usage
+
+This module needs to be instantiated on an existing outbound TL connection, possibly deep in an existing design hierarchy while also abiding by these guidelines:
+- design sources must not depend on simulation components
+- design sources must not directly instantiate simulation components, even with `ifdefs`
+- synthesis of the design must not disturbed whatsoever
+- we must be able to run simulations with Verilator (which prevents us from using forces for example)
+
+One way of achieving this is by disconnecting an outbound TL interface in the design source where `sim_sram` needs to be inserted.
+The disconnection must be made in the desired design block ONLY if `` `SYNTHESIS`` is NOT defined and a special macro (user's choice on the name) is defined for simulations.
+This module is instantiated in the testbench rather than in the design.
+Its inbound and outbound TL interfaces are then connected to the disconnected TL interface in the design by hierarchically referencing their paths.
+
+This disconnection is currently done in `hw/ip/rv_core_ibex/rtl/rv_core_ibex.sv`, which relies on `` `RV_CORE_IBEX_SIM_SRAM`` being defined.
+In UVM DV simulations, we do not disconnect anything - we use forces instead to make the connections.
diff --git a/hw/dv/sv/sim_sram/doc/index.md b/hw/dv/sv/sim_sram/doc/index.md
deleted file mode 100644
index 45829b3..0000000
--- a/hw/dv/sv/sim_sram/doc/index.md
+++ /dev/null
@@ -1,85 +0,0 @@
----
-title: "Simulation SRAM"
----
-
-The `sim_sram` module intercepts an outbound TL interface to carve out a chunk of "fake" memory used for simulation purposes only.
-This chunk of memory must not overlap with any device on the system address map - it must be an invalid address range from the system's perspective.
-
-It has 4 interfaces - clock, reset, input TL interface and the output TL interface.
-It instantiates a [1:2 TL socket]({{< relref "hw/ip/tlul/doc#bus-primitives" >}}) to split the incoming TL access into two.
-One of the outputs of the socket is sinked within the module while the other is returned back.
-
-If the user chooses to instantiate an actual SRAM instance, the sinked TL interface is connected to TL SRAM adapter which converts the TL access into an SRAM access.
-The [technology-independent]({{< relref "hw/ip/prim/doc" >}}) `prim_ram_1p` module is used for instantiating the memory.
-
-![Block Diagram](sim_sram.svg)
-
-This module is not meant to be synthesized.
-An easy way to ensure this is to instantiate it in simulation if and only if a special macro is defined.
-Though, it is written with the synthesizable subset of SystemVerilog to be Verilator-friendly.
-
-The most typical usecase for this module is for a SW test running on an embedded CPU in the design to be able to exchange information with the testbench.
-In DV, this is envisioned to be used for the following purposes:
-- write the status of the SW test
-- use as a SW logging device
-- write the output of an operation
-- signal an event
-
-These usecases apply to Verilator as well.
-However, at this time, the Verilator based simulations rely on the on-device UART for logging.
-
-This module must be instantiated before any XBAR / bus fabric element in the design which can return an error response when it sees an access to an invalid address range.
-Ideally, it should be placed as close to the CPU (or equivalent) as possible to reduce the simulation time.
-
-## Customizations
-
-`sim_sram` exposes the following parameters:
-
-- `InstantiateSram` (default: 0):
-
-  Controls whether to instantiate the SRAM or not.
-  The most typical (and a very much recommended) operating mode is for the SW test to only write data to this SRAM, to make it portable across various simulation platforms (DV, Verilator, FPGA etc.).
-  The testbench can simply probe the sinked output of the socket to monitor writes to specific addresses within this range.
-  So, in reality the actual SRAM instance is not needed for most cases.
-  However, it does enable the possibility of having a testbench driven SW test control (the SW test can read the contents of the SRAM to know how to proceed) for tests that are custom written for a particular simulation platform.
-  It also enables handshaking between the SW and the testbench without using any of the on-chip resources, should there be a need for them to synchronize during simulation.
-
-- `AddrWidth` (default: 32):
-
-  This should be the same as the address width of the TL interface.
-
-- `Width` (default: 32):
-
-  This should be the same as the data width of the TL interface.
-
-- `Depth` (default: 8):
-
-  Depth of the SRAM in bus words.
-
-- `ErrOnRead` (default: 1):
-
-  If the SW test reads from the SRAM, throw an error (see `InstantiateSram` above for more details).
-
-In addition, the module instantiates the `sim_sram_if` interface to allow the testbench to control the `start_addr` of the SRAM (defaults to 0) at run-time by hierarchically referencing it:
-```systemverilog
-  // In top level testbench which instantiates the `sim_sram`:
-  initial u_sim_sram.u_sim_sram_if.start_addr = 32'h3000_0000;
-```
-This interface also exposes `tl_i`, which is connected to the sinked TL output of the socket (qualified TL access made to the SRAM).
-The handle to this interface can be supplied to the UVM side via `uvm_config_db` for fine-grained control and monitoring.
-
-## Usage
-
-This module needs to be instantiated on an existing outbound TL connection, possibly deep in an existing design hierarchy while also abiding by these guidelines:
-- design sources must not depend on simulation components
-- design sources must not directly instantiate simulation components, even with `ifdefs`
-- synthesis of the design must not disturbed whatsoever
-- we must be able to run simulations with Verilator (which prevents us from using forces for example)
-
-One way of achieving this is by disconnecting an outbound TL interface in the design source where `sim_sram` needs to be inserted.
-The disconnection must be made in the desired desigm block ONLY if `` `SYNTHESIS`` is NOT defined and a special macro (user's choice on the name) is defined for simulations.
-This module is instantiated in the testbench rather than in the design.
-Its inbound and outbound TL interaces are then connected to the disconnected TL interface in the design by hierarchically referencing their paths.
-
-This disconnection is currently done in `hw/ip/rv_core_ibex/rtl/rv_core_ibex.sv`, which relies on `` `RV_CORE_IBEX_SIM_SRAM`` being defined.
-In UVM DV simulations, we do not disconnect anything - we use forces instead to make the connections.
diff --git a/hw/dv/sv/spi_agent/README.md b/hw/dv/sv/spi_agent/README.md
new file mode 100644
index 0000000..4c82f45
--- /dev/null
+++ b/hw/dv/sv/spi_agent/README.md
@@ -0,0 +1,3 @@
+# SPI UVM Agent
+
+SPI UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/spi_agent/doc/index.md b/hw/dv/sv/spi_agent/doc/index.md
deleted file mode 100644
index 30e7899..0000000
--- a/hw/dv/sv/spi_agent/doc/index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "SPI UVM Agent"
----
-
-SPI UVM Agent is extended from DV library agent classes.
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_device_cmd_rsp_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_device_cmd_rsp_seq.sv
index 45a66a1..a776623 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_device_cmd_rsp_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_device_cmd_rsp_seq.sv
@@ -17,13 +17,12 @@
 
   // read queue
   spi_item rsp_q[$];
-  typedef enum {
-    SpiIdle,
-    SpiCmd,
-    SpiData
-  } spi_fsm_e;
   spi_fsm_e spi_state = SpiIdle;
 
+  // state-variables
+  spi_cmd_e  cmd;
+  int        byte_cnt = 0;
+
   virtual task body();
 
     // wait for reset release
@@ -42,94 +41,84 @@
 
   virtual task decode_cmd();
     spi_item         item;
-    spi_item         rsp = spi_item::type_id::create("rsp");
-    spi_item         rsp_clone;
-    spi_cmd_e        cmd;
-    bit       [31:0] addr;
-    bit       [31:0] data;
-    bit       [31:0] addr_cnt = 0;
-    int              byte_cnt = 0;
 
     forever begin
+
+      // Block here until the monitor sends a new seq_item, or we end the transaction.
+      fork begin: iso_fork
+        fork
+          get_nxt_req(item);
+          begin
+            // If CSB goes inactive, cleanup then go back to waiting for the next txn
+            @(posedge cfg.vif.csb[0]);
+            spi_state = SpiIdle;
+            rsp_q.delete();
+          end
+        join_any
+        disable fork;
+      end: iso_fork join
+      if (cfg.vif.csb[0] == 1'b1) continue;
+
+      // Check for start of transaction.
+      if (item.first_byte) begin
+        // decode command, check for errors
+        cmd = cmd_check(item.data.pop_front);
+        spi_state = SpiCmd;
+      end
+
       case (spi_state)
-        SpiIdle: begin
-          get_nxt_req(item);
-          // find start of transaction
-          if (item.first_byte) begin
-            // decode command
-            cmd = cmd_check(item.data.pop_front);
-            spi_state = SpiCmd;
-          end
-        end
-
+        SpiIdle:;
         SpiCmd: begin
-          get_nxt_req(item);
-          // make sure that we did not start a new transaction
-          if (item.first_byte) begin
-            // decode command
-            cmd = cmd_check(item.data.pop_front);
-            spi_state = SpiCmd;
-            addr_cnt = 0;
-          end else begin
-            addr[31-8*byte_cnt-:8] = item.data.pop_front();
-            byte_cnt += 1;
-            if (byte_cnt + 1 == cfg.spi_cmd_width) begin  // +1 to accound for the cmd byte
-              byte_cnt  = 0;
-              spi_state = SpiData;
-            end
+          byte_cnt += 1;
+          if (byte_cnt == cfg.spi_cmd_width) begin
+            byte_cnt  = 0;
+            spi_state = SpiData;
+            // If the command is a READ, we need to begin sending data now.
+            if (cmd inside {ReadStd, ReadDual, ReadQuad}) handle_reads();
           end
         end
-
         SpiData: begin
           case (cmd)
-            ReadStd, ReadDual, ReadQuad, ReadSts1, ReadSts2, ReadSts3, ReadJedec: begin
-              // read_until CSB low
-              data = $urandom();
-              addr_cnt += 4;
-              case (cmd)
-                ReadStd:  `DV_CHECK_RANDOMIZE_WITH_FATAL(rsp, rsp.data.size() == 8;)
-                ReadDual: `DV_CHECK_RANDOMIZE_WITH_FATAL(rsp, rsp.data.size() == 16;)
-                default:  `DV_CHECK_RANDOMIZE_WITH_FATAL(rsp, rsp.data.size() == 32;)
-              endcase  // case (cmd)
-              `downcast(rsp_clone, rsp.clone());
-              rsp_q.push_back(rsp_clone);
-              rsp = new();
-              // offload input queue
-              get_nxt_req(item);
-              if (item.first_byte) begin
-                // decode command
-                cmd = cmd_check(item.data.pop_front);
-                spi_state = SpiCmd;
-                addr_cnt = 0;
-              end
-            end
-
-            WriteStd, WriteDual, WriteQuad: begin
-              get_nxt_req(item);
-              if (item.first_byte) begin
-                // decode command
-                cmd = cmd_check(item.data.pop_front);
-                spi_state = SpiCmd;
-                addr_cnt = 0;
-              end else begin
-                data[31-8*addr_cnt[1:0]-:8] = item.data.pop_front();
-                addr_cnt += 1;
-              end
-              // potential TODO add associative array for read back of write data
-            end
-
-            default: begin
-              `uvm_fatal(`gfn, $sformatf("UNSUPPORTED COMMAND"))
-            end
+            ReadStd,  ReadDual,  ReadQuad: handle_reads();
+            WriteStd, WriteDual, WriteQuad: handle_writes(item);
+            default: `uvm_fatal(`gfn, $sformatf("UNSUPPORTED COMMAND"))
           endcase
         end
-        default: begin
-          `uvm_fatal(`gfn, $sformatf("BAD STATE"))
-        end
-      endcase
-    end
+        default: `uvm_fatal(`gfn, $sformatf("BAD STATE"))
+      endcase // (spi_state)
+
+    end // forever
+
   endtask
 
+  function void handle_reads();
+    spi_item rsp = spi_item::type_id::create("rsp");
+    spi_item rsp_clone;
+
+    case (cmd)
+      ReadStd :  cfg.spi_mode = Standard;
+      ReadDual:  cfg.spi_mode = Dual;
+      ReadQuad:  cfg.spi_mode = Quad;
+      default :  cfg.spi_mode = RsvdSpd;
+    endcase
+
+    // Create rsp
+    // Currently, the rsp is just random data
+    rsp = new();
+    `DV_CHECK_RANDOMIZE_WITH_FATAL(rsp,
+      (cmd == ReadStd)  -> data.size() ==  256;
+      (cmd == ReadDual) -> data.size() ==  512;
+      (cmd == ReadQuad) -> data.size() == 1024;
+      num_lanes == cfg.get_sio_size();)
+    `downcast(rsp_clone, rsp.clone());
+    rsp_q.push_back(rsp_clone);
+
+  endfunction // handle_read
+
+  function void handle_writes(spi_item item);
+    void'(item.data.pop_front());
+    // potential TODO add associative array for read back of write data
+  endfunction // handle_writes
 
   function spi_cmd_e cmd_check(bit [7:0] data);
     spi_cmd_e cmd;
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_device_flash_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_device_flash_seq.sv
index 0796229..89cf468 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_device_flash_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_device_flash_seq.sv
@@ -10,19 +10,31 @@
   `uvm_object_utils(spi_device_flash_seq)
   `uvm_object_new
 
+  // indicate which CSB is for this seq to respond
+  bit [CSB_WIDTH-1:0] csb_sel = 0;
+
   // return the data in this queue if it's provided, otherwise, random data will be used
   bit[7:0] byte_data_q[$];
   // allow user to have forever loop to auto respond the req
   bit is_forever_rsp_seq;
 
   task body();
+    cfg.spi_func_mode = SpiModeFlash;
     do begin
-      spi_item req;
-      p_sequencer.req_analysis_fifo.get(req);
+      p_sequencer.req_analysis_fifo.peek(req);
+      if (req.csb_sel == csb_sel) begin
+        `DV_CHECK(p_sequencer.req_analysis_fifo.try_get(req))
+      end else begin
+        // expect the req is handled by other mode sequence like TPM seq
+        // After a small delay, try_get should fail
+        #1ps `DV_CHECK(!p_sequencer.req_analysis_fifo.try_get(req))
+        continue;
+      end
       `downcast(rsp, req.clone());
       rsp.payload_q = byte_data_q;
       start_item(rsp);
       finish_item(rsp);
+      get_response(rsp);
     end while (is_forever_rsp_seq);
   endtask
 
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_device_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_device_seq.sv
index 01192df..d5c8e7d 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_device_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_device_seq.sv
@@ -31,11 +31,11 @@
 
   virtual task send_rsp(ref spi_item item_q[$]);
     forever begin
-      spi_item  rsp;
       wait(item_q.size > 0);
       rsp = item_q.pop_front();
       start_item(rsp);
       finish_item(rsp);
+      get_response(rsp);
     end
   endtask
 
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_host_dummy_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_host_dummy_seq.sv
index 57fc937..10c8010 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_host_dummy_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_host_dummy_seq.sv
@@ -6,12 +6,23 @@
   `uvm_object_utils(spi_host_dummy_seq)
   `uvm_object_new
 
+  rand bit [CSB_WIDTH-1:0] csb_sel;
+  rand bit [7:0] dummy_data;
+
   virtual task body();
     req = spi_item::type_id::create("req");
     start_item(req);
     `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
-                                   item_type inside {SpiTransSckNoCsb, SpiTransCsbNoSck};
-                                   data.size() == 1; // no used, set to 1 to simpify randomization
+                                   item_type inside {SpiTransSckNoCsb, SpiTransCsbNoSck,
+                                                    SpiTransIncompleteOpcode};
+                                   if (cfg.spi_func_mode == SpiModeGeneric) {
+                                    item_type != SpiTransIncompleteOpcode;
+                                   } else {
+                                    // TODO(#15721), dummy CSB without clock affects flash mode
+                                    item_type != SpiTransCsbNoSck;
+                                   }
+                                   csb_sel == local::csb_sel;
+                                   data.size() == 1; // use for SpiTransIncompleteOpcode
                                    )
     finish_item(req);
     get_response(rsp);
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_host_flash_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_host_flash_seq.sv
index 0147361..54348dc 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_host_flash_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_host_flash_seq.sv
@@ -8,7 +8,9 @@
   // if address isn't provided, will be randomized based on cmd_infos
   rand bit [7:0] address_q[$];
   rand bit [7:0] payload_q[$];
-  rand int          read_size;
+  rand int       read_size;
+
+  bit [CSB_WIDTH-1:0] csb_sel = 0;
 
   `uvm_object_utils_begin(spi_host_flash_seq)
     `uvm_field_int(opcode,          UVM_DEFAULT)
@@ -19,44 +21,51 @@
   `uvm_object_new
 
   virtual task body();
-    int addr_bytes, num_lanes, dummy_cycles;
+    int num_addr_bytes, num_lanes, dummy_cycles;
     bit write_command;
 
     req = spi_item::type_id::create("req");
     start_item(req);
 
+    cfg.spi_func_mode = SpiModeFlash;
     cfg.extract_cmd_info_from_opcode(opcode,
         // output
-        addr_bytes, write_command, num_lanes, dummy_cycles);
+        num_addr_bytes, write_command, num_lanes, dummy_cycles);
     if (address_q.size() == 0) begin
       `DV_CHECK_MEMBER_RANDOMIZE_WITH_FATAL(address_q,
-          address_q.size == addr_bytes;)
+          address_q.size == num_addr_bytes;)
     end else begin
-      `DV_CHECK_EQ(address_q.size(), addr_bytes)
+      `DV_CHECK_EQ(address_q.size(), num_addr_bytes)
     end
 
     `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
-                                   item_type == SpiFlashTrans;
+                                   item_type == SpiTransNormal;
+                                   csb_sel == local::csb_sel;
                                    opcode == local::opcode;
                                    write_command == local::write_command;
                                    num_lanes == local::num_lanes;
                                    dummy_cycles == local::dummy_cycles;
-                                   address_q.size() == addr_bytes;
+                                   address_q.size() == num_addr_bytes;
                                    foreach (address_q[i]) {
                                      address_q[i] == local::address_q[i];
                                    }
-                                   if (write_command) {
-                                     read_size == 0;
-                                     payload_q.size() == local::payload_q.size();
-                                     foreach (payload_q[i]) {
-                                       payload_q[i] == local::payload_q[i];
-                                     }
-                                   } else { // read
-                                     read_size == local::read_size;
-                                     payload_q.size() == 0;
+                                   if (num_lanes == 0) {
+                                    read_size == 0;
+                                    payload_q.size() == 0;
+                                   } else {
+                                    if (write_command) {
+                                      read_size == 0;
+                                      payload_q.size() == local::payload_q.size();
+                                      foreach (payload_q[i]) {
+                                        payload_q[i] == local::payload_q[i];
+                                      }
+                                    } else { // read
+                                      read_size == local::read_size;
+                                      payload_q.size() == 0;
+                                    }
                                    }
                                   // TODO, consolidate data and payload later
-                                  data.size == 1;)
+                                  data.size == 0;)
     finish_item(req);
     get_response(rsp);
   endtask
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_host_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_host_seq.sv
index 33b790c..705f5c4 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_host_seq.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_host_seq.sv
@@ -9,6 +9,7 @@
   // data to be sent
   rand bit [7:0] data[$];
 
+  bit [CSB_WIDTH-1:0] csb_sel = 0;
   // constrain size of data sent / received to be at most 64kB
   constraint data_size_c {
     data.size() inside {[1:65536]};
@@ -17,7 +18,9 @@
   virtual task body();
     req = spi_item::type_id::create("req");
     start_item(req);
+    cfg.spi_func_mode = SpiModeGeneric;
     `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
+                                   csb_sel == local::csb_sel;
                                    item_type == SpiTransNormal;
                                    data.size() == local::data.size();
                                    foreach (data[i]) {
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv b/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv
new file mode 100644
index 0000000..3096a8b
--- /dev/null
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_host_tpm_seq.sv
@@ -0,0 +1,61 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+class spi_host_tpm_seq extends spi_base_seq;
+  local bit [7:0] address_q[$];
+  rand bit [TPM_ADDR_WIDTH-1:0] addr;
+  rand bit [7:0] data_q[$];
+  rand int       read_size;
+  rand int       write_command;
+
+  bit [CSB_WIDTH-1:0] csb_sel = 0;
+
+  constraint write_command_and_data_c {
+    if (write_command) {
+      read_size == 0;
+    } else {
+      data_q.size == 0;
+    }
+  }
+
+  `uvm_object_utils_begin(spi_host_tpm_seq)
+    `uvm_field_int(addr,            UVM_DEFAULT)
+    `uvm_field_int(write_command,   UVM_DEFAULT)
+    `uvm_field_int(read_size,       UVM_DEFAULT)
+    `uvm_field_int(csb_sel,         UVM_DEFAULT)
+    `uvm_field_queue_int(data_q,    UVM_DEFAULT)
+    `uvm_field_queue_int(address_q, UVM_DEFAULT)
+  `uvm_object_utils_end
+  `uvm_object_new
+
+  virtual task body();
+    // MSB byte sent first
+    repeat (TPM_ADDR_WIDTH_BYTE) begin
+      address_q.push_front(addr[7:0]);
+      addr = addr >> 8;
+    end
+    req = spi_item::type_id::create("req");
+    start_item(req);
+
+    cfg.spi_func_mode = SpiModeTpm;
+
+    `DV_CHECK_RANDOMIZE_WITH_FATAL(req,
+                                   item_type == SpiTransNormal;
+                                   csb_sel == local::csb_sel;
+                                   write_command == local::write_command;
+                                   address_q.size() == local::address_q.size();
+                                   foreach (address_q[i]) {
+                                     address_q[i] == local::address_q[i];
+                                   }
+                                   payload_q.size() == 0; // not used for tpm
+                                   data.size == local::data_q.size();
+                                   foreach (data_q[i]) {
+                                     data[i] == local::data_q[i];
+                                   }
+                                   read_size == local::read_size;)
+    finish_item(req);
+    get_response(rsp);
+  endtask
+
+endclass
diff --git a/hw/dv/sv/spi_agent/seq_lib/spi_seq_list.sv b/hw/dv/sv/spi_agent/seq_lib/spi_seq_list.sv
index cf33658..7bd1c77 100644
--- a/hw/dv/sv/spi_agent/seq_lib/spi_seq_list.sv
+++ b/hw/dv/sv/spi_agent/seq_lib/spi_seq_list.sv
@@ -5,6 +5,7 @@
 `include "spi_base_seq.sv"
 `include "spi_host_seq.sv"
 `include "spi_host_flash_seq.sv"
+`include "spi_host_tpm_seq.sv"
 `include "spi_device_flash_seq.sv"
 `include "spi_host_dummy_seq.sv"
 `include "spi_device_seq.sv"
diff --git a/hw/dv/sv/spi_agent/spi_agent.core b/hw/dv/sv/spi_agent/spi_agent.core
index 045c8b1..93a869d 100644
--- a/hw/dv/sv/spi_agent/spi_agent.core
+++ b/hw/dv/sv/spi_agent/spi_agent.core
@@ -27,6 +27,7 @@
       - seq_lib/spi_host_seq.sv: {is_include_file: true}
       - seq_lib/spi_host_dummy_seq.sv: {is_include_file: true}
       - seq_lib/spi_host_flash_seq.sv: {is_include_file: true}
+      - seq_lib/spi_host_tpm_seq.sv: {is_include_file: true}
       - seq_lib/spi_device_flash_seq.sv: {is_include_file: true}
       - seq_lib/spi_device_seq.sv: {is_include_file: true}
       - seq_lib/spi_device_cmd_rsp_seq.sv: {is_include_file: true}
diff --git a/hw/dv/sv/spi_agent/spi_agent_cfg.sv b/hw/dv/sv/spi_agent/spi_agent_cfg.sv
index 53bcb4f..5adc813 100644
--- a/hw/dv/sv/spi_agent/spi_agent_cfg.sv
+++ b/hw/dv/sv/spi_agent/spi_agent_cfg.sv
@@ -4,7 +4,8 @@
 
 class spi_agent_cfg extends dv_base_agent_cfg;
 
-  // enable checkers in monitor
+  // enable monitor collection and checker
+  bit en_monitor = 1'b1;
   bit en_monitor_checks = 1'b1;
 
   // byte order (configured by vseq)
@@ -15,10 +16,8 @@
   bit host_bit_dir;               // 0 - msb -> lsb, 1 - lsb -> msb
   bit device_bit_dir;             // 0 - msb -> lsb, 1 - lsb -> msb
   bit sck_on;                     // keep sck on
-  bit [1:0] csb_sel;      // Select active CSB
   bit partial_byte;       // Transfering less than byte
   bit [3:0] bits_to_transfer; // Bits to transfer if using less than byte
-  bit csb_consecutive;            // Don't deassert CSB
   bit decode_commands;  // Used in monitor if decoding of commands needed
   bit [2:0] cmd_addr_size = 4; //Address size for command
 
@@ -26,20 +25,25 @@
   // spi_host regs
   //-------------------------
   // csid reg
-  bit [MAX_CS-1:0] csid;
+  bit [CSB_WIDTH-1:0] csid;
+  // indicate csb is selected in cfg or spi_item
+  bit                     csb_sel_in_cfg = 1;
   // configopts register fields
-  bit              sck_polarity[MAX_CS];       // aka CPOL
-  bit              sck_phase[MAX_CS];          // aka CPHA
-  bit              full_cyc[MAX_CS];
-  bit [3:0]        csn_lead[MAX_CS];
-  bit [3:0]        csn_trail[MAX_CS];
-  bit [3:0]        csn_idle[MAX_CS];
+  bit              sck_polarity[NUM_CSB];       // aka CPOL
+  bit              sck_phase[NUM_CSB];          // aka CPHA
+  bit              full_cyc[NUM_CSB];
+  bit [3:0]        csn_lead[NUM_CSB];
+  bit [3:0]        csn_trail[NUM_CSB];
+  bit [3:0]        csn_idle[NUM_CSB];
   // command register fields
   spi_mode_e       spi_mode;
   // Cmd info configs
   spi_flash_cmd_info cmd_infos[bit[7:0]];
-  bit              is_flash_mode;
+  // Controls address size for flash_cmd_info of type SpiFlashAddrCfg.
+  bit              flash_addr_4b_en;
 
+  // set this mode before starting an item
+  spi_func_mode_e  spi_func_mode;
 
   // address width in bytes (default is 4 bytes)
   int spi_cmd_width   = 4;
@@ -59,6 +63,13 @@
   uint max_extra_dly_ns_btw_word    = 1000; // no timeout btw word
   uint extra_dly_chance_pc_btw_word = 5;    // percentage of extra delay btw each word
 
+  // min_idle_ns_after_csb_drop and its max_* sibling specify the range for
+  // which CSB will be held inactive at the end of a host sequence item.
+  // A value will be randomly chosen in the range. CSB is guaranteed to be inactive for
+  // at least min_idle_ns_after_csb_drop before the next transaction begins.
+  uint min_idle_ns_after_csb_drop = 1;
+  uint max_idle_ns_after_csb_drop = 1000;
+
   // interface handle used by driver, monitor & the sequencer
   virtual spi_if vif;
 
@@ -66,9 +77,7 @@
     `uvm_field_int (sck_period_ps,  UVM_DEFAULT)
     `uvm_field_int (host_bit_dir,   UVM_DEFAULT)
     `uvm_field_int (device_bit_dir, UVM_DEFAULT)
-    `uvm_field_int (csb_sel,        UVM_DEFAULT)
     `uvm_field_int (partial_byte,   UVM_DEFAULT)
-    `uvm_field_int (csb_consecutive,    UVM_DEFAULT)
     `uvm_field_int (decode_commands,    UVM_DEFAULT)
     `uvm_field_int (cmd_addr_size,      UVM_DEFAULT)
     `uvm_field_int (bits_to_transfer,             UVM_DEFAULT)
@@ -78,6 +87,8 @@
     `uvm_field_int (en_extra_dly_btw_word,        UVM_DEFAULT)
     `uvm_field_int (max_extra_dly_ns_btw_word,    UVM_DEFAULT)
     `uvm_field_int (extra_dly_chance_pc_btw_word, UVM_DEFAULT)
+    `uvm_field_int (min_idle_ns_after_csb_drop,   UVM_DEFAULT)
+    `uvm_field_int (max_idle_ns_after_csb_drop,   UVM_DEFAULT)
 
     `uvm_field_sarray_int(sck_polarity,   UVM_DEFAULT)
     `uvm_field_sarray_int(sck_phase,      UVM_DEFAULT)
@@ -86,14 +97,14 @@
     `uvm_field_sarray_int(csn_trail,       UVM_DEFAULT)
     `uvm_field_sarray_int(csn_idle,        UVM_DEFAULT)
     `uvm_field_enum(spi_mode_e, spi_mode, UVM_DEFAULT)
-    `uvm_field_int(is_flash_mode, UVM_DEFAULT)
+    `uvm_field_enum(spi_func_mode_e, spi_func_mode, UVM_DEFAULT)
   `uvm_object_utils_end
 
   `uvm_object_new
 
-  virtual task wait_sck_edge(sck_edge_type_e sck_edge_type);
+  virtual task wait_sck_edge(sck_edge_type_e sck_edge_type, bit [CSB_WIDTH-1:0] csb_id);
     bit       wait_posedge;
-    bit [1:0] sck_mode = {sck_polarity[csid], sck_phase[csid]};
+    bit [1:0] sck_mode = {sck_polarity[csb_id], sck_phase[csb_id]};
 
     // sck pola   sck_pha   mode
     //        0         0      0: sample at leading  posedge_sck (drive @ prev negedge_sck)
@@ -117,7 +128,7 @@
   endtask
 
   // TODO use  dv_utils_pkg::endian_swap_byte_arr() if possible
-  virtual function void swap_byte_order(ref bit [7:0] data[$]);
+  virtual function void swap_byte_order(ref logic [7:0] data[$]);
     bit [7:0] data_arr[];
     data_arr = data;
     `uvm_info(`gfn, $sformatf("\n  spi_agent_cfg, data_q_baseline: %p", data), UVM_DEBUG)
@@ -146,22 +157,60 @@
   endfunction  : is_opcode_supported
 
   virtual function void extract_cmd_info_from_opcode(input bit [7:0] opcode,
-      output bit [2:0] addr_bytes,
+      output bit [2:0] num_addr_bytes,
       output bit write_command,
       output bit [2:0] num_lanes,
       output int dummy_cycles);
     if (cmd_infos.exists(opcode)) begin
-      addr_bytes    = cmd_infos[opcode].addr_bytes;
-      write_command = cmd_infos[opcode].write_command;
-      num_lanes     = cmd_infos[opcode].num_lanes;
-      dummy_cycles  = cmd_infos[opcode].dummy_cycles;
+      num_addr_bytes = get_num_addr_byte(opcode);
+      write_command  = cmd_infos[opcode].write_command;
+      num_lanes      = cmd_infos[opcode].num_lanes;
+      dummy_cycles   = cmd_infos[opcode].dummy_cycles;
     end else begin
       // if it's invalid opcode, here is the default setting
       `uvm_info(`gfn, $sformatf("extract invalid opcode: 0x%0h", opcode), UVM_MEDIUM)
-      write_command = 1;
-      addr_bytes    = 0;
-      num_lanes     = 1;
-      dummy_cycles  = 0;
+      write_command  = 1;
+      num_addr_bytes = 0;
+      num_lanes      = 1;
+      dummy_cycles   = 0;
     end
   endfunction  : extract_cmd_info_from_opcode
+
+  // this task collects one byte data based on num_lanes, which is used in both monitor and driver
+  task read_byte(input int num_lanes,
+                 input bit is_device_rsp,
+                 input  bit [CSB_WIDTH-1:0] csb_id,
+                 output logic [7:0] data);
+    int which_bit = 8;
+    while (which_bit != 0) begin
+      wait_sck_edge(SamplingEdge, csb_id);
+      case (num_lanes)
+        1: data[--which_bit] = is_device_rsp ? vif.sio[1] : vif.sio[0];
+        2: begin
+          data[--which_bit] = vif.sio[1];
+          data[--which_bit] = vif.sio[0];
+        end
+        4: begin
+          data[--which_bit] = vif.sio[3];
+          data[--which_bit] = vif.sio[2];
+          data[--which_bit] = vif.sio[1];
+          data[--which_bit] = vif.sio[0];
+        end
+        default: `uvm_fatal(`gfn, $sformatf("Unsupported lanes num 0x%0h", num_lanes))
+      endcase
+    end
+
+    `uvm_info(`gfn, $sformatf("sampled one byte data for flash: 0x%0h", data), UVM_HIGH)
+  endtask
+
+  virtual function int get_num_addr_byte(bit [7:0] opcode);
+    `DV_CHECK(cmd_infos.exists(opcode))
+    case (cmd_infos[opcode].addr_mode)
+      SpiFlashAddrDisabled: return 0;
+      SpiFlashAddrCfg: return flash_addr_4b_en ? 4 : 3;
+      SpiFlashAddr3b: return 3;
+      SpiFlashAddr4b: return 4;
+      default: `uvm_fatal(`gfn, "Impossible value")
+    endcase
+  endfunction  : get_num_addr_byte
 endclass : spi_agent_cfg
diff --git a/hw/dv/sv/spi_agent/spi_agent_pkg.sv b/hw/dv/sv/spi_agent/spi_agent_pkg.sv
index 97a0794..3d9739c 100644
--- a/hw/dv/sv/spi_agent/spi_agent_pkg.sv
+++ b/hw/dv/sv/spi_agent/spi_agent_pkg.sv
@@ -13,15 +13,26 @@
   `include "dv_macros.svh"
 
   // parameter
-  parameter uint MAX_CS = 4;      // set to the maximum valid value
+  parameter uint TPM_START_MAX_WAIT_TIME_NS = 10_000_000; // 10ms
+  parameter uint CSB_WIDTH = 2;
+  parameter uint NUM_CSB = CSB_WIDTH ** 2;
+  parameter time TIME_SCK_STABLE_TO_CSB_NS = 10ns;
+  parameter uint TPM_ADDR_WIDTH      = 24;
+  parameter uint TPM_ADDR_WIDTH_BYTE = TPM_ADDR_WIDTH / 8;
+  parameter bit TPM_WAIT             = 1'b0;
+  parameter bit TPM_START            = 1'b1;
+  parameter byte MAX_TPM_SIZE        = 64;
+  parameter byte TPM_CMD_DIR_BIT_POS = 7;
+  parameter bit TPM_CMD_WRITE_BIT_VALUE = 0;
+  parameter bit TPM_CMD_READ_BIT_VALUE  = 1;
 
   // transaction type
   typedef enum {
     // device dut
     SpiTransNormal,    // normal SPI trans
     SpiTransSckNoCsb,  // bad SPI trans with sck but no csb
-    SpiTransCsbNoSck,   // bad SPI trans with csb but no sck
-    SpiFlashTrans
+    SpiTransCsbNoSck,  // bad SPI trans with csb but no sck
+    SpiTransIncompleteOpcode  // bad SPI trans with incompleted opcode for flash/tpm mode only
   } spi_trans_type_e;
 
   // sck edge type - used by driver and monitor
@@ -40,25 +51,61 @@
     RsvdSpd  = 2'b11   // RFU
   } spi_mode_e;
 
+  // spi functional mode
+  typedef enum bit [1:0] {
+    SpiModeGeneric,
+    SpiModeFlash,
+    SpiModeTpm
+  } spi_func_mode_e;
+
+  // Command-Set implemented by the agent in device_mode.
+  // The first byte in a transaction should be one of these commands.
   typedef enum bit [7:0] {
-    ReadStd    = 8'b11001100,
-    WriteStd   = 8'b11111100,
-    ReadDual   = 8'b00000011,
-    WriteDual  = 8'b00001100,
-    ReadQuad   = 8'b00001111,
-    WriteQuad  = 8'b11110000,
-    CmdOnly    = 8'b10000001,
-    ReadSts1   = 8'b00000101,
-    ReadSts2   = 8'b00110101,
-    ReadSts3   = 8'b00010101,
-    ReadJedec  = 8'b10011111,
-    ReadSfdp   = 8'b01011010
+    ReadStd      = 8'b11001100,
+    WriteStd     = 8'b11111100,
+    ReadDual     = 8'b00000011,
+    WriteDual    = 8'b00001100,
+    ReadQuad     = 8'b00001111,
+    WriteQuad    = 8'b11110000,
+    CmdOnly      = 8'b10000001
   } spi_cmd_e;
 
+  typedef enum bit [1:0] {
+    SpiFlashAddrDisabled,
+    // Configurable 3b or 4b address
+    SpiFlashAddrCfg,
+    // Fixed 3b addr
+    SpiFlashAddr3b,
+    // Fixed 4b addr
+    SpiFlashAddr4b
+  } spi_flash_addr_mode_e;
+
+  typedef enum bit [1:0] {
+    SpiIdle,
+    SpiCmd,
+    SpiData
+  } spi_fsm_e;
+
   // forward declare classes to allow typedefs below
   typedef class spi_item;
   typedef class spi_agent_cfg;
 
+  string msg_id = "spi_agent_pkg";
+
+  // formart: {write, 0, size-1 (6 bits)}
+  function automatic bit [7:0] get_tpm_cmd(bit write, uint size);
+    `DV_CHECK_LE(size, MAX_TPM_SIZE, , , msg_id)
+    return {write ? TPM_CMD_WRITE_BIT_VALUE : TPM_CMD_READ_BIT_VALUE, 1'b0, 6'(size - 1)};
+  endfunction
+
+  function automatic void decode_tpm_cmd(input bit [7:0] cmd,
+                                         output bit write, output uint size);
+    write = (cmd[TPM_CMD_DIR_BIT_POS] == TPM_CMD_WRITE_BIT_VALUE) ? 1 : 0;
+    `DV_CHECK_EQ(cmd[6], 0, , , msg_id)
+    size = cmd[5:0] + 1;
+    `DV_CHECK_LE(size, MAX_TPM_SIZE, , , msg_id)
+  endfunction
+
   // package sources
   `include "spi_flash_cmd_info.sv"
   `include "spi_agent_cfg.sv"
diff --git a/hw/dv/sv/spi_agent/spi_device_driver.sv b/hw/dv/sv/spi_agent/spi_device_driver.sv
index cba6013..1d355e0 100644
--- a/hw/dv/sv/spi_agent/spi_device_driver.sv
+++ b/hw/dv/sv/spi_agent/spi_device_driver.sv
@@ -2,15 +2,34 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
+// The spi_device_driver responds to bus transactions initiated by a SPI host,
+// and its responses depend on cfg.spi_func_mode.
+//
+// For SpiModeGeneric, the driver waits for CSB to be asserted, then drives
+// the contents of spi_item.data sequentially, timed with the SPI host's clock
+// output.
+//
+// For SpiModeFlash, the spi_item must be submitted at exactly the moment when
+// the driver is to begin driving the response. The driver sends the contents
+// of spi_item.payload_q sequentially, timed with the SPI host's clock output.
+// Note that spi_device_driver does NOT observe the command, address, or dummy
+// cycles, and it assumes those phases have already passed. For this mode,
+// spi_device_driver is anticipated to be used in conjunction with the
+// spi_monitor's req_analysis_port, which writes a spi_item into the
+// spi_sequencer's connected req_analysis_fifo at the moment those phases have
+// completed.
 class spi_device_driver extends spi_driver;
   `uvm_component_utils(spi_device_driver)
   `uvm_component_new
 
+  bit [CSB_WIDTH-1:0] active_csb = 0;
+
   virtual task reset_signals();
     forever begin
       @(negedge cfg.vif.rst_n);
       `uvm_info(`gfn, "\n  dev_drv: in reset progress", UVM_DEBUG)
       under_reset = 1'b1;
+      cfg.vif.sio_out = 'z;
       @(posedge cfg.vif.rst_n);
       under_reset = 1'b0;
       `uvm_info(`gfn, "\n  dev_drv: out of reset", UVM_DEBUG)
@@ -22,15 +41,29 @@
 
     forever begin
       seq_item_port.get_next_item(req);
-      wait (!under_reset && !cfg.vif.csb[cfg.csid]);
+      `DV_CHECK_EQ(cfg.vif.disconnected, 0)
+
+      if (cfg.csb_sel_in_cfg) active_csb = cfg.csid;
+      else                    active_csb = req.csb_sel;
+
+      $cast(rsp, req.clone());
+      rsp.set_id_info(req);
+      // The response should read the payload actually consumed, not start with
+      // it filled out.
+      rsp.payload_q = '{};
+
+      wait (!under_reset && !cfg.vif.csb[active_csb]);
       fork
         begin: iso_fork
           fork
-            if (cfg.is_flash_mode == 0) begin
-              send_rx_item(req);
-            end else begin
-              send_flash_item(req);
-            end
+            case (cfg.spi_func_mode)
+              SpiModeGeneric: send_rx_item(req, rsp);
+              SpiModeFlash: send_flash_item(req, rsp);
+              SpiModeTpm: send_tpm_item(req, rsp);
+              default: begin
+                `uvm_fatal(`gfn, $sformatf("Invalid mode %s", cfg.spi_func_mode.name))
+              end
+            endcase
             drive_bus_to_highz();
             drive_bus_for_reset();
           join_any
@@ -38,11 +71,12 @@
         end: iso_fork
       join
       seq_item_port.item_done();
+      seq_item_port.put_response(rsp);
       `uvm_info(`gfn, "\n  dev_drv: item done", UVM_HIGH)
     end
   endtask : get_and_drive
 
-  virtual task send_rx_item(spi_item item);
+  virtual task send_rx_item(spi_item item, spi_item rsp);
     logic [3:0] sio_bits;
     bit         bits_q[$];
     int         max_tx_bits;
@@ -55,7 +89,7 @@
                               bits_q.size()), UVM_DEBUG)
     // pop enough bits do drive all needed sio
     while (bits_q.size() > 0) begin
-       if (bits_q.size() > 0) cfg.wait_sck_edge(DrivingEdge);
+       if (bits_q.size() > 0) cfg.wait_sck_edge(DrivingEdge, active_csb);
       for (int i = 0; i < 4; i++) begin
         sio_bits[i] = (i < max_tx_bits) ? bits_q.pop_front() : 1'bz;
       end
@@ -64,68 +98,76 @@
                                 bits_q.size(), sio_bits[0]), UVM_DEBUG)
 
     end
+    // TODO, ideally we should capture the data device receives and put in rsp.
   endtask : send_rx_item
 
-  virtual task send_flash_item(spi_item item);
+  virtual task send_flash_item(spi_item item, spi_item rsp);
     logic [3:0] sio_bits;
     bit         bits_q[$];
-    bit [7:0]   data[$] = {item.payload_q};
+    logic [7:0] data[$] = {item.payload_q};
+    spi_mode_e spi_mode;
 
     `uvm_info(`gfn, $sformatf("sending rx_item:\n%s", item.sprint()), UVM_MEDIUM)
 
-    if (item.write_command) return;
-
-    if (cfg.byte_order) cfg.swap_byte_order(data);
-    bits_q = {>> 1 {data}};
-
     fork
-      begin: isolation_thread
-        fork
-          begin: csb_deassert_thread
-            wait(cfg.vif.csb[cfg.csb_sel] == 1'b1);
-          end
-          begin
-            spi_mode_e spi_mode;
-            if (item.num_lanes == 1) spi_mode = Standard;
-            else if (item.num_lanes == 2) spi_mode = Dual;
-            else spi_mode = Quad;
+      begin : thread_collect_payload
+        forever begin
+          logic [7:0] byte_data;
+          cfg.read_byte(item.num_lanes, !item.write_command, active_csb, byte_data);
+          rsp.payload_q.push_back(byte_data);
+          if (!item.write_command) rsp.read_size++;
+        end
+      end : thread_collect_payload
+
+      begin : thread_send_read_data
+        if (!item.write_command) begin
+          if (cfg.byte_order) cfg.swap_byte_order(data);
+          bits_q = {>> 1 {data}};
+
+          if (item.num_lanes == 1) spi_mode = Standard;
+          else if (item.num_lanes == 2) spi_mode = Dual;
+          else spi_mode = Quad;
 
           forever begin
-            cfg.wait_sck_edge(DrivingEdge);
-            for (int i = 0; i < item.num_lanes; i++) begin
-              sio_bits[i] = bits_q.size > 0 ? bits_q.pop_front() : $urandom_range(0, 1);
+            cfg.wait_sck_edge(DrivingEdge, active_csb);
+            // The first bit in bits_q is the MSB, which is driven on the sio
+            // lane with the highest active index.
+            for (int i = $high(sio_bits); i >= $low(sio_bits); i--) begin
+              if (i >= item.num_lanes) begin
+                sio_bits[i] = 1'bz;
+              end else begin
+                sio_bits[i] = bits_q.size > 0 ? bits_q.pop_front() : $urandom_range(0, 1);
+              end
             end
             send_data_to_sio(spi_mode, sio_bits);
-            end
           end
-        join_any
-        disable fork;
-        `uvm_info(`gfn, $sformatf("finished rx_item:\n%s", item.sprint()), UVM_HIGH)
-      end: isolation_thread
+        end
+      end : thread_send_read_data
     join
   endtask : send_flash_item
 
+  virtual task send_tpm_item(spi_item item, spi_item rsp);
+    // TODO, this mode isn't used in OT project
+    `uvm_fatal(`gfn, "TPM device mode isn't supported")
+  endtask : send_tpm_item
+
   virtual task send_data_to_sio(spi_mode_e mode, input logic [3:0] sio_bits);
     case (mode)
-      Standard: cfg.vif.sio[1]   <= sio_bits[0];
-      Dual:     cfg.vif.sio[1:0] <= sio_bits[1:0];
-      default:  cfg.vif.sio      <= sio_bits;
+      Standard: cfg.vif.sio_out[1]   <= sio_bits[0];
+      Dual:     cfg.vif.sio_out[1:0] <= sio_bits[1:0];
+      default:  cfg.vif.sio_out      <= sio_bits;
     endcase
   endtask : send_data_to_sio
 
   virtual task drive_bus_to_highz();
-    @(posedge cfg.vif.csb[cfg.csid]);
-    case (cfg.spi_mode)
-      Standard: cfg.vif.sio[1]   = 1'bz;
-      Dual:     cfg.vif.sio[1:0] = 2'bzz;
-      default:  cfg.vif.sio      = 4'bzzzz;   // including Quad SPI mode
-    endcase
+    @(posedge cfg.vif.csb[active_csb]);
+    cfg.vif.sio_out = 'z;
     `uvm_info(`gfn, "\n  dev_drv: drive_bus_to_highz is done", UVM_DEBUG)
   endtask : drive_bus_to_highz
 
   virtual task drive_bus_for_reset();
     @(negedge cfg.vif.rst_n);
-    cfg.vif.sio = 4'bzzzz;
+    cfg.vif.sio_out = 'z;
     `uvm_info(`gfn, "\n  dev_drv: drive_bus_for_reset is done", UVM_DEBUG)
   endtask : drive_bus_for_reset
 endclass : spi_device_driver
diff --git a/hw/dv/sv/spi_agent/spi_flash_cmd_info.sv b/hw/dv/sv/spi_agent/spi_flash_cmd_info.sv
index c36f8c5..438d36c 100644
--- a/hw/dv/sv/spi_agent/spi_flash_cmd_info.sv
+++ b/hw/dv/sv/spi_agent/spi_flash_cmd_info.sv
@@ -6,49 +6,40 @@
 // cmd, it knows how many addr_bytes, direction, dummy_cycles etc
 class spi_flash_cmd_info extends uvm_sequence_item;
   rand bit [7:0] opcode;
-  rand bit [2:0] addr_bytes;
+  rand spi_flash_addr_mode_e addr_mode;
   rand bit write_command;
+  // number of lanes when sending payload, set to 0 if no payload is expected
   rand bit [2:0] num_lanes;
-  rand int dummy_cycles; // TODO add support in driver and monitor
-  rand bit addr_swap;
-  rand bit data_swap;
+  rand int dummy_cycles;
 
-  constraint addr_bytes_c {
-    addr_bytes inside {0, 3, 4};
+  constraint addr_mode_c {
     // for dual/quad mode, it always contains address
-    num_lanes > 1 -> addr_bytes > 0;
+    num_lanes > 1 -> addr_mode != SpiFlashAddrDisabled;
   }
 
   constraint num_lanes_c {
     write_command -> num_lanes == 1;
-    num_lanes inside {1, 2, 4};
-  }
-
-  // payload_swap_en only works with write data and SingleIO mode
-  constraint data_swap_c {
-    data_swap -> num_lanes == 1 &&  write_command == 1;
+    num_lanes inside {0, 1, 2, 4};
   }
 
   constraint dummy_cycles_c {
+    // for dual/quad read, need at least 2 dummy cycles
+    num_lanes > 1 && !write_command -> dummy_cycles >= 2;
+    // write or no payload doesn't have dummy cycle.
+    write_command || num_lanes == 0 -> dummy_cycles == 0;
     dummy_cycles dist {
-      0       :/ 2,
-      [1:8]   :/ 2,
-      [9:100] :/ 1
+      0     :/ 1,
+      [1:7] :/ 1,
+      8     :/ 1
     };
   }
-  // TODO, simplify setting. Remove this later
-  constraint simplify_c {
-    addr_swap == 0;
-    data_swap == 0;
-  }
+
   `uvm_object_utils_begin(spi_flash_cmd_info)
     `uvm_field_int(opcode,        UVM_DEFAULT)
-    `uvm_field_int(addr_bytes,    UVM_DEFAULT)
+    `uvm_field_enum(spi_flash_addr_mode_e, addr_mode, UVM_DEFAULT)
     `uvm_field_int(write_command, UVM_DEFAULT)
     `uvm_field_int(dummy_cycles,  UVM_DEFAULT)
     `uvm_field_int(num_lanes,     UVM_DEFAULT)
-    `uvm_field_int(addr_swap,     UVM_DEFAULT)
-    `uvm_field_int(data_swap,     UVM_DEFAULT)
   `uvm_object_utils_end
 
   `uvm_object_new
diff --git a/hw/dv/sv/spi_agent/spi_host_driver.sv b/hw/dv/sv/spi_agent/spi_host_driver.sv
index bbba068..edf6995 100644
--- a/hw/dv/sv/spi_agent/spi_host_driver.sv
+++ b/hw/dv/sv/spi_agent/spi_host_driver.sv
@@ -7,6 +7,7 @@
   `uvm_component_new
 
   uint sck_pulses = 0;
+  bit [CSB_WIDTH-1:0] active_csb;
 
   virtual task run_phase(uvm_phase phase);
     fork
@@ -18,11 +19,13 @@
   // Resets signals
   virtual task reset_signals();
     forever begin
-      @(negedge cfg.vif.rst_n);
+      @(negedge cfg.vif.rst_n or negedge cfg.vif.disconnected);
+      if (cfg.vif.disconnected) continue;
       under_reset = 1'b1;
+      active_csb  = 1'b0;
       cfg.vif.sck <= cfg.sck_polarity[0];
-      cfg.vif.sio <= 'hz;
-      cfg.vif.csb <= 'hF;
+      cfg.vif.sio_out <= 'x;
+      cfg.vif.csb <= '1;
       sck_pulses = 0;
       @(posedge cfg.vif.rst_n);
       under_reset = 1'b0;
@@ -32,153 +35,236 @@
   // generate sck
   task gen_sck();
     @(posedge cfg.vif.rst_n);
-    fork
-      forever begin
-        if (sck_pulses > 0 || cfg.sck_on) begin
-          cfg.vif.sck <= ~cfg.vif.sck;
-          #((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
-          cfg.vif.sck <= ~cfg.vif.sck;
-          #((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
-          if (sck_pulses > 0) sck_pulses--;
-          // dly after a word transfer is completed
-          if (sck_pulses % 32 == 0) #(get_rand_extra_delay_ns_btw_word() * 1ns);
-        end else begin
-          @(cfg.sck_on, sck_pulses);
-          if (sck_pulses > 0) begin
-            // drive half cycle first
-            cfg.vif.sck <= cfg.sck_polarity[0];
-            #(cfg.sck_period_ps / 2 * 1ps);
-          end
+    forever begin
+      if (sck_pulses > 0 || cfg.sck_on) begin
+        cfg.vif.sck <= ~cfg.vif.sck;
+        #((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
+        cfg.vif.sck <= ~cfg.vif.sck;
+        #((cfg.sck_period_ps / 2 + get_rand_extra_delay_ns_btw_sck() * 1000) * 1ps);
+        if (sck_pulses > 0) sck_pulses--;
+        // dly after a word transfer is completed
+        if (sck_pulses % 32 == 0) #(get_rand_extra_delay_ns_btw_word() * 1ns);
+      end else begin
+        @(cfg.sck_on, sck_pulses);
+        if (sck_pulses > 0) begin
+          // drive half cycle first
+          cfg.vif.sck <= cfg.sck_polarity[active_csb];
+          #(cfg.sck_period_ps / 2 * 1ps);
         end
-        cfg.vif.sck_pulses = sck_pulses;
       end
-      forever begin
-        @(cfg.sck_polarity[0]);
-        cfg.vif.sck_polarity = cfg.sck_polarity[0];
-        if (sck_pulses == 0) cfg.vif.sck <= cfg.sck_polarity[0];
-      end
-      forever begin
-        @(cfg.sck_phase[0]);
-        cfg.vif.sck_phase = cfg.sck_phase[0];
-      end
-    join
+      cfg.vif.sck_pulses = sck_pulses;
+    end
   endtask : gen_sck
 
+  task switch_polarity_and_phase();
+    if (cfg.vif.sck != cfg.sck_polarity[active_csb]) begin
+      cfg.vif.sck <= cfg.sck_polarity[active_csb];
+      #TIME_SCK_STABLE_TO_CSB_NS;
+    end
+  endtask : switch_polarity_and_phase
+
   task get_and_drive();
     forever begin
       seq_item_port.get_next_item(req);
+      `DV_CHECK_EQ(cfg.vif.disconnected, 0)
+
       $cast(rsp, req.clone());
       rsp.set_id_info(req);
       `uvm_info(`gfn, $sformatf("spi_host_driver: rcvd item:\n%0s", req.sprint()), UVM_HIGH)
+
+      if (cfg.csb_sel_in_cfg) active_csb = cfg.csid;
+      else                    active_csb = req.csb_sel;
+
+      switch_polarity_and_phase();
+      // switch from x to z to release the IO
+      cfg.vif.sio_out <= 'z;
+
       case (req.item_type)
-        SpiTransNormal:   drive_normal_item();
+        SpiTransNormal: begin
+          case (cfg.spi_func_mode)
+            SpiModeGeneric: drive_normal_item();
+            SpiModeFlash: drive_flash_item();
+            SpiModeTpm: drive_tpm_item();
+            default: begin
+              `uvm_fatal(`gfn, $sformatf("Invalid mode %s", cfg.spi_func_mode.name))
+            end
+          endcase
+        end
         SpiTransSckNoCsb: drive_sck_no_csb_item();
         SpiTransCsbNoSck: drive_csb_no_sck_item();
-        SpiFlashTrans:    drive_flash_item();
+        SpiTransIncompleteOpcode: begin
+          // TODO(#15721), need to have at least 3 spi_clk when CSB is active, otherwise,
+          // this incomplete req may have bad side effect due to `sys_csb_deasserted_pulse_i`
+          // isn't set correctly
+          int num_bits = $urandom_range(3, 7);
+          // invoke `drive_normal_item` to send less than 8 bits data as incompleted opcode
+          drive_normal_item(.partial_byte(1), .num_bits(num_bits));
+        end
+        default: `uvm_fatal(`gfn, $sformatf("Invalid type %s", req.item_type.name))
       endcase
+
+      cfg.vif.csb[active_csb] <= 1'b1;
+      cfg.vif.sio_out <= 'x;
+
+      #($urandom_range(cfg.max_idle_ns_after_csb_drop, cfg.min_idle_ns_after_csb_drop) * 1ns);
       `uvm_info(`gfn, "spi_host_driver: item sent", UVM_HIGH)
       seq_item_port.item_done(rsp);
     end
   endtask
 
-  task issue_data(bit [7:0] transfer_data[$]);
+  task issue_data(input bit [7:0] transfer_data[$], output bit [7:0] returned_data[$],
+                  input bit last_data = 1, input int num_bits = 8);
     for (int i = 0; i < transfer_data.size(); i++) begin
       bit [7:0] host_byte;
       bit [7:0] device_byte;
       int       which_bit;
-      bit [3:0] num_bits;
-      if (cfg.partial_byte == 1) begin
-        num_bits = cfg.bits_to_transfer;
-      end else begin
-        num_bits = 8;
-      end
+
+      if (transfer_data.size == 0) return;
+
       host_byte = transfer_data[i];
       for (int j = 0; j < num_bits; j++) begin
         // drive sio early so that it is stable at the sampling edge
         which_bit = cfg.host_bit_dir ? j : 7 - j;
-        cfg.vif.sio[0] <= host_byte[which_bit];
+        cfg.vif.sio_out[0] <= host_byte[which_bit];
         // wait for sampling edge to sample sio (half cycle)
-        cfg.wait_sck_edge(SamplingEdge);
+        cfg.wait_sck_edge(SamplingEdge, active_csb);
         which_bit = cfg.device_bit_dir ? j : 7 - j;
         device_byte[which_bit] = cfg.vif.sio[1];
         // wait for driving edge to complete 1 cycle
-        if (i != transfer_data.size() - 1 || j != (num_bits - 1)) cfg.wait_sck_edge(DrivingEdge);
+        if (!last_data || i != transfer_data.size() - 1 || j != (num_bits - 1)) begin
+          cfg.wait_sck_edge(DrivingEdge, active_csb);
+        end
       end
-      rsp.data[i] = device_byte;
+      returned_data[i] = device_byte;
     end
   endtask
 
-  task drive_normal_item();
-    cfg.vif.csb[cfg.csb_sel] <= 1'b0;
-    if ((req.data.size() == 1) && (cfg.partial_byte == 1)) begin
-      sck_pulses = cfg.bits_to_transfer;
+  task drive_normal_item(bit partial_byte = cfg.partial_byte,
+                         int num_bits = cfg.partial_byte ? cfg.bits_to_transfer : 8);
+    cfg.vif.csb[active_csb] <= 1'b0;
+    if ((req.data.size() == 1) && (partial_byte == 1)) begin
+      sck_pulses = num_bits;
+      num_bits = num_bits;
     end else begin
       sck_pulses = req.data.size() * 8;
     end
 
     // for mode 1 and 3, get the leading edges out of the way
-    cfg.wait_sck_edge(LeadingEdge);
+    cfg.wait_sck_edge(LeadingEdge, active_csb);
 
     // drive data
-    issue_data(req.data);
+    issue_data(req.data, rsp.data, , num_bits);
 
     wait(sck_pulses == 0);
-    if (cfg.csb_consecutive == 0) begin
-      cfg.vif.csb[cfg.csb_sel] <= 1'b1;
-      cfg.vif.sio[0] <= 1'bx;
-    end
   endtask
 
   task drive_flash_item();
     bit [7:0] cmd_addr_bytes[$];
+    bit [7:0] dummy_return_q[$]; // nothing to return for flash cmd, addr and write
 
     `uvm_info(`gfn, $sformatf("Driving flash item: \n%s", req.sprint()), UVM_MEDIUM)
-    cfg.vif.csb[cfg.csb_sel] <= 1'b0;
+    cfg.vif.csb[active_csb] <= 1'b0;
 
     cmd_addr_bytes = {req.opcode, req.address_q};
     sck_pulses = cmd_addr_bytes.size() * 8 + req.dummy_cycles;
-    if (req.write_command) begin
-      `DV_CHECK_EQ(req.num_lanes, 1)
-      sck_pulses += req.payload_q.size * 8;
-    end else begin
-      `DV_CHECK_EQ(req.payload_q.size, 0)
-      sck_pulses += req.read_size * (8 / req.num_lanes);
+    if (req.num_lanes > 0) begin
+      if (req.write_command) begin
+        `DV_CHECK_EQ(req.num_lanes, 1)
+        sck_pulses += req.payload_q.size * 8;
+      end else begin
+        `DV_CHECK_EQ(req.payload_q.size, 0)
+        sck_pulses += req.read_size * (8 / req.num_lanes);
+      end
     end
 
     // for mode 1 and 3, get the leading edges out of the way
-    cfg.wait_sck_edge(LeadingEdge);
+    cfg.wait_sck_edge(LeadingEdge, active_csb);
 
     // driver cmd and address
-    issue_data(cmd_addr_bytes);
+    issue_data(cmd_addr_bytes, dummy_return_q);
 
     // align to DrivingEdge, if the item has more to send
-    if (req.dummy_cycles > 0 || req.payload_q.size > 0 ) cfg.wait_sck_edge(DrivingEdge);
+    if (req.dummy_cycles || req.payload_q.size || req.read_size) begin
+      cfg.wait_sck_edge(DrivingEdge, active_csb);
+      cfg.vif.sio_out <= 'dz;
 
-    repeat (req.dummy_cycles) begin
-      //cfg.vif.sio <= 'dz;
-      cfg.wait_sck_edge(DrivingEdge);
+      if (req.dummy_cycles > 0) begin
+        repeat (req.dummy_cycles) begin
+          cfg.wait_sck_edge(SamplingEdge, active_csb);
+        end
+        if (req.payload_q.size || req.read_size) cfg.wait_sck_edge(DrivingEdge, active_csb);
+      end
+      // drive data
+      if (req.write_command) begin
+        issue_data(req.payload_q, dummy_return_q);
+      end else begin
+        repeat (req.read_size) begin
+          logic [7:0] data;
+          cfg.read_byte(.num_lanes(req.num_lanes), .is_device_rsp(1),
+                        .csb_id(active_csb), .data(data));
+          rsp.payload_q.push_back(data);
+        end
+        `uvm_info(`gfn, $sformatf("collect read data for flash: 0x%p", rsp.payload_q), UVM_MEDIUM)
+      end
     end
-    // drive data
-    issue_data(req.payload_q);
 
     wait(sck_pulses == 0);
-    cfg.vif.csb[cfg.csb_sel] <= 1'b1;
-    cfg.vif.sio <= 'dx;
+  endtask
+
+  task drive_tpm_item();
+    bit [7:0] cmd_bytes[$];
+    bit [7:0] returned_bytes[$];
+    byte data_num_byte;
+    bit [7:0] tpm_rsp;
+
+    `uvm_info(`gfn, $sformatf("Driving TPM item: \n%s", req.sprint()), UVM_MEDIUM)
+
+    `DV_CHECK_EQ_FATAL(req.address_q.size(), TPM_ADDR_WIDTH_BYTE)
+    // When issuing write command, data size cannot be 0.
+    `DV_CHECK(req.data.size() > 0 | !req.write_command)
+    data_num_byte = req.write_command ? req.data.size() : req.read_size;
+    cmd_bytes = {get_tpm_cmd(req.write_command, data_num_byte), req.address_q};
+
+    cfg.vif.csb[active_csb] <= 1'b0;
+    sck_pulses = cmd_bytes.size() * 8;
+    issue_data(.transfer_data(cmd_bytes), .returned_data(returned_bytes), .last_data(0));
+
+    // polling TPM_START
+    `DV_SPINWAIT(
+      do begin
+        bit [7:0] dummy_bytes[$];
+        dummy_bytes = {$urandom};
+        sck_pulses += 8;
+        issue_data(.transfer_data(dummy_bytes), .returned_data(returned_bytes), .last_data(0));
+        tpm_rsp = returned_bytes[0];
+      end while (tpm_rsp[0] == TPM_WAIT);
+      , , TPM_START_MAX_WAIT_TIME_NS)
+
+    `uvm_info(`gfn, "Received TPM START", UVM_HIGH)
+    sck_pulses += (req.write_command ? req.data.size : req.read_size) * 8;
+    if (req.write_command) begin
+      issue_data(.transfer_data(req.data), .returned_data(returned_bytes), .last_data(1));
+      // no data will return for write
+      foreach (returned_bytes[i]) `DV_CHECK_EQ(returned_bytes[i], 0)
+    end else begin // TPM read
+      bit [7:0] dummy_bytes[$];
+      repeat (req.read_size) dummy_bytes.push_back($urandom);
+      issue_data(.transfer_data(dummy_bytes), .returned_data(rsp.data), .last_data(1));
+      `uvm_info(`gfn, $sformatf("collect read data for TPM: 0x%p", rsp.data), UVM_HIGH)
+    end
+    wait(sck_pulses == 0);
   endtask
 
   task drive_sck_no_csb_item();
-    repeat (req.dummy_clk_cnt) begin
+    repeat (req.dummy_sck_cnt) begin
       #($urandom_range(1, 100) * 1ns);
       cfg.vif.sck <= ~cfg.vif.sck;
     end
-    cfg.vif.sck <= cfg.sck_polarity[0];
-    #1ps; // make sure sck and csb (for next item) not change at the same time
   endtask
 
   task drive_csb_no_sck_item();
-    cfg.vif.csb[cfg.csb_sel] <= 1'b0;
-    #(req.dummy_sck_length_ns * 1ns);
-    cfg.vif.csb[cfg.csb_sel] <= 1'b1;
+    cfg.vif.csb[active_csb] <= 1'b0;
+    #(req.dummy_csb_length_ns * 1ns);
   endtask
 
   function uint get_rand_extra_delay_ns_btw_sck();
diff --git a/hw/dv/sv/spi_agent/spi_if.sv b/hw/dv/sv/spi_agent/spi_if.sv
index 9dac172..5d6cfd2 100644
--- a/hw/dv/sv/spi_agent/spi_if.sv
+++ b/hw/dv/sv/spi_agent/spi_if.sv
@@ -4,14 +4,20 @@
 
 interface spi_if
   import spi_agent_pkg::*;
+  import uvm_pkg::*;
 (
-  input rst_n
+  input rst_n,
+  inout wire [3:0] sio
 );
-
   // standard spi interface pins
   logic       sck;
-  logic [3:0] csb;
-  logic [3:0] sio;
+  logic [CSB_WIDTH-1:0] csb;
+
+  // spi host drives sio to x when idle. release to z in case the io is used for other peripheral
+  bit disconnected;
+
+  logic [3:0] sio_out;
+  assign sio = disconnected ? 'z : sio_out;
 
   // debug signals
   logic [7:0] host_byte;
@@ -22,9 +28,15 @@
   bit         sck_polarity;
   bit         sck_phase;
 
+  bit         en_chk = 1;
+  string      msg_id = "spi_if";
   //---------------------------------
   // common tasks
   //---------------------------------
+  function automatic void disconnect(bit set_disconnect = 1);
+    disconnected = set_disconnect;
+  endfunction : disconnect
+
   task automatic wait_for_clks(int clks);
     repeat (clks) @(posedge sck);
   endtask : wait_for_clks
@@ -38,4 +50,18 @@
     endcase
   endtask : get_data_from_sio
 
+  function automatic bit [CSB_WIDTH-1:0] get_active_csb();
+    foreach (csb[i]) begin
+      if (csb[i] === 0) begin
+        return i;
+      end
+    end
+    `uvm_fatal(msg_id, "Don't call this function - get_active_csb when there is no active CSB")
+  endfunction : get_active_csb
+
+  // check only 1 csb can be active
+  initial forever begin
+    @(csb);
+    if (en_chk) `DV_CHECK_LE($countones(CSB_WIDTH'(~csb)), 1, , , msg_id)
+  end
 endinterface : spi_if
diff --git a/hw/dv/sv/spi_agent/spi_item.sv b/hw/dv/sv/spi_agent/spi_item.sv
index 6e120e9..2f02965 100644
--- a/hw/dv/sv/spi_agent/spi_item.sv
+++ b/hw/dv/sv/spi_agent/spi_item.sv
@@ -7,45 +7,65 @@
   // hold transaction type
   rand spi_trans_type_e item_type;
   // byte of data sent or received
-  rand bit [7:0] data[$];
+  rand logic [7:0] data[$];
   // start of transaction
   bit first_byte;
   // flash command constraints
   rand int read_size;
-  rand bit [7:0] payload_q[$];
+  rand logic [7:0] payload_q[$];
   rand bit write_command;
   rand bit [7:0] address_q[$];
   rand bit [7:0] opcode;
-  rand bit [2:0] num_lanes; // 1,2 or 4 lanes for read response
+  // 1,2 or 4 lanes for read response, 0 means no data
+  rand bit [2:0] num_lanes;
   rand int dummy_cycles;
 
   // for dummy transaction
-  rand uint dummy_clk_cnt;
-  rand uint dummy_sck_length_ns;
+  rand uint dummy_sck_cnt;
+  rand uint dummy_csb_length_ns;
+
+  // indicate the active csb
+  rand bit [CSB_WIDTH-1:0] csb_sel;
+
+  // transaction status. only use in monitor on flash mode
+  // allow scb to process payload when one byte data is received, instead
+  // of wait until the entire item is collected. This indicates item has collected all data.
+  bit mon_item_complete;
 
   // constrain size of data sent / received to be at most 64kB
-  constraint data_size_c { data.size() inside {[1:65536]}; }
+  constraint data_size_c { data.size() inside {[0:65536]}; }
 
-  constraint dummy_clk_cnt_c { dummy_clk_cnt inside {[1:1000]}; }
+  constraint dummy_sck_cnt_c {
+    if (item_type == SpiTransSckNoCsb) {
+      dummy_sck_cnt inside {[1:1000]};
+    } else {
+      dummy_sck_cnt == 0;
+    }}
 
-  constraint dummy_sck_length_c { dummy_sck_length_ns inside {[1:1000]}; }
+  constraint dummy_csb_length_ns_c {
+    if (item_type == SpiTransCsbNoSck) {
+      dummy_csb_length_ns inside {[1:1000]};
+    } else {
+      dummy_csb_length_ns == 0;
+    }}
 
   constraint num_lanes_c {
     write_command -> num_lanes == 1;
-    num_lanes inside {1, 2, 4};
+    num_lanes inside {0, 1, 2, 4};
   }
 
   `uvm_object_utils_begin(spi_item)
     `uvm_field_enum(spi_trans_type_e, item_type, UVM_DEFAULT)
     `uvm_field_queue_int(data,                   UVM_DEFAULT)
     `uvm_field_int(first_byte,                   UVM_DEFAULT)
-    `uvm_field_int(dummy_clk_cnt,                UVM_DEFAULT)
-    `uvm_field_int(dummy_sck_length_ns,          UVM_DEFAULT)
+    `uvm_field_int(dummy_sck_cnt,                UVM_DEFAULT)
+    `uvm_field_int(dummy_csb_length_ns,          UVM_DEFAULT)
     `uvm_field_int(read_size,                    UVM_DEFAULT)
     `uvm_field_int(write_command,                UVM_DEFAULT)
     `uvm_field_int(opcode,                       UVM_DEFAULT)
     `uvm_field_int(num_lanes,                    UVM_DEFAULT)
     `uvm_field_int(dummy_cycles,                 UVM_DEFAULT)
+    `uvm_field_int(csb_sel,                      UVM_DEFAULT)
     `uvm_field_queue_int(payload_q,              UVM_DEFAULT)
     `uvm_field_queue_int(address_q,              UVM_DEFAULT)
   `uvm_object_utils_end
@@ -61,8 +81,8 @@
 
     txt = "\n \t ----| SPI ITEM |----";
     txt = {txt, $sformatf("\n ----| Item Type: \t%s", item_type.name()) };
-    txt = {txt, $sformatf("\n ----| Dummy Clk Cnt: \t%0d",  dummy_clk_cnt) };
-    txt = {txt, $sformatf("\n ----| Dummy Sck Lengtht: \t%0d ns",  dummy_sck_length_ns) };
+    txt = {txt, $sformatf("\n ----| Dummy Clk Cnt: \t%0d",  dummy_sck_cnt) };
+    txt = {txt, $sformatf("\n ----| Dummy Sck Lengtht: \t%0d ns",  dummy_csb_length_ns) };
     txt = {txt, $sformatf("\n ----| First Byte: \t%b ",  first_byte) };
     txt = {txt, $sformatf("\n ----| Data:") };
 
diff --git a/hw/dv/sv/spi_agent/spi_monitor.sv b/hw/dv/sv/spi_agent/spi_monitor.sv
index ba12062..2391cba 100644
--- a/hw/dv/sv/spi_agent/spi_monitor.sv
+++ b/hw/dv/sv/spi_agent/spi_monitor.sv
@@ -14,6 +14,11 @@
   spi_cmd_e cmd;
   spi_cmd_e cmdtmp;
   int cmd_byte;
+  bit [CSB_WIDTH-1:0] active_csb;
+
+  // store the data across multiple CSB in case host sends byte transfers
+  bit [7:0] generic_mode_host_byte_q[$];
+  bit [7:0] generic_mode_device_byte_q[$];
 
   // Analysis port for the collected transfer.
   uvm_analysis_port #(spi_item) host_analysis_port;
@@ -28,251 +33,303 @@
   endfunction
 
   virtual task run_phase(uvm_phase phase);
-    collect_trans(phase);
+    forever collect_trans(phase);
   endtask
 
-  // collect transactions forever
+  // collect transactions
   virtual protected task collect_trans(uvm_phase phase);
+    bit flash_opcode_received;
+
+    wait (cfg.en_monitor);
+    wait (&cfg.vif.csb === 0);
+    active_csb = cfg.vif.get_active_csb();
+
+    cfg.vif.sck_polarity = cfg.sck_polarity[active_csb];
+    cfg.vif.sck_phase = cfg.sck_phase[active_csb];
+
     host_item   = spi_item::type_id::create("host_item", this);
     device_item = spi_item::type_id::create("device_item", this);
-
-    forever begin
-      @(negedge cfg.vif.csb);
-      // indicate that this the first byte in a spi transaction
-      host_item.first_byte = 1;
-      cmd = CmdOnly;
-      cmd_byte = 0;
-      if (cfg.is_flash_mode == 0) begin
-        collect_curr_trans();
-      end else begin
-        collect_flash_trans();
+    fork
+      begin : isolation_thread
+        fork
+          begin : csb_deassert_thread
+            wait(cfg.vif.csb[active_csb] == 1'b1);
+          end
+          begin : main_mon_thread
+            case (cfg.spi_func_mode)
+              SpiModeGeneric: begin
+                // indicate that this the first byte in a spi transaction
+                host_item.first_byte = 1;
+                cmd = CmdOnly;
+                cmd_byte = 0;
+                collect_curr_trans();
+              end
+              SpiModeFlash: collect_flash_trans(host_item, flash_opcode_received);
+              SpiModeTpm: collect_tpm_trans(host_item);
+              default: begin
+                `uvm_fatal(`gfn, $sformatf("Invalid mode %s", cfg.spi_func_mode.name))
+              end
+            endcase
+          end : main_mon_thread
+        join_any
+        disable fork;
+      end : isolation_thread
+    join
+    // write to host_analysis_port
+    case (cfg.spi_func_mode)
+      SpiModeFlash: begin
+        if (flash_opcode_received) begin
+          host_analysis_port.write(host_item);
+          host_item.mon_item_complete = 1;
+        end
       end
-    end
+      SpiModeTpm: begin
+        if (host_item.address_q.size > 0) begin
+          host_analysis_port.write(host_item);
+          wait(cfg.vif.csb[active_csb] == 1'b1);
+        end
+      end
+      default: ; // do nothing, in SpiModeGeneric, it writes to fifo for each byte
+    endcase
   endtask : collect_trans
 
   virtual protected task collect_curr_trans();
-    fork
-      begin: isolation_thread
-        fork
-          begin: csb_deassert_thread
-            wait(cfg.vif.csb[cfg.csb_sel] == 1'b1);
-          end
-          begin: sample_thread
-            // for mode 1 and 3, get the leading edges out of the way
-            cfg.wait_sck_edge(LeadingEdge);
-            forever begin
-              bit [7:0] host_byte;    // from sio
-              bit [7:0] device_byte;  // from sio
-              int       which_bit_h;
-              int       which_bit_d;
-              int       data_shift;
-              bit [3:0] num_samples;
-              if (cfg.partial_byte == 1) begin
-                num_samples = cfg.bits_to_transfer;
-              end else begin
-                num_samples = 8;
-              end
+    // for mode 1 and 3, get the leading edges out of the way
+    cfg.wait_sck_edge(LeadingEdge, active_csb);
+    forever begin : loop_forever
+      logic [7:0] host_byte;    // from sio
+      logic [7:0] device_byte;  // from sio
+      int       which_bit_h;
+      int       which_bit_d;
+      int       data_shift;
+      bit [3:0] num_samples;
 
-              case(cmd)
-              CmdOnly, ReadStd, WriteStd: begin
-                data_shift = 1;
-              end
-              ReadDual, WriteDual: begin
-                data_shift = 2;
-              end
-              ReadQuad, WriteQuad: begin
-                data_shift = 4;
-              end
-              default: begin
-                data_shift = 1;
-              end
-              endcase
+      host_item.csb_sel = active_csb;
+      device_item.csb_sel = active_csb;
 
-              for (int i = 0; i < num_samples; i = i + data_shift) begin
-                // wait for the sampling edge
-                cfg.wait_sck_edge(SamplingEdge);
-                // check sio not x or z
-                if (cfg.en_monitor_checks) begin
-                  `DV_CHECK_CASE_NE(cfg.vif.sio[3:0], 4'bxxxx)
-                  `DV_CHECK_CASE_NE(cfg.vif.sio[3:0], 4'bxxxx)
-                end
-
-                which_bit_h = cfg.host_bit_dir ? i : 7 - i;
-                which_bit_d = cfg.device_bit_dir ? i : 7 - i;
-              case(cmd)
-              CmdOnly, ReadStd, WriteStd: begin
-                // sample sio[0] for tx
-                host_byte[which_bit_h] = cfg.vif.sio[0];
-                // sample sio[1] for rx
-                device_byte[which_bit_d] = cfg.vif.sio[1];
-              end // cmdonly,readstd,writestd
-              ReadDual, WriteDual: begin
-                // sample sio[0] sio[1] for tx bidir
-                host_byte[which_bit_h] = cfg.vif.sio[1];
-                host_byte[which_bit_h-1] = cfg.vif.sio[0];
-                // sample sio[0] sio[1] for rx bidir
-                device_byte[which_bit_d] = cfg.vif.sio[1];
-                device_byte[which_bit_d-1] = cfg.vif.sio[0];
-              end // ReadDual,WriteDual
-              ReadQuad, WriteQuad: begin
-                // sample sio[0] sio[1] sio[2] sio[3] for tx bidir
-                host_byte[which_bit_h] = cfg.vif.sio[3];
-                host_byte[which_bit_h-1] = cfg.vif.sio[2];
-                host_byte[which_bit_h-2] = cfg.vif.sio[1];
-                host_byte[which_bit_h-3] = cfg.vif.sio[0];
-                // sample sio[0] sio[1] sio[2] sio[3] for rx bidir
-                device_byte[which_bit_d] = cfg.vif.sio[3];
-                device_byte[which_bit_d-1] = cfg.vif.sio[2];
-                device_byte[which_bit_d-2] = cfg.vif.sio[1];
-                device_byte[which_bit_d-3] = cfg.vif.sio[0];
-              end // ReadQuad,WriteQuad
-              default: begin
-                // sample sio[0] for tx
-                host_byte[which_bit_h] = cfg.vif.sio[0];
-                // sample sio[1] for rx
-                device_byte[which_bit_d] = cfg.vif.sio[1];
-              end
-              endcase
-
-                cfg.vif.host_bit  = which_bit_h;
-                cfg.vif.host_byte = host_byte;
-               // sending 7 bits will be padded with 0 for the last bit
-                if (i == 6 && num_samples == 7) begin
-                  which_bit_d = cfg.device_bit_dir ? 7 : 0;
-                  device_byte[which_bit_d] = 0;
-                end
-                cfg.vif.device_bit = which_bit_d;
-                cfg.vif.device_byte = device_byte;
-              end
-
-              // sending less than 7 bits will not be captured, byte to be re-sent
-              if (num_samples >= 7) begin
-                host_item.data.push_back(host_byte);
-                device_item.data.push_back(device_byte);
-              end
-              // sending transactions when collect a word data
-              if (host_item.data.size == cfg.num_bytes_per_trans_in_mon &&
-                  device_item.data.size == cfg.num_bytes_per_trans_in_mon) begin
-              if (host_item.first_byte == 1 && cfg.decode_commands == 1)  begin
-                cmdtmp = spi_cmd_e'(host_byte);
-                `uvm_info(`gfn, $sformatf("spi_monitor: cmdtmp \n%0h", cmdtmp), UVM_DEBUG)
-                 end
-                 cmd_byte++;
-              if (cmd_byte == cfg.num_bytes_per_trans_in_mon)begin
-                 cmd = cmdtmp;
-                `uvm_info(`gfn, $sformatf("spi_monitor: cmd \n%0h", cmd), UVM_DEBUG)
-                 end
-                `uvm_info(`gfn, $sformatf("spi_monitor: host packet:\n%0s", host_item.sprint()),
-                          UVM_DEBUG)
-                `uvm_info(`gfn, $sformatf("spi_monitor: device packet:\n%0s", device_item.sprint()),
-                          UVM_DEBUG)
-                `downcast(host_clone, host_item.clone());
-                `downcast(device_clone, device_item.clone());
-                host_analysis_port.write(host_clone);
-                device_analysis_port.write(device_clone);
-                // write to fifo for re-active env
-                req_analysis_port.write(host_clone);
-                rsp_analysis_port.write(device_clone);
-                // clean items
-                host_item   = spi_item::type_id::create("host_item", this);
-                device_item = spi_item::type_id::create("device_item", this);
-              end
-            end // forever
-          end: sample_thread
-        join_any
-        disable fork;
+      if (cfg.partial_byte == 1) begin
+        num_samples = cfg.bits_to_transfer;
+      end else begin
+        num_samples = 8;
       end
-    join
+
+      case (cmd)
+        CmdOnly, ReadStd, WriteStd: begin
+          data_shift = 1;
+        end
+        ReadDual, WriteDual: begin
+          data_shift = 2;
+        end
+        ReadQuad, WriteQuad: begin
+          data_shift = 4;
+        end
+        default: begin
+          data_shift = 1;
+        end
+      endcase
+
+      for (int i = 0; i < num_samples; i = i + data_shift) begin : loop_num_samples
+        // wait for the sampling edge
+        cfg.wait_sck_edge(SamplingEdge, active_csb);
+        // check sio not x
+        if (cfg.en_monitor_checks) begin
+          foreach (cfg.vif.sio[i]) `DV_CHECK_CASE_NE(cfg.vif.sio[i], 1'bx)
+        end
+
+        which_bit_h = cfg.host_bit_dir ? i : 7 - i;
+        which_bit_d = cfg.device_bit_dir ? i : 7 - i;
+        case (cmd)
+          CmdOnly, ReadStd, WriteStd: begin
+            // sample sio[0] for tx
+            host_byte[which_bit_h] = cfg.vif.sio[0];
+            // sample sio[1] for rx
+            device_byte[which_bit_d] = cfg.vif.sio[1];
+          end // cmdonly,readstd,writestd
+          ReadDual, WriteDual: begin
+            // TODO, update this section if host_bit_dir can be 0
+            `DV_CHECK_EQ(cfg.host_bit_dir, 0, "currently assume host_bit_dir is 0")
+            `DV_CHECK_EQ(cfg.device_bit_dir, 0, "currently assume device_bit_dir is 0")
+            // sample sio[0] sio[1] for tx bidir
+            host_byte[which_bit_h -: 2] = cfg.vif.sio[1:0];
+            // sample sio[0] sio[1] for rx bidir
+            device_byte[which_bit_h -: 2] = cfg.vif.sio[1:0];
+          end // ReadDual,WriteDual
+          ReadQuad, WriteQuad: begin
+            // TODO, update this section if host_bit_dir can be 0
+            `DV_CHECK_EQ(cfg.host_bit_dir, 0, "currently assume host_bit_dir is 0")
+            `DV_CHECK_EQ(cfg.device_bit_dir, 0, "currently assume device_bit_dir is 0")
+            // sample sio[0] sio[1] sio[2] sio[3] for tx bidir
+            host_byte[which_bit_h -: 4] = cfg.vif.sio[3:0];
+            // sample sio[0] sio[1] sio[2] sio[3] for rx bidir
+            device_byte[which_bit_h -: 4] = cfg.vif.sio[3:0];
+          end // ReadQuad,WriteQuad
+          default: begin
+            // sample sio[0] for tx
+            host_byte[which_bit_h] = cfg.vif.sio[0];
+            // sample sio[1] for rx
+            device_byte[which_bit_d] = cfg.vif.sio[1];
+          end
+        endcase
+
+        cfg.vif.host_bit  = which_bit_h;
+        cfg.vif.host_byte = host_byte;
+        // sending 7 bits will be padded with 0 for the last bit
+        if (i == 6 && num_samples == 7) begin
+          which_bit_d = cfg.device_bit_dir ? 7 : 0;
+          device_byte[which_bit_d] = 0;
+        end
+        cfg.vif.device_bit = which_bit_d;
+        cfg.vif.device_byte = device_byte;
+      end : loop_num_samples
+
+      // sending less than 7 bits will not be captured, byte to be re-sent
+      if (num_samples >= 7) begin
+        generic_mode_host_byte_q.push_back(host_byte);
+        generic_mode_device_byte_q.push_back(device_byte);
+      end
+      // in generic mode, these queue should always have same size
+      `DV_CHECK_EQ_FATAL(generic_mode_host_byte_q.size, generic_mode_device_byte_q.size)
+      `DV_CHECK_LE(generic_mode_host_byte_q.size, cfg.num_bytes_per_trans_in_mon)
+      // sending transactions when collect a word data
+      if (generic_mode_host_byte_q.size == cfg.num_bytes_per_trans_in_mon) begin
+        host_item.data = generic_mode_host_byte_q;
+        device_item.data = generic_mode_device_byte_q;
+        generic_mode_host_byte_q.delete();
+        generic_mode_device_byte_q.delete();
+        if (host_item.first_byte == 1 && cfg.decode_commands == 1)  begin
+          cmdtmp = spi_cmd_e'(host_byte);
+          `uvm_info(`gfn, $sformatf("spi_monitor: cmdtmp \n%0h", cmdtmp), UVM_DEBUG)
+        end
+        cmd_byte++;
+        if (cmd_byte == cfg.spi_cmd_width)begin
+          cmd = cmdtmp;
+          `uvm_info(`gfn, $sformatf("spi_monitor: cmd \n%0h", cmd), UVM_DEBUG)
+        end
+        `uvm_info(`gfn, $sformatf("spi_monitor: host packet:\n%0s", host_item.sprint()),
+                  UVM_DEBUG)
+        `uvm_info(`gfn, $sformatf("spi_monitor: device packet:\n%0s", device_item.sprint()),
+                  UVM_DEBUG)
+        `downcast(host_clone, host_item.clone());
+        `downcast(device_clone, device_item.clone());
+        host_analysis_port.write(host_clone);
+        device_analysis_port.write(device_clone);
+        // write to fifo for re-active env
+        req_analysis_port.write(host_clone);
+        rsp_analysis_port.write(device_clone);
+        // clean items
+        host_item   = spi_item::type_id::create("host_item", this);
+        device_item = spi_item::type_id::create("device_item", this);
+      end
+    end : loop_forever
   endtask : collect_curr_trans
 
-  virtual protected task collect_flash_trans();
-    fork
-      begin: isolation_thread
-        spi_item item = spi_item::type_id::create("host_item", this);
-        bit opcode_received;
-        fork
-          begin: csb_deassert_thread
-            wait(cfg.vif.csb[cfg.csb_sel] == 1'b1);
-          end
-          begin: sample_thread
-            int addr_bytes;
-            opcode_received = 0;
-            item.item_type = SpiFlashTrans;
-            // for mode 1 and 3, get the leading edges out of the way
-            cfg.wait_sck_edge(LeadingEdge);
+  virtual protected task collect_flash_trans(spi_item item, ref bit flash_opcode_received);
+    int num_addr_bytes;
+    flash_opcode_received = 0;
+    // for mode 1 and 3, get the leading edges out of the way
+    cfg.wait_sck_edge(LeadingEdge, active_csb);
 
-            // first byte is opcode. opcode or address is always sent on single mode
-            sample_flash_one_byte_data(.num_lanes(1), .is_device_rsp(0), .data(item.opcode));
-            opcode_received = 1;
-            cfg.extract_cmd_info_from_opcode(item.opcode,
-                // output
-                addr_bytes, item.write_command, item.num_lanes, item.dummy_cycles);
-            `uvm_info(`gfn, $sformatf("sampled flash opcode: 0x%0h", item.opcode), UVM_MEDIUM)
+    // first byte is opcode. opcode or address is always sent on single mode
+    sample_and_check_byte(.num_lanes(1), .is_device_rsp(0),
+                          .data(item.opcode), .check_data_not_z(1));
+    flash_opcode_received = 1;
+    cfg.extract_cmd_info_from_opcode(item.opcode,
+        // output
+        num_addr_bytes, item.write_command, item.num_lanes, item.dummy_cycles);
+    `uvm_info(`gfn, $sformatf("sampled flash opcode: 0x%0h", item.opcode), UVM_MEDIUM)
 
-            sample_flash_address(addr_bytes, item.address_q);
+    sample_address(num_addr_bytes, item.address_q);
 
-            repeat (item.dummy_cycles) begin
-              cfg.wait_sck_edge(SamplingEdge);
-            end
-            req_analysis_port.write(item);
+    repeat (item.dummy_cycles) begin
+      cfg.wait_sck_edge(SamplingEdge, active_csb);
+    end
+    req_analysis_port.write(item);
 
-            forever begin
-              byte byte_data;
-              sample_flash_one_byte_data(.num_lanes(item.num_lanes),
-                                         .is_device_rsp(!item.write_command),
-                                         .data(byte_data));
-              item.payload_q.push_back(byte_data);
-            end
-          end: sample_thread
-        join_any
-        disable fork;
-
-        // only send out the item when opcode is fully received, otherwise,
-        // it's consider dropped
-        if (opcode_received) host_analysis_port.write(item);
-      end
-    join
+    forever begin
+      logic[7:0] byte_data;
+      sample_and_check_byte(item.num_lanes, !item.write_command, byte_data);
+      item.payload_q.push_back(byte_data);
+    end
   endtask : collect_flash_trans
 
-  virtual task sample_flash_one_byte_data(input int num_lanes, input bit is_device_rsp,
-                                          output bit[7:0] data);
-    int which_bit = 8;
-    for (int i = 0; i < 8; i += num_lanes) begin
-      cfg.wait_sck_edge(SamplingEdge);
-      case(num_lanes)
-        1: data[--which_bit] = is_device_rsp ? cfg.vif.sio[1] : cfg.vif.sio[0];
-        2: begin
-          data[--which_bit] = cfg.vif.sio[1];
-          data[--which_bit] = cfg.vif.sio[0];
+  virtual protected task collect_tpm_trans(spi_item item);
+    uint size;
+    bit[7:0] cmd, tpm_rsp;
+
+    cfg.wait_sck_edge(LeadingEdge, active_csb);
+    // read the 1st byte to get TPM direction and size
+    sample_and_check_byte(.num_lanes(1), .is_device_rsp(0), .data(cmd), .check_data_not_z(1));
+    decode_tpm_cmd(cmd, item.write_command, size);
+    if (!item.write_command) item.read_size = size;
+    `uvm_info(`gfn, $sformatf("Received TPM command: 0x%0x", cmd), UVM_MEDIUM)
+    // read 3 bytes address
+    fork
+      begin : tpm_sio_0
+        sample_address(.num_bytes(TPM_ADDR_WIDTH_BYTE), .byte_addr_q(item.address_q));
+      end
+      begin : tpm_sio_1
+        bit[7:0] data;
+        // check the data of the last byte is 0, which indicates SPI device in the WAIT state
+        for (int i = 0; i < TPM_ADDR_WIDTH_BYTE; i++) begin
+          bit last_byte = (i == TPM_ADDR_WIDTH_BYTE - 1);
+          sample_and_check_byte(.num_lanes(1), .is_device_rsp(1), .data(data),
+                                .check_data_not_z(last_byte));
         end
-        4: begin
-          data[--which_bit] = cfg.vif.sio[3];
-          data[--which_bit] = cfg.vif.sio[2];
-          data[--which_bit] = cfg.vif.sio[1];
-          data[--which_bit] = cfg.vif.sio[0];
-        end
-        default: `uvm_fatal(`gfn, $sformatf("Unsupported lanes num 0x%0h", num_lanes))
-      endcase
+        `DV_CHECK_EQ(data, 0)
+      end
+    join
+    `uvm_info(`gfn, $sformatf("Received TPM addr: %p", item.address_q), UVM_MEDIUM)
+    req_analysis_port.write(item);
+
+    // poll TPM_START
+    `DV_SPINWAIT(
+      do begin
+        cfg.read_byte(.num_lanes(1), .is_device_rsp(1), .csb_id(active_csb), .data(tpm_rsp));
+        // current TPM design could only return these values
+        `DV_CHECK_FATAL(tpm_rsp inside {TPM_START, TPM_WAIT, '1})
+      end while (tpm_rsp[0] == TPM_WAIT);
+      , , TPM_START_MAX_WAIT_TIME_NS)
+    `uvm_info(`gfn, "Received TPM START", UVM_MEDIUM)
+
+    // sample data
+    for (int i = 0; i < size; i++) begin
+      sample_and_check_byte(.num_lanes(1), .is_device_rsp(!item.write_command),
+                            .data(item.data[i]), .check_data_not_z(1));
+      `uvm_info(`gfn, $sformatf("collect %s data for TPM, idx %0d: 0x%0x",
+                              item.write_command ? "write" : "read", i, item.data[i]), UVM_MEDIUM)
     end
-    `DV_CHECK_EQ(which_bit, 0)
-    `uvm_info(`gfn, $sformatf("sampled one byte data for flash: 0x%0h", data), UVM_MEDIUM)
-  endtask : sample_flash_one_byte_data
+  endtask
 
   // address is 3 or 4 bytes
-  virtual task sample_flash_address(input int num_bytes, output bit[7:0] byte_addr_q[$]);
+  virtual task sample_address(input int num_bytes, output bit[7:0] byte_addr_q[$]);
     for (int i = 0; i < num_bytes; i++) begin
       byte addr;
-      sample_flash_one_byte_data(.num_lanes(1), .is_device_rsp(0), .data(addr));
+      sample_and_check_byte(.num_lanes(1), .is_device_rsp(0),
+                            .data(addr), .check_data_not_z(1));
       byte_addr_q.push_back(addr);
     end
-    `uvm_info(`gfn, $sformatf("sampled flash addr: %p", byte_addr_q), UVM_MEDIUM)
-  endtask : sample_flash_address
+    `uvm_info(`gfn, $sformatf("sampled flash addr: %p", byte_addr_q), UVM_HIGH)
+  endtask : sample_address
+
+  task sample_and_check_byte(input int num_lanes,
+                             input bit is_device_rsp,
+                             output logic [7:0] data,
+                             input bit check_data_not_z = 0);
+    cfg.read_byte(num_lanes, is_device_rsp, active_csb, data);
+
+    if (cfg.en_monitor_checks) begin
+      foreach (data[i]) begin
+        `DV_CHECK_CASE_NE(data[i], 1'bx)
+        if (check_data_not_z) `DV_CHECK_CASE_NE(data[i], 1'bz)
+      end
+    end
+  endtask
 
   virtual task monitor_ready_to_end();
-    ok_to_end = cfg.vif.csb[cfg.csb_sel];
+    ok_to_end = 1;
     forever begin
-      @(cfg.vif.csb[cfg.csb_sel]);
-      ok_to_end = cfg.vif.csb[cfg.csb_sel];
+      @(cfg.vif.csb);
+      ok_to_end = (cfg.vif.csb == '1);
     end
   endtask : monitor_ready_to_end
 
diff --git a/hw/dv/sv/str_utils/README.md b/hw/dv/sv/str_utils/README.md
new file mode 100644
index 0000000..b13afd1
--- /dev/null
+++ b/hw/dv/sv/str_utils/README.md
@@ -0,0 +1,6 @@
+# str_utils_pkg
+
+This package provides some basic string and path manipulation utility functions that
+can be used across the project. It can be imported in non-UVM testbenches as
+well. Please see the package for the list of available functions and their
+descriptions.
diff --git a/hw/dv/sv/str_utils/doc/index.md b/hw/dv/sv/str_utils/doc/index.md
deleted file mode 100644
index ea2fbff..0000000
--- a/hw/dv/sv/str_utils/doc/index.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-title: "str_utils_pkg"
----
-
-This package provides some basic string and path manipulation utility functions that
-can be used across the project. It can be imported in non-UVM testbenches as
-well. Please see the package for the list of available functions and their
-descriptions.
diff --git a/hw/dv/sv/str_utils/str_utils_pkg.sv b/hw/dv/sv/str_utils/str_utils_pkg.sv
index b82c03e..dfd73a4 100644
--- a/hw/dv/sv/str_utils/str_utils_pkg.sv
+++ b/hw/dv/sv/str_utils/str_utils_pkg.sv
@@ -18,6 +18,16 @@
     return 0;
   endfunction : str_has_substr
 
+  // Returns 1 when string 's' starts with substring 'sub'.
+  function automatic bit str_starts_with(string s, string sub);
+    return s.substr(0, sub.len() - 1) == sub;
+  endfunction
+
+  // Returns 1 when string 's' ends with substring 'sub'.
+  function automatic bit str_ends_with(string s, string sub);
+    return s.substr(s.len() - sub.len(), s.len() - 1) == sub;
+  endfunction
+
   // Returns the index of first occurrence of string 'sub' within string 's''s given index range.
   // Returns -1 otherwise.
   function automatic int str_find(string s, string sub, int range_lo = 0, int range_hi = -1);
@@ -129,6 +139,31 @@
     return str;
   endfunction : str_join
 
+  // Cuts sections (such as comments) from string s starting and ending with provided substrings.
+  //
+  // start_delim: substring representing the start of section to be removed (e.g. "//", "/*").
+  // end_delim: substring representing the end of the section to be removed (e.g. "\n", "*/").
+  // remove_start_delim: include the start_delim substring in the removal.
+  // remove_end_delim: include the end_delim substring in the removal.
+  function automatic string str_remove_sections(string s,
+                                                string start_delim,
+                                                string end_delim,
+                                                bit remove_start_delim = 1,
+                                                bit remove_end_delim = 1);
+      int start_idx, end_idx;
+      start_idx = str_utils_pkg::str_find(s, start_delim);
+      while (start_idx != -1) begin
+        int rm_start_idx, rm_end_idx;
+        rm_start_idx = remove_start_delim ? start_idx : start_idx + start_delim.len() - 1;
+        end_idx = str_utils_pkg::str_find(s, end_delim, .range_lo(start_idx + start_delim.len()));
+        if (end_idx == -1) break;
+        rm_end_idx = remove_end_delim ? end_idx + end_delim.len() - 1 : end_idx - 1;
+        s = str_utils_pkg::str_replace(s, s.substr(rm_start_idx, rm_end_idx), "");
+        start_idx = str_utils_pkg::str_find(s, start_delim, .range_lo(end_idx + end_delim.len()));
+      end
+      return s;
+  endfunction
+
   // Converts a string to an array of bytes.
   function automatic void str_to_bytes(string s, output byte bytes[]);
     bytes = new[s.len()];
diff --git a/hw/dv/sv/sw_logger_if/sw_logger_if.sv b/hw/dv/sv/sw_logger_if/sw_logger_if.sv
index 823820d..033484e 100644
--- a/hw/dv/sv/sw_logger_if/sw_logger_if.sv
+++ b/hw/dv/sv/sw_logger_if/sw_logger_if.sv
@@ -94,6 +94,7 @@
   // Indicate when the log was printed and what was the final string.
   event  printed_log_event;
   arg_t  printed_log;
+  arg_t  printed_arg [];
 
   // Sets the sw_name with the provided path.
   //
@@ -107,10 +108,9 @@
     string sw_dir;
     string sw_basename;
     if (_ready) `dv_fatal("This function cannot be called after calling ready()")
-    sw_dir = str_utils_pkg::str_path_dirname(sw_image);
     sw_basename = str_utils_pkg::str_path_basename(.filename(sw_image), .drop_extn(1'b1));
-    sw_log_db_files[sw_basename] = {sw_dir, "/", sw_basename, ".logs.txt"};
-    sw_rodata_db_files[sw_basename] = {sw_dir, "/", sw_basename, ".rodata.txt"};
+    sw_log_db_files[sw_basename] = {sw_basename, ".logs.txt"};
+    sw_rodata_db_files[sw_basename] = {sw_basename, ".rodata.txt"};
   endfunction
 
   // signal to indicate that this monitor is good to go - all initializations are done
@@ -123,15 +123,17 @@
   /********************/
   initial begin
     wait(_ready);
-    if (parse_sw_log_file()) begin
-      if (write_sw_logs_to_file) begin
-        sw_logs_output_file = $sformatf("%m.log");
-        sw_logs_output_fd = $fopen(sw_logs_output_file, "w");
+    if (enable) begin
+      if (parse_sw_log_file()) begin
+        if (write_sw_logs_to_file) begin
+          sw_logs_output_file = $sformatf("%m.log");
+          sw_logs_output_fd = $fopen(sw_logs_output_file, "w");
+        end
+        fork
+          get_addr_data_from_bus();
+          construct_log_and_print();
+        join_none
       end
-      fork
-        get_addr_data_from_bus();
-        construct_log_and_print();
-      join_none
     end
   end
 
@@ -151,7 +153,10 @@
     foreach (sw_log_db_files[sw]) begin
       int fd;
       fd = $fopen(sw_log_db_files[sw], "r");
-      if (!fd) continue;
+      if (!fd) begin
+        `dv_info($sformatf("Failed to open sw log db file %s.", sw_log_db_files[sw]))
+        return 1'b0;
+      end
 
       while (!$feof(fd)) begin
         string        field;
@@ -527,6 +532,7 @@
     end
 
     printed_log = sw_log.format;
+    printed_arg = sw_log.arg;
     ->printed_log_event;
   endfunction
 
diff --git a/hw/dv/sv/sw_test_status/sw_test_status_if.sv b/hw/dv/sv/sw_test_status/sw_test_status_if.sv
index 8b2ba2c..fec54e1 100644
--- a/hw/dv/sv/sw_test_status/sw_test_status_if.sv
+++ b/hw/dv/sv/sw_test_status/sw_test_status_if.sv
@@ -6,6 +6,8 @@
   parameter int AddrWidth = 32
 ) (
   input logic clk_i,
+  input logic rst_ni,               // Under reset.
+  input logic fetch_en,             // Fetch enabled.
   input logic wr_valid,             // Qualified write access.
   input logic [AddrWidth-1:0] addr, // Incoming addr.
   input logic [15:0] data           // Incoming data.
@@ -42,7 +44,15 @@
   endfunction
 
   always @(posedge clk_i) begin
-    if (data_valid) begin
+    if (!rst_ni) begin
+      sw_test_status_prev = sw_test_status;
+      sw_test_status = SwTestStatusUnderReset;
+    end
+    else if (sw_test_status == SwTestStatusUnderReset && fetch_en) begin
+      sw_test_status_prev = sw_test_status;
+      sw_test_status = SwTestStatusBooted;
+    end
+    else if (data_valid) begin
       sw_test_status_prev = sw_test_status;
       sw_test_status = sw_test_status_e'(data);
 
diff --git a/hw/dv/sv/sw_test_status/sw_test_status_pkg.sv b/hw/dv/sv/sw_test_status/sw_test_status_pkg.sv
index e844f18..f13a56e 100644
--- a/hw/dv/sv/sw_test_status/sw_test_status_pkg.sv
+++ b/hw/dv/sv/sw_test_status/sw_test_status_pkg.sv
@@ -6,13 +6,14 @@
 
   // Enum to indicate the SW test status.
   typedef enum bit [15:0] {
-    SwTestStatusUnderReset  = 16'h0000, // 'boot', CPU is under reset.
-    SwTestStatusBooted      = 16'hb004, // 'boot', CPU has booted.
-    SwTestStatusInBootRom   = 16'hb090, // 'bogo', BOotrom GO, SW has entered the boot rom code.
-    SwTestStatusInTest      = 16'h4354, // 'test', SW has entered the test code.
-    SwTestStatusInWfi       = 16'h1d1e, // 'idle', CPU has entered WFI state.
-    SwTestStatusPassed      = 16'h900d, // 'good', SW test has passed.
-    SwTestStatusFailed      = 16'hbaad  // 'baad', SW test has failed.
+    SwTestStatusUnderReset    = 16'h0000, // 'boot', CPU is under reset.
+    SwTestStatusBooted        = 16'hb004, // 'boot', CPU has booted.
+    SwTestStatusInBootRom     = 16'hb090, // 'bogo', BOotrom GO, SW has entered the boot rom code.
+    SwTestStatusInBootRomHalt = 16'hb057, // 'bost', BOotrom STop, ROM_EXEC_EN=0.
+    SwTestStatusInTest        = 16'h4354, // 'test', SW has entered the test code.
+    SwTestStatusInWfi         = 16'h1d1e, // 'idle', CPU has entered WFI state.
+    SwTestStatusPassed        = 16'h900d, // 'good', SW test has passed.
+    SwTestStatusFailed        = 16'hbaad  // 'baad', SW test has failed.
   } sw_test_status_e;
 
 endpackage
diff --git a/hw/dv/sv/test_vectors/README.md b/hw/dv/sv/test_vectors/README.md
new file mode 100644
index 0000000..eb40626
--- /dev/null
+++ b/hw/dv/sv/test_vectors/README.md
@@ -0,0 +1,50 @@
+# Test Vectors
+
+## test_vectors_pkg.sv
+This class read a list of vector files and parse the useful information to
+uvm_sequences.
+The following is a list of common properties and methods:
+* **_file_list**: A string list of file names grouped by tested functionality.
+* **test_vectors_t**: A structure of parsed information.
+* **str_to_bytes**: A method to parse string to a vector of bytes.
+* **get_test_vectors_path**: A method to get file directory from the run option,
+  and concatenate with input file name. Return the full file path.
+* **open_file**: A method to open a file with specific path, and return a open
+  file handle.
+* **parse_sha**: A method to parse SHA vectors files. Return an array of
+  test_vectors_t.
+
+## SHA256 vectors
+The test vector files inside of `vectors/sha/sha256/` are downloaded from the
+[NIST website](https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Secure-Hashing#shavs).
+
+## SHA3 vectors
+Test vector files for the 224, 256, 384, and 512 bit variants of SHA3 found at
+`vectors/sha/sha3-<224/256/384/512>` are downloaded from the [NIST
+website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/sha3/sha-3bytetestvectors.zip).
+
+## HMAC vectors
+Files inside of `vectors/hmac/` contain test vectors extracted from [IETF RFC 4868](https://tools.ietf.org/html/rfc4868).
+
+## SHAKE vectors
+Test vector files for the SHA3 Extendable Output Functions in `vectors/xof/shake` are
+downloaded from the [NIST
+website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/shs/shabytetestvectors.zip).
+
+## cSHAKE vectors
+Test vectors for the Customizable SHAKE functions are taken from the [NIST
+website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf).
+These vectors are found in `vectors/xof/cshake/`.
+
+## KMAC vectors
+Test vectors for KMAC algorithm are taken from NIST website for both the
+[XOF](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf)
+and
+[non-XOF](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMACXOF_samples.pdf)
+variants.
+These vectors are found in `vectors/xof/kmac/`.
+
+## AES vectors
+Test vectors for AES can be found under hw/ip/aes/dv/test_vectors
+Test vectors for AES algorithm are taken from the
+[NIST website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ModesA_All.pdf)
diff --git a/hw/dv/sv/test_vectors/doc/index.md b/hw/dv/sv/test_vectors/doc/index.md
deleted file mode 100644
index d92380c..0000000
--- a/hw/dv/sv/test_vectors/doc/index.md
+++ /dev/null
@@ -1,52 +0,0 @@
----
-title: "Test Vectors"
----
-
-## test_vectors_pkg.sv
-This class read a list of vector files and parse the useful information to
-uvm_sequences.
-The following is a list of common properties and methods:
-* **_file_list**: A string list of file names grouped by tested functionality.
-* **test_vectors_t**: A structure of parsed information.
-* **str_to_bytes**: A method to parse string to a vector of bytes.
-* **get_test_vectors_path**: A method to get file directory from the run option,
-  and concatenate with input file name. Return the full file path.
-* **open_file**: A method to open a file with specific path, and return a open
-  file handle.
-* **parse_sha**: A method to parse SHA vectors files. Return an array of
-  test_vectors_t.
-
-## SHA256 vectors
-The test vector files inside of `vectors/sha/sha256/` are downloaded from the
-[NIST website](https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Secure-Hashing#shavs).
-
-## SHA3 vectors
-Test vector files for the 224, 256, 384, and 512 bit variants of SHA3 found at
-`vectors/sha/sha3-<224/256/384/512>` are downloaded from the [NIST
-website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/sha3/sha-3bytetestvectors.zip).
-
-## HMAC vectors
-Files inside of `vectors/hmac/` contain test vectors extracted from [IETF RFC 4868](https://tools.ietf.org/html/rfc4868).
-
-## SHAKE vectors
-Test vector files for the SHA3 Extendable Output Functions in `vectors/xof/shake` are
-downloaded from the [NIST
-website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/shs/shabytetestvectors.zip).
-
-## cSHAKE vectors
-Test vectors for the Customizable SHAKE functions are taken from the [NIST
-website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/cSHAKE_samples.pdf).
-These vectors are found in `vectors/xof/cshake/`.
-
-## KMAC vectors
-Test vectors for KMAC algorithm are taken from NIST website for both the
-[XOF](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMAC_samples.pdf)
-and
-[non-XOF](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/KMACXOF_samples.pdf)
-variants.
-These vectors are found in `vectors/xof/kmac/`.
-
-## AES vectors
-Test vectors for AES can be found under hw/ip/aes/dv/test_vectors
-Test vectors for AES algorithm are taken from the
-[NIST website](https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_ModesA_All.pdf)
diff --git a/hw/dv/sv/tl_agent/README.md b/hw/dv/sv/tl_agent/README.md
new file mode 100644
index 0000000..844d014
--- /dev/null
+++ b/hw/dv/sv/tl_agent/README.md
@@ -0,0 +1,3 @@
+# TileLink UVM Agent
+
+**TODO**
diff --git a/hw/dv/sv/tl_agent/doc/index.md b/hw/dv/sv/tl_agent/doc/index.md
deleted file mode 100644
index f3fb3ed..0000000
--- a/hw/dv/sv/tl_agent/doc/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: "TileLink UVM Agent"
----
diff --git a/hw/dv/sv/tl_agent/tl_agent_cfg.sv b/hw/dv/sv/tl_agent/tl_agent_cfg.sv
index 5956f12..671d6a0 100644
--- a/hw/dv/sv/tl_agent/tl_agent_cfg.sv
+++ b/hw/dv/sv/tl_agent/tl_agent_cfg.sv
@@ -134,6 +134,20 @@
   // Added to allow some TLUL checks to be ignored on certain scenarios.
   bit check_tl_errs = 1'b1;
 
+  // Invalidate drive option
+  // Setting these to '1', make driver send 'x' during invalidate cycle.
+  // Setting these to '0', make driver send 'random value' during invalidate cycle.
+  rand bit invalidate_a_x;
+  rand bit invalidate_d_x;
+
+  constraint invalidate_a_channel_op_c {
+    invalidate_a_x dist { 1 := 7, 0 := 3};
+  }
+  constraint invalidate_d_channel_op_c {
+    invalidate_d_x dist { 1 := 7, 0 := 3};
+  }
+
+
   `uvm_object_utils_begin(tl_agent_cfg)
     `uvm_field_int(max_outstanding_req,   UVM_DEFAULT)
     `uvm_field_enum(tl_level_e, tl_level, UVM_DEFAULT)
diff --git a/hw/dv/sv/tl_agent/tl_agent_cov.sv b/hw/dv/sv/tl_agent/tl_agent_cov.sv
index cce9639..c995475 100644
--- a/hw/dv/sv/tl_agent/tl_agent_cov.sv
+++ b/hw/dv/sv/tl_agent/tl_agent_cov.sv
@@ -70,7 +70,8 @@
 
   // knob to create m_outstanding_item_w_same_addr_cov_obj, even design supports more than
   // 1 outstanding items, but may not support they use the same address
-  // TODO: may need to disable it for spi_device and hmac even they support 2 oustanding items
+  // TODO(#16840): may need to disable it for spi_device and hmac even they support 2 oustanding
+  // items
   bit en_cov_outstanding_item_w_same_addr = 1;
 
   // item coverage, call sample(item) at the end of transaction to sample them
diff --git a/hw/dv/sv/tl_agent/tl_device_driver.sv b/hw/dv/sv/tl_agent/tl_device_driver.sv
index 7e27ccc..533c65d 100644
--- a/hw/dv/sv/tl_agent/tl_device_driver.sv
+++ b/hw/dv/sv/tl_agent/tl_device_driver.sv
@@ -23,6 +23,8 @@
 
   // reset signals every time reset occurs.
   virtual task reset_signals();
+    invalidate_d_channel();
+    cfg.vif.d2h_int.a_ready <= 1'b0;
     forever begin
       @(negedge cfg.vif.rst_n);
       invalidate_d_channel();
@@ -111,15 +113,22 @@
   endtask : d_channel_thread
 
   function void invalidate_d_channel();
-    cfg.vif.d2h_int.d_opcode <= tlul_pkg::tl_d_op_e'('x);
-    cfg.vif.d2h_int.d_param <= '{default:'x};
-    cfg.vif.d2h_int.d_size <= '{default:'x};
-    cfg.vif.d2h_int.d_source <= '{default:'x};
-    cfg.vif.d2h_int.d_sink <= '{default:'x};
-    cfg.vif.d2h_int.d_data <= '{default:'x};
-    cfg.vif.d2h_int.d_user <= '{default:'x};
-    cfg.vif.d2h_int.d_error <= 1'bx;
-    cfg.vif.d2h_int.d_valid <= 1'b0;
+    if (cfg.invalidate_d_x) begin
+      cfg.vif.d2h_int.d_opcode <= tlul_pkg::tl_d_op_e'('x);
+      cfg.vif.d2h_int.d_param <= '{default:'x};
+      cfg.vif.d2h_int.d_size <= '{default:'x};
+      cfg.vif.d2h_int.d_source <= '{default:'x};
+      cfg.vif.d2h_int.d_sink <= '{default:'x};
+      cfg.vif.d2h_int.d_data <= '{default:'x};
+      cfg.vif.d2h_int.d_user <= '{default:'x};
+      cfg.vif.d2h_int.d_error <= 1'bx;
+      cfg.vif.d2h_int.d_valid <= 1'b0;
+    end else begin // if (cfg.invalidate_d_x)
+      tlul_pkg::tl_d2h_t d2h;
+      `DV_CHECK_STD_RANDOMIZE_FATAL(d2h)
+      d2h.d_valid = 1'b0;
+      cfg.vif.d2h_int <= d2h;
+    end
   endfunction : invalidate_d_channel
 
 endclass
diff --git a/hw/dv/sv/tl_agent/tl_host_driver.sv b/hw/dv/sv/tl_agent/tl_host_driver.sv
index 524a08e..b65e37d 100644
--- a/hw/dv/sv/tl_agent/tl_host_driver.sv
+++ b/hw/dv/sv/tl_agent/tl_host_driver.sv
@@ -50,6 +50,8 @@
 
   // reset signals every time reset occurs.
   virtual task reset_signals();
+    invalidate_a_channel();
+    cfg.vif.h2d_int.d_ready <= 1'b0;
     forever begin
       @(negedge cfg.vif.rst_n);
       reset_asserted = 1'b1;
@@ -232,17 +234,24 @@
   endfunction
 
   function void invalidate_a_channel();
-    cfg.vif.h2d_int.a_opcode <= tlul_pkg::tl_a_op_e'('x);
-    cfg.vif.h2d_int.a_param <= '{default:'x};
-    cfg.vif.h2d_int.a_size <= '{default:'x};
-    cfg.vif.h2d_int.a_source <= '{default:'x};
-    cfg.vif.h2d_int.a_address <= '{default:'x};
-    cfg.vif.h2d_int.a_mask <= '{default:'x};
-    cfg.vif.h2d_int.a_data <= '{default:'x};
-    // The assignment to tl_type must have a cast since the LRM doesn't allow enum assignment of
-    // values not belonging to the enumeration set.
-    cfg.vif.h2d_int.a_user <= '{instr_type:prim_mubi_pkg::mubi4_t'('x), default:'x};
-    cfg.vif.h2d_int.a_valid <= 1'b0;
+    if (cfg.invalidate_a_x) begin
+      cfg.vif.h2d_int.a_opcode <= tlul_pkg::tl_a_op_e'('x);
+      cfg.vif.h2d_int.a_param <= '{default:'x};
+      cfg.vif.h2d_int.a_size <= '{default:'x};
+      cfg.vif.h2d_int.a_source <= '{default:'x};
+      cfg.vif.h2d_int.a_address <= '{default:'x};
+      cfg.vif.h2d_int.a_mask <= '{default:'x};
+      cfg.vif.h2d_int.a_data <= '{default:'x};
+      // The assignment to tl_type must have a cast since the LRM doesn't allow enum assignment of
+      // values not belonging to the enumeration set.
+      cfg.vif.h2d_int.a_user <= '{instr_type:prim_mubi_pkg::mubi4_t'('x), default:'x};
+      cfg.vif.h2d_int.a_valid <= 1'b0;
+    end else begin
+      tlul_pkg::tl_h2d_t h2d;
+      `DV_CHECK_STD_RANDOMIZE_FATAL(h2d)
+      h2d.a_valid = 1'b0;
+      cfg.vif.h2d_int <= h2d;
+    end
   endfunction : invalidate_a_channel
 
 endclass : tl_host_driver
diff --git a/hw/dv/sv/tl_agent/tl_monitor.sv b/hw/dv/sv/tl_agent/tl_monitor.sv
index eb6376a..c3f1581 100644
--- a/hw/dv/sv/tl_agent/tl_monitor.sv
+++ b/hw/dv/sv/tl_agent/tl_monitor.sv
@@ -6,8 +6,6 @@
 // ---------------------------------------------
 // TileLink interface monitor
 // ---------------------------------------------
-
-// TODO: Implement protocl check in the monitor
 class tl_monitor extends dv_base_monitor#(
     .ITEM_T (tl_seq_item),
     .CFG_T  (tl_agent_cfg),
diff --git a/hw/dv/sv/tl_agent/tl_reg_adapter.sv b/hw/dv/sv/tl_agent/tl_reg_adapter.sv
index aa106b1..3bf8fa8 100644
--- a/hw/dv/sv/tl_agent/tl_reg_adapter.sv
+++ b/hw/dv/sv/tl_agent/tl_reg_adapter.sv
@@ -28,8 +28,6 @@
     ITEM_T bus_req;
     bus_req = ITEM_T::type_id::create("bus_req");
 
-    // TODO: add a knob to contrl the randomization in case TLUL implementation changes and does
-    // not support partial read/write
     // randomize CSR partial or full read
     // for partial read DUT (except memory) always return the entire 4 bytes bus data
     // if CSR full read (all bytes are enabled) & !MEM, randomly select full or partial read
diff --git a/hw/dv/sv/uart_agent/README.md b/hw/dv/sv/uart_agent/README.md
new file mode 100644
index 0000000..e39fe4a
--- /dev/null
+++ b/hw/dv/sv/uart_agent/README.md
@@ -0,0 +1,3 @@
+# UART Agent
+
+**TODO**
diff --git a/hw/dv/sv/uart_agent/doc/index.md b/hw/dv/sv/uart_agent/doc/index.md
deleted file mode 100644
index 69e34dc..0000000
--- a/hw/dv/sv/uart_agent/doc/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
----
-title: "UART Agent"
----
diff --git a/hw/dv/sv/uart_agent/uart_agent_cfg.sv b/hw/dv/sv/uart_agent/uart_agent_cfg.sv
index 7335207..c2abc8c 100644
--- a/hw/dv/sv/uart_agent/uart_agent_cfg.sv
+++ b/hw/dv/sv/uart_agent/uart_agent_cfg.sv
@@ -23,7 +23,6 @@
   // Logger settings.
   bit en_logger           = 1'b0; // enable logger on tx
   bit use_rx_for_logger   = 1'b0; // use rx instead of tx
-  string logger_id        = "uart_logger";
   bit write_logs_to_file  = 1'b1;
 
   // reset is controlled at upper seq-level as no reset pin on uart interface
diff --git a/hw/dv/sv/uart_agent/uart_if.sv b/hw/dv/sv/uart_agent/uart_if.sv
index 77d6b60..d15dad5 100644
--- a/hw/dv/sv/uart_agent/uart_if.sv
+++ b/hw/dv/sv/uart_agent/uart_if.sv
@@ -13,8 +13,13 @@
   bit   uart_rx_clk = 1'b1;
   int   uart_rx_clk_pulses = 0;
 
+  // UART TX from the DUT when signaled over muxed IOs can experience glitches in the same
+  // time-step (a simulation artifact). Delaying by 1ps eliminates them.
+  wire uart_tx_int;
+  assign #1ps uart_tx_int = uart_tx;
+
   clocking mon_tx_cb @(negedge uart_tx_clk);
-    input  #10ns uart_tx;
+    input  #10ns uart_tx_int;
   endclocking
   modport mon_tx_mp(clocking mon_tx_cb);
 
diff --git a/hw/dv/sv/uart_agent/uart_logger.sv b/hw/dv/sv/uart_agent/uart_logger.sv
index a7aadb2..7af918a 100644
--- a/hw/dv/sv/uart_agent/uart_logger.sv
+++ b/hw/dv/sv/uart_agent/uart_logger.sv
@@ -8,17 +8,21 @@
   int logs_output_fd = 0;
 
   uart_agent_cfg cfg;
-  uvm_tlm_analysis_fifo #(uart_item) log_item_fifo;
+  uvm_tlm_analysis_fifo #(uart_item)  log_item_fifo;
+  uvm_analysis_port #(string)         log_analysis_port;
 
   `uvm_component_new
 
   virtual function void build_phase(uvm_phase phase);
     log_item_fifo = new("log_item_fifo", this);
+    log_analysis_port = new("log_analysis_port", this);
   endfunction
 
   virtual task run_phase(uvm_phase phase);
+    string fname = {`gfn, ".log"};
     if (cfg.write_logs_to_file) begin
-      logs_output_fd = $fopen({cfg.logger_id, ".log"}, "w");
+      logs_output_fd = $fopen(fname, "w");
+      `DV_CHECK_FATAL(!logs_output_fd, $sformatf("Failed to open %s for writing", fname))
     end
     capture_logs();
   endtask
@@ -39,10 +43,11 @@
       forever begin
         log_item_fifo.get(item);
         char = string'(item.data);
-        `uvm_info(cfg.logger_id, $sformatf("received char: %0s", char), UVM_DEBUG)
+        `uvm_info(`gfn, $sformatf("received char: %0s", char), UVM_DEBUG)
         // Continue concatenating chars into the log string untl lf or cr is encountered.
         if (item.data inside {lf, cr}) begin
           print_log(log);
+          log_analysis_port.write(log);
           log = "";
         end
         else begin
@@ -69,19 +74,20 @@
     if (log == "") return;
     case (1)
       (!uvm_re_match(info, log)): begin
-        `uvm_info(cfg.logger_id, log.substr(info.len() - 1, log.len() - 1), UVM_LOW)
+        `uvm_info(`gfn, log.substr(info.len() - 1, log.len() - 1), UVM_LOW)
       end
       (!uvm_re_match(warn, log)): begin
-        `uvm_warning(cfg.logger_id, log.substr(warn.len() - 1, log.len() - 1))
+        // verilog_lint: waive forbidden-macro
+        `uvm_warning(`gfn, log.substr(warn.len() - 1, log.len() - 1))
       end
       (!uvm_re_match(error, log)): begin
-        `uvm_error(cfg.logger_id, log.substr(error.len() - 1, log.len() - 1))
+        `uvm_error(`gfn, log.substr(error.len() - 1, log.len() - 1))
       end
       (!uvm_re_match(fatal, log)): begin
-        `uvm_fatal(cfg.logger_id, log.substr(fatal.len() - 1, log.len() - 1))
+        `uvm_fatal(`gfn, log.substr(fatal.len() - 1, log.len() - 1))
       end
       default: begin
-        `uvm_info(cfg.logger_id, log, UVM_LOW)
+        `uvm_info(`gfn, log, UVM_LOW)
       end
     endcase
     if (logs_output_fd) begin
diff --git a/hw/dv/sv/uart_agent/uart_monitor.sv b/hw/dv/sv/uart_agent/uart_monitor.sv
index aa659d7..8eb4a40 100644
--- a/hw/dv/sv/uart_agent/uart_monitor.sv
+++ b/hw/dv/sv/uart_agent/uart_monitor.sv
@@ -47,19 +47,19 @@
         item = uart_item::type_id::create("item");
         // get the start bit
         @(cfg.vif.mon_tx_mp.mon_tx_cb);
-        `uvm_info(`gfn, $sformatf("tx start bit %0b", cfg.vif.uart_tx), UVM_DEBUG)
-        item.start_bit = cfg.vif.uart_tx;
+        `uvm_info(`gfn, $sformatf("tx start bit %0b", cfg.vif.uart_tx_int), UVM_HIGH)
+        item.start_bit = cfg.vif.uart_tx_int;
         // get the data bits
         for (int i = 0; i < 8; i++) begin
           @(cfg.vif.mon_tx_mp.mon_tx_cb);
-          `uvm_info(`gfn, $sformatf("tx data bit[%0d] %0b", i, cfg.vif.uart_tx), UVM_DEBUG)
-          item.data[i] = cfg.vif.uart_tx;
+          `uvm_info(`gfn, $sformatf("tx data bit[%0d] %0b", i, cfg.vif.uart_tx_int), UVM_DEBUG)
+          item.data[i] = cfg.vif.uart_tx_int;
         end
         // get the parity bit
         if (cfg.vif.uart_tx_clk_pulses > 2) begin
           @(cfg.vif.mon_tx_mp.mon_tx_cb);
-          `uvm_info(`gfn, $sformatf("tx parity bit %0b", cfg.vif.uart_tx), UVM_DEBUG)
-          item.parity = cfg.vif.uart_tx;
+          `uvm_info(`gfn, $sformatf("tx parity bit %0b", cfg.vif.uart_tx_int), UVM_DEBUG)
+          item.parity = cfg.vif.uart_tx_int;
           if (cfg.en_tx_checks && item.parity != `GET_PARITY(item.data, cfg.odd_parity)) begin
             `uvm_error(`gfn, "Parity failed")
           end
@@ -68,10 +68,10 @@
 
         // get the stop bit
         @(cfg.vif.mon_tx_mp.mon_tx_cb);
-        `uvm_info(`gfn, $sformatf("tx stop bit %0b", cfg.vif.uart_tx), UVM_DEBUG)
-        item.stop_bit = cfg.vif.uart_tx;
+        `uvm_info(`gfn, $sformatf("tx stop bit %0b", cfg.vif.uart_tx_int), UVM_DEBUG)
+        item.stop_bit = cfg.vif.uart_tx_int;
         // check stop bit
-        if (cfg.en_tx_checks && cfg.vif.uart_tx !== 1'b1) begin
+        if (cfg.en_tx_checks && cfg.vif.uart_tx_int !== 1'b1) begin
           `uvm_error(`gfn, "No stop bit when expected!")
         end
         `uvm_info(`gfn, $sformatf("collected uart tx txn:\n%0s", item.sprint()), UVM_HIGH)
@@ -82,7 +82,7 @@
 
         if (cfg.en_cov) cov.uart_cg.sample(UartTx, item);
       end else begin
-        @(cfg.vif.uart_tx);
+        @(cfg.vif.uart_tx_int);
       end
     end
   endtask
@@ -149,7 +149,7 @@
         cfg.vif.uart_tx_clk = ~cfg.vif.uart_tx_clk;
         cfg.vif.uart_tx_clk_pulses--;
       end else begin
-        @(cfg.vif.uart_tx, cfg.vif.uart_tx_clk_pulses);
+        @(cfg.vif.uart_tx_int, cfg.vif.uart_tx_clk_pulses);
       end
     end
   endtask
@@ -178,7 +178,7 @@
       #(cfg.vif.uart_clk_period_ns * (50 - cfg.get_max_drift_cycle_pct()) / 100);
       `DV_SPINWAIT_EXIT(
           begin
-            @(cfg.vif.uart_tx);
+            @(cfg.vif.uart_tx_int);
             `uvm_error(`gfn, $sformatf(
                 "Expect uart_tx stable from %0d to %0d of the period, but it's changed",
                 50 - cfg.get_max_drift_cycle_pct(), 50 + cfg.get_max_drift_cycle_pct()))
diff --git a/hw/dv/sv/usb20_agent/README.md b/hw/dv/sv/usb20_agent/README.md
new file mode 100644
index 0000000..d2f75a4
--- /dev/null
+++ b/hw/dv/sv/usb20_agent/README.md
@@ -0,0 +1,4 @@
+# USB20 UVM Agent
+
+USB20 UVM Agent is extended from DV library agent classes. This is just a
+skeleton implementation for now. There is no functionality available yet.
diff --git a/hw/dv/sv/usb20_agent/doc/index.md b/hw/dv/sv/usb20_agent/doc/index.md
deleted file mode 100644
index e88ecfc..0000000
--- a/hw/dv/sv/usb20_agent/doc/index.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: "USB20 UVM Agent"
----
-
-USB20 UVM Agent is extended from DV library agent classes. This is just a
-skeleton implementation for now. There is no functionality available yet.
diff --git a/hw/dv/tools/README.md b/hw/dv/tools/README.md
new file mode 100644
index 0000000..cf7dc84
--- /dev/null
+++ b/hw/dv/tools/README.md
@@ -0,0 +1,3 @@
+# Tools
+
+{{% sectionContent %}}
diff --git a/hw/dv/tools/doc/_index.md b/hw/dv/tools/doc/_index.md
deleted file mode 100644
index 46de5c0..0000000
--- a/hw/dv/tools/doc/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "DV simulation flow"
----
-
-**TODO**
diff --git a/hw/dv/tools/dvsim/common_sim_cfg.hjson b/hw/dv/tools/dvsim/common_sim_cfg.hjson
index 22733cb..1c4403b 100644
--- a/hw/dv/tools/dvsim/common_sim_cfg.hjson
+++ b/hw/dv/tools/dvsim/common_sim_cfg.hjson
@@ -16,18 +16,12 @@
   // Default directory structure for the output
   build_dir:          "{scratch_path}/{build_mode}"
   run_dir_name:       "{index}.{test}"
-  run_dir:            "{scratch_path}/{run_dir_name}/out"
-  sw_build_dir:       "{scratch_path}"
+  run_dir:            "{scratch_path}/{run_dir_name}/latest"
   sw_root_dir:        "{proj_root}/sw"
 
   // Default file to store build_seed value
   build_seed_file_path: "{build_dir}/build_seed.log"
 
-  // pass and fail patterns
-  build_pass_patterns: []
-  // TODO: Add back FuseSoC fail pattern after
-  // https://github.com/lowRISC/opentitan/issues/7348 is resolved.
-  build_fail_patterns: []
   run_pass_patterns:   ["^TEST PASSED (UVM_)?CHECKS$"]
   run_fail_patterns:   ["^UVM_ERROR\\s[^:].*$",
                         "^UVM_FATAL\\s[^:].*$",
@@ -76,9 +70,7 @@
                "+define+DUT_HIER={dut_instance}"]
 
   run_opts: ["+UVM_NO_RELNOTES",
-             "+UVM_VERBOSITY={expand_uvm_verbosity_{verbosity}}",
-             // TODO: remove once smoke regr passes:
-             "+prim_cdc_rand_delay_mode=disable"]
+             "+UVM_VERBOSITY={expand_uvm_verbosity_{verbosity}}"]
 
   // Default list of things to export to shell
   exports: [
@@ -125,10 +117,7 @@
       tests: []
       reseed: 1
       run_opts: [// Knob used to configure an existing test / vseq to have a shorter runtime.
-                 "+smoke_test=1",
-                 // Enable CDC random delay once, at the start of the sim.
-                 // TODO: uncomment once smoke regr passes:
-                 // "+prim_cdc_rand_delay_mode=once"
+                 "+smoke_test=1"
                 ]
     }
     {
@@ -141,16 +130,14 @@
     {
       name: nightly
       en_sim_modes: ["cov"]
-      run_opts: [// Enable CDC random delay changes every 10 source data changes.
-                 // TODO: Uncomment once nightly regr passes:
-                 // "+prim_cdc_rand_delay_mode=interval", "+prim_cdc_rand_delay_interval=10"
-                ]
     }
   ]
 
   // Project defaults for VCS
   vcs_cov_cfg_file: "{{build_mode}_vcs_cov_cfg_file}"
   vcs_unr_cfg_file: "{dv_root}/tools/vcs/unr.cfg"
+  vcs_xprop_cfg_file: "{dv_root}/tools/vcs/xprop.cfg"
+  vcs_fsm_reset_cov_cfg_file: "{dv_root}/tools/vcs/fsm_reset_cov.cfg"
   vcs_cov_excl_files: []
 
   // Build-specific coverage cfg files for VCS.
diff --git a/hw/dv/tools/dvsim/sim.mk b/hw/dv/tools/dvsim/sim.mk
index b20c361..41b0f3f 100644
--- a/hw/dv/tools/dvsim/sim.mk
+++ b/hw/dv/tools/dvsim/sim.mk
@@ -60,47 +60,81 @@
 	# `sw_images` is a space-separated list of tests to be built into an image.
 	# Optionally, each item in the list can have additional metadata / flags using
 	# the delimiter ':'. The format is as follows:
-	# <path-to-sw-test>:<index>:<flag1>:<flag2>
+	# <Bazel label>:<index>:<flag1>:<flag2>
 	#
-	# If no delimiter is detected, then the full string is considered to be the
-	# <path-to-sw-test>. If 1 delimiter is detected, then it must be <path-to-sw-
-	# test> followed by <index>. The <flag> is considered optional.
+	# If one delimiter is detected, then the full string is considered to be the
+	# <Bazel label>. If two delimiters are detected, then it must be <Bazel label>
+	# followed by <index>. The <flag> is considered optional.
+	#
+	# After the images are built, we use `bazel cquery ...` to locate the built
+	# software artifacts so they can be copied to the test bench run directory.
+	# We only copy device SW images, and do not copy host-side artifacts (like
+	# opentitantool) that are also dependencies of the Bazel test target that
+	# encode the software image targets.
 	set -e; \
-	mkdir -p ${sw_build_dir}; \
 	for sw_image in ${sw_images}; do \
-		image=`echo $$sw_image | cut -d: -f 1`; \
-		index=`echo $$sw_image | cut -d: -f 2`; \
-		flags=(`echo $$sw_image | cut -d: -f 3- --output-delimiter " "`); \
-		target_dir=`dirname ${sw_build_dir}/build-bin/$$image`; \
-		mkdir -p $$target_dir; \
-		cd ${proj_root}; \
-		if [[ -z $$image ]]; then \
+		if [[ -z $$sw_image ]]; then \
 			echo "ERROR: SW image \"$$sw_image\" is malformed."; \
-			echo "Expected format: path-to-sw-test:index:optional-flags."; \
+			echo "Expected format: <Bazel label>:<index>:<optional-flags>."; \
 			exit 1; \
 		fi; \
-		if [[ $${flags[@]} =~ "prebuilt" ]]; then \
-			echo "SW image \"$$image\" is prebuilt - copying sources."; \
-			cp ${proj_root}/$$image* $$target_dir/.; \
+		prebuilt_path=`echo $$sw_image | cut -d: -f 1`; \
+		bazel_target=`echo $$sw_image | cut -d: -f 2`; \
+		index=`echo $$sw_image | cut -d: -f 3`; \
+		flags=(`echo $$sw_image | cut -d: -f 4- --output-delimiter " "`); \
+		bazel_label="`echo $$sw_image | cut -d: -f 1-2`"; \
+		if [[ $${index} != 4 ]]; then \
+			bazel_label="$${bazel_label}_$${sw_build_device}"; \
+			bazel_cquery="labels(data, $${bazel_label}) union labels(srcs, $${bazel_label})"; \
 		else \
-			echo "Building SW image \"$$image\"."; \
-			bazel_label="//`dirname $${image}`:`basename $${image}`"; \
-			if [[ $$index == "1" ]]; then \
-				bazel_label+="_sim_dv"; \
-			fi; \
+			bazel_cquery="$${bazel_label}"; \
+		fi; \
+		cd ${proj_root}; \
+		if [[ $${flags[@]} =~ "prebuilt" ]]; then \
+			echo "SW image \"$$bazel_label\" is prebuilt - copying sources."; \
+			cp ${proj_root}/$${prebuilt_path} $${run_dir}/`basename $${prebuilt_path}`; \
+		else \
+			echo "Building SW image \"$${bazel_label}\"."; \
 			bazel_opts="${sw_build_opts} --define DISABLE_VERILATOR_BUILD=true"; \
+			bazel_opts+=" --//hw/ip/otp_ctrl/data:img_seed=${seed}"; \
+			if [[ "${build_seed}" != "None" ]]; then \
+				bazel_opts+=" --//hw/ip/otp_ctrl/data:lc_seed=${build_seed}"; \
+				bazel_opts+=" --//hw/ip/otp_ctrl/data:otp_seed=${build_seed}"; \
+			fi; \
+			if [[ -n $${BAZEL_OTP_DATA_PERM_FLAG} ]]; then \
+				bazel_opts+=" --//hw/ip/otp_ctrl/data:data_perm=$${BAZEL_OTP_DATA_PERM_FLAG}"; \
+			fi; \
 			if [[ -z $${BAZEL_PYTHON_WHEELS_REPO} ]]; then \
 				echo "Building \"$${bazel_label}\" on network connected machine."; \
 				bazel_cmd="./bazelisk.sh"; \
 			else \
 				echo "Building \"$${bazel_label}\" on air-gapped machine."; \
-				bazel_opts+="${sw_build_opts} --distdir=$${BAZEL_DISTDIR} --repository_cache=$${BAZEL_CACHE}"; \
+				bazel_opts+=" --define SPECIFY_BINDGEN_LIBSTDCXX=true"; \
+				bazel_opts+=" --distdir=$${BAZEL_DISTDIR} --repository_cache=$${BAZEL_CACHE}"; \
 				bazel_cmd="bazel"; \
 			fi; \
+			echo "Building with command: $${bazel_cmd} build $${bazel_opts} $${bazel_label}"; \
 			$${bazel_cmd} build $${bazel_opts} $${bazel_label}; \
-			find -L $$($${bazel_cmd} info output_path)/ \
-				-type f -name "$$(basename $${image})*" | \
-				xargs -I % cp -f % $${target_dir}; \
+			for dep in $$($${bazel_cmd} cquery \
+				$${bazel_cquery} \
+				--ui_event_filters=-info \
+				--noshow_progress \
+				--output=starlark); do \
+				if [[ $$dep != //hw* ]] && [[ $$dep != //util* ]] && [[ $$dep != //sw/host* ]]; then \
+					for artifact in $$($${bazel_cmd} cquery $${dep} \
+						--ui_event_filters=-info \
+						--noshow_progress \
+						--output=starlark \
+						--starlark:expr="\"\\n\".join([f.path for f in target.files.to_list()])"); do \
+						cp -f $${artifact} $${run_dir}/$$(basename $${artifact}); \
+						if [[ $$artifact == *.bin && \
+							-f "$$(echo $${artifact} | cut -d. -f 1).elf" ]]; then \
+							cp -f "$$(echo $${artifact} | cut -d. -f 1).elf" \
+								$${run_dir}/$$(basename -s .bin $${artifact}).elf; \
+						fi; \
+					done; \
+				fi; \
+			done; \
 		fi; \
 	done;
 endif
diff --git a/hw/dv/tools/dvsim/testplans/tl_device_access_types_wo_intg_testplan.hjson b/hw/dv/tools/dvsim/testplans/tl_device_access_types_wo_intg_testplan.hjson
index e72060d..be1d1e0 100644
--- a/hw/dv/tools/dvsim/testplans/tl_device_access_types_wo_intg_testplan.hjson
+++ b/hw/dv/tools/dvsim/testplans/tl_device_access_types_wo_intg_testplan.hjson
@@ -13,7 +13,7 @@
       name: tl_d_illegal_access
       desc: '''Drive unsupported requests via TL interface and verify correctness of response
             / behavior. Below error cases are tested bases on the
-            [TLUL spec]({{< relref "hw/ip/tlul/doc/_index.md#explicit-error-cases" >}})
+            [TLUL spec](/hw/ip/tlul/README.md#explicit-error-cases)
             - TL-UL protocol error cases
               - invalid opcode
               - some mask bits not set when opcode is `PutFullData`
diff --git a/hw/dv/tools/dvsim/tests/alert_test.hjson b/hw/dv/tools/dvsim/tests/alert_test.hjson
index 7f82aaf..808d01e 100644
--- a/hw/dv/tools/dvsim/tests/alert_test.hjson
+++ b/hw/dv/tools/dvsim/tests/alert_test.hjson
@@ -11,7 +11,6 @@
   tests: [
     {
       name: "{name}_alert_test"
-      build_mode: "cover_reg_top"
       uvm_test_seq: "{name}_common_vseq"
       run_opts: ["+run_alert_test", "+en_scb=0"]
       reseed: 50
diff --git a/hw/dv/tools/dvsim/tests/stress_all_test.hjson b/hw/dv/tools/dvsim/tests/stress_all_test.hjson
index c1e21cb..88d3bfa 100644
--- a/hw/dv/tools/dvsim/tests/stress_all_test.hjson
+++ b/hw/dv/tools/dvsim/tests/stress_all_test.hjson
@@ -12,6 +12,7 @@
       uvm_test_seq: "{name}_stress_all_vseq"
       // 10ms
       run_opts: ["+test_timeout_ns=10000000000"]
+      run_timeout_mins: 180
     }
   ]
 }
diff --git a/hw/dv/tools/dvsim/tests/stress_tests.hjson b/hw/dv/tools/dvsim/tests/stress_tests.hjson
index 58b74a8..10e1db7 100644
--- a/hw/dv/tools/dvsim/tests/stress_tests.hjson
+++ b/hw/dv/tools/dvsim/tests/stress_tests.hjson
@@ -1,15 +1,11 @@
 // Copyright lowRISC contributors.
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
-{
-  tests: [
-    {
-      name: "{name}_stress_all"
-      uvm_test_seq: "{name}_stress_all_vseq"
-      // 10ms
-      run_opts: ["+test_timeout_ns=10000000000"]
-    }
 
+// this contains stress_all and stress_all_with_rand_reset
+{
+  import_cfgs: ["{proj_root}/hw/dv/tools/dvsim/tests/stress_all_test.hjson"]
+  tests: [
     {
       name: "{name}_stress_all_with_rand_reset"
       uvm_test_seq: "{name}_common_vseq"
@@ -17,6 +13,7 @@
                  // 10ms
                  "+test_timeout_ns=10000000000",
                  "+stress_seq={name}_stress_all_vseq"]
+      run_timeout_mins: 180
     }
   ]
 }
diff --git a/hw/dv/tools/dvsim/vcs.hjson b/hw/dv/tools/dvsim/vcs.hjson
index 2b9262c..db2c640 100644
--- a/hw/dv/tools/dvsim/vcs.hjson
+++ b/hw/dv/tools/dvsim/vcs.hjson
@@ -12,12 +12,13 @@
   //  - Force capability on registers, variables, and nets
   // TODO Trim this flag since +f hurts performance.
   vcs_build_opt_debug_access: "-debug_access+f"
-
+  post_flist_opts: ""
   build_opts: ["-sverilog -full64 -licqueue -ntb_opts uvm-1.2",
                "-timescale={timescale}",
                "-Mdir={build_ex}.csrc",
                "-o {build_ex}",
                "-f {sv_flist}",
+               "{post_flist_opts}",
                // Enable LCA features. It does not require separate licenses.
                "-lca",
                // List multiple tops for the simulation. Prepend each top level with `-top`.
@@ -28,6 +29,8 @@
                "+warn=noUII-L",
                // Disable unnecessary LCA warning.
                "+warn=noLCA_FEATURES_ENABLED",
+               // Disable warnings about bind directive not being applied.
+               "+warn=noBNA",
                // Below option required for $error/$fatal system calls
                "-assert svaext",
                // Force unique and priority to evaluate compliance checking only on the stable
@@ -169,6 +172,8 @@
                      "-grade index testfile",
                      // Use simple ratio of total covered bins over total bins across cps & crs,
                      "-group ratio",
+                     // Follow LRM naming conventions for array bins.
+                     "-group lrm_bin_name",
                      // Compute overall coverage for per-instance covergroups individually rather
                      // than cumulatively.
                      "-group instcov_for_score",
@@ -240,6 +245,9 @@
   // Include hierarchies for both code coverage and assertions.
   vcs_cov_cfg_file: ""
 
+  // Supply cov configuration file for -cm_fsmresetfilter.
+  vcs_fsm_reset_cov_cfg_file: ""
+
   // Supply the cov exclusion files.
   vcs_cov_excl_files: []
 
@@ -279,10 +287,15 @@
                    "-cm_line contassign",
                    // Dump toggle coverage on mdas, array of structs and on ports only
                    "-cm_tgl mda+structarr+portsonly",
+                   // Report condition coverage within tasks, functions and for loops.
+                   "-cm_cond for+tf",
                    // Ignore initial blocks for coverage
                    "-cm_report noinitial",
-                   // Filter unreachable/statically constant blocks
-                   "-cm_noconst",
+                   // Filter unreachable/statically constant blocks. seqnoconst does a more
+                   // sophisticated analysis including NBA / assignment with delays.
+                   "-cm_seqnoconst",
+                   // Creates a constfile.txt indicating a list of detected constants.
+                   "-diag noconst"
                    // Don't count coverage that's coming from zero-time glitches
                    "-cm_glitch 0",
                    // Ignore warnings about not applying cm_glitch to path and FSM
@@ -290,7 +303,9 @@
                    // Coverage database output location
                    "-cm_dir {cov_db_dir}",
                    // The following option is to improve runtime performance
-                   "-Xkeyopt=rtopt"
+                   "-Xkeyopt=rtopt",
+                   // Exclude FSM transitions that can only happen on reset
+                   "-cm_fsmresetfilter {vcs_fsm_reset_cov_cfg_file}",
                    ]
 
       run_opts:   [// Enable the required cov metrics
@@ -305,7 +320,7 @@
     {
       name: vcs_xprop
       is_sim_mode: 1
-      build_opts: ["-xprop={dv_root}/tools/vcs/xprop.cfg",
+      build_opts: ["-xprop={vcs_xprop_cfg_file}",
                    // Enable xmerge mode specific performance optimization
                    "-xprop=mmsopt"]
     }
diff --git a/hw/dv/tools/dvsim/verilator.hjson b/hw/dv/tools/dvsim/verilator.hjson
index adbe53a..1241376 100644
--- a/hw/dv/tools/dvsim/verilator.hjson
+++ b/hw/dv/tools/dvsim/verilator.hjson
@@ -22,7 +22,6 @@
   build_dir:          "{scratch_path}/{build_mode}"
   run_dir_name:       "{index}.{test}"
   run_dir:            "{scratch_path}/{run_dir_name}/out"
-  sw_build_dir:       "{scratch_path}"
   sw_root_dir:        "{proj_root}/sw"
 
   regressions: [
@@ -82,7 +81,6 @@
   ]
 
   // pass and fail patterns
-  build_pass_patterns: []
   build_fail_patterns: [// Verilator compile error.
                         "^%Error.*?:",
                         // FuseSoC build error.
diff --git a/hw/dv/tools/dvsim/xcelium.hjson b/hw/dv/tools/dvsim/xcelium.hjson
index ce3c3d8..5322930 100644
--- a/hw/dv/tools/dvsim/xcelium.hjson
+++ b/hw/dv/tools/dvsim/xcelium.hjson
@@ -8,6 +8,7 @@
   build_db_dir: "{build_dir}/xcelium.d"
 
   build_opts: ["-elaborate -64bit -sv",
+               // Wait to acquire a license.
                "-licqueue",
                // TODO: duplicate primitives between OT and Ibex #1231
                "-ALLOWREDEFINITION",
@@ -15,6 +16,7 @@
                "-timescale {timescale}",
                "-f {sv_flist}",
                "-uvmhome CDNS-1.2",
+               // Specify the library name for the run step to use.
                "-xmlibdirname {build_db_dir}",
                // List multiple tops for the simulation. Prepend each top level with `-top`.
                "{eval_cmd} echo {sim_tops} | sed -E 's/(\\S+)/-top \\1/g'",
@@ -29,6 +31,8 @@
                // Use this to conditionally compile for Xcelium (example: LRM interpretations differ
                // across tools).
                "+define+XCELIUM",
+               // Suppress printing of the copyright banner.
+               "-nocopyright",
                // Ignore "timescale is not specified for the package" warning
                "-nowarn TSNSPK",
                // Ignore "IEEE 1800-2009 SystemVerilog simulation semantics" warning
@@ -43,6 +47,10 @@
                "-nowarn SPDUSD",
                // Needed for including "secded_enc.h".
                "-I{build_dir}/src/lowrisc_dv_secded_enc_0",
+               // This warning is thrown when a scalar enum variable is assigned to an enum array.
+               // Other tools (e.g., FPV) treat such assignments as an error, hence we bump it to
+               // an error in simulation so that this can be caught early in CI.
+               "-xmerror ENUMERR"
                ]
 
   // We want to allow the possibility of passing no test or no test sequence. Unfortunately,
@@ -53,6 +61,9 @@
   uvm_testseq_plusarg: "{eval_cmd} echo {uvm_test_seq} | sed -E 's/(.+)/+UVM_TEST_SEQ=\\1/'"
 
   run_opts:   ["-input {run_script}",
+               // Suppress printing of the copyright banner.
+               "-nocopyright",
+               // Wait to acquire a license.
                "-licqueue",
                "-64bit -xmlibdirname {build_db_dir}",
                // Use the same snapshot name set during the build step.
@@ -228,8 +239,8 @@
     {
       name: xcelium_profile
       is_sim_mode: 1
-      build_opts: []
-      run_opts:   []
+      build_opts: ["-perfstat"]
+      run_opts:   ["-perfstat", "-perflog perfstat.log"]
     }
     {
       name: xcelium_xprop
diff --git a/hw/dv/tools/ralgen/README.md b/hw/dv/tools/ralgen/README.md
new file mode 100644
index 0000000..1edbb94
--- /dev/null
+++ b/hw/dv/tools/ralgen/README.md
@@ -0,0 +1,103 @@
+# `ralgen`: A FuseSoC generator for UVM RAL package
+
+The `ralgen.py` script is implemented as a
+[FuseSoC generator](https://fusesoc.readthedocs.io/en/master/user/generators.html).
+which enables the automatic creation of the SystemVerilog UVM RAL package and
+its insertion into the dependency tree when compiling the DV testbench.
+
+This approach is useful for DV simulation flows that use FuseSoC as the backend
+to generate the filelist for compilation. A separate RAL package generation
+step is no longer needed since it gets handled within FuseSoC.
+
+## Generator
+
+The adjoining `ralgen.core` file registers the `ralgen` generator. The FuseSoC
+core file that 'calls' the generator adds it as a dependency. When calling the
+generator, the following parameters are set:
+* **name (mandatory)**: Name of the RAL package (typically, same is the IP).
+* **dv_base_names (optional)**: The base class names from which the register
+  classes are derived. Set this option to derive the register classes not from
+  the default `dv_base_reg`, but from user defined custom class definitions.
+  This argument follows the following format:
+  `--dv-base-names block:type:entity-name block:type:entity-name ...`.
+  `block`: can be any block names.
+  `type`: can be `block`, `reg`, `field`, `pkg`, `mem`, or use `all` to override
+  all types within the block.
+  `entity_name`: the name of the base class / package. If the `type` is set to `all`,
+  then this represents the prefix of the bass class / package. The suffixes
+  `_reg_block`, `_reg`, `_reg_field`, `_mem`, `_reg_pkg` are applied to infer the
+  actual base class / package names from which the generated DV classes will extend.
+  Note that we assume the fusesoc core file naming convention follows the package
+  name without the `_pkg` suffix.
+* **ip_hjson**: Path to the hjson specification written for an IP which includes
+  the register descriptions. This needs to be a valid input for `reggen`.
+* **top_hjson**: Path to the hjson specification for a top level design. This
+  needs to be a valid input for `topgen`.
+
+Only one of the last two arguments is mandatory. If both are set, or if neither
+of them are, then the tool throws an error and exits.
+
+The following snippet shows how it is called:
+```
+generate:
+  ral:
+    generator: ralgen
+    parameters:
+      name: <name>
+      ip_hjson|top_hjson: <path-to-hjson-spec>
+      [dv_base_names:
+        - block_1:type:entity_name_1
+        - block_2:type:entity_name_2]
+
+
+targets:
+  default:
+    ...
+    generate:
+      - ral
+```
+
+Note that the path to `hjson` specification in the snippet above is relative
+to the core file in which the generator is called.
+
+## `ralgen` script
+
+When FuseSoC processes the dependency list and encounters a generator, it
+passes a YAML file containing the above parameters to the generator tool
+(the `ralgen.py`) as a single input. It then parses the YAML input to
+extract those parameters.
+
+`ralgen.py` really is just a wrapper around
+[`reggen`](../../../../util/reggen/doc/setup_and_use.md) and the `util/topgen.py`
+scripts, which are the ones that actually create the RAL package.
+Due to the way those scripts are implemented, RAL packages for the IP level
+testbenches are generated using
+[`reggen`](../../../../util/reggen/README.md), and for the chip level
+testbench, `util/topgen.py`. Which one to choose is decided by whether
+the `ip_hjson` or `top_hjson` parameter is supplied.
+
+In addition, the `ralgen.py` script also creates a FuseSoC core file. It uses
+the `name` parameter to derive the
+[VLNV](https://fusesoc.readthedocs.io/en/master/user/overview.html#core-naming-rules)
+name for the generated core file.
+
+The generated core file adds **`lowrisc:dv:dv_base_reg`** as a dependency for
+the generated RAL package. This is required because our DV register block,
+register and field models are derived from the
+[DV library](../../sv/dv_lib/README.md) of classes. This
+ensures the right compilation order is maintained. If the `dv_base_names`
+argument is set, then it adds **`lowrisc:dv:my_base_reg`** as an extra
+dependency, where `my_base` is the value of the argument as shown in the
+example above. This core file and the associated sources are assumed to be
+available in the provided FuseSoC search paths.
+
+## Limitations
+
+The script is not designed to be manually invoked, but in theory, it can be, if
+a YAML file that contains the right set of parameters is presented to it
+(compliant with FuseSoC).
+
+If the user wishes to create the RAL package manually outside of the DV
+simulation flow, then the `make` command can be invoked in the `hw/'` area
+instead. It generates the RTL, DV and SW collaterals for all IPs, as well as
+the top level in a single step.
diff --git a/hw/dv/tools/ralgen/doc/index.md b/hw/dv/tools/ralgen/doc/index.md
deleted file mode 100644
index 6fe20a7..0000000
--- a/hw/dv/tools/ralgen/doc/index.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-title: "`ralgen`: A FuseSoC generator for UVM RAL package"
----
-
-The `ralgen.py` script is implemented as a
-[FuseSoC generator](https://fusesoc.readthedocs.io/en/master/user/generators.html).
-which enables the automatic creation of the SystemVerilog UVM RAL package and
-its insertion into the dependency tree when compiling the DV testbench.
-
-This approach is useful for DV simulation flows that use FuseSoC as the backend
-to generate the filelist for compilation. A separate RAL package generation
-step is no longer needed since it gets handled within FuseSoC.
-
-## Generator
-
-The adjoining `ralgen.core` file registers the `ralgen` generator. The FuseSoC
-core file that 'calls' the generator adds it as a dependency. When calling the
-generator, the following parameters are set:
-* **name (mandatory)**: Name of the RAL package (typically, same is the IP).
-* **dv_base_names (optional)**: The base class names from which the register
-  classes are derived. Set this option to derive the register classes not from
-  the default `dv_base_reg`, but from user defined custom class definitions.
-  This argument follows the following format:
-  `--dv-base-names block:type:entity-name block:type:entity-name ...`.
-  `block`: can be any block names.
-  `type`: can be `block`, `reg`, `field`, `pkg`, `mem`, or use `all` to override
-  all types within the block.
-  `entity_name`: the name of the base class / package. If the `type` is set to `all`,
-  then this represents the prefix of the bass class / package. The suffixes
-  `_reg_block`, `_reg`, `_reg_field`, `_mem`, `_reg_pkg` are applied to infer the
-  actual base class / package names from which the generated DV classes will extend.
-  Note that we assume the fusesoc core file naming convention follows the package
-  name without the `_pkg` suffix.
-* **ip_hjson**: Path to the hjson specification written for an IP which includes
-  the register descriptions. This needs to be a valid input for `reggen`.
-* **top_hjson**: Path to the hjson specification for a top level design. This
-  needs to be a valid input for `topgen`.
-
-Only one of the last two arguments is mandatory. If both are set, or if neither
-of them are, then the tool throws an error and exits.
-
-The following snippet shows how it is called:
-```
-generate:
-  ral:
-    generator: ralgen
-    parameters:
-      name: <name>
-      ip_hjson|top_hjson: <path-to-hjson-spec>
-      [dv_base_names:
-        - block_1:type:entity_name_1
-        - block_2:type:entity_name_2]
-
-
-targets:
-  default:
-    ...
-    generate:
-      - ral
-```
-
-Note that the path to `hjson` specification in the snippet above is relative
-to the core file in which the generator is called.
-
-## `ralgen` script
-
-When FuseSoC processes the dependency list and encounters a generator, it
-passes a YAML file containing the above parameters to the generator tool
-(the `ralgen.py`) as a single input. It then parses the YAML input to
-extract those parameters.
-
-`ralgen.py` really is just a wrapper around
-[`reggen`]({{< relref "util/reggen/doc" >}}) and the `util/topgen.py`
-scripts, which are the ones that actually create the RAL package.
-Due to the way those scripts are implemented, RAL packages for the IP level
-testbenches are generated using
-[`reggen`](({{< relref "util/reggen/doc" >}})), and for the chip level
-testbench, `util/topgen.py`. Which one to choose is decided by whether
-the `ip_hjson` or `top_hjson` parameter is supplied.
-
-In addition, the `ralgen.py` script also creates a FuseSoC core file. It uses
-the `name` parameter to derive the
-[VLNV](https://fusesoc.readthedocs.io/en/master/user/overview.html#core-naming-rules)
-name for the generated core file.
-
-The generated core file adds **`lowrisc:dv:dv_base_reg`** as a dependency for
-the generated RAL package. This is required because our DV register block,
-register and field models are derived from the
-[DV library]({{< relref "hw/dv/sv/dv_lib/doc" >}}) of classes. This
-ensures the right compilation order is maintained. If the `dv_base_names`
-argument is set, then it adds **`lowrisc:dv:my_base_reg`** as an extra
-dependency, where `my_base` is the value of the argument as shown in the
-example above. This core file and the associated sources are assumed to be
-available in the provided FuseSoC search paths.
-
-## Limitations
-
-The script is not designed to be manually invoked, but in theory, it can be, if
-a YAML file that contains the right set of parameters is presented to it
-(compliant with FuseSoC).
-
-If the user wishes to create the RAL package manually outside of the DV
-simulation flow, then the `make` command can be invoked in the `hw/'` area
-instead. It generates the RTL, DV and SW collaterals for all IPs, as well as
-the top level in a single step.
diff --git a/hw/dv/tools/ralgen/ralgen.py b/hw/dv/tools/ralgen/ralgen.py
index 68dd28d..b868051 100755
--- a/hw/dv/tools/ralgen/ralgen.py
+++ b/hw/dv/tools/ralgen/ralgen.py
@@ -62,8 +62,9 @@
         if hjson_path:
             args += ["--hjson-path", root_dir / hjson_path]
         if alias_hjson:
+            args += ["--alias-files"]
             for alias in alias_hjson:
-                args += ["--alias-files", root_dir / alias]
+                args += [root_dir / alias]
     if dv_base_names:
         args += ["--dv-base-names"] + dv_base_names
 
diff --git a/hw/dv/tools/sim.tcl b/hw/dv/tools/sim.tcl
index d9540b1..c513490 100644
--- a/hw/dv/tools/sim.tcl
+++ b/hw/dv/tools/sim.tcl
@@ -14,12 +14,39 @@
   quit
 }
 
+# Dumping waves in specific hierarchies.
+#
+# By default, if wave dumping is enabled, all hierarchies of the top level testbench are dumped.
+# For large designs, this may slow down the simulation considerably. To bypass this and only enable
+# waves in specific hierarchies, set the dump_tb_top flag to 0 (i.e. uncomment the line below), and
+# specify the paths to dump on line 32.
+# set dump_tb_top 0
+
 source "${dv_root}/tools/common.tcl"
 source "${dv_root}/tools/waves.tcl"
 
+global waves
+global simulator
+global tb_top
+
+# Dumping waves in specific hierarchies (example):
+# wavedumpScope $waves $simulator tb.dut.foo.bar 12
+# wavedumpScope $waves $simulator tb.dut.baz 0
+
+if {$simulator eq "xcelium"} {
+  puts "INFO: The following assertions are permamently disabled:"
+  assertion -list -depth all -multiline -permoff $tb_top
+}
+
 # In GUI mode, let the user take control of running the simulation.
 global gui
 if {$gui == 0} {
   run
-  quit
+  if {$simulator eq "xcelium"} {
+    # Xcelium provides a `finish` tcl command instead of `quit`. The argument '2' enables the
+    # logging of additional resource usage information.
+    finish 2
+  } else {
+    quit
+  }
 }
diff --git a/hw/dv/tools/vcs/common_cov_excl.cfg b/hw/dv/tools/vcs/common_cov_excl.cfg
index 9bde255..35a32c4 100644
--- a/hw/dv/tools/vcs/common_cov_excl.cfg
+++ b/hw/dv/tools/vcs/common_cov_excl.cfg
@@ -13,6 +13,8 @@
 -node tb.dut *tl_o.d_sink
 // [UNR] due to the ECC logics
 -node tb.dut *tl_o.d_user.rsp_intg[6]
+// [UNR] unused inputs at prim_secded_* in reg_top and mem.
+-node tb.dut*_chk.u_chk data_i[56:43]
 
 // [LOW_RISK] Verified in prim_alert_sender/receiver TB."
 -node tb.dut* *alert_rx_i*.ping_p
diff --git a/hw/dv/tools/vcs/cover.cfg b/hw/dv/tools/vcs/cover.cfg
index 2f74643..ccde34a 100644
--- a/hw/dv/tools/vcs/cover.cfg
+++ b/hw/dv/tools/vcs/cover.cfg
@@ -8,54 +8,39 @@
 +tree tb.dut
 -module pins_if     // DV construct.
 -module clk_rst_if  // DV construct.
-// Prim_alert/esc pairs are verified in FPV and DV testbenches.
+
+// The modules below are preverified in FPV and / or DV testbenches.
 -moduletree prim_alert_sender
 -moduletree prim_alert_receiver
+-moduletree prim_count
 -moduletree prim_esc_sender
 -moduletree prim_esc_receiver
--moduletree prim_prince // prim_prince is verified in a separate DV environment.
--moduletree prim_lfsr // prim_lfsr is verified in FPV.
+-moduletree prim_lfsr
+-moduletree prim_onehot_check
+-moduletree prim_prince
+-moduletree prim_secded_inv_64_57_dec // use in reg_top
+-moduletree prim_secded_inv_39_32_dec // use in reg_top
+
 // csr_assert_fpv is an auto-generated csr read assertion module. So only assertion coverage is
 // meaningful to collect.
 -moduletree *csr_assert_fpv
--module prim_cdc_rand_delay // DV CDC module
+-module prim_cdc_rand_delay  // DV construct.
 
 begin tgl
   -tree tb
   +tree tb.dut 1
   +module prim_alert_sender
   +module prim_alert_receiver
+  +module prim_count
   +module prim_esc_sender
   +module prim_esc_receiver
-  +module prim_prince
   +module prim_lfsr
+  +module prim_onehot_check
+  +module prim_prince
+  +module prim_secded_inv_64_57_dec
+  +module prim_secded_inv_39_32_dec
 end
 
 begin assert
   +moduletree *csr_assert_fpv
-  -moduletree prim_cdc_rand_delay // TODO: CDC not enabled yet
-  // These three assertions in prim_lc_sync and prim_mubi* check when `lc_ctrl_pkg::lc_tx_t` or
-  // `mubi*_t` input are neither `On` or `Off`, it is interrupted to the correct `On` or `Off`
-  // after one clock cycle. This behavior is implemented outside of IP level design thus these
-  // assertions are not covered in IP level testbenchs.
-  // TODO: check these assertions in top-level or FPV.
-  -assert PrimLcSyncCheckTransients_A
-  -assert PrimLcSyncCheckTransients0_A
-  -assert PrimLcSyncCheckTransients1_A
-
-  -assert PrimMubi4SyncCheckTransients_A
-  -assert PrimMubi4SyncCheckTransients0_A
-  -assert PrimMubi4SyncCheckTransients1_A
-
-  -assert PrimMubi8SyncCheckTransients_A
-  -assert PrimMubi8SyncCheckTransients0_A
-  -assert PrimMubi8SyncCheckTransients1_A
-
-  -assert PrimMubi12SyncCheckTransients_A
-  -assert PrimMubi12SyncCheckTransients0_A
-  -assert PrimMubi12SyncCheckTransients1_A
-
-  -assert PrimMubi16SyncCheckTransients_A
-  -assert PrimMubi16SyncCheckTransients0_A
-  -assert PrimMubi16SyncCheckTransients1_A
 end
diff --git a/hw/dv/tools/vcs/cover_reg_top.cfg b/hw/dv/tools/vcs/cover_reg_top.cfg
index 965cda5..771cd56 100644
--- a/hw/dv/tools/vcs/cover_reg_top.cfg
+++ b/hw/dv/tools/vcs/cover_reg_top.cfg
@@ -7,11 +7,14 @@
 
 +moduletree *_reg_top
 +node tb.dut tl_*
+-module prim_cdc_rand_delay  // DV construct.
+-module prim_onehot_check    // FPV verified
+-moduletree prim_secded_inv_64_57_dec // use in reg_top
+-moduletree prim_secded_inv_39_32_dec // use in reg_top
 
 begin assert
   +moduletree *csr_assert_fpv
   +moduletree tlul_assert
-  -moduletree prim_cdc_rand_delay // TODO: CDC not enabled yet
 end
 
 // Remove everything else from toggle coverage except:
@@ -20,4 +23,6 @@
 begin tgl
   -tree tb
   +module prim_alert_sender
+  +module prim_secded_inv_64_57_dec
+  +module prim_secded_inv_39_32_dec
 end
diff --git a/hw/dv/tools/vcs/fsm_reset_cov.cfg b/hw/dv/tools/vcs/fsm_reset_cov.cfg
new file mode 100644
index 0000000..6207462
--- /dev/null
+++ b/hw/dv/tools/vcs/fsm_reset_cov.cfg
@@ -0,0 +1,11 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+// This file is used with -cm_fsmresetfilter to exclude FSM transitions that can only happen on
+// reset.
+// Format: signal=<reset_signal_name> case=TRUE/FALSE (indicates reset is high/low active)
+
+signal=rst_n      case=FALSE
+signal=rst_ni     case=FALSE
+signal=rst_aon_ni case=FALSE
diff --git a/hw/dv/tools/vcs/unr.cfg b/hw/dv/tools/vcs/unr.cfg
index debceaf..290f3a4 100644
--- a/hw/dv/tools/vcs/unr.cfg
+++ b/hw/dv/tools/vcs/unr.cfg
@@ -10,6 +10,10 @@
 # Provide the reset specification: signal_name, active_value, num clk cycles reset to be active
 -reset rst_ni 0 20
 
+# Enables the Elite licensing for UNR
+# Adding this switch avoids the compile error saying could not find the `VC-static-cov` license
+-fmlElite
+
 # Black box common security modules
  -blackBoxes -type design prim_count+prim_spare_fsm+prim_double_lfsr
 
diff --git a/hw/dv/tools/vcs/xprop.cfg b/hw/dv/tools/vcs/xprop.cfg
index 022d2ba..da9650f 100644
--- a/hw/dv/tools/vcs/xprop.cfg
+++ b/hw/dv/tools/vcs/xprop.cfg
@@ -2,7 +2,11 @@
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 
+// Use most pessimistic X propagation.
 merge = xmerge;
 
-// Turn on xprop for dut only
+// Turn on xprop for dut only.
 instance { tb.dut } { xpropOn };
+
+// Modules excluded from xprop instrumentation.
+module { prim_cdc_rand_delay } { xpropOff };
diff --git a/hw/dv/tools/waves.tcl b/hw/dv/tools/waves.tcl
index 35103db..eeb76e2 100644
--- a/hw/dv/tools/waves.tcl
+++ b/hw/dv/tools/waves.tcl
@@ -12,91 +12,31 @@
   quit
 }
 
-global simulator
-global waves
-global tb_top
-
-set wavedump_db "waves.$waves"
-
-# TODO: convert this to a proc?
-set fid ""
-switch $waves {
-  "none" {
-    puts "INFO: Dumping waves is not enabled."
-  }
-
-  "fsdb" {
-    if {$simulator eq "xcelium"} {
-      call fsdbDumpfile $wavedump_db
-    } else {
-      fsdbDumpfile $wavedump_db
-    }
-  }
-
-  "shm" {
-    checkEq simulator "xcelium"
-    database -open $wavedump_db -default -shm
-  }
-
-  "vpd" {
-    checkEq simulator "vcs"
-    set fid [dump -file $wavedump_db -type VPD]
-  }
-
-  "vcd" {
-    if {$simulator eq "xcelium"} {
-      database -open $wavedump_db -default -vcd
-    } else {
-      puts "ERROR: Simulator $simulator does not support dumping waves in VCD."
-      quit
-    }
-  }
-
-  "evcd" {
-    if {$simulator eq "xcelium"} {
-      database -open $wavedump_db -default -evcd
-    } else {
-      puts "ERROR: Simulator $simulator does not support dumping waves in EVCD."
-      quit
-    }
-  }
-
-  default {
-    puts "ERROR: Unknown wave format: ${waves}."
-    quit
-  }
-}
-
-if {$waves ne "none"} {
-  puts "INFO: Dumping waves in [string toupper $waves] format to $wavedump_db."
-}
-
-# Provides wave-format-agnostic way to set a scope (design heirarchy).
+# A procedure that provides a wave-format-agnostic way to enable a scope (design heirarchy).
 #
 # In large designs, dumping waves on the entire hierarchy can significantly slow down the
 # simulation. It is useful in that case to only dump the relevant scopes of interest during debug.
 #
+# waves       : The wave-format (fsdb, shm, vpd, vcd, evcd, etc).
+# simulator   : The simulator used for running the simulation (vcs, xcelium, etc).
 # scope       : Design / testbench hierarchy to dump waves. Defaults to $tb_top.
-# fid         : File ID returned by the dump command in the first step above.
 # depth       : Levels in the hierarchy to dump waves. Defaults to 0 (dump all levels).
-# fsdb_flags  : Additional string flags passed to fsdbDumpVars. Defaults to "+all".
+# fsdb_flags  : Additional string flags passed to fsdbDumpVars. Defaults to "+all", which enables
+#               dumping of memories, MDAs, structs, unions, power, packed structs, and SVAs.
 # probe_flags : Additional string flags passed to probe command (Xcelium). Defaults to "-all".
-# dump_flags  : Additional string flags passed to dump command (VCS). Defaults to "-aggregates".
+# dump_flags  : Additional string flags passed to dump command (VCS). Defaults to "-aggregates",
+#               which enables dumping of structs and arrays.
 #
-# Depending on the need, more such technlogy specific flags can be added in future.
-proc wavedumpScope {scope fid {depth 0} {fsdb_flags "+all"} {probe_flags  "-all"}
+# Depending on the need, more such technology specific flags can be added in future.
+proc wavedumpScope {waves simulator scope {depth 0} {fsdb_flags "+all"} {probe_flags "-all"}
                     {dump_flags "-aggregates"}} {
-  global simulator
-  global waves
-  global wavedump_db
 
   switch $waves {
     "none" {
+      return
     }
 
     "fsdb" {
-      # The fsdbDumpvars +all command dumps everything: memories, MDAs,
-      # structs, unions, power, packed structs. In addition, also dump SVAs.
       if {$simulator eq "xcelium"} {
         call fsdbDumpvars $depth $scope $fsdb_flags
         call fsdbDumpSVA $depth $scope
@@ -114,9 +54,8 @@
     }
 
     "vpd" {
-      # The dump command switch -aggregates enables dumping of structs &
-      # arrays.
-      dump -add "$scope" -fid $fid -depth $depth $dump_flags
+      global vpd_fid
+      dump -add "$scope" -fid $vpd_fid -depth $depth $dump_flags
     }
 
     "vcd" {
@@ -142,19 +81,83 @@
       quit
     }
   }
+
   puts "INFO: Dumping waves in scope \"$scope:$depth\"."
 }
 
-# Decide whether to dump the entire testbench hierarchy by default.
+# A global variable representing the file id (fid) of the waves dumped in VPD format.
+setDefault vpd_fid 0
+
+# The entry point to enable dumping waves.
 #
-# If this variable is not set externally, it is set to 1 by default here. When set to 1, it adds the
-# entire top-level testbench instance for dumping waves. For larger designs, this may slow down the
-# simulation. The user can if needed, set it to 0 in the external tcl script that sources this
-# script and manually add the hierarchies of interest in there, using the wavedumpScope proc.
-setDefault dump_tb_top 1
+# If waves are not enabled (i.e. $waves == "none"), we do nothing. If enabled, then first, we run
+# the tcl command to establish the dump file. Then, we run `wavedumpScope args...` to enable dumping
+# the required hierarchies.
+global waves
+global simulator
+if {$waves ne "none"} {
+  set wavedump_db "waves.$waves"
+  puts "INFO: Dumping waves in [string toupper $waves] format to $wavedump_db."
 
+  # If waves are enabled, then issue the necessary tcl commands to enable the generation of waves.
+  # To explicitly list the hierarchies to dump, use the wavedumpScope proc instead.
+  switch $waves {
+    "fsdb" {
+      if {$simulator eq "xcelium"} {
+        call fsdbDumpfile $wavedump_db
+      } else {
+        fsdbDumpfile $wavedump_db
+      }
+    }
 
-# By default, add the full test bench scope for wavedump.
-if {$dump_tb_top == 1} {
-  wavedumpScope $tb_top $fid
+    "shm" {
+      checkEq simulator "xcelium"
+      database -open $wavedump_db -default -shm
+    }
+
+    "vpd" {
+      checkEq simulator "vcs"
+      global vpd_fid
+      set vpd_fid [dump -file $wavedump_db -type VPD]
+    }
+
+    "vcd" {
+      if {$simulator eq "xcelium"} {
+        database -open $wavedump_db -default -vcd
+      } else {
+        puts "ERROR: Simulator $simulator does not support dumping waves in VCD."
+        quit
+      }
+    }
+
+    "evcd" {
+      if {$simulator eq "xcelium"} {
+        database -open $wavedump_db -default -evcd
+      } else {
+        puts "ERROR: Simulator $simulator does not support dumping waves in EVCD."
+        quit
+      }
+    }
+
+    default {
+      puts "ERROR: Unknown wave format: ${waves}."
+      quit
+    }
+  }
+
+  # Decide whether to dump the entire testbench hierarchy by default.
+  #
+  # If this variable is not set externally, it is set to 1 by default here. When set to 1, it adds
+  # the entire top-level testbench instance for dumping waves. For larger designs, this may slow
+  # down the simulation. The user can if needed, set it to 0 in the external tcl script that sources
+  # this script and manually add the hierarchies of interest in there, using the wavedumpScope proc.
+  # See the adjoining sim.tcl for an example.
+  setDefault dump_tb_top 1
+
+  if {$dump_tb_top == 1} {
+    global tb_top
+    wavedumpScope $waves $simulator $tb_top 0
+  } else {
+    puts "INFO: the hierarchies to be dumped are expected to be indicated externally."
+  }
 }
diff --git a/hw/dv/tools/xcelium/common.ccf b/hw/dv/tools/xcelium/common.ccf
index 2a8c29c..9d003ae 100644
--- a/hw/dv/tools/xcelium/common.ccf
+++ b/hw/dv/tools/xcelium/common.ccf
@@ -29,14 +29,10 @@
 // Scores statements within a block.
 set_statement_scoring
 
-
 // Enables Toggle scoring and reporting of SystemVerilog enumerations and multidimensional static
 // arrays , vectors, packed union, modport and generate blocks.
 set_toggle_scoring -sv_enum enable_mda -sv_struct_with_enum -sv_modport -sv_mda 16 -sv_mda_of_struct -sv_generate -sv_packed_union
 
-// Enables scoring of reset states and transitions for identified FSMs.
-set_fsm_reset_scoring
-
 // Enable toggle coverage only on ports.
 set_toggle_portsonly
 
diff --git a/hw/dv/tools/xcelium/common_cov_excl.tcl b/hw/dv/tools/xcelium/common_cov_excl.tcl
index 7580fcf..6c9243f 100644
--- a/hw/dv/tools/xcelium/common_cov_excl.tcl
+++ b/hw/dv/tools/xcelium/common_cov_excl.tcl
@@ -7,5 +7,6 @@
 exclude -inst $::env(DUT_TOP) -toggle '*tl_o.d_param' -comment "\[UNR\] Follows tl_i.a_param which is unsupported."
 exclude -inst $::env(DUT_TOP) -toggle '*tl_o.d_sink' -comment "\[UNR\] Based on our Comportability Spec."
 exclude -inst $::env(DUT_TOP) -toggle '*tl_o.d_user.rsp_intg'\[6\] -comment "\[UNR\] Due to the ECC logics"
-exclude -inst $::env(DUT_TOP) -toggle '*alert_rx_*.ping_*' -comment "\[LOW_RISK\] Verified in prim_alert_receiver TB."
+exclude -inst $::env(DUT_TOP).gen_alert_tx*.u_prim_alert_sender -toggle '*alert*.ping_*' -comment "\[LOW_RISK\] Verified in prim_alert_receiver TB."
+exclude -inst $::env(DUT_TOP).*_chk.u_chk -toggle 'data_i[56:43]' -comment "\[UNR\] unused inputs at prim_secded_* in reg_top and mem."
 exclude -inst $::env(DUT_TOP).gen_alert_tx*.u_prim_alert_sender -toggle '*alert*.ping_*' -comment "\[LOW_RISK\] Verified in prim_alert_receiver TB."
diff --git a/hw/dv/tools/xcelium/cover.ccf b/hw/dv/tools/xcelium/cover.ccf
index fe6d74b..7752f48 100644
--- a/hw/dv/tools/xcelium/cover.ccf
+++ b/hw/dv/tools/xcelium/cover.ccf
@@ -14,14 +14,18 @@
 deselect_coverage -betfs -module clk_rst_if
 deselect_coverage -betfs -module prim_alert_sender...
 deselect_coverage -betfs -module prim_alert_receiver...
+deselect_coverage -betfs -module prim_count...
 deselect_coverage -betfs -module prim_esc_sender...
 deselect_coverage -betfs -module prim_esc_receiver...
+deselect_coverage -betfs -module prim_onehot_check...
 deselect_coverage -betfs -module prim_prince...
 deselect_coverage -betfs -module prim_lfsr...
-
+deselect_coverage -betfs -module prim_secded_inv_64_57_dec...
+deselect_coverage -betfs -module prim_secded_inv_39_32_dec...
 
 // Black-box DV CDC module.
 deselect_coverage -betfs -module prim_cdc_rand_delay
+
 // csr_assert_fpv is an auto-generated csr read assertion module. So only assertion coverage is
 // meaningful to collect.
 deselect_coverage -betf -module *csr_assert_fpv...
@@ -37,32 +41,11 @@
 
 select_coverage -toggle -module prim_alert_sender
 select_coverage -toggle -module prim_alert_receiver
+select_coverage -toggle -module prim_count
 select_coverage -toggle -module prim_esc_sender
 select_coverage -toggle -module prim_esc_receiver
+select_coverage -toggle -module prim_onehot_check
 select_coverage -toggle -module prim_prince
 select_coverage -toggle -module prim_lfsr
-
-// These three assertions in prim_lc_sync and prim_mubi* check when `lc_ctrl_pkg::lc_tx_t` or
-// `mubi*_t` input are neither `On` or `Off`, it is interrupted to the correct `On` or `Off`
-// after one clock cycle. This behavior is implemented outside of IP level design thus these
-// assertions are not covered in IP level testbenchs.
-// TODO: check these assertions in top-level or FPV.
-deselect_coverage -assertion *.PrimLcSyncCheckTransients_A
-deselect_coverage -assertion *.PrimLcSyncCheckTransients0_A
-deselect_coverage -assertion *.PrimLcSyncCheckTransients1_A
-
-deselect_coverage -assertion *.PrimMubi4SyncCheckTransients_A
-deselect_coverage -assertion *.PrimMubi4SyncCheckTransients0_A
-deselect_coverage -assertion *.PrimMubi4SyncCheckTransients1_A
-
-deselect_coverage -assertion PrimMubi8SyncCheckTransients_A
-deselect_coverage -assertion PrimMubi8SyncCheckTransients0_A
-deselect_coverage -assertion PrimMubi8SyncCheckTransients1_A
-
-deselect_coverage -assertion PrimMubi12SyncCheckTransients_A
-deselect_coverage -assertion PrimMubi12SyncCheckTransients0_A
-deselect_coverage -assertion PrimMubi12SyncCheckTransients1_A
-
-deselect_coverage -assertion PrimMubi16SyncCheckTransients_A
-deselect_coverage -assertion PrimMubi16SyncCheckTransients0_A
-deselect_coverage -assertion PrimMubi16SyncCheckTransients1_A
+select_coverage -toggle -module prim_secded_inv_64_57_dec
+select_coverage -toggle -module prim_secded_inv_39_32_dec
diff --git a/hw/dv/tools/xcelium/cover_reg_top.ccf b/hw/dv/tools/xcelium/cover_reg_top.ccf
index d82fe5a..ba86d3d 100644
--- a/hw/dv/tools/xcelium/cover_reg_top.ccf
+++ b/hw/dv/tools/xcelium/cover_reg_top.ccf
@@ -8,6 +8,9 @@
 // Only collect code coverage on the *_reg_top instance.
 deselect_coverage -betfs -module ${DUT_TOP}...
 select_coverage -befs -module *_reg_top...
+deselect_coverage -betfs -module prim_onehot_check...
+deselect_coverage -betfs -module prim_secded_inv_64_57_dec...
+deselect_coverage -betfs -module prim_secded_inv_39_32_dec...
 
 // Black-box DV CDC module.
 deselect_coverage -betfs -module prim_cdc_rand_delay
@@ -24,6 +27,8 @@
 // Include toggle coverage on `prim_alert_sender` because the `alert_test` task under
 // `cip_base_vseq` drives `alert_test_i` and verifies `alert_rx/tx` handshake in each IP.
 select_coverage -toggle -module prim_alert_sender
+select_coverage -toggle -module prim_secded_inv_64_57_dec
+select_coverage -toggle -module prim_secded_inv_39_32_dec
 
 // TODO: The intent below is to only enable coverage on the DUT's TL interfaces (tests using this
 // ccf file are meant to fully exercise the TL interfaces, but they do not verify the rest of the
diff --git a/hw/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc b/hw/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc
index 1d7bd61..53589d9 100644
--- a/hw/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc
+++ b/hw/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc
@@ -76,6 +76,7 @@
     bad_fmt = true;
   } else {
     char *txt_end;
+    errno = 0;
     *arg_val = strtoul(arg_text, &txt_end, 0);
 
     // If txt_end doesn't point at a \0 then we didn't read the entire
@@ -305,7 +306,7 @@
   std::cout << std::endl
             << "Simulation statistics" << std::endl
             << "=====================" << std::endl
-            << "Executed cycles:  " << time_ / 2 << std::endl
+            << "Executed cycles:  " << std::dec << time_ / 2 << std::endl
             << "Wallclock time:   " << GetExecutionTimeMs() / 1000.0 << " s"
             << std::endl
             << "Simulation speed: " << speed_hz << " cycles/s "
diff --git a/hw/formal/README.md b/hw/formal/README.md
new file mode 100644
index 0000000..032111b
--- /dev/null
+++ b/hw/formal/README.md
@@ -0,0 +1,336 @@
+# OpenTitan Assertions
+
+# OpenTitan Assertions
+
+
+## What Are Assertions?
+Assertions are statements about your design that are expected to be always true.
+Here are two examples:
+*   <code>`ASSERT(grantOneHot, $onehot0(grant), clk, !rst_n)</code>
+    <br />This asserts that signal <code>grant</code> will be either
+    one-hot encoded or all-zero.
+*   <code>`ASSERT(ackTwoClocksAfterReq, req |-> ##2 ack, clk, !rst_n)</code>
+    <br />Every time <code>req</code> goes high, <code>ack</code> must be
+    high exactly 2 clock cycles later.
+
+Above examples are using the <code>`ASSERT</code> macro defined in [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv),
+whose four arguments are assertion name, property, clock, and reset (active-high reset).
+
+Assertions are usually added by the designer in the RTL file.
+Assertions can 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), which contains a generic protocol checker for the TileLink-UL standard.
+
+## Types of Assertions
+There are two types of assertions:
+*   **Concurrent assertions** can span time and are triggered by a clock edge.
+See the two examples in the previous section.
+*   **Immediate assertions** do not depend upon a clock edge.
+They are typically used in an initial block to check for correct parameter settings.
+Example:
+```
+initial begin
+  checkFifoWidth: assert (FifoDepth > 0) else begin
+    $error("FifoDepth parameter should be > 0");
+  end
+end
+```
+
+## Useful Macros
+The file [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv) and [prim_assert_standard_macros.svh](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert_standard_macros.svh) define many useful shortcuts that you can use in your RTL code.
+Some of them are detailed below:
+
+### ASSERT(__name, __prop, __clk = `ASSERT_DEFAULT_CLK, __rst = `ASSERT_DEFAULT_RST)
+*   This is a shortcut macro for a generic concurrent assignment.
+*   The first argument is the assertion name.
+    It is recommended to follow the [naming conventions](#naming-conventions).
+    The assertion name should be descriptive, which will help during debug.
+*   The second argument is the assertion property.
+*   The last two are optional arguments to specify the clock and reset signals (active-high reset) if different from default value.
+*   Note that this macro doesn't support a custom error message (such as the $error message in the previous section).
+    However, the macro will print out the property name and the entire property code such as `req |-> ack`.
+
+For example, <code>`ASSERT(myAssertion, req |-> ack, clk, !rst_n)</code>
+is expanded as follows:
+```
+myAssertion: assert property (
+  @(posedge clk) disable iff ((!rst_n) !== 1'b0)
+    (req |-> ack)
+) else begin
+  $error("Assert failed: [%m] %s: %s\n",
+      `STRINGIFY(myAssertion), `STRINGIFY(req |-> ack));
+end
+```
+### `ASSERT_INIT(__name, __prop)
+Concurrent assertion inside an initial block. It can be used for checking parameters.
+
+### `ASSERT_FINAL(__name, __prop)
+Concurrent assertion inside a final block. It can be used e.g. for making sure that a FIFO is empty at the end of each sim.
+
+### `ASSERT_NEVER(__name, __prop,  __clk = `ASSERT_DEFAULT_CLK, __rst = `ASSERT_DEFAULT_RST)
+Assert that a concurrent property never happens.
+
+### `ASSERT_KNOWN(__name, __signal,  __clk = `ASSERT_DEFAULT_CLK, __rst = `ASSERT_DEFAULT_RST)
+Assert that `signal` has a known value after reset, where "known" refers to a value that is not X.
+
+### More Macros and Examples
+*   For more macros see file [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv) and [prim_assert_standard_macros.svh](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert_standard_macros.svh).
+*   For more examples, search the repository for ASSERT by typing `grep -r ASSERT .`
+*   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).
+
+## Useful SVA System Functions
+Below table lists useful SVA (SystemVerilog assertion) functions that can be used for assertion properties.
+
+<table>
+  <tr>
+   <td><strong>System Function</strong>
+   </td>
+   <td><strong>Description</strong>
+   </td>
+  </tr>
+  <tr>
+   <td>$rose()
+   </td>
+   <td>True if LSB of expression changed to 1
+   </td>
+  </tr>
+  <tr>
+   <td>$fell()
+   </td>
+   <td>True if LSB of expression changed to 0
+   </td>
+  </tr>
+  <tr>
+   <td>$stable()
+   </td>
+   <td>True if the value of expression didn't change
+   </td>
+  </tr>
+  <tr>
+   <td>$past()
+   </td>
+   <td>Value of expression of previous clock cycle
+   </td>
+  </tr>
+  <tr>
+   <td>$past( , n_cycles)
+   </td>
+   <td>Value of expression n_cycles clocks ago
+   </td>
+  </tr>
+  <tr>
+   <td>$countones()
+   </td>
+   <td>Number of ones in the expression
+   </td>
+  </tr>
+  <tr>
+   <td>$onehot()
+   </td>
+   <td>True if exactly one bit is 1
+   </td>
+  </tr>
+  <tr>
+   <td>$onehot0()
+   </td>
+   <td>True if no bits or only one bit is 1
+   </td>
+  </tr>
+  <tr>
+   <td>$isunknown()
+   </td>
+   <td>True if any bit in the expression is 'X' or 'Z'
+   </td>
+  </tr>
+</table>
+
+## Useful SVA Operators
+Below table lists useful operators that can be used for assertion properties.
+
+<table>
+  <tr>
+   <td><strong>Operator</strong>
+   </td>
+   <td><strong>Description</strong>
+   </td>
+  </tr>
+  <tr>
+   <td>##n
+   </td>
+   <td>Delay operator, fixed time interval of n clock cycles
+   </td>
+  </tr>
+  <tr>
+   <td>##[m:n]
+   </td>
+   <td>Delay operator, time interval range between m and n clocks
+   </td>
+  </tr>
+  <tr>
+   <td>|->
+   </td>
+   <td>"Overlapping" implication (same cycle)
+   </td>
+  </tr>
+  <tr>
+   <td>|=>
+   </td>
+   <td>"Non-overlapping" implication (next cycle)
+     <br /> <code>a |=> b</code> is equivalent to <code>a |-> ##1 b</code>
+   </td>
+  </tr>
+  <tr>
+   <td>not, or, and
+   </td>
+   <td>Property operators
+   </td>
+  </tr>
+</table>
+
+There are also powerful repetition operators, see [here](https://www.systemverilog.io/sva-basics) for more details.
+
+## Symbolic Variables
+
+When design has a set of modules or signals that share same properties, symbolic variables can be used to reduce duplicated assertions.
+For example, in the [rv_plic design](../ip_templates/rv_plic/README.md), the array of input `intr_src_i` are signals sharing the same properties.
+Each `intr_src_i[index]` will trigger the interrupt pending (`ip`) signal depending on the corresponding level indicator (`le`) is set to level triggered or edge triggered.
+Without symbolic variables, the above assertions can be implemented as below:
+```systemverilog
+  genvar i;
+  generate for (i = 0; i < N_SOURCE; i++) begin : gen_rv_plic_fpv
+    `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[i]) |->
+            $past(rv_plic.le[i]) || $past(intr_src_i[i]), clk_i, !rst_ni)
+  end
+```
+
+In contrast, symbolic variable can abstract the design by declaring the index with constraints.
+To ensure the symbolic variable performs the expected behaviors, two assumptions need to be written:
+* Constraint the symbolic variable with the correct bound.
+* Randomize the variable at the beginning of the simulation, then keep it stable throughout the rest of the simulation.
+```systemverilog
+  logic [$clog2(N_SOURCE)-1:0] src_sel;
+  `ASSUME_FPV(IsrcRange_M, src_sel >= 0 && src_sel < N_SOURCE, clk_i, !rst_ni)
+  `ASSUME_FPV(IsrcStable_M, ##1 $stable(src_sel), clk_i, !rst_ni)
+  `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[src_sel]) |->
+          $past(rv_plic.le[src_sel]) || $past(intr_src_i[src_sel]), clk_i, !rst_ni)
+```
+
+## Coverpoints
+Coverpoints are used for properties and corner cases that the designer wants to make sure are being exercised by the testbench (e.g. FIFO-full checks).
+The code coverage tool then reports the coverage percentage of these coverpoints together with the other cover metrics (such as line coverage and branch coverage).
+
+The macro <code>`COVER(name, prop, clk, rst)</code> of [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv) can be used to add coverpoints to your design, where the cover property uses the same SVA syntax, operators, and system functions as the the assert properties.
+
+## Running FPV on DVSim
+
+### Cadence JasperGold
+If you have access to JasperGold from Cadence, you can formally verify your assertions.
+Before running FPV, please make sure the target has been added to one of the [three batch scripts](../../doc/guides/getting_started/src/setup_formal.md#formal-property-verification-fpv).
+
+For example, to run formal property verification (FPV) using JasperGold on the prim module `prim_arbiter_fixed`, type:
+```
+  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_prim_fpv_cfgs.hjson --select-cfgs prim_arbiter_fixed_fpv
+```
+JasperGold will then report which assertions have been proven or disproven, and whether or not there are any unreachable assertions or coverpoints.
+Adding a `--gui` option will open the JasperGold GUI.
+
+To run formal property verification for all prim modules, type:
+```
+  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_prim_fpv_cfgs.hjson
+```
+
+### Synopsys VC Formal
+
+If you have access to VC Formal from Synopsys, you can formally verify your assertions.
+For example, to run formal property verification (FPV) using VC Formal on module `gpio`, type:
+```
+  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_prim_fpv_cfgs.hjson --select-cfgs prim_arbiter_fixed_fpv -t vcformal
+```
+VC Formal will then report which assertions have been proven or disproven, and whether or not there are any unreachable assertions or coverpoints.
+Adding a `--gui` option will open the VCFormal GUI.
+
+To run formal property verification for all prim modules, type:
+```
+  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_prim_fpv_cfgs.hjson -t vcformal
+```
+This script generates a report of all FPV runs.
+The report is printed at the end of the run, which lists the total number of assertions and the number of proven, vacuous, covered and failing assertions for each block. CRASH identifies modules that fail to run VC Formal.
+
+## Running Connectivity Tests
+Connectivity tests use formal method to exhaustively verify system-level connections, which are specified in a high-level format (for example: CSV format for JasperGold).
+
+### Cadence JasperGold on dvsim
+The `dvsim` formal flow supports connectivity test. Each top-level can create its own connectivity setting with a customized Hjson file.
+For example, `top_earlgrey` has `hw/top_earlgrey/formal/chip_conn_cfgs.hjson` that specifies its top_level name, fusesoc_core file, and csv file path.
+You can trigger top_earlgrey's connectivity test using `dvsim`:
+```
+  util/dvsim/dvsim.py hw/top_earlgrey/formal/chip_conn_cfgs.hjson
+```
+Adding a `--gui` option will open the JaperGold GUI.
+
+## Running FPV on security blocks for common countermeasure primitives
+A [security countermeasure verification framework](../../doc/contributing/dv/sec_cm_dv_framework/README.md) is implemented in design and fpv tcl script to verify common countermeasure primitives in a semi-automated way.
+
+### Common security assertion macros
+OpenTitan's security IP blocks have implemented assertions to check against common fault injections.
+These assertions ensure if the security prim module returns an error, a corresponding alert should fire immediately without any gating conditions.
+There are three pre-defined assertion macros under file `hw/ip/prim/rtl/prim_assert_sec_cm.svh`:
+* ASSERT_PRIM_FSM_TRIGGER_ALERT: if design module `prim_sparse_fsm_flop` returns `state_o` value that is not one of the defined FSM states, which means the FSM state might be attacked, a fatal alert is expected to fire.
+* ASSERT_PRIM_COUNT_TRIGGER_ALERT: if design module `prim_count` sets `err_o` to 1, which means the two counters do not match, a fatal alert is expected to fire.
+* ASSERT_PRIM_DOUBLE_LFSR_TRIGGER_ALERT: if design module `prim_double_lfsr` sets `err_o` to 1, which means two LFSR states do not match, a fatal alert is expected to fire.
+Note that assertions defined with these macros will have a common prefix name `FpvSecCm`, which will help the FPV tcl file to group them in a specific task.
+
+### FPV fault injection
+The above security assertions are expected to be unreachable in normal design environment.
+They are only reachable if a fault is injected.
+To create a FPV environment that has fault injections, the `stopat` command and black-box methods are used.
+* FSM fault injection: a `stopat` is placed at `state_o` output.
+* Counter fault injection: black-box the prim_count module.
+* Double LFSR fault injection: black-box the prim_double_lfsr module.
+Then we will let formal environment to randomly drive these outputs and run security assertions to ensure these error cases will trigger alerts under any circumstances.
+
+### Set up FPV security check environment
+To set up the FPV security check environment, please follow the steps below:
+1. Add an item under `hw/top_earlgrey/formal/top_earlgrey_sec_cm_fpv_cfgs.hjson` with the naming convention "{ip_name}_sec_cm".
+2. Under the item add an entry "task" with value "FpvSecCm".
+This entry tells the tcl file to black-box security prim modules in the FPV environment, and define required macros.
+This "task" entry also tells the tcl file to disable regular assertions and only analyze macro defined security assertions with prefix `FpvSecCm`.
+3. Under the item add an entry "stopats" if design has sparse FSMs.
+This is an optional step. Because some designs contain more than one stopat path, the entry "stopats" is declared as a list of strings.
+
+Here is an example on csrng module:
+```
+{
+  name: csrng_sec_cm
+  dut: csrng
+  fusesoc_core: lowrisc:dv:csrng_sva
+  import_cfgs: ["{proj_root}/hw/formal/tools/dvsim/common_fpv_cfg.hjson"]
+  rel_path: "hw/ip/csrng/{sub_flow}/{tool}"
+  cov: false
+  task: "FpvSecCm"
+  stopats: ["*u_state_regs.state_o"]
+}
+```
+
+## Naming Conventions
+For assertions, it is preferred to use postfix `_A` for assertions, `_M` for assumptions, `_P` for properties, and `_S` for sequences.
+For example:
+```systemverilog
+  `ASSUME_FPV(IsrcRange_M, src_sel >= 0 && src_sel < N_SOURCE, clk_i, !rst_ni)
+  `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[src_sel]) |->
+          $past(rv_plic.le[src_sel]) || $past(intr_src_i[src_sel]), clk_i, !rst_ni)
+```
+
+## Implementation Guidelines
+The recommended guidelines for where to implement assertions are as follows:
+* Basic assertions should be implemented directly in the RTL file.
+  These basic functional assertions are often inserted by designers to act as a smoke check.
+* Assertions used for the testbench to achieve verification goals should be implemented under the `ip/hw/module_name/fpv/vip` folder.
+  This FPV environment can be automatically generated by the [`fpvgen.py` script](../../util/fpvgen/README.md).
+* Portable assertions written for common interfaces or submodules should also be implemented under the `ip/hw/submodule_or_interface/fpv/vip` folder.
+  These portable assertion collections can be easily reused by other testbench via a bind file.
+
+## References
+*   [SVA Basics](https://www.systemverilog.io/sva-basics)
+*   [SVA Tutorial](https://www.doulos.com/knowhow/sysverilog/tutorial/assertions/)
+*   "SystemVerilog Assertions Design Tricks and SVA Bind Files", SNUG 2009,
+Clifford E. Cummings,
+[paper](http://www.sunburst-design.com/papers/CummingsSNUG2009SJ_SVA_Bind.pdf)
diff --git a/hw/formal/doc/index.md b/hw/formal/doc/index.md
deleted file mode 100644
index 3609d53..0000000
--- a/hw/formal/doc/index.md
+++ /dev/null
@@ -1,338 +0,0 @@
----
-title: "OpenTitan Assertions"
----
-
-# OpenTitan Assertions
-
-
-## What Are Assertions?
-Assertions are statements about your design that are expected to be always true.
-Here are two examples:
-*   <code>`ASSERT(grantOneHot, $onehot0(grant), clk, !rst_n)</code>
-    <br />This asserts that signal <code>grant</code> will be either
-    one-hot encoded or all-zero.
-*   <code>`ASSERT(ackTwoClocksAfterReq, req |-> ##2 ack, clk, !rst_n)</code>
-    <br />Every time <code>req</code> goes high, <code>ack</code> must be
-    high exactly 2 clock cycles later.
-
-Above examples are using the <code>`ASSERT</code> macro defined in [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv),
-whose four arguments are assertion name, property, clock, and reset (active-high reset).
-
-Assertions are usually added by the designer in the RTL file.
-Assertions can 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]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}), which contains a generic protocol checker for the TileLink-UL standard.
-
-## Types of Assertions
-There are two types of assertions:
-*   **Concurrent assertions** can span time and are triggered by a clock edge.
-See the two examples in the previous section.
-*   **Immediate assertions** do not depend upon a clock edge.
-They are typically used in an initial block to check for correct parameter settings.
-Example:
-```
-initial begin
-  checkFifoWidth: assert (FifoDepth > 0) else begin
-    $error("FifoDepth parameter should be > 0");
-  end
-end
-```
-
-## Useful Macros
-The file [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv) defines many useful shortcuts that you can use in your RTL code.
-Some of them are detailed below:
-
-### `ASSERT(name, prop, clk, rst)
-*   This is a shortcut macro for a generic concurrent assignment.
-*   The first argument is the assertion name.
-    It is recommended to use lowerCamelCase for the assertion name.
-    The assertio name should be descriptive, which will help during debug.
-*   The second argument is the assertion property.
-*   The last two arguments specify the clock and reset signals (active-high reset).
-*   Note that this macro doesn't support a custom error message (such as the $error message in the previous section).
-    However, the macro will print out the property name and the entire property code such as `req |-> ack`.
-
-For example, <code>`ASSERT(myAssertion, req |-> ack, clk, !rst_n)</code>
-is expanded as follows:
-```
-myAssertion: assert property (
-  @(posedge clk) disable iff ((!rst_n) !== 1'b0)
-    (req |-> ack)
-) else begin
-  $error("Assert failed: [%m] %s: %s\n",
-      `STRINGIFY(myAssertion), `STRINGIFY(req |-> ack));
-end
-```
-### `ASSERT_INIT(name, prop)
-Concurrent assertion inside an initial block. It can be used for checking parameters.
-
-### `ASSERT_FINAL(name, prop)
-Concurrent assertion inside a final block. It can be used e.g. for making sure that a FIFO is empty at the end of each sim.
-
-### `ASSERT_NEVER(name, prop, clk, rst)
-Assert that a concurrent property never happens.
-
-### `ASSERT_KNOWN(name, signal, clk, rst)
-Assert that `signal` has a known value after reset, where "known" refers to a value that is not X.
-
-### More Macros and Examples
-*   For more macros see file [prim_assert.sv](prim_assert.sv).
-*   For more examples, search the repository for ASSERT by typing `grep -r ASSERT .`
-*   Also see [tlul_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/tlul/rtl/tlul_assert.sv) and its [documentation]({{< relref "hw/ip/tlul/doc/TlulProtocolChecker.md" >}}).
-
-## Useful SVA System Functions
-Below table lists useful SVA (SystemVerilog assertion) functions that can be used for assertion properties.
-
-<table>
-  <tr>
-   <td><strong>System Function</strong>
-   </td>
-   <td><strong>Description</strong>
-   </td>
-  </tr>
-  <tr>
-   <td>$rose()
-   </td>
-   <td>True if LSB of expression changed to 1
-   </td>
-  </tr>
-  <tr>
-   <td>$fell()
-   </td>
-   <td>True if LSB of expression changed to 0
-   </td>
-  </tr>
-  <tr>
-   <td>$stable()
-   </td>
-   <td>True if the value of expression didn't change
-   </td>
-  </tr>
-  <tr>
-   <td>$past()
-   </td>
-   <td>Value of expression of previous clock cycle
-   </td>
-  </tr>
-  <tr>
-   <td>$past( , n_cycles)
-   </td>
-   <td>Value of expression n_cycles clocks ago
-   </td>
-  </tr>
-  <tr>
-   <td>$countones()
-   </td>
-   <td>Number of ones in the expression
-   </td>
-  </tr>
-  <tr>
-   <td>$onehot()
-   </td>
-   <td>True if exactly one bit is 1
-   </td>
-  </tr>
-  <tr>
-   <td>$onehot0()
-   </td>
-   <td>True if no bits or only one bit is 1
-   </td>
-  </tr>
-  <tr>
-   <td>$isunknown()
-   </td>
-   <td>True if any bit in the expression is 'X' or 'Z'
-   </td>
-  </tr>
-</table>
-
-## Useful SVA Operators
-Below table lists useful operators that can be used for assertion properties.
-
-<table>
-  <tr>
-   <td><strong>Operator</strong>
-   </td>
-   <td><strong>Description</strong>
-   </td>
-  </tr>
-  <tr>
-   <td>##n
-   </td>
-   <td>Delay operator, fixed time interval of n clock cycles
-   </td>
-  </tr>
-  <tr>
-   <td>##[m:n]
-   </td>
-   <td>Delay operator, time interval range between m and n clocks
-   </td>
-  </tr>
-  <tr>
-   <td>|->
-   </td>
-   <td>"Overlapping" implication (same cycle)
-   </td>
-  </tr>
-  <tr>
-   <td>|=>
-   </td>
-   <td>"Non-overlapping" implication (next cycle)
-     <br /> <code>a |=> b</code> is equivalent to <code>a |-> ##1 b</code>
-   </td>
-  </tr>
-  <tr>
-   <td>not, or, and
-   </td>
-   <td>Property operators
-   </td>
-  </tr>
-</table>
-
-There are also powerful repetition operators, see [here](https://www.systemverilog.io/sva-basics) for more details.
-
-## Symbolic Variables
-
-When design has a set of modules or signals that share same properties, symbolic variables can be used to reduce duplicated assertions.
-For example, in the [rv_plic design](../ip/rv_plic/doc/_index.md), the array of input `intr_src_i` are signals sharing sam properties.
-Each `intr_src_i[index]` will trigger the interrupt pending (`ip`) signal depending on the corresponding level indicator (`le`) is set to level triggered or edge triggered.
-Without symbolic variables, the above assertions can be implemented as below:
-```systemverilog
-  genvar i;
-  generate for (i = 0; i < N_SOURCE; i++) begin : gen_rv_plic_fpv
-    `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[i]) |->
-            $past(rv_plic.le[i]) || $past(intr_src_i[i]), clk_i, !rst_ni)
-  end
-```
-
-In contrast, symbolic variable can abstract the design by declaring the index with constraints.
-To ensure the symbolic variable performs the expected behaviors, two assumptions need to be written:
-* Constraint the symbolic variable with the correct bound.
-* Randomize the variable at the beginning of the simulation, then keep it stable throughout the rest of the simulation.
-```systemverilog
-  logic [$clog2(N_SOURCE)-1:0] src_sel;
-  `ASSUME_FPV(IsrcRange_M, src_sel >= 0 && src_sel < N_SOURCE, clk_i, !rst_ni)
-  `ASSUME_FPV(IsrcStable_M, ##1 $stable(src_sel), clk_i, !rst_ni)
-  `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[src_sel]) |->
-          $past(rv_plic.le[src_sel]) || $past(intr_src_i[src_sel]), clk_i, !rst_ni)
-```
-
-## Coverpoints
-Coverpoints are used for properties and corner cases that the designer wants to make sure are being exercised by the testbench (e.g. FIFO-full checks).
-The code coverage tool then reports the coverage percentage of these coverpoints together with the other cover metrics (such as line coverage and branch coverage).
-
-The macro <code>`COVER(name, prop, clk, rst)</code> of [prim_assert.sv](https://github.com/lowRISC/opentitan/blob/master/hw/ip/prim/rtl/prim_assert.sv) can be used to add coverpoints to your design, where the cover property uses the same SVA syntax, operators, and system functions as the the assert properties.
-
-## Running FPV on DVSim
-
-### Cadence JasperGold
-If you have access to JasperGold from Cadence, you can formally verify your assertions.
-Before running FPV, please make sure the target has been added to the [batch script](https://github.com/lowRISC/opentitan/blob/master/hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson).
-
-For example, to run formal property verification (FPV) using JasperGold on module `gpio`, type:
-```
-  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson --select-cfgs gpio
-```
-JasperGold will then report which assertions have been proven or disproven, and whether or not there are any unreachable assertions or coverpoints.
-Adding a `--gui` option will open the JasperGold GUI.
-
-To run formal property verification for all modules, type:
-```
-  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson
-```
-
-### Synopsys VC Formal
-
-If you have access to VC Formal from Synopsys, you can formally verify your assertions.
-For example, to run formal property verification (FPV) using VC Formal on module `gpio`, type:
-```
-  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson --select-cfgs gpio -t vcformal
-```
-VC Formal will then report which assertions have been proven or disproven, and whether or not there are any unreachable assertions or coverpoints.
-Adding a `--gui` option will open the VCFormal GUI.
-
-To run formal property verification for all modules, type:
-```
-  $REPO_TOP/util/dvsim/dvsim.py $REPO_TOP/hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson -t vcformal
-```
-This script generates a report of all FPV runs.
-The report is printed at the end of the run, which lists the total number of assertions and the number of proven, vacuous,
-covered and failing assertions for each block. CRASH identifies modules that fail to run VC Formal.
-
-## Running Connectivity Tests
-Connectivity tests use formal method to exhaustively verify system-level connections, which are specified in a high-level format (for example: CSV format for JasperGold).
-
-### Cadence JasperGold on dvsim
-The `dvsim` formal flow supports connectivity test. Each top-level can create its own connectivity setting with a customized Hjson file.
-For example, `top_earlgrey` has `hw/top_earlgrey/formal/chip_conn_cfgs.hjson` that specifies its top_level name, fusesoc_core file, and csv file path.
-You can trigger top_earlgrey's connectivity test using `dvsim`:
-```
-  util/dvsim/dvsim.py hw/top_earlgrey/formal/chip_conn_cfgs.hjson
-```
-Adding a `--gui` option will open the JaperGold GUI.
-
-## Running FPV on security blocks for common countermeasure primitives
-A [security countermeasure verification framework]({{< relref "doc/ug/sec_cm_dv_framework" >}}) is implemented in design and fpv tcl script to verify common countermeasure primitives in a semi-automated way.
-
-### Common security assertion macros
-OpenTitan's security IP blocks have implemented assertions to check against common fault injections.
-These assertions ensure if the security prim module returns an error, a corresponding alert should fire immediately without any gating conditions.
-There are three pre-defined assertion macros under file `hw/ip/prim/rtl/prim_assert_sec_cm.svh`:
-* ASSERT_PRIM_FSM_TRIGGER_ALERT: if design module `prim_sparse_fsm_flop` returns `state_o` value that is not one of the defined FSM states, which means the FSM state might be attacked, a fatal alert is expected to fire.
-* ASSERT_PRIM_COUNT_TRIGGER_ALERT: if design module `prim_count` sets `err_o` to 1, which means the two counters do not match, a fatal alert is expected to fire.
-* ASSERT_PRIM_DOUBLE_LFSR_TRIGGER_ALERT: if design module `prim_double_lfsr` sets `err_o` to 1, which means two LFSR states do not match, a fatal alert is expected to fire.
-Note that assertions defined with these macros will have a common prefix name `FpvSecCm`, which will help the FPV tcl file to group them in a specific task.
-
-### FPV fault injection
-The above security assertions are expected to be unreachable in normal design environment, and are only reachable if a fault is injected.
-To create a FPV environment that has fault injections, the `stopat` command and black box methods are used.
-* FSM fault injection: a `stopat` is placed at `state_o` output.
-* Counter fault injection: black-box the prim_count module.
-* Double LFSR fault injection: black-box the prim_double_lfsr module.
-Then we will let formal environment to randomly drive these outputs and run security assertions to ensure these error cases will trigger alerts under any circumstance.
-
-### Set up FPV security check environment
-To set up the FPV security check environment, please follow the steps below:
-1. Add an item under `hw/top_earlgrey/formal/top_earlgrey_fpv_cfgs.hjson` with the naming convention "{ip_name}_sec_cm".
-2. Under the item add an entry "task" with value "FpvSecCm".
-This entry tells the tcl file to black-box security prim modules in the FPV environment, and define required macros.
-This "task" entry also tells the tcl file to disable regular assertions and only analyze macro defined security assertions with prefix `FpvSecCm`.
-3. Under the item add an entry "stopats" if design has sparse FSMs.
-This is an optional step. Because some designs contain more than one stopat path, the entry "stopats" is declared as a list of strings.
-
-Here is an example on csrng module:
-```
-{
-  name: csrng_sec_cm
-  dut: csrng
-  fusesoc_core: lowrisc:dv:csrng_sva
-  import_cfgs: ["{proj_root}/hw/formal/tools/dvsim/common_fpv_cfg.hjson"]
-  rel_path: "hw/ip/csrng/{sub_flow}/{tool}"
-  cov: false
-  task: "FpvSecCm"
-  stopats: ["*u_state_regs.state_o"]
-}
-```
-
-## Naming Conventions
-For assertions, it is preferred to use postfix `_A` for assertions, `_M` for assumptions, `_P` for properties, and `_S` for sequences.
-For example:
-```systemverilog
-  `ASSUME_FPV(IsrcRange_M, src_sel >= 0 && src_sel < N_SOURCE, clk_i, !rst_ni)
-  `ASSERT(LevelTriggeredIp_A, $rose(rv_plic.ip[src_sel]) |->
-          $past(rv_plic.le[src_sel]) || $past(intr_src_i[src_sel]), clk_i, !rst_ni)
-```
-
-## Implementation Guidelines
-The recommended guidelines for where to implement assertions are as follows:
-* Basic assertions should be implemented directly in the RTL file.
-  These basic functional assertions are often inserted by designers to act as a smoke check.
-* Assertions used for the testbench to achieve verification goals should be implemented under the `ip/hw/module_name/fpv/vip` folder.
-  This FPV environment can be automatically generated by the [`fpvgen.py` script](../../util/fpvgen/doc).
-* Portable assertions written for common interfaces or submodules should also be implemented under the `ip/hw/submodule_or_interface/fpv/vip` folder.
-  These portable assertion collections can be easily reused by other testbench via a bind file.
-
-## References
-*   [SVA Basics](https://www.systemverilog.io/sva-basics)
-*   [SVA Tutorial](https://www.doulos.com/knowhow/sysverilog/tutorial/assertions/)
-*   "SystemVerilog Assertions Design Tricks and SVA Bind Files", SNUG 2009,
-Clifford E. Cummings,
-[paper](http://www.sunburst-design.com/papers/CummingsSNUG2009SJ_SVA_Bind.pdf)
diff --git a/hw/formal/tools/dvsim/common_formal_cfg.hjson b/hw/formal/tools/dvsim/common_formal_cfg.hjson
index d7e333a..edc70fd 100644
--- a/hw/formal/tools/dvsim/common_formal_cfg.hjson
+++ b/hw/formal/tools/dvsim/common_formal_cfg.hjson
@@ -36,22 +36,25 @@
   report_opts: ["--logpath={build_log}",
                 "--reppath={build_dir}/results.hjson",
                 "--cov={cov}",
-                "--dut={dut}"]
+                "--dut={dut}",
+                "--exp-fail-path={exp_fail_hjson}"]
 
-  // Common fail patterns.
+  // Common pass or fail patterns.
   build_fail_patterns: [// FuseSoC build error
                         "^ERROR:.*$"]
 
   defines: ""
   bbox_cmd: ""
+  exp_fail_hjson: ""
 
   // Vars that need to exported to the env
   exports: [
-    { DUT_TOP:     "{dut}" },
-    { COV:         "{cov}" },
-    { sub_flow:    "{sub_flow}"},
-    { FPV_DEFINES: "{defines}"},
-    { BBOX_CMD:    "'{bbox_cmd}'"},
+    { DUT_TOP:        "{dut}" },
+    { COV:            "{cov}" },
+    { EXP_FAIL_HJSON: "'{exp_fail_hjson}'" },
+    { sub_flow:       "{sub_flow}" },
+    { FPV_DEFINES:    "{defines}" },
+    { BBOX_CMD:       "'{bbox_cmd}'" },
   ]
 
   overrides: [
diff --git a/hw/formal/tools/jaspergold/conn.tcl b/hw/formal/tools/jaspergold/conn.tcl
index 8ea8eaf..f2273d7 100644
--- a/hw/formal/tools/jaspergold/conn.tcl
+++ b/hw/formal/tools/jaspergold/conn.tcl
@@ -43,16 +43,17 @@
 
 elaborate -top $env(DUT_TOP)
 
-# Add this assumption to avoid a false functional loop.
-assume -env {top_earlgrey.u_pinmux_aon.reg2hw.mio_pad_sleep_status == '1}
-
 # Currently only for top_earlgrey
-# Because in JasperGold we can only drive primary inputs. We put a stopat to aovid clock input
-# from being driven internally.
 if {$env(DUT_TOP) == "chip_earlgrey_asic"} {
+  # Because in JasperGold we can only drive primary inputs. We put a stopat to
+  # aovid clock input from being driven internally.
   stopat -env IOC6
   clock IOC6
   reset -expr {POR_N}
+  # Add this assumption to avoid a false functional loop.
+  assume -env {top_earlgrey.u_pinmux_aon.reg2hw.mio_pad_sleep_status == '1}
+  # Add this assumption to avoid signal inversion in the pad wrappers.
+  assume -env {top_earlgrey.u_pinmux_aon.dio_pad_attr_q == '0}
 }
 
 #-------------------------------------------------------------------------
diff --git a/hw/formal/tools/jaspergold/fpv.tcl b/hw/formal/tools/jaspergold/fpv.tcl
index 2f36765..67542e3 100644
--- a/hw/formal/tools/jaspergold/fpv.tcl
+++ b/hw/formal/tools/jaspergold/fpv.tcl
@@ -26,7 +26,8 @@
 # read design
 #-------------------------------------------------------------------------
 
-# TODO: better way to handle macro define. Consider use it as an input for sim_cfg.
+# Blackbox prim_count, prim_double_lfsr, and prim_onehot_check to create security countermeasures.
+# Blackbox prim_ram_1p and prim_ram_1p_scr to avoid compiling memory blocks.
 if {$env(TASK) == "FpvSecCm"} {
   analyze -sv09 \
     +define+FPV_ON \
@@ -34,6 +35,8 @@
     -bbox_m prim_count \
     -bbox_m prim_double_lfsr \
     -bbox_m prim_onehot_check \
+    -bbox_m prim_ram_1p \
+    -bbox_m prim_ram_1p_scr \
     -f [glob *.scr]
 } elseif {$env(DUT_TOP) == "pinmux_tb"} {
   analyze -sv09 \
@@ -50,8 +53,7 @@
   elaborate -top $env(DUT_TOP) \
             -enable_sva_isunknown \
             -disable_auto_bbox \
-            -param OutSelDnCnt $OutSelDnCnt \
-            -param CntStyle $CntStyle
+            -param ResetValue $ResetValue
 } else {
   elaborate -top $env(DUT_TOP) -enable_sva_isunknown -disable_auto_bbox
 }
@@ -70,8 +72,27 @@
 # tlul_assert.sv operates on the negedge clock
 # even clock this sampled at both_edges, input should only change at posedge clock cycle
 # TODO: create each DUT_TOP's individual config file
+if {$env(DUT_TOP) == "pinmux_tb"} {
+  clock clk_i -both_edges
+  clock clk_aon_i -factor 5
+  clock -rate -default clk_i
+  reset -expr {!rst_ni !rst_aon_ni !rst_sys_ni}
 
-if {$env(DUT_TOP) == "rv_dm"} {
+} elseif {$env(DUT_TOP) == "prim_fifo_async_sram_adapter_tb"} {
+  clock clk_wr_i -factor 2
+  clock -rate {wvalid_i, wready_o, wdata_i} clk_wr_i
+  clock clk_rd_i -factor 3
+  clock -rate {rvalid_o, rready_i, rdata_o} clk_rd_i
+  reset -expr {!rst_ni}
+
+} elseif {$env(DUT_TOP) == "pwrmgr"} {
+  clock clk_i -both_edges
+  clock clk_slow_i -factor 3
+  clock clk_lc_i
+  clock -rate {esc_rst_tx_i} clk_lc_i
+  reset -expr {!rst_ni !rst_slow_ni !rst_main_ni !rst_lc_ni}
+
+} elseif {$env(DUT_TOP) == "rv_dm"} {
   clock clk_i -both_edges
   clock jtag_i.tck
   clock -rate {testmode, unavailable_i, reg_tl_d_i, sba_tl_h_i} clk_i
@@ -98,23 +119,26 @@
   clock -rate {tl_i, cio_d_i, cio_dp_i, cio_dn_i, cio_sense_i} clk_i
   reset -expr {!rst_ni !rst_aon_ni}
 
-# TODO: work with the block owner and re-define FPV checkings for xbar
-# } elseif {$env(DUT_TOP) == "xbar_main"} {
-#   clock clk_main_i -both_edges
-#   reset -expr {!rst_main_ni}
-
-} elseif {$env(DUT_TOP) == "prim_fifo_async_sram_adapter_tb"} {
-  clock clk_wr_i -factor 2
-  clock -rate {wvalid_i, wready_o, wdata_i} clk_wr_i
-  clock clk_rd_i -factor 3
-  clock -rate {rvalid_o, rready_i, rdata_o} clk_rd_i
-  reset -expr {!rst_ni}
-
-} elseif {$env(DUT_TOP) == "pinmux_tb"} {
+} elseif {$env(DUT_TOP) == "clkmgr"} {
+  clock clk_main_i
   clock clk_i -both_edges
-  clock clk_aon_i -factor 5
+  clock clk_io_i -factor 1
+  clock clk_usb_i -factor 1
+  clock clk_aon_i -factor 2
   clock -rate -default clk_i
-  reset -expr {!rst_ni !rst_aon_ni}
+  reset -expr {!rst_ni !rst_main_ni}
+
+} elseif {$env(DUT_TOP) == "rstmgr"} {
+  clock clk_main_i
+  clock clk_i -both_edges
+  clock clk_io_i -factor 1
+  clock clk_io_div2_i -factor 1
+  clock clk_io_div4_i -factor 1
+  clock clk_usb_i -factor 1
+  clock clk_aon_i -factor 2
+  clock -rate -default clk_i
+  reset -expr {!rst_ni !rst_por_ni}
+
 } else {
   clock clk_i -both_edges
   reset -expr {!rst_ni}
@@ -124,9 +148,6 @@
 #-------------------------------------------------------------------------
 # disable assertions
 #-------------------------------------------------------------------------
-assert -disable {*SyncCheckTransients_A}
-assert -disable {*SyncCheckTransients0_A}
-assert -disable {*SyncCheckTransients1_A}
 
 #-------------------------------------------------------------------------
 # assume properties for inputs
@@ -194,6 +215,6 @@
 if {$env(COV) == 1} {
   check_cov -measure -time_limit 2h
   check_cov -report -force -exclude { reset waived }
-  check_cov -report -type all -no_return -report_file cover.html \
+  check_cov -report -no_return -report_file cover.html \
       -html -force -exclude { reset waived }
 }
diff --git a/hw/formal/tools/jaspergold/jaspergold.hjson b/hw/formal/tools/jaspergold/jaspergold.hjson
index 0d89671..1c65f63 100644
--- a/hw/formal/tools/jaspergold/jaspergold.hjson
+++ b/hw/formal/tools/jaspergold/jaspergold.hjson
@@ -7,6 +7,9 @@
                "-proj jgproject",
                "-allow_unsupported_OS"]
 
+  // If this line is not seen in the log, then fail the test.
+  build_pass_patterns: ["^INFO: Proof threads stopped\\.$"]
+
   exports: [
     {COMMON_MSG_TCL_PATH: "{formal_root}/tools/{tool}/jaspergold_common_message_process.tcl"}
   ]
diff --git a/hw/formal/tools/jaspergold/parse-formal-report.py b/hw/formal/tools/jaspergold/parse-formal-report.py
index 3cdb212..2868806 100644
--- a/hw/formal/tools/jaspergold/parse-formal-report.py
+++ b/hw/formal/tools/jaspergold/parse-formal-report.py
@@ -27,17 +27,37 @@
     return results
 
 
-def extract_messages_count(str_buffer, patterns):
+def extract_messages_count(str_buffer, patterns, exp_unproven_properties):
     '''Extract messages matching patterns from full_file as a dictionary.
 
     The patterns argument is a list of pairs, (key, pattern). Each pattern is a regex
     and the total count of all matches in str_buffer are stored in a dictionary under
     the paired key.
+    If input argument `exp_unproven_properties` is not None, the total count will not
+    include the expected properties. However, if the expected are not found in its
+    category, will add the properties to the category.
     '''
     results = OrderedDict()
     for key, pattern in patterns:
         results.setdefault(key, 0)
-        results[key] += len(re.findall(pattern, str_buffer, flags=re.MULTILINE))
+
+        if exp_unproven_properties and key in exp_unproven_properties:
+            matched_pattern = re.findall(pattern, str_buffer, flags=re.MULTILINE)
+            for unproven_property in exp_unproven_properties[key]:
+                unproven_property_found = 0
+                for item in matched_pattern:
+                    # If the expected unproven property is found, remove the item from the
+                    # list of matched_pattern.
+                    if unproven_property in item:
+                        matched_pattern.remove(item)
+                        unproven_property_found = 1
+                # If expected unproven property is not found, add the property to the
+                # list of matched pattern.
+                if not unproven_property_found:
+                    matched_pattern.append("Fail to find this property: " + unproven_property)
+            results[key] += len(matched_pattern)
+        else:
+            results[key] += len(re.findall(pattern, str_buffer, flags=re.MULTILINE))
 
     return results
 
@@ -62,7 +82,21 @@
     return extract_messages(str_buffer, err_warn_patterns)
 
 
-def get_summary(str_buffer):
+def get_expected_failures(exp_failure_path):
+    '''Get expected fail properties from a hjson file otherwise return None.'''
+    if exp_failure_path is None or exp_failure_path == "":
+        return {}
+    else:
+        try:
+            with open(exp_failure_path, 'r') as f:
+                exp_failures = hjson.load(f, use_decimal=True, object_pairs_hook=OrderedDict)
+                return exp_failures
+        except ValueError:
+            log.error("{} not found".format(exp_failure_path))
+            return {}
+
+
+def get_summary(str_buffer, exp_failures):
     '''Count errors, warnings, and property status from the log file'''
     message_patterns = [("errors", r"^ERROR: .*"),
                         ("errors", r"^\[ERROR.*"),
@@ -73,7 +107,7 @@
                         ("covered", r"^\[\d+\].*covered.*"),
                         ("undetermined", r"^\[\d+\].*undetermined.*"),
                         ("unreachable", r"^\[\d+\].*unreachable.*")]
-    summary = extract_messages_count(str_buffer, message_patterns)
+    summary = extract_messages_count(str_buffer, message_patterns, exp_failures)
 
     # Undetermined properties are categorized as pass because we could not find
     # any counter-cases within the limited time of running.
@@ -84,16 +118,17 @@
     return summary
 
 
-def get_results(logpath):
+def get_results(logpath, exp_failure_path):
     '''Parse log file and extract info to a dictionary'''
     try:
         with Path(logpath).open() as f:
             results = OrderedDict()
             full_file = f.read()
             results["messages"] = parse_message(full_file)
-            summary = get_summary(full_file)
-            if summary:
-                results["summary"] = summary
+
+            results["exp_failures"] = get_expected_failures(exp_failure_path)
+
+            results["summary"] = get_summary(full_file, results["exp_failures"])
             return results
 
     except IOError as err:
@@ -123,7 +158,7 @@
                     cov_results[key] = "N/A"
                     # Report ERROR but continue the parsing script.
                     log.info("ERROR: parse %s coverage error. Expect one matching value, get %s",
-                              key, item)
+                             key, item)
             return cov_results
 
     except IOError as err:
@@ -186,9 +221,18 @@
                         default=None,
                         help=('Tesbench name. '
                               'By default is empty, used for coverage parsing.'))
+
+    parser.add_argument('--exp-fail-path',
+                        type=str,
+                        default=None,
+                        help=('The path of a hjson file that contains expected failing properties.'
+                              '''By default is empty, used only if there are properties that are
+                               expected to fail. If input is an empty string, will treat it as not
+                               passing a file.'''))
+
     args = parser.parse_args()
 
-    results = get_results(args.logpath)
+    results = get_results(args.logpath, args.exp_fail_path)
 
     if args.cov:
         results["coverage"] = get_cov_results(args.logpath, args.dut)
diff --git a/hw/ip/README.md b/hw/ip/README.md
new file mode 100644
index 0000000..5ea7d6d
--- /dev/null
+++ b/hw/ip/README.md
@@ -0,0 +1,3 @@
+# IP cores
+
+{{% sectionContent type="section" depth="1" %}}
diff --git a/hw/ip/_index.md b/hw/ip/_index.md
deleted file mode 100644
index 422d3c1..0000000
--- a/hw/ip/_index.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "IP cores"
----
-
-{{% sectionContent %}}
diff --git a/hw/ip/adc_ctrl/README.md b/hw/ip/adc_ctrl/README.md
new file mode 100644
index 0000000..71c82c2
--- /dev/null
+++ b/hw/ip/adc_ctrl/README.md
@@ -0,0 +1,265 @@
+# Analog to Digital Converter Control Interface
+
+# Overview
+
+This document specifies the ADC controller IP functionality.
+This IP block implements control and filter logic for an analog block that implements a dual ADC.
+This module conforms to the [Comportable guideline for peripheral functionality.](../../../doc/contributing/hw/comportability/README.md)
+See that document for integration overview within the broader top level system.
+
+## Features
+
+The IP block implements the following features:
+
+- Register interface to dual ADC analog block
+- Support for 2 ADC channels
+- Support for 8 filters on the values from the channels
+- Support ADCs with 10-bit output (two reserved bits in CSR)
+- Support for debounce timers on the filter output
+- Run on a slow always-on clock to enable usage while the device is sleeping
+- Low power periodic scan mode for monitoring ADC channels
+
+## Description
+
+The ADC controller is a simple front-end to an analog block that allows filtering and debouncing of the analog signals.
+
+## Compatibility
+
+The ADC controller programming interface is not based on any existing interface.
+
+# Theory of Operation
+
+The block diagram shows a conceptual view of the ADC controller state machine and filters.
+
+## Block Diagram
+
+![ADC_CTRL Block Diagram](doc/adc_overview.svg)
+
+
+## Hardware Interface
+
+* [Interface Tables](data/adc_ctrl.hjson#interfaces)
+
+### Signals
+
+In addition to the interrupts and bus signals, the tables below lists additional IOs.
+
+Signal                  | Direction | Description
+------------------------|-----------|---------------
+`adc_o`                 | `output`  | Output controls to the actual `AST ADC` module.  Contains signals such as power down control and ADC channel select.
+`adc_i`                 | `input`   | Input data from `AST ADC` module. Contains ADC data output as well as data valid indication.
+
+
+## Design Details
+
+### Sampling state machine
+
+The state machine that takes ADC samples follows a very simple pattern:
+
+1. *Power up ADC*: The controller issues the power up command to the ADC.
+
+2. *Wait for ADC turn on*: The controller waits for the number of clock cycles programmed in [`adc_pd_ctl.pwrup_time`](data/adc_ctrl.hjson#adc_pd_ctl) which should be set to match the ADC power up delay.
+
+3. *Take sample Channel 0*: The ADC is requested to sample channel 0.
+When the ADC signals complete the value is stored in [`adc_chn_val[0].adc_chn_value`](data/adc_ctrl.hjson#adc_chn_val_0).
+Note that the time taken in this step depends on the properties of the ADC.
+
+4. *Take sample Channel 1*: The ADC is requested to sample channel 1.
+When the ADC signals complete the value is stored in [`adc_chn_val[1].adc_chn_value`](data/adc_ctrl.hjson#adc_chn_val_1).
+Note that the time taken in this step depends on the properties of the ADC.
+
+5. *Evaluate Filters*: The filters are evaluated and debounce logic applied (see [next section](#filters-and-debounce)).
+
+6. *Scan type check*: At this point if the [`adc_pd_ctl.lp_mode`](data/adc_ctrl.hjson#adc_pd_ctl) bit is clear scanning continues at step (3).
+   If the bit is set the next step depends on how many samples have hit the filters.
+   If more than [`adc_lp_sample_ctl.lp_sample_cnt`](data/adc_ctrl.hjson#adc_lp_sample_ctl) samples have hit then continuous scanning continues at step (3) else periodic scanning will continue at the next step (7).
+
+7. *Power off ADC*: The controller issues the power down command to the ADC.
+
+8. *Wait sleep time*: The controller will wait for the next sample timer to time out before restarting at step (1).
+
+In active operation the controller is in continuous scanning mode:
+* The ADC is continually powered on.
+* The sampling cycle time is the time taken for the ADC to take two samples (450us) plus internal processing time (4 clock cycles) from the ADC controller.
+* The debounce timer will trigger the [`filter_status`](data/adc_ctrl.hjson#filter_status) and interrupt after a configurable number of matching ADC samples have been seen, as determined by [`adc_sample_ctl`](data/adc_ctrl.hjson#adc_sample_ctl).
+
+For low power operation the periodic scanning mode can be used.
+In this mode samples are taken using a slower periodic sampling cycle time with the ADC powered down most of the time.
+Once a small number of cycles have hit the filter with periodic scanning then the controller switches to continuous scanning in order to more accurately debounce the signal.
+In low power mode:
+* The ADC is periodically powered up to take samples; this interval is determined by [`adc_pd_ctl.wakeup_time`](data/adc_ctrl.hjson#adc_pd_ctl).
+* Similar to normal operation, the ADC power-up delay is controlled by [`adc_pd_ctl.pwrup_time`](data/adc_ctrl.hjson#adc_pd_ctl).
+* Once the ADC is powered up, two samples are taken and compared to the filter thresholds.
+* If a configurable number of matches, as determined by [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl), are seen, the ADC controller transitions to normal operation for continuous sampling.
+
+Although it can be used at any time, the periodic operation mode and use of the slow clock allows the ADC controller to continue to scan when most of the chip is in sleep or power-down modes.
+The controller can be configured to issue a wakeup to the rest of the chip.
+
+If a filter is enabled for wakeup in [`adc_wakeup_ctl`](data/adc_ctrl.hjson#adc_wakeup_ctl) and [`filter_status`](data/adc_ctrl.hjson#filter_status) indicates a match, a wakeup is generated to the system power manager.
+
+
+## Filters and debounce
+
+There are two reserved bits in ADC filter control registers for future use.
+In the current implementation, ADC has 10-bit granularity.
+Each step is 2.148mV.
+It covers 0-2.2V.
+
+The ADC controller implements eight pairs of filters that feed the debounce logic.
+Each pair has a filter for channel 0 and a filter for channel 1.
+
+A filter consists of a `max` value, a `min` value and a `cond` flag indicating if the filter is hit by a sample inside or outside the range.
+* *Inside the range*: the filter is hit if `min` &le; `value` &le; `max`.
+* *Outside the range*: inverse of inside, so the filter is hit if `value` &lt; `min` or `value` &gt; `max`.
+
+Some example filters:
+* Inside `min=7`, `max=23`: any value between and including 7 and 23 will hit.
+* Outside `min=7`, `max=23`: any value less than 7 or greater than 23 will hit.
+* Inside `min=7`, `max=7`: the value must be exactly 7 to hit (sample noise may make an exact hit unlikely).
+* Inside `min=0`, `max=7`: the value must be less than 8 to hit.
+* Outside `min=8`, `max=0xFFF`: the value must be less than 8 to hit (alternate method).
+* Inside `min=0`, `max=0xFFF`: any value will hit. This may be useful to exclude one channel from the filter.
+* Outside `min=0`, `max=0xFFF`: no value will hit. If set for either channel the filter is effectively disabled.
+
+All pairs of filters that are enabled in [`adc_chn0_filter_ctl[7:0]`](data/adc_ctrl.hjson#adc_chn0_filter_ctl_0) and [`adc_chn1_filter_ctl[7:0]`](data/adc_ctrl.hjson#adc_chn1_filter_ctl_0) are evaluated after each pair of samples has been taken.
+The filter result is passed to the periodic scan counter if enabled and not at its limit otherwise the result is passed to the debounce counter.
+The list below describes how the counters interpret the filter results:
+* If no filters are hit then the counter will reset to zero.
+* If one or more filters are hit but the set hit differs from the previous evaluation the counter resets to zero.
+* If one or more filters are hit and either none was hit in the previous evaluation or the same set was hit in the previous evaluation and the counter is not at its threshold then the counter will increment.
+* If one or more filters are hit and the same set was hit in the previous evaluation and the counter is at its threshold then the counter stays at the threshold.
+* If the counter is the periodic scan counter and it reaches its threshold, as defined by [`adc_lp_sample_ctl.lp_sample_cnt`](data/adc_ctrl.hjson#adc_lp_sample_ctl), then continuous scanning is enabled and the debounce counter will be used for future evaluations.
+* If the counter is the debounce counter and it reaches its threshold, as defined by [`adc_sample_ctl.np_sample_cnt`](data/adc_ctrl.hjson#adc_sample_ctl), then:
+  * An interrupt is raised if the threshold is met for the first time.
+  * The current sample values are latched into [`adc_chn_val[0].adc_chn_value_intr`](data/adc_ctrl.hjson#adc_chn_val_0) and  [`adc_chn_val[1].adc_chn_value_intr`](data/adc_ctrl.hjson#adc_chn_val_1).
+    *  If a series of interrupts and matches are seen, these registers only record the value of the last debounced hit.
+  * The [`adc_intr_status`](data/adc_ctrl.hjson#adc_intr_status) register is updated by setting the bits corresponding to filters that are hit (note that bits that are already set will not be cleared).
+    This will cause the block to raise an interrupt if it was not already doing so.
+  * If a filter is a hit and is also enabled in [`adc_wakeup_ctl`](data/adc_ctrl.hjson#adc_wakeup_ctl) the corresponding filter generates a wakeup.
+  * Note that the debounce counter will remain at its threshold until the set of filters are changed by software to debounce a different event or if the current match changes.
+    *  This implies that a stable matching event continuously matches until some condition in the system (changed filter settings, changed ADC output, software issued fsm reset in [`adc_fsm_rst`](data/adc_ctrl.hjson#adc_fsm_rst)) alters the result.
+
+
+Because scanning continues the [`adc_intr_status`](data/adc_ctrl.hjson#adc_intr_status) register will reflect any debounced events that are detected between the controller raising an interrupt and the status bits being cleared (by having 1 written to them).
+However, the [`adc_chn_val[0].adc_chn_value_intr`](data/adc_ctrl.hjson#adc_chn_val_0) and [`adc_chn_val[1].adc_chn_value_intr`](data/adc_ctrl.hjson#adc_chn_val_1) registers record the value at the time the interrupt was first raised and thus reflect the filter state from that point.
+
+### ADC_CTRL and ADC Interface
+
+The interface between the ADC controller and the ADC is diagrammed below.
+The interface is from the perspective of the ADC controller.
+Before operation can begin, the ADC controller first powers on the ADC by setting `adc_o.pd` to 0.
+The controller then waits for the ADC to fully power up, as determined by [`adc_pd_ctl.pwrup_time`](data/adc_ctrl.hjson#adc_pd_ctl).
+
+Once the ADC is ready to go, the controller then selects the channel it wishes to sample by setting `adc_o.channel_sel`.
+The controller holds this value until the ADC responds with `adc_i.data_valid` and `adc_i.data`.
+
+Since there is no request sample signal between the controller and the ADC, the ADC takes a new sample when `adc_o.channel_sel` is changed from 0 to a valid channel.
+To take a new sample then, the controller actively sets `adc_o.channel_sel` to 0, before setting it to another valid channel.
+
+```wavejson
+{
+  signal: [
+    {node: '.a..b........', phase:0.2},
+    {name: 'clk_aon_i',         wave: 'p.|..|.....|....|...'},
+    {name: 'adc_o.pd',          wave: '10|..|.....|....|..1'},
+    {name: 'adc_o.channel_sel', wave: '0.|.3|..04.|....|0..'},
+    {name: 'adc_i.data_valid',  wave: '0.|..|.1.0.|.1..|.0.'},
+    {name: 'adc_i.data',        wave: 'x.|..|.3.x.|.4..|.x.', data: ['ch0', 'ch1', 'ch1']},
+  ],
+  edge: [  'a<->b wakeup time',   ]
+}
+```
+
+# Programmers Guide
+
+## Initialization
+
+The controller should be initialized with the properties of the ADC and scan times.
+* The ADC power up delay must be set in [`adc_pd_ctl.pwrup_time`](data/adc_ctrl.hjson#adc_pd_ctl).
+* The time to delay between samples in a slow scan should be set in [`adc_pd_ctl.wakeup_time`](data/adc_ctrl.hjson#adc_pd_ctl).
+* The number of samples to cause transition from slow to fast scan should be set in [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl).
+* The number of samples for debounce should be set in [`adc_sample_ctl`](data/adc_ctrl.hjson#adc_sample_ctl).
+* The filter registers [`adc_chnX_filter_ctlN`](data/adc_ctrl.hjson#adc_chnx_filter_ctln) should be programmed.
+* The interrupt [`adc_intr_ctl`](data/adc_ctrl.hjson#adc_intr_ctl) and wakeup [`adc_wakeup_ctl`](data/adc_ctrl.hjson#adc_wakeup_ctl) enables should be configured.
+* All ones should be written to [`adc_intr_status`](data/adc_ctrl.hjson#adc_intr_status) and  [`filter_status`](data/adc_ctrl.hjson#filter_status) to ensure there are no spurious pending triggers.
+* Optionally, the low-power mode should be set in [`adc_pd_ctl.lp_mode`](data/adc_ctrl.hjson#adc_pd_ctl) if the system is going to the low-power mode.
+* The state machine will only start running when [`adc_en_ctl`](data/adc_ctrl.hjson#adc_en_ctl) is set.
+
+## Running in normal mode
+
+If fast sampling is always required then the [`adc_pd_ctl.lp_mode`](data/adc_ctrl.hjson#adc_pd_ctl) bit should be clear.
+In this case the values in the [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl) are not used.
+The ADC will always be enabled and consuming power.
+
+If power saving is required then the controller can be set to operate in low power mode by setting [`adc_pd_ctl.lp_mode`](data/adc_ctrl.hjson#adc_pd_ctl).
+The [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl) must be programmed prior to setting this bit.
+
+## Running with the rest of the chip in sleep
+
+Once programmed the controller and ADC can run when the rest of the chip is in low power state and the main clocks are stopped.
+This allows the chip to be woken when appropriate values are detected on the two ADC channels.
+The fast sampling mode can be used but will usually consume more power than desired when the chip is in sleep.
+So it is expected that [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl) is configured and low power mode enabled by setting [`adc_pd_ctl.lp_mode`](data/adc_ctrl.hjson#adc_pd_ctl) prior to the sleep being initiated.
+
+If the ADC wakeup is not required then the controller and ADC should both be disabled by clearing [`adc_en_ctl`](data/adc_ctrl.hjson#adc_en_ctl) prior to the sleep being initiated.
+
+## Use Case
+
+While the ADC controller is meant to be used generically, it can be configured to satisfy more complex use cases.
+As an illustrative example, the programmers guide uses the [Chrome OS Hardware Debug](https://chromium.googlesource.com/chromiumos/third_party/hdctools/+/HEAD/docs/ccd.md) as an example of how the ADC controller can be used.
+
+The debug setup referred to uses a USB-C debug accessory.
+This insertion of this debug accessory into a system, can be detected by the ADC controller.
+
+The debug accessory voltage range of interest is shown in the diagram below:
+![Debug Cable Regions](doc/debug_cable_regions.svg)
+
+The ADC can be used to detect debug cable connection / disconnection in the non-overlapping regions.
+As an example use case of the two channel filters they can be used for detection of a USB-C debug accessory.
+The ADC must meet some minimum specifications:
+* Full scale range is 0.0V to 2.2V
+* If the signal is below 0.0V the ADC value will be zero.
+* If the signal is above 2.2V the ADC value will be maximum (i.e. same as 2.2V)
+* Absolute maximum error +/- 15 mV in the 0.25 - 0.45 V range
+* Absolute maximum error +/- 30 mV in the rest of the 0.0 - 2.2 V range
+
+The following assumes:
+* The slow clock runs at 200kHz or 5 us.
+* The ADC requires 30 us to power on.
+* The ADC takes a single sample in 44 clocks (220 us)
+
+The controller should be initialized with the properties of the ADC and scan times.
+* The ADC power up delay must be set in [`adc_pd_ctl.pwrup_time`](data/adc_ctrl.hjson#adc_pd_ctl) to `6` (30 us).
+* The time to delay between samples in a slow scan should be set in [`adc_pd_ctl.wakeup_time`](data/adc_ctrl.hjson#adc_pd_ctl) to `1600` (8ms).
+* The number of samples to cause transition from slow to fast scan should be set in [`adc_lp_sample_ctl`](data/adc_ctrl.hjson#adc_lp_sample_ctl) to `4` (causing slow scan to be 4*8ms = 32ms of debounce time).
+* The number of samples for debounce should be set in [`adc_sample_ctl`](data/adc_ctrl.hjson#adc_sample_ctl) to `155` (causing the total debounce time to be 32ms (slow scan) + 220us * 2 * 155 = 100ms, at the low end of the USB-C spec window).
+
+* For the 10-bit ADC granularity, the filter registers [`adc_chnX_filter_ctlN`](data/adc_ctrl.hjson#adc_chnx_filter_ctln) should be programmed to:
+
+| Filter | Ch0 Min      | Ch0 Max      | Ch1 Min      | Ch1 Max      | Device connected            |
+|--------|--------------|--------------|--------------|--------------|-----------------------------|
+| 0 IN   |  149 (0.32V) |  279 (0.60V) |  149 (0.32V) |  279 (0.60V) | Debug Sink (local RpUSB)    |
+| 1 IN   |  391 (0.84V) |  524 (1.125V)|  391 (0.84V) |  524 (1.125V)| Debug Sink (local Rp1.5A)   |
+| 2 IN   |  712 (1.53V) |  931 (2.00V) |  712 (1.53V) |  931 (2.00V) | Debug Sink (local Rp3A)     |
+| 3 IN   |  712 (1.53V) |  847 (1.82V) |  405 (0.87V) |  503 (1.08V) | Debug Source with RpUSB     |
+| 4 IN   |  349 (0.75V) |  512 (1.12V) |  186 (0.40V) |  279 (0.60V) | Debug Source with Rp1.5A    |
+| 5 IN   |  405 (0.87V) |  503 (1.08V) |  712 (1.53V) |  841 (1.82V) | Debug Source RpUSB Flipped  |
+| 6 IN   |  186 (0.40V) |  279 (0.60V) |  349 (0.75V) |  521 (1.12V) | Debug Source Rp1.5A Flipped |
+| 7 OUT  |  116 (0.25V) |  954 (2.05V) |  116 (0.25V) |  954 (2.05V) | Disconnect                  |
+
+
+* The interrupt [`adc_intr_ctl`](data/adc_ctrl.hjson#adc_intr_ctl) and wakeup [`adc_wakeup_ctl`](data/adc_ctrl.hjson#adc_wakeup_ctl) enables should be configured.
+* All ones should be written to [`adc_intr_status`](data/adc_ctrl.hjson#adc_intr_status) and  [`filter_status`](data/adc_ctrl.hjson#filter_status) to ensure there are no spurious pending triggers.
+* The state machine will only start running when [`adc_en_ctl`](data/adc_ctrl.hjson#adc_en_ctl) is set.
+
+Note that for the debug controller (DTS in USB-C specification) as a power source the filter that is hit will indicate the orientation of the connector.
+If the debug controller is acting as a power sink then the orientation cannot be known unless the debug controller supports the optional behavior of converting one of its pulldowns to an Ra (rather than Rp) to indicate CC2 (the CC that is not used for communication).
+This would not be detected by the filters since it happens later than connection detection and debounce in the USB-C protocol state machine, but could be detected by monitoring the current ADC value.
+
+## Device Interface Functions (DIFs)
+
+- [Device Interface Functions](../../../sw/device/lib/dif/dif_adc_ctrl.h)
+
+## Registers
+
+* [Register Table](data/adc_ctrl.hjson#registers)
diff --git a/hw/ip/adc_ctrl/data/adc_ctrl.hjson b/hw/ip/adc_ctrl/data/adc_ctrl.hjson
index f71e59e..76e32f4 100644
--- a/hw/ip/adc_ctrl/data/adc_ctrl.hjson
+++ b/hw/ip/adc_ctrl/data/adc_ctrl.hjson
@@ -1,7 +1,26 @@
 // Copyright lowRISC contributors.
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
-{ name: "adc_ctrl",
+{ name:               "adc_ctrl",
+  human_name:         "ADC Controller",
+  one_line_desc:      "Low-power controller for a dual-channel ADC with filtering and debouncing capability",
+  one_paragraph_desc: '''
+  Analog to Digital Converter (ADC) Controller provides a simple front-end to an analog block that allows filtering and debouncing of the sampled data.
+  ADC Controller supports 2 ADC channels and 8 filters on the values from the channels.
+  It has debounce timers on the filter output and supports ADCs with 10-bit output.
+  To enable usage while the device is sleeping, it runs on a slow always-on clock.
+  In addition, it has a low power periodic scan mode for monitoring ADC channels.
+  '''
+  design_spec:        "../doc",
+  dv_doc:             "../doc/dv",
+  hw_checklist:       "../doc/checklist",
+  sw_checklist:       "/sw/device/lib/dif/dif_adc_ctrl",
+  version:            "1.0",
+  life_stage:         "L1",
+  design_stage:       "D3",
+  verification_stage: "V2S",
+  dif_stage:          "S2",
+  notes:              "",
   clocking: [
     {clock: "clk_i", reset: "rst_ni", primary: true},
     {clock: "clk_aon_i", reset: "rst_aon_ni"}
@@ -24,8 +43,8 @@
     }
   ],
   interrupt_list: [
-    { name: "debug_cable",
-      desc: "USB-C debug cable is attached or disconnected",
+    { name: "match_done",
+      desc: "ADC match or measurement event done",
     }
   ],
   alert_list: [
@@ -37,7 +56,7 @@
   ],
   wakeup_list: [
     { name: "wkup_req",
-      desc: "USB-C debug cable wakeup request",
+      desc: "ADC wakeup request",
     }
   ],
   param_list: [
@@ -304,7 +323,7 @@
       fields: [
         { bits: "8:0",
           name: "EN",
-          desc: "0: interrupt source is enabled; 1: interrupt source not enabled",
+          desc: "0: interrupt source is not enabled; 1: interrupt source is enabled",
         }
       ]
     }
@@ -312,45 +331,17 @@
     { name: "adc_intr_status",
       desc: "Debug cable internal status",
       swaccess: "rw1c",
-      hwaccess: "hwo",
+      hwaccess: "hrw",
       resval: "0",
       tags: [ // the value of these regs is determined by the
               // value on the pins, hence it cannot be predicted.
               "excl:CsrNonInitTests:CsrExclCheck"],
       fields: [
-        { bits: "0",
-          name: "cc_sink_det",
-          desc: "0: filter0 condition is not met; 1: filter0 condition is met",
+        { bits: "NumAdcFilter-1:0",
+          name: "filter_match",
+          desc: "0: filter condition is not met; 1: filter condition is met",
         }
-        { bits: "1",
-          name: "cc_1A5_sink_det",
-          desc: "0: filter1 condition is not met; 1: filter1 condition is met",
-        }
-        { bits: "2",
-          name: "cc_3A0_sink_det",
-          desc: "0: filter2 condition is not met; 1: filter2 condition is met",
-        }
-        { bits: "3",
-          name: "cc_src_det",
-          desc: "0: filter3 condition is not met; 1: filter3 condition is met",
-        }
-        { bits: "4",
-          name: "cc_1A5_src_det",
-          desc: "0: filter4 condition is not met; 1: filter4 condition is met",
-        }
-        { bits: "5",
-          name: "cc_src_det_flip",
-          desc: "0: filter5 condition is not met; 1: filter5 condition is met",
-        }
-        { bits: "6",
-          name: "cc_1A5_src_det_flip",
-          desc: "0: filter6 condition is not met; 1: filter6 condition is met",
-        }
-        { bits: "7",
-          name: "cc_discon",
-          desc: "0: filter7 condition is not met; 1: filter7 condition is met",
-        }
-        { bits: "8",
+        { bits: "NumAdcFilter:NumAdcFilter",
           name: "oneshot",
           desc: "0: oneshot sample is not done ; 1: oneshot sample is done",
         }
diff --git a/hw/ip/adc_ctrl/data/adc_ctrl.prj.hjson b/hw/ip/adc_ctrl/data/adc_ctrl.prj.hjson
deleted file mode 100644
index d2cff89..0000000
--- a/hw/ip/adc_ctrl/data/adc_ctrl.prj.hjson
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright lowRISC contributors.
-// Licensed under the Apache License, Version 2.0, see LICENSE for details.
-// SPDX-License-Identifier: Apache-2.0
-
-{
-    name:               "adc_ctrl",
-    design_spec:        "../doc",
-    dv_doc:             "../doc/dv",
-    hw_checklist:       "../doc/checklist",
-    sw_checklist:       "/sw/device/lib/dif/dif_adc_ctrl",
-    version:            "1.0",
-    life_stage:         "L1",
-    design_stage:       "D2S",
-    verification_stage: "V2S",
-    dif_stage:          "S0",
-}
diff --git a/hw/ip/adc_ctrl/data/adc_ctrl_sec_cm_testplan.hjson b/hw/ip/adc_ctrl/data/adc_ctrl_sec_cm_testplan.hjson
index e00f1dc..b5fbd8e 100644
--- a/hw/ip/adc_ctrl/data/adc_ctrl_sec_cm_testplan.hjson
+++ b/hw/ip/adc_ctrl/data/adc_ctrl_sec_cm_testplan.hjson
@@ -26,7 +26,7 @@
     {
       name: sec_cm_bus_integrity
       desc: "Verify the countermeasure(s) BUS.INTEGRITY."
-      milestone: V2S
+      stage: V2S
       tests: ["adc_ctrl_tl_intg_err"]
     }
   ]
diff --git a/hw/ip/adc_ctrl/data/adc_ctrl_testplan.hjson b/hw/ip/adc_ctrl/data/adc_ctrl_testplan.hjson
index 8fa056b..a3f8fc2 100644
--- a/hw/ip/adc_ctrl/data/adc_ctrl_testplan.hjson
+++ b/hw/ip/adc_ctrl/data/adc_ctrl_testplan.hjson
@@ -29,7 +29,7 @@
             - Compare sample registers against expected.
             - Check oneshot bit of interrupt status register works as expected.
             '''
-      milestone: V1
+      stage: V1
       tests: ["adc_ctrl_smoke"]
     }
     {
@@ -50,7 +50,7 @@
           - Confirm this by reading the filter status register.
           - Ensure that only one ADC channel is selected at a time.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_polled"]
     }
     {
@@ -58,7 +58,7 @@
       desc: '''
           As filters_polled but with filter parameters fixed during the test.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_polled_fixed"]
     }
     {
@@ -80,7 +80,7 @@
           - Confirm correct interrupt sample value has been captured in ADC sample registers(s)
           - Check interrupt signal operates as expected.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_interrupt"]
     }
     {
@@ -88,7 +88,7 @@
       desc: '''
           As filters_interrupt but with filter parameters fixed during the test.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_interrupt_fixed"]
     }
     {
@@ -108,7 +108,7 @@
           - Confirm this by reading the filter status register.
           - Check wakeup signal operates as expected.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_wakeup"]
     }
     {
@@ -116,7 +116,7 @@
       desc: '''
           As filters_wakeup but with filter parameters fixed during the test.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_wakeup_fixed"]
     }
     {
@@ -138,7 +138,7 @@
           - Check wakeup signal operates as expected.
           - Check interrupt signal operates as expected.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_filters_both"]
     }
     {
@@ -163,7 +163,7 @@
           - Check wakeup signal operates as expected.
           - Check interrupt signal operates as expected.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_clock_gating"]
     }
     {
@@ -182,7 +182,7 @@
 
           - Confirm timing of power down and channel select signals to ADC.
           '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_poweron_counter"]
     }
     {
@@ -201,7 +201,7 @@
 
           - Confirm return to fast sampling happens as expected.
         '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_lowpower_counter"]
     }
     {
@@ -221,7 +221,7 @@
 
           - Ensure ADC controller FSM and counters are reset.
         '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_fsm_reset"]
     }
 
@@ -236,7 +236,7 @@
             Checking:
               - All sequences should be finished and checked by the scoreboard
       '''
-      milestone: V2
+      stage: V2
       tests: ["adc_ctrl_stress_all"]
     }
 
diff --git a/hw/ip/adc_ctrl/doc/_index.md b/hw/ip/adc_ctrl/doc/_index.md
deleted file mode 100644
index 81eb7d0..0000000
--- a/hw/ip/adc_ctrl/doc/_index.md
+++ /dev/null
@@ -1,243 +0,0 @@
----
-title: "Analog to Digital Converter Control Interface"
----
-
-# Overview
-
-This document specifies the ADC controller IP functionality.
-This IP block implements control and filter logic for an analog block that implements a dual ADC.
-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.
-
-## Features
-
-The IP block implements the following features:
-
-- Register interface to dual ADC analog block
-- Support for 2 ADC channels
-- Support for 8 filters on the values from the channels
-- Support ADCs with 10-bit output (two reserved bits in CSR)
-- Support for debounce timers on the filter output
-- Run on a slow always-on clock to enable usage while the device is sleeping
-- Low power periodic scan mode for monitoring ADC channels
-
-## Description
-
-The ADC controller is a simple front-end to an analog block that allows filtering and debouncing of the analog signals.
-
-## Compatibility
-
-The ADC controller programming interface is not based on any existing interface.
-
-# Theory of Operation
-
-The block diagram shows a conceptual view of the ADC controller state machine and filters.
-
-## Block Diagram
-
-![ADC_CTRL Block Diagram](adc_overview.svg "image_tooltip")
-
-## Hardware Interface
-
-{{< incGenFromIpDesc "../data/adc_ctrl.hjson" "hwcfg" >}}
-
-### Signals
-
-In addition to the interrupts and bus signals, the tables below lists additional IOs.
-
-Signal                  | Direction | Description
-------------------------|-----------|---------------
-`adc_o`                 | `output`  | Output controls to the actual `AST ADC` module.  Contains signals such as power down control and ADC channel select.
-`adc_i`                 | `input`   | Input data from `AST ADC` module. Contains ADC data output as well as data valid indication.
-
-
-## Design Details
-
-## Sampling state machine
-
-The state machine that takes ADC samples follows a very simple pattern:
-
-1. *Power up ADC*: The controller issues the power up command to the ADC.
-
-2. *Wait for ADC turn on*: The controller waits for the number of clock cycles programmed in {{< regref "adc_pd_ctl.pwrup_time" >}} which should be set to match the ADC power up delay.
-
-3. *Take sample Channel 0*: The ADC is requested to sample channel 0.
-When the ADC signals complete the value is stored in {{< regref "adc_chn_val[0].adc_chn_value" >}}.
-Note that the time taken in this step depends on the properties of the ADC.
-
-4. *Take sample Channel 1*: The ADC is requested to sample channel 1.
-When the ADC signals complete the value is stored in {{< regref "adc_chn_val[1].adc_chn_value" >}}.
-Note that the time taken in this step depends on the properties of the ADC.
-
-5. *Evaluate Filters*: The filters are evaluated and debounce logic applied (see [next section](#filters-and-debounce)).
-
-6. *Scan type check*: At this point if the {{< regref "adc_pd_ctl.lp_mode" >}} bit is clear scanning continues at step (3).
-   If the bit is set the next step depends on how many samples have hit the filters.
-   If more than {{< regref "adc_lp_sample_ctl.lp_sample_cnt" >}} samples have hit then continuous scanning continues at step (3) else periodic scanning will continue at the next step (7).
-
-7. *Power off ADC*: The controller issues the power down command to the ADC.
-
-8. *Wait sleep time*: The controller will wait for the next sample timer to time out before restarting at step (1).
-
-In active operation the controller is in continuous scanning mode:
-* The ADC is continually powered on.
-* The sampling cycle time is the time taken for the ADC to take two samples (450us) plus internal processing time (4 clock cycles) from the ADC controller.
-* The debounce timer will trigger the {{< regref "filter_status" >}} and interrupt after a configurable number of matching ADC samples have been seen, as determined by {{< regref "adc_sample_ctl" >}}.
-
-For low power operation the periodic scanning mode can be used.
-In this mode samples are taken using a slower periodic sampling cycle time with the ADC powered down most of the time.
-Once a small number of cycles have hit the filter with periodic scanning then the controller switches to continuous scanning in order to more accurately debounce the signal.
-In low power mode:
-* The ADC is periodically powered up to take samples; this interval is determined by {{< regref "adc_pd_ctl.wakeup_time" >}}.
-* Similar to normal operation, the ADC power-up delay is controlled by {{< regref "adc_pd_ctl.pwrup_time" >}}.
-* Once the ADC is powered up, two samples are taken and compared to the filter thresholds.
-* If a configurable number of matches, as determined by {{< regref "adc_lp_sample_ctl" >}}, are seen, the ADC controller transitions to normal operation for continuous sampling.
-
-Although it can be used at any time, the periodic operation mode and use of the slow clock allows the ADC controller to continue to scan when most of the chip is in sleep or power-down modes.
-The controller can be configured to issue a wakeup to the rest of the chip.
-
-If a filter is enabled for wakeup in {{< regref "adc_wakeup_ctl" >}} and {{< regref "filter_status" >}} indicates a match, a wakeup is generated to the system power manager.
-
-
-## Filters and debounce
-
-There are two reserved bits in ADC filter control registers for future use.
-In the current implementation, ADC has 10-bit granularity.
-Each step is 2.148mV.
-It covers 0-2.2V.
-
-The ADC controller implements eight pairs of filters that feed the debounce logic.
-Each pair has a filter for channel 0 and a filter for channel 1.
-
-A filter consists of a `max` value, a `min` value and a `cond` flag indicating if the filter is hit by a sample inside or outside the range.
-* *Inside the range*: the filter is hit if `min` &le; `value` &le; `max`.
-* *Outside the range*: inverse of inside, so the filter is hit if `value` &lt; `min` or `value` &gt; `max`.
-
-Some example filters:
-* Inside `min=7`, `max=23`: any value between and including 7 and 23 will hit.
-* Outside `min=7`, `max=23`: any value less than 7 or greater than 23 will hit.
-* Inside `min=7`, `max=7`: the value must be exactly 7 to hit (sample noise may make an exact hit unlikely).
-* Inside `min=0`, `max=7`: the value must be less than 8 to hit.
-* Outside `min=8`, `max=0xFFF`: the value must be less than 8 to hit (alternate method).
-* Inside `min=0`, `max=0xFFF`: any value will hit. This may be useful to exclude one channel from the filter.
-* Outside `min=0`, `max=0xFFF`: no value will hit. If set for either channel the filter is effectively disabled.
-
-All pairs of filters that are enabled in {{< regref "adc_chn0_filter_ctl[7:0]" >}} and {{< regref "adc_chn1_filter_ctl[7:0]" >}} are evaluated after each pair of samples has been taken.
-The filter result is passed to the periodic scan counter if enabled and not at its limit otherwise the result is passed to the debounce counter.
-The list below describes how the counters interpret the filter results:
-* If no filters are hit then the counter will reset to zero.
-* If one or more filters are hit but the set hit differs from the previous evaluation the counter resets to zero.
-* If one or more filters are hit and either none was hit in the previous evaluation or the same set was hit in the previous evaluation and the counter is not at its threshold then the counter will increment.
-* If one or more filters are hit and the same set was hit in the previous evaluation and the counter is at its threshold then the counter stays at the threshold.
-* If the counter is the periodic scan counter and it reaches its threshold, as defined by {{< regref "adc_lp_sample_ctl.lp_sample_cnt" >}}, then continuous scanning is enabled and the debounce counter will be used for future evaluations.
-* If the counter is the debounce counter and it reaches its threshold, as defined by {{< regref "adc_sample_ctl.np_sample_cnt" >}}, then:
-  * An interrupt is raised if the threshold is met for the first time.
-  * The current sample values are latched into {{< regref "adc_chn_val[0].adc_chn_value_intr" >}} and  {{< regref "adc_chn_val[1].adc_chn_value_intr" >}}.
-    *  If a series of interrupts and matches are seen, these registers only record the value of the last debounced hit.
-  * The {{< regref adc_intr_status >}} register is updated by setting the bits corresponding to filters that are hit (note that bits that are already set will not be cleared).
-    This will cause the block to raise an interrupt if it was not already doing so.
-  * If a filter is a hit and is also enabled in {{< regref "adc_wakeup_ctl" >}} the corresponding filter generates a wakeup.
-  * Note that the debounce counter will remain at its threshold until the set of filters are changed by software to debounce a different event or if the current match changes.
-    *  This implies that a stable matching event continuously matches until some condition in the system (changed filter settings or changed ADC output) alters the result.
-
-
-Because scanning continues the {{< regref "adc_intr_status" >}} register will reflect any debounced events that are detected between the controller raising an interrupt and the status bits being cleared (by having 1 written to them).
-However, the {{< regref "adc_chn_val[0].adc_chn_value_intr" >}} and {{< regref "adc_chn_val[1].adc_chn_value_intr" >}} registers record the value at the time the interrupt was first raised and thus reflect the filter state from that point.
-
-{{< wavejson >}}
-{
-  signal: [
-    {node: '.a..b........', phase:0.2},
-    {name: 'adc_pd_i'     , wave: '10|..|.....|....|..1'},
-    {name: 'clk_ast_adc_i', wave: 'p.|..|.....|....|...'},
-    {name: 'adc_chnsel_i' , wave: '0.|.3|..04.|....|0..'},
-    {name: 'adc_d_val_o'  , wave: '0.|..|.1.0.|.1..|.0.'},
-    {name: 'adc_d_o'      , wave: 'x.|..|.3.x.|.4..|.x.', data: ['ch0', 'ch1', 'ch1']},
-  ],
-  edge: [  'a<->b wakeup time',   ]
-}
-{{< /wavejson >}}
-
-# Programmers Guide
-
-## Initialization
-
-The controller should be initialized with the properties of the ADC and scan times.
-* The ADC power up delay must be set in {{< regref "adc_pd_ctl.pwrup_time" >}}.
-* The time to delay between samples in a slow scan should be set in {{< regref "adc_pd_ctl.wakeup_time" >}}.
-* The number of samples to cause transition from slow to fast scan should be set in {{< regref "adc_lp_sample_ctl" >}}.
-* The number of samples for debounce should be set in {{< regref "adc_sample_ctl" >}}.
-* The filter registers {{< regref "adc_chnX_filter_ctlN" >}} should be programmed.
-* The interrupt {{< regref "adc_intr_ctl" >}} and wakeup {{< regref "adc_wakeup_ctl" >}} enables should be configured.
-* All ones should be written to {{< regref "adc_intr_status" >}} and  {{< regref "filter_status" >}} to ensure there are no spurious pending triggers.
-* Optionally, the low-power mode should be set in {{< regref "adc_pd_ctl.lp_mode" >}} if the system is going to the low-power mode.
-* The state machine will only start running when {{< regref "adc_en_ctl" >}} is set.
-
-## Running in normal mode
-
-If fast sampling is always required then the {{< regref "adc_pd_ctl.lp_mode" >}} bit should be clear.
-In this case the values in the {{< regref "adc_lp_sample_ctl" >}} are not used.
-The ADC will always be enabled and consuming power.
-
-If power saving is required then the controller can be set to operate in low power mode by setting {{< regref "adc_pd_ctl.lp_mode" >}}.
-The {{< regref "adc_lp_sample_ctl" >}} must be programed prior to setting this bit.
-
-## Running with the rest of the chip in sleep
-
-Once programmed the controller and ADC can run when the rest of the chip is in low power state and the main clocks are stopped.
-This allows the chip to be woken when appropriate values are detected on the two ADC channels.
-The fast sampling mode can be used but will usually consume more power than desired when the chip is in sleep.
-So it is expected that {{< regref "adc_lp_sample_ctl" >}} is configured and low power mode enabled by setting {{< regref "adc_pd_ctl.lp_mode" >}} prior to the sleep being initiated.
-
-If the ADC wakeup is not required then the controller and ADC should both be disabled by clearing {{< regref "adc_en_ctl" >}} prior to the sleep being initiated.
-
-## Use for USB-C debug accessory detection.
-
-Please see the following diagram for the regions of interest in debug cable detection.
-![Debug Cable Regions](debug_cable_regions.svg "image_tooltip")
-
-The ADC can be used to detect debug cable connection / disconnection in the non-overlapping regions.
-As an example use case of the two channel filters they can be used for detection of a USB-C debug accessory.
-The ADC must meet some minimum specifications:
-* Full scale range is 0.0V to 2.2V
-* If the signal is below 0.0V the ADC value will be zero.
-* If the signal is above 2.2V the ADC value will be maximum (i.e. same as 2.2V)
-* Absolute maximum error +/- 15 mV in the 0.25 - 0.45 V range
-* Absolute maximum error +/- 30 mV in the rest of the 0.0 - 2.2 V range
-
-The following assumes:
-* The slow clock runs at 200kHz or 5 us.
-* The ADC requires 30 us to power on.
-* The ADC takes a single sample in 44 clocks (220 us)
-
-The controller should be initialized with the properties of the ADC and scan times.
-* The ADC power up delay must be set in {{< regref "adc_pd_ctl.pwrup_time" >}} to `6` (30 us).
-* The time to delay between samples in a slow scan should be set in {{< regref "adc_pd_ctl.wakeup_time" >}} to `1600` (8ms).
-* The number of samples to cause transition from slow to fast scan should be set in {{< regref "adc_lp_sample_ctl" >}} to `4` (causing slow scan to be 4*8ms = 32ms of debounce time).
-* The number of samples for debounce should be set in {{< regref "adc_sample_ctl" >}} to `155` (causing the total debounce time to be 32ms (slow scan) + 220us * 2 * 155 = 100ms, at the low end of the USB-C spec window).
-
-* For the 10-bit ADC granularity, the filter registers {{< regref "adc_chnX_filter_ctlN" >}} should be programmed to:
-
-| Filter | Ch0 Min      | Ch0 Max      | Ch1 Min      | Ch1 Max      | Device connected            |
-|--------|--------------|--------------|--------------|--------------|-----------------------------|
-| 0 IN   |  149 (0.32V) |  279 (0.60V) |  149 (0.32V) |  279 (0.60V) | Debug Sink (local RpUSB)    |
-| 1 IN   |  391 (0.84V) |  524 (1.125V)|  391 (0.84V) |  524 (1.125V)| Debug Sink (local Rp1.5A)   |
-| 2 IN   |  712 (1.53V) |  931 (2.00V) |  712 (1.53V) |  931 (2.00V) | Debug Sink (local Rp3A)     |
-| 3 IN   |  712 (1.53V) |  847 (1.82V) |  405 (0.87V) |  503 (1.08V) | Debug Source with RpUSB     |
-| 4 IN   |  349 (0.75V) |  512 (1.12V) |  186 (0.40V) |  279 (0.60V) | Debug Source with Rp1.5A    |
-| 5 IN   |  405 (0.87V) |  503 (1.08V) |  712 (1.53V) |  841 (1.82V) | Debug Source RpUSB Flipped  |
-| 6 IN   |  186 (0.40V) |  279 (0.60V) |  349 (0.75V) |  521 (1.12V) | Debug Source Rp1.5A Flipped |
-| 7 OUT  |  116 (0.25V) |  954 (2.05V) |  116 (0.25V) |  954 (2.05V) | Disconnect                  |
-
-
-* The interrupt {{< regref "adc_intr_ctl" >}} and wakeup {{< regref "adc_wakeup_ctl" >}} enables should be configured.
-* All ones should be written to {{< regref "adc_intr_status" >}} and  {{< regref "filter_status" >}} to ensure there are no spurious pending triggers.
-* The state machine will only start running when {{< regref "adc_en_ctl" >}} is set.
-
-Note that for the debug controller (DTS in USB-C specification) as a power source the filter that is hit will indicate the orientation of the connector.
-If the debug controller is acting as a power sink then the orientation cannot be known unless the debug controller supports the optional behavior of converting one of its pulldowns to an Ra (rather than Rp) to indicate CC2 (the CC that is not used for communication).
-This would not be detected by the filters since it happens later than connection detection and debounce in the USB-C protocol state machine, but could be detected by monitoring the current ADC value.
-
-## Registers
-
-{{< incGenFromIpDesc "../data/adc_ctrl.hjson" "registers" >}}
diff --git a/hw/ip/adc_ctrl/doc/checklist.md b/hw/ip/adc_ctrl/doc/checklist.md
index b679b32..00ebfce 100644
--- a/hw/ip/adc_ctrl/doc/checklist.md
+++ b/hw/ip/adc_ctrl/doc/checklist.md
@@ -1,14 +1,12 @@
----
-title: "ADC_CTRL Checklist"
----
+# ADC_CTRL Checklist
 
 <!--
 NOTE: This is a template checklist document that is required to be copied over to the 'doc'
 directory for a new design that transitions from L0 (Specification) to L1 (Development)
 stage, and updated as needed. Once done, please remove this comment before checking it in.
 -->
-This checklist is for [Hardware Stage]({{< relref "/doc/project/development_stages.md" >}}) transitions for the [ADC_CTRL peripheral.]()
-All checklist items refer to the content in the [Checklist.]({{< relref "/doc/project/checklist.md" >}})
+This checklist is for [Hardware Stage](../../../../doc/project_governance/development_stages.md) transitions for the [ADC_CTRL peripheral.]()
+All checklist items refer to the content in the [Checklist.](../../../../doc/project_governance/checklist/README.md)
 
 ## Design Checklist
 
@@ -16,7 +14,7 @@
 
 Type          | Item                           | Resolution  | Note/Collaterals
 --------------|--------------------------------|-------------|------------------
-Documentation | [SPEC_COMPLETE][]              | In progress | [ADC_CTRL Design Spec]({{<relref "." >}})
+Documentation | [SPEC_COMPLETE][]              | In progress | [ADC_CTRL Design Spec](../README.md)
 Documentation | [CSR_DEFINED][]                | Done        |
 RTL           | [CLKRST_CONNECTED][]           | Done        |
 RTL           | [IP_TOP][]                     | Done        |
@@ -26,15 +24,15 @@
 RTL           | [ASSERT_KNOWN_ADDED][]         | Done        |
 Code Quality  | [LINT_SETUP][]                 | Done        |
 
-[SPEC_COMPLETE]:              {{<relref "/doc/project/checklist.md#spec_complete" >}}
-[CSR_DEFINED]:                {{<relref "/doc/project/checklist.md#csr_defined" >}}
-[CLKRST_CONNECTED]:           {{<relref "/doc/project/checklist.md#clkrst_connected" >}}
-[IP_TOP]:                     {{<relref "/doc/project/checklist.md#ip_top" >}}
-[IP_INSTANTIABLE]:            {{<relref "/doc/project/checklist.md#ip_instantiable" >}}
-[PHYSICAL_MACROS_DEFINED_80]: {{<relref "/doc/project/checklist.md#physical_macros_defined_80" >}}
-[FUNC_IMPLEMENTED]:           {{<relref "/doc/project/checklist.md#func_implemented" >}}
-[ASSERT_KNOWN_ADDED]:         {{<relref "/doc/project/checklist.md#assert_known_added" >}}
-[LINT_SETUP]:                 {{<relref "/doc/project/checklist.md#lint_setup" >}}
+[SPEC_COMPLETE]:              ../../../../doc/project_governance/checklist/README.md#spec_complete
+[CSR_DEFINED]:                ../../../../doc/project_governance/checklist/README.md#csr_defined
+[CLKRST_CONNECTED]:           ../../../../doc/project_governance/checklist/README.md#clkrst_connected
+[IP_TOP]:                     ../../../../doc/project_governance/checklist/README.md#ip_top
+[IP_INSTANTIABLE]:            ../../../../doc/project_governance/checklist/README.md#ip_instantiable
+[PHYSICAL_MACROS_DEFINED_80]: ../../../../doc/project_governance/checklist/README.md#physical_macros_defined_80
+[FUNC_IMPLEMENTED]:           ../../../../doc/project_governance/checklist/README.md#func_implemented
+[ASSERT_KNOWN_ADDED]:         ../../../../doc/project_governance/checklist/README.md#assert_known_added
+[LINT_SETUP]:                 ../../../../doc/project_governance/checklist/README.md#lint_setup
 
 ### D2
 
@@ -59,24 +57,24 @@
 Code Quality  | [TIMING_CHECK][]          | Done        |
 Security      | [SEC_CM_DOCUMENTED][]     | N/A         |
 
-[NEW_FEATURES]:          {{<relref "/doc/project/checklist.md#new_features" >}}
-[BLOCK_DIAGRAM]:         {{<relref "/doc/project/checklist.md#block_diagram" >}}
-[DOC_INTERFACE]:         {{<relref "/doc/project/checklist.md#doc_interface" >}}
-[DOC_INTEGRATION_GUIDE]: {{<relref "/doc/project/checklist.md#doc_integration_guide" >}}
-[MISSING_FUNC]:          {{<relref "/doc/project/checklist.md#missing_func" >}}
-[FEATURE_FROZEN]:        {{<relref "/doc/project/checklist.md#feature_frozen" >}}
-[FEATURE_COMPLETE]:      {{<relref "/doc/project/checklist.md#feature_complete" >}}
-[PORT_FROZEN]:           {{<relref "/doc/project/checklist.md#port_frozen" >}}
-[ARCHITECTURE_FROZEN]:   {{<relref "/doc/project/checklist.md#architecture_frozen" >}}
-[REVIEW_TODO]:           {{<relref "/doc/project/checklist.md#review_todo" >}}
-[STYLE_X]:               {{<relref "/doc/project/checklist.md#style_x" >}}
-[CDC_SYNCMACRO]:         {{<relref "/doc/project/checklist.md#cdc_syncmacro" >}}
-[LINT_PASS]:             {{<relref "/doc/project/checklist.md#lint_pass" >}}
-[CDC_SETUP]:             {{<relref "/doc/project/checklist.md#cdc_setup" >}}
-[RDC_SETUP]:             {{<relref "/doc/project/checklist.md#rdc_setup" >}}
-[AREA_CHECK]:            {{<relref "/doc/project/checklist.md#area_check" >}}
-[TIMING_CHECK]:          {{<relref "/doc/project/checklist.md#timing_check" >}}
-[SEC_CM_DOCUMENTED]:     {{<relref "/doc/project/checklist.md#sec_cm_documented" >}}
+[NEW_FEATURES]:          ../../../../doc/project_governance/checklist/README.md#new_features
+[BLOCK_DIAGRAM]:         ../../../../doc/project_governance/checklist/README.md#block_diagram
+[DOC_INTERFACE]:         ../../../../doc/project_governance/checklist/README.md#doc_interface
+[DOC_INTEGRATION_GUIDE]: ../../../../doc/project_governance/checklist/README.md#doc_integration_guide
+[MISSING_FUNC]:          ../../../../doc/project_governance/checklist/README.md#missing_func
+[FEATURE_FROZEN]:        ../../../../doc/project_governance/checklist/README.md#feature_frozen
+[FEATURE_COMPLETE]:      ../../../../doc/project_governance/checklist/README.md#feature_complete
+[PORT_FROZEN]:           ../../../../doc/project_governance/checklist/README.md#port_frozen
+[ARCHITECTURE_FROZEN]:   ../../../../doc/project_governance/checklist/README.md#architecture_frozen
+[REVIEW_TODO]:           ../../../../doc/project_governance/checklist/README.md#review_todo
+[STYLE_X]:               ../../../../doc/project_governance/checklist/README.md#style_x
+[CDC_SYNCMACRO]:         ../../../../doc/project_governance/checklist/README.md#cdc_syncmacro
+[LINT_PASS]:             ../../../../doc/project_governance/checklist/README.md#lint_pass
+[CDC_SETUP]:             ../../../../doc/project_governance/checklist/README.md#cdc_setup
+[RDC_SETUP]:             ../../../../doc/project_governance/checklist/README.md#rdc_setup
+[AREA_CHECK]:            ../../../../doc/project_governance/checklist/README.md#area_check
+[TIMING_CHECK]:          ../../../../doc/project_governance/checklist/README.md#timing_check
+[SEC_CM_DOCUMENTED]:     ../../../../doc/project_governance/checklist/README.md#sec_cm_documented
 
 ### D2S
 
@@ -90,39 +88,39 @@
 Security      | [SEC_CM_RTL_REVIEWED][]      | N/A         |
 Security      | [SEC_CM_COUNCIL_REVIEWED][]  | N/A         | This block only contains the bus-integrity CM.
 
-[SEC_CM_ASSETS_LISTED]:    {{<relref "/doc/project/checklist.md#sec_cm_assets_listed" >}}
-[SEC_CM_IMPLEMENTED]:      {{<relref "/doc/project/checklist.md#sec_cm_implemented" >}}
-[SEC_CM_RND_CNST]:         {{<relref "/doc/project/checklist.md#sec_cm_rnd_cnst" >}}
-[SEC_CM_NON_RESET_FLOPS]:  {{<relref "/doc/project/checklist.md#sec_cm_non_reset_flops" >}}
-[SEC_CM_SHADOW_REGS]:      {{<relref "/doc/project/checklist.md#sec_cm_shadow_regs" >}}
-[SEC_CM_RTL_REVIEWED]:     {{<relref "/doc/project/checklist.md#sec_cm_rtl_reviewed" >}}
-[SEC_CM_COUNCIL_REVIEWED]: {{<relref "/doc/project/checklist.md#sec_cm_council_reviewed" >}}
+[SEC_CM_ASSETS_LISTED]:    ../../../../doc/project_governance/checklist/README.md#sec_cm_assets_listed
+[SEC_CM_IMPLEMENTED]:      ../../../../doc/project_governance/checklist/README.md#sec_cm_implemented
+[SEC_CM_RND_CNST]:         ../../../../doc/project_governance/checklist/README.md#sec_cm_rnd_cnst
+[SEC_CM_NON_RESET_FLOPS]:  ../../../../doc/project_governance/checklist/README.md#sec_cm_non_reset_flops
+[SEC_CM_SHADOW_REGS]:      ../../../../doc/project_governance/checklist/README.md#sec_cm_shadow_regs
+[SEC_CM_RTL_REVIEWED]:     ../../../../doc/project_governance/checklist/README.md#sec_cm_rtl_reviewed
+[SEC_CM_COUNCIL_REVIEWED]: ../../../../doc/project_governance/checklist/README.md#sec_cm_council_reviewed
 
 ### D3
 
  Type         | Item                    | Resolution  | Note/Collaterals
 --------------|-------------------------|-------------|------------------
-Documentation | [NEW_FEATURES_D3][]     | Not Started |
-RTL           | [TODO_COMPLETE][]       | Not Started |
-Code Quality  | [LINT_COMPLETE][]       | Not Started |
-Code Quality  | [CDC_COMPLETE][]        | Not Started |
-Code Quality  | [RDC_COMPLETE][]        | Not Started |
-Review        | [REVIEW_RTL][]          | Not Started |
-Review        | [REVIEW_DELETED_FF][]   | Not Started |
-Review        | [REVIEW_SW_CHANGE][]    | Not Started |
-Review        | [REVIEW_SW_ERRATA][]    | Not Started |
-Review        | Reviewer(s)             | Not Started |
-Review        | Signoff date            | Not Started |
+Documentation | [NEW_FEATURES_D3][]     | Done        |
+RTL           | [TODO_COMPLETE][]       | Done        |
+Code Quality  | [LINT_COMPLETE][]       | Done        |
+Code Quality  | [CDC_COMPLETE][]        | Waived      | Done as part of top level CDC
+Code Quality  | [RDC_COMPLETE][]        | Waived      | Done as part of top level RDC
+Review        | [REVIEW_RTL][]          | Done        | by msfschaffner@
+Review        | [REVIEW_DELETED_FF][]   | Waived      | Done as part of top level
+Review        | [REVIEW_SW_CHANGE][]    | Done        | by timothytrippel@
+Review        | [REVIEW_SW_ERRATA][]    | Done        | by timothytrippel@
+Review        | Reviewer(s)             | Done        | msfschaffner@ timothytrippel@ weicaiyang@ tjaychen@
+Review        | Signoff date            | Done        | 2022-08-01
 
-[NEW_FEATURES_D3]:      {{<relref "/doc/project/checklist.md#new_features_d3" >}}
-[TODO_COMPLETE]:        {{<relref "/doc/project/checklist.md#todo_complete" >}}
-[LINT_COMPLETE]:        {{<relref "/doc/project/checklist.md#lint_complete" >}}
-[CDC_COMPLETE]:         {{<relref "/doc/project/checklist.md#cdc_complete" >}}
-[RDC_COMPLETE]:         {{<relref "/doc/project/checklist.md#rdc_complete" >}}
-[REVIEW_RTL]:           {{<relref "/doc/project/checklist.md#review_rtl" >}}
-[REVIEW_DELETED_FF]:    {{<relref "/doc/project/checklist.md#review_deleted_ff" >}}
-[REVIEW_SW_CHANGE]:     {{<relref "/doc/project/checklist.md#review_sw_change" >}}
-[REVIEW_SW_ERRATA]:     {{<relref "/doc/project/checklist.md#review_sw_errata" >}}
+[NEW_FEATURES_D3]:      ../../../../doc/project_governance/checklist/README.md#new_features_d3
+[TODO_COMPLETE]:        ../../../../doc/project_governance/checklist/README.md#todo_complete
+[LINT_COMPLETE]:        ../../../../doc/project_governance/checklist/README.md#lint_complete
+[CDC_COMPLETE]:         ../../../../doc/project_governance/checklist/README.md#cdc_complete
+[RDC_COMPLETE]:         ../../../../doc/project_governance/checklist/README.md#rdc_complete
+[REVIEW_RTL]:           ../../../../doc/project_governance/checklist/README.md#review_rtl
+[REVIEW_DELETED_FF]:    ../../../../doc/project_governance/checklist/README.md#review_deleted_ff
+[REVIEW_SW_CHANGE]:     ../../../../doc/project_governance/checklist/README.md#review_sw_change
+[REVIEW_SW_ERRATA]:     ../../../../doc/project_governance/checklist/README.md#review_sw_errata
 
 ## Verification Checklist
 
@@ -130,8 +128,8 @@
 
  Type         | Item                                  | Resolution  | Note/Collaterals
 --------------|---------------------------------------|-------------|------------------
-Documentation | [DV_DOC_DRAFT_COMPLETED][]            | Done | [ADC_CTRL DV document]({{<relref "dv/index.md" >}})
-Documentation | [TESTPLAN_COMPLETED][]                | Done | [ADC_CTRL Testplan]({{<relref "dv/index.md#testplan" >}})
+Documentation | [DV_DOC_DRAFT_COMPLETED][]            | Done | [ADC_CTRL DV document](../dv/README.md)
+Documentation | [TESTPLAN_COMPLETED][]                | Done | [ADC_CTRL Testplan](../dv/README.md#testplan)
 Testbench     | [TB_TOP_CREATED][]                    | Done |
 Testbench     | [PRELIMINARY_ASSERTION_CHECKS_ADDED][]| Done |
 Testbench     | [SIM_TB_ENV_CREATED][]                | Done |
@@ -153,28 +151,28 @@
 Review        | [STD_TEST_CATEGORIES_PLANNED][]       | Done |
 Review        | [V2_CHECKLIST_SCOPED][]               | Done |
 
-[DV_DOC_DRAFT_COMPLETED]:             {{<relref "/doc/project/checklist.md#dv_doc_draft_completed" >}}
-[TESTPLAN_COMPLETED]:                 {{<relref "/doc/project/checklist.md#testplan_completed" >}}
-[TB_TOP_CREATED]:                     {{<relref "/doc/project/checklist.md#tb_top_created" >}}
-[PRELIMINARY_ASSERTION_CHECKS_ADDED]: {{<relref "/doc/project/checklist.md#preliminary_assertion_checks_added" >}}
-[SIM_TB_ENV_CREATED]:                 {{<relref "/doc/project/checklist.md#sim_tb_env_created" >}}
-[SIM_RAL_MODEL_GEN_AUTOMATED]:        {{<relref "/doc/project/checklist.md#sim_ral_model_gen_automated" >}}
-[CSR_CHECK_GEN_AUTOMATED]:            {{<relref "/doc/project/checklist.md#csr_check_gen_automated" >}}
-[TB_GEN_AUTOMATED]:                   {{<relref "/doc/project/checklist.md#tb_gen_automated" >}}
-[SIM_SMOKE_TEST_PASSING]:             {{<relref "/doc/project/checklist.md#sim_smoke_test_passing" >}}
-[SIM_CSR_MEM_TEST_SUITE_PASSING]:     {{<relref "/doc/project/checklist.md#sim_csr_mem_test_suite_passing" >}}
-[FPV_MAIN_ASSERTIONS_PROVEN]:         {{<relref "/doc/project/checklist.md#fpv_main_assertions_proven" >}}
-[SIM_ALT_TOOL_SETUP]:                 {{<relref "/doc/project/checklist.md#sim_alt_tool_setup" >}}
-[SIM_SMOKE_REGRESSION_SETUP]:         {{<relref "/doc/project/checklist.md#sim_smoke_regression_setup" >}}
-[SIM_NIGHTLY_REGRESSION_SETUP]:       {{<relref "/doc/project/checklist.md#sim_nightly_regression_setup" >}}
-[FPV_REGRESSION_SETUP]:               {{<relref "/doc/project/checklist.md#fpv_regression_setup" >}}
-[SIM_COVERAGE_MODEL_ADDED]:           {{<relref "/doc/project/checklist.md#sim_coverage_model_added" >}}
-[TB_LINT_SETUP]:                      {{<relref "/doc/project/checklist.md#tb_lint_setup" >}}
-[PRE_VERIFIED_SUB_MODULES_V1]:        {{<relref "/doc/project/checklist.md#pre_verified_sub_modules_v1" >}}
-[DESIGN_SPEC_REVIEWED]:               {{<relref "/doc/project/checklist.md#design_spec_reviewed" >}}
-[TESTPLAN_REVIEWED]:                  {{<relref "/doc/project/checklist.md#testplan_reviewed" >}}
-[STD_TEST_CATEGORIES_PLANNED]:        {{<relref "/doc/project/checklist.md#std_test_categories_planned" >}}
-[V2_CHECKLIST_SCOPED]:                {{<relref "/doc/project/checklist.md#v2_checklist_scoped" >}}
+[DV_DOC_DRAFT_COMPLETED]:             ../../../../doc/project_governance/checklist/README.md#dv_doc_draft_completed
+[TESTPLAN_COMPLETED]:                 ../../../../doc/project_governance/checklist/README.md#testplan_completed
+[TB_TOP_CREATED]:                     ../../../../doc/project_governance/checklist/README.md#tb_top_created
+[PRELIMINARY_ASSERTION_CHECKS_ADDED]: ../../../../doc/project_governance/checklist/README.md#preliminary_assertion_checks_added
+[SIM_TB_ENV_CREATED]:                 ../../../../doc/project_governance/checklist/README.md#sim_tb_env_created
+[SIM_RAL_MODEL_GEN_AUTOMATED]:        ../../../../doc/project_governance/checklist/README.md#sim_ral_model_gen_automated
+[CSR_CHECK_GEN_AUTOMATED]:            ../../../../doc/project_governance/checklist/README.md#csr_check_gen_automated
+[TB_GEN_AUTOMATED]:                   ../../../../doc/project_governance/checklist/README.md#tb_gen_automated
+[SIM_SMOKE_TEST_PASSING]:             ../../../../doc/project_governance/checklist/README.md#sim_smoke_test_passing
+[SIM_CSR_MEM_TEST_SUITE_PASSING]:     ../../../../doc/project_governance/checklist/README.md#sim_csr_mem_test_suite_passing
+[FPV_MAIN_ASSERTIONS_PROVEN]:         ../../../../doc/project_governance/checklist/README.md#fpv_main_assertions_proven
+[SIM_ALT_TOOL_SETUP]:                 ../../../../doc/project_governance/checklist/README.md#sim_alt_tool_setup
+[SIM_SMOKE_REGRESSION_SETUP]:         ../../../../doc/project_governance/checklist/README.md#sim_smoke_regression_setup
+[SIM_NIGHTLY_REGRESSION_SETUP]:       ../../../../doc/project_governance/checklist/README.md#sim_nightly_regression_setup
+[FPV_REGRESSION_SETUP]:               ../../../../doc/project_governance/checklist/README.md#fpv_regression_setup
+[SIM_COVERAGE_MODEL_ADDED]:           ../../../../doc/project_governance/checklist/README.md#sim_coverage_model_added
+[TB_LINT_SETUP]:                      ../../../../doc/project_governance/checklist/README.md#tb_lint_setup
+[PRE_VERIFIED_SUB_MODULES_V1]:        ../../../../doc/project_governance/checklist/README.md#pre_verified_sub_modules_v1
+[DESIGN_SPEC_REVIEWED]:               ../../../../doc/project_governance/checklist/README.md#design_spec_reviewed
+[TESTPLAN_REVIEWED]:                  ../../../../doc/project_governance/checklist/README.md#testplan_reviewed
+[STD_TEST_CATEGORIES_PLANNED]:        ../../../../doc/project_governance/checklist/README.md#std_test_categories_planned
+[V2_CHECKLIST_SCOPED]:                ../../../../doc/project_governance/checklist/README.md#v2_checklist_scoped
 
 ### V2
 
@@ -201,26 +199,26 @@
 Review        | [DV_DOC_TESTPLAN_REVIEWED][]            | Done |
 Review        | [V3_CHECKLIST_SCOPED][]                 | Done |
 
-[DESIGN_DELTAS_CAPTURED_V2]:          {{<relref "/doc/project/checklist.md#design_deltas_captured_v2" >}}
-[DV_DOC_COMPLETED]:                   {{<relref "/doc/project/checklist.md#dv_doc_completed" >}}
-[FUNCTIONAL_COVERAGE_IMPLEMENTED]:    {{<relref "/doc/project/checklist.md#functional_coverage_implemented" >}}
-[ALL_INTERFACES_EXERCISED]:           {{<relref "/doc/project/checklist.md#all_interfaces_exercised" >}}
-[ALL_ASSERTION_CHECKS_ADDED]:         {{<relref "/doc/project/checklist.md#all_assertion_checks_added" >}}
-[SIM_TB_ENV_COMPLETED]:               {{<relref "/doc/project/checklist.md#sim_tb_env_completed" >}}
-[SIM_ALL_TESTS_PASSING]:              {{<relref "/doc/project/checklist.md#sim_all_tests_passing" >}}
-[FPV_ALL_ASSERTIONS_WRITTEN]:         {{<relref "/doc/project/checklist.md#fpv_all_assertions_written" >}}
-[FPV_ALL_ASSUMPTIONS_REVIEWED]:       {{<relref "/doc/project/checklist.md#fpv_all_assumptions_reviewed" >}}
-[SIM_FW_SIMULATED]:                   {{<relref "/doc/project/checklist.md#sim_fw_simulated" >}}
-[SIM_NIGHTLY_REGRESSION_V2]:          {{<relref "/doc/project/checklist.md#sim_nightly_regression_v2" >}}
-[SIM_CODE_COVERAGE_V2]:               {{<relref "/doc/project/checklist.md#sim_code_coverage_v2" >}}
-[SIM_FUNCTIONAL_COVERAGE_V2]:         {{<relref "/doc/project/checklist.md#sim_functional_coverage_v2" >}}
-[FPV_CODE_COVERAGE_V2]:               {{<relref "/doc/project/checklist.md#fpv_code_coverage_v2" >}}
-[FPV_COI_COVERAGE_V2]:                {{<relref "/doc/project/checklist.md#fpv_coi_coverage_v2" >}}
-[PRE_VERIFIED_SUB_MODULES_V2]:        {{<relref "/doc/project/checklist.md#pre_verified_sub_modules_v2" >}}
-[NO_HIGH_PRIORITY_ISSUES_PENDING]:    {{<relref "/doc/project/checklist.md#no_high_priority_issues_pending" >}}
-[ALL_LOW_PRIORITY_ISSUES_ROOT_CAUSED]:{{<relref "/doc/project/checklist.md#all_low_priority_issues_root_caused" >}}
-[DV_DOC_TESTPLAN_REVIEWED]:           {{<relref "/doc/project/checklist.md#dv_doc_testplan_reviewed" >}}
-[V3_CHECKLIST_SCOPED]:                {{<relref "/doc/project/checklist.md#v3_checklist_scoped" >}}
+[DESIGN_DELTAS_CAPTURED_V2]:          ../../../../doc/project_governance/checklist/README.md#design_deltas_captured_v2
+[DV_DOC_COMPLETED]:                   ../../../../doc/project_governance/checklist/README.md#dv_doc_completed
+[FUNCTIONAL_COVERAGE_IMPLEMENTED]:    ../../../../doc/project_governance/checklist/README.md#functional_coverage_implemented
+[ALL_INTERFACES_EXERCISED]:           ../../../../doc/project_governance/checklist/README.md#all_interfaces_exercised
+[ALL_ASSERTION_CHECKS_ADDED]:         ../../../../doc/project_governance/checklist/README.md#all_assertion_checks_added
+[SIM_TB_ENV_COMPLETED]:               ../../../../doc/project_governance/checklist/README.md#sim_tb_env_completed
+[SIM_ALL_TESTS_PASSING]:              ../../../../doc/project_governance/checklist/README.md#sim_all_tests_passing
+[FPV_ALL_ASSERTIONS_WRITTEN]:         ../../../../doc/project_governance/checklist/README.md#fpv_all_assertions_written
+[FPV_ALL_ASSUMPTIONS_REVIEWED]:       ../../../../doc/project_governance/checklist/README.md#fpv_all_assumptions_reviewed
+[SIM_FW_SIMULATED]:                   ../../../../doc/project_governance/checklist/README.md#sim_fw_simulated
+[SIM_NIGHTLY_REGRESSION_V2]:          ../../../../doc/project_governance/checklist/README.md#sim_nightly_regression_v2
+[SIM_CODE_COVERAGE_V2]:               ../../../../doc/project_governance/checklist/README.md#sim_code_coverage_v2
+[SIM_FUNCTIONAL_COVERAGE_V2]:         ../../../../doc/project_governance/checklist/README.md#sim_functional_coverage_v2
+[FPV_CODE_COVERAGE_V2]:               ../../../../doc/project_governance/checklist/README.md#fpv_code_coverage_v2
+[FPV_COI_COVERAGE_V2]:                ../../../../doc/project_governance/checklist/README.md#fpv_coi_coverage_v2
+[PRE_VERIFIED_SUB_MODULES_V2]:        ../../../../doc/project_governance/checklist/README.md#pre_verified_sub_modules_v2
+[NO_HIGH_PRIORITY_ISSUES_PENDING]:    ../../../../doc/project_governance/checklist/README.md#no_high_priority_issues_pending
+[ALL_LOW_PRIORITY_ISSUES_ROOT_CAUSED]:../../../../doc/project_governance/checklist/README.md#all_low_priority_issues_root_caused
+[DV_DOC_TESTPLAN_REVIEWED]:           ../../../../doc/project_governance/checklist/README.md#dv_doc_testplan_reviewed
+[V3_CHECKLIST_SCOPED]:                ../../../../doc/project_governance/checklist/README.md#v3_checklist_scoped
 
 ### V2S
 
@@ -232,11 +230,11 @@
 Coverage      | [SIM_COVERAGE_REVIEWED][]               | Done |